From 6974cf8ca643fcea25942ec26b192de27de47fe8 Mon Sep 17 00:00:00 2001 From: Ravi <7014230+arelra@users.noreply.github.com> Date: Fri, 11 Sep 2026 21:06:03 +0100 Subject: [PATCH 1/8] Migrate first batch of unit tests to Node's test runner Co-authored-by: GPT-5.6 Sol --- dotcom-rendering/jest.config.js | 1 + dotcom-rendering/package.json | 1 + .../src/lib/age-warning.node.test.ts | 81 ++++++++ dotcom-rendering/src/lib/age-warning.test.ts | 54 ----- .../src/lib/alternate-lang-links.node.test.ts | 112 ++++++++++ .../src/lib/alternate-lang-links.test.ts | 99 --------- .../src/lib/canRenderAds.node.test.ts | 27 +++ dotcom-rendering/src/lib/canRenderAds.test.ts | 25 --- .../src/lib/formatAttrString.node.test.ts | 32 +++ .../src/lib/formatAttrString.test.ts | 30 --- .../src/lib/formatCount.node.test.ts | 21 ++ dotcom-rendering/src/lib/formatCount.test.ts | 19 -- .../src/lib/getZIndex.node.test.ts | 21 ++ dotcom-rendering/src/lib/getZIndex.test.ts | 27 --- dotcom-rendering/src/lib/isLight.node.test.ts | 54 +++++ dotcom-rendering/src/lib/isLight.test.ts | 40 ---- .../src/lib/isValidUrl.node.test.ts | 38 ++++ dotcom-rendering/src/lib/isValidUrl.test.ts | 35 ---- ....ts => linkNotificationCount.node.test.ts} | 16 +- ...tring.test.ts => querystring.node.test.ts} | 8 +- dotcom-rendering/src/lib/result.node.test.ts | 196 ++++++++++++++++++ dotcom-rendering/src/lib/result.test.ts | 185 ----------------- .../src/lib/transparentColour.node.test.ts | 52 +++++ .../src/lib/transparentColour.test.ts | 28 --- dotcom-rendering/src/lib/tuple.node.test.ts | 66 ++++++ dotcom-rendering/src/lib/tuple.test.ts | 89 -------- 26 files changed, 714 insertions(+), 643 deletions(-) create mode 100644 dotcom-rendering/src/lib/age-warning.node.test.ts delete mode 100644 dotcom-rendering/src/lib/age-warning.test.ts create mode 100644 dotcom-rendering/src/lib/alternate-lang-links.node.test.ts delete mode 100644 dotcom-rendering/src/lib/alternate-lang-links.test.ts create mode 100644 dotcom-rendering/src/lib/canRenderAds.node.test.ts delete mode 100644 dotcom-rendering/src/lib/canRenderAds.test.ts create mode 100644 dotcom-rendering/src/lib/formatAttrString.node.test.ts delete mode 100644 dotcom-rendering/src/lib/formatAttrString.test.ts create mode 100644 dotcom-rendering/src/lib/formatCount.node.test.ts delete mode 100644 dotcom-rendering/src/lib/formatCount.test.ts create mode 100644 dotcom-rendering/src/lib/getZIndex.node.test.ts delete mode 100644 dotcom-rendering/src/lib/getZIndex.test.ts create mode 100644 dotcom-rendering/src/lib/isLight.node.test.ts delete mode 100644 dotcom-rendering/src/lib/isLight.test.ts create mode 100644 dotcom-rendering/src/lib/isValidUrl.node.test.ts delete mode 100644 dotcom-rendering/src/lib/isValidUrl.test.ts rename dotcom-rendering/src/lib/{linkNotificationCount.test.ts => linkNotificationCount.node.test.ts} (73%) rename dotcom-rendering/src/lib/{querystring.test.ts => querystring.node.test.ts} (59%) create mode 100644 dotcom-rendering/src/lib/result.node.test.ts delete mode 100644 dotcom-rendering/src/lib/result.test.ts create mode 100644 dotcom-rendering/src/lib/transparentColour.node.test.ts delete mode 100644 dotcom-rendering/src/lib/transparentColour.test.ts create mode 100644 dotcom-rendering/src/lib/tuple.node.test.ts delete mode 100644 dotcom-rendering/src/lib/tuple.test.ts diff --git a/dotcom-rendering/jest.config.js b/dotcom-rendering/jest.config.js index 5257a547f9c..02fd5aad4d6 100644 --- a/dotcom-rendering/jest.config.js +++ b/dotcom-rendering/jest.config.js @@ -23,6 +23,7 @@ module.exports = { '^.+\\.(mjs|js|ts|tsx)$': ['@swc/jest', swcConfig], }, testMatch: ['**/*.test.+(ts|tsx|js)'], + testPathIgnorePatterns: ['\\.node\\.test\\.'], setupFilesAfterEnv: ['/scripts/jest/setup.ts'], moduleNameMapper: { '^svgs/(.*)$': '/__mocks__/svgMock.tsx', diff --git a/dotcom-rendering/package.json b/dotcom-rendering/package.json index 2cfea960681..3fd983c6d01 100644 --- a/dotcom-rendering/package.json +++ b/dotcom-rendering/package.json @@ -10,6 +10,7 @@ "lint:stats": "pnpm lint --format node_modules/eslint-stats/byError.js", "tsc": "tsc", "test": "IMAGE_DIGEST=sha256:12345 jest --maxWorkers=50%", + "test:node": "IMAGE_DIGEST=sha256:12345 node --import=tsx --test \"src/**/*.node.test.ts\"", "test:watch": "IMAGE_DIGEST=sha256:12345 jest --watch --maxWorkers=25%", "test:ci": "IMAGE_DIGEST=sha256:12345 jest --runInBand", "playwright:open": "playwright test --ui", diff --git a/dotcom-rendering/src/lib/age-warning.node.test.ts b/dotcom-rendering/src/lib/age-warning.node.test.ts new file mode 100644 index 00000000000..92a3f01f7bf --- /dev/null +++ b/dotcom-rendering/src/lib/age-warning.node.test.ts @@ -0,0 +1,81 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import type { TagType } from '../types/tag'; +import { getAgeWarning } from './age-warning'; + +void nodeDescribe('getAgeWarning', () => { + const infoTag: TagType = { + id: 'info/info', + type: 'info', + title: 'info', + }; + const studentsTag: TagType = { + id: 'education/students', + type: 'topic', + title: 'info', + }; + + const today = new Date(); + const oneMonthOld = new Date( + new Date().setDate(today.getDate() - 31), + ).toDateString(); + const twoMonthsOld = new Date( + new Date().setDate(today.getDate() - 65), + ).toDateString(); + const oneYearOld = new Date( + new Date().setDate(today.getDate() - 370), + ).toDateString(); + const twoYearsOld = new Date( + new Date().setDate(today.getDate() - 750), + ).toDateString(); + + void nodeIt( + 'shows age warning when publication date is more than 1 month ago', + () => { + assert.equal( + getAgeWarning([studentsTag], oneMonthOld), + '1 month old', + ); + }, + ); + + void nodeIt( + 'shows age warning when publication date is more than 2 months ago', + () => { + assert.equal( + getAgeWarning([studentsTag], twoMonthsOld), + '2 months old', + ); + }, + ); + + void nodeIt( + 'shows age warning when publication date is more than 1 year ago', + () => { + assert.equal( + getAgeWarning([studentsTag], oneYearOld), + '1 year old', + ); + }, + ); + + void nodeIt( + 'shows age warning when publication date is more than 2 years ago', + () => { + assert.equal( + getAgeWarning([studentsTag], twoYearsOld), + '2 years old', + ); + }, + ); + + void nodeIt( + 'is undefined if one of the tags is excluded from age warning', + () => { + assert.equal( + getAgeWarning([studentsTag, infoTag], oneMonthOld), + undefined, + ); + }, + ); +}); diff --git a/dotcom-rendering/src/lib/age-warning.test.ts b/dotcom-rendering/src/lib/age-warning.test.ts deleted file mode 100644 index 277532da78b..00000000000 --- a/dotcom-rendering/src/lib/age-warning.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { TagType } from '../types/tag'; -import { getAgeWarning } from './age-warning'; - -describe('getAgeWarning', () => { - const infoTag: TagType = { - id: 'info/info', - type: 'info', - title: 'info', - }; - const studentsTag: TagType = { - id: 'education/students', - type: 'topic', - title: 'info', - }; - - const today = new Date(); - const oneMonthOld = new Date( - new Date().setDate(today.getDate() - 31), - ).toDateString(); - const twoMonthsOld = new Date( - new Date().setDate(today.getDate() - 65), - ).toDateString(); - const oneYearOld = new Date( - new Date().setDate(today.getDate() - 370), - ).toDateString(); - const twoYearsOld = new Date( - new Date().setDate(today.getDate() - 750), - ).toDateString(); - - it('shows age warning when publication date is more than 1 month ago', () => { - const testTags = [studentsTag]; - expect(getAgeWarning(testTags, oneMonthOld)).toEqual('1 month old'); - }); - - it('shows age warning when publication date is more than 2 months ago', () => { - const testTags = [studentsTag]; - expect(getAgeWarning(testTags, twoMonthsOld)).toEqual('2 months old'); - }); - - it('shows age warning when publication date is more than 1 year ago', () => { - const testTags = [studentsTag]; - expect(getAgeWarning(testTags, oneYearOld)).toEqual('1 year old'); - }); - - it('shows age warning when publication date is more than 2 years ago', () => { - const testTags = [studentsTag]; - expect(getAgeWarning(testTags, twoYearsOld)).toEqual('2 years old'); - }); - - it('is undefined if one of the tags is excluded from age warning', () => { - const testTags = [studentsTag, infoTag]; - expect(getAgeWarning(testTags, oneMonthOld)).toEqual(undefined); - }); -}); diff --git a/dotcom-rendering/src/lib/alternate-lang-links.node.test.ts b/dotcom-rendering/src/lib/alternate-lang-links.node.test.ts new file mode 100644 index 00000000000..0b0cee78198 --- /dev/null +++ b/dotcom-rendering/src/lib/alternate-lang-links.node.test.ts @@ -0,0 +1,112 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { generateAlternateLangLinks } from './alternate-lang-links'; +import { editionalisedPages, editionList } from './edition'; + +const everyEditionWithLangLocale = editionList.filter( + (edition) => edition.langLocale, +); + +const everyEditionWithNoLangLocale = editionList.filter( + (edition) => !edition.langLocale, +); + +const everyEditionWithEditionalisedPages = editionList + .filter((edition) => edition.hasEditionalisedPages) + .flatMap((edition) => + editionalisedPages.map((page) => `${edition.pageId}/${page}`), + ); + +const everyEditionWithNoEditionalisedPages = editionList + .filter((edition) => !edition.hasEditionalisedPages) + .flatMap((edition) => + editionalisedPages.map((page) => `${edition.pageId}/${page}`), + ); + +void nodeDescribe('alternate lang links', () => { + void nodeIt( + 'generate hreflang links for network fronts with a lang locale', + () => { + for (const edition of everyEditionWithLangLocale) { + const langLinks = generateAlternateLangLinks( + 'https://www.theguardian.com', + edition.pageId, + ); + assert.deepEqual(langLinks, [ + '', + '', + '', + '', + '', + ]); + } + }, + ); + + void nodeIt( + 'do NOT generate hreflang links for network fronts with NO lang locale', + () => { + for (const edition of everyEditionWithNoLangLocale) { + assert.deepEqual( + generateAlternateLangLinks( + 'https://www.theguardian.com', + edition.pageId, + ), + [], + ); + } + }, + ); + + void nodeIt('generate hreflang links for editionalised pages', () => { + for (const pageId of everyEditionWithEditionalisedPages) { + const langLinks = generateAlternateLangLinks( + 'https://www.theguardian.com', + pageId, + ); + const pageIdSuffix = pageId.split('/')[1] ?? ''; + assert.deepEqual(langLinks, [ + ``, + ``, + ``, + ]); + } + }); + + void nodeIt( + 'do NOT generate hreflang links for editions with NO editionalised pages', + () => { + for (const pageId of everyEditionWithNoEditionalisedPages) { + assert.deepEqual( + generateAlternateLangLinks( + 'https://www.theguardian.com', + pageId, + ), + [], + ); + } + }, + ); + + void nodeIt( + 'do NOT generate hreflang links for NON editionalised pages', + () => { + const pageIdsNotEditionalisedPages = [ + 'uk/something', + 'us/something', + 'au/something', + 'international/something', + 'uk/business/something', + ]; + for (const pageId of pageIdsNotEditionalisedPages) { + assert.deepEqual( + generateAlternateLangLinks( + 'https://www.theguardian.com', + pageId, + ), + [], + ); + } + }, + ); +}); diff --git a/dotcom-rendering/src/lib/alternate-lang-links.test.ts b/dotcom-rendering/src/lib/alternate-lang-links.test.ts deleted file mode 100644 index e23db28bb89..00000000000 --- a/dotcom-rendering/src/lib/alternate-lang-links.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { generateAlternateLangLinks } from './alternate-lang-links'; -import { editionalisedPages, editionList } from './edition'; - -const everyEditionWithLangLocale = editionList.filter( - (edition) => edition.langLocale, -); - -const everyEditionWithNoLangLocale = editionList.filter( - (edition) => !edition.langLocale, -); - -const everyEditionWithEditionalisedPages = editionList - .filter((edition) => edition.hasEditionalisedPages) - .map((edition) => - editionalisedPages.map((page) => `${edition.pageId}/${page}`), - ) - .flat(); - -// This is an invalid combination e.g. europe/travel -const everyEditionWithNoEditionalisedPages = editionList - .filter((edition) => !edition.hasEditionalisedPages) - .map((edition) => - editionalisedPages.map((page) => `${edition.pageId}/${page}`), - ) - .flat(); - -describe('alternate lang links', () => { - it('generate hreflang links for network fronts with a lang locale', () => { - const langLinksForEditionsWithLangLocale = - everyEditionWithLangLocale.map((edition) => { - return generateAlternateLangLinks( - 'https://www.theguardian.com', - edition.pageId, - ); - }); - for (const langLinks of langLinksForEditionsWithLangLocale) { - expect(langLinks.length).toBe(5); - expect(langLinks).toEqual([ - '', - '', - '', - '', - '', - ]); - } - }); - it('do NOT generate hreflang links for network fronts with NO lang locale', () => { - const langLinksForEditionsWithNoLangLocale = - everyEditionWithNoLangLocale.map((edition) => { - return generateAlternateLangLinks( - 'https://www.theguardian.com', - edition.pageId, - ); - }); - for (const langLinks of langLinksForEditionsWithNoLangLocale) { - expect(langLinks.length).toBe(0); - } - }); - it('generate hreflang links for editionalised pages', () => { - for (const pageId of everyEditionWithEditionalisedPages) { - const langLinks = generateAlternateLangLinks( - 'https://www.theguardian.com', - pageId, - ); - const pageIdSuffix = pageId.split('/')[1] ?? ''; - expect(langLinks.length).toBe(3); - expect(langLinks).toEqual([ - ``, - ``, - ``, - ]); - } - }); - it('do NOT generate hreflang links for editions with NO editionalised pages', () => { - for (const pageId of everyEditionWithNoEditionalisedPages) { - const langLinks = generateAlternateLangLinks( - 'https://www.theguardian.com', - pageId, - ); - expect(langLinks.length).toBe(0); - } - }); - it('do NOT generate hreflang links for NON editionalised pages', () => { - const pageIdsNotEditionalisedPages = [ - 'uk/something', - 'us/something', - 'au/something', - 'international/something', - 'uk/business/something', - ]; - for (const pageId of pageIdsNotEditionalisedPages) { - const langLinks = generateAlternateLangLinks( - 'https://www.theguardian.com', - pageId, - ); - expect(langLinks.length).toBe(0); - } - }); -}); diff --git a/dotcom-rendering/src/lib/canRenderAds.node.test.ts b/dotcom-rendering/src/lib/canRenderAds.node.test.ts new file mode 100644 index 00000000000..724ecbd173d --- /dev/null +++ b/dotcom-rendering/src/lib/canRenderAds.node.test.ts @@ -0,0 +1,27 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { Standard } from '../../fixtures/generated/fe-articles/Standard'; +import { enhanceArticleType } from '../types/article'; +import { canRenderAds } from './canRenderAds'; + +const standardPage = enhanceArticleType(Standard, 'Web'); + +void nodeDescribe('canRenderAds', () => { + void nodeIt('shows ads by default', () => { + assert.equal(canRenderAds(standardPage.frontendData), true); + }); + + void nodeIt('does not show ads if user is ad-free', () => { + const adFreePage = Object.assign({}, standardPage.frontendData); + adFreePage.isAdFreeUser = true; + + assert.equal(canRenderAds(adFreePage), false); + }); + + void nodeIt('does not show ads if page should not display them', () => { + const adFreePage = Object.assign({}, standardPage.frontendData); + adFreePage.shouldHideAds = true; + + assert.equal(canRenderAds(adFreePage), false); + }); +}); diff --git a/dotcom-rendering/src/lib/canRenderAds.test.ts b/dotcom-rendering/src/lib/canRenderAds.test.ts deleted file mode 100644 index ddf11a270fa..00000000000 --- a/dotcom-rendering/src/lib/canRenderAds.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Standard } from '../../fixtures/generated/fe-articles/Standard'; -import { enhanceArticleType } from '../types/article'; -import { canRenderAds } from './canRenderAds'; - -const standardPage = enhanceArticleType(Standard, 'Web'); - -describe('canRenderAds', () => { - it('shows ads by default', () => { - expect(canRenderAds(standardPage.frontendData)).toBe(true); - }); - - it('does not show ads if user is ad-free', () => { - const adFreePage = Object.assign({}, standardPage.frontendData); - adFreePage.isAdFreeUser = true; - - expect(canRenderAds(adFreePage)).toBe(false); - }); - - it('does not show ads if page should not display them', () => { - const adFreePage = Object.assign({}, standardPage.frontendData); - adFreePage.shouldHideAds = true; - - expect(canRenderAds(adFreePage)).toBe(false); - }); -}); diff --git a/dotcom-rendering/src/lib/formatAttrString.node.test.ts b/dotcom-rendering/src/lib/formatAttrString.node.test.ts new file mode 100644 index 00000000000..b23aec41682 --- /dev/null +++ b/dotcom-rendering/src/lib/formatAttrString.node.test.ts @@ -0,0 +1,32 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { formatAttrString } from './formatAttrString'; + +const expectedOutput = 'this-headline-should-be-converted'; + +void nodeDescribe('formatAttrString', () => { + void nodeIt('Lowercases all', () => { + const input = 'This Headline Should Be Converted'; + assert.equal(formatAttrString(input), expectedOutput); + }); + + void nodeIt('Converts spaces to hyphens', () => { + const input = 'this headline should be converted'; + assert.equal(formatAttrString(input), expectedOutput); + }); + + void nodeIt('Removes anything but spaces and letters', () => { + const input = '/this headline should be converted.'; + assert.equal(formatAttrString(input), expectedOutput); + }); + + void nodeIt('Does not remove numbers', () => { + const input = 'this headline should be converted 12'; + assert.equal(formatAttrString(input), `${expectedOutput}-12`); + }); + + void nodeIt('Puts it all together', () => { + const input = '/this Headline should be converted. 12'; + assert.equal(formatAttrString(input), `${expectedOutput}-12`); + }); +}); diff --git a/dotcom-rendering/src/lib/formatAttrString.test.ts b/dotcom-rendering/src/lib/formatAttrString.test.ts deleted file mode 100644 index 726778855f4..00000000000 --- a/dotcom-rendering/src/lib/formatAttrString.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { formatAttrString } from './formatAttrString'; - -const expectedOutput = 'this-headline-should-be-converted'; - -describe('formatAttrString', () => { - it('Lowercases all', () => { - const input = 'This Headline Should Be Converted'; - expect(formatAttrString(input)).toBe(expectedOutput); - }); - - it('Converts spaces to hyphens', () => { - const input = 'this headline should be converted'; - expect(formatAttrString(input)).toBe(expectedOutput); - }); - - it('Removes anything but spaces and letters', () => { - const input = '/this headline should be converted.'; - expect(formatAttrString(input)).toBe(expectedOutput); - }); - - it('Does not remove numbers', () => { - const input = 'this headline should be converted 12'; - expect(formatAttrString(input)).toBe(`${expectedOutput}-12`); - }); - - it('Puts it all together', () => { - const input = '/this Headline should be converted. 12'; - expect(formatAttrString(input)).toBe(`${expectedOutput}-12`); - }); -}); diff --git a/dotcom-rendering/src/lib/formatCount.node.test.ts b/dotcom-rendering/src/lib/formatCount.node.test.ts new file mode 100644 index 00000000000..6ca7b7e734e --- /dev/null +++ b/dotcom-rendering/src/lib/formatCount.node.test.ts @@ -0,0 +1,21 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { formatCount } from './formatCount'; + +void nodeDescribe('formatCount', () => { + void nodeIt('formats simple numbers', () => { + assert.deepEqual(formatCount(123), { short: '123', long: '123' }); + }); + void nodeIt('formats medium numbers', () => { + assert.deepEqual(formatCount(9876), { short: '9876', long: '9,876' }); + }); + void nodeIt('formats very long numbers', () => { + assert.deepEqual(formatCount(92878), { short: '93k', long: '92,878' }); + }); + void nodeIt('returns zero for zero', () => { + assert.deepEqual(formatCount(0), { short: '0', long: '0' }); + }); + void nodeIt('returns an ellipsis for undefined', () => { + assert.deepEqual(formatCount(), { short: '…', long: '…' }); + }); +}); diff --git a/dotcom-rendering/src/lib/formatCount.test.ts b/dotcom-rendering/src/lib/formatCount.test.ts deleted file mode 100644 index f16401b2823..00000000000 --- a/dotcom-rendering/src/lib/formatCount.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { formatCount } from './formatCount'; - -describe('formatCount', () => { - it('formats simple numbers', () => { - expect(formatCount(123)).toEqual({ short: '123', long: '123' }); - }); - it('formats medium numbers', () => { - expect(formatCount(9876)).toEqual({ short: '9876', long: '9,876' }); - }); - it('formats very long numbers', () => { - expect(formatCount(92878)).toEqual({ short: '93k', long: '92,878' }); - }); - it('returns zero for zero', () => { - expect(formatCount(0)).toEqual({ short: '0', long: '0' }); - }); - it('returns an ellipsis for undefined', () => { - expect(formatCount()).toEqual({ short: '…', long: '…' }); - }); -}); diff --git a/dotcom-rendering/src/lib/getZIndex.node.test.ts b/dotcom-rendering/src/lib/getZIndex.node.test.ts new file mode 100644 index 00000000000..b613caa010f --- /dev/null +++ b/dotcom-rendering/src/lib/getZIndex.node.test.ts @@ -0,0 +1,21 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { getZIndex } from './getZIndex'; + +void nodeDescribe('getZIndex', () => { + void nodeIt('gets the correct zindex for group and sibling', () => { + assert.ok(getZIndex('sticky-video-button') > getZIndex('sticky-video')); + assert.ok( + getZIndex('expanded-veggie-menu-wrapper') > + getZIndex('expanded-veggie-menu'), + ); + assert.ok( + getZIndex('stickyAdWrapperLabsHeader') > + getZIndex('stickyAdWrapper'), + ); + assert.ok(getZIndex('tableOfContents') > getZIndex('articleHeadline')); + assert.ok(getZIndex('subNavBanner') > getZIndex('articleHeadline')); + assert.ok(getZIndex('subNavBanner') > getZIndex('bodyArea')); + assert.ok(getZIndex('card-nested-link') > getZIndex('card-link')); + }); +}); diff --git a/dotcom-rendering/src/lib/getZIndex.test.ts b/dotcom-rendering/src/lib/getZIndex.test.ts deleted file mode 100644 index 78bb441772c..00000000000 --- a/dotcom-rendering/src/lib/getZIndex.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { getZIndex } from './getZIndex'; - -describe('getZIndex', () => { - it('gets the correct zindex for group and sibling', () => { - expect(getZIndex('sticky-video-button')).toBeGreaterThan( - getZIndex('sticky-video'), - ); - expect(getZIndex('expanded-veggie-menu-wrapper')).toBeGreaterThan( - getZIndex('expanded-veggie-menu'), - ); - expect(getZIndex('stickyAdWrapperLabsHeader')).toBeGreaterThan( - getZIndex('stickyAdWrapper'), - ); - expect(getZIndex('tableOfContents')).toBeGreaterThan( - getZIndex('articleHeadline'), - ); - expect(getZIndex('subNavBanner')).toBeGreaterThan( - getZIndex('articleHeadline'), - ); - expect(getZIndex('subNavBanner')).toBeGreaterThan( - getZIndex('bodyArea'), - ); - expect(getZIndex('card-nested-link')).toBeGreaterThan( - getZIndex('card-link'), - ); - }); -}); diff --git a/dotcom-rendering/src/lib/isLight.node.test.ts b/dotcom-rendering/src/lib/isLight.node.test.ts new file mode 100644 index 00000000000..72e13edb4e3 --- /dev/null +++ b/dotcom-rendering/src/lib/isLight.node.test.ts @@ -0,0 +1,54 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { isLight } from './isLight'; + +void nodeDescribe('isLight', () => { + void nodeIt( + 'should return the correct response for dark hex colours', + () => { + for (const colour of [ + '#791a4e', + '#644f4e', + '#7f4e2a', + '#aa365e', + '#5e387c', + '#87656e', + '#223cdd', + '#555eee', + '#334cde', + '#b54bbb', + ]) { + assert.equal(isLight(colour), false); + } + }, + ); + + void nodeIt( + 'should return the correct response for light hex colours', + () => { + for (const colour of ['#ea3eee', '#97dc45', '#7ec621', '#54dbb6']) { + assert.equal(isLight(colour), true); + } + }, + ); + + void nodeIt( + 'should return the correct response for 3 digit hex colours', + () => { + assert.equal(isLight('#f4e'), true); + assert.equal(isLight('#fff'), true); + assert.equal(isLight('#999'), true); + assert.equal(isLight('#64e'), false); + assert.equal(isLight('#000'), false); + }, + ); + + void nodeIt('should handle if the # is missing', () => { + assert.equal(isLight('97dc45'), true); + assert.equal(isLight('000'), false); + }); + + void nodeIt('should handle if the colour string is invalid', () => { + assert.equal(isLight('wyx'), false); + }); +}); diff --git a/dotcom-rendering/src/lib/isLight.test.ts b/dotcom-rendering/src/lib/isLight.test.ts deleted file mode 100644 index 4e97490462c..00000000000 --- a/dotcom-rendering/src/lib/isLight.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { isLight } from './isLight'; - -describe('isLight', () => { - it('should return the correct response for dark hex colours', () => { - expect(isLight('#791a4e')).toBe(false); - expect(isLight('#644f4e')).toBe(false); - expect(isLight('#7f4e2a')).toBe(false); - expect(isLight('#aa365e')).toBe(false); - expect(isLight('#5e387c')).toBe(false); - expect(isLight('#87656e')).toBe(false); - expect(isLight('#223cdd')).toBe(false); - expect(isLight('#555eee')).toBe(false); - expect(isLight('#334cde')).toBe(false); - expect(isLight('#b54bbb')).toBe(false); - }); - - it('should return the correct response for light hex colours', () => { - expect(isLight('#ea3eee')).toBe(true); - expect(isLight('#97dc45')).toBe(true); - expect(isLight('#7ec621')).toBe(true); - expect(isLight('#54dbb6')).toBe(true); - }); - - it('should return the correct response for 3 digit hex colours', () => { - expect(isLight('#f4e')).toBe(true); - expect(isLight('#fff')).toBe(true); - expect(isLight('#999')).toBe(true); - expect(isLight('#64e')).toBe(false); - expect(isLight('#000')).toBe(false); - }); - - it('should handle if the # is missing', () => { - expect(isLight('97dc45')).toBe(true); - expect(isLight('000')).toBe(false); - }); - - it('should handle if the colour string is invalid', () => { - expect(isLight('wyx')).toBe(false); - }); -}); diff --git a/dotcom-rendering/src/lib/isValidUrl.node.test.ts b/dotcom-rendering/src/lib/isValidUrl.node.test.ts new file mode 100644 index 00000000000..863e8a78b0c --- /dev/null +++ b/dotcom-rendering/src/lib/isValidUrl.node.test.ts @@ -0,0 +1,38 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { isValidUrl } from './isValidUrl'; + +void nodeDescribe('isValidUrl', () => { + void nodeDescribe('invalidInputs', () => { + const invalidInputs = [ + '', + 'guardian.co', + 'anemailaddress@company.com', + 'com/hello?athing=1&anotherthing=%20', + 'https://guardian.co.uk withASpace', + ]; + + for (const input of invalidInputs) { + void nodeIt( + `returns false for invalid input of \`${input}\``, + () => { + assert.equal(isValidUrl(input), false); + }, + ); + } + }); + + void nodeDescribe('validInputs', () => { + const validInputs = [ + 'https://guardian.co.uk/australia-news/series/guardian-australia-s-morning-mail', + 'https://regexr.com/39nr7', + 'http://www.google.com/hello?athing=1&anotherthing=%20', + ]; + + for (const input of validInputs) { + void nodeIt(`returns true for valid input of \`${input}\``, () => { + assert.equal(isValidUrl(input), true); + }); + } + }); +}); diff --git a/dotcom-rendering/src/lib/isValidUrl.test.ts b/dotcom-rendering/src/lib/isValidUrl.test.ts deleted file mode 100644 index e67eac86dd4..00000000000 --- a/dotcom-rendering/src/lib/isValidUrl.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { isValidUrl } from './isValidUrl'; - -describe('isValidUrl', () => { - describe('invalidInputs', () => { - const invalidInputs = [ - '', - 'guardian.co', - 'anemailaddress@company.com', - 'com/hello?athing=1&anotherthing=%20', - 'https://guardian.co.uk withASpace', - ]; - - it.each(invalidInputs)( - 'returns false for invalid input of `%s`', - (input) => { - expect(isValidUrl(input)).toBeFalsy(); - }, - ); - }); - - describe('validInputs', () => { - const validInputs = [ - 'https://guardian.co.uk/australia-news/series/guardian-australia-s-morning-mail', - 'https://regexr.com/39nr7', - 'http://www.google.com/hello?athing=1&anotherthing=%20', - ]; - - it.each(validInputs)( - 'returns true for valid input of `%s`', - (input) => { - expect(isValidUrl(input)).toBeTruthy(); - }, - ); - }); -}); diff --git a/dotcom-rendering/src/lib/linkNotificationCount.test.ts b/dotcom-rendering/src/lib/linkNotificationCount.node.test.ts similarity index 73% rename from dotcom-rendering/src/lib/linkNotificationCount.test.ts rename to dotcom-rendering/src/lib/linkNotificationCount.node.test.ts index 73b30391896..c374a0a389e 100644 --- a/dotcom-rendering/src/lib/linkNotificationCount.test.ts +++ b/dotcom-rendering/src/lib/linkNotificationCount.node.test.ts @@ -1,8 +1,10 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; import type { DropdownLinkType } from '../components/Dropdown.island'; import { linkNotificationCount } from './linkNotificationCount'; -describe('linksNotificationCount', () => { - it('returns the sum of notifications across all links', () => { +void nodeDescribe('linksNotificationCount', () => { + void nodeIt('returns the sum of notifications across all links', () => { const links: DropdownLinkType[] = [ { id: 'one', @@ -40,12 +42,10 @@ describe('linksNotificationCount', () => { }, ]; - const notificationCount = linkNotificationCount(links); - - expect(notificationCount).toEqual(3); + assert.equal(linkNotificationCount(links), 3); }); - it('returns 0 when there are no notifications', () => { + void nodeIt('returns 0 when there are no notifications', () => { const links = [ { id: 'one', @@ -61,8 +61,6 @@ describe('linksNotificationCount', () => { }, ]; - const notificationCount = linkNotificationCount(links); - - expect(notificationCount).toEqual(0); + assert.equal(linkNotificationCount(links), 0); }); }); diff --git a/dotcom-rendering/src/lib/querystring.test.ts b/dotcom-rendering/src/lib/querystring.node.test.ts similarity index 59% rename from dotcom-rendering/src/lib/querystring.test.ts rename to dotcom-rendering/src/lib/querystring.node.test.ts index cecfd9f2b3d..ee082a59965 100644 --- a/dotcom-rendering/src/lib/querystring.test.ts +++ b/dotcom-rendering/src/lib/querystring.node.test.ts @@ -1,7 +1,9 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; import { constructQuery } from './querystring'; -describe('constructQuery', () => { - it('constructs the correct query string from an object', () => { +void nodeDescribe('constructQuery', () => { + void nodeIt('constructs the correct query string from an object', () => { const testParams = { sens: 'f', si: 'f', @@ -16,6 +18,6 @@ describe('constructQuery', () => { a: undefined, }; const expectedQuery = `sens=f&si=f&vl=333&cc=UK&s=sport&inskin=f&ct=article&url=%2Fsport%2F2017%2Fsep%2F30%2Ftest-article&su=0&pa=f&a=undefined`; - expect(constructQuery(testParams)).toBe(expectedQuery); + assert.equal(constructQuery(testParams), expectedQuery); }); }); diff --git a/dotcom-rendering/src/lib/result.node.test.ts b/dotcom-rendering/src/lib/result.node.test.ts new file mode 100644 index 00000000000..ab4a5e5cd9c --- /dev/null +++ b/dotcom-rendering/src/lib/result.node.test.ts @@ -0,0 +1,196 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { literal, safeParse } from 'valibot'; +import { error, fromValibot, ok, type Result } from './result'; + +void nodeDescribe('ok', () => { + void nodeIt('creates an instance of Ok', () => { + const result = ok(3); + const value = result.getOrThrow('Expected an Ok'); + + assert.equal(result.ok, true); + assert.equal(value, 3); + }); +}); + +void nodeDescribe('error', () => { + void nodeIt('creates an instance of Err', () => { + const result = error('An error'); + const err = result.getErrorOrThrow('Expected an Err'); + + assert.equal(result.ok, false); + assert.equal(err, 'An error'); + }); +}); + +void nodeDescribe('flatMap', () => { + const f = (a: number): Result => ok(a + 1); + const h = (): Result => error('h error'); + + void nodeIt( + 'runs the function and unwraps the result when both Results are Ok', + () => { + const result = ok(3).flatMap(f); + const value = result.getOrThrow('Expected an Ok'); + + assert.equal(result.ok, true); + assert.equal(value, 4); + }, + ); + + void nodeIt('passes through the Err when the first Result is Err', () => { + const result = error('error message').flatMap(f); + const err = result.getErrorOrThrow('Expected an Err'); + + assert.equal(result.ok, false); + assert.equal(err, 'error message'); + }); + + void nodeIt('passes through the Err when the second Result is Err', () => { + const result = ok(3).flatMap(h); + const err = result.getErrorOrThrow('Expected an Err'); + + assert.equal(result.ok, false); + assert.equal(err, 'h error'); + }); + + void nodeIt('passes through the first Err when both are Err', () => { + const result = error('error message').flatMap(h); + const err = result.getErrorOrThrow('Expected an Err'); + + assert.equal(result.ok, false); + assert.equal(err, 'error message'); + }); + + void nodeIt('obeys left identity law', () => { + const value = 3; + + assert.deepEqual(ok(value).flatMap(f), f(value)); + }); + + void nodeIt('obeys right identity law', () => { + const result = ok(3); + + assert.deepEqual(result.flatMap(ok), result); + }); + + void nodeIt('obeys associativity law', () => { + const result = ok(3); + const g = (a: number): Result => ok(a * 3); + + assert.deepEqual( + result.flatMap(f).flatMap(g), + result.flatMap((a) => f(a).flatMap(g)), + ); + }); +}); + +void nodeDescribe('map', () => { + const f = (a: number): number => a + 1; + + void nodeIt('runs the function when Result is Ok', () => { + const result = ok(3).map(f); + const value = result.getOrThrow('Expected an Ok'); + + assert.equal(result.ok, true); + assert.equal(value, 4); + }); + + void nodeIt('passes the error through when Result is Err', () => { + const result = error('error message').map(f); + const err = result.getErrorOrThrow('Expected an Err'); + + assert.equal(result.ok, false); + assert.equal(err, 'error message'); + }); + + void nodeIt('obeys identity', () => { + const identity = (a: A): A => a; + const value = 3; + const result = ok(value); + + assert.deepEqual(result.map(identity), result); + }); + + void nodeIt('obeys composition', () => { + const g = (a: number): number => a * 3; + const result = ok(3); + + assert.deepEqual( + result.map(f).map(g), + result.map((a) => g(f(a))), + ); + }); +}); + +void nodeDescribe('mapError', () => { + const f = (err: string): string => `An error: ${err}`; + + void nodeIt('produces a new error if Err', () => { + const err = error('error details'); + + assert.deepEqual(err.mapError(f), error('An error: error details')); + }); + + void nodeIt('does nothing if Ok', () => { + const result = ok(3); + + assert.deepEqual(result.mapError(f), result); + }); +}); + +void nodeDescribe('getOrThrow', () => { + void nodeIt('gets the value if Ok', () => { + const value = ok(3).getOrThrow('Expected an Ok'); + + assert.equal(value, 3); + }); + + void nodeIt('throws if Err', () => { + const result = error('An error'); + + assert.throws( + () => result.getOrThrow('Expected an Ok'), + /Expected an Ok/, + ); + }); +}); + +void nodeDescribe('getErrorOrThrow', () => { + void nodeIt('gets the value if Err', () => { + const err = error('An error').getErrorOrThrow('Expected an Err'); + + assert.equal(err, 'An error'); + }); + + void nodeIt('throws if Ok', () => { + const result = ok(3); + + assert.throws( + () => result.getErrorOrThrow('Expected an Err'), + /Expected an Err/, + ); + }); +}); + +void nodeDescribe('fromValibot', () => { + const schema = literal('string literal'); + + void nodeIt('creates an Ok from a successful parse result', () => { + const valibotResult = safeParse(schema, 'string literal'); + + const result = fromValibot(valibotResult); + const value = result.getOrThrow('Expected an Ok'); + + assert.equal(value, 'string literal'); + }); + + void nodeIt('creates an Err from an unsuccessful parse result', () => { + const valibotResult = safeParse(schema, 'invalid literal'); + + const result = fromValibot(valibotResult); + const err = result.getErrorOrThrow('Expected an Err'); + + assert.equal(err[0].expected, '"string literal"'); + }); +}); diff --git a/dotcom-rendering/src/lib/result.test.ts b/dotcom-rendering/src/lib/result.test.ts deleted file mode 100644 index b625fc5e213..00000000000 --- a/dotcom-rendering/src/lib/result.test.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { literal, safeParse } from 'valibot'; -import { error, fromValibot, ok, type Result } from './result'; - -describe('ok', () => { - it('creates an instance of Ok', () => { - const result = ok(3); - const value = result.getOrThrow('Expected an Ok'); - - expect(result.ok).toBe(true); - expect(value).toBe(3); - }); -}); - -describe('error', () => { - it('creates an instance of Err', () => { - const result = error('An error'); - const err = result.getErrorOrThrow('Expected an Err'); - - expect(result.ok).toBe(false); - expect(err).toBe('An error'); - }); -}); - -describe('flatMap', () => { - const f = (a: number): Result => ok(a + 1); - const h = (): Result => error('h error'); - - it('runs the function and unwraps the result when both Results are Ok', () => { - const result = ok(3).flatMap(f); - const value = result.getOrThrow('Expected an Ok'); - - expect(result.ok).toBe(true); - expect(value).toBe(4); - }); - - it('passes through the Err when the first Result is Err', () => { - const result = error('error message').flatMap(f); - const err = result.getErrorOrThrow('Expected an Err'); - - expect(result.ok).toBe(false); - expect(err).toBe('error message'); - }); - - it('passes through the Err when the second Result is Err', () => { - const result = ok(3).flatMap(h); - const err = result.getErrorOrThrow('Expected an Err'); - - expect(result.ok).toBe(false); - expect(err).toBe('h error'); - }); - - it('passes through the first Err when both are Err', () => { - const result = error('error message').flatMap(h); - const err = result.getErrorOrThrow('Expected an Err'); - - expect(result.ok).toBe(false); - expect(err).toBe('error message'); - }); - - it('obeys left identity law', () => { - const value = 3; - - expect(ok(value).flatMap(f)).toEqual(f(value)); - }); - - it('obeys right identity law', () => { - const result = ok(3); - - expect(result.flatMap(ok)).toEqual(result); - }); - - it('obeys associativity law', () => { - const result = ok(3); - const g = (a: number): Result => ok(a * 3); - - expect(result.flatMap(f).flatMap(g)).toEqual( - result.flatMap((a) => f(a).flatMap(g)), - ); - }); -}); - -describe('map', () => { - const f = (a: number): number => a + 1; - - it('runs the function when Result is Ok', () => { - const result = ok(3).map(f); - const value = result.getOrThrow('Expected an Ok'); - - expect(result.ok).toBe(true); - expect(value).toBe(4); - }); - - it('passes the error through when Result is Err', () => { - const result = error('error message').map(f); - const err = result.getErrorOrThrow('Expected an Err'); - - expect(result.ok).toBe(false); - expect(err).toBe('error message'); - }); - - it('obeys identity', () => { - const identity = (a: A): A => a; - const value = 3; - const result = ok(value); - - expect(result.map(identity)).toEqual(result); - }); - - it('obeys composition', () => { - const g = (a: number): number => a * 3; - const result = ok(3); - - expect(result.map(f).map(g)).toEqual(result.map((a) => g(f(a)))); - }); -}); - -describe('mapError', () => { - const f = (e: string): string => `An error: ${e}`; - - it('produces a new error if Err', () => { - const err = error('error details'); - - expect(err.mapError(f)).toEqual(error('An error: error details')); - }); - - it('does nothing if Ok', () => { - const result = ok(3); - - expect(result.mapError(f)).toEqual(result); - }); -}); - -describe('getOrThrow', () => { - it('gets the value if Ok', () => { - const value = ok(3).getOrThrow('Expected an Ok'); - - expect(value).toBe(3); - }); - - it('throws if Err', () => { - const result = error('An error'); - - expect(() => result.getOrThrow('Expected an Ok')).toThrow( - 'Expected an Ok', - ); - }); -}); - -describe('getErrorOrThrow', () => { - it('gets the value if Err', () => { - const err = error('An error').getErrorOrThrow('Expected an Err'); - - expect(err).toBe('An error'); - }); - - it('throws if Ok', () => { - const result = ok(3); - - expect(() => result.getErrorOrThrow('Expected an Err')).toThrow( - 'Expected an Err', - ); - }); -}); - -describe('fromValibot', () => { - const schema = literal('string literal'); - - it('creates an Ok from a successful parse result', () => { - const valibotResult = safeParse(schema, 'string literal'); - - const result = fromValibot(valibotResult); - const value = result.getOrThrow('Expected an Ok'); - - expect(value).toBe('string literal'); - }); - - it('creates an Err from an unsuccessful parse result', () => { - const valibotResult = safeParse(schema, 'invalid literal'); - - const result = fromValibot(valibotResult); - const err = result.getErrorOrThrow('Expected an Err'); - - expect(err[0].expected).toBe('"string literal"'); - }); -}); diff --git a/dotcom-rendering/src/lib/transparentColour.node.test.ts b/dotcom-rendering/src/lib/transparentColour.node.test.ts new file mode 100644 index 00000000000..97dd2e8b539 --- /dev/null +++ b/dotcom-rendering/src/lib/transparentColour.node.test.ts @@ -0,0 +1,52 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { transparentColour } from './transparentColour'; + +void nodeDescribe('transparentColour', () => { + const validHexColours = [ + ['#000000', 'rgba(0, 0, 0, 0.5)'], + ['#C70000', 'rgba(199, 0, 0, 0.5)'], + ['#aabbcc', 'rgba(170, 187, 204, 0.5)'], + ['#ffffff', 'rgba(255, 255, 255, 0.5)'], + ] as const; + + for (const [hex, output] of validHexColours) { + void nodeIt(`For valid hex ${hex}, return ${output}`, () => { + assert.equal(transparentColour(hex), output); + }); + } + + const shortHexColours = [ + ['#000', 'rgba(0, 0, 0, 0.5)'], + ['#c00', 'rgba(204, 0, 0, 0.5)'], + ['#abc', 'rgba(170, 187, 204, 0.5)'], + ['#fff', 'rgba(255, 255, 255, 0.5)'], + ] as const; + + for (const [hex, output] of shortHexColours) { + void nodeIt(`For short hex ${hex}, return ${output}`, () => { + assert.equal(transparentColour(hex), output); + }); + } + + const invalidHexColours = [ + '---', + '#ab', + '#abcd', + '#gggggg', + '-ffffff', + 'rgb(0,0,0)', + ]; + + for (const hex of invalidHexColours) { + void nodeIt( + `For invalid hex ${hex}, return rgba(127, 127, 127, 0.5)`, + () => { + assert.equal( + transparentColour(hex), + 'rgba(127, 127, 127, 0.5)', + ); + }, + ); + } +}); diff --git a/dotcom-rendering/src/lib/transparentColour.test.ts b/dotcom-rendering/src/lib/transparentColour.test.ts deleted file mode 100644 index 8cb10e6be48..00000000000 --- a/dotcom-rendering/src/lib/transparentColour.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { transparentColour } from './transparentColour'; - -describe('transparentColour', () => { - test.each([ - ['#000000', 'rgba(0, 0, 0, 0.5)'], - ['#C70000', 'rgba(199, 0, 0, 0.5)'], - ['#aabbcc', 'rgba(170, 187, 204, 0.5)'], - ['#ffffff', 'rgba(255, 255, 255, 0.5)'], - ])('For valid hex %s, return %s', (hex, output) => { - expect(transparentColour(hex)).toEqual(output); - }); - - test.each([ - ['#000', 'rgba(0, 0, 0, 0.5)'], - ['#c00', 'rgba(204, 0, 0, 0.5)'], - ['#abc', 'rgba(170, 187, 204, 0.5)'], - ['#fff', 'rgba(255, 255, 255, 0.5)'], - ])('For short hex %s, return %s', (hex, output) => { - expect(transparentColour(hex)).toEqual(output); - }); - - test.each(['---', '#ab', '#abcd', '#gggggg', '-ffffff', 'rgb(0,0,0)'])( - 'For invalid hex %s, return rgba(127, 127, 127, 0.5)', - (hex) => { - expect(transparentColour(hex)).toEqual('rgba(127, 127, 127, 0.5)'); - }, - ); -}); diff --git a/dotcom-rendering/src/lib/tuple.node.test.ts b/dotcom-rendering/src/lib/tuple.node.test.ts new file mode 100644 index 00000000000..ca9d9753dfc --- /dev/null +++ b/dotcom-rendering/src/lib/tuple.node.test.ts @@ -0,0 +1,66 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { isNonEmptyArray, takeFirst } from './tuple'; + +void nodeDescribe('takeFirst', () => { + void nodeIt( + 'Always returns the correct array length when the array is one less, the same as, or one more than n', + () => { + const results = [ + // Format, from 1 to 12, cover length n - 1, n & n+1 + takeFirst([0, 1], 1), + takeFirst([0], 1), + takeFirst([], 1), + takeFirst([0, 1, 2], 2), + takeFirst([0, 1], 2), + takeFirst([0], 2), + takeFirst([0, 1, 2, 3], 3), + takeFirst([0, 1, 2], 3), + takeFirst([0, 1], 3), + takeFirst([0, 1, 2, 3, 4], 4), + takeFirst([0, 1, 2, 3], 4), + takeFirst([0, 1, 2], 4), + takeFirst([0, 1, 2, 3, 4, 5], 5), + takeFirst([0, 1, 2, 3, 4], 5), + takeFirst([0, 1, 2, 3], 5), + takeFirst([0, 1, 2, 3, 4, 5, 6], 6), + takeFirst([0, 1, 2, 3, 4, 5], 6), + takeFirst([0, 1, 2, 3, 4], 6), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7], 7), + takeFirst([0, 1, 2, 3, 4, 5, 6], 7), + takeFirst([0, 1, 2, 3, 4, 5], 7), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8], 8), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7], 8), + takeFirst([0, 1, 2, 3, 4, 5, 6], 8), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 9), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8], 9), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7], 9), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 10), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 10), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8], 10), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 11), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 11), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 11), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 12), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 12), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 12), + ] as const; + + const expectedLengths = [ + 1, 1, 0, 2, 2, 1, 3, 3, 2, 4, 4, 3, 5, 5, 4, 6, 6, 5, 7, 7, 6, + 8, 8, 7, 9, 9, 8, 10, 10, 9, 11, 11, 10, 12, 12, 11, + ]; + + assert.deepEqual( + results.map((result) => result.length), + expectedLengths, + ); + }, + ); +}); + +void nodeIt('isNonEmptyArray', () => { + assert.equal(isNonEmptyArray([]), false); + assert.equal(isNonEmptyArray([1]), true); + assert.equal(isNonEmptyArray([1, 2, 3]), true); +}); diff --git a/dotcom-rendering/src/lib/tuple.test.ts b/dotcom-rendering/src/lib/tuple.test.ts deleted file mode 100644 index 33b398afb3d..00000000000 --- a/dotcom-rendering/src/lib/tuple.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { isNonEmptyArray, takeFirst } from './tuple'; - -describe('takeFirst', () => { - it('Always returns the correct array length when the array is one less, the same as, or one more than n', () => { - const results = [ - // Format, from 1 to 12, cover length n - 1, n & n+1 - takeFirst([0, 1], 1), - takeFirst([0], 1), - takeFirst([], 1), - takeFirst([0, 1, 2], 2), - takeFirst([0, 1], 2), - takeFirst([0], 2), - takeFirst([0, 1, 2, 3], 3), - takeFirst([0, 1, 2], 3), - takeFirst([0, 1], 3), - takeFirst([0, 1, 2, 3, 4], 4), - takeFirst([0, 1, 2, 3], 4), - takeFirst([0, 1, 2], 4), - takeFirst([0, 1, 2, 3, 4, 5], 5), - takeFirst([0, 1, 2, 3, 4], 5), - takeFirst([0, 1, 2, 3], 5), - takeFirst([0, 1, 2, 3, 4, 5, 6], 6), - takeFirst([0, 1, 2, 3, 4, 5], 6), - takeFirst([0, 1, 2, 3, 4], 6), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7], 7), - takeFirst([0, 1, 2, 3, 4, 5, 6], 7), - takeFirst([0, 1, 2, 3, 4, 5], 7), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8], 8), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7], 8), - takeFirst([0, 1, 2, 3, 4, 5, 6], 8), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 9), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8], 9), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7], 9), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 10), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 10), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8], 10), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 11), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 11), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 11), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 12), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 12), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 12), - ] as const; - - // Expected results from n are n, n & n-1 - expect(results[0].length).toEqual(1); - expect(results[1].length).toEqual(1); - expect(results[2].length).toEqual(0); - expect(results[3].length).toEqual(2); - expect(results[4].length).toEqual(2); - expect(results[5].length).toEqual(1); - expect(results[6].length).toEqual(3); - expect(results[7].length).toEqual(3); - expect(results[8].length).toEqual(2); - expect(results[9].length).toEqual(4); - expect(results[10].length).toEqual(4); - expect(results[11].length).toEqual(3); - expect(results[12].length).toEqual(5); - expect(results[13].length).toEqual(5); - expect(results[14].length).toEqual(4); - expect(results[15].length).toEqual(6); - expect(results[16].length).toEqual(6); - expect(results[17].length).toEqual(5); - expect(results[18].length).toEqual(7); - expect(results[19].length).toEqual(7); - expect(results[20].length).toEqual(6); - expect(results[21].length).toEqual(8); - expect(results[22].length).toEqual(8); - expect(results[23].length).toEqual(7); - expect(results[24].length).toEqual(9); - expect(results[25].length).toEqual(9); - expect(results[26].length).toEqual(8); - expect(results[27].length).toEqual(10); - expect(results[28].length).toEqual(10); - expect(results[29].length).toEqual(9); - expect(results[30].length).toEqual(11); - expect(results[31].length).toEqual(11); - expect(results[32].length).toEqual(10); - expect(results[33].length).toEqual(12); - expect(results[34].length).toEqual(12); - expect(results[35].length).toEqual(11); - }); -}); - -it('isNonEmptyArray', () => { - expect(isNonEmptyArray([])).toBe(false); - expect(isNonEmptyArray([1])).toBe(true); - expect(isNonEmptyArray([1, 2, 3])).toBe(true); -}); From eea6663fb25a4e2e1ebf67a3fe815326d3c28b55 Mon Sep 17 00:00:00 2001 From: Ravi <7014230+arelra@users.noreply.github.com> Date: Fri, 11 Sep 2026 21:27:18 +0100 Subject: [PATCH 2/8] Migrate second batch of unit tests to Node's test runner Co-authored-by: GPT-5.6 Sol --- ...test.ts => electionComponent.node.test.ts} | 13 +- .../src/cricketMatch.node.test.ts | 83 ++++++++ dotcom-rendering/src/cricketMatch.test.ts | 2 +- .../src/footballMatches.node.test.ts | 188 ++++++++++++++++++ ...ions.test.ts => acquisitions.node.test.ts} | 9 +- ...ting.test.ts => ad-targeting.node.test.ts} | 11 +- .../src/lib/articleMeta.node.test.ts | 76 +++++++ dotcom-rendering/src/lib/articleMeta.test.ts | 59 ------ ...lpers.test.ts => cardHelpers.node.test.ts} | 23 ++- dotcom-rendering/src/lib/edition.node.test.ts | 56 ++++++ dotcom-rendering/src/lib/edition.test.ts | 50 ----- .../lib/getLiveblogAdPositions.node.test.ts | 93 +++++++++ .../src/lib/getLiveblogAdPositions.test.ts | 70 ------- .../lib/getTagPageAdPositions.node.test.ts | 37 ++++ .../src/lib/getTagPageAdPositions.test.ts | 25 --- .../lib/identity-component-event.node.test.ts | 28 +++ .../src/lib/identity-component-event.test.ts | 16 -- dotcom-rendering/src/lib/labs.node.test.ts | 42 ++++ dotcom-rendering/src/lib/labs.test.ts | 29 --- dotcom-rendering/src/lib/lang.node.test.ts | 21 ++ dotcom-rendering/src/lib/lang.test.ts | 19 -- .../src/lib/ophan-helpers.node.test.ts | 13 ++ .../src/lib/ophan-helpers.test.ts | 10 - .../parseCheckoutOutCookieData.node.test.ts | 78 ++++++++ .../parser/parseCheckoutOutCookieData.test.ts | 67 ------- .../src/lib/puzzlesHubExperiment.node.test.ts | 45 +++++ .../src/lib/puzzlesHubExperiment.test.ts | 34 ---- .../lib/sendTargetingParams.apps.node.test.ts | 80 ++++++++ .../src/lib/sendTargetingParams.apps.test.ts | 71 ------- .../src/lib/theFilter.node.test.ts | 40 ++++ dotcom-rendering/src/lib/theFilter.test.ts | 31 --- ....test.ts => article-sections.node.test.ts} | 8 +- .../src/model/enhance-dots.node.test.ts | 80 ++++++++ .../src/model/enhance-dots.test.ts | 75 ------- .../enhance-product-summary.node.test.ts | 103 ++++++++++ .../src/model/enhance-product-summary.test.ts | 92 --------- ...os.test.ts => enhance-videos.node.test.ts} | 11 +- ... enhanceCommercialProperties.node.test.ts} | 15 +- ...ists.test.ts => enhanceLists.node.test.ts} | 9 +- ...eTags.test.ts => enhanceTags.node.test.ts} | 8 +- 40 files changed, 1130 insertions(+), 690 deletions(-) rename dotcom-rendering/src/components/ElectionTrackers/{electionComponent.test.ts => electionComponent.node.test.ts} (72%) create mode 100644 dotcom-rendering/src/cricketMatch.node.test.ts create mode 100644 dotcom-rendering/src/footballMatches.node.test.ts rename dotcom-rendering/src/lib/{acquisitions.test.ts => acquisitions.node.test.ts} (79%) rename dotcom-rendering/src/lib/{ad-targeting.test.ts => ad-targeting.node.test.ts} (79%) create mode 100644 dotcom-rendering/src/lib/articleMeta.node.test.ts delete mode 100644 dotcom-rendering/src/lib/articleMeta.test.ts rename dotcom-rendering/src/lib/{cardHelpers.test.ts => cardHelpers.node.test.ts} (59%) create mode 100644 dotcom-rendering/src/lib/edition.node.test.ts delete mode 100644 dotcom-rendering/src/lib/edition.test.ts create mode 100644 dotcom-rendering/src/lib/getLiveblogAdPositions.node.test.ts delete mode 100644 dotcom-rendering/src/lib/getLiveblogAdPositions.test.ts create mode 100644 dotcom-rendering/src/lib/getTagPageAdPositions.node.test.ts delete mode 100644 dotcom-rendering/src/lib/getTagPageAdPositions.test.ts create mode 100644 dotcom-rendering/src/lib/identity-component-event.node.test.ts delete mode 100644 dotcom-rendering/src/lib/identity-component-event.test.ts create mode 100644 dotcom-rendering/src/lib/labs.node.test.ts delete mode 100644 dotcom-rendering/src/lib/labs.test.ts create mode 100644 dotcom-rendering/src/lib/lang.node.test.ts delete mode 100644 dotcom-rendering/src/lib/lang.test.ts create mode 100644 dotcom-rendering/src/lib/ophan-helpers.node.test.ts delete mode 100644 dotcom-rendering/src/lib/ophan-helpers.test.ts create mode 100644 dotcom-rendering/src/lib/parser/parseCheckoutOutCookieData.node.test.ts delete mode 100644 dotcom-rendering/src/lib/parser/parseCheckoutOutCookieData.test.ts create mode 100644 dotcom-rendering/src/lib/puzzlesHubExperiment.node.test.ts delete mode 100644 dotcom-rendering/src/lib/puzzlesHubExperiment.test.ts create mode 100644 dotcom-rendering/src/lib/sendTargetingParams.apps.node.test.ts delete mode 100644 dotcom-rendering/src/lib/sendTargetingParams.apps.test.ts create mode 100644 dotcom-rendering/src/lib/theFilter.node.test.ts delete mode 100644 dotcom-rendering/src/lib/theFilter.test.ts rename dotcom-rendering/src/model/{article-sections.test.ts => article-sections.node.test.ts} (86%) create mode 100644 dotcom-rendering/src/model/enhance-dots.node.test.ts delete mode 100644 dotcom-rendering/src/model/enhance-dots.test.ts create mode 100644 dotcom-rendering/src/model/enhance-product-summary.node.test.ts delete mode 100644 dotcom-rendering/src/model/enhance-product-summary.test.ts rename dotcom-rendering/src/model/{enhance-videos.test.ts => enhance-videos.node.test.ts} (67%) rename dotcom-rendering/src/model/{enhanceCommercialProperties.test.ts => enhanceCommercialProperties.node.test.ts} (75%) rename dotcom-rendering/src/model/{enhanceLists.test.ts => enhanceLists.node.test.ts} (88%) rename dotcom-rendering/src/model/{enhanceTags.test.ts => enhanceTags.node.test.ts} (80%) diff --git a/dotcom-rendering/src/components/ElectionTrackers/electionComponent.test.ts b/dotcom-rendering/src/components/ElectionTrackers/electionComponent.node.test.ts similarity index 72% rename from dotcom-rendering/src/components/ElectionTrackers/electionComponent.test.ts rename to dotcom-rendering/src/components/ElectionTrackers/electionComponent.node.test.ts index e076e41aa63..b699276bad9 100644 --- a/dotcom-rendering/src/components/ElectionTrackers/electionComponent.test.ts +++ b/dotcom-rendering/src/components/ElectionTrackers/electionComponent.node.test.ts @@ -1,3 +1,4 @@ +import { it as nodeIt } from 'node:test'; import { parse } from 'valibot'; import { euParliament } from '../../../fixtures/manual/electionTrackers/euParliament'; import { ukGeneralExitPoll } from '../../../fixtures/manual/electionTrackers/ukGeneralExitPoll'; @@ -7,26 +8,26 @@ import { usCongressEmpty } from '../../../fixtures/manual/electionTrackers/usCon import { usPresidential } from '../../../fixtures/manual/electionTrackers/usPresidential'; import { ElectionComponents } from './electionComponent'; -it('validates US Congress data', () => { +void nodeIt('validates US Congress data', () => { parse(ElectionComponents, usCongressEmpty); }); -it('validates UK General data', () => { +void nodeIt('validates UK General data', () => { parse(ElectionComponents, ukGeneralFinal); }); -it('validates UK General Exit Poll data', () => { +void nodeIt('validates UK General Exit Poll data', () => { parse(ElectionComponents, ukGeneralExitPoll); }); -it('validates UK Local data', () => { +void nodeIt('validates UK Local data', () => { parse(ElectionComponents, ukLocal); }); -it('validates US Presidential data', () => { +void nodeIt('validates US Presidential data', () => { parse(ElectionComponents, usPresidential); }); -it('validates EU Parliament data', () => { +void nodeIt('validates EU Parliament data', () => { parse(ElectionComponents, euParliament); }); diff --git a/dotcom-rendering/src/cricketMatch.node.test.ts b/dotcom-rendering/src/cricketMatch.node.test.ts new file mode 100644 index 00000000000..e5f3ab22b1a --- /dev/null +++ b/dotcom-rendering/src/cricketMatch.node.test.ts @@ -0,0 +1,83 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { liveMatch, resultMatch } from '../fixtures/manual/cricketMatch'; +import { parseCricketMatch } from './cricketMatch'; + +void nodeDescribe('parseCricketMatchV2', () => { + void nodeIt('parses a winner result cricket match correctly', () => { + const result = parseCricketMatch(resultMatch).getOrThrow( + 'Expected parsing cricket match to succeed', + ); + + assert.equal(result.kind, 'Result'); + assert.deepEqual(result.result, { + type: 'home-win', + description: 'England win by 115 runs', + winner: { + type: 'runs', + team: 'England', + margin: 115, + }, + }); + assert.deepEqual( + result.matchDate, + new Date('2026-06-17T10:00:00.000Z'), + ); + }); + + void nodeIt('parses a cricket match in pre-match status', () => { + const result = parseCricketMatch({ + ...liveMatch, + result: 'pre-match', + fullResult: undefined, + }).getOrThrow('Expected parsing cricket match to succeed'); + + assert.equal(result.kind, 'Fixture'); + assert.equal(result.result, undefined); + }); + + void nodeIt('parses a cricket match in in-play status', () => { + const result = parseCricketMatch({ + ...liveMatch, + result: 'in-play', + fullResult: undefined, + }).getOrThrow('Expected parsing cricket match to succeed'); + + assert.equal(result.kind, 'Live'); + assert.equal(result.result, undefined); + }); + + void nodeIt('parses an abandoned cricket match correctly', () => { + const result = parseCricketMatch({ + ...liveMatch, + fullResult: { + resultType: 'abandoned', + description: 'Match abandoned due to rain', + winner: undefined, + }, + }).getOrThrow('Expected parsing cricket match to succeed'); + + assert.deepEqual(result.result, { + type: 'abandoned', + description: 'Match abandoned due to rain', + winner: undefined, + }); + }); + + void nodeIt('parses a cricket match with no winner', () => { + const result = parseCricketMatch({ + ...liveMatch, + fullResult: { + resultType: 'no-result', + description: 'No result', + winner: undefined, + }, + }).getOrThrow('Expected parsing cricket match to succeed'); + + assert.deepEqual(result.result, { + type: 'no-result', + description: 'No result', + winner: undefined, + }); + }); +}); diff --git a/dotcom-rendering/src/cricketMatch.test.ts b/dotcom-rendering/src/cricketMatch.test.ts index 3391ddd6ff8..e6be85fb996 100644 --- a/dotcom-rendering/src/cricketMatch.test.ts +++ b/dotcom-rendering/src/cricketMatch.test.ts @@ -1,4 +1,4 @@ -import { liveMatch, resultMatch } from '../fixtures/manual/cricketMatch'; +import { resultMatch, liveMatch } from '../fixtures/manual/cricketMatch'; import { parseCricketMatch } from './cricketMatch'; describe('parseCricketMatchV2', () => { diff --git a/dotcom-rendering/src/footballMatches.node.test.ts b/dotcom-rendering/src/footballMatches.node.test.ts new file mode 100644 index 00000000000..80f6dabbbbc --- /dev/null +++ b/dotcom-rendering/src/footballMatches.node.test.ts @@ -0,0 +1,188 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { footballData } from '../fixtures/generated/football-live'; +import { + emptyMatches, + liveMatch, + matchDayLive, + matchDayLiveSecondHalf, + matchFixture, + matchResult, +} from '../fixtures/manual/footballMatches'; +import { parse } from './footballMatches'; +import type { + FEFootballMatch, + FEMatchByDateAndCompetition, + FEResult, +} from './frontend/feFootballMatchListPage'; + +const withMatches = ( + matches: FEFootballMatch[], +): FEMatchByDateAndCompetition[] => + emptyMatches.map((day) => ({ + ...day, + competitionMatches: day.competitionMatches.map((competition) => ({ + ...competition, + matches, + })), + })); + +void nodeDescribe('footballMatches', () => { + void nodeIt('should parse match fixtures correctly', () => { + const result = parse(footballData.matchesList).getOrThrow( + 'Expected football match parsing to succeed', + ); + + assert.equal(result.length, 1); + + const day = result[0]; + assert.equal(day?.dateISOString, '2025-04-28T00:00:00.000Z'); + assert.equal(day?.competitions.length, 2); + + const competition = day?.competitions[0]; + assert.equal(competition?.name, 'Serie A'); + assert.equal(competition?.matches[0]?.kind, 'Fixture'); + assert.equal(competition?.tag, 'football/serieafootball'); + }); + + void nodeIt( + 'should return an error when football days have invalid dates', + () => { + const invalidDate: FEMatchByDateAndCompetition[] = emptyMatches.map( + (day) => ({ + ...day, + date: 'foo', + }), + ); + + const result = parse(invalidDate).getErrorOrThrow( + 'Expected football match parsing to fail', + ); + + assert.equal(result.kind, 'FootballDayInvalidDate'); + }, + ); + + void nodeIt( + 'should return an error when football matches have an invalid date', + () => { + const invalidMatchResult: FEMatchByDateAndCompetition[] = + withMatches([ + matchFixture, + { ...matchResult, date: '' }, + matchDayLive, + ]); + const invalidMatchFixture: FEMatchByDateAndCompetition[] = + withMatches([ + { ...matchFixture, date: '' }, + matchResult, + matchDayLive, + ]); + const invalidLiveMatch: FEMatchByDateAndCompetition[] = withMatches( + [matchResult, matchFixture, { ...matchDayLive, date: '' }], + ); + + const resultOne = parse(invalidMatchResult).getErrorOrThrow( + 'Expected football match parsing to fail', + ); + const resultTwo = parse(invalidMatchFixture).getErrorOrThrow( + 'Expected football match parsing to fail', + ); + const resultThree = parse(invalidLiveMatch).getErrorOrThrow( + 'Expected football match parsing to fail', + ); + + assert.equal(resultOne.kind, 'FootballMatchInvalidDate'); + assert.equal(resultTwo.kind, 'FootballMatchInvalidDate'); + + if (resultThree.kind !== 'InvalidMatchDay') { + throw new Error('Expected an invalid match day error'); + } + + assert.equal( + resultThree.errors[0]!.kind, + 'FootballMatchInvalidDate', + ); + }, + ); + + void nodeIt('should return an error when it receives a live match', () => { + const result = parse(withMatches([liveMatch])).getErrorOrThrow( + 'Expected football match parsing to fail', + ); + + assert.equal(result.kind, 'UnexpectedLiveMatch'); + }); + void nodeIt('should return a clean team name', () => { + const matchesListWithTeamName = (teamName: string): FEResult => { + return { + ...matchResult, + homeTeam: { + ...matchResult.homeTeam, + name: teamName, + }, + }; + }; + + const uncleanToCleanNames: Record = { + Ladies: '', + Holland: 'The Netherlands', + 'Ivory Coast': 'Côte d’Ivoire', + 'Union Saint Gilloise': 'Union Saint-Gilloise', + 'Bosnia-Herzegovina': 'Bosnia and Herzegovina', + 'Congo DR': 'DR Congo', + Curacao: 'Curaçao', + 'Czech Republic': 'Czechia', + }; + + for (const [uncleanName, cleanName] of Object.entries( + uncleanToCleanNames, + )) { + const matchDay = parse( + withMatches([matchesListWithTeamName(uncleanName)]), + ).getOrThrow('Expected football match parsing to succeed'); + + const match = matchDay[0]!.competitions[0]!.matches[0]; + if (match?.kind !== 'Result') { + throw new Error('Expected Result'); + } + + assert.equal(match.homeTeam.name, cleanName); + } + }); + void nodeIt( + 'should replace known live match status with our status', + () => { + const matchDay = parse( + withMatches([matchDayLiveSecondHalf]), + ).getOrThrow('Expected football live match parsing to succeed'); + + const match = matchDay[0]!.competitions[0]!.matches[0]; + if (match?.kind !== 'Live') { + throw new Error('Expected live match'); + } + + assert.equal(match.status, '2nd'); + }, + ); + void nodeIt( + 'should replace unknown live match status with first two characters', + () => { + const matchDayLiveUnknownStatus = { + ...matchDayLiveSecondHalf, + matchStatus: 'Something odd', + }; + + const matchDay = parse( + withMatches([matchDayLiveUnknownStatus]), + ).getOrThrow('Expected football live match parsing to succeed'); + + const match = matchDay[0]!.competitions[0]!.matches[0]; + if (match?.kind !== 'Live') { + throw new Error('Expected live match'); + } + + assert.equal(match.status, 'So'); + }, + ); +}); diff --git a/dotcom-rendering/src/lib/acquisitions.test.ts b/dotcom-rendering/src/lib/acquisitions.node.test.ts similarity index 79% rename from dotcom-rendering/src/lib/acquisitions.test.ts rename to dotcom-rendering/src/lib/acquisitions.node.test.ts index c48945d3eda..b8a146d1756 100644 --- a/dotcom-rendering/src/lib/acquisitions.test.ts +++ b/dotcom-rendering/src/lib/acquisitions.node.test.ts @@ -1,7 +1,9 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; import { addTrackingCodesToUrl } from './acquisitions'; -describe('acquisitions', () => { - it('should addTrackingCodesToUrl', () => { +void nodeDescribe('acquisitions', () => { + void nodeIt('should addTrackingCodesToUrl', () => { const result = addTrackingCodesToUrl({ base: `https://support.theguardian.com/contribute`, componentType: 'ACQUISITIONS_HEADER', @@ -15,7 +17,8 @@ describe('acquisitions', () => { referrerUrl: 'https://theguardian.com/uk', }); - expect(result).toEqual( + assert.equal( + result, 'https://support.theguardian.com/contribute?REFPVID=abcdefg&INTCMP=header_support&acquisitionData=%7B%22source%22%3A%22GUARDIAN_WEB%22%2C%22componentId%22%3A%22header_support%22%2C%22componentType%22%3A%22ACQUISITIONS_HEADER%22%2C%22campaignCode%22%3A%22header_support%22%2C%22abTest%22%3A%7B%22name%22%3A%22testName%22%2C%22variant%22%3A%22variantName%22%7D%2C%22referrerPageviewId%22%3A%22abcdefg%22%2C%22referrerUrl%22%3A%22https%3A%2F%2Ftheguardian.com%2Fuk%22%7D', ); }); diff --git a/dotcom-rendering/src/lib/ad-targeting.test.ts b/dotcom-rendering/src/lib/ad-targeting.node.test.ts similarity index 79% rename from dotcom-rendering/src/lib/ad-targeting.test.ts rename to dotcom-rendering/src/lib/ad-targeting.node.test.ts index 88da2cf0099..68357662237 100644 --- a/dotcom-rendering/src/lib/ad-targeting.test.ts +++ b/dotcom-rendering/src/lib/ad-targeting.node.test.ts @@ -1,3 +1,5 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; import { buildAdTargeting } from './ad-targeting'; const sharedAdTargeting = { @@ -12,7 +14,7 @@ const sharedAdTargeting = { url: '/money/2017/mar/10/ministers-to-criminalise-use-of-ticket-tout-harvesting-software', }; -describe('buildAdTargeting', () => { +void nodeDescribe('buildAdTargeting', () => { const expectedAdTargeting = { adUnit: '/59666047/theguardian.com/money/article/ng', customParams: { @@ -38,8 +40,8 @@ describe('buildAdTargeting', () => { }, }; - it('builds adTargeting correctly', () => { - expect( + void nodeIt('builds adTargeting correctly', () => { + assert.deepEqual( buildAdTargeting({ isAdFreeUser: false, isSensitive: false, @@ -48,6 +50,7 @@ describe('buildAdTargeting', () => { sharedAdTargeting, adUnit: '/59666047/theguardian.com/money/article/ng', }), - ).toEqual(expectedAdTargeting); + expectedAdTargeting, + ); }); }); diff --git a/dotcom-rendering/src/lib/articleMeta.node.test.ts b/dotcom-rendering/src/lib/articleMeta.node.test.ts new file mode 100644 index 00000000000..1ff85ba1a8d --- /dev/null +++ b/dotcom-rendering/src/lib/articleMeta.node.test.ts @@ -0,0 +1,76 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { ArticleDesign, ArticleDisplay, Pillar } from './articleFormat'; +import { shouldShowContributor } from './articleMeta'; + +void nodeDescribe('shouldShowContributor', () => { + const standardFormat = { + theme: Pillar.News, + design: ArticleDesign.Standard, + display: ArticleDisplay.Standard, + }; + const standardComment = { + ...standardFormat, + design: ArticleDesign.Comment, + }; + const showcaseStandard = { + ...standardFormat, + display: ArticleDisplay.Showcase, + }; + const showcaseComment = { + ...showcaseStandard, + design: ArticleDesign.Comment, + }; + const numberedList = { + ...standardFormat, + display: ArticleDisplay.NumberedList, + }; + + const immersive = { + ...standardFormat, + display: ArticleDisplay.Immersive, + }; + + void nodeIt( + 'should return true if Standard display and Standard design', + () => { + assert.equal(shouldShowContributor(standardFormat), true); + }, + ); + + void nodeIt( + 'should return false if Standard display and Comment design', + () => { + assert.equal(shouldShowContributor(standardComment), false); + }, + ); + + void nodeIt( + 'should return true if Showcase display and Standard design', + () => { + assert.equal(shouldShowContributor(showcaseStandard), true); + }, + ); + + void nodeIt( + 'should return false if Showcase display and Comment design', + () => { + assert.equal(shouldShowContributor(showcaseComment), false); + }, + ); + + void nodeIt('should return true if Numbered list display', () => { + assert.equal(shouldShowContributor(numberedList), true); + }); + + void nodeIt('should return false if Immersive display', () => { + assert.equal(shouldShowContributor(immersive), false); + }); + + void nodeIt( + 'should return true if Immersive display uses the new grid', + () => { + assert.equal(shouldShowContributor(immersive, true), true); + }, + ); +}); diff --git a/dotcom-rendering/src/lib/articleMeta.test.ts b/dotcom-rendering/src/lib/articleMeta.test.ts deleted file mode 100644 index 2955e6918c7..00000000000 --- a/dotcom-rendering/src/lib/articleMeta.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { ArticleDesign, ArticleDisplay, Pillar } from './articleFormat'; -import { shouldShowContributor } from './articleMeta'; - -describe('shouldShowContributor', () => { - const standardFormat = { - theme: Pillar.News, - design: ArticleDesign.Standard, - display: ArticleDisplay.Standard, - }; - const standardComment = { - ...standardFormat, - design: ArticleDesign.Comment, - }; - const showcaseStandard = { - ...standardFormat, - display: ArticleDisplay.Showcase, - }; - const showcaseComment = { - ...showcaseStandard, - design: ArticleDesign.Comment, - }; - const numberedList = { - ...standardFormat, - display: ArticleDisplay.NumberedList, - }; - - const immersive = { - ...standardFormat, - display: ArticleDisplay.Immersive, - }; - - it('should return true if Standard display and Standard design', () => { - expect(shouldShowContributor(standardFormat)).toBe(true); - }); - - it('should return false if Standard display and Comment design', () => { - expect(shouldShowContributor(standardComment)).toBe(false); - }); - - it('should return true if Showcase display and Standard design', () => { - expect(shouldShowContributor(showcaseStandard)).toBe(true); - }); - - it('should return false if Showcase display and Comment design', () => { - expect(shouldShowContributor(showcaseComment)).toBe(false); - }); - - it('should return true if Numbered list display', () => { - expect(shouldShowContributor(numberedList)).toBe(true); - }); - - it('should return false if Immersive display', () => { - expect(shouldShowContributor(immersive)).toBe(false); - }); - - it('should return true if Immersive display uses the new grid', () => { - expect(shouldShowContributor(immersive, true)).toBe(true); - }); -}); diff --git a/dotcom-rendering/src/lib/cardHelpers.test.ts b/dotcom-rendering/src/lib/cardHelpers.node.test.ts similarity index 59% rename from dotcom-rendering/src/lib/cardHelpers.test.ts rename to dotcom-rendering/src/lib/cardHelpers.node.test.ts index a0376496b99..a3e9edc4f2e 100644 --- a/dotcom-rendering/src/lib/cardHelpers.test.ts +++ b/dotcom-rendering/src/lib/cardHelpers.node.test.ts @@ -1,7 +1,9 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; import type { DCRContainerPalette } from '../types/front'; import { cardHasDarkBackground } from './cardHelpers'; -describe('cardHasDarkBackground', () => { +void nodeDescribe('cardHasDarkBackground', () => { const testCases = [ { containerPalette: undefined, @@ -32,12 +34,15 @@ describe('cardHasDarkBackground', () => { expectedResult: boolean; }[]; - it.each(testCases)( - 'returns $expectedResult for $format format, $containerPalette containerPalette', - ({ containerPalette, expectedResult }) => { - expect(cardHasDarkBackground(containerPalette)).toBe( - expectedResult, - ); - }, - ); + for (const { containerPalette, expectedResult } of testCases) { + void nodeIt( + `returns ${expectedResult} for $format format, ${containerPalette} containerPalette`, + () => { + assert.equal( + cardHasDarkBackground(containerPalette), + expectedResult, + ); + }, + ); + } }); diff --git a/dotcom-rendering/src/lib/edition.node.test.ts b/dotcom-rendering/src/lib/edition.node.test.ts new file mode 100644 index 00000000000..afd41dba5ef --- /dev/null +++ b/dotcom-rendering/src/lib/edition.node.test.ts @@ -0,0 +1,56 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { + editionalisedPages, + editionList, + isEditionalisedPage, + isNetworkFront, +} from './edition'; + +const everyNetworkFront = editionList.map((edition) => edition.pageId); + +const everyEditionalisedPage = editionList + .map((edition) => + editionalisedPages.map((page) => `${edition.pageId}/${page}`), + ) + .flat(); + +void nodeDescribe('is network front', () => { + void nodeIt('returns true if pageId is a network front', () => { + assert.equal( + everyNetworkFront.every((page) => isNetworkFront(page)), + true, + ); + }); + void nodeIt('returns false if pageId is NOT a network front', () => { + assert.equal(everyEditionalisedPage.every(isNetworkFront), false); + assert.equal(isNetworkFront('eu'), false); + assert.equal(isNetworkFront('int'), false); + assert.equal(isNetworkFront('uk/'), false); + }); +}); + +void nodeDescribe('is editionalised page', () => { + void nodeIt('returns true if pageId is editionalised', () => { + assert.equal( + everyEditionalisedPage.every((page) => isEditionalisedPage(page)), + true, + ); + }); + void nodeIt('returns false if pageId is NOT editionalised', () => { + assert.equal( + everyNetworkFront.every((page) => isEditionalisedPage(page)), + false, + ); + assert.equal(isEditionalisedPage('uk'), false); + assert.equal(isEditionalisedPage('au'), false); + assert.equal(isEditionalisedPage('international'), false); + assert.equal(isEditionalisedPage('travel'), false); + assert.equal(isEditionalisedPage('culture'), false); + assert.equal(isEditionalisedPage('lifeandstyle'), false); + assert.equal( + isEditionalisedPage('lifeandstyle/health-and-wellbeing'), + false, + ); + }); +}); diff --git a/dotcom-rendering/src/lib/edition.test.ts b/dotcom-rendering/src/lib/edition.test.ts deleted file mode 100644 index 406e2a311a9..00000000000 --- a/dotcom-rendering/src/lib/edition.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { - editionalisedPages, - editionList, - isEditionalisedPage, - isNetworkFront, -} from './edition'; - -const everyNetworkFront = editionList.map((edition) => edition.pageId); - -const everyEditionalisedPage = editionList - .map((edition) => - editionalisedPages.map((page) => `${edition.pageId}/${page}`), - ) - .flat(); - -describe('is network front', () => { - it('returns true if pageId is a network front', () => { - expect(everyNetworkFront.every((page) => isNetworkFront(page))).toBe( - true, - ); - }); - it('returns false if pageId is NOT a network front', () => { - expect(everyEditionalisedPage.every(isNetworkFront)).toBe(false); - expect(isNetworkFront('eu')).toBe(false); - expect(isNetworkFront('int')).toBe(false); - expect(isNetworkFront('uk/')).toBe(false); - }); -}); - -describe('is editionalised page', () => { - it('returns true if pageId is editionalised', () => { - expect( - everyEditionalisedPage.every((page) => isEditionalisedPage(page)), - ).toBe(true); - }); - it('returns false if pageId is NOT editionalised', () => { - expect( - everyNetworkFront.every((page) => isEditionalisedPage(page)), - ).toBe(false); - expect(isEditionalisedPage('uk')).toBe(false); - expect(isEditionalisedPage('au')).toBe(false); - expect(isEditionalisedPage('international')).toBe(false); - expect(isEditionalisedPage('travel')).toBe(false); - expect(isEditionalisedPage('culture')).toBe(false); - expect(isEditionalisedPage('lifeandstyle')).toBe(false); - expect(isEditionalisedPage('lifeandstyle/health-and-wellbeing')).toBe( - false, - ); - }); -}); diff --git a/dotcom-rendering/src/lib/getLiveblogAdPositions.node.test.ts b/dotcom-rendering/src/lib/getLiveblogAdPositions.node.test.ts new file mode 100644 index 00000000000..598a7faa235 --- /dev/null +++ b/dotcom-rendering/src/lib/getLiveblogAdPositions.node.test.ts @@ -0,0 +1,93 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { liveBlock as mockBlock } from '../../fixtures/manual/liveBlock'; +import type { Block } from '../types/blocks'; +import { getLiveblogAdPositions } from './getLiveblogAdPositions'; + +void nodeDescribe('get liveblog ad positions', () => { + const twoBlocks = Array(2).fill(mockBlock); + + void nodeIt('should insert zero ads if zero blocks', () => { + assert.deepEqual(getLiveblogAdPositions([]).desktopAdPositions, []); + assert.deepEqual(getLiveblogAdPositions([]).mobileAdPositions, []); + }); + void nodeIt('should insert zero ads if one block', () => { + assert.deepEqual( + getLiveblogAdPositions([mockBlock]).desktopAdPositions, + [], + ); + assert.deepEqual( + getLiveblogAdPositions([mockBlock]).mobileAdPositions, + [], + ); + }); + void nodeIt( + 'should insert an ad after the first block if two blocks', + () => { + assert.deepEqual( + getLiveblogAdPositions(twoBlocks).desktopAdPositions, + [0], + ); + assert.deepEqual( + getLiveblogAdPositions(twoBlocks).mobileAdPositions, + [0], + ); + }, + ); + + void nodeDescribe('many blocks', () => { + const block: Block = { + ...mockBlock, + elements: [ + { + elementId: '4ac2fcd8-284c-4038-91a1-093811f389ba', + _type: 'model.dotcomrendering.pageElements.TextBlockElement', + html: `

${'a'.repeat(1000)}

`, + }, + ], + }; + + const tenBlocks = Array(10).fill(block); + + void nodeIt( + 'On desktop, it should insert an ad after every fourth block given repeated text elements of 1,000 characters', + () => { + assert.deepEqual( + getLiveblogAdPositions(tenBlocks).desktopAdPositions, + [0, 4, 8], + ); + }, + ); + + void nodeIt( + 'On mobile, it should insert an ad after every second block given repeated text elements of 1,000 characters', + () => { + assert.deepEqual( + getLiveblogAdPositions(tenBlocks).mobileAdPositions, + [0, 2, 4, 6, 8], + ); + }, + ); + + // 40 blocks is enough that without a limit, there would be more than 8 blocks inserted on both mobile and desktop. + const fortyBlocks = Array(40).fill(block); + + void nodeIt( + 'On desktop, it should not insert more that 8 slots', + () => { + assert.equal( + getLiveblogAdPositions(fortyBlocks).desktopAdPositions + .length, + 8, + ); + }, + ); + + void nodeIt('On mobile, it should not insert more that 8 slots', () => { + assert.equal( + getLiveblogAdPositions(fortyBlocks).mobileAdPositions.length, + 8, + ); + }); + }); +}); diff --git a/dotcom-rendering/src/lib/getLiveblogAdPositions.test.ts b/dotcom-rendering/src/lib/getLiveblogAdPositions.test.ts deleted file mode 100644 index 32d0f203d5d..00000000000 --- a/dotcom-rendering/src/lib/getLiveblogAdPositions.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { liveBlock as mockBlock } from '../../fixtures/manual/liveBlock'; -import type { Block } from '../types/blocks'; -import { getLiveblogAdPositions } from './getLiveblogAdPositions'; - -describe('get liveblog ad positions', () => { - const twoBlocks = Array(2).fill(mockBlock); - - it('should insert zero ads if zero blocks', () => { - expect(getLiveblogAdPositions([]).desktopAdPositions).toEqual([]); - expect(getLiveblogAdPositions([]).mobileAdPositions).toEqual([]); - }); - it('should insert zero ads if one block', () => { - expect(getLiveblogAdPositions([mockBlock]).desktopAdPositions).toEqual( - [], - ); - expect(getLiveblogAdPositions([mockBlock]).mobileAdPositions).toEqual( - [], - ); - }); - it('should insert an ad after the first block if two blocks', () => { - expect(getLiveblogAdPositions(twoBlocks).desktopAdPositions).toEqual([ - 0, - ]); - expect(getLiveblogAdPositions(twoBlocks).mobileAdPositions).toEqual([ - 0, - ]); - }); - - describe('many blocks', () => { - const block: Block = { - ...mockBlock, - elements: [ - { - elementId: '4ac2fcd8-284c-4038-91a1-093811f389ba', - _type: 'model.dotcomrendering.pageElements.TextBlockElement', - html: `

${'a'.repeat(1000)}

`, - }, - ], - }; - - const tenBlocks = Array(10).fill(block); - - it('On desktop, it should insert an ad after every fourth block given repeated text elements of 1,000 characters', () => { - expect( - getLiveblogAdPositions(tenBlocks).desktopAdPositions, - ).toEqual([0, 4, 8]); - }); - - it('On mobile, it should insert an ad after every second block given repeated text elements of 1,000 characters', () => { - expect(getLiveblogAdPositions(tenBlocks).mobileAdPositions).toEqual( - [0, 2, 4, 6, 8], - ); - }); - - // 40 blocks is enough that without a limit, there would be more than 8 blocks inserted on both mobile and desktop. - const fortyBlocks = Array(40).fill(block); - - it('On desktop, it should not insert more that 8 slots', () => { - expect( - getLiveblogAdPositions(fortyBlocks).desktopAdPositions, - ).toHaveLength(8); - }); - - it('On mobile, it should not insert more that 8 slots', () => { - expect( - getLiveblogAdPositions(fortyBlocks).mobileAdPositions, - ).toHaveLength(8); - }); - }); -}); diff --git a/dotcom-rendering/src/lib/getTagPageAdPositions.node.test.ts b/dotcom-rendering/src/lib/getTagPageAdPositions.node.test.ts new file mode 100644 index 00000000000..62f644dd3c9 --- /dev/null +++ b/dotcom-rendering/src/lib/getTagPageAdPositions.node.test.ts @@ -0,0 +1,37 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { getTagPageBannerAdPositions } from './getTagPageAdPositions'; + +void nodeDescribe('Tag page fronts-banner ad slots', () => { + void nodeIt( + 'should insert 0 ads if there are less than 5 containers', + () => { + assert.deepEqual(getTagPageBannerAdPositions(1), []); + assert.deepEqual(getTagPageBannerAdPositions(3), []); + }, + ); + + void nodeIt('should insert 1 ad if there are 5-7 containers', () => { + assert.deepEqual(getTagPageBannerAdPositions(4), [2]); + assert.deepEqual(getTagPageBannerAdPositions(6), [2]); + }); + + void nodeIt('should insert 2 ads if there are 8-10 containers', () => { + assert.deepEqual(getTagPageBannerAdPositions(7), [2, 5]); + assert.deepEqual(getTagPageBannerAdPositions(9), [2, 5]); + }); + + void nodeIt( + 'should insert no more than 8 ads if there are more than 18 containers', + () => { + assert.deepEqual( + getTagPageBannerAdPositions(19), + [2, 5, 8, 11, 14, 17], + ); + assert.deepEqual( + getTagPageBannerAdPositions(25), + [2, 5, 8, 11, 14, 17, 20, 23], + ); + }, + ); +}); diff --git a/dotcom-rendering/src/lib/getTagPageAdPositions.test.ts b/dotcom-rendering/src/lib/getTagPageAdPositions.test.ts deleted file mode 100644 index 3b743740430..00000000000 --- a/dotcom-rendering/src/lib/getTagPageAdPositions.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { getTagPageBannerAdPositions } from './getTagPageAdPositions'; - -describe('Tag page fronts-banner ad slots', () => { - it('should insert 0 ads if there are less than 5 containers', () => { - expect(getTagPageBannerAdPositions(1)).toEqual([]); - expect(getTagPageBannerAdPositions(3)).toEqual([]); - }); - - it('should insert 1 ad if there are 5-7 containers', () => { - expect(getTagPageBannerAdPositions(4)).toEqual([2]); - expect(getTagPageBannerAdPositions(6)).toEqual([2]); - }); - - it('should insert 2 ads if there are 8-10 containers', () => { - expect(getTagPageBannerAdPositions(7)).toEqual([2, 5]); - expect(getTagPageBannerAdPositions(9)).toEqual([2, 5]); - }); - - it('should insert no more than 8 ads if there are more than 18 containers', () => { - expect(getTagPageBannerAdPositions(19)).toEqual([2, 5, 8, 11, 14, 17]); - expect(getTagPageBannerAdPositions(25)).toEqual([ - 2, 5, 8, 11, 14, 17, 20, 23, - ]); - }); -}); diff --git a/dotcom-rendering/src/lib/identity-component-event.node.test.ts b/dotcom-rendering/src/lib/identity-component-event.node.test.ts new file mode 100644 index 00000000000..ed6c00dac87 --- /dev/null +++ b/dotcom-rendering/src/lib/identity-component-event.node.test.ts @@ -0,0 +1,28 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { createAuthenticationEventParams } from './identity-component-event'; + +void nodeDescribe('createAuthenticationEventParams', () => { + void nodeIt( + 'creates authentication event params given a component Id', + () => { + assert.equal( + createAuthenticationEventParams('amp_sidebar_signin'), + 'componentEventParams=componentType%3Didentityauthentication%26componentId%3Damp_sidebar_signin', + ); + }, + ); + + void nodeIt( + 'creates authentication event params given a component Id and a page view Id', + () => { + assert.equal( + createAuthenticationEventParams( + 'amp_sidebar_signin', + 'pageViewId', + ), + 'componentEventParams=componentType%3Didentityauthentication%26componentId%3Damp_sidebar_signin%26viewId%3DpageViewId', + ); + }, + ); +}); diff --git a/dotcom-rendering/src/lib/identity-component-event.test.ts b/dotcom-rendering/src/lib/identity-component-event.test.ts deleted file mode 100644 index ab697b80550..00000000000 --- a/dotcom-rendering/src/lib/identity-component-event.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { createAuthenticationEventParams } from './identity-component-event'; - -describe('createAuthenticationEventParams', () => { - it('creates authentication event params given a component Id', () => { - expect(createAuthenticationEventParams('amp_sidebar_signin')).toBe( - 'componentEventParams=componentType%3Didentityauthentication%26componentId%3Damp_sidebar_signin', - ); - }); - it('creates authentication event params given a component Id and a page view Id', () => { - expect( - createAuthenticationEventParams('amp_sidebar_signin', 'pageViewId'), - ).toBe( - 'componentEventParams=componentType%3Didentityauthentication%26componentId%3Damp_sidebar_signin%26viewId%3DpageViewId', - ); - }); -}); diff --git a/dotcom-rendering/src/lib/labs.node.test.ts b/dotcom-rendering/src/lib/labs.node.test.ts new file mode 100644 index 00000000000..ea057a338a9 --- /dev/null +++ b/dotcom-rendering/src/lib/labs.node.test.ts @@ -0,0 +1,42 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import type { Branding } from '../types/branding'; +import { getOphanComponents } from './labs'; + +void nodeDescribe('getOphanComponents', () => { + void nodeIt( + 'constructs the correct data attributes for branding in article meta', + () => { + const branding = { sponsorName: 'Some Sponsor' } as Branding; + assert.deepEqual( + getOphanComponents({ + branding, + locationPrefix: 'article-meta', + }), + { + ophanComponentName: 'labs-logo | article-meta-some-sponsor', + ophanComponentLink: 'labs-logo-article-meta-some-sponsor', + }, + ); + }, + ); + + void nodeIt( + 'constructs the correct data attributes for branding in related content', + () => { + const branding = { sponsorName: 'Some Sponsor' } as Branding; + assert.deepEqual( + getOphanComponents({ + branding, + locationPrefix: 'article-related-content', + }), + { + ophanComponentName: + 'labs-logo | article-related-content-some-sponsor', + ophanComponentLink: + 'labs-logo-article-related-content-some-sponsor', + }, + ); + }, + ); +}); diff --git a/dotcom-rendering/src/lib/labs.test.ts b/dotcom-rendering/src/lib/labs.test.ts deleted file mode 100644 index 178db5e7622..00000000000 --- a/dotcom-rendering/src/lib/labs.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { Branding } from '../types/branding'; -import { getOphanComponents } from './labs'; - -describe('getOphanComponents', () => { - it('constructs the correct data attributes for branding in article meta', () => { - const branding = { sponsorName: 'Some Sponsor' } as Branding; - expect( - getOphanComponents({ branding, locationPrefix: 'article-meta' }), - ).toStrictEqual({ - ophanComponentName: 'labs-logo | article-meta-some-sponsor', - ophanComponentLink: 'labs-logo-article-meta-some-sponsor', - }); - }); - - it('constructs the correct data attributes for branding in related content', () => { - const branding = { sponsorName: 'Some Sponsor' } as Branding; - expect( - getOphanComponents({ - branding, - locationPrefix: 'article-related-content', - }), - ).toStrictEqual({ - ophanComponentName: - 'labs-logo | article-related-content-some-sponsor', - ophanComponentLink: - 'labs-logo-article-related-content-some-sponsor', - }); - }); -}); diff --git a/dotcom-rendering/src/lib/lang.node.test.ts b/dotcom-rendering/src/lib/lang.node.test.ts new file mode 100644 index 00000000000..e0ac06fb557 --- /dev/null +++ b/dotcom-rendering/src/lib/lang.node.test.ts @@ -0,0 +1,21 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { decideLanguage, decideLanguageDirection } from './lang'; + +void nodeDescribe('decideLanguage', () => { + void nodeIt('returns undefined if input is "en"', () => { + assert.equal(decideLanguage('en'), undefined); + }); + + void nodeIt('returns input if it is not "en"', () => { + assert.equal(decideLanguage('at'), 'at'); + assert.equal(decideLanguage('fr'), 'fr'); + }); +}); + +void nodeDescribe('describeLanguageDirection', () => { + void nodeIt('returns rtl if input is true', () => { + assert.equal(decideLanguageDirection(true), 'rtl'); + assert.equal(decideLanguageDirection(false), undefined); + }); +}); diff --git a/dotcom-rendering/src/lib/lang.test.ts b/dotcom-rendering/src/lib/lang.test.ts deleted file mode 100644 index 42140d1e226..00000000000 --- a/dotcom-rendering/src/lib/lang.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { decideLanguage, decideLanguageDirection } from './lang'; - -describe('decideLanguage', () => { - test('returns undefined if input is "en"', () => { - expect(decideLanguage('en')).toBe(undefined); - }); - - test('returns input if it is not "en"', () => { - expect(decideLanguage('at')).toBe('at'); - expect(decideLanguage('fr')).toBe('fr'); - }); -}); - -describe('describeLanguageDirection', () => { - test('returns rtl if input is true', () => { - expect(decideLanguageDirection(true)).toBe('rtl'); - expect(decideLanguageDirection(false)).toBe(undefined); - }); -}); diff --git a/dotcom-rendering/src/lib/ophan-helpers.node.test.ts b/dotcom-rendering/src/lib/ophan-helpers.node.test.ts new file mode 100644 index 00000000000..d8a93b51f97 --- /dev/null +++ b/dotcom-rendering/src/lib/ophan-helpers.node.test.ts @@ -0,0 +1,13 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { nestedOphanComponents } from './ophan-helpers'; + +void nodeDescribe('Ophan helpers', () => { + void nodeIt('should handle nested values', () => { + assert.equal(nestedOphanComponents('logo'), 'logo'); + assert.equal( + nestedOphanComponents('nav', 'sub nav', 'final element'), + 'nav : sub nav : final element', + ); + }); +}); diff --git a/dotcom-rendering/src/lib/ophan-helpers.test.ts b/dotcom-rendering/src/lib/ophan-helpers.test.ts deleted file mode 100644 index e4300fea5cf..00000000000 --- a/dotcom-rendering/src/lib/ophan-helpers.test.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { nestedOphanComponents } from './ophan-helpers'; - -describe('Ophan helpers', () => { - it('should handle nested values', () => { - expect(nestedOphanComponents('logo')).toBe('logo'); - expect(nestedOphanComponents('nav', 'sub nav', 'final element')).toBe( - 'nav : sub nav : final element', - ); - }); -}); diff --git a/dotcom-rendering/src/lib/parser/parseCheckoutOutCookieData.node.test.ts b/dotcom-rendering/src/lib/parser/parseCheckoutOutCookieData.node.test.ts new file mode 100644 index 00000000000..61b34ae9b5d --- /dev/null +++ b/dotcom-rendering/src/lib/parser/parseCheckoutOutCookieData.node.test.ts @@ -0,0 +1,78 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { parseCheckoutCompleteCookieData } from './parseCheckoutOutCookieData'; + +void nodeDescribe('parseCheckoutCompleteCookieData', () => { + const encodeCheckoutCompleteCookieDataObj = ( + userType: string, + product: string, + ) => + encodeURIComponent(`{"userType":"${userType}","product":"${product}"}`); + + void nodeDescribe('successful parse', () => { + void nodeIt( + 'should successfully parse a url encoded json object with a valid userType and product valid field', + () => { + const cookieString = encodeCheckoutCompleteCookieDataObj( + 'new', + 'SupporterPlus', + ); + assert.deepEqual( + parseCheckoutCompleteCookieData(cookieString), + { + userType: 'new', + product: 'SupporterPlus', + }, + ); + }, + ); + }); + + void nodeDescribe('unsuccessful parse should return undefined', () => { + void nodeIt('invalid user type', () => { + const cookieString = encodeCheckoutCompleteCookieDataObj( + 'invalid', + 'SupporterPlus', + ); + assert.equal( + parseCheckoutCompleteCookieData(cookieString), + undefined, + ); + }); + void nodeIt('invalid product type', () => { + const cookieString = encodeCheckoutCompleteCookieDataObj( + 'new', + 'undefined', + ); + assert.equal( + parseCheckoutCompleteCookieData(cookieString), + undefined, + ); + }); + void nodeIt('invalid field', () => { + const cookieString = encodeURIComponent( + `{"invalid":"new", "product": "SupporterPlus"}`, + ); + assert.equal( + parseCheckoutCompleteCookieData(cookieString), + undefined, + ); + }); + void nodeIt('invalid json structure', () => { + const cookieString = encodeURIComponent( + `{"userType":"new", "product": "SupporterPlus"`, + ); + assert.equal( + parseCheckoutCompleteCookieData(cookieString), + undefined, + ); + }); + void nodeIt('plain string', () => { + const cookieString = `{"userType":"new", "product": "SupporterPlus"}`; + assert.equal( + parseCheckoutCompleteCookieData(cookieString), + undefined, + ); + }); + }); +}); diff --git a/dotcom-rendering/src/lib/parser/parseCheckoutOutCookieData.test.ts b/dotcom-rendering/src/lib/parser/parseCheckoutOutCookieData.test.ts deleted file mode 100644 index e4892e03f16..00000000000 --- a/dotcom-rendering/src/lib/parser/parseCheckoutOutCookieData.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { parseCheckoutCompleteCookieData } from './parseCheckoutOutCookieData'; - -describe('parseCheckoutCompleteCookieData', () => { - const encodeCheckoutCompleteCookieDataObj = ( - userType: string, - product: string, - ) => - encodeURIComponent(`{"userType":"${userType}","product":"${product}"}`); - - describe('successful parse', () => { - it('should successfully parse a url encoded json object with a valid userType and product valid field', () => { - const cookieString = encodeCheckoutCompleteCookieDataObj( - 'new', - 'SupporterPlus', - ); - expect(parseCheckoutCompleteCookieData(cookieString)).toStrictEqual( - { - userType: 'new', - product: 'SupporterPlus', - }, - ); - }); - }); - - describe('unsuccessful parse should return undefined', () => { - it('invalid user type', () => { - const cookieString = encodeCheckoutCompleteCookieDataObj( - 'invalid', - 'SupporterPlus', - ); - expect(parseCheckoutCompleteCookieData(cookieString)).toBe( - undefined, - ); - }); - it('invalid product type', () => { - const cookieString = encodeCheckoutCompleteCookieDataObj( - 'new', - 'undefined', - ); - expect(parseCheckoutCompleteCookieData(cookieString)).toBe( - undefined, - ); - }); - it('invalid field', () => { - const cookieString = encodeURIComponent( - `{"invalid":"new", "product": "SupporterPlus"}`, - ); - expect(parseCheckoutCompleteCookieData(cookieString)).toBe( - undefined, - ); - }); - it('invalid json structure', () => { - const cookieString = encodeURIComponent( - `{"userType":"new", "product": "SupporterPlus"`, - ); - expect(parseCheckoutCompleteCookieData(cookieString)).toBe( - undefined, - ); - }); - it('plain string', () => { - const cookieString = `{"userType":"new", "product": "SupporterPlus"}`; - expect(parseCheckoutCompleteCookieData(cookieString)).toBe( - undefined, - ); - }); - }); -}); diff --git a/dotcom-rendering/src/lib/puzzlesHubExperiment.node.test.ts b/dotcom-rendering/src/lib/puzzlesHubExperiment.node.test.ts new file mode 100644 index 00000000000..9cc32d6fb45 --- /dev/null +++ b/dotcom-rendering/src/lib/puzzlesHubExperiment.node.test.ts @@ -0,0 +1,45 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { + isPuzzlesHubEnabled, + isPuzzlesHubVariant, +} from './puzzlesHubExperiment'; + +void nodeDescribe('isPuzzlesHubVariant', () => { + const testCases = [ + ['control', { 'puzzles-new-hub': 'control' }], + ['missing', {}], + ['unknown group', { 'puzzles-new-hub': 'other' }], + ['unrelated participation', { another: 'variant' }], + ] as const; + + for (const [name, participations] of testCases) { + void nodeIt(`rejects ${name}`, () => { + assert.equal(isPuzzlesHubVariant(participations), false); + }); + } + + void nodeIt('accepts only puzzles-new-hub:variant', () => { + assert.equal( + isPuzzlesHubVariant({ 'puzzles-new-hub': 'variant' }), + true, + ); + }); +}); + +void nodeDescribe('isPuzzlesHubEnabled', () => { + void nodeIt( + 'allow local development without an experiment participation', + () => { + assert.equal(isPuzzlesHubEnabled({}, true), true); + }, + ); + + void nodeIt('requires the variant outside local development', () => { + assert.equal(isPuzzlesHubEnabled({}, false), false); + assert.equal( + isPuzzlesHubEnabled({ 'puzzles-new-hub': 'variant' }, false), + true, + ); + }); +}); diff --git a/dotcom-rendering/src/lib/puzzlesHubExperiment.test.ts b/dotcom-rendering/src/lib/puzzlesHubExperiment.test.ts deleted file mode 100644 index 1a34669908f..00000000000 --- a/dotcom-rendering/src/lib/puzzlesHubExperiment.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { - isPuzzlesHubEnabled, - isPuzzlesHubVariant, -} from './puzzlesHubExperiment'; - -describe('isPuzzlesHubVariant', () => { - it.each([ - ['control', { 'puzzles-new-hub': 'control' }], - ['missing', {}], - ['unknown group', { 'puzzles-new-hub': 'other' }], - ['unrelated participation', { another: 'variant' }], - ])('rejects %s', (_, participations) => { - expect(isPuzzlesHubVariant(participations)).toBe(false); - }); - - it('accepts only puzzles-new-hub:variant', () => { - expect(isPuzzlesHubVariant({ 'puzzles-new-hub': 'variant' })).toBe( - true, - ); - }); -}); - -describe('isPuzzlesHubEnabled', () => { - it('allow local development without an experiment participation', () => { - expect(isPuzzlesHubEnabled({}, true)).toBe(true); - }); - - it('requires the variant outside local development', () => { - expect(isPuzzlesHubEnabled({}, false)).toBe(false); - expect( - isPuzzlesHubEnabled({ 'puzzles-new-hub': 'variant' }, false), - ).toBe(true); - }); -}); diff --git a/dotcom-rendering/src/lib/sendTargetingParams.apps.node.test.ts b/dotcom-rendering/src/lib/sendTargetingParams.apps.node.test.ts new file mode 100644 index 00000000000..abc8e293b66 --- /dev/null +++ b/dotcom-rendering/src/lib/sendTargetingParams.apps.node.test.ts @@ -0,0 +1,80 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { getTargetingParams } from './sendTargetingParams.apps'; + +void nodeDescribe('getTargetingParams', () => { + void nodeIt( + 'extracts ad targeting params from editionCommercialProperties in the format Bridget consumes', + () => { + const testEditionCommercialProperties = { + adTargeting: [ + { + name: 'su', + value: ['0'], + }, + { + name: 'k', + value: [ + 'us-politics', + 'state-of-georgia', + 'us-crime', + 'us-news', + 'donaldtrump', + ], + }, + { + name: 'edition', + value: 'uk', + }, + { + name: 'tn', + value: ['news'], + }, + { + name: 'co', + value: ['sam-levin', 'hugo-lowell'], + }, + { + name: 'sh', + value: 'https://www.theguardian.com/p/zm6gk', + }, + { + name: 'p', + value: 'ng', + }, + { + name: 'ct', + value: 'article', + }, + { + name: 'url', + value: '/us-news/2023/aug/24/trump-surrender-georgia-jail-overturn-2020-election', + }, + ], + }; + + const expectedValue = new Map([ + ['ct', 'article'], + ['co', 'sam-levin,hugo-lowell'], + [ + 'url', + '/us-news/2023/aug/24/trump-surrender-georgia-jail-overturn-2020-election', + ], + ['su', '0'], + ['edition', 'uk'], + ['tn', 'news'], + ['p', 'app'], + ['rp', 'dotcom-rendering'], + [ + 'k', + 'us-politics,state-of-georgia,us-crime,us-news,donaldtrump', + ], + ]); + + assert.deepEqual( + getTargetingParams(testEditionCommercialProperties), + expectedValue, + ); + }, + ); +}); diff --git a/dotcom-rendering/src/lib/sendTargetingParams.apps.test.ts b/dotcom-rendering/src/lib/sendTargetingParams.apps.test.ts deleted file mode 100644 index 83eb9e5a2b1..00000000000 --- a/dotcom-rendering/src/lib/sendTargetingParams.apps.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { getTargetingParams } from './sendTargetingParams.apps'; - -describe('getTargetingParams', () => { - it('extracts ad targeting params from editionCommercialProperties in the format Bridget consumes', () => { - const testEditionCommercialProperties = { - adTargeting: [ - { - name: 'su', - value: ['0'], - }, - { - name: 'k', - value: [ - 'us-politics', - 'state-of-georgia', - 'us-crime', - 'us-news', - 'donaldtrump', - ], - }, - { - name: 'edition', - value: 'uk', - }, - { - name: 'tn', - value: ['news'], - }, - { - name: 'co', - value: ['sam-levin', 'hugo-lowell'], - }, - { - name: 'sh', - value: 'https://www.theguardian.com/p/zm6gk', - }, - { - name: 'p', - value: 'ng', - }, - { - name: 'ct', - value: 'article', - }, - { - name: 'url', - value: '/us-news/2023/aug/24/trump-surrender-georgia-jail-overturn-2020-election', - }, - ], - }; - - const expectedValue = new Map([ - ['ct', 'article'], - ['co', 'sam-levin,hugo-lowell'], - [ - 'url', - '/us-news/2023/aug/24/trump-surrender-georgia-jail-overturn-2020-election', - ], - ['su', '0'], - ['edition', 'uk'], - ['tn', 'news'], - ['p', 'app'], - ['rp', 'dotcom-rendering'], - ['k', 'us-politics,state-of-georgia,us-crime,us-news,donaldtrump'], - ]); - - expect(getTargetingParams(testEditionCommercialProperties)).toEqual( - expectedValue, - ); - }); -}); diff --git a/dotcom-rendering/src/lib/theFilter.node.test.ts b/dotcom-rendering/src/lib/theFilter.node.test.ts new file mode 100644 index 00000000000..54cb8e70d7c --- /dev/null +++ b/dotcom-rendering/src/lib/theFilter.node.test.ts @@ -0,0 +1,40 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { isFilterPageId } from './theFilter'; + +void nodeDescribe('isFilterPageId', () => { + void nodeIt('returns true for a UK Filter article pageId', () => { + assert.equal( + isFilterPageId( + 'thefilter/2026/jul/02/jess-cartner-morleys-july-style-essentials-2026', + ), + true, + ); + }); + + void nodeIt('returns true for a US Filter article pageId', () => { + assert.equal( + isFilterPageId( + 'thefilter-us/2025/dec/27/best-wine-subscriptions-us', + ), + true, + ); + }); + + void nodeIt('returns false for a non-Filter pageId', () => { + assert.equal( + isFilterPageId('technology/2026/jan/01/some-other-article'), + false, + ); + }); + + void nodeIt( + 'returns false for a pageId that merely contains "thefilter" mid-string', + () => { + assert.equal( + isFilterPageId('lifestyle/thefilter-mentioned/some-article'), + false, + ); + }, + ); +}); diff --git a/dotcom-rendering/src/lib/theFilter.test.ts b/dotcom-rendering/src/lib/theFilter.test.ts deleted file mode 100644 index bc2723ddaf4..00000000000 --- a/dotcom-rendering/src/lib/theFilter.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { isFilterPageId } from './theFilter'; - -describe('isFilterPageId', () => { - it('returns true for a UK Filter article pageId', () => { - expect( - isFilterPageId( - 'thefilter/2026/jul/02/jess-cartner-morleys-july-style-essentials-2026', - ), - ).toBe(true); - }); - - it('returns true for a US Filter article pageId', () => { - expect( - isFilterPageId( - 'thefilter-us/2025/dec/27/best-wine-subscriptions-us', - ), - ).toBe(true); - }); - - it('returns false for a non-Filter pageId', () => { - expect( - isFilterPageId('technology/2026/jan/01/some-other-article'), - ).toBe(false); - }); - - it('returns false for a pageId that merely contains "thefilter" mid-string', () => { - expect( - isFilterPageId('lifestyle/thefilter-mentioned/some-article'), - ).toBe(false); - }); -}); diff --git a/dotcom-rendering/src/model/article-sections.test.ts b/dotcom-rendering/src/model/article-sections.node.test.ts similarity index 86% rename from dotcom-rendering/src/model/article-sections.test.ts rename to dotcom-rendering/src/model/article-sections.node.test.ts index 079cf8feb77..2bb70a722e8 100644 --- a/dotcom-rendering/src/model/article-sections.test.ts +++ b/dotcom-rendering/src/model/article-sections.node.test.ts @@ -1,6 +1,8 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; import { findBySubsection } from './article-sections'; -describe('returns section for each subsection', () => { +void nodeDescribe('returns section for each subsection', () => { const testCases = [ [[], 'Guardian'], [['books', 'childrens-books-site'], 'Books'], @@ -89,10 +91,10 @@ describe('returns section for each subsection', () => { [['tv-and-radio'], 'TvRadio'], ] as const; - it('returns correct Section for each test case', () => { + void nodeIt('returns correct Section for each test case', () => { for (const [subsections, section] of testCases) { for (const subsection of subsections) { - expect(findBySubsection(subsection).name).toEqual(section); + assert.equal(findBySubsection(subsection).name, section); } } }); diff --git a/dotcom-rendering/src/model/enhance-dots.node.test.ts b/dotcom-rendering/src/model/enhance-dots.node.test.ts new file mode 100644 index 00000000000..07359deb207 --- /dev/null +++ b/dotcom-rendering/src/model/enhance-dots.node.test.ts @@ -0,0 +1,80 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import type { FEElement } from '../types/content'; +import { enhanceDots } from './enhance-dots'; + +void nodeDescribe('Middot Tests', () => { + void nodeIt( + 'Output should not be the same as input as dot has been replaced', + () => { + const input: FEElement[] = [ + { + _type: 'model.dotcomrendering.pageElements.TextBlockElement', + elementId: 'mockId', + html: '

I am the first paragraph

', + }, + { + _type: 'model.dotcomrendering.pageElements.TextBlockElement', + elementId: 'mockId', + html: '

• I should have a dot.

', + }, + ]; + + const expectedOutput: FEElement[] = [ + { + _type: 'model.dotcomrendering.pageElements.TextBlockElement', + elementId: 'mockId', + html: '

I am the first paragraph

', + }, + { + _type: 'model.dotcomrendering.pageElements.TextBlockElement', + elementId: 'mockId', + html: '

I should have have a dot.

', + }, + ]; + + assert.notEqual(enhanceDots(input), expectedOutput); + }, + ); + + void nodeIt('It does not incorrectly replace * with dot spans', () => { + const input: FEElement[] = [ + { + _type: 'model.dotcomrendering.pageElements.TextBlockElement', + elementId: 'mockId', + html: '

I am the first paragraph

', + }, + { + _type: 'model.dotcomrendering.pageElements.TextBlockElement', + elementId: 'mockId', + html: '

*

', + }, + { + _type: 'model.dotcomrendering.pageElements.TextBlockElement', + elementId: 'mockId', + html: '

I am text.

', + }, + ]; + + const expectedOutput: FEElement[] = [ + { + _type: 'model.dotcomrendering.pageElements.TextBlockElement', + elementId: 'mockId', + html: '

I am the first paragraph

', + }, + { + _type: 'model.dotcomrendering.pageElements.TextBlockElement', + elementId: 'mockId', + html: '

*

', + }, + + { + _type: 'model.dotcomrendering.pageElements.TextBlockElement', + elementId: 'mockId', + html: '

I am text.

', + }, + ]; + + assert.deepEqual(enhanceDots(input), expectedOutput); + }); +}); diff --git a/dotcom-rendering/src/model/enhance-dots.test.ts b/dotcom-rendering/src/model/enhance-dots.test.ts deleted file mode 100644 index 2f4720e5724..00000000000 --- a/dotcom-rendering/src/model/enhance-dots.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -import type { FEElement } from '../types/content'; -import { enhanceDots } from './enhance-dots'; - -describe('Middot Tests', () => { - it('Output should not be the same as input as dot has been replaced', () => { - const input: FEElement[] = [ - { - _type: 'model.dotcomrendering.pageElements.TextBlockElement', - elementId: 'mockId', - html: '

I am the first paragraph

', - }, - { - _type: 'model.dotcomrendering.pageElements.TextBlockElement', - elementId: 'mockId', - html: '

• I should have a dot.

', - }, - ]; - - const expectedOutput: FEElement[] = [ - { - _type: 'model.dotcomrendering.pageElements.TextBlockElement', - elementId: 'mockId', - html: '

I am the first paragraph

', - }, - { - _type: 'model.dotcomrendering.pageElements.TextBlockElement', - elementId: 'mockId', - html: '

I should have have a dot.

', - }, - ]; - - expect(enhanceDots(input)).not.toBe(expectedOutput); - }); - - it('It does not incorrectly replace * with dot spans', () => { - const input: FEElement[] = [ - { - _type: 'model.dotcomrendering.pageElements.TextBlockElement', - elementId: 'mockId', - html: '

I am the first paragraph

', - }, - { - _type: 'model.dotcomrendering.pageElements.TextBlockElement', - elementId: 'mockId', - html: '

*

', - }, - { - _type: 'model.dotcomrendering.pageElements.TextBlockElement', - elementId: 'mockId', - html: '

I am text.

', - }, - ]; - - const expectedOutput: FEElement[] = [ - { - _type: 'model.dotcomrendering.pageElements.TextBlockElement', - elementId: 'mockId', - html: '

I am the first paragraph

', - }, - { - _type: 'model.dotcomrendering.pageElements.TextBlockElement', - elementId: 'mockId', - html: '

*

', - }, - - { - _type: 'model.dotcomrendering.pageElements.TextBlockElement', - elementId: 'mockId', - html: '

I am text.

', - }, - ]; - - expect(enhanceDots(input)).toEqual(expectedOutput); - }); -}); diff --git a/dotcom-rendering/src/model/enhance-product-summary.node.test.ts b/dotcom-rendering/src/model/enhance-product-summary.node.test.ts new file mode 100644 index 00000000000..d4812538e03 --- /dev/null +++ b/dotcom-rendering/src/model/enhance-product-summary.node.test.ts @@ -0,0 +1,103 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { enhanceProductSummary } from './enhance-product-summary'; +import { + findEnhancedProductSummary, + productElement, + productSummaryElement, +} from './enhance-product-summary.test-helpers'; + +void nodeDescribe('enhanceProductSummary', () => { + void nodeIt( + 'enhances product summary elements with its selected product elements', + () => { + const selectedIds = ['1', '2']; + const input = [ + productElement( + [ + 'https://www.homebase.co.uk/en-uk/tower-airx-t17166-5l-grey-single-basket-air-fryer-digital-air-fryer/p/0757395', + ], + '1', + ), + productElement( + [ + 'https://www.lakeland.co.uk/27537/lakeland-slimline-air-fryer-black-8l', + ], + '2', + ), + productElement( + [ + 'https://ninjakitchen.co.uk/product/ninja-double-stack-xl-9-5l-air-fryer-sl400uk-zidSL400UK', + ], + '3', + ), + productSummaryElement( + selectedIds.map((id) => ({ productId: id, ctaIndex: 0 })), + ), + ]; + + const output = enhanceProductSummary(input); + + const enhancedProductSummaryElement = + findEnhancedProductSummary(output); + + assert.equal(enhancedProductSummaryElement?.products.length, 2); + assert.deepEqual( + enhancedProductSummaryElement?.products.map( + (mapping) => mapping.productBlock.id, + ), + selectedIds, + ); + }, + ); + + void nodeIt( + 'enhances product summary elements with the correct CTA indices', + () => { + const summaryProducts = [ + { productId: '3', ctaIndex: 1 }, + { productId: '1', ctaIndex: 0 }, + ]; + const input = [ + productElement( + [ + 'https://www.homebase.co.uk/en-uk/tower-airx-t17166-5l-grey-single-basket-air-fryer-digital-air-fryer/p/0757395', + ], + '1', + ), + productElement( + [ + 'https://www.lakeland.co.uk/27537/lakeland-slimline-air-fryer-black-8l', + ], + '2', + ), + productElement( + [ + 'https://ninjakitchen.co.uk/product/ninja-double-stack-xl-9-5l-air-fryer-sl400uk-zidSL400UK', + ], + '3', + ), + productSummaryElement(summaryProducts), + ]; + + const output = enhanceProductSummary(input); + + const enhancedProductSummaryElement = + findEnhancedProductSummary(output); + + assert.equal(enhancedProductSummaryElement?.products.length, 2); + assert.deepEqual( + enhancedProductSummaryElement?.products.map( + (mapping) => mapping.ctaIndex, + ), + [1, 0], + ); + assert.deepEqual( + enhancedProductSummaryElement?.products.map( + (mapping) => mapping.productBlock.id, + ), + ['3', '1'], + ); + }, + ); +}); diff --git a/dotcom-rendering/src/model/enhance-product-summary.test.ts b/dotcom-rendering/src/model/enhance-product-summary.test.ts deleted file mode 100644 index e53cca12ced..00000000000 --- a/dotcom-rendering/src/model/enhance-product-summary.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { enhanceProductSummary } from './enhance-product-summary'; -import { - findEnhancedProductSummary, - productElement, - productSummaryElement, -} from './enhance-product-summary.test-helpers'; - -describe('enhanceProductSummary', () => { - it('enhances product summary elements with its selected product elements', () => { - const selectedIds = ['1', '2']; - const input = [ - productElement( - [ - 'https://www.homebase.co.uk/en-uk/tower-airx-t17166-5l-grey-single-basket-air-fryer-digital-air-fryer/p/0757395', - ], - '1', - ), - productElement( - [ - 'https://www.lakeland.co.uk/27537/lakeland-slimline-air-fryer-black-8l', - ], - '2', - ), - productElement( - [ - 'https://ninjakitchen.co.uk/product/ninja-double-stack-xl-9-5l-air-fryer-sl400uk-zidSL400UK', - ], - '3', - ), - productSummaryElement( - selectedIds.map((id) => ({ productId: id, ctaIndex: 0 })), - ), - ]; - - const output = enhanceProductSummary(input); - - const enhancedProductSummaryElement = - findEnhancedProductSummary(output); - - expect(enhancedProductSummaryElement?.products).toHaveLength(2); - expect( - enhancedProductSummaryElement?.products.map( - (mapping) => mapping.productBlock.id, - ), - ).toEqual(selectedIds); - }); - - it('enhances product summary elements with the correct CTA indices', () => { - const summaryProducts = [ - { productId: '3', ctaIndex: 1 }, - { productId: '1', ctaIndex: 0 }, - ]; - const input = [ - productElement( - [ - 'https://www.homebase.co.uk/en-uk/tower-airx-t17166-5l-grey-single-basket-air-fryer-digital-air-fryer/p/0757395', - ], - '1', - ), - productElement( - [ - 'https://www.lakeland.co.uk/27537/lakeland-slimline-air-fryer-black-8l', - ], - '2', - ), - productElement( - [ - 'https://ninjakitchen.co.uk/product/ninja-double-stack-xl-9-5l-air-fryer-sl400uk-zidSL400UK', - ], - '3', - ), - productSummaryElement(summaryProducts), - ]; - - const output = enhanceProductSummary(input); - - const enhancedProductSummaryElement = - findEnhancedProductSummary(output); - - expect(enhancedProductSummaryElement?.products).toHaveLength(2); - expect( - enhancedProductSummaryElement?.products.map( - (mapping) => mapping.ctaIndex, - ), - ).toEqual([1, 0]); - expect( - enhancedProductSummaryElement?.products.map( - (mapping) => mapping.productBlock.id, - ), - ).toEqual(['3', '1']); - }); -}); diff --git a/dotcom-rendering/src/model/enhance-videos.test.ts b/dotcom-rendering/src/model/enhance-videos.node.test.ts similarity index 67% rename from dotcom-rendering/src/model/enhance-videos.test.ts rename to dotcom-rendering/src/model/enhance-videos.node.test.ts index d434f8da4c1..a4ebfde5b09 100644 --- a/dotcom-rendering/src/model/enhance-videos.test.ts +++ b/dotcom-rendering/src/model/enhance-videos.node.test.ts @@ -1,10 +1,12 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; import { ArticleDesign, type ArticleFormat } from '../lib/articleFormat'; import type { FEElement } from '../types/content'; import { enhanceGuVideos } from './enhance-videos'; -describe('Enhance Videos', () => { - describe('for GuVideoElement', () => { - it('sets the html of the GuVideoBlockElement', () => { +void nodeDescribe('Enhance Videos', () => { + void nodeDescribe('for GuVideoElement', () => { + void nodeIt('sets the html of the GuVideoBlockElement', () => { const html = ''; const videoElement: FEElement = { @@ -29,7 +31,8 @@ describe('Enhance Videos', () => { design: ArticleDesign.Video, } as unknown as ArticleFormat; - expect(enhanceGuVideos(format, html)(inputElements)).toEqual( + assert.deepEqual( + enhanceGuVideos(format, html)(inputElements), expectedOutput, ); }); diff --git a/dotcom-rendering/src/model/enhanceCommercialProperties.test.ts b/dotcom-rendering/src/model/enhanceCommercialProperties.node.test.ts similarity index 75% rename from dotcom-rendering/src/model/enhanceCommercialProperties.test.ts rename to dotcom-rendering/src/model/enhanceCommercialProperties.node.test.ts index 765f6e412a2..10c5a976a8f 100644 --- a/dotcom-rendering/src/model/enhanceCommercialProperties.test.ts +++ b/dotcom-rendering/src/model/enhanceCommercialProperties.node.test.ts @@ -1,3 +1,5 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; import { Labs } from '../../fixtures/generated/fe-articles/Labs'; import { Standard } from '../../fixtures/generated/fe-articles/Standard'; import type { CommercialProperties } from '../types/commercial'; @@ -5,15 +7,16 @@ import { enhanceCommercialProperties } from './enhanceCommercialProperties'; const isNumber = (width: unknown): width is number => typeof width === 'number'; -describe('Enhance Branding', () => { - it('does not change properties if they have no branding', () => { +void nodeDescribe('Enhance Branding', () => { + void nodeIt('does not change properties if they have no branding', () => { const { commercialProperties } = Standard; - expect(enhanceCommercialProperties(commercialProperties)).toEqual( + assert.deepEqual( + enhanceCommercialProperties(commercialProperties), commercialProperties, ); }); - it('should have no widths above 140', () => { + void nodeIt('should have no widths above 140', () => { const { commercialProperties: partial } = Labs; const commercialProperties: CommercialProperties = { ...partial, @@ -43,7 +46,7 @@ describe('Enhance Branding', () => { .map((p) => p.branding?.logo.dimensions.width) .filter(isNumber); - expect(Math.max(...dimensionsFail)).toBeGreaterThan(140); + assert.ok(Math.max(...dimensionsFail) > 140); const dimensionsPass = Object.values( enhanceCommercialProperties(commercialProperties), @@ -51,6 +54,6 @@ describe('Enhance Branding', () => { .map((p) => p.branding?.logo.dimensions.width) .filter(isNumber); - expect(Math.max(...dimensionsPass)).toBeLessThanOrEqual(140); + assert.ok(Math.max(...dimensionsPass) <= 140); }); }); diff --git a/dotcom-rendering/src/model/enhanceLists.test.ts b/dotcom-rendering/src/model/enhanceLists.node.test.ts similarity index 88% rename from dotcom-rendering/src/model/enhanceLists.test.ts rename to dotcom-rendering/src/model/enhanceLists.node.test.ts index e59476b04d3..d8cd0b0ca52 100644 --- a/dotcom-rendering/src/model/enhanceLists.test.ts +++ b/dotcom-rendering/src/model/enhanceLists.node.test.ts @@ -1,9 +1,11 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; import type { FEElement } from '../types/content'; import type { TagType } from '../types/tag'; import { enhanceLists } from './enhanceLists'; -describe('Enhance lists', () => { - it('enhances a multi-byline element correctly', () => { +void nodeDescribe('Enhance lists', () => { + void nodeIt('enhances a multi-byline element correctly', () => { const elementsEnhancer = (elements: FEElement[]): FEElement[] => elements; @@ -92,7 +94,8 @@ describe('Enhance lists', () => { }, ]; - expect(enhanceLists(elementsEnhancer, tags)(inputElements)).toEqual( + assert.deepEqual( + enhanceLists(elementsEnhancer, tags)(inputElements), outputElements, ); }); diff --git a/dotcom-rendering/src/model/enhanceTags.test.ts b/dotcom-rendering/src/model/enhanceTags.node.test.ts similarity index 80% rename from dotcom-rendering/src/model/enhanceTags.test.ts rename to dotcom-rendering/src/model/enhanceTags.node.test.ts index 42d074e323f..53ce635531f 100644 --- a/dotcom-rendering/src/model/enhanceTags.test.ts +++ b/dotcom-rendering/src/model/enhanceTags.node.test.ts @@ -1,8 +1,10 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; import type { FETagType } from '../types/tag'; import { enhanceTags } from './enhanceTags'; -describe('enhanceTags', () => { - it('maps a list of FETagType to TagType', () => { +void nodeDescribe('enhanceTags', () => { + void nodeIt('maps a list of FETagType to TagType', () => { const feTags: FETagType[] = [ { properties: { @@ -20,7 +22,7 @@ describe('enhanceTags', () => { const tags = enhanceTags(feTags); - expect(tags).toEqual([ + assert.deepEqual(tags, [ { id: 'profile/morwennaferrier', type: 'Contributor', From 5d38ceed1a373811776259385eb85ab21481dabf Mon Sep 17 00:00:00 2001 From: Ravi <7014230+arelra@users.noreply.github.com> Date: Fri, 11 Sep 2026 21:50:13 +0100 Subject: [PATCH 3/8] Migrate third batch of unit tests to Node's test runner Co-authored-by: GPT-5.6 Sol --- .../src/cricketMatch.node.test.ts | 2 - dotcom-rendering/src/cricketMatch.test.ts | 78 -- dotcom-rendering/src/footballMatches.test.ts | 171 ----- .../src/lib/affiliateLinksUtils.node.test.ts | 141 ++++ .../src/lib/affiliateLinksUtils.test.ts | 124 --- .../src/lib/branding.node.test.ts | 708 ++++++++++++++++++ dotcom-rendering/src/lib/branding.test.ts | 14 + dotcom-rendering/src/lib/byline.node.test.ts | 289 +++++++ dotcom-rendering/src/lib/byline.test.ts | 268 ------- .../src/lib/decide-cation.node.test.ts | 130 ++++ .../src/lib/decide-cation.test.ts | 109 --- .../src/lib/getFrontsAdPositions.node.test.ts | 691 +++++++++++++++++ .../src/lib/getFrontsAdPositions.test.ts | 2 + .../src/lib/liveblogAdSlots.node.test.ts | 286 +++++++ .../src/lib/liveblogAdSlots.test.ts | 260 ------- ...tion.test.ts => notification.node.test.ts} | 119 +-- dotcom-rendering/src/lib/video.node.test.ts | 502 +++++++++++++ dotcom-rendering/src/lib/video.test.ts | 100 ++- .../model/buildLightboxImages.node.test.ts | 422 +++++++++++ .../src/model/buildLightboxImages.test.ts | 347 --------- .../enhance-ad-placeholders.node.test.ts | 371 +++++++++ .../src/model/enhance-ad-placeholders.test.ts | 315 -------- ...e.test.ts => enhanceTimeline.node.test.ts} | 58 +- ....ts => extractTrendingTopics.node.test.ts} | 117 +-- .../src/model/groupTrailsByDates.node.test.ts | 139 ++++ .../src/model/groupTrailsByDates.test.ts | 2 + .../src/model/unwrapHtml.node.test.ts | 145 ++++ dotcom-rendering/src/model/unwrapHtml.test.ts | 136 ---- .../src/model/validate.node.test.ts | 79 ++ .../model/validate.puzzlesPage.node.test.ts | 198 +++++ .../src/model/validate.puzzlesPage.test.ts | 20 +- dotcom-rendering/src/model/validate.test.ts | 6 +- 32 files changed, 4349 insertions(+), 2000 deletions(-) delete mode 100644 dotcom-rendering/src/cricketMatch.test.ts delete mode 100644 dotcom-rendering/src/footballMatches.test.ts create mode 100644 dotcom-rendering/src/lib/affiliateLinksUtils.node.test.ts delete mode 100644 dotcom-rendering/src/lib/affiliateLinksUtils.test.ts create mode 100644 dotcom-rendering/src/lib/branding.node.test.ts create mode 100644 dotcom-rendering/src/lib/byline.node.test.ts delete mode 100644 dotcom-rendering/src/lib/byline.test.ts create mode 100644 dotcom-rendering/src/lib/decide-cation.node.test.ts delete mode 100644 dotcom-rendering/src/lib/decide-cation.test.ts create mode 100644 dotcom-rendering/src/lib/getFrontsAdPositions.node.test.ts create mode 100644 dotcom-rendering/src/lib/liveblogAdSlots.node.test.ts delete mode 100644 dotcom-rendering/src/lib/liveblogAdSlots.test.ts rename dotcom-rendering/src/lib/{notification.test.ts => notification.node.test.ts} (57%) create mode 100644 dotcom-rendering/src/lib/video.node.test.ts create mode 100644 dotcom-rendering/src/model/buildLightboxImages.node.test.ts delete mode 100644 dotcom-rendering/src/model/buildLightboxImages.test.ts create mode 100644 dotcom-rendering/src/model/enhance-ad-placeholders.node.test.ts delete mode 100644 dotcom-rendering/src/model/enhance-ad-placeholders.test.ts rename dotcom-rendering/src/model/{enhanceTimeline.test.ts => enhanceTimeline.node.test.ts} (78%) rename dotcom-rendering/src/model/{extractTrendingTopics.test.ts => extractTrendingTopics.node.test.ts} (59%) create mode 100644 dotcom-rendering/src/model/groupTrailsByDates.node.test.ts create mode 100644 dotcom-rendering/src/model/unwrapHtml.node.test.ts delete mode 100644 dotcom-rendering/src/model/unwrapHtml.test.ts create mode 100644 dotcom-rendering/src/model/validate.node.test.ts create mode 100644 dotcom-rendering/src/model/validate.puzzlesPage.node.test.ts diff --git a/dotcom-rendering/src/cricketMatch.node.test.ts b/dotcom-rendering/src/cricketMatch.node.test.ts index e5f3ab22b1a..53651c7bb5f 100644 --- a/dotcom-rendering/src/cricketMatch.node.test.ts +++ b/dotcom-rendering/src/cricketMatch.node.test.ts @@ -60,7 +60,6 @@ void nodeDescribe('parseCricketMatchV2', () => { assert.deepEqual(result.result, { type: 'abandoned', description: 'Match abandoned due to rain', - winner: undefined, }); }); @@ -77,7 +76,6 @@ void nodeDescribe('parseCricketMatchV2', () => { assert.deepEqual(result.result, { type: 'no-result', description: 'No result', - winner: undefined, }); }); }); diff --git a/dotcom-rendering/src/cricketMatch.test.ts b/dotcom-rendering/src/cricketMatch.test.ts deleted file mode 100644 index e6be85fb996..00000000000 --- a/dotcom-rendering/src/cricketMatch.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { resultMatch, liveMatch } from '../fixtures/manual/cricketMatch'; -import { parseCricketMatch } from './cricketMatch'; - -describe('parseCricketMatchV2', () => { - it('parses a winner result cricket match correctly', () => { - const result = parseCricketMatch(resultMatch).getOrThrow( - 'Expected parsing cricket match to succeed', - ); - - expect(result.kind).toEqual('Result'); - expect(result.result).toEqual({ - type: 'home-win', - description: 'England win by 115 runs', - winner: { - type: 'runs', - team: 'England', - margin: 115, - }, - }); - expect(result.matchDate).toEqual(new Date('2026-06-17T10:00:00.000Z')); - }); - - it('parses a cricket match in pre-match status', () => { - const result = parseCricketMatch({ - ...liveMatch, - result: 'pre-match', - fullResult: undefined, - }).getOrThrow('Expected parsing cricket match to succeed'); - - expect(result.kind).toEqual('Fixture'); - expect(result.result).toEqual(undefined); - }); - - it('parses a cricket match in in-play status', () => { - const result = parseCricketMatch({ - ...liveMatch, - result: 'in-play', - fullResult: undefined, - }).getOrThrow('Expected parsing cricket match to succeed'); - - expect(result.kind).toEqual('Live'); - expect(result.result).toEqual(undefined); - }); - - it('parses an abandoned cricket match correctly', () => { - const result = parseCricketMatch({ - ...liveMatch, - fullResult: { - resultType: 'abandoned', - description: 'Match abandoned due to rain', - winner: undefined, - }, - }).getOrThrow('Expected parsing cricket match to succeed'); - - expect(result.result).toEqual({ - type: 'abandoned', - description: 'Match abandoned due to rain', - winner: undefined, - }); - }); - - it('parses a cricket match with no winner', () => { - const result = parseCricketMatch({ - ...liveMatch, - fullResult: { - resultType: 'no-result', - description: 'No result', - winner: undefined, - }, - }).getOrThrow('Expected parsing cricket match to succeed'); - - expect(result.result).toEqual({ - type: 'no-result', - description: 'No result', - winner: undefined, - }); - }); -}); diff --git a/dotcom-rendering/src/footballMatches.test.ts b/dotcom-rendering/src/footballMatches.test.ts deleted file mode 100644 index 35faf608661..00000000000 --- a/dotcom-rendering/src/footballMatches.test.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { footballData } from '../fixtures/generated/football-live'; -import { - emptyMatches, - liveMatch, - matchDayLive, - matchDayLiveSecondHalf, - matchFixture, - matchResult, -} from '../fixtures/manual/footballMatches'; -import { parse } from './footballMatches'; -import type { - FEFootballMatch, - FEMatchByDateAndCompetition, - FEResult, -} from './frontend/feFootballMatchListPage'; - -const withMatches = ( - matches: FEFootballMatch[], -): FEMatchByDateAndCompetition[] => - emptyMatches.map((day) => ({ - ...day, - competitionMatches: day.competitionMatches.map((competition) => ({ - ...competition, - matches, - })), - })); - -describe('footballMatches', () => { - it('should parse match fixtures correctly', () => { - const result = parse(footballData.matchesList).getOrThrow( - 'Expected football match parsing to succeed', - ); - - expect(result.length).toBe(1); - - const day = result[0]; - expect(day?.dateISOString).toBe('2025-04-28T00:00:00.000Z'); - expect(day?.competitions.length).toBe(2); - - const competition = day?.competitions[0]; - expect(competition?.name).toBe('Serie A'); - expect(competition?.matches[0]?.kind).toBe('Fixture'); - expect(competition?.tag).toBe('football/serieafootball'); - }); - - it('should return an error when football days have invalid dates', () => { - const invalidDate: FEMatchByDateAndCompetition[] = emptyMatches.map( - (day) => ({ - ...day, - date: 'foo', - }), - ); - - const result = parse(invalidDate).getErrorOrThrow( - 'Expected football match parsing to fail', - ); - - expect(result.kind).toBe('FootballDayInvalidDate'); - }); - - it('should return an error when football matches have an invalid date', () => { - const invalidMatchResult: FEMatchByDateAndCompetition[] = withMatches([ - matchFixture, - { ...matchResult, date: '' }, - matchDayLive, - ]); - const invalidMatchFixture: FEMatchByDateAndCompetition[] = withMatches([ - { ...matchFixture, date: '' }, - matchResult, - matchDayLive, - ]); - const invalidLiveMatch: FEMatchByDateAndCompetition[] = withMatches([ - matchResult, - matchFixture, - { ...matchDayLive, date: '' }, - ]); - - const resultOne = parse(invalidMatchResult).getErrorOrThrow( - 'Expected football match parsing to fail', - ); - const resultTwo = parse(invalidMatchFixture).getErrorOrThrow( - 'Expected football match parsing to fail', - ); - const resultThree = parse(invalidLiveMatch).getErrorOrThrow( - 'Expected football match parsing to fail', - ); - - expect(resultOne.kind).toBe('FootballMatchInvalidDate'); - expect(resultTwo.kind).toBe('FootballMatchInvalidDate'); - - if (resultThree.kind !== 'InvalidMatchDay') { - throw new Error('Expected an invalid match day error'); - } - - expect(resultThree.errors[0]!.kind).toBe('FootballMatchInvalidDate'); - }); - - it('should return an error when it receives a live match', () => { - const result = parse(withMatches([liveMatch])).getErrorOrThrow( - 'Expected football match parsing to fail', - ); - - expect(result.kind).toBe('UnexpectedLiveMatch'); - }); - it('should return a clean team name', () => { - const matchesListWithTeamName = (teamName: string): FEResult => { - return { - ...matchResult, - homeTeam: { - ...matchResult.homeTeam, - name: teamName, - }, - }; - }; - - const uncleanToCleanNames: Record = { - Ladies: '', - Holland: 'The Netherlands', - 'Ivory Coast': 'Côte d’Ivoire', - 'Union Saint Gilloise': 'Union Saint-Gilloise', - 'Bosnia-Herzegovina': 'Bosnia and Herzegovina', - 'Congo DR': 'DR Congo', - Curacao: 'Curaçao', - 'Czech Republic': 'Czechia', - }; - - for (const [uncleanName, cleanName] of Object.entries( - uncleanToCleanNames, - )) { - const matchDay = parse( - withMatches([matchesListWithTeamName(uncleanName)]), - ).getOrThrow('Expected football match parsing to succeed'); - - const match = matchDay[0]!.competitions[0]!.matches[0]; - if (match?.kind !== 'Result') { - throw new Error('Expected Result'); - } - - expect(match.homeTeam.name).toBe(cleanName); - } - }); - it('should replace known live match status with our status', () => { - const matchDay = parse( - withMatches([matchDayLiveSecondHalf]), - ).getOrThrow('Expected football live match parsing to succeed'); - - const match = matchDay[0]!.competitions[0]!.matches[0]; - if (match?.kind !== 'Live') { - throw new Error('Expected live match'); - } - - expect(match.status).toBe('2nd'); - }); - it('should replace unknown live match status with first two characters', () => { - const matchDayLiveUnknownStatus = { - ...matchDayLiveSecondHalf, - matchStatus: 'Something odd', - }; - - const matchDay = parse( - withMatches([matchDayLiveUnknownStatus]), - ).getOrThrow('Expected football live match parsing to succeed'); - - const match = matchDay[0]!.competitions[0]!.matches[0]; - if (match?.kind !== 'Live') { - throw new Error('Expected live match'); - } - - expect(match.status).toBe('So'); - }); -}); diff --git a/dotcom-rendering/src/lib/affiliateLinksUtils.node.test.ts b/dotcom-rendering/src/lib/affiliateLinksUtils.node.test.ts new file mode 100644 index 00000000000..9617226ae29 --- /dev/null +++ b/dotcom-rendering/src/lib/affiliateLinksUtils.node.test.ts @@ -0,0 +1,141 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { + buildMergedAbTestString, + buildXcustParamForAffiliateLink, + extractAbTestParticipationFromUrl, +} from './affiliateLinksUtils'; + +void nodeDescribe('extractAbTestParticipationFromUrl', () => { + void nodeIt('extracts AB test participations from xcust', () => { + const url = + 'https://go.skimresources.com/?id=114047X1572903&url=https%3A%2F%2Fwww.argos.co.uk%2Fproduct%2F8112969&sref=https://www.theguardian.com/thefilter/2024/nov/21/best-coffee-machines&xcust=referrer%7Cwww.theguardian.com%7CaccountId%7C114047X1572903%7CabTestParticipations%7Cthefilter-at-a-glance-redesign-v2%3Acarousel%7CcomponentId%7Ccarousel-card'; + + assert.deepEqual(extractAbTestParticipationFromUrl(url), { + 'thefilter-at-a-glance-redesign-v2': 'carousel', + }); + }); + + void nodeIt( + 'returns empty object when xcust has no AB test section', + () => { + const url = + 'https://go.skimresources.com/?id=114047X1572903&url=https%3A%2F%2Fwww.argos.co.uk%2Fproduct%2F8112969&xcust=referrer%7Cwww.theguardian.com%7CaccountId%7C114047X1572903%7CcomponentId%7Ccarousel-card'; + + assert.deepEqual(extractAbTestParticipationFromUrl(url), {}); + }, + ); +}); + +void nodeDescribe('buildXcustValueForAffiliateLink', () => { + void nodeIt('returns xcust value for skimlinks URLs', () => { + const xcustResult = buildXcustParamForAffiliateLink({ + url: new URL( + 'https://go.skimresources.com/?id=1234X9876&url=https%3A%2F%2Fwww.theguardian.com%2Fuk', + ), + abTestParticipations: {}, + utmParamsString: '', + referrerDomain: 'www.theguardian.com', + xcustComponentId: null, + }); + + assert.equal( + xcustResult, + 'referrer|www.theguardian.com|accountId|1234X9876', + ); + }); + + void nodeIt('includes optional xcust values when provided', () => { + const xcustResult = buildXcustParamForAffiliateLink({ + url: new URL( + 'https://go.skimresources.com/?id=1111&url=https%3A%2F%2Fwww.theguardian.com%2Fus-news', + ), + abTestParticipations: { abTest1: 'variantA' }, + utmParamsString: 'utm_medium|cpc|utm_campaign|summer', + referrerDomain: 'www.theguardian.com', + xcustComponentId: 'related-content', + }); + + assert.equal( + xcustResult, + 'referrer|www.theguardian.com|accountId|1111|abTestParticipations|abTest1:variantA|utm_medium|cpc|utm_campaign|summer|componentId|related-content', + ); + }); + + void nodeIt('merges existing and incoming AB test participations', () => { + const xcustResult = buildXcustParamForAffiliateLink({ + url: new URL( + 'https://go.skimresources.com/?id=1111&url=https%3A%2F%2Fwww.theguardian.com%2Fus-news&xcust=referrer%7Cwww.theguardian.com%7CaccountId%7C1111%7CabTestParticipations%7CexistingTest%3Acontrol%2CabTest1%3AoldVariant', + ), + abTestParticipations: { abTest1: 'variantA', newTest: 'variantB' }, + utmParamsString: '', + referrerDomain: 'www.theguardian.com', + xcustComponentId: null, + }); + + assert.ok(xcustResult.includes('|abTestParticipations|')); + assert.ok(xcustResult.includes('existingTest:control')); + assert.ok(xcustResult.includes('newTest:variantB')); + assert.ok(xcustResult.includes('abTest1:oldVariant')); + assert.ok(!xcustResult.includes('abTest1:variantA')); + }); + + void nodeIt( + 'preserves existing AB participations when url already has xcust', + () => { + const xcustResult = buildXcustParamForAffiliateLink({ + url: new URL( + 'https://go.skimresources.com/?id=1111&url=https%3A%2F%2Fwww.theguardian.com%2Fus-news&xcust=referrer%7Cold.example%7CaccountId%7C1111%7CabTestParticipations%7ColdTest%3AoldVariant', + ), + abTestParticipations: { newTest: 'newVariant' }, + utmParamsString: '', + referrerDomain: 'www.theguardian.com', + xcustComponentId: null, + }); + + assert.ok( + xcustResult.includes( + 'referrer|www.theguardian.com|accountId|1111', + ), + ); + assert.ok(xcustResult.includes('newTest:newVariant')); + assert.ok(xcustResult.includes('oldTest:oldVariant')); + }, + ); +}); + +void nodeDescribe('buildMergedAbTestString', () => { + void nodeIt( + 'returns incoming AB test string when URL has no existing participations', + () => { + const url = + 'https://go.skimresources.com/?id=1111&url=https%3A%2F%2Fwww.theguardian.com%2Fus-news'; + + assert.equal( + buildMergedAbTestString({ + url, + abTestParticipations: { + abTest1: 'variantA', + abTest2: 'variantB', + }, + }), + 'abTest1:variantA,abTest2:variantB', + ); + }, + ); + + void nodeIt('keeps existing URL values when keys collide', () => { + const url = + 'https://go.skimresources.com/?id=1111&url=https%3A%2F%2Fwww.theguardian.com%2Fus-news&xcust=referrer%7Cwww.theguardian.com%7CaccountId%7C1111%7CabTestParticipations%7ColdTest%3AoldVariant'; + + assert.equal( + buildMergedAbTestString({ + url, + abTestParticipations: { + newTest: 'newVariant', + }, + }), + 'newTest:newVariant,oldTest:oldVariant', + ); + }); +}); diff --git a/dotcom-rendering/src/lib/affiliateLinksUtils.test.ts b/dotcom-rendering/src/lib/affiliateLinksUtils.test.ts deleted file mode 100644 index afbfd29bb00..00000000000 --- a/dotcom-rendering/src/lib/affiliateLinksUtils.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { - buildMergedAbTestString, - buildXcustParamForAffiliateLink, - extractAbTestParticipationFromUrl, -} from './affiliateLinksUtils'; - -describe('extractAbTestParticipationFromUrl', () => { - it('extracts AB test participations from xcust', () => { - const url = - 'https://go.skimresources.com/?id=114047X1572903&url=https%3A%2F%2Fwww.argos.co.uk%2Fproduct%2F8112969&sref=https://www.theguardian.com/thefilter/2024/nov/21/best-coffee-machines&xcust=referrer%7Cwww.theguardian.com%7CaccountId%7C114047X1572903%7CabTestParticipations%7Cthefilter-at-a-glance-redesign-v2%3Acarousel%7CcomponentId%7Ccarousel-card'; - - expect(extractAbTestParticipationFromUrl(url)).toEqual({ - 'thefilter-at-a-glance-redesign-v2': 'carousel', - }); - }); - - it('returns empty object when xcust has no AB test section', () => { - const url = - 'https://go.skimresources.com/?id=114047X1572903&url=https%3A%2F%2Fwww.argos.co.uk%2Fproduct%2F8112969&xcust=referrer%7Cwww.theguardian.com%7CaccountId%7C114047X1572903%7CcomponentId%7Ccarousel-card'; - - expect(extractAbTestParticipationFromUrl(url)).toEqual({}); - }); -}); - -describe('buildXcustValueForAffiliateLink', () => { - it('returns xcust value for skimlinks URLs', () => { - const xcustResult = buildXcustParamForAffiliateLink({ - url: new URL( - 'https://go.skimresources.com/?id=1234X9876&url=https%3A%2F%2Fwww.theguardian.com%2Fuk', - ), - abTestParticipations: {}, - utmParamsString: '', - referrerDomain: 'www.theguardian.com', - xcustComponentId: null, - }); - - expect(xcustResult).toBe( - 'referrer|www.theguardian.com|accountId|1234X9876', - ); - }); - - it('includes optional xcust values when provided', () => { - const xcustResult = buildXcustParamForAffiliateLink({ - url: new URL( - 'https://go.skimresources.com/?id=1111&url=https%3A%2F%2Fwww.theguardian.com%2Fus-news', - ), - abTestParticipations: { abTest1: 'variantA' }, - utmParamsString: 'utm_medium|cpc|utm_campaign|summer', - referrerDomain: 'www.theguardian.com', - xcustComponentId: 'related-content', - }); - - expect(xcustResult).toBe( - 'referrer|www.theguardian.com|accountId|1111|abTestParticipations|abTest1:variantA|utm_medium|cpc|utm_campaign|summer|componentId|related-content', - ); - }); - - it('merges existing and incoming AB test participations', () => { - const xcustResult = buildXcustParamForAffiliateLink({ - url: new URL( - 'https://go.skimresources.com/?id=1111&url=https%3A%2F%2Fwww.theguardian.com%2Fus-news&xcust=referrer%7Cwww.theguardian.com%7CaccountId%7C1111%7CabTestParticipations%7CexistingTest%3Acontrol%2CabTest1%3AoldVariant', - ), - abTestParticipations: { abTest1: 'variantA', newTest: 'variantB' }, - utmParamsString: '', - referrerDomain: 'www.theguardian.com', - xcustComponentId: null, - }); - - expect(xcustResult).toContain('|abTestParticipations|'); - expect(xcustResult).toContain('existingTest:control'); - expect(xcustResult).toContain('newTest:variantB'); - expect(xcustResult).toContain('abTest1:oldVariant'); - expect(xcustResult).not.toContain('abTest1:variantA'); - }); - - it('preserves existing AB participations when url already has xcust', () => { - const xcustResult = buildXcustParamForAffiliateLink({ - url: new URL( - 'https://go.skimresources.com/?id=1111&url=https%3A%2F%2Fwww.theguardian.com%2Fus-news&xcust=referrer%7Cold.example%7CaccountId%7C1111%7CabTestParticipations%7ColdTest%3AoldVariant', - ), - abTestParticipations: { newTest: 'newVariant' }, - utmParamsString: '', - referrerDomain: 'www.theguardian.com', - xcustComponentId: null, - }); - - expect(xcustResult).toContain( - 'referrer|www.theguardian.com|accountId|1111', - ); - expect(xcustResult).toContain('newTest:newVariant'); - expect(xcustResult).toContain('oldTest:oldVariant'); - }); -}); - -describe('buildMergedAbTestString', () => { - it('returns incoming AB test string when URL has no existing participations', () => { - const url = - 'https://go.skimresources.com/?id=1111&url=https%3A%2F%2Fwww.theguardian.com%2Fus-news'; - - expect( - buildMergedAbTestString({ - url, - abTestParticipations: { - abTest1: 'variantA', - abTest2: 'variantB', - }, - }), - ).toBe('abTest1:variantA,abTest2:variantB'); - }); - - it('keeps existing URL values when keys collide', () => { - const url = - 'https://go.skimresources.com/?id=1111&url=https%3A%2F%2Fwww.theguardian.com%2Fus-news&xcust=referrer%7Cwww.theguardian.com%7CaccountId%7C1111%7CabTestParticipations%7ColdTest%3AoldVariant'; - - expect( - buildMergedAbTestString({ - url, - abTestParticipations: { - newTest: 'newVariant', - }, - }), - ).toBe('newTest:newVariant,oldTest:oldVariant'); - }); -}); diff --git a/dotcom-rendering/src/lib/branding.node.test.ts b/dotcom-rendering/src/lib/branding.node.test.ts new file mode 100644 index 00000000000..a197d28314b --- /dev/null +++ b/dotcom-rendering/src/lib/branding.node.test.ts @@ -0,0 +1,708 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import type { Branding } from '../types/branding'; +import { decideCollectionBranding, decideTagPageBranding } from './branding'; + +// For the purpose of these tests we don't care about the contents of the logo objects +const logo = {} as Branding['logo']; + +const assertMatchObject = (actual: unknown, expected: unknown): void => { + if (expected === null || typeof expected !== 'object') { + assert.deepEqual(actual, expected); + return; + } + + assert.ok(actual !== null && typeof actual === 'object'); + for (const [key, value] of Object.entries(expected)) { + assertMatchObject((actual as Record)[key], value); + } +}; + +void nodeDescribe('decideCollectionBranding', () => { + void nodeIt('picks branding from a card by their edition', () => { + const cards = [ + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' as const }, + branding: { + brandingType: { name: 'paid-content' as const }, + sponsorName: 'foo', + aboutThisLink: '', + logo, + }, + }, + { + edition: { id: 'US' as const }, + branding: { + brandingType: { name: 'sponsored' as const }, + sponsorName: 'bar', + aboutThisLink: '', + logo, + }, + }, + ], + }, + }, + ]; + const ukBranding = decideCollectionBranding({ + frontBranding: undefined, + couldDisplayFrontBranding: false, + cards, + editionId: 'UK', + isContainerBranding: false, + }); + assertMatchObject(ukBranding, { + kind: 'paid-content', + isFrontBranding: false, + branding: { + brandingType: { name: 'paid-content' }, + sponsorName: 'foo', + aboutThisLink: '', + logo, + }, + isContainerBranding: false, + hasMultipleBranding: false, + }); + const usBranding = decideCollectionBranding({ + frontBranding: undefined, + couldDisplayFrontBranding: false, + cards, + editionId: 'US', + isContainerBranding: false, + }); + assertMatchObject(usBranding, { + kind: 'sponsored', + isFrontBranding: false, + branding: { + brandingType: { name: 'sponsored' }, + sponsorName: 'bar', + aboutThisLink: '', + logo, + }, + isContainerBranding: false, + hasMultipleBranding: false, + }); + }); + + void nodeIt('is paid content derived from multiple cards', () => { + const cardBranding = { + brandingType: { name: 'paid-content' as const }, + sponsorName: 'foo', + aboutThisLink: '', + logo, + }; + const collectionBranding = decideCollectionBranding({ + frontBranding: undefined, + couldDisplayFrontBranding: false, + cards: [ + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: cardBranding, + }, + ], + }, + }, + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: cardBranding, + }, + ], + }, + }, + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: cardBranding, + }, + ], + }, + }, + ], + editionId: 'UK', + isContainerBranding: false, + }); + assertMatchObject(collectionBranding, { + kind: 'paid-content', + isFrontBranding: false, + branding: cardBranding, + }); + }); + + void nodeIt('undefined when not all cards have branding', () => { + // The branding we'll apply to each card in this test + const collectionBranding = decideCollectionBranding({ + frontBranding: undefined, + couldDisplayFrontBranding: false, + cards: [ + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: { + brandingType: { name: 'paid-content' }, + sponsorName: 'foo', + aboutThisLink: '', + logo, + }, + }, + ], + }, + }, + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: { + brandingType: { name: 'paid-content' }, + sponsorName: 'bar', + aboutThisLink: '', + logo, + }, + }, + ], + }, + }, + { + properties: { + editionBrandings: [], + }, + }, + ], + editionId: 'UK', + isContainerBranding: false, + }); + assert.equal(collectionBranding, undefined); + }); + + void nodeIt('is undefined when no cards have branding', () => { + const collectionBranding = decideCollectionBranding({ + frontBranding: undefined, + couldDisplayFrontBranding: false, + cards: [ + { + properties: { + editionBrandings: [], + }, + }, + { + properties: { + editionBrandings: [], + }, + }, + { + properties: { + editionBrandings: [], + }, + }, + ], + editionId: 'UK', + isContainerBranding: false, + }); + assert.equal(collectionBranding, undefined); + }); + + void nodeIt('is undefined when cards have different branding types', () => { + const collectionBranding = decideCollectionBranding({ + frontBranding: undefined, + couldDisplayFrontBranding: false, + cards: [ + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: { + brandingType: { name: 'paid-content' }, + sponsorName: 'foo', + aboutThisLink: '', + logo, + }, + }, + ], + }, + }, + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: { + brandingType: { name: 'sponsored' }, + sponsorName: 'bar', + aboutThisLink: '', + logo, + }, + }, + ], + }, + }, + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: { + brandingType: { name: 'foundation' }, + sponsorName: 'baz', + aboutThisLink: '', + logo, + }, + }, + ], + }, + }, + ], + editionId: 'UK', + isContainerBranding: false, + }); + assert.equal(collectionBranding, undefined); + }); + + void nodeIt( + 'is sponsored branding when all of the branding types are sponsored and the names match', + () => { + const cardBranding = { + brandingType: { name: 'sponsored' as const }, + sponsorName: 'foo', + aboutThisLink: '', + logo, + }; + const collectionBranding = decideCollectionBranding({ + frontBranding: undefined, + couldDisplayFrontBranding: false, + cards: [ + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: cardBranding, + }, + ], + }, + }, + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: cardBranding, + }, + ], + }, + }, + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: cardBranding, + }, + ], + }, + }, + ], + editionId: 'UK', + isContainerBranding: false, + }); + assert.deepEqual(collectionBranding, { + kind: 'sponsored', + isFrontBranding: false, + branding: cardBranding, + isContainerBranding: false, + hasMultipleBranding: false, + }); + }, + ); + + void nodeIt( + 'is undefined when branding cards are sponsored and have different sponsor names', + () => { + // The branding we'll apply to each card in this test + const collectionBranding = decideCollectionBranding({ + frontBranding: undefined, + couldDisplayFrontBranding: false, + cards: [ + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: { + brandingType: { name: 'sponsored' }, + sponsorName: 'foo', + aboutThisLink: '', + logo, + }, + }, + ], + }, + }, + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: { + brandingType: { name: 'sponsored' }, + sponsorName: 'bar', + aboutThisLink: '', + logo, + }, + }, + ], + }, + }, + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: { + brandingType: { name: 'sponsored' }, + sponsorName: 'baz', + aboutThisLink: '', + logo, + }, + }, + ], + }, + }, + ], + editionId: 'UK', + isContainerBranding: false, + }); + assert.equal(collectionBranding, undefined); + }, + ); + + void nodeIt( + 'is paid content branding when all of the branding types are paid-content and the names match', + () => { + const collectionBranding = decideCollectionBranding({ + frontBranding: undefined, + couldDisplayFrontBranding: false, + cards: [ + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: { + brandingType: { name: 'paid-content' }, + sponsorName: 'foo', + aboutThisLink: '', + logo, + }, + }, + ], + }, + }, + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: { + brandingType: { name: 'paid-content' }, + sponsorName: 'foo', + aboutThisLink: '', + logo, + }, + }, + ], + }, + }, + ], + editionId: 'UK', + isContainerBranding: false, + }); + assert.deepEqual(collectionBranding, { + kind: 'paid-content', + isFrontBranding: false, + branding: { + brandingType: { name: 'paid-content' }, + sponsorName: 'foo', + aboutThisLink: '', + logo, + }, + isContainerBranding: false, + hasMultipleBranding: false, + }); + }, + ); + + void nodeIt( + 'is paid content multiple branding when branding cards are paid-content and have different sponsor names', + () => { + const collectionBranding = decideCollectionBranding({ + frontBranding: undefined, + couldDisplayFrontBranding: false, + cards: [ + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: { + brandingType: { name: 'paid-content' }, + sponsorName: 'foo', + aboutThisLink: '', + logo, + }, + }, + ], + }, + }, + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: { + brandingType: { name: 'paid-content' }, + sponsorName: 'bar', + aboutThisLink: '', + logo, + }, + }, + ], + }, + }, + ], + editionId: 'UK', + isContainerBranding: false, + }); + assert.deepEqual(collectionBranding, { + kind: 'paid-content', + isFrontBranding: false, + branding: { + brandingType: { name: 'paid-content' }, + sponsorName: 'foo', + aboutThisLink: '', + logo, + }, + isContainerBranding: false, + hasMultipleBranding: true, + }); + }, + ); + + void nodeIt( + 'is front branding when present and possible to display', + () => { + const collectionBranding = decideCollectionBranding({ + frontBranding: { + brandingType: { name: 'paid-content' }, + sponsorName: 'bar', + aboutThisLink: '', + logo, + }, + couldDisplayFrontBranding: true, + cards: [], + editionId: 'UK', + isContainerBranding: false, + }); + assert.deepEqual(collectionBranding, { + kind: 'paid-content', + isFrontBranding: true, + branding: { + brandingType: { name: 'paid-content' }, + sponsorName: 'bar', + aboutThisLink: '', + logo, + }, + isContainerBranding: false, + hasMultipleBranding: false, + }); + }, + ); + + void nodeIt( + 'is undefined when there is front branding (and no card branding) that is not eligible for display on this collection', + () => { + const collectionBranding = decideCollectionBranding({ + frontBranding: { + brandingType: { name: 'paid-content' }, + sponsorName: 'bar', + aboutThisLink: '', + logo, + }, + couldDisplayFrontBranding: false, + cards: [], + editionId: 'UK', + isContainerBranding: false, + }); + assert.equal(collectionBranding, undefined); + }, + ); + + void nodeIt('when cards are present', () => { + const cardBranding = { + brandingType: { name: 'paid-content' as const }, + sponsorName: 'foo', + aboutThisLink: '', + logo, + }; + const collectionBranding = decideCollectionBranding({ + frontBranding: { + brandingType: { name: 'sponsored' }, + sponsorName: 'bar', + aboutThisLink: '', + logo, + }, + couldDisplayFrontBranding: false, + cards: [ + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: cardBranding, + }, + ], + }, + }, + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: cardBranding, + }, + ], + }, + }, + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: cardBranding, + }, + ], + }, + }, + ], + editionId: 'UK', + isContainerBranding: false, + }); + assert.deepEqual(collectionBranding, { + kind: 'paid-content', + isFrontBranding: false, + branding: cardBranding, + isContainerBranding: false, + hasMultipleBranding: false, + }); + }); + + void nodeIt( + 'is undefined when front branding matches card branding, but we are not displaying front branding', + () => { + const cardBranding = { + brandingType: { name: 'paid-content' as const }, + sponsorName: 'foo', + aboutThisLink: '', + logo, + }; + const collectionBranding = decideCollectionBranding({ + frontBranding: { + brandingType: { name: 'paid-content' }, + sponsorName: 'foo', + aboutThisLink: '', + logo, + }, + couldDisplayFrontBranding: false, + cards: [ + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: cardBranding, + }, + ], + }, + }, + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: cardBranding, + }, + ], + }, + }, + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: cardBranding, + }, + ], + }, + }, + ], + editionId: 'UK', + isContainerBranding: false, + }); + assert.equal(collectionBranding, undefined); + }, + ); +}); + +void nodeDescribe('decideTagPageBranding', () => { + void nodeIt('picks branding from a tag page by their edition', () => { + const branding = { + brandingType: { name: 'sponsored' }, + sponsorName: 'Guardian.org', + aboutThisLink: '', + logo, + } satisfies Branding; + + const tagPageBranding = decideTagPageBranding({ + branding, + }); + + assertMatchObject(tagPageBranding, { + kind: 'sponsored', + isFrontBranding: true, + branding: { + brandingType: { name: 'sponsored' }, + sponsorName: 'Guardian.org', + aboutThisLink: '', + }, + isContainerBranding: false, + hasMultipleBranding: false, + }); + }); + void nodeIt( + 'is undefined when branding does not have a brandingType name present', + () => { + const branding = { + sponsorName: 'Guardian.org', + aboutThisLink: '', + logo, + }; + + const tagPageBranding = decideTagPageBranding({ + branding, + }); + assert.equal(tagPageBranding, undefined); + }, + ); +}); diff --git a/dotcom-rendering/src/lib/branding.test.ts b/dotcom-rendering/src/lib/branding.test.ts index 498c28b6f89..eaf49d3f273 100644 --- a/dotcom-rendering/src/lib/branding.test.ts +++ b/dotcom-rendering/src/lib/branding.test.ts @@ -1,9 +1,23 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; import type { Branding } from '../types/branding'; import { decideCollectionBranding, decideTagPageBranding } from './branding'; // For the purpose of these tests we don't care about the contents of the logo objects const logo = {} as Branding['logo']; +const assertMatchObject = (actual: unknown, expected: unknown): void => { + if (expected === null || typeof expected !== 'object') { + assert.deepEqual(actual, expected); + return; + } + + assert.ok(actual !== null && typeof actual === 'object'); + for (const [key, value] of Object.entries(expected)) { + assertMatchObject((actual as Record)[key], value); + } +}; + describe('decideCollectionBranding', () => { it('picks branding from a card by their edition', () => { const cards = [ diff --git a/dotcom-rendering/src/lib/byline.node.test.ts b/dotcom-rendering/src/lib/byline.node.test.ts new file mode 100644 index 00000000000..8495b335746 --- /dev/null +++ b/dotcom-rendering/src/lib/byline.node.test.ts @@ -0,0 +1,289 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { getBylineComponentsFromTokens, getSoleContributor } from './byline'; + +void nodeDescribe('Byline utilities', () => { + void nodeIt( + 'should link a single tag by linking name tokens with Contributor tag titles', + () => { + const bylineTokens = ['Eva Smith', 'and friends']; + const tags = [ + { + id: 'eva-smith', + type: 'Contributor', + title: 'Eva Smith', + }, + ]; + + const bylineComponents = getBylineComponentsFromTokens( + bylineTokens, + tags, + ); + + assert.deepEqual(bylineComponents, [ + { tag: tags[0], token: 'Eva Smith' }, + 'and friends', + ]); + }, + ); + + void nodeIt( + 'should link multiple tags by linking name tokens with Contributor tag titles', + () => { + const bylineTokens = ['Eva Smith', ' and ', 'Duncan Campbell']; + const tags = [ + { + id: 'eva-smith', + type: 'Contributor', + title: 'Eva Smith', + }, + { + id: 'duncan-campbell', + type: 'Contributor', + title: 'Duncan Campbell', + }, + ]; + const bylineComponents = getBylineComponentsFromTokens( + bylineTokens, + tags, + ); + + assert.deepEqual(bylineComponents, [ + { tag: tags[0], token: 'Eva Smith' }, + ' and ', + { tag: tags[1], token: 'Duncan Campbell' }, + ]); + }, + ); + + void nodeIt( + 'should not reuse a contributor tag, to successfully disambiguate identical names', + () => { + const bylineTokens = [ + 'Duncan Campbell', + ' and ', + 'Duncan Campbell', + ]; + const tags = [ + { + id: 'duncan-campbell', + type: 'Contributor', + title: 'Duncan Campbell', + }, + { + id: 'duncan-campbell-1', + type: 'Contributor', + title: 'Duncan Campbell', + }, + ]; + + const bylineComponents = getBylineComponentsFromTokens( + bylineTokens, + tags, + ); + + assert.deepEqual(bylineComponents, [ + { tag: tags[0], token: 'Duncan Campbell' }, + ' and ', + { tag: tags[1], token: 'Duncan Campbell' }, + ]); + }, + ); + + void nodeDescribe('getSoleContributor', () => { + void nodeDescribe('returns a contributor', () => { + void nodeIt('Sebastian Köhn, as told to Wilfried Chan', () => { + // https://www.theguardian.com/world/2022/jul/23/i-literally-screamed-out-loud-in-pain-my-two-weeks-of-monkeypox-hell + + const soleContributor = getSoleContributor( + [ + { + id: 'profile/wilfred-chan', + type: 'Contributor', + title: 'Wilfred Chan', + }, + ], + 'Sebastian Köhn, as told to Wilfred Chan', + ); + + assert.equal(soleContributor?.title, 'Wilfred Chan'); + }); + + void nodeIt('Jim Waterson Media editor', () => { + // https://www.theguardian.com/media/2021/nov/17/geordie-greig-ousted-as-editor-of-the-daily-mail + + const soleContributor = getSoleContributor( + [ + { + id: 'media/geordie-greig', + type: 'Keyword', + title: 'Geordie Greig', + }, + { + id: 'profile/jim-waterson', + type: 'Contributor', + title: 'Jim Waterson', + twitterHandle: 'jimwaterson', + bylineImageUrl: + 'https://i.guim.co.uk/img/uploads/2019/01/21/Jim_Waterson.jpg?width=300&quality=85&auto=format&fit=max&s=70dd40e52d9cbe5053f58ad8c4421664', + }, + ], + 'Jim Waterson Media editor', + ); + + assert.equal(soleContributor?.title, 'Jim Waterson'); + }); + + void nodeIt('First Dog on the Moon', () => { + // https://www.theguardian.com/commentisfree/2022/jul/22/europe-is-ablaze-italian-glaciers-are-collapsing-the-climate-crisis-is-here + + const soleContributor = getSoleContributor( + [ + { + id: 'profile/first-dog-on-the-moon', + type: 'Contributor', + title: 'First Dog on the Moon', + }, + ], + 'First Dog on the Moon', + ); + + assert.equal(soleContributor?.title, 'First Dog on the Moon'); + }); + + void nodeIt('Sam Levine in New York', () => { + // https://www.theguardian.com/us-news/2022/jul/22/january-6-panel-american-democracy-nose-dive + + const soleContributor = getSoleContributor( + [ + { + id: 'profile/sam-levine', + type: 'Contributor', + title: 'Sam Levine', + }, + ], + 'Sam Levine in New York', + ); + + assert.equal(soleContributor?.title, 'Sam Levine'); + }); + }); + + void nodeDescribe('returns `undefined`', () => { + void nodeIt( + 'Sam Levin in Los Angeles and Sam Levine in New York', + () => { + // https://www.theguardian.com/us-news/2020/oct/12/republicans-election-2020-unauthorized-ballot-boxes + + const soleContributor = getSoleContributor( + [ + { + id: 'profile/sam-levin', + type: 'Contributor', + title: 'Sam Levin', + twitterHandle: 'SamTLevin', + }, + { + id: 'profile/sam-levine', + type: 'Contributor', + title: 'Sam Levine', + }, + ], + 'Sam Levin in Los Angeles and Sam Levine in New York', + ); + assert.equal(soleContributor, undefined); + }, + ); + + void nodeIt('Gabriel Smith', () => { + const soleContributor = getSoleContributor( + [ + { + id: 'profile/ben-beaumont-thomas', + type: 'Contributor', + title: 'Ben Beaumont-Thomas', + twitterHandle: 'ben_bt', + }, + ], + 'Gabriel Smith', + ); + assert.equal(soleContributor, undefined); + }); + + void nodeIt('Zoe Williams and others', () => { + // https://www.theguardian.com/commentisfree/2022/jul/20/britain-next-prime-minister-rishi-sunak-liz-truss-conservative-leader + + const soleContributor = getSoleContributor( + [ + { + id: 'profile/zoewilliams', + type: 'Contributor', + title: 'Zoe Williams', + twitterHandle: 'zoesqwilliams', + }, + { + id: 'profile/sahil-dutta', + type: 'Contributor', + title: 'Sahil Dutta', + }, + { + id: 'profile/henry-hill', + type: 'Contributor', + title: 'Henry Hill', + }, + { + id: 'profile/simonjenkins', + type: 'Contributor', + title: 'Simon Jenkins', + }, + { + id: 'profile/moya-lothian-mclean', + type: 'Contributor', + title: 'Moya Lothian-McLean', + }, + ], + 'Zoe Williams and others', + ); + + assert.equal(soleContributor, undefined); + }); + + void nodeIt( + 'Paul MacInnes, Nesrine Malik, Julie Bindel, Peter Preston', + () => { + // https://www.theguardian.com/commentisfree/2011/dec/30/person-of-2011-writers-verdict + + const soleContributor = getSoleContributor( + [ + { + id: 'profile/paulmacinnes', + type: 'Contributor', + title: 'Paul MacInnes', + twitterHandle: 'PaulMac', + }, + { + id: 'profile/peterpreston', + type: 'Contributor', + title: 'Peter Preston', + }, + { + id: 'profile/nesrinemalik', + type: 'Contributor', + title: 'Nesrine Malik', + }, + { + id: 'profile/juliebindel', + type: 'Contributor', + title: 'Julie Bindel', + twitterHandle: 'bindelj', + }, + ], + 'Paul MacInnes, Nesrine Malik, Julie Bindel, Peter Preston', + ); + + assert.equal(soleContributor, undefined); + }, + ); + }); + }); +}); diff --git a/dotcom-rendering/src/lib/byline.test.ts b/dotcom-rendering/src/lib/byline.test.ts deleted file mode 100644 index 0ba313e554d..00000000000 --- a/dotcom-rendering/src/lib/byline.test.ts +++ /dev/null @@ -1,268 +0,0 @@ -import { getBylineComponentsFromTokens, getSoleContributor } from './byline'; - -describe('Byline utilities', () => { - it('should link a single tag by linking name tokens with Contributor tag titles', () => { - const bylineTokens = ['Eva Smith', 'and friends']; - const tags = [ - { - id: 'eva-smith', - type: 'Contributor', - title: 'Eva Smith', - }, - ]; - - const bylineComponents = getBylineComponentsFromTokens( - bylineTokens, - tags, - ); - - expect(bylineComponents).toEqual([ - { tag: tags[0], token: 'Eva Smith' }, - 'and friends', - ]); - }); - - it('should link multiple tags by linking name tokens with Contributor tag titles', () => { - const bylineTokens = ['Eva Smith', ' and ', 'Duncan Campbell']; - const tags = [ - { - id: 'eva-smith', - type: 'Contributor', - title: 'Eva Smith', - }, - { - id: 'duncan-campbell', - type: 'Contributor', - title: 'Duncan Campbell', - }, - ]; - const bylineComponents = getBylineComponentsFromTokens( - bylineTokens, - tags, - ); - - expect(bylineComponents).toEqual([ - { tag: tags[0], token: 'Eva Smith' }, - ' and ', - { tag: tags[1], token: 'Duncan Campbell' }, - ]); - }); - - it('should not reuse a contributor tag, to successfully disambiguate identical names', () => { - const bylineTokens = ['Duncan Campbell', ' and ', 'Duncan Campbell']; - const tags = [ - { - id: 'duncan-campbell', - type: 'Contributor', - title: 'Duncan Campbell', - }, - { - id: 'duncan-campbell-1', - type: 'Contributor', - title: 'Duncan Campbell', - }, - ]; - - const bylineComponents = getBylineComponentsFromTokens( - bylineTokens, - tags, - ); - - expect(bylineComponents).toEqual([ - { tag: tags[0], token: 'Duncan Campbell' }, - ' and ', - { tag: tags[1], token: 'Duncan Campbell' }, - ]); - }); - - describe('getSoleContributor', () => { - describe('returns a contributor', () => { - it('Sebastian Köhn, as told to Wilfried Chan', () => { - // https://www.theguardian.com/world/2022/jul/23/i-literally-screamed-out-loud-in-pain-my-two-weeks-of-monkeypox-hell - - const soleContributor = getSoleContributor( - [ - { - id: 'profile/wilfred-chan', - type: 'Contributor', - title: 'Wilfred Chan', - }, - ], - 'Sebastian Köhn, as told to Wilfred Chan', - ); - - expect(soleContributor?.title).toBe('Wilfred Chan'); - }); - - it('Jim Waterson Media editor', () => { - // https://www.theguardian.com/media/2021/nov/17/geordie-greig-ousted-as-editor-of-the-daily-mail - - const soleContributor = getSoleContributor( - [ - { - id: 'media/geordie-greig', - type: 'Keyword', - title: 'Geordie Greig', - }, - { - id: 'profile/jim-waterson', - type: 'Contributor', - title: 'Jim Waterson', - twitterHandle: 'jimwaterson', - bylineImageUrl: - 'https://i.guim.co.uk/img/uploads/2019/01/21/Jim_Waterson.jpg?width=300&quality=85&auto=format&fit=max&s=70dd40e52d9cbe5053f58ad8c4421664', - }, - ], - 'Jim Waterson Media editor', - ); - - expect(soleContributor?.title).toBe('Jim Waterson'); - }); - - it('First Dog on the Moon', () => { - // https://www.theguardian.com/commentisfree/2022/jul/22/europe-is-ablaze-italian-glaciers-are-collapsing-the-climate-crisis-is-here - - const soleContributor = getSoleContributor( - [ - { - id: 'profile/first-dog-on-the-moon', - type: 'Contributor', - title: 'First Dog on the Moon', - }, - ], - 'First Dog on the Moon', - ); - - expect(soleContributor?.title).toBe('First Dog on the Moon'); - }); - - it('Sam Levine in New York', () => { - // https://www.theguardian.com/us-news/2022/jul/22/january-6-panel-american-democracy-nose-dive - - const soleContributor = getSoleContributor( - [ - { - id: 'profile/sam-levine', - type: 'Contributor', - title: 'Sam Levine', - }, - ], - 'Sam Levine in New York', - ); - - expect(soleContributor?.title).toBe('Sam Levine'); - }); - }); - - describe('returns `undefined`', () => { - it('Sam Levin in Los Angeles and Sam Levine in New York', () => { - // https://www.theguardian.com/us-news/2020/oct/12/republicans-election-2020-unauthorized-ballot-boxes - - const soleContributor = getSoleContributor( - [ - { - id: 'profile/sam-levin', - type: 'Contributor', - title: 'Sam Levin', - twitterHandle: 'SamTLevin', - }, - { - id: 'profile/sam-levine', - type: 'Contributor', - title: 'Sam Levine', - }, - ], - 'Sam Levin in Los Angeles and Sam Levine in New York', - ); - expect(soleContributor).toBe(undefined); - }); - - it('Gabriel Smith', () => { - const soleContributor = getSoleContributor( - [ - { - id: 'profile/ben-beaumont-thomas', - type: 'Contributor', - title: 'Ben Beaumont-Thomas', - twitterHandle: 'ben_bt', - }, - ], - 'Gabriel Smith', - ); - expect(soleContributor).toBe(undefined); - }); - - it('Zoe Williams and others', () => { - // https://www.theguardian.com/commentisfree/2022/jul/20/britain-next-prime-minister-rishi-sunak-liz-truss-conservative-leader - - const soleContributor = getSoleContributor( - [ - { - id: 'profile/zoewilliams', - type: 'Contributor', - title: 'Zoe Williams', - twitterHandle: 'zoesqwilliams', - }, - { - id: 'profile/sahil-dutta', - type: 'Contributor', - title: 'Sahil Dutta', - }, - { - id: 'profile/henry-hill', - type: 'Contributor', - title: 'Henry Hill', - }, - { - id: 'profile/simonjenkins', - type: 'Contributor', - title: 'Simon Jenkins', - }, - { - id: 'profile/moya-lothian-mclean', - type: 'Contributor', - title: 'Moya Lothian-McLean', - }, - ], - 'Zoe Williams and others', - ); - - expect(soleContributor).toBe(undefined); - }); - - it('Paul MacInnes, Nesrine Malik, Julie Bindel, Peter Preston', () => { - // https://www.theguardian.com/commentisfree/2011/dec/30/person-of-2011-writers-verdict - - const soleContributor = getSoleContributor( - [ - { - id: 'profile/paulmacinnes', - type: 'Contributor', - title: 'Paul MacInnes', - twitterHandle: 'PaulMac', - }, - { - id: 'profile/peterpreston', - type: 'Contributor', - title: 'Peter Preston', - }, - { - id: 'profile/nesrinemalik', - type: 'Contributor', - title: 'Nesrine Malik', - }, - { - id: 'profile/juliebindel', - type: 'Contributor', - title: 'Julie Bindel', - twitterHandle: 'bindelj', - }, - ], - 'Paul MacInnes, Nesrine Malik, Julie Bindel, Peter Preston', - ); - - expect(soleContributor).toBe(undefined); - }); - }); - }); -}); diff --git a/dotcom-rendering/src/lib/decide-cation.node.test.ts b/dotcom-rendering/src/lib/decide-cation.node.test.ts new file mode 100644 index 00000000000..acc9991d1c2 --- /dev/null +++ b/dotcom-rendering/src/lib/decide-cation.node.test.ts @@ -0,0 +1,130 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import type { + EmbedBlockElement, + ImageBlockElement, + TextBlockElement, +} from '../types/content'; +import { decideMainMediaCaption } from './decide-caption'; + +void nodeDescribe('decideMainMediaCaption', () => { + void nodeDescribe('when mainMedia is not supported', () => { + void nodeIt('undefined returns an empty string', () => { + assert.deepEqual(decideMainMediaCaption(undefined), ''); + }); + void nodeIt('a text block returns an empty string', () => { + assert.deepEqual( + decideMainMediaCaption({ + elementId: 'test-id', + html: '

test

', + _type: 'model.dotcomrendering.pageElements.TextBlockElement', + } as TextBlockElement), + '', + ); + }); + }); + + void nodeDescribe('ImageBlockElement', () => { + const mockImageBlockElement = { + elementId: 'mock-element-id', + data: {}, + role: 'inline', + _type: 'model.dotcomrendering.pageElements.ImageBlockElement', + } as ImageBlockElement; + + void nodeIt( + 'returns an empty string if there is no caption, displayCredit, or credit', + () => { + assert.deepEqual( + decideMainMediaCaption(mockImageBlockElement), + '', + ); + }, + ); + + void nodeIt('includes the caption, if it exists', () => { + assert.deepEqual( + decideMainMediaCaption({ + ...mockImageBlockElement, + data: { + caption: 'image block caption', + }, + }), + 'image block caption', + ); + }); + + void nodeIt('includes the credit, if it should be displayed', () => { + assert.deepEqual( + decideMainMediaCaption({ + ...mockImageBlockElement, + displayCredit: true, + data: { + credit: 'image block display credit', + }, + }), + 'image block display credit', + ); + }); + + void nodeIt( + 'does not include the credit, if it should not be displayed', + () => { + assert.deepEqual( + decideMainMediaCaption({ + ...mockImageBlockElement, + displayCredit: false, + data: { + credit: 'image block display credit', + }, + }), + '', + ); + }, + ); + + void nodeIt( + 'includes both the credit and caption, if they exist', + () => { + assert.deepEqual( + decideMainMediaCaption({ + ...mockImageBlockElement, + displayCredit: true, + data: { + caption: 'mock caption', + credit: 'mock display credit', + }, + }), + 'mock caption mock display credit', + ); + }, + ); + }); + + void nodeDescribe('EmbedBlockElement', () => { + void nodeIt('returns an empty string if there is no caption', () => { + assert.deepEqual( + decideMainMediaCaption({ + elementId: 'test-id', + html: '

test

', + isMandatory: false, + _type: 'model.dotcomrendering.pageElements.EmbedBlockElement', + } as EmbedBlockElement), + '', + ); + }); + + void nodeIt('returns the correct caption, if exists', () => { + assert.deepEqual( + decideMainMediaCaption({ + elementId: 'test-id', + html: '

test

', + isMandatory: true, + caption: 'test caption', + _type: 'model.dotcomrendering.pageElements.EmbedBlockElement', + } as EmbedBlockElement), + 'test caption', + ); + }); + }); +}); diff --git a/dotcom-rendering/src/lib/decide-cation.test.ts b/dotcom-rendering/src/lib/decide-cation.test.ts deleted file mode 100644 index ef4fc81d1be..00000000000 --- a/dotcom-rendering/src/lib/decide-cation.test.ts +++ /dev/null @@ -1,109 +0,0 @@ -import type { - EmbedBlockElement, - ImageBlockElement, - TextBlockElement, -} from '../types/content'; -import { decideMainMediaCaption } from './decide-caption'; - -describe('decideMainMediaCaption', () => { - describe('when mainMedia is not supported', () => { - it('undefined returns an empty string', () => { - expect(decideMainMediaCaption(undefined)).toEqual(''); - }); - it('a text block returns an empty string', () => { - expect( - decideMainMediaCaption({ - elementId: 'test-id', - html: '

test

', - _type: 'model.dotcomrendering.pageElements.TextBlockElement', - } as TextBlockElement), - ).toEqual(''); - }); - }); - - describe('ImageBlockElement', () => { - const mockImageBlockElement = { - elementId: 'mock-element-id', - data: {}, - role: 'inline', - _type: 'model.dotcomrendering.pageElements.ImageBlockElement', - } as ImageBlockElement; - - it('returns an empty string if there is no caption, displayCredit, or credit', () => { - expect(decideMainMediaCaption(mockImageBlockElement)).toEqual(''); - }); - - it('includes the caption, if it exists', () => { - expect( - decideMainMediaCaption({ - ...mockImageBlockElement, - data: { - caption: 'image block caption', - }, - }), - ).toEqual('image block caption'); - }); - - it('includes the credit, if it should be displayed', () => { - expect( - decideMainMediaCaption({ - ...mockImageBlockElement, - displayCredit: true, - data: { - credit: 'image block display credit', - }, - }), - ).toEqual('image block display credit'); - }); - - it('does not include the credit, if it should not be displayed', () => { - expect( - decideMainMediaCaption({ - ...mockImageBlockElement, - displayCredit: false, - data: { - credit: 'image block display credit', - }, - }), - ).toEqual(''); - }); - - it('includes both the credit and caption, if they exist', () => { - expect( - decideMainMediaCaption({ - ...mockImageBlockElement, - displayCredit: true, - data: { - caption: 'mock caption', - credit: 'mock display credit', - }, - }), - ).toEqual('mock caption mock display credit'); - }); - }); - - describe('EmbedBlockElement', () => { - it('returns an empty string if there is no caption', () => { - expect( - decideMainMediaCaption({ - elementId: 'test-id', - html: '

test

', - isMandatory: false, - _type: 'model.dotcomrendering.pageElements.EmbedBlockElement', - } as EmbedBlockElement), - ).toEqual(''); - }); - - it('returns the correct caption, if exists', () => { - expect( - decideMainMediaCaption({ - elementId: 'test-id', - html: '

test

', - isMandatory: true, - caption: 'test caption', - _type: 'model.dotcomrendering.pageElements.EmbedBlockElement', - } as EmbedBlockElement), - ).toEqual('test caption'); - }); - }); -}); diff --git a/dotcom-rendering/src/lib/getFrontsAdPositions.node.test.ts b/dotcom-rendering/src/lib/getFrontsAdPositions.node.test.ts new file mode 100644 index 00000000000..249adc10289 --- /dev/null +++ b/dotcom-rendering/src/lib/getFrontsAdPositions.node.test.ts @@ -0,0 +1,691 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { + brandedTestCollections, + largeFlexibleGeneralCollection, + largeFlexibleSpecialCollection, + smallFlexibleGeneralCollection, + smallFlexibleSpecialCollection, + testCollectionsUk, + testCollectionsUs, + testCollectionsWithSecondaryLevel, +} from '../../fixtures/manual/frontCollections'; +import type { DCRCollectionType } from '../types/front'; +import { + type AdCandidate, + getDesktopAdPositions, + getMobileAdPositions, + removeConsecutiveAdSlotsReducer, +} from './getFrontsAdPositions'; + +const testCollection: AdCandidate = { + collectionType: 'flexible/general', + displayName: 'Test Collection', + containerLevel: 'Primary', + containerPalette: 'EventPalette', + grouped: { + snap: [], + splash: [], + standard: [], + }, +}; + +const defaultTestCollections: AdCandidate[] = [...Array(12)].map( + () => ({ ...testCollection }), +); + +void nodeDescribe('Mobile Ads', () => { + void nodeIt( + `Should not insert ad after container if it's the first one and it's a thrasher`, + () => { + const testCollections = [ + { ...testCollection, collectionType: 'fixed/thrasher' }, + ...defaultTestCollections, + ] satisfies AdCandidate[]; + + const mobileAdPositions = getMobileAdPositions( + testCollections, + 'uk', + ); + + assert.ok(!mobileAdPositions.includes(0)); + }, + ); + + void nodeIt( + `should not insert an ad in the merchandising-high position`, + () => { + const testCollections = [ + ...defaultTestCollections.slice(0, 3), + { ...testCollection, collectionType: 'news/most-popular' }, + ] satisfies AdCandidate[]; + const mobileAdPositions = getMobileAdPositions( + testCollections, + 'uk', + ); + assert.ok(!mobileAdPositions.includes(3)); + }, + ); + + void nodeIt('Should not insert ad before a thrasher container', () => { + const testCollections = [...defaultTestCollections]; + testCollections.splice(5, 0, { + ...testCollection, + collectionType: 'fixed/thrasher', + }); + testCollections.splice(9, 0, { + ...testCollection, + collectionType: 'fixed/thrasher', + }); + + const mobileAdPositions = getMobileAdPositions(testCollections, 'uk'); + + assert.ok(!mobileAdPositions.includes(6)); + assert.ok(!mobileAdPositions.includes(8)); + }); + + void nodeIt( + `Should allow inserting an ad before a thrasher container if it's a filter page`, + () => { + const testCollections = [...defaultTestCollections]; + testCollections.splice(5, 0, { + ...testCollection, + collectionType: 'fixed/thrasher', + }); + testCollections.splice(9, 0, { + ...testCollection, + collectionType: 'fixed/thrasher', + }); + + const mobileAdPositions = getMobileAdPositions( + testCollections, + 'uk/thefilter', + ); + + assert.ok(mobileAdPositions.includes(6)); + assert.ok(mobileAdPositions.includes(8)); + }, + ); + + // We used https://www.theguardian.com/uk/commentisfree as a blueprint + void nodeIt( + 'Non-network front, with more than 4 collections, without thrashers', + () => { + const testCollections: AdCandidate[] = [ + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (0) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (2) + { ...testCollection, collectionType: 'static/medium/4' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (4) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (6) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (8) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is before merch high position + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position + { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container + ]; + + const mobileAdPositions = getMobileAdPositions( + testCollections, + 'uk', + ); + + assert.deepEqual(mobileAdPositions, [0, 2, 4, 6, 8]); + }, + ); + + // We used https://www.theguardian.com/uk as a blueprint + void nodeIt( + 'UK Network Front, with more than 4 collections, with thrashers at various places', + () => { + const testCollections: AdCandidate[] = [ + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (0) + { ...testCollection, collectionType: 'static/medium/4' }, + { ...testCollection, collectionType: 'flexible/special' }, // Ad position (2) + { ...testCollection, collectionType: 'flexible/special' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (4) + { ...testCollection, collectionType: 'flexible/special' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (8) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (11) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (14) + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, + { ...testCollection, collectionType: 'scrollable/feature' }, // Ad position (17) + { ...testCollection, collectionType: 'static/medium/4' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (19) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - is before merch high position + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position + { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container + ]; + + const mobileAdPositions = getMobileAdPositions( + testCollections, + 'uk', + ); + + assert.deepEqual(mobileAdPositions, [0, 2, 4, 8, 11, 14, 17, 19]); + }, + ); + + // We used https://www.theguardian.com/international as a blueprint + void nodeIt( + 'International Network Front, with more than 4 collections, with thrashers at various places', + () => { + const testCollections: AdCandidate[] = [ + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (0) + { ...testCollection, collectionType: 'static/medium/4' }, + { ...testCollection, collectionType: 'flexible/special' }, // Ad position (2) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/special' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (5) + { ...testCollection, collectionType: 'static/medium/4' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (7) + { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (11) + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (14) + { ...testCollection, collectionType: 'static/medium/4' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (16) + { ...testCollection, collectionType: 'scrollable/feature' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position + { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container + ]; + + const mobileAdPositions = getMobileAdPositions( + testCollections, + 'uk', + ); + + assert.deepEqual(mobileAdPositions, [0, 2, 5, 7, 11, 14, 16]); + }, + ); + + // We used https://www.theguardian.com/us as a blueprint + void nodeIt( + 'US Network Front, with more than 4 collections, with thrashers at various places', + () => { + const testCollections: AdCandidate[] = [ + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (0) + { ...testCollection, collectionType: 'static/medium/4' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (2) + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, + { ...testCollection, collectionType: 'flexible/special' }, // Ad position (5) + { ...testCollection, collectionType: 'flexible/special' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (9) + { ...testCollection, collectionType: 'static/medium/4' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (12) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (14) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (16) + { ...testCollection, collectionType: 'scrollable/feature' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ignored - is merch high position + { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container + ]; + + const mobileAdPositions = getMobileAdPositions( + testCollections, + 'uk', + ); + + assert.deepEqual(mobileAdPositions, [0, 2, 5, 9, 12, 14, 16]); + }, + ); + + // We used https://www.theguardian.com/uk/lifeandstyle as a blueprint + void nodeIt( + 'Lifeandstyle front, with more than 4 collections, with thrashers at various places', + () => { + const testCollections: AdCandidate[] = [ + { ...testCollection, collectionType: 'flexible/special' }, // Ad position (0) + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (3) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (6) + { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (9) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (12) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position + { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container + ]; + + const mobileAdPositions = getMobileAdPositions( + testCollections, + 'uk', + ); + + assert.deepEqual(mobileAdPositions, [0, 3, 6, 9, 12]); + }, + ); + + // We used https://www.theguardian.com/tone/recipes as a blueprint + void nodeIt( + 'Recipes front, with more than 4 collections, with thrasher at the first position', + () => { + const testCollections: AdCandidate[] = [ + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ignored - is first container and thrasher + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (1) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (3) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (5) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (7) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (9) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is before merch high position + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position + { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container + ]; + + const mobileAdPositions = getMobileAdPositions( + testCollections, + 'uk', + ); + + assert.deepEqual(mobileAdPositions, [1, 3, 5, 7, 9]); + }, + ); + + void nodeIt( + 'Europe Network Front, with more than 4 collections and thrashers in various places', + () => { + const testCollections: AdCandidate[] = [ + { + ...testCollection, + collectionType: 'flexible/general', + containerLevel: 'Primary', + }, // Ignored - is before secondary container and is not large enough + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, // Ignored - is before secondary container + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, // Ignored - is before secondary container + { + ...testCollection, + collectionType: 'scrollable/medium', + containerLevel: 'Secondary', + }, // Ignored - is before secondary container + { + ...testCollection, + collectionType: 'scrollable/feature', + containerLevel: 'Secondary', + }, // Ad position (4) + { + ...testCollection, + collectionType: 'static/feature/2', + containerLevel: 'Primary', + }, // Ignored - is before secondary container + { + ...testCollection, + collectionType: 'scrollable/medium', + containerLevel: 'Secondary', + }, // Ad position (6) + { + ...testCollection, + collectionType: 'flexible/special', + containerLevel: 'Primary', + }, // Ignored - is before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (8) + { + ...testCollection, + collectionType: 'flexible/general', + containerLevel: 'Primary', + }, // Ignored is consecutive ad after position 8 + { + ...testCollection, + collectionType: 'static/feature/2', + containerLevel: 'Primary', + }, // Ignored - is before secondary container + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, // Ignored - is before secondary container + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, // Ignored - is before secondary container + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, // Ad position (13) + { + ...testCollection, + collectionType: 'static/feature/2', + containerLevel: 'Primary', + }, // Ignored - is before secondary container + { + ...testCollection, + collectionType: 'scrollable/medium', + containerLevel: 'Secondary', + }, // Ignored - is before secondary container + { + ...testCollection, + collectionType: 'scrollable/medium', + containerLevel: 'Secondary', + }, // Ignored - is before secondary container + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, // Ignored - is before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (18) + { + ...testCollection, + collectionType: 'flexible/general', + containerLevel: 'Primary', + }, // Ignored - is before secondary container + { + ...testCollection, + collectionType: 'scrollable/feature', + containerLevel: 'Secondary', + }, // Ignored - is merch high position + { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container + ]; + + const mobileAdPositions = getMobileAdPositions( + testCollections, + 'uk', + ); + + assert.deepEqual(mobileAdPositions, [4, 6, 8, 13, 18]); + }, + ); +}); + +void nodeDescribe('Desktop Ads', () => { + void nodeIt( + 'calculates ad positions correctly for an example of the UK network front', + () => { + const adPositions = getDesktopAdPositions(testCollectionsUk, 'uk'); + + assert.deepEqual(adPositions, [3, 6, 8, 14, 17]); + }, + ); + + void nodeIt( + 'calculates ad positions correctly for an example of the US network front', + () => { + const adPositions = getDesktopAdPositions(testCollectionsUs, 'us'); + + assert.deepEqual(adPositions, [3, 6, 10, 12, 19]); + }, + ); + + void nodeIt('does NOT insert ads above or below branded content', () => { + const adPositions = getDesktopAdPositions(brandedTestCollections, 'uk'); + + assert.deepEqual(adPositions, []); + }); + + void nodeIt('does NOT insert ads above secondary level containers', () => { + const adPositions = getDesktopAdPositions( + testCollectionsWithSecondaryLevel, + 'europe', + ); + + assert.deepEqual(adPositions, []); + }); + + void nodeIt('inserts a maximum of 8 ads for fronts', () => { + const adPositions = getDesktopAdPositions( + // 10x number of test collections in fixture to reach maximum level + new Array(10) + .fill(testCollectionsWithSecondaryLevel) + .flat(), + 'europe', + ); + + assert.deepEqual(adPositions.length, 8); + }); +}); + +void nodeDescribe('inserting an ad after the first collection', () => { + void nodeDescribe('on mobile', () => { + void nodeIt( + 'inserts an ad after the first collection if it is a LARGE flexible general container', + () => { + const adPositions = getMobileAdPositions( + [ + ...largeFlexibleGeneralCollection, + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, + { + ...testCollection, + collectionType: 'scrollable/medium', + containerLevel: 'Secondary', + }, + ], + 'uk', + ); + + assert.ok(adPositions.includes(0)); + assert.ok(!adPositions.includes(1)); + }, + ); + + void nodeIt( + 'inserts an ad after the first collection if it is a LARGE flexible special container', + () => { + const adPositions = getMobileAdPositions( + [ + ...largeFlexibleSpecialCollection, + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, + { + ...testCollection, + collectionType: 'scrollable/medium', + containerLevel: 'Secondary', + }, + ], + 'uk', + ); + + assert.ok(adPositions.includes(0)); + assert.ok(!adPositions.includes(1)); + }, + ); + + void nodeIt( + 'does NOT insert an ad after the first collection if it is a SMALL flexible general container', + () => { + const adPositions = getMobileAdPositions( + [ + ...smallFlexibleGeneralCollection, + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, + ], + 'uk', + ); + + assert.ok(!adPositions.includes(0)); + }, + ); + + void nodeIt( + 'does NOT insert an ad after the first collection if it is a SMALL flexible special container', + () => { + const adPositions = getMobileAdPositions( + [ + ...smallFlexibleSpecialCollection, + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, + ], + 'uk', + ); + + assert.ok(!adPositions.includes(0)); + }, + ); + }); + + void nodeDescribe('on desktop', () => { + void nodeIt( + 'inserts an ad before the second collection if it is preceded by a LARGE flexible general container', + () => { + const adPositions = getDesktopAdPositions( + [ + ...largeFlexibleGeneralCollection, + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, + { + ...testCollection, + collectionType: 'scrollable/medium', + containerLevel: 'Secondary', + }, + ], + 'uk', + ); + + assert.ok(adPositions.includes(1)); + assert.ok(!adPositions.includes(2)); + }, + ); + + void nodeIt( + 'inserts an ad before the second collection if it is preceded by a LARGE flexible special container', + () => { + const adPositions = getDesktopAdPositions( + [ + ...largeFlexibleSpecialCollection, + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, + { + ...testCollection, + collectionType: 'scrollable/medium', + containerLevel: 'Secondary', + }, + ], + 'uk', + ); + + assert.ok(adPositions.includes(1)); + assert.ok(!adPositions.includes(2)); + }, + ); + + void nodeIt( + 'does NOT insert an ad before the second collection if it is preceded by a SMALL flexible general container', + () => { + const adPositions = getDesktopAdPositions( + [ + ...smallFlexibleGeneralCollection, + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, + { + ...testCollection, + collectionType: 'scrollable/medium', + containerLevel: 'Secondary', + }, + ], + 'uk', + ); + + assert.ok(!adPositions.includes(1)); + }, + ); + + void nodeIt( + 'does NOT insert an ad before the second collection if it is preceded by a SMALL flexible special container', + () => { + const adPositions = getDesktopAdPositions( + [ + ...smallFlexibleSpecialCollection, + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, + { + ...testCollection, + collectionType: 'scrollable/medium', + containerLevel: 'Secondary', + }, + ], + 'uk', + ); + + assert.ok(!adPositions.includes(1)); + }, + ); + }); +}); + +void nodeDescribe('removeConsecutiveAdSlotsReducer', () => { + void nodeIt( + 'removes consecutive slots from array of all consecutive numbers', + () => { + const arr = [0, 1, 2, 3, 4, 5]; + const result = arr.reduce(removeConsecutiveAdSlotsReducer, []); + assert.deepEqual(result, [0, 2, 4]); + }, + ); + + void nodeIt( + 'removes consecutive slots from array of some consecutive numbers', + () => { + const arr = [0, 3, 7, 11, 12, 13, 19, 20]; + const result = arr.reduce(removeConsecutiveAdSlotsReducer, []); + assert.deepEqual(result, [0, 3, 7, 11, 13, 19]); + }, + ); + + void nodeIt('handles empty array', () => { + const arr: number[] = []; + const result = arr.reduce(removeConsecutiveAdSlotsReducer, []); + assert.deepEqual(result, []); + }); +}); diff --git a/dotcom-rendering/src/lib/getFrontsAdPositions.test.ts b/dotcom-rendering/src/lib/getFrontsAdPositions.test.ts index bec47bd6ab9..4906d3159a3 100644 --- a/dotcom-rendering/src/lib/getFrontsAdPositions.test.ts +++ b/dotcom-rendering/src/lib/getFrontsAdPositions.test.ts @@ -1,3 +1,5 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; import { brandedTestCollections, largeFlexibleGeneralCollection, diff --git a/dotcom-rendering/src/lib/liveblogAdSlots.node.test.ts b/dotcom-rendering/src/lib/liveblogAdSlots.node.test.ts new file mode 100644 index 00000000000..57ef859ec8c --- /dev/null +++ b/dotcom-rendering/src/lib/liveblogAdSlots.node.test.ts @@ -0,0 +1,286 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import type { FEElement } from '../types/content'; +import { + calculateApproximateBlockHeight, + shouldDisplayAd, +} from './liveblogAdSlots'; + +void nodeDescribe('calculateApproximateBlockHeight', () => { + const textElementOneLineDesktop: FEElement[] = [ + { + elementId: '1', + _type: 'model.dotcomrendering.pageElements.TextBlockElement', + html: `

${'a'.repeat(72)}

`, + }, + ]; + + const textElementTwoLinesDestkop: FEElement[] = [ + { + elementId: '1', + _type: 'model.dotcomrendering.pageElements.TextBlockElement', + html: `

${'a'.repeat(73)}

`, + }, + ]; + + const textElementOneLineMobile: FEElement[] = [ + { + elementId: '1', + _type: 'model.dotcomrendering.pageElements.TextBlockElement', + html: `

${'a'.repeat(39)}

`, + }, + ]; + + const textElementTwoLinesMobile: FEElement[] = [ + { + elementId: '1', + _type: 'model.dotcomrendering.pageElements.TextBlockElement', + html: `

${'a'.repeat(40)}

`, + }, + ]; + + const multipleTextElements: FEElement[] = [ + { + elementId: '1', + _type: 'model.dotcomrendering.pageElements.TextBlockElement', + html: `

${'a'.repeat(38)}

`, + }, + { + elementId: '2', + _type: 'model.dotcomrendering.pageElements.TextBlockElement', + html: `

${'a'.repeat(38)}

`, + }, + ]; + + const youtubeElement: FEElement[] = [ + { + _type: 'model.dotcomrendering.pageElements.YoutubeBlockElement', + id: '1', + elementId: '2', + assetId: '', + expired: false, + mediaTitle: '', + }, + ]; + + const defaultBlockSpacing = 75; + + void nodeDescribe('zero elements', () => { + for (const screenSize of ['mobile', 'desktop']) { + void nodeIt( + `should return zero when there are zero elements on ${screenSize}`, + () => { + const isMobile = screenSize === 'mobile'; + assert.deepEqual( + calculateApproximateBlockHeight([], isMobile), + 0, + ); + }, + ); + } + }); + + void nodeDescribe('text block elements', () => { + const textLineHeight = 23.8; + const margin = 14; + + for (const [screenSize, textElementOneLine, textElementTwoLines] of [ + ['mobile', textElementOneLineMobile, textElementTwoLinesMobile], + ['desktop', textElementOneLineDesktop, textElementTwoLinesDestkop], + ] as const) { + void nodeIt( + `should return the correct height for varying line length on ${screenSize}`, + () => { + const isMobile = screenSize === 'mobile'; + + assert.deepEqual( + calculateApproximateBlockHeight( + textElementOneLine, + isMobile, + ), + textLineHeight + margin + defaultBlockSpacing, + ); + assert.deepEqual( + calculateApproximateBlockHeight( + textElementTwoLines, + isMobile, + ), + 2 * textLineHeight + margin + defaultBlockSpacing, + ); + }, + ); + } + + for (const screenSize of ['mobile', 'desktop']) { + void nodeIt( + `should return the correct height when there are multiple elements on ${screenSize}`, + () => { + const isMobile = screenSize === 'mobile'; + + assert.deepEqual( + calculateApproximateBlockHeight( + multipleTextElements, + isMobile, + ), + 2 * textLineHeight + 2 * margin + defaultBlockSpacing, + ); + }, + ); + } + }); + + void nodeDescribe('youtube block elements', () => { + for (const [screenSize, heightExcludingText] of [ + ['mobile', 195], + ['desktop', 350], + ] as const) { + void nodeIt( + `should return the correct height on ${screenSize}`, + () => { + const isMobile = screenSize === 'mobile'; + const margin = 12; + + assert.deepEqual( + calculateApproximateBlockHeight( + youtubeElement, + isMobile, + ), + heightExcludingText + margin + defaultBlockSpacing, + ); + }, + ); + } + }); +}); + +void nodeDescribe('shouldDisplayAd', () => { + void nodeDescribe('The final block of content', () => { + for (const screenSize of ['mobile', 'desktop']) { + void nodeIt( + `should NOT display an ad if this is the final block on ${screenSize}`, + () => { + const isMobile = screenSize === 'mobile'; + + const block = 5; + const totalBlocks = 5; + const numAdsInserted = 1; + const numPixelsWithoutAdvert = 5000; + + const result = shouldDisplayAd( + block, + totalBlocks, + numAdsInserted, + numPixelsWithoutAdvert, + isMobile, + ); + + assert.ok(!result); + }, + ); + } + }); + + void nodeDescribe('Reaching the ad limit', () => { + for (const screenSize of ['mobile', 'desktop']) { + void nodeIt( + `should NOT insert another ad slot if we have reached the limit on ${screenSize}.`, + () => { + const isMobile = screenSize === 'mobile'; + const block = 5; + const totalBlocks = 10; + const numAdsInserted = 8; + const numPixelsWithoutAdvert = 5000; + + const result = shouldDisplayAd( + block, + totalBlocks, + numAdsInserted, + numPixelsWithoutAdvert, + isMobile, + ); + + assert.ok(!result); + }, + ); + } + }); + + void nodeDescribe('inserting the first ad slot', () => { + for (const screenSize of ['mobile', 'desktop']) { + void nodeIt( + `should display ad if this is the first block on ${screenSize}.`, + () => { + const isMobile = screenSize === 'mobile'; + const block = 1; + const totalBlocks = 10; + const numAdsInserted = 0; + const numPixelsWithoutAdvert = 550; + + const result = shouldDisplayAd( + block, + totalBlocks, + numAdsInserted, + numPixelsWithoutAdvert, + isMobile, + ); + + assert.ok(result); + }, + ); + } + }); + + void nodeDescribe('inserting further ad slots', () => { + for (const [pixels, screenSize] of [ + [1200, 'mobile'], + [1500, 'desktop'], + ] as const) { + void nodeIt( + `should display ad if number of pixels without an ad is more than ${pixels} on ${screenSize}`, + () => { + const isMobile = screenSize === 'mobile'; + const block = 5; + const totalBlocks = 10; + const numAdsInserted = 1; + const numPixelsWithoutAdvert = pixels + 50; + + const result = shouldDisplayAd( + block, + totalBlocks, + numAdsInserted, + numPixelsWithoutAdvert, + isMobile, + ); + + assert.ok(result); + }, + ); + } + + for (const [pixels, screenSize] of [ + [1200, 'mobile'], + [1500, 'desktop'], + ] as const) { + void nodeIt( + `should NOT display ad if number of pixels without an ad is less than ${pixels} on ${screenSize}`, + () => { + const isMobile = screenSize === 'mobile'; + const block = 5; + const totalBlocks = 10; + const numAdsInserted = 1; + const numPixelsWithoutAdvert = pixels - 50; + + const result = shouldDisplayAd( + block, + totalBlocks, + numAdsInserted, + numPixelsWithoutAdvert, + isMobile, + ); + + assert.ok(!result); + }, + ); + } + }); +}); diff --git a/dotcom-rendering/src/lib/liveblogAdSlots.test.ts b/dotcom-rendering/src/lib/liveblogAdSlots.test.ts deleted file mode 100644 index 99cb32ef8f7..00000000000 --- a/dotcom-rendering/src/lib/liveblogAdSlots.test.ts +++ /dev/null @@ -1,260 +0,0 @@ -import type { FEElement } from '../types/content'; -import { - calculateApproximateBlockHeight, - shouldDisplayAd, -} from './liveblogAdSlots'; - -describe('calculateApproximateBlockHeight', () => { - const textElementOneLineDesktop: FEElement[] = [ - { - elementId: '1', - _type: 'model.dotcomrendering.pageElements.TextBlockElement', - html: `

${'a'.repeat(72)}

`, - }, - ]; - - const textElementTwoLinesDestkop: FEElement[] = [ - { - elementId: '1', - _type: 'model.dotcomrendering.pageElements.TextBlockElement', - html: `

${'a'.repeat(73)}

`, - }, - ]; - - const textElementOneLineMobile: FEElement[] = [ - { - elementId: '1', - _type: 'model.dotcomrendering.pageElements.TextBlockElement', - html: `

${'a'.repeat(39)}

`, - }, - ]; - - const textElementTwoLinesMobile: FEElement[] = [ - { - elementId: '1', - _type: 'model.dotcomrendering.pageElements.TextBlockElement', - html: `

${'a'.repeat(40)}

`, - }, - ]; - - const multipleTextElements: FEElement[] = [ - { - elementId: '1', - _type: 'model.dotcomrendering.pageElements.TextBlockElement', - html: `

${'a'.repeat(38)}

`, - }, - { - elementId: '2', - _type: 'model.dotcomrendering.pageElements.TextBlockElement', - html: `

${'a'.repeat(38)}

`, - }, - ]; - - const youtubeElement: FEElement[] = [ - { - _type: 'model.dotcomrendering.pageElements.YoutubeBlockElement', - id: '1', - elementId: '2', - assetId: '', - expired: false, - mediaTitle: '', - }, - ]; - - const defaultBlockSpacing = 75; - - describe('zero elements', () => { - it.each(['mobile', 'desktop'])( - 'should return zero when there are zero elements on %s', - (screenSize) => { - const isMobile = screenSize === 'mobile'; - expect(calculateApproximateBlockHeight([], isMobile)).toEqual( - 0, - ); - }, - ); - }); - - describe('text block elements', () => { - const textLineHeight = 23.8; - const margin = 14; - - it.each([ - ['mobile', textElementOneLineMobile, textElementTwoLinesMobile], - ['desktop', textElementOneLineDesktop, textElementTwoLinesDestkop], - ])( - 'should return the correct height for varying line length on %s', - (screenSize, textElementOneLine, textElementTwoLines) => { - const isMobile = screenSize === 'mobile'; - - expect( - calculateApproximateBlockHeight( - textElementOneLine, - isMobile, - ), - ).toEqual(textLineHeight + margin + defaultBlockSpacing); - expect( - calculateApproximateBlockHeight( - textElementTwoLines, - isMobile, - ), - ).toEqual(2 * textLineHeight + margin + defaultBlockSpacing); - }, - ); - - it.each(['mobile', 'desktop'])( - 'should return the correct height when there are multiple elements on %s', - (screenSize) => { - const isMobile = screenSize === 'mobile'; - - expect( - calculateApproximateBlockHeight( - multipleTextElements, - isMobile, - ), - ).toEqual( - 2 * textLineHeight + 2 * margin + defaultBlockSpacing, - ); - }, - ); - }); - - describe('youtube block elements', () => { - it.each([ - ['mobile', 195], - ['desktop', 350], - ])( - 'should return the correct height on %s', - (screenSize, heightExcludingText) => { - const isMobile = screenSize === 'mobile'; - const margin = 12; - - expect( - calculateApproximateBlockHeight(youtubeElement, isMobile), - ).toEqual(heightExcludingText + margin + defaultBlockSpacing); - }, - ); - }); -}); - -describe('shouldDisplayAd', () => { - describe('The final block of content', () => { - it.each(['mobile', 'desktop'])( - 'should NOT display an ad if this is the final block on %s', - (screenSize) => { - const isMobile = screenSize === 'mobile'; - - const block = 5; - const totalBlocks = 5; - const numAdsInserted = 1; - const numPixelsWithoutAdvert = 5000; - - const result = shouldDisplayAd( - block, - totalBlocks, - numAdsInserted, - numPixelsWithoutAdvert, - isMobile, - ); - - expect(result).toBeFalsy(); - }, - ); - }); - - describe('Reaching the ad limit', () => { - it.each(['mobile', 'desktop'])( - 'should NOT insert another ad slot if we have reached the limit on %s.', - (screenSize) => { - const isMobile = screenSize === 'mobile'; - const block = 5; - const totalBlocks = 10; - const numAdsInserted = 8; - const numPixelsWithoutAdvert = 5000; - - const result = shouldDisplayAd( - block, - totalBlocks, - numAdsInserted, - numPixelsWithoutAdvert, - isMobile, - ); - - expect(result).toBeFalsy(); - }, - ); - }); - - describe('inserting the first ad slot', () => { - it.each(['mobile', 'desktop'])( - 'should display ad if this is the first block on %s.', - (screenSize) => { - const isMobile = screenSize === 'mobile'; - const block = 1; - const totalBlocks = 10; - const numAdsInserted = 0; - const numPixelsWithoutAdvert = 550; - - const result = shouldDisplayAd( - block, - totalBlocks, - numAdsInserted, - numPixelsWithoutAdvert, - isMobile, - ); - - expect(result).toBeTruthy(); - }, - ); - }); - - describe('inserting further ad slots', () => { - it.each([ - [1200, 'mobile'], - [1500, 'desktop'], - ])( - 'should display ad if number of pixels without an ad is more than %s on %s', - (pixels, screenSize) => { - const isMobile = screenSize === 'mobile'; - const block = 5; - const totalBlocks = 10; - const numAdsInserted = 1; - const numPixelsWithoutAdvert = pixels + 50; - - const result = shouldDisplayAd( - block, - totalBlocks, - numAdsInserted, - numPixelsWithoutAdvert, - isMobile, - ); - - expect(result).toBeTruthy(); - }, - ); - - it.each([ - [1200, 'mobile'], - [1500, 'desktop'], - ])( - 'should NOT display ad if number of pixels without an ad is less than %s on %s', - (pixels, screenSize) => { - const isMobile = screenSize === 'mobile'; - const block = 5; - const totalBlocks = 10; - const numAdsInserted = 1; - const numPixelsWithoutAdvert = pixels - 50; - - const result = shouldDisplayAd( - block, - totalBlocks, - numAdsInserted, - numPixelsWithoutAdvert, - isMobile, - ); - - expect(result).toBeFalsy(); - }, - ); - }); -}); diff --git a/dotcom-rendering/src/lib/notification.test.ts b/dotcom-rendering/src/lib/notification.node.test.ts similarity index 57% rename from dotcom-rendering/src/lib/notification.test.ts rename to dotcom-rendering/src/lib/notification.node.test.ts index cc331b5be59..6d67df6af9f 100644 --- a/dotcom-rendering/src/lib/notification.test.ts +++ b/dotcom-rendering/src/lib/notification.node.test.ts @@ -1,7 +1,9 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; import { addNotificationsToDropdownLinks } from './notification'; -describe('addNotificationsToDropdownLinks', () => { - it('augments dropdown links with notifications', () => { +void nodeDescribe('addNotificationsToDropdownLinks', () => { + void nodeIt('augments dropdown links with notifications', () => { const links = [ { id: 'account_overview', @@ -30,7 +32,7 @@ describe('addNotificationsToDropdownLinks', () => { notifications, ); - expect(linksWithNotifications).toEqual([ + assert.deepEqual(linksWithNotifications, [ { id: 'account_overview', url: `https://example.com/account_overview`, @@ -54,7 +56,7 @@ describe('addNotificationsToDropdownLinks', () => { ]); }); - it('adds multiple notification messages to a link', () => { + void nodeIt('adds multiple notification messages to a link', () => { const links = [ { id: 'account_overview', @@ -83,7 +85,7 @@ describe('addNotificationsToDropdownLinks', () => { notifications, ); - expect(linksWithNotifications).toEqual([ + assert.deepEqual(linksWithNotifications, [ { id: 'account_overview', url: `https://example.com/account_overview`, @@ -107,58 +109,61 @@ describe('addNotificationsToDropdownLinks', () => { ]); }); - it('adds new notifications if target already has notifications', () => { - const links = [ - { - id: 'account_overview', - url: `https://example.com/account_overview`, - title: 'Account overview', - dataLinkName: 'nav2 : topbar : account overview', - notifications: [ - { - id: 'existing', - message: 'Existing notification message', - target: 'account_overview', - ophanLabel: 'notification-label-1', - }, - ], - }, - ]; - const notifications = [ - { - id: 'new', - message: 'New notification message', - target: 'account_overview', - ophanLabel: 'notification-label-2', - }, - ]; + void nodeIt( + 'adds new notifications if target already has notifications', + () => { + const links = [ + { + id: 'account_overview', + url: `https://example.com/account_overview`, + title: 'Account overview', + dataLinkName: 'nav2 : topbar : account overview', + notifications: [ + { + id: 'existing', + message: 'Existing notification message', + target: 'account_overview', + ophanLabel: 'notification-label-1', + }, + ], + }, + ]; + const notifications = [ + { + id: 'new', + message: 'New notification message', + target: 'account_overview', + ophanLabel: 'notification-label-2', + }, + ]; - const linksWithNotifications = addNotificationsToDropdownLinks( - links, - notifications, - ); + const linksWithNotifications = addNotificationsToDropdownLinks( + links, + notifications, + ); - expect(linksWithNotifications).toEqual([ - { - id: 'account_overview', - url: `https://example.com/account_overview`, - title: 'Account overview', - dataLinkName: 'nav2 : topbar : account overview', - notifications: [ - { - id: 'existing', - message: 'Existing notification message', - target: 'account_overview', - ophanLabel: 'notification-label-1', - }, - { - id: 'new', - message: 'New notification message', - target: 'account_overview', - ophanLabel: 'notification-label-2', - }, - ], - }, - ]); - }); + assert.deepEqual(linksWithNotifications, [ + { + id: 'account_overview', + url: `https://example.com/account_overview`, + title: 'Account overview', + dataLinkName: 'nav2 : topbar : account overview', + notifications: [ + { + id: 'existing', + message: 'Existing notification message', + target: 'account_overview', + ophanLabel: 'notification-label-1', + }, + { + id: 'new', + message: 'New notification message', + target: 'account_overview', + ophanLabel: 'notification-label-2', + }, + ], + }, + ]); + }, + ); }); diff --git a/dotcom-rendering/src/lib/video.node.test.ts b/dotcom-rendering/src/lib/video.node.test.ts new file mode 100644 index 00000000000..70939fc4afb --- /dev/null +++ b/dotcom-rendering/src/lib/video.node.test.ts @@ -0,0 +1,502 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import type { FEMediaAsset } from '../frontend/feFront'; +import type { VideoAssets } from '../types/content'; +import type { Source } from './video'; +import { + convertCurrentTimeToProgressPercentage, + convertFEMediaAssetsToVideoAssets, + convertProgressPercentageToCurrentTime, + extractValidSourcesFromAssets, + findOptimisedSourcePerMimeType, + formatTimeForDisplay, + getAspectRatioFromSources, + roundAspectRatio, +} from './video'; + +const mp4Asset480w: VideoAssets = { + url: 'https://guim-example.co.uk/atomID-1_480w.mp4', + mimeType: 'video/mp4', + dimensions: { + height: 384, + width: 480, + }, + aspectRatio: '5:4', + hasAudio: true, +}; + +const mp4Asset720h: VideoAssets = { + url: 'https://guim-example.co.uk/atomID-1_720h.mp4', + mimeType: 'video/mp4', + dimensions: { + height: 720, + width: 900, + }, + aspectRatio: '5:4', + hasAudio: true, +}; + +const m3u8Asset720h: VideoAssets = { + url: 'https://guim-example.co.uk/atomID-1.m3u8', + mimeType: 'application/x-mpegURL', + dimensions: { + height: 720, + width: 900, + }, + aspectRatio: '5:4', + hasAudio: true, +}; +const unsupportedAsset: VideoAssets = { + url: 'https://guim-example.co.uk/atomID-1.mov', + mimeType: 'video/quicktime', + dimensions: { + height: 720, + width: 900, + }, + aspectRatio: '5:4', + hasAudio: true, +}; + +const mp4Src480w: Source = { + src: 'https://guim-example.co.uk/atomID-1_480w.mp4', + mimeType: 'video/mp4', + height: 384, + width: 480, + aspectRatio: '5:4', + hasAudio: true, +}; +const mp4Src720h: Source = { + src: 'https://guim-example.co.uk/atomID-1_720h.mp4', + mimeType: 'video/mp4', + height: 720, + width: 900, + aspectRatio: '5:4', + hasAudio: true, +}; +const m3u8Src480w: Source = { + src: 'https://guim-example.co.uk/atomID-1.m3u8', + mimeType: 'application/x-mpegURL', + height: 384, + width: 480, + aspectRatio: '5:4', + hasAudio: true, +}; +const m3u8Src720h: Source = { + src: 'https://guim-example.co.uk/atomID-1.m3u8', + mimeType: 'application/x-mpegURL', + height: 720, + width: 900, + aspectRatio: '5:4', + hasAudio: true, +}; + +void nodeDescribe('video', () => { + void nodeDescribe('extractValidSourcesFromAssets', () => { + void nodeIt('should drop unsupported assets', () => { + const assets = [mp4Asset480w, m3u8Asset720h, unsupportedAsset]; + const expected = [mp4Src480w, m3u8Src720h]; + + assert.deepEqual( + extractValidSourcesFromAssets(assets, 'Loop'), + expected, + ); + }); + + void nodeIt( + 'should reorder sources by supportedVideoFileTypes order', + () => { + const assets = [ + m3u8Asset720h, + mp4Asset480w, + m3u8Asset720h, + mp4Asset720h, + m3u8Asset720h, + ]; + const expected = [ + mp4Src480w, + mp4Src720h, + m3u8Src720h, + m3u8Src720h, + m3u8Src720h, + ]; + assert.deepEqual( + extractValidSourcesFromAssets(assets, 'Loop'), + expected, + ); + }, + ); + + void nodeIt( + 'should prefer M3U8 sources for long videos with Default video style', + () => { + const assets = [mp4Asset480w, m3u8Asset720h, mp4Asset720h]; + const expected = [m3u8Src720h, mp4Src480w, mp4Src720h]; + + assert.deepEqual( + extractValidSourcesFromAssets(assets, 'Default', 37), + expected, + ); + }, + ); + + void nodeIt( + 'should prefer MP4 sources for short videos with Default video style', + () => { + const assets = [mp4Asset480w, m3u8Asset720h, mp4Asset720h]; + const expected = [mp4Src480w, mp4Src720h, m3u8Src720h]; + + assert.deepEqual( + extractValidSourcesFromAssets(assets, 'Default', 12), + expected, + ); + }, + ); + + void nodeIt('should prefer MP4 sources with Loop video style', () => { + const assets = [mp4Asset480w, m3u8Asset720h, mp4Asset720h]; + const expected = [mp4Src480w, mp4Src720h, m3u8Src720h]; + + assert.deepEqual( + extractValidSourcesFromAssets(assets, 'Loop'), + expected, + ); + }); + + void nodeIt( + 'should prefer MP4 sources with Cinemagraph video style', + () => { + const assets = [mp4Asset480w, m3u8Asset720h, mp4Asset720h]; + const expected = [mp4Src480w, mp4Src720h, m3u8Src720h]; + + assert.deepEqual( + extractValidSourcesFromAssets(assets, 'Cinemagraph'), + expected, + ); + }, + ); + }); + + void nodeDescribe('convertFEMediaAssetsToVideoAssets', () => { + const feMediaAsset480w: FEMediaAsset = { + id: 'https://guim-example.co.uk/atomID-1_480w.mp4', + version: 1, + platform: 'Url', + assetType: 'video', + mimeType: 'video/mp4', + dimensions: { + height: 384, + width: 480, + }, + hasAudio: true, + }; + const feMediaAsset720h: FEMediaAsset = { + id: 'https://guim-example.co.uk/atomID-1_720h.mp4', + version: 1, + platform: 'Url', + assetType: 'video', + mimeType: 'video/mp4', + dimensions: { + height: 720, + width: 900, + }, + hasAudio: true, + }; + + void nodeIt('should convert FE media assets to video assets', () => { + assert.deepEqual( + convertFEMediaAssetsToVideoAssets([ + feMediaAsset480w, + feMediaAsset720h, + ]), + [ + { + url: 'https://guim-example.co.uk/atomID-1_480w.mp4', + mimeType: 'video/mp4', + aspectRatio: undefined, + dimensions: { + height: 384, + width: 480, + }, + hasAudio: true, + }, + { + url: 'https://guim-example.co.uk/atomID-1_720h.mp4', + mimeType: 'video/mp4', + aspectRatio: undefined, + dimensions: { + height: 720, + width: 900, + }, + hasAudio: true, + }, + ], + ); + }); + + void nodeIt( + 'should return an empty array when given an empty array', + () => { + assert.deepEqual(convertFEMediaAssetsToVideoAssets([]), []); + }, + ); + }); + + void nodeDescribe('getAspectRatioFromSources', () => { + void nodeIt( + 'should return the aspect ratio from the first source if it is defined', + () => { + const testSource: Source = { + ...mp4Src480w, + height: 720, + width: 480, + aspectRatio: '5:3', + hasAudio: true, + }; + + const fiveThreeAspectRatio = 1.667; + + assert.deepEqual( + getAspectRatioFromSources([testSource]), + fiveThreeAspectRatio, + ); + }, + ); + + void nodeIt( + 'should calculate the aspect ratio from the width and height if aspect ratio is missing', + () => { + const testSource: Source = { + ...mp4Src480w, + height: 720, + width: 480, + aspectRatio: undefined, + hasAudio: true, + }; + + const twoThreeAspectRatio = 0.667; + + assert.deepEqual( + getAspectRatioFromSources([testSource]), + twoThreeAspectRatio, + ); + }, + ); + + void nodeIt( + 'should return the default aspect ratio if the aspect ratio is undefined and width is 0', + () => { + const testSource: Source = { + ...mp4Src480w, + height: 720, + width: 0, + aspectRatio: undefined, + hasAudio: true, + }; + assert.deepEqual( + getAspectRatioFromSources([testSource]), + 5 / 4, + ); + }, + ); + + void nodeIt( + 'should return the default aspect ratio if the aspect ratio is undefined and height is 0', + () => { + const testSource: Source = { + ...mp4Src480w, + height: 0, + width: 480, + aspectRatio: undefined, + hasAudio: true, + }; + assert.deepEqual( + getAspectRatioFromSources([testSource]), + 5 / 4, + ); + }, + ); + }); + + void nodeDescribe('findOptimisedSourcePerMimeType', () => { + const testSources: Source[] = [ + mp4Src480w, + mp4Src720h, + m3u8Src480w, + m3u8Src720h, + ]; + + void nodeIt( + 'selects the smaller videos when there are multiple and all are larger than the screen width.', + () => { + const screenWidth = 400; + + const sources = findOptimisedSourcePerMimeType( + testSources, + screenWidth, + ); + + assert.deepEqual(sources, [mp4Src480w, m3u8Src480w]); + }, + ); + + void nodeIt( + 'selects the larger videos when there are two and one is larger than the screen width and one is smaller.', + () => { + const screenWidth = 600; + + const sources = findOptimisedSourcePerMimeType( + testSources, + screenWidth, + ); + + assert.deepEqual(sources, [mp4Src720h, m3u8Src720h]); + }, + ); + + void nodeIt( + 'selects the larger videos when there are multiple and all are smaller than the screen width.', + () => { + const screenWidth = 800; + + const sources = findOptimisedSourcePerMimeType( + testSources, + screenWidth, + ); + + assert.deepEqual(sources, [mp4Src720h, m3u8Src720h]); + }, + ); + + void nodeIt( + 'selects the smaller videos when some are equal to the screen width and others are larger.', + () => { + const screenWidth = 480; + + const sources = findOptimisedSourcePerMimeType( + testSources, + screenWidth, + ); + + assert.deepEqual(sources, [mp4Src480w, m3u8Src480w]); + }, + ); + + void nodeIt( + 'selects the larger videos when some are equal to the screen width and others are smaller.', + () => { + const screenWidth = 720; + + const sources = findOptimisedSourcePerMimeType( + testSources, + screenWidth, + ); + + assert.deepEqual(sources, [mp4Src720h, m3u8Src720h]); + }, + ); + }); + + void nodeDescribe('convertCurrentTimeToProgressPercentage', () => { + for (const testCase of [ + { currentTime: 0, duration: 23, expectedPercentage: 0 }, + { currentTime: 24, duration: 32, expectedPercentage: 75 }, + { currentTime: 56, duration: 56, expectedPercentage: 100 }, + { currentTime: 12, duration: 11, expectedPercentage: 100 }, + { currentTime: -5, duration: 10, expectedPercentage: null }, + { currentTime: 5, duration: -10, expectedPercentage: null }, + ]) { + void nodeIt( + 'should return the correct progress percentage based on the current time and duration', + () => { + const { currentTime, duration, expectedPercentage } = + testCase; + assert.deepEqual( + convertCurrentTimeToProgressPercentage( + currentTime, + duration, + ), + expectedPercentage, + ); + }, + ); + } + }); + + void nodeDescribe('convertProgressPercentageToCurrentTime', () => { + for (const testCase of [ + { progressPercentage: 0, duration: 23, expectedCurrentTime: 0 }, + { progressPercentage: 75, duration: 32, expectedCurrentTime: 24 }, + { progressPercentage: 100, duration: 56, expectedCurrentTime: 56 }, + { progressPercentage: 103, duration: 11, expectedCurrentTime: 11 }, + { progressPercentage: 10, duration: 0, expectedCurrentTime: null }, + { progressPercentage: 8, duration: -10, expectedCurrentTime: null }, + { + progressPercentage: -0.1244235, + duration: 10, + expectedCurrentTime: 0, + }, + ]) { + void nodeIt( + 'should return the correct current time based on the progress percentage and duration', + () => { + const { + progressPercentage, + duration, + expectedCurrentTime, + } = testCase; + assert.deepEqual( + convertProgressPercentageToCurrentTime( + progressPercentage, + duration, + ), + expectedCurrentTime, + ); + }, + ); + } + }); + + void nodeDescribe('formatTimeForDisplay', () => { + for (const testCase of [ + { timeInSeconds: -1.24, expectedFormattedTime: '0:00' }, + { timeInSeconds: 0, expectedFormattedTime: '0:00' }, + { timeInSeconds: 59, expectedFormattedTime: '0:59' }, + { timeInSeconds: 60, expectedFormattedTime: '1:00' }, + { timeInSeconds: 61, expectedFormattedTime: '1:01' }, + { timeInSeconds: 92.5, expectedFormattedTime: '1:32' }, + { timeInSeconds: 1000, expectedFormattedTime: '16:40' }, + { timeInSeconds: 10000, expectedFormattedTime: '166:40' }, + ]) { + void nodeIt( + 'should return the correct formatted time based on the time in seconds', + () => { + const { timeInSeconds, expectedFormattedTime } = testCase; + assert.deepEqual( + formatTimeForDisplay(timeInSeconds), + expectedFormattedTime, + ); + }, + ); + } + }); + void nodeDescribe('roundAspectRatio', () => { + for (const testCase of [ + { aspectRatio: 0.56938445, expectedRoundedAspectRatio: 0.569 }, + { aspectRatio: 1.277777, expectedRoundedAspectRatio: 1.278 }, + { aspectRatio: 1.25, expectedRoundedAspectRatio: 1.25 }, + { aspectRatio: 0.8, expectedRoundedAspectRatio: 0.8 }, + ]) { + void nodeIt( + 'should return the correct aspect ratio rounded to 3 decimal places', + () => { + const { aspectRatio, expectedRoundedAspectRatio } = + testCase; + assert.deepEqual( + roundAspectRatio(aspectRatio), + expectedRoundedAspectRatio, + ); + }, + ); + } + }); +}); diff --git a/dotcom-rendering/src/lib/video.test.ts b/dotcom-rendering/src/lib/video.test.ts index ac23ac49b5f..8c178a30c19 100644 --- a/dotcom-rendering/src/lib/video.test.ts +++ b/dotcom-rendering/src/lib/video.test.ts @@ -1,3 +1,5 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; import type { FEMediaAsset } from '../frontend/feFront'; import type { VideoAssets } from '../types/content'; import type { Source } from './video'; @@ -336,28 +338,32 @@ describe('video', () => { }); describe('convertCurrentTimeToProgressPercentage', () => { - it.each([ + for (const testCase of [ { currentTime: 0, duration: 23, expectedPercentage: 0 }, { currentTime: 24, duration: 32, expectedPercentage: 75 }, { currentTime: 56, duration: 56, expectedPercentage: 100 }, { currentTime: 12, duration: 11, expectedPercentage: 100 }, { currentTime: -5, duration: 10, expectedPercentage: null }, { currentTime: 5, duration: -10, expectedPercentage: null }, - ])( - 'should return the correct progress percentage based on the current time and duration', - ({ currentTime, duration, expectedPercentage }) => { - expect( - convertCurrentTimeToProgressPercentage( - currentTime, - duration, - ), - ).toEqual(expectedPercentage); - }, - ); + ]) { + void nodeIt( + 'should return the correct progress percentage based on the current time and duration', + () => { + const { currentTime, duration, expectedPercentage } = + testCase; + expect( + convertCurrentTimeToProgressPercentage( + currentTime, + duration, + ), + ).toEqual(expectedPercentage); + }, + ); + } }); describe('convertProgressPercentageToCurrentTime', () => { - it.each([ + for (const testCase of [ { progressPercentage: 0, duration: 23, expectedCurrentTime: 0 }, { progressPercentage: 75, duration: 32, expectedCurrentTime: 24 }, { progressPercentage: 100, duration: 56, expectedCurrentTime: 56 }, @@ -369,21 +375,28 @@ describe('video', () => { duration: 10, expectedCurrentTime: 0, }, - ])( - 'should return the correct current time based on the progress percentage and duration', - ({ progressPercentage, duration, expectedCurrentTime }) => { - expect( - convertProgressPercentageToCurrentTime( + ]) { + void nodeIt( + 'should return the correct current time based on the progress percentage and duration', + () => { + const { progressPercentage, duration, - ), - ).toEqual(expectedCurrentTime); - }, - ); + expectedCurrentTime, + } = testCase; + expect( + convertProgressPercentageToCurrentTime( + progressPercentage, + duration, + ), + ).toEqual(expectedCurrentTime); + }, + ); + } }); describe('formatTimeForDisplay', () => { - it.each([ + for (const testCase of [ { timeInSeconds: -1.24, expectedFormattedTime: '0:00' }, { timeInSeconds: 0, expectedFormattedTime: '0:00' }, { timeInSeconds: 59, expectedFormattedTime: '0:59' }, @@ -392,28 +405,35 @@ describe('video', () => { { timeInSeconds: 92.5, expectedFormattedTime: '1:32' }, { timeInSeconds: 1000, expectedFormattedTime: '16:40' }, { timeInSeconds: 10000, expectedFormattedTime: '166:40' }, - ])( - 'should return the correct formatted time based on the time in seconds', - ({ timeInSeconds, expectedFormattedTime }) => { - expect(formatTimeForDisplay(timeInSeconds)).toEqual( - expectedFormattedTime, - ); - }, - ); + ]) { + void nodeIt( + 'should return the correct formatted time based on the time in seconds', + () => { + const { timeInSeconds, expectedFormattedTime } = testCase; + expect(formatTimeForDisplay(timeInSeconds)).toEqual( + expectedFormattedTime, + ); + }, + ); + } }); describe('roundAspectRatio', () => { - it.each([ + for (const testCase of [ { aspectRatio: 0.56938445, expectedRoundedAspectRatio: 0.569 }, { aspectRatio: 1.277777, expectedRoundedAspectRatio: 1.278 }, { aspectRatio: 1.25, expectedRoundedAspectRatio: 1.25 }, { aspectRatio: 0.8, expectedRoundedAspectRatio: 0.8 }, - ])( - 'should return the correct aspect ratio rounded to 3 decimal places', - ({ aspectRatio, expectedRoundedAspectRatio }) => { - expect(roundAspectRatio(aspectRatio)).toEqual( - expectedRoundedAspectRatio, - ); - }, - ); + ]) { + void nodeIt( + 'should return the correct aspect ratio rounded to 3 decimal places', + () => { + const { aspectRatio, expectedRoundedAspectRatio } = + testCase; + expect(roundAspectRatio(aspectRatio)).toEqual( + expectedRoundedAspectRatio, + ); + }, + ); + } }); }); diff --git a/dotcom-rendering/src/model/buildLightboxImages.node.test.ts b/dotcom-rendering/src/model/buildLightboxImages.node.test.ts new file mode 100644 index 00000000000..fc6e83ee503 --- /dev/null +++ b/dotcom-rendering/src/model/buildLightboxImages.node.test.ts @@ -0,0 +1,422 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { Standard as ExampleArticle } from '../../fixtures/generated/fe-articles/Standard'; +import { images } from '../../fixtures/generated/images'; +import type { Block } from '../types/blocks'; +import type { + FEElement, + MultiImageBlockElement, + ProductBlockElement, + ProductCta, + ProductImage, +} from '../types/content'; +import { buildLightboxImages } from './buildLightboxImages'; + +const format = ExampleArticle.format; + +// Well above the 620px lightbox threshold +const largeImage = images[0]; + +const largeProductImage: ProductImage = { + url: 'https://media.guim.co.uk/large-product/900.jpg', + caption: 'A large product image', + credit: 'Photograph: Test/The Guardian', + alt: 'A large product', + displayCredit: false, + height: 900, + width: 900, +}; + +const smallProductImage: ProductImage = { + url: 'https://media.guim.co.uk/small-product/300.jpg', + caption: 'A small product image', + credit: 'Photograph: Test/The Guardian', + alt: 'A small product', + displayCredit: false, + height: 300, + width: 300, +}; + +const productCtas: ProductCta[] = [ + { + url: 'https://example.com/buy-1', + text: '', + retailer: 'Amazon', + price: '£19.99', + }, + { + url: 'https://example.com/buy-2', + text: '', + retailer: 'John Lewis', + price: '£21.00', + }, +]; + +const baseProduct: ProductBlockElement = { + _type: 'model.dotcomrendering.pageElements.ProductBlockElement', + elementId: 'product-1', + brandName: 'Acme', + starRating: '5', + productName: 'Widget', + primaryHeadingHtml: '', + secondaryHeadingHtml: '', + customAttributes: [], + content: [], + id: '123', + displayType: 'InlineOnly', + productCtas: [], +}; + +const emptyAttributes = { pinned: false, summary: false, keyEvent: false }; + +const buildBlock = (elements: FEElement[]): Block => ({ + id: 'block-1', + elements, + attributes: emptyAttributes, + primaryDateLine: '', + secondaryDateLine: '', +}); + +void nodeDescribe('buildLightboxImages', () => { + void nodeIt( + "includes a product's own image when it is large enough", + () => { + const product: ProductBlockElement = { + ...baseProduct, + image: largeProductImage, + }; + + const result = buildLightboxImages( + format, + [buildBlock([product])], + [], + ); + + assert.equal(result.length, 1); + assert.deepEqual( + { + masterUrl: result[0]?.masterUrl, + elementId: result[0]?.elementId, + width: result[0]?.width, + height: result[0]?.height, + position: result[0]?.position, + }, + { + masterUrl: largeProductImage.url, + elementId: product.elementId, + width: largeProductImage.width, + height: largeProductImage.height, + position: 1, + }, + ); + }, + ); + + void nodeIt("excludes a product's own image when it is too small", () => { + const product: ProductBlockElement = { + ...baseProduct, + image: smallProductImage, + }; + + const result = buildLightboxImages(format, [buildBlock([product])], []); + + assert.deepEqual(result, []); + }); + + void nodeIt('excludes a product with no image', () => { + const result = buildLightboxImages( + format, + [buildBlock([baseProduct])], + [], + ); + + assert.deepEqual(result, []); + }); + + void nodeIt("includes images nested inside a product's content", () => { + const product: ProductBlockElement = { + ...baseProduct, + content: [largeImage], + }; + + const result = buildLightboxImages(format, [buildBlock([product])], []); + + assert.equal(result.length, 1); + assert.deepEqual(result[0]?.elementId, largeImage.elementId); + }); + + void nodeIt( + 'assigns positions in document order across regular and product images', + () => { + const product: ProductBlockElement = { + ...baseProduct, + elementId: 'product-2', + image: largeProductImage, + content: [largeImage], + }; + + const result = buildLightboxImages( + format, + [buildBlock([product])], + [], + ); + + assert.deepEqual( + result.map((image) => image.elementId), + [largeImage.elementId, product.elementId], + ); + assert.deepEqual( + result.map((image) => image.position), + [1, 2], + ); + }, + ); + + void nodeIt("includes a product's own CTAs on its card image", () => { + const product: ProductBlockElement = { + ...baseProduct, + image: largeProductImage, + productCtas, + }; + + const result = buildLightboxImages(format, [buildBlock([product])], []); + + assert.deepEqual(result[0]?.productCtas, productCtas); + }); + + void nodeIt('omits productCtas entirely when a product has none', () => { + const product: ProductBlockElement = { + ...baseProduct, + image: largeProductImage, + productCtas: [], + }; + + const result = buildLightboxImages(format, [buildBlock([product])], []); + + assert.equal(result[0]?.productCtas, undefined); + }); + + void nodeIt( + "includes the owning product's CTAs on an image nested inside its content", + () => { + const product: ProductBlockElement = { + ...baseProduct, + content: [largeImage], + productCtas, + }; + + const result = buildLightboxImages( + format, + [buildBlock([product])], + [], + ); + + assert.equal(result.length, 1); + assert.deepEqual(result[0]?.productCtas, productCtas); + }, + ); + + void nodeIt( + "uses the innermost product's CTAs for an image nested inside a product nested in another product's content", + () => { + const innerCtas: ProductCta[] = [ + { + url: 'https://example.com/inner', + text: '', + retailer: 'Inner', + price: '£5', + }, + ]; + const outerCtas: ProductCta[] = [ + { + url: 'https://example.com/outer', + text: '', + retailer: 'Outer', + price: '£50', + }, + ]; + const innerProduct: ProductBlockElement = { + ...baseProduct, + elementId: 'inner-product', + content: [largeImage], + productCtas: innerCtas, + }; + const outerProduct: ProductBlockElement = { + ...baseProduct, + elementId: 'outer-product', + content: [innerProduct], + productCtas: outerCtas, + }; + + const result = buildLightboxImages( + format, + [buildBlock([outerProduct])], + [], + ); + + assert.equal(result.length, 1); + assert.deepEqual(result[0]?.productCtas, innerCtas); + }, + ); + + void nodeIt( + "falls back to the product's own caption for a content image with no caption of its own", + () => { + const product: ProductBlockElement = { + ...baseProduct, + image: largeProductImage, + content: [largeImage], + }; + + const result = buildLightboxImages( + format, + [buildBlock([product])], + [], + ); + + const contentEntry = result.find( + (image) => image.elementId === largeImage.elementId, + ); + assert.deepEqual(contentEntry?.caption, largeProductImage.caption); + }, + ); + + void nodeIt( + "keeps a content image's own caption instead of falling back to the product's", + () => { + const imageWithOwnCaption = { + ...largeImage, + data: { + ...largeImage.data, + caption: "The image's own caption", + }, + }; + const product: ProductBlockElement = { + ...baseProduct, + image: largeProductImage, + content: [imageWithOwnCaption], + }; + + const result = buildLightboxImages( + format, + [buildBlock([product])], + [], + ); + + const contentEntry = result.find( + (image) => image.elementId === largeImage.elementId, + ); + assert.deepEqual(contentEntry?.caption, "The image's own caption"); + }, + ); + + void nodeIt( + "uses the innermost product's caption for an image nested inside a product nested in another product's content", + () => { + const innerProductImage: ProductImage = { + ...largeProductImage, + caption: 'Inner product caption', + }; + const outerProductImage: ProductImage = { + ...largeProductImage, + caption: 'Outer product caption', + }; + const innerProduct: ProductBlockElement = { + ...baseProduct, + elementId: 'inner-product', + content: [largeImage], + image: innerProductImage, + }; + const outerProduct: ProductBlockElement = { + ...baseProduct, + elementId: 'outer-product', + content: [innerProduct], + image: outerProductImage, + }; + + const result = buildLightboxImages( + format, + [buildBlock([outerProduct])], + [], + ); + + const contentEntry = result.find( + (image) => image.elementId === largeImage.elementId, + ); + assert.deepEqual(contentEntry?.caption, innerProductImage.caption); + }, + ); + + void nodeIt( + "keeps a product's content image and card image adjacent, rather than grouping all content images before all card images", + () => { + const secondImage = images[1]; + + const productA: ProductBlockElement = { + ...baseProduct, + elementId: 'product-a', + image: largeProductImage, + content: [largeImage], + productCtas, + }; + const productB: ProductBlockElement = { + ...baseProduct, + elementId: 'product-b', + image: { + ...largeProductImage, + url: 'https://media.guim.co.uk/large-product-b/900.jpg', + }, + content: [secondImage], + productCtas, + }; + + const result = buildLightboxImages( + format, + [buildBlock([productA, productB])], + [], + ); + + assert.deepEqual( + result.map((image) => image.elementId), + [ + largeImage.elementId, + productA.elementId, + secondImage.elementId, + productB.elementId, + ], + ); + assert.deepEqual( + result.map((image) => image.position), + [1, 2, 3, 4], + ); + }, + ); + + void nodeIt( + "gives every sub-image of a MultiImageBlockElement the owning product's CTAs", + () => { + const multiImage: MultiImageBlockElement = { + _type: 'model.dotcomrendering.pageElements.MultiImageBlockElement', + elementId: 'multi-1', + images: [largeImage, { ...largeImage, elementId: 'image-2' }], + }; + const product: ProductBlockElement = { + ...baseProduct, + content: [multiImage], + productCtas, + }; + + const result = buildLightboxImages( + format, + [buildBlock([product])], + [], + ); + + assert.equal(result.length, 2); + assert.equal( + result.every((image) => image.productCtas === productCtas), + true, + ); + }, + ); +}); diff --git a/dotcom-rendering/src/model/buildLightboxImages.test.ts b/dotcom-rendering/src/model/buildLightboxImages.test.ts deleted file mode 100644 index d114db1c2ea..00000000000 --- a/dotcom-rendering/src/model/buildLightboxImages.test.ts +++ /dev/null @@ -1,347 +0,0 @@ -import { Standard as ExampleArticle } from '../../fixtures/generated/fe-articles/Standard'; -import { images } from '../../fixtures/generated/images'; -import type { Block } from '../types/blocks'; -import type { - FEElement, - MultiImageBlockElement, - ProductBlockElement, - ProductCta, - ProductImage, -} from '../types/content'; -import { buildLightboxImages } from './buildLightboxImages'; - -const format = ExampleArticle.format; - -// Well above the 620px lightbox threshold -const largeImage = images[0]; - -const largeProductImage: ProductImage = { - url: 'https://media.guim.co.uk/large-product/900.jpg', - caption: 'A large product image', - credit: 'Photograph: Test/The Guardian', - alt: 'A large product', - displayCredit: false, - height: 900, - width: 900, -}; - -const smallProductImage: ProductImage = { - url: 'https://media.guim.co.uk/small-product/300.jpg', - caption: 'A small product image', - credit: 'Photograph: Test/The Guardian', - alt: 'A small product', - displayCredit: false, - height: 300, - width: 300, -}; - -const productCtas: ProductCta[] = [ - { - url: 'https://example.com/buy-1', - text: '', - retailer: 'Amazon', - price: '£19.99', - }, - { - url: 'https://example.com/buy-2', - text: '', - retailer: 'John Lewis', - price: '£21.00', - }, -]; - -const baseProduct: ProductBlockElement = { - _type: 'model.dotcomrendering.pageElements.ProductBlockElement', - elementId: 'product-1', - brandName: 'Acme', - starRating: '5', - productName: 'Widget', - primaryHeadingHtml: '', - secondaryHeadingHtml: '', - customAttributes: [], - content: [], - id: '123', - displayType: 'InlineOnly', - productCtas: [], -}; - -const emptyAttributes = { pinned: false, summary: false, keyEvent: false }; - -const buildBlock = (elements: FEElement[]): Block => ({ - id: 'block-1', - elements, - attributes: emptyAttributes, - primaryDateLine: '', - secondaryDateLine: '', -}); - -describe('buildLightboxImages', () => { - it("includes a product's own image when it is large enough", () => { - const product: ProductBlockElement = { - ...baseProduct, - image: largeProductImage, - }; - - const result = buildLightboxImages(format, [buildBlock([product])], []); - - expect(result).toHaveLength(1); - expect(result[0]).toMatchObject({ - masterUrl: largeProductImage.url, - elementId: product.elementId, - width: largeProductImage.width, - height: largeProductImage.height, - position: 1, - }); - }); - - it("excludes a product's own image when it is too small", () => { - const product: ProductBlockElement = { - ...baseProduct, - image: smallProductImage, - }; - - const result = buildLightboxImages(format, [buildBlock([product])], []); - - expect(result).toEqual([]); - }); - - it('excludes a product with no image', () => { - const result = buildLightboxImages( - format, - [buildBlock([baseProduct])], - [], - ); - - expect(result).toEqual([]); - }); - - it("includes images nested inside a product's content", () => { - const product: ProductBlockElement = { - ...baseProduct, - content: [largeImage], - }; - - const result = buildLightboxImages(format, [buildBlock([product])], []); - - expect(result).toHaveLength(1); - expect(result[0]?.elementId).toEqual(largeImage.elementId); - }); - - it('assigns positions in document order across regular and product images', () => { - const product: ProductBlockElement = { - ...baseProduct, - elementId: 'product-2', - image: largeProductImage, - content: [largeImage], - }; - - const result = buildLightboxImages(format, [buildBlock([product])], []); - - expect(result.map((image) => image.elementId)).toEqual([ - largeImage.elementId, - product.elementId, - ]); - expect(result.map((image) => image.position)).toEqual([1, 2]); - }); - - it("includes a product's own CTAs on its card image", () => { - const product: ProductBlockElement = { - ...baseProduct, - image: largeProductImage, - productCtas, - }; - - const result = buildLightboxImages(format, [buildBlock([product])], []); - - expect(result[0]?.productCtas).toEqual(productCtas); - }); - - it('omits productCtas entirely when a product has none', () => { - const product: ProductBlockElement = { - ...baseProduct, - image: largeProductImage, - productCtas: [], - }; - - const result = buildLightboxImages(format, [buildBlock([product])], []); - - expect(result[0]?.productCtas).toBeUndefined(); - }); - - it("includes the owning product's CTAs on an image nested inside its content", () => { - const product: ProductBlockElement = { - ...baseProduct, - content: [largeImage], - productCtas, - }; - - const result = buildLightboxImages(format, [buildBlock([product])], []); - - expect(result).toHaveLength(1); - expect(result[0]?.productCtas).toEqual(productCtas); - }); - - it("uses the innermost product's CTAs for an image nested inside a product nested in another product's content", () => { - const innerCtas: ProductCta[] = [ - { - url: 'https://example.com/inner', - text: '', - retailer: 'Inner', - price: '£5', - }, - ]; - const outerCtas: ProductCta[] = [ - { - url: 'https://example.com/outer', - text: '', - retailer: 'Outer', - price: '£50', - }, - ]; - const innerProduct: ProductBlockElement = { - ...baseProduct, - elementId: 'inner-product', - content: [largeImage], - productCtas: innerCtas, - }; - const outerProduct: ProductBlockElement = { - ...baseProduct, - elementId: 'outer-product', - content: [innerProduct], - productCtas: outerCtas, - }; - - const result = buildLightboxImages( - format, - [buildBlock([outerProduct])], - [], - ); - - expect(result).toHaveLength(1); - expect(result[0]?.productCtas).toEqual(innerCtas); - }); - - it("falls back to the product's own caption for a content image with no caption of its own", () => { - const product: ProductBlockElement = { - ...baseProduct, - image: largeProductImage, - content: [largeImage], - }; - - const result = buildLightboxImages(format, [buildBlock([product])], []); - - const contentEntry = result.find( - (image) => image.elementId === largeImage.elementId, - ); - expect(contentEntry?.caption).toEqual(largeProductImage.caption); - }); - - it("keeps a content image's own caption instead of falling back to the product's", () => { - const imageWithOwnCaption = { - ...largeImage, - data: { ...largeImage.data, caption: "The image's own caption" }, - }; - const product: ProductBlockElement = { - ...baseProduct, - image: largeProductImage, - content: [imageWithOwnCaption], - }; - - const result = buildLightboxImages(format, [buildBlock([product])], []); - - const contentEntry = result.find( - (image) => image.elementId === largeImage.elementId, - ); - expect(contentEntry?.caption).toEqual("The image's own caption"); - }); - - it("uses the innermost product's caption for an image nested inside a product nested in another product's content", () => { - const innerProductImage: ProductImage = { - ...largeProductImage, - caption: 'Inner product caption', - }; - const outerProductImage: ProductImage = { - ...largeProductImage, - caption: 'Outer product caption', - }; - const innerProduct: ProductBlockElement = { - ...baseProduct, - elementId: 'inner-product', - content: [largeImage], - image: innerProductImage, - }; - const outerProduct: ProductBlockElement = { - ...baseProduct, - elementId: 'outer-product', - content: [innerProduct], - image: outerProductImage, - }; - - const result = buildLightboxImages( - format, - [buildBlock([outerProduct])], - [], - ); - - const contentEntry = result.find( - (image) => image.elementId === largeImage.elementId, - ); - expect(contentEntry?.caption).toEqual(innerProductImage.caption); - }); - - it("keeps a product's content image and card image adjacent, rather than grouping all content images before all card images", () => { - const secondImage = images[1]; - - const productA: ProductBlockElement = { - ...baseProduct, - elementId: 'product-a', - image: largeProductImage, - content: [largeImage], - productCtas, - }; - const productB: ProductBlockElement = { - ...baseProduct, - elementId: 'product-b', - image: { - ...largeProductImage, - url: 'https://media.guim.co.uk/large-product-b/900.jpg', - }, - content: [secondImage], - productCtas, - }; - - const result = buildLightboxImages( - format, - [buildBlock([productA, productB])], - [], - ); - - expect(result.map((image) => image.elementId)).toEqual([ - largeImage.elementId, - productA.elementId, - secondImage.elementId, - productB.elementId, - ]); - expect(result.map((image) => image.position)).toEqual([1, 2, 3, 4]); - }); - - it("gives every sub-image of a MultiImageBlockElement the owning product's CTAs", () => { - const multiImage: MultiImageBlockElement = { - _type: 'model.dotcomrendering.pageElements.MultiImageBlockElement', - elementId: 'multi-1', - images: [largeImage, { ...largeImage, elementId: 'image-2' }], - }; - const product: ProductBlockElement = { - ...baseProduct, - content: [multiImage], - productCtas, - }; - - const result = buildLightboxImages(format, [buildBlock([product])], []); - - expect(result).toHaveLength(2); - expect(result.every((image) => image.productCtas === productCtas)).toBe( - true, - ); - }); -}); diff --git a/dotcom-rendering/src/model/enhance-ad-placeholders.node.test.ts b/dotcom-rendering/src/model/enhance-ad-placeholders.node.test.ts new file mode 100644 index 00000000000..5d8fd1babfe --- /dev/null +++ b/dotcom-rendering/src/model/enhance-ad-placeholders.node.test.ts @@ -0,0 +1,371 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { ArticleDesign, ArticleDisplay, Pillar } from '../lib/articleFormat'; +import type { + AdPlaceholderBlockElement, + FEElement, + ImageBlockElement, + SubheadingBlockElement, + TextBlockElement, +} from '../types/content'; +import { enhanceAdPlaceholders } from './enhance-ad-placeholders'; + +const exampleFormat = { + design: ArticleDesign.Feature, + display: ArticleDisplay.Immersive, + theme: Pillar.Culture, +}; + +const galleryFormat = { + design: ArticleDesign.Gallery, + display: ArticleDisplay.Immersive, + theme: Pillar.News, +}; + +// Test helper functions + +const getTestParagraphElements = (length: number): TextBlockElement[] => + Array(length).fill({ + _type: 'model.dotcomrendering.pageElements.TextBlockElement', + elementId: 'mockId', + html: '

I am a paragraph

', + }); + +const getTestImageBlockElements = (length: number): ImageBlockElement[] => + Array(length).fill({ + _type: 'model.dotcomrendering.pageElements.ImageBlockElement', + elementId: 'mockId', + media: { allImages: [] }, + data: {}, + displayCredit: true, + imageSources: [], + role: 'inline', + }); + +const getInlineImageElement = (): ImageBlockElement => ({ + _type: 'model.dotcomrendering.pageElements.ImageBlockElement', + media: { allImages: [] }, + data: {}, + displayCredit: true, + role: 'inline', + imageSources: [], + elementId: '12345', +}); + +const getThumbnailImageElement = (): ImageBlockElement => ({ + _type: 'model.dotcomrendering.pageElements.ImageBlockElement', + media: { allImages: [] }, + data: {}, + displayCredit: true, + role: 'thumbnail', + imageSources: [], + elementId: '12345', +}); + +const getSubheadingElement = (): SubheadingBlockElement => ({ + _type: 'model.dotcomrendering.pageElements.SubheadingBlockElement', + elementId: 'mockId', + html: "

I am a subheading.

", +}); + +const elementIsAdPlaceholder = ( + element: FEElement, +): element is AdPlaceholderBlockElement => + element._type === + 'model.dotcomrendering.pageElements.AdPlaceholderBlockElement'; + +// Tests +void nodeDescribe('enhanceAdPlaceholders', () => { + void nodeDescribe('for general articles', () => { + const testCases = [ + { paragraphs: 0, expectedPositions: [] }, + { paragraphs: 1, expectedPositions: [] }, + { paragraphs: 3, expectedPositions: [] }, + { paragraphs: 6, expectedPositions: [3] }, + { paragraphs: 9, expectedPositions: [3] }, + { paragraphs: 11, expectedPositions: [3, 10] }, + { paragraphs: 12, expectedPositions: [3, 10] }, + { + paragraphs: 16, + expectedPositions: [3, 10], + }, + { + paragraphs: 87, + expectedPositions: [ + 3, 10, 17, 24, 31, 38, 45, 52, 59, 66, 73, 80, 87, 94, + ], + }, + { + paragraphs: 88, + expectedPositions: [ + 3, 10, 17, 24, 31, 38, 45, 52, 59, 66, 73, 80, 87, 94, + ], + }, + { + paragraphs: 999, + expectedPositions: [ + 3, 10, 17, 24, 31, 38, 45, 52, 59, 66, 73, 80, 87, 94, 101, + ], + }, + ] satisfies Array<{ paragraphs: number; expectedPositions: number[] }>; + + for (const { paragraphs, expectedPositions } of testCases) { + void nodeDescribe( + `for ${paragraphs} paragraph(s) in an article`, + () => { + const elements = getTestParagraphElements(paragraphs); + const expectedPlaceholders = expectedPositions.length; + const input: FEElement[] = elements; + + const output = enhanceAdPlaceholders( + exampleFormat, + 'Apps', + false, + )(input); + const placeholderIndices = output.flatMap((el, idx) => + elementIsAdPlaceholder(el) ? [idx] : [], + ); + + void nodeIt( + `should insert ${expectedPlaceholders} ad placeholder(s)`, + () => { + assert.deepEqual( + placeholderIndices.length, + expectedPlaceholders, + ); + }, + ); + + if (expectedPlaceholders > 0) { + void nodeIt( + `should insert ad placeholder(s) in the expected position(s): ${expectedPositions.join( + ',', + )}`, + () => { + assert.deepEqual( + placeholderIndices, + expectedPositions, + ); + }, + ); + } + }, + ); + } + + void nodeIt( + 'should not insert an ad placeholder before an inline image element, but can insert it after the image', + () => { + const threeParagraphs = getTestParagraphElements(3); + + const elements = [ + ...threeParagraphs, + getInlineImageElement(), + ...threeParagraphs, + ]; + + const input: FEElement[] = elements; + + const output = enhanceAdPlaceholders( + exampleFormat, + 'Apps', + false, + )(input); + const outputPlaceholders = output.filter( + elementIsAdPlaceholder, + ); + + assert.deepEqual(outputPlaceholders.length, 1); + + const placeholderIndices = output.flatMap((el, idx) => + elementIsAdPlaceholder(el) ? [idx] : [], + ); + + // Expect one placeholder to be present after the fourth element only + assert.deepEqual(placeholderIndices, [4]); + }, + ); + + void nodeIt( + 'should not insert an ad placeholder after a thumbnail image element', + () => { + const threeParagraphs = getTestParagraphElements(3); + + const elements = [ + ...threeParagraphs, + getThumbnailImageElement(), + ...threeParagraphs, + ]; + + const input: FEElement[] = elements; + + const output = enhanceAdPlaceholders( + exampleFormat, + 'Apps', + false, + )(input); + const outputPlaceholders = output.filter( + elementIsAdPlaceholder, + ); + + assert.deepEqual(outputPlaceholders.length, 1); + + const placeholderIndices = output.flatMap((el, idx) => + elementIsAdPlaceholder(el) ? [idx] : [], + ); + + // Expect one placeholder to be present after the fifth element only + assert.deepEqual(placeholderIndices, [5]); + }, + ); + + void nodeIt( + 'should not insert an ad placeholder after an element which is not an image or text', + () => { + const threeParagraphs = getTestParagraphElements(3); + + const elements = [ + ...threeParagraphs, + getSubheadingElement(), + ...threeParagraphs, + ]; + + const input: FEElement[] = elements; + + const output = enhanceAdPlaceholders( + exampleFormat, + 'Apps', + false, + )(input); + const outputPlaceholders = output.filter( + elementIsAdPlaceholder, + ); + + assert.deepEqual(outputPlaceholders.length, 1); + + const placeholderIndices = output.flatMap((el, idx) => + elementIsAdPlaceholder(el) ? [idx] : [], + ); + + // Expect one placeholder to be present after the fifth element only + assert.deepEqual(placeholderIndices, [5]); + }, + ); + + void nodeIt( + 'should not insert ad placeholders if shouldHideAds is true', + () => { + const input: FEElement[] = getTestParagraphElements(6); + + const output = enhanceAdPlaceholders( + exampleFormat, + 'Apps', + true, + )(input); + const outputPlaceholders = output.filter( + elementIsAdPlaceholder, + ); + + assert.deepEqual(outputPlaceholders.length, 0); + }, + ); + }); + + void nodeDescribe('for gallery articles', () => { + const testCases = [ + { images: 0, expectedPositions: [] }, + { images: 1, expectedPositions: [] }, + { images: 4, expectedPositions: [4] }, + { images: 6, expectedPositions: [4] }, + { images: 9, expectedPositions: [4, 9] }, + { images: 16, expectedPositions: [4, 9, 14, 19] }, + { + images: 87, + expectedPositions: [ + 4, 9, 14, 19, 24, 29, 34, 39, 44, 49, 54, 59, 64, 69, 74, + 79, 84, 89, 94, 99, 104, + ], + }, + ] satisfies Array<{ images: number; expectedPositions: number[] }>; + + for (const { images, expectedPositions } of testCases) { + void nodeDescribe( + `for ${images} images(s) in a gallery article`, + () => { + const elements = getTestImageBlockElements(images); + const expectedPlaceholders = expectedPositions.length; + const input: FEElement[] = elements; + + const output = enhanceAdPlaceholders( + galleryFormat, + 'Apps', + false, + )(input); + const placeholderIndices = output.flatMap((el, idx) => + elementIsAdPlaceholder(el) ? [idx] : [], + ); + + void nodeIt( + `should insert ${expectedPlaceholders} ad placeholder(s)`, + () => { + assert.deepEqual( + placeholderIndices.length, + expectedPlaceholders, + ); + }, + ); + + if (expectedPlaceholders > 0) { + void nodeIt( + `should insert ad placeholder(s) in the expected position(s): ${expectedPositions.join( + ',', + )}`, + () => { + assert.deepEqual( + placeholderIndices, + expectedPositions, + ); + }, + ); + } + }, + ); + } + + void nodeIt( + 'should not insert ad placeholders if shouldHideAds is true', + () => { + const input: FEElement[] = getTestParagraphElements(6); + + const output = enhanceAdPlaceholders( + galleryFormat, + 'Apps', + true, + )(input); + const outputPlaceholders = output.filter( + elementIsAdPlaceholder, + ); + + assert.deepEqual(outputPlaceholders.length, 0); + }, + ); + + void nodeIt( + 'should still insert ad placeholders if renderingTarget is web', + () => { + const input: FEElement[] = getTestParagraphElements(6); + + const output = enhanceAdPlaceholders( + galleryFormat, + 'Web', + false, + )(input); + const outputPlaceholders = output.filter( + elementIsAdPlaceholder, + ); + + assert.ok(outputPlaceholders.length > 0); + }, + ); + }); +}); diff --git a/dotcom-rendering/src/model/enhance-ad-placeholders.test.ts b/dotcom-rendering/src/model/enhance-ad-placeholders.test.ts deleted file mode 100644 index 09dc975a89d..00000000000 --- a/dotcom-rendering/src/model/enhance-ad-placeholders.test.ts +++ /dev/null @@ -1,315 +0,0 @@ -import { ArticleDesign, ArticleDisplay, Pillar } from '../lib/articleFormat'; -import type { - AdPlaceholderBlockElement, - FEElement, - ImageBlockElement, - SubheadingBlockElement, - TextBlockElement, -} from '../types/content'; -import { enhanceAdPlaceholders } from './enhance-ad-placeholders'; - -const exampleFormat = { - design: ArticleDesign.Feature, - display: ArticleDisplay.Immersive, - theme: Pillar.Culture, -}; - -const galleryFormat = { - design: ArticleDesign.Gallery, - display: ArticleDisplay.Immersive, - theme: Pillar.News, -}; - -// Test helper functions - -const getTestParagraphElements = (length: number): TextBlockElement[] => - Array(length).fill({ - _type: 'model.dotcomrendering.pageElements.TextBlockElement', - elementId: 'mockId', - html: '

I am a paragraph

', - }); - -const getTestImageBlockElements = (length: number): ImageBlockElement[] => - Array(length).fill({ - _type: 'model.dotcomrendering.pageElements.ImageBlockElement', - elementId: 'mockId', - media: { allImages: [] }, - data: {}, - displayCredit: true, - imageSources: [], - role: 'inline', - }); - -const getInlineImageElement = (): ImageBlockElement => ({ - _type: 'model.dotcomrendering.pageElements.ImageBlockElement', - media: { allImages: [] }, - data: {}, - displayCredit: true, - role: 'inline', - imageSources: [], - elementId: '12345', -}); - -const getThumbnailImageElement = (): ImageBlockElement => ({ - _type: 'model.dotcomrendering.pageElements.ImageBlockElement', - media: { allImages: [] }, - data: {}, - displayCredit: true, - role: 'thumbnail', - imageSources: [], - elementId: '12345', -}); - -const getSubheadingElement = (): SubheadingBlockElement => ({ - _type: 'model.dotcomrendering.pageElements.SubheadingBlockElement', - elementId: 'mockId', - html: "

I am a subheading.

", -}); - -const elementIsAdPlaceholder = ( - element: FEElement, -): element is AdPlaceholderBlockElement => - element._type === - 'model.dotcomrendering.pageElements.AdPlaceholderBlockElement'; - -// Tests -describe('enhanceAdPlaceholders', () => { - describe('for general articles', () => { - const testCases = [ - { paragraphs: 0, expectedPositions: [] }, - { paragraphs: 1, expectedPositions: [] }, - { paragraphs: 3, expectedPositions: [] }, - { paragraphs: 6, expectedPositions: [3] }, - { paragraphs: 9, expectedPositions: [3] }, - { paragraphs: 11, expectedPositions: [3, 10] }, - { paragraphs: 12, expectedPositions: [3, 10] }, - { - paragraphs: 16, - expectedPositions: [3, 10], - }, - { - paragraphs: 87, - expectedPositions: [ - 3, 10, 17, 24, 31, 38, 45, 52, 59, 66, 73, 80, 87, 94, - ], - }, - { - paragraphs: 88, - expectedPositions: [ - 3, 10, 17, 24, 31, 38, 45, 52, 59, 66, 73, 80, 87, 94, - ], - }, - { - paragraphs: 999, - expectedPositions: [ - 3, 10, 17, 24, 31, 38, 45, 52, 59, 66, 73, 80, 87, 94, 101, - ], - }, - ] satisfies Array<{ paragraphs: number; expectedPositions: number[] }>; - - describe.each(testCases)( - 'for $paragraphs paragraph(s) in an article', - ({ paragraphs, expectedPositions }) => { - const elements = getTestParagraphElements(paragraphs); - const expectedPlaceholders = expectedPositions.length; - const input: FEElement[] = elements; - - const output = enhanceAdPlaceholders( - exampleFormat, - 'Apps', - false, - )(input); - const placeholderIndices = output.flatMap((el, idx) => - elementIsAdPlaceholder(el) ? [idx] : [], - ); - - it(`should insert ${expectedPlaceholders} ad placeholder(s)`, () => { - expect(placeholderIndices.length).toEqual( - expectedPlaceholders, - ); - }); - - if (expectedPlaceholders > 0) { - it(`should insert ad placeholder(s) in the expected position(s): ${expectedPositions.join( - ',', - )}`, () => { - expect(placeholderIndices).toEqual(expectedPositions); - }); - } - }, - ); - - it('should not insert an ad placeholder before an inline image element, but can insert it after the image', () => { - const threeParagraphs = getTestParagraphElements(3); - - const elements = [ - ...threeParagraphs, - getInlineImageElement(), - ...threeParagraphs, - ]; - - const input: FEElement[] = elements; - - const output = enhanceAdPlaceholders( - exampleFormat, - 'Apps', - false, - )(input); - const outputPlaceholders = output.filter(elementIsAdPlaceholder); - - expect(outputPlaceholders.length).toEqual(1); - - const placeholderIndices = output.flatMap((el, idx) => - elementIsAdPlaceholder(el) ? [idx] : [], - ); - - // Expect one placeholder to be present after the fourth element only - expect(placeholderIndices).toEqual([4]); - }); - - it('should not insert an ad placeholder after a thumbnail image element', () => { - const threeParagraphs = getTestParagraphElements(3); - - const elements = [ - ...threeParagraphs, - getThumbnailImageElement(), - ...threeParagraphs, - ]; - - const input: FEElement[] = elements; - - const output = enhanceAdPlaceholders( - exampleFormat, - 'Apps', - false, - )(input); - const outputPlaceholders = output.filter(elementIsAdPlaceholder); - - expect(outputPlaceholders.length).toEqual(1); - - const placeholderIndices = output.flatMap((el, idx) => - elementIsAdPlaceholder(el) ? [idx] : [], - ); - - // Expect one placeholder to be present after the fifth element only - expect(placeholderIndices).toEqual([5]); - }); - - it('should not insert an ad placeholder after an element which is not an image or text', () => { - const threeParagraphs = getTestParagraphElements(3); - - const elements = [ - ...threeParagraphs, - getSubheadingElement(), - ...threeParagraphs, - ]; - - const input: FEElement[] = elements; - - const output = enhanceAdPlaceholders( - exampleFormat, - 'Apps', - false, - )(input); - const outputPlaceholders = output.filter(elementIsAdPlaceholder); - - expect(outputPlaceholders.length).toEqual(1); - - const placeholderIndices = output.flatMap((el, idx) => - elementIsAdPlaceholder(el) ? [idx] : [], - ); - - // Expect one placeholder to be present after the fifth element only - expect(placeholderIndices).toEqual([5]); - }); - - it('should not insert ad placeholders if shouldHideAds is true', () => { - const input: FEElement[] = getTestParagraphElements(6); - - const output = enhanceAdPlaceholders( - exampleFormat, - 'Apps', - true, - )(input); - const outputPlaceholders = output.filter(elementIsAdPlaceholder); - - expect(outputPlaceholders.length).toEqual(0); - }); - }); - - describe('for gallery articles', () => { - const testCases = [ - { images: 0, expectedPositions: [] }, - { images: 1, expectedPositions: [] }, - { images: 4, expectedPositions: [4] }, - { images: 6, expectedPositions: [4] }, - { images: 9, expectedPositions: [4, 9] }, - { images: 16, expectedPositions: [4, 9, 14, 19] }, - { - images: 87, - expectedPositions: [ - 4, 9, 14, 19, 24, 29, 34, 39, 44, 49, 54, 59, 64, 69, 74, - 79, 84, 89, 94, 99, 104, - ], - }, - ] satisfies Array<{ images: number; expectedPositions: number[] }>; - - describe.each(testCases)( - 'for $images images(s) in a gallery article', - ({ images, expectedPositions }) => { - const elements = getTestImageBlockElements(images); - const expectedPlaceholders = expectedPositions.length; - const input: FEElement[] = elements; - - const output = enhanceAdPlaceholders( - galleryFormat, - 'Apps', - false, - )(input); - const placeholderIndices = output.flatMap((el, idx) => - elementIsAdPlaceholder(el) ? [idx] : [], - ); - - it(`should insert ${expectedPlaceholders} ad placeholder(s)`, () => { - expect(placeholderIndices.length).toEqual( - expectedPlaceholders, - ); - }); - - if (expectedPlaceholders > 0) { - it(`should insert ad placeholder(s) in the expected position(s): ${expectedPositions.join( - ',', - )}`, () => { - expect(placeholderIndices).toEqual(expectedPositions); - }); - } - }, - ); - - it('should not insert ad placeholders if shouldHideAds is true', () => { - const input: FEElement[] = getTestParagraphElements(6); - - const output = enhanceAdPlaceholders( - galleryFormat, - 'Apps', - true, - )(input); - const outputPlaceholders = output.filter(elementIsAdPlaceholder); - - expect(outputPlaceholders.length).toEqual(0); - }); - - it('should still insert ad placeholders if renderingTarget is web', () => { - const input: FEElement[] = getTestParagraphElements(6); - - const output = enhanceAdPlaceholders( - galleryFormat, - 'Web', - false, - )(input); - const outputPlaceholders = output.filter(elementIsAdPlaceholder); - - expect(outputPlaceholders.length).toBeGreaterThan(0); - }); - }); -}); diff --git a/dotcom-rendering/src/model/enhanceTimeline.test.ts b/dotcom-rendering/src/model/enhanceTimeline.node.test.ts similarity index 78% rename from dotcom-rendering/src/model/enhanceTimeline.test.ts rename to dotcom-rendering/src/model/enhanceTimeline.node.test.ts index 3db1b55acf7..070fe785034 100644 --- a/dotcom-rendering/src/model/enhanceTimeline.test.ts +++ b/dotcom-rendering/src/model/enhanceTimeline.node.test.ts @@ -1,4 +1,5 @@ import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; import { images } from '../../fixtures/generated/images'; import type { FEElement } from '../../src/types/content'; import { enhanceTimeline } from './enhanceTimeline'; @@ -140,8 +141,8 @@ const elementsWithMultipleSections: FEElement[] = [ }, ]; -describe('enhanceTimeline', () => { - it('keeps a main media with a role that is valid', () => { +void nodeDescribe('enhanceTimeline', () => { + void nodeIt('keeps a main media with a role that is valid', () => { const enhanced = enhanceTimeline(identity)(elementsWithNoSections); assert.equal( enhanced[0]?._type, @@ -150,10 +151,10 @@ describe('enhanceTimeline', () => { const timelineEvent = enhanced[0].events[0]; assert.notEqual(timelineEvent, undefined); - expect(timelineEvent?.main).toBeDefined(); + assert.notEqual(timelineEvent?.main, undefined); }); - it('drops a main media with a role that is not valid', () => { + void nodeIt('drops a main media with a role that is not valid', () => { const enhanced = enhanceTimeline(identity)(elementsWithNoSections); assert.equal( enhanced[0]?._type, @@ -162,10 +163,10 @@ describe('enhanceTimeline', () => { const timelineEvent = enhanced[0].events[1]; assert.notEqual(timelineEvent, undefined); - expect(timelineEvent?.main).toBeUndefined(); + assert.equal(timelineEvent?.main, undefined); }); - it('keeps a main media without a role', () => { + void nodeIt('keeps a main media without a role', () => { const enhanced = enhanceTimeline(identity)(elementsWithNoSections); assert.equal( enhanced[0]?._type, @@ -174,9 +175,9 @@ describe('enhanceTimeline', () => { const timelineEvent = enhanced[0].events[2]; assert.notEqual(timelineEvent, undefined); - expect(timelineEvent?.main).toBeDefined(); + assert.notEqual(timelineEvent?.main, undefined); }); - it('keeps a body element with a role that is valid', () => { + void nodeIt('keeps a body element with a role that is valid', () => { const enhanced = enhanceTimeline(identity)(elementsWithNoSections); assert.equal( enhanced[0]?._type, @@ -185,10 +186,10 @@ describe('enhanceTimeline', () => { const timelineEvent = enhanced[0].events[3]; assert.notEqual(timelineEvent, undefined); - expect(timelineEvent?.body).toEqual([images[1]]); + assert.deepEqual(timelineEvent?.body, [images[1]]); }); - it('drops a body element with a role that is not valid', () => { + void nodeIt('drops a body element with a role that is not valid', () => { const enhanced = enhanceTimeline(identity)(elementsWithNoSections); assert.equal( enhanced[0]?._type, @@ -197,10 +198,10 @@ describe('enhanceTimeline', () => { const timelineEvent = enhanced[0].events[4]; assert.notEqual(timelineEvent, undefined); - expect(timelineEvent?.body).toEqual([]); + assert.deepEqual(timelineEvent?.body, []); }); - it('keeps a body element without a role', () => { + void nodeIt('keeps a body element without a role', () => { const enhanced = enhanceTimeline(identity)(elementsWithNoSections); assert.equal( enhanced[0]?._type, @@ -209,7 +210,7 @@ describe('enhanceTimeline', () => { const timelineEvent = enhanced[0].events[5]; assert.notEqual(timelineEvent, undefined); - expect(timelineEvent?.body).toEqual([ + assert.deepEqual(timelineEvent?.body, [ { _type: 'model.dotcomrendering.pageElements.MediaAtomBlockElement', elementId: 'mock-id', @@ -219,7 +220,7 @@ describe('enhanceTimeline', () => { ]); }); - it('enhances a timeline with one section appropriately', () => { + void nodeIt('enhances a timeline with one section appropriately', () => { const enhanced = enhanceTimeline(identity)(elementsWithOneSection); assert.equal( enhanced[0]?._type, @@ -228,19 +229,22 @@ describe('enhanceTimeline', () => { const timelineSection = enhanced[0].sections[0]; assert.notEqual(timelineSection, undefined); - expect(timelineSection?.title).toEqual('Section 1'); + assert.deepEqual(timelineSection?.title, 'Section 1'); }); - it('enhances a timeline with multiple sections appropriately', () => { - const enhanced = enhanceTimeline(identity)( - elementsWithMultipleSections, - ); - assert.equal( - enhanced[0]?._type, - 'model.dotcomrendering.pageElements.DCRSectionedTimelineBlockElement', - ); - - const timelineSections = enhanced[0].sections; - expect(timelineSections).toHaveLength(2); - }); + void nodeIt( + 'enhances a timeline with multiple sections appropriately', + () => { + const enhanced = enhanceTimeline(identity)( + elementsWithMultipleSections, + ); + assert.equal( + enhanced[0]?._type, + 'model.dotcomrendering.pageElements.DCRSectionedTimelineBlockElement', + ); + + const timelineSections = enhanced[0].sections; + assert.equal(timelineSections.length, 2); + }, + ); }); diff --git a/dotcom-rendering/src/model/extractTrendingTopics.test.ts b/dotcom-rendering/src/model/extractTrendingTopics.node.test.ts similarity index 59% rename from dotcom-rendering/src/model/extractTrendingTopics.test.ts rename to dotcom-rendering/src/model/extractTrendingTopics.node.test.ts index 1dca8ddceb0..a06854b0145 100644 --- a/dotcom-rendering/src/model/extractTrendingTopics.test.ts +++ b/dotcom-rendering/src/model/extractTrendingTopics.node.test.ts @@ -1,3 +1,5 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; import type { FETagType } from '../types/tag'; import type { NarrowedFECollectionType, @@ -43,8 +45,8 @@ const tagD = tag('d'); const tagE = tag('e'); const tagF = tag('f'); -describe('extractTrendingTopics', () => { - it('returns tags in correct order with normal input', () => { +void nodeDescribe('extractTrendingTopics', () => { + void nodeIt('returns tags in correct order with normal input', () => { const collection: NarrowedFECollectionType = { curated: [ card('a', [tagA]), @@ -58,12 +60,13 @@ describe('extractTrendingTopics', () => { ], }; - expect( + assert.deepEqual( extractTrendingTopicsFomFront([collection], 'au/environment'), - ).toEqual([tagA, tagB, tagC, tagD, tagE, tagF]); + [tagA, tagB, tagC, tagD, tagE, tagF], + ); }); - it('deduplicates cards', () => { + void nodeIt('deduplicates cards', () => { const collection: NarrowedFECollectionType = { curated: [ card('a', [tagA]), @@ -82,15 +85,16 @@ describe('extractTrendingTopics', () => { curated: [card('g', [tagF])], backfill: [card('g', [tagF])], }; - expect( + assert.deepEqual( extractTrendingTopicsFomFront( [collection, secondCollection], 'au/environment', ), - ).toEqual([tagA, tagB, tagC, tagD, tagE, tagF]); + [tagA, tagB, tagC, tagD, tagE, tagF], + ); }); - it('removes cards with id matching pageId', () => { + void nodeIt('removes cards with id matching pageId', () => { const tagWithPageId = tag('au/environment'); const collection: NarrowedFECollectionType = { curated: [ @@ -104,56 +108,61 @@ describe('extractTrendingTopics', () => { card('f', [tagWithPageId, tagA, tagB, tagC, tagD, tagE, tagF]), ], }; - expect( + assert.deepEqual( extractTrendingTopicsFomFront([collection], 'au/environment'), - ).toEqual([tagA, tagB, tagC, tagD, tagE, tagF]); + [tagA, tagB, tagC, tagD, tagE, tagF], + ); }); - it('removes cards without paidContentType or tagType being Keyword or Topics', () => { - const tagWithTopicsPaidContentType = tag( - 'tagWithTopicsPaidContentType', - '', - 'Topics', - ); - const tagWithKeywordPaidContentType = tag( - 'tagWithKeywordPaidContentType', - '', - 'Keyword', - ); - const tagWithKeywordTagType = tag('tagWithKeywordTagType'); - const tagWithNoneOfTheAbove = tag( - 'tagWithNoneOfTheAbove', - 'Series', - 'Series', - ); - const collection: NarrowedFECollectionType = { - curated: [ - card('a', [tagWithNoneOfTheAbove]), - card('b', [ - tagWithNoneOfTheAbove, - tagWithTopicsPaidContentType, - ]), - ], - backfill: [ - card('c', [ - tagWithNoneOfTheAbove, - tagWithTopicsPaidContentType, - tagWithKeywordPaidContentType, - ]), - card('d', [ - tagWithNoneOfTheAbove, + void nodeIt( + 'removes cards without paidContentType or tagType being Keyword or Topics', + () => { + const tagWithTopicsPaidContentType = tag( + 'tagWithTopicsPaidContentType', + '', + 'Topics', + ); + const tagWithKeywordPaidContentType = tag( + 'tagWithKeywordPaidContentType', + '', + 'Keyword', + ); + const tagWithKeywordTagType = tag('tagWithKeywordTagType'); + const tagWithNoneOfTheAbove = tag( + 'tagWithNoneOfTheAbove', + 'Series', + 'Series', + ); + const collection: NarrowedFECollectionType = { + curated: [ + card('a', [tagWithNoneOfTheAbove]), + card('b', [ + tagWithNoneOfTheAbove, + tagWithTopicsPaidContentType, + ]), + ], + backfill: [ + card('c', [ + tagWithNoneOfTheAbove, + tagWithTopicsPaidContentType, + tagWithKeywordPaidContentType, + ]), + card('d', [ + tagWithNoneOfTheAbove, + tagWithTopicsPaidContentType, + tagWithKeywordPaidContentType, + tagWithKeywordTagType, + ]), + ], + }; + assert.deepEqual( + extractTrendingTopicsFomFront([collection], 'au/environment'), + [ tagWithTopicsPaidContentType, tagWithKeywordPaidContentType, tagWithKeywordTagType, - ]), - ], - }; - expect( - extractTrendingTopicsFomFront([collection], 'au/environment'), - ).toEqual([ - tagWithTopicsPaidContentType, - tagWithKeywordPaidContentType, - tagWithKeywordTagType, - ]); - }); + ], + ); + }, + ); }); diff --git a/dotcom-rendering/src/model/groupTrailsByDates.node.test.ts b/dotcom-rendering/src/model/groupTrailsByDates.node.test.ts new file mode 100644 index 00000000000..d88c14d2c3d --- /dev/null +++ b/dotcom-rendering/src/model/groupTrailsByDates.node.test.ts @@ -0,0 +1,139 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { trails } from '../../fixtures/manual/trails'; +import type { DCRFrontCard } from '../types/front'; +import { groupTrailsByDates } from './groupTrailsByDates'; + +const datesToTrails = (dates: Date[]): DCRFrontCard[] => { + return dates.map((date) => ({ + ...trails[0], + webPublicationDate: date.toISOString(), + })); +}; + +void nodeDescribe('groupTrailsByDates', () => { + void nodeIt( + 'Will split trails into days & months depending on the frequency', + () => { + const dates = [ + // SHOULD BE GROUPED BY DAY + // 3 on the 23rd of June + new Date(2023, 5, 23, 12), + new Date(2023, 5, 23, 12), + new Date(2023, 5, 23, 12), + // 5 on the 25th of June + new Date(2023, 5, 25, 12), + new Date(2023, 5, 25, 12), + new Date(2023, 5, 25, 12), + new Date(2023, 5, 25, 12), + new Date(2023, 5, 25, 12), + // 7 on the 26th of June + new Date(2023, 5, 26, 12), + new Date(2023, 5, 26, 12), + new Date(2023, 5, 26, 12), + new Date(2023, 5, 26, 12), + new Date(2023, 5, 26, 12), + new Date(2023, 5, 26, 12), + + // SHOULD BE GROUPED BY MONTH + // 1 on the 2nd of May + new Date(2023, 4, 2, 12), + // 3 on 3rd of May + new Date(2023, 4, 3, 12), + new Date(2023, 4, 3, 12), + // 1 on 4th of May + new Date(2023, 4, 4, 12), + // 1 on 5th of May + new Date(2023, 4, 5, 12), + ]; + + const result = groupTrailsByDates(datesToTrails(dates), 'UK'); + + assert.deepEqual(result[0]?.day, '26'); + assert.deepEqual(result[1]?.day, '25'); + assert.deepEqual(result[2]?.day, '23'); + + assert.deepEqual(result[3]?.day, undefined); + assert.deepEqual(result[3]?.month, 'May'); + }, + ); + + void nodeIt('Will handle all editions', () => { + const dates = [ + // The whole of the last day of June (months are 0-indexed) + '2024-06-30T00:00:00Z', + '2024-06-30T01:00:00Z', + '2024-06-30T02:00:00Z', + '2024-06-30T03:00:00Z', + '2024-06-30T04:00:00Z', + '2024-06-30T05:00:00Z', + '2024-06-30T06:00:00Z', + '2024-06-30T07:00:00Z', + '2024-06-30T08:00:00Z', + '2024-06-30T09:00:00Z', + '2024-06-30T10:00:00Z', + '2024-06-30T11:00:00Z', + '2024-06-30T12:00:00Z', + '2024-06-30T13:00:00Z', + '2024-06-30T14:00:00Z', + '2024-06-30T15:00:00Z', + '2024-06-30T16:00:00Z', + '2024-06-30T17:00:00Z', + '2024-06-30T18:00:00Z', + '2024-06-30T19:00:00Z', + '2024-06-30T20:00:00Z', + '2024-06-30T21:00:00Z', + '2024-06-30T22:00:00Z', + '2024-06-30T23:00:00Z', + ].map((date) => new Date(date)); + + const uk = groupTrailsByDates(datesToTrails(dates), 'UK'); + + assert.deepEqual(uk[1]?.day, '30'); + assert.deepEqual(uk[1]?.month, 'June'); + assert.equal(uk[1]?.trails.length, 23); + assert.equal(uk[0]?.day, undefined); + assert.deepEqual(uk[0]?.month, 'July'); + assert.equal(uk[0]?.trails.length, 1); + + const au = groupTrailsByDates(datesToTrails(dates), 'AU'); + + assert.deepEqual(au[1]?.day, '30'); + assert.deepEqual(au[1]?.month, 'June'); + assert.equal(au[1]?.trails.length, 14); + assert.deepEqual(au[0]?.day, '1'); + assert.deepEqual(au[0]?.month, 'July'); + assert.equal(au[0]?.trails.length, 10); + + const us = groupTrailsByDates(datesToTrails(dates), 'US'); + + assert.deepEqual(us[1]?.day, '29'); + assert.deepEqual(us[1]?.month, 'June'); + assert.equal(us[1]?.trails.length, 4); + assert.deepEqual(us[0]?.day, '30'); + assert.deepEqual(us[0]?.month, 'June'); + assert.equal(us[0]?.trails.length, 20); + }); + + void nodeIt('Will respect "forceDay" being set to true', () => { + const dates = [ + // This would be grouped by month if left to the pop out frequency + // 1 on the 2nd of May + new Date(2023, 4, 2, 12), + // 3 on 3rd of May + new Date(2023, 4, 3, 12), + new Date(2023, 4, 3, 12), + // 1 on 4th of May + new Date(2023, 4, 4, 12), + // 1 on 5th of May + new Date(2023, 4, 5, 12), + ]; + + const result = groupTrailsByDates(datesToTrails(dates), 'UK', true); + + assert.deepEqual(result[0]?.day, '5'); + assert.deepEqual(result[1]?.day, '4'); + assert.deepEqual(result[2]?.day, '3'); + assert.deepEqual(result[3]?.day, '2'); + }); +}); diff --git a/dotcom-rendering/src/model/groupTrailsByDates.test.ts b/dotcom-rendering/src/model/groupTrailsByDates.test.ts index b31cf6efe71..eb175ee12c0 100644 --- a/dotcom-rendering/src/model/groupTrailsByDates.test.ts +++ b/dotcom-rendering/src/model/groupTrailsByDates.test.ts @@ -1,3 +1,5 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; import { trails } from '../../fixtures/manual/trails'; import type { DCRFrontCard } from '../types/front'; import { groupTrailsByDates } from './groupTrailsByDates'; diff --git a/dotcom-rendering/src/model/unwrapHtml.node.test.ts b/dotcom-rendering/src/model/unwrapHtml.node.test.ts new file mode 100644 index 00000000000..3f0d4fcf2d7 --- /dev/null +++ b/dotcom-rendering/src/model/unwrapHtml.node.test.ts @@ -0,0 +1,145 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { unwrapHtml } from './unwrapHtml'; + +type Params = Parameters[0]; + +void nodeDescribe('unwrapHtml', () => { + void nodeIt('Returns unwrapped HTML if prefix and suffix match', () => { + // Blockquote, elements inside + const bqUnwrap: Params = { + html: '

inner

', + fixes: [ + { + prefix: '
', + suffix: '
', + }, + ], + }; + + const { willUnwrap: bqIsUnwrapped, unwrappedHtml: bqUnwrappedHtml } = + unwrapHtml(bqUnwrap); + + // Paragraph, no elements inside + const pUnwrap: Params = { + html: '

inner

', + fixes: [ + { + prefix: '

', + suffix: '

', + }, + ], + }; + const { willUnwrap: pIsUnwrapped, unwrappedHtml: pUnwrappedHtml } = + unwrapHtml(pUnwrap); + + // Testy test + assert.ok(bqIsUnwrapped); + assert.equal(bqUnwrappedHtml, '

inner

'); + assert.ok(pIsUnwrapped); + assert.equal(pUnwrappedHtml, 'inner'); + }); + + void nodeIt( + 'Returns non-unwrapped HTML if prefix and suffix do not match', + () => { + const bqUnwrap: Params = { + html: '

inner

', + fixes: [ + { + prefix: '
', + suffix: '
', + }, + ], + }; + const { willUnwrap: isUnwrapped, unwrappedHtml } = + unwrapHtml(bqUnwrap); + + assert.ok(!isUnwrapped); + assert.equal(unwrappedHtml, bqUnwrap.html); + }, + ); + + void nodeIt( + 'Returns wrapped HTML if prefix and suffix of one "fix" match from multiple options', + () => { + const bqUnwrap: Params = { + html: '

inner

', + fixes: [ + { + prefix: '
', + suffix: '
', + unwrappedElement: 'blockquote', + }, + { + prefix: '

', + suffix: '

', + unwrappedElement: 'p', + }, + ], + }; + + const { + willUnwrap: bqIsUnwrapped, + unwrappedHtml: bqUnwrappedHtml, + unwrappedElement: bqUnwrappedElement, + } = unwrapHtml(bqUnwrap); + + const pUnwrap: Params = { + html: '

inner

', + fixes: [ + { + prefix: '

', + suffix: '

', + unwrappedElement: 'p', + }, + { + prefix: '
    ', + suffix: '
', + unwrappedElement: 'ul', + }, + ], + }; + const { + willUnwrap: pIsUnwrapped, + unwrappedHtml: pUnwrappedHtml, + unwrappedElement: pUnwrappedElement, + } = unwrapHtml(pUnwrap); + + const ulUnwrap: Params = { + html: '
  • Test
  • test2
', + fixes: [ + { + prefix: '

', + suffix: '

', + unwrappedElement: 'p', + }, + { + prefix: '
    ', + suffix: '
', + unwrappedElement: 'ul', + }, + ], + }; + + // Unwrap Unordered lists + const { + willUnwrap: ulIsUnwrapped, + unwrappedHtml: ulUnwrappedHtml, + unwrappedElement: ulUnwrappedElement, + } = unwrapHtml(ulUnwrap); + + assert.ok(bqIsUnwrapped); + assert.equal(bqUnwrappedHtml, '

inner

'); + assert.equal(bqUnwrappedElement, 'blockquote'); + + assert.ok(pIsUnwrapped); + assert.equal(pUnwrappedHtml, 'inner'); + assert.equal(pUnwrappedElement, 'p'); + + assert.ok(ulIsUnwrapped); + assert.equal(ulUnwrappedHtml, '
  • Test
  • test2
  • '); + assert.equal(ulUnwrappedElement, 'ul'); + }, + ); +}); diff --git a/dotcom-rendering/src/model/unwrapHtml.test.ts b/dotcom-rendering/src/model/unwrapHtml.test.ts deleted file mode 100644 index 6ff786cb5f6..00000000000 --- a/dotcom-rendering/src/model/unwrapHtml.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { unwrapHtml } from './unwrapHtml'; - -type Params = Parameters[0]; - -describe('unwrapHtml', () => { - it('Returns unwrapped HTML if prefix and suffix match', () => { - // Blockquote, elements inside - const bqUnwrap: Params = { - html: '

    inner

    ', - fixes: [ - { - prefix: '
    ', - suffix: '
    ', - }, - ], - }; - - const { willUnwrap: bqIsUnwrapped, unwrappedHtml: bqUnwrappedHtml } = - unwrapHtml(bqUnwrap); - - // Paragraph, no elements inside - const pUnwrap: Params = { - html: '

    inner

    ', - fixes: [ - { - prefix: '

    ', - suffix: '

    ', - }, - ], - }; - const { willUnwrap: pIsUnwrapped, unwrappedHtml: pUnwrappedHtml } = - unwrapHtml(pUnwrap); - - // Testy test - expect(bqIsUnwrapped).toBeTruthy(); - expect(bqUnwrappedHtml).toBe('

    inner

    '); - expect(pIsUnwrapped).toBeTruthy(); - expect(pUnwrappedHtml).toBe('inner'); - }); - - it('Returns non-unwrapped HTML if prefix and suffix do not match', () => { - const bqUnwrap: Params = { - html: '

    inner

    ', - fixes: [ - { - prefix: '
    ', - suffix: '
    ', - }, - ], - }; - const { willUnwrap: isUnwrapped, unwrappedHtml } = unwrapHtml(bqUnwrap); - - expect(isUnwrapped).toBeFalsy(); - expect(unwrappedHtml).toBe(bqUnwrap.html); - }); - - it('Returns wrapped HTML if prefix and suffix of one "fix" match from multiple options', () => { - const bqUnwrap: Params = { - html: '

    inner

    ', - fixes: [ - { - prefix: '
    ', - suffix: '
    ', - unwrappedElement: 'blockquote', - }, - { - prefix: '

    ', - suffix: '

    ', - unwrappedElement: 'p', - }, - ], - }; - - const { - willUnwrap: bqIsUnwrapped, - unwrappedHtml: bqUnwrappedHtml, - unwrappedElement: bqUnwrappedElement, - } = unwrapHtml(bqUnwrap); - - const pUnwrap: Params = { - html: '

    inner

    ', - fixes: [ - { - prefix: '

    ', - suffix: '

    ', - unwrappedElement: 'p', - }, - { - prefix: '
      ', - suffix: '
    ', - unwrappedElement: 'ul', - }, - ], - }; - const { - willUnwrap: pIsUnwrapped, - unwrappedHtml: pUnwrappedHtml, - unwrappedElement: pUnwrappedElement, - } = unwrapHtml(pUnwrap); - - const ulUnwrap: Params = { - html: '
    • Test
    • test2
    ', - fixes: [ - { - prefix: '

    ', - suffix: '

    ', - unwrappedElement: 'p', - }, - { - prefix: '
      ', - suffix: '
    ', - unwrappedElement: 'ul', - }, - ], - }; - - // Unwrap Unordered lists - const { - willUnwrap: ulIsUnwrapped, - unwrappedHtml: ulUnwrappedHtml, - unwrappedElement: ulUnwrappedElement, - } = unwrapHtml(ulUnwrap); - - expect(bqIsUnwrapped).toBeTruthy(); - expect(bqUnwrappedHtml).toBe('

    inner

    '); - expect(bqUnwrappedElement).toBe('blockquote'); - - expect(pIsUnwrapped).toBeTruthy(); - expect(pUnwrappedHtml).toBe('inner'); - expect(pUnwrappedElement).toBe('p'); - - expect(ulIsUnwrapped).toBeTruthy(); - expect(ulUnwrappedHtml).toBe('
  • Test
  • test2
  • '); - expect(ulUnwrappedElement).toBe('ul'); - }); -}); diff --git a/dotcom-rendering/src/model/validate.node.test.ts b/dotcom-rendering/src/model/validate.node.test.ts new file mode 100644 index 00000000000..71c9b2a0e44 --- /dev/null +++ b/dotcom-rendering/src/model/validate.node.test.ts @@ -0,0 +1,79 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { Comment } from '../../fixtures/generated/fe-articles/Comment'; +import { Feature } from '../../fixtures/generated/fe-articles/Feature'; +import { Live } from '../../fixtures/generated/fe-articles/Live'; +import { MatchReport } from '../../fixtures/generated/fe-articles/MatchReport'; +import { Review } from '../../fixtures/generated/fe-articles/Review'; +import { Standard } from '../../fixtures/generated/fe-articles/Standard'; +import { hostedArticle } from '../../fixtures/manual/hostedArticle'; +import { hostedGallery } from '../../fixtures/manual/hostedGallery'; +import { hostedVideo } from '../../fixtures/manual/hostedVideo'; +import { validateAsFEArticle } from './validate'; + +const articles = [ + { + name: 'Standard', + data: Standard, + }, + { + name: 'Feature', + data: Feature, + }, + { + name: 'Comment', + data: Comment, + }, + { + name: 'Match Report', + data: MatchReport, + }, + { + name: 'Review', + data: Review, + }, + { + name: 'Liveblog', + data: Live, + }, +] as const; + +const hostedContentArticles = [ + { + name: 'Hosted Article', + data: hostedArticle, + }, + { + name: 'Hosted Gallery', + data: hostedGallery, + }, + { + name: 'Hosted Video', + data: hostedVideo, + }, +]; + +void nodeDescribe('validate', () => { + void nodeIt('throws on invalid data', () => { + const data = { foo: 'bar' }; + assert.throws(() => validateAsFEArticle(data), TypeError); + }); + + for (const article of articles) { + void nodeIt(`validates data for a ${article.name} article`, () => { + assert.equal(validateAsFEArticle(article.data), article.data); + }); + } + + for (const hostedItem of hostedContentArticles) { + void nodeIt( + `validates data for hosted ${hostedItem.name} content`, + () => { + assert.equal( + validateAsFEArticle(hostedItem.data), + hostedItem.data, + ); + }, + ); + } +}); diff --git a/dotcom-rendering/src/model/validate.puzzlesPage.node.test.ts b/dotcom-rendering/src/model/validate.puzzlesPage.node.test.ts new file mode 100644 index 00000000000..d1315ea3dc2 --- /dev/null +++ b/dotcom-rendering/src/model/validate.puzzlesPage.node.test.ts @@ -0,0 +1,198 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { validateAsPuzzlesPageType } from './validate'; + +const validPage = () => ({ + id: 'puzzles', + webTitle: 'Puzzles and games', + editionId: 'UK', + canonicalUrl: 'https://www.theguardian.com/puzzles-and-games', + isAdFreeUser: false, + config: { serverSideABTests: { 'puzzles-new-hub': 'variant' } }, + nav: {}, + pageFooter: {}, + layout: { + containers: [ + { + id: 'word-games', + title: 'Word games', + variant: 'standard', + content: { + nestedContainers: [], + items: [ + [ + { + id: 'word-wheel', + title: 'Word wheel', + type: 'word-game', + set: 'all', + cardVariant: 'primary', + cadence: 'Daily', + slug: 'word-wheel', + variant: 'iframe-page', + }, + ], + ], + }, + }, + ], + }, +}); + +void nodeDescribe('validateAsPuzzlesPageType', () => { + void nodeIt('accepts a valid recursive blueprint contract', () => { + assert.equal( + validateAsPuzzlesPageType(validPage()).layout.containers[0]?.id, + 'word-games', + ); + }); + + void nodeIt( + 'accepts enabled on featured containers and rejects it elsewhere', + () => { + const featuredPage = validPage(); + featuredPage.layout.containers[0]!.variant = 'featured'; + ( + featuredPage.layout.containers[0] as { enabled?: boolean } + ).enabled = true; + assert.notEqual(validateAsPuzzlesPageType(featuredPage), undefined); + + featuredPage.layout.containers[0]!.variant = 'standard'; + assert.throws(() => validateAsPuzzlesPageType(featuredPage)); + }, + ); + + for (const [name, mutate] of [ + [ + 'unknown card variant', + (page: ReturnType) => { + page.layout.containers[0]!.content.items[0]![0]!.cardVariant = + 'hero'; + }, + ], + [ + 'missing cadence', + (page: ReturnType) => { + const card = page.layout.containers[0]!.content + .items[0]![0]! as { + cadence?: string; + }; + delete card.cadence; + }, + ], + [ + 'invalid colour', + (page: ReturnType) => { + const card = page.layout.containers[0]!.content + .items[0]![0]! as { + backgroundColour?: string; + }; + card.backgroundColour = 'red'; + }, + ], + [ + 'unsupported span', + (page: ReturnType) => { + const container = page.layout.containers[0]! as { + desktopSpan?: number; + }; + container.desktopSpan = 13; + }, + ], + [ + 'duplicate stable ID', + (page: ReturnType) => { + page.layout.containers[0]!.content.items[0]!.push({ + ...page.layout.containers[0]!.content.items[0]![0]!, + }); + }, + ], + ] as const) { + void nodeIt(`rejects ${name}`, () => { + const page = validPage(); + mutate(page); + assert.throws(() => validateAsPuzzlesPageType(page), { + message: 'Unable to validate request body for puzzles page.', + }); + }); + } + + void nodeIt( + 'accepts supporting content with valid puzzle references', + () => { + const page = validPage(); + page.layout.containers.push({ + id: 'supporting', + title: '', + variant: 'supporting', + adSlot: 'mostpop', + content: { items: [], nestedContainers: [] }, + supporting: { + usefulLinksTitle: 'Useful links', + usefulLinks: [ + { + title: 'Archive', + url: '/puzzles-and-games/word-wheel/archive', + }, + ], + popularTitle: 'Most popular puzzles', + popularGroups: [ + { title: 'Most played', itemIds: ['word-wheel'] }, + ], + }, + } as never); + + assert.equal( + validateAsPuzzlesPageType(page).layout.containers.length, + 2, + ); + }, + ); + + void nodeIt( + 'rejects supporting content which references an unknown puzzle', + () => { + const page = validPage(); + page.layout.containers.push({ + id: 'supporting', + title: '', + variant: 'supporting', + content: { items: [], nestedContainers: [] }, + supporting: { + usefulLinksTitle: 'Useful links', + usefulLinks: [], + popularTitle: 'Most popular puzzles', + popularGroups: [ + { title: 'Most played', itemIds: ['missing'] }, + ], + }, + } as never); + + assert.throws(() => validateAsPuzzlesPageType(page)); + }, + ); + + void nodeIt( + 'accepts a valid top-level ad placement and rejects one nested inside content', + () => { + const page = validPage(); + const ad = { + id: 'inline-ad', + title: '', + variant: 'ad', + adSlot: 'inline1', + content: { items: [], nestedContainers: [] }, + }; + page.layout.containers.push(ad as never); + assert.equal( + validateAsPuzzlesPageType(page).layout.containers.length, + 2, + ); + page.layout.containers.pop(); + page.layout.containers[0]!.content.nestedContainers.push( + ad as never, + ); + assert.throws(() => validateAsPuzzlesPageType(page)); + }, + ); +}); diff --git a/dotcom-rendering/src/model/validate.puzzlesPage.test.ts b/dotcom-rendering/src/model/validate.puzzlesPage.test.ts index d28f902092b..76ac1ce082e 100644 --- a/dotcom-rendering/src/model/validate.puzzlesPage.test.ts +++ b/dotcom-rendering/src/model/validate.puzzlesPage.test.ts @@ -1,3 +1,5 @@ +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; import { validateAsPuzzlesPageType } from './validate'; const validPage = () => ({ @@ -55,7 +57,7 @@ describe('validateAsPuzzlesPageType', () => { expect(() => validateAsPuzzlesPageType(featuredPage)).toThrow(); }); - it.each([ + for (const [name, mutate] of [ [ 'unknown card variant', (page: ReturnType) => { @@ -100,13 +102,15 @@ describe('validateAsPuzzlesPageType', () => { }); }, ], - ])('rejects %s', (_, mutate) => { - const page = validPage(); - mutate(page); - expect(() => validateAsPuzzlesPageType(page)).toThrow( - 'Unable to validate request body for puzzles page', - ); - }); + ] as const) { + void nodeIt(`rejects ${name}`, () => { + const page = validPage(); + mutate(page); + expect(() => validateAsPuzzlesPageType(page)).toThrow( + 'Unable to validate request body for puzzles page', + ); + }); + } it('accepts supporting content with valid puzzle references', () => { const page = validPage(); diff --git a/dotcom-rendering/src/model/validate.test.ts b/dotcom-rendering/src/model/validate.test.ts index 5a969f89503..491be8c28f6 100644 --- a/dotcom-rendering/src/model/validate.test.ts +++ b/dotcom-rendering/src/model/validate.test.ts @@ -1,7 +1,5 @@ -/** - * @jest-environment node - */ - +import assert from 'node:assert/strict'; +import { describe as nodeDescribe, it as nodeIt } from 'node:test'; import { Comment } from '../../fixtures/generated/fe-articles/Comment'; import { Feature } from '../../fixtures/generated/fe-articles/Feature'; import { Live } from '../../fixtures/generated/fe-articles/Live'; From e30e4b6de054e9199776a4a71d1720b2b2fd0b9c Mon Sep 17 00:00:00 2001 From: Ravi <7014230+arelra@users.noreply.github.com> Date: Mon, 14 Sep 2026 09:38:21 +0100 Subject: [PATCH 4/8] Remove left over migrated tests --- dotcom-rendering/src/lib/branding.test.ts | 684 ------------------ .../src/lib/getFrontsAdPositions.test.ts | 598 --------------- dotcom-rendering/src/lib/video.test.ts | 439 ----------- .../src/model/groupTrailsByDates.test.ts | 136 ---- .../src/model/validate.puzzlesPage.test.ts | 178 ----- dotcom-rendering/src/model/validate.test.ts | 73 -- 6 files changed, 2108 deletions(-) delete mode 100644 dotcom-rendering/src/lib/branding.test.ts delete mode 100644 dotcom-rendering/src/lib/getFrontsAdPositions.test.ts delete mode 100644 dotcom-rendering/src/lib/video.test.ts delete mode 100644 dotcom-rendering/src/model/groupTrailsByDates.test.ts delete mode 100644 dotcom-rendering/src/model/validate.puzzlesPage.test.ts delete mode 100644 dotcom-rendering/src/model/validate.test.ts diff --git a/dotcom-rendering/src/lib/branding.test.ts b/dotcom-rendering/src/lib/branding.test.ts deleted file mode 100644 index eaf49d3f273..00000000000 --- a/dotcom-rendering/src/lib/branding.test.ts +++ /dev/null @@ -1,684 +0,0 @@ -import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; -import type { Branding } from '../types/branding'; -import { decideCollectionBranding, decideTagPageBranding } from './branding'; - -// For the purpose of these tests we don't care about the contents of the logo objects -const logo = {} as Branding['logo']; - -const assertMatchObject = (actual: unknown, expected: unknown): void => { - if (expected === null || typeof expected !== 'object') { - assert.deepEqual(actual, expected); - return; - } - - assert.ok(actual !== null && typeof actual === 'object'); - for (const [key, value] of Object.entries(expected)) { - assertMatchObject((actual as Record)[key], value); - } -}; - -describe('decideCollectionBranding', () => { - it('picks branding from a card by their edition', () => { - const cards = [ - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' as const }, - branding: { - brandingType: { name: 'paid-content' as const }, - sponsorName: 'foo', - aboutThisLink: '', - logo, - }, - }, - { - edition: { id: 'US' as const }, - branding: { - brandingType: { name: 'sponsored' as const }, - sponsorName: 'bar', - aboutThisLink: '', - logo, - }, - }, - ], - }, - }, - ]; - const ukBranding = decideCollectionBranding({ - frontBranding: undefined, - couldDisplayFrontBranding: false, - cards, - editionId: 'UK', - isContainerBranding: false, - }); - expect(ukBranding).toMatchObject({ - kind: 'paid-content', - isFrontBranding: false, - branding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'foo', - aboutThisLink: '', - logo, - }, - isContainerBranding: false, - hasMultipleBranding: false, - }); - const usBranding = decideCollectionBranding({ - frontBranding: undefined, - couldDisplayFrontBranding: false, - cards, - editionId: 'US', - isContainerBranding: false, - }); - expect(usBranding).toMatchObject({ - kind: 'sponsored', - isFrontBranding: false, - branding: { - brandingType: { name: 'sponsored' }, - sponsorName: 'bar', - aboutThisLink: '', - logo, - }, - isContainerBranding: false, - hasMultipleBranding: false, - }); - }); - - it('is paid content derived from multiple cards', () => { - const cardBranding = { - brandingType: { name: 'paid-content' as const }, - sponsorName: 'foo', - aboutThisLink: '', - logo, - }; - const collectionBranding = decideCollectionBranding({ - frontBranding: undefined, - couldDisplayFrontBranding: false, - cards: [ - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: cardBranding, - }, - ], - }, - }, - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: cardBranding, - }, - ], - }, - }, - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: cardBranding, - }, - ], - }, - }, - ], - editionId: 'UK', - isContainerBranding: false, - }); - expect(collectionBranding).toMatchObject({ - kind: 'paid-content', - isFrontBranding: false, - branding: cardBranding, - }); - }); - - it('undefined when not all cards have branding', () => { - // The branding we'll apply to each card in this test - const collectionBranding = decideCollectionBranding({ - frontBranding: undefined, - couldDisplayFrontBranding: false, - cards: [ - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'foo', - aboutThisLink: '', - logo, - }, - }, - ], - }, - }, - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'bar', - aboutThisLink: '', - logo, - }, - }, - ], - }, - }, - { - properties: { - editionBrandings: [], - }, - }, - ], - editionId: 'UK', - isContainerBranding: false, - }); - expect(collectionBranding).toBeUndefined(); - }); - - it('is undefined when no cards have branding', () => { - const collectionBranding = decideCollectionBranding({ - frontBranding: undefined, - couldDisplayFrontBranding: false, - cards: [ - { - properties: { - editionBrandings: [], - }, - }, - { - properties: { - editionBrandings: [], - }, - }, - { - properties: { - editionBrandings: [], - }, - }, - ], - editionId: 'UK', - isContainerBranding: false, - }); - expect(collectionBranding).toBeUndefined(); - }); - - it('is undefined when cards have different branding types', () => { - const collectionBranding = decideCollectionBranding({ - frontBranding: undefined, - couldDisplayFrontBranding: false, - cards: [ - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'foo', - aboutThisLink: '', - logo, - }, - }, - ], - }, - }, - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: { - brandingType: { name: 'sponsored' }, - sponsorName: 'bar', - aboutThisLink: '', - logo, - }, - }, - ], - }, - }, - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: { - brandingType: { name: 'foundation' }, - sponsorName: 'baz', - aboutThisLink: '', - logo, - }, - }, - ], - }, - }, - ], - editionId: 'UK', - isContainerBranding: false, - }); - expect(collectionBranding).toBeUndefined(); - }); - - it('is sponsored branding when all of the branding types are sponsored and the names match', () => { - const cardBranding = { - brandingType: { name: 'sponsored' as const }, - sponsorName: 'foo', - aboutThisLink: '', - logo, - }; - const collectionBranding = decideCollectionBranding({ - frontBranding: undefined, - couldDisplayFrontBranding: false, - cards: [ - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: cardBranding, - }, - ], - }, - }, - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: cardBranding, - }, - ], - }, - }, - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: cardBranding, - }, - ], - }, - }, - ], - editionId: 'UK', - isContainerBranding: false, - }); - expect(collectionBranding).toStrictEqual({ - kind: 'sponsored', - isFrontBranding: false, - branding: cardBranding, - isContainerBranding: false, - hasMultipleBranding: false, - }); - }); - - it('is undefined when branding cards are sponsored and have different sponsor names', () => { - // The branding we'll apply to each card in this test - const collectionBranding = decideCollectionBranding({ - frontBranding: undefined, - couldDisplayFrontBranding: false, - cards: [ - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: { - brandingType: { name: 'sponsored' }, - sponsorName: 'foo', - aboutThisLink: '', - logo, - }, - }, - ], - }, - }, - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: { - brandingType: { name: 'sponsored' }, - sponsorName: 'bar', - aboutThisLink: '', - logo, - }, - }, - ], - }, - }, - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: { - brandingType: { name: 'sponsored' }, - sponsorName: 'baz', - aboutThisLink: '', - logo, - }, - }, - ], - }, - }, - ], - editionId: 'UK', - isContainerBranding: false, - }); - expect(collectionBranding).toBeUndefined(); - }); - - it('is paid content branding when all of the branding types are paid-content and the names match', () => { - const collectionBranding = decideCollectionBranding({ - frontBranding: undefined, - couldDisplayFrontBranding: false, - cards: [ - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'foo', - aboutThisLink: '', - logo, - }, - }, - ], - }, - }, - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'foo', - aboutThisLink: '', - logo, - }, - }, - ], - }, - }, - ], - editionId: 'UK', - isContainerBranding: false, - }); - expect(collectionBranding).toStrictEqual({ - kind: 'paid-content', - isFrontBranding: false, - branding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'foo', - aboutThisLink: '', - logo, - }, - isContainerBranding: false, - hasMultipleBranding: false, - }); - }); - - it('is paid content multiple branding when branding cards are paid-content and have different sponsor names', () => { - const collectionBranding = decideCollectionBranding({ - frontBranding: undefined, - couldDisplayFrontBranding: false, - cards: [ - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'foo', - aboutThisLink: '', - logo, - }, - }, - ], - }, - }, - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'bar', - aboutThisLink: '', - logo, - }, - }, - ], - }, - }, - ], - editionId: 'UK', - isContainerBranding: false, - }); - expect(collectionBranding).toStrictEqual({ - kind: 'paid-content', - isFrontBranding: false, - branding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'foo', - aboutThisLink: '', - logo, - }, - isContainerBranding: false, - hasMultipleBranding: true, - }); - }); - - it('is front branding when present and possible to display', () => { - const collectionBranding = decideCollectionBranding({ - frontBranding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'bar', - aboutThisLink: '', - logo, - }, - couldDisplayFrontBranding: true, - cards: [], - editionId: 'UK', - isContainerBranding: false, - }); - expect(collectionBranding).toStrictEqual({ - kind: 'paid-content', - isFrontBranding: true, - branding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'bar', - aboutThisLink: '', - logo, - }, - isContainerBranding: false, - hasMultipleBranding: false, - }); - }); - - it('is undefined when there is front branding (and no card branding) that is not eligible for display on this collection', () => { - const collectionBranding = decideCollectionBranding({ - frontBranding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'bar', - aboutThisLink: '', - logo, - }, - couldDisplayFrontBranding: false, - cards: [], - editionId: 'UK', - isContainerBranding: false, - }); - expect(collectionBranding).toBeUndefined(); - }); - - it('when cards are present', () => { - const cardBranding = { - brandingType: { name: 'paid-content' as const }, - sponsorName: 'foo', - aboutThisLink: '', - logo, - }; - const collectionBranding = decideCollectionBranding({ - frontBranding: { - brandingType: { name: 'sponsored' }, - sponsorName: 'bar', - aboutThisLink: '', - logo, - }, - couldDisplayFrontBranding: false, - cards: [ - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: cardBranding, - }, - ], - }, - }, - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: cardBranding, - }, - ], - }, - }, - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: cardBranding, - }, - ], - }, - }, - ], - editionId: 'UK', - isContainerBranding: false, - }); - expect(collectionBranding).toStrictEqual({ - kind: 'paid-content', - isFrontBranding: false, - branding: cardBranding, - isContainerBranding: false, - hasMultipleBranding: false, - }); - }); - - it('is undefined when front branding matches card branding, but we are not displaying front branding', () => { - const cardBranding = { - brandingType: { name: 'paid-content' as const }, - sponsorName: 'foo', - aboutThisLink: '', - logo, - }; - const collectionBranding = decideCollectionBranding({ - frontBranding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'foo', - aboutThisLink: '', - logo, - }, - couldDisplayFrontBranding: false, - cards: [ - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: cardBranding, - }, - ], - }, - }, - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: cardBranding, - }, - ], - }, - }, - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: cardBranding, - }, - ], - }, - }, - ], - editionId: 'UK', - isContainerBranding: false, - }); - expect(collectionBranding).toBeUndefined(); - }); -}); - -describe('decideTagPageBranding', () => { - it('picks branding from a tag page by their edition', () => { - const branding = { - brandingType: { name: 'sponsored' }, - sponsorName: 'Guardian.org', - aboutThisLink: '', - logo, - } satisfies Branding; - - const tagPageBranding = decideTagPageBranding({ - branding, - }); - - expect(tagPageBranding).toMatchObject({ - kind: 'sponsored', - isFrontBranding: true, - branding: { - brandingType: { name: 'sponsored' }, - sponsorName: 'Guardian.org', - aboutThisLink: '', - }, - isContainerBranding: false, - hasMultipleBranding: false, - }); - }); - it('is undefined when branding does not have a brandingType name present', () => { - const branding = { - sponsorName: 'Guardian.org', - aboutThisLink: '', - logo, - }; - - const tagPageBranding = decideTagPageBranding({ - branding, - }); - expect(tagPageBranding).toBeUndefined(); - }); -}); diff --git a/dotcom-rendering/src/lib/getFrontsAdPositions.test.ts b/dotcom-rendering/src/lib/getFrontsAdPositions.test.ts deleted file mode 100644 index 4906d3159a3..00000000000 --- a/dotcom-rendering/src/lib/getFrontsAdPositions.test.ts +++ /dev/null @@ -1,598 +0,0 @@ -import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; -import { - brandedTestCollections, - largeFlexibleGeneralCollection, - largeFlexibleSpecialCollection, - smallFlexibleGeneralCollection, - smallFlexibleSpecialCollection, - testCollectionsUk, - testCollectionsUs, - testCollectionsWithSecondaryLevel, -} from '../../fixtures/manual/frontCollections'; -import type { DCRCollectionType } from '../types/front'; -import { - type AdCandidate, - getDesktopAdPositions, - getMobileAdPositions, - removeConsecutiveAdSlotsReducer, -} from './getFrontsAdPositions'; - -const testCollection: AdCandidate = { - collectionType: 'flexible/general', - displayName: 'Test Collection', - containerLevel: 'Primary', - containerPalette: 'EventPalette', - grouped: { - snap: [], - splash: [], - standard: [], - }, -}; - -const defaultTestCollections: AdCandidate[] = [...Array(12)].map( - () => ({ ...testCollection }), -); - -describe('Mobile Ads', () => { - it(`Should not insert ad after container if it's the first one and it's a thrasher`, () => { - const testCollections = [ - { ...testCollection, collectionType: 'fixed/thrasher' }, - ...defaultTestCollections, - ] satisfies AdCandidate[]; - - const mobileAdPositions = getMobileAdPositions(testCollections, 'uk'); - - expect(mobileAdPositions).not.toContain(0); - }); - - it(`should not insert an ad in the merchandising-high position`, () => { - const testCollections = [ - ...defaultTestCollections.slice(0, 3), - { ...testCollection, collectionType: 'news/most-popular' }, - ] satisfies AdCandidate[]; - const mobileAdPositions = getMobileAdPositions(testCollections, 'uk'); - expect(mobileAdPositions).not.toContain(3); - }); - - it('Should not insert ad before a thrasher container', () => { - const testCollections = [...defaultTestCollections]; - testCollections.splice(5, 0, { - ...testCollection, - collectionType: 'fixed/thrasher', - }); - testCollections.splice(9, 0, { - ...testCollection, - collectionType: 'fixed/thrasher', - }); - - const mobileAdPositions = getMobileAdPositions(testCollections, 'uk'); - - expect(mobileAdPositions).not.toContain(6); - expect(mobileAdPositions).not.toContain(8); - }); - - it(`Should allow inserting an ad before a thrasher container if it's a filter page`, () => { - const testCollections = [...defaultTestCollections]; - testCollections.splice(5, 0, { - ...testCollection, - collectionType: 'fixed/thrasher', - }); - testCollections.splice(9, 0, { - ...testCollection, - collectionType: 'fixed/thrasher', - }); - - const mobileAdPositions = getMobileAdPositions( - testCollections, - 'uk/thefilter', - ); - - expect(mobileAdPositions).toContain(6); - expect(mobileAdPositions).toContain(8); - }); - - // We used https://www.theguardian.com/uk/commentisfree as a blueprint - it('Non-network front, with more than 4 collections, without thrashers', () => { - const testCollections: AdCandidate[] = [ - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (0) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (2) - { ...testCollection, collectionType: 'static/medium/4' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (4) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (6) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (8) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is before merch high position - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position - { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container - ]; - - const mobileAdPositions = getMobileAdPositions(testCollections, 'uk'); - - expect(mobileAdPositions).toEqual([0, 2, 4, 6, 8]); - }); - - // We used https://www.theguardian.com/uk as a blueprint - it('UK Network Front, with more than 4 collections, with thrashers at various places', () => { - const testCollections: AdCandidate[] = [ - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (0) - { ...testCollection, collectionType: 'static/medium/4' }, - { ...testCollection, collectionType: 'flexible/special' }, // Ad position (2) - { ...testCollection, collectionType: 'flexible/special' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (4) - { ...testCollection, collectionType: 'flexible/special' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (8) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (11) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (14) - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, - { ...testCollection, collectionType: 'scrollable/feature' }, // Ad position (17) - { ...testCollection, collectionType: 'static/medium/4' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (19) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - is before merch high position - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position - { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container - ]; - - const mobileAdPositions = getMobileAdPositions(testCollections, 'uk'); - - expect(mobileAdPositions).toEqual([0, 2, 4, 8, 11, 14, 17, 19]); - }); - - // We used https://www.theguardian.com/international as a blueprint - it('International Network Front, with more than 4 collections, with thrashers at various places', () => { - const testCollections: AdCandidate[] = [ - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (0) - { ...testCollection, collectionType: 'static/medium/4' }, - { ...testCollection, collectionType: 'flexible/special' }, // Ad position (2) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'flexible/special' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (5) - { ...testCollection, collectionType: 'static/medium/4' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (7) - { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (11) - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (14) - { ...testCollection, collectionType: 'static/medium/4' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (16) - { ...testCollection, collectionType: 'scrollable/feature' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position - { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container - ]; - - const mobileAdPositions = getMobileAdPositions(testCollections, 'uk'); - - expect(mobileAdPositions).toEqual([0, 2, 5, 7, 11, 14, 16]); - }); - - // We used https://www.theguardian.com/us as a blueprint - it('US Network Front, with more than 4 collections, with thrashers at various places', () => { - const testCollections: AdCandidate[] = [ - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (0) - { ...testCollection, collectionType: 'static/medium/4' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (2) - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, - { ...testCollection, collectionType: 'flexible/special' }, // Ad position (5) - { ...testCollection, collectionType: 'flexible/special' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (9) - { ...testCollection, collectionType: 'static/medium/4' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (12) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (14) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (16) - { ...testCollection, collectionType: 'scrollable/feature' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ignored - is merch high position - { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container - ]; - - const mobileAdPositions = getMobileAdPositions(testCollections, 'uk'); - - expect(mobileAdPositions).toEqual([0, 2, 5, 9, 12, 14, 16]); - }); - - // We used https://www.theguardian.com/uk/lifeandstyle as a blueprint - it('Lifeandstyle front, with more than 4 collections, with thrashers at various places', () => { - const testCollections: AdCandidate[] = [ - { ...testCollection, collectionType: 'flexible/special' }, // Ad position (0) - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (3) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (6) - { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (9) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (12) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position - { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container - ]; - - const mobileAdPositions = getMobileAdPositions(testCollections, 'uk'); - - expect(mobileAdPositions).toEqual([0, 3, 6, 9, 12]); - }); - - // We used https://www.theguardian.com/tone/recipes as a blueprint - it('Recipes front, with more than 4 collections, with thrasher at the first position', () => { - const testCollections: AdCandidate[] = [ - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ignored - is first container and thrasher - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (1) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (3) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (5) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (7) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (9) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is before merch high position - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position - { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container - ]; - - const mobileAdPositions = getMobileAdPositions(testCollections, 'uk'); - - expect(mobileAdPositions).toEqual([1, 3, 5, 7, 9]); - }); - - it('Europe Network Front, with more than 4 collections and thrashers in various places', () => { - const testCollections: AdCandidate[] = [ - { - ...testCollection, - collectionType: 'flexible/general', - containerLevel: 'Primary', - }, // Ignored - is before secondary container and is not large enough - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, // Ignored - is before secondary container - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, // Ignored - is before secondary container - { - ...testCollection, - collectionType: 'scrollable/medium', - containerLevel: 'Secondary', - }, // Ignored - is before secondary container - { - ...testCollection, - collectionType: 'scrollable/feature', - containerLevel: 'Secondary', - }, // Ad position (4) - { - ...testCollection, - collectionType: 'static/feature/2', - containerLevel: 'Primary', - }, // Ignored - is before secondary container - { - ...testCollection, - collectionType: 'scrollable/medium', - containerLevel: 'Secondary', - }, // Ad position (6) - { - ...testCollection, - collectionType: 'flexible/special', - containerLevel: 'Primary', - }, // Ignored - is before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (8) - { - ...testCollection, - collectionType: 'flexible/general', - containerLevel: 'Primary', - }, // Ignored is consecutive ad after position 8 - { - ...testCollection, - collectionType: 'static/feature/2', - containerLevel: 'Primary', - }, // Ignored - is before secondary container - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, // Ignored - is before secondary container - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, // Ignored - is before secondary container - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, // Ad position (13) - { - ...testCollection, - collectionType: 'static/feature/2', - containerLevel: 'Primary', - }, // Ignored - is before secondary container - { - ...testCollection, - collectionType: 'scrollable/medium', - containerLevel: 'Secondary', - }, // Ignored - is before secondary container - { - ...testCollection, - collectionType: 'scrollable/medium', - containerLevel: 'Secondary', - }, // Ignored - is before secondary container - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, // Ignored - is before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (18) - { - ...testCollection, - collectionType: 'flexible/general', - containerLevel: 'Primary', - }, // Ignored - is before secondary container - { - ...testCollection, - collectionType: 'scrollable/feature', - containerLevel: 'Secondary', - }, // Ignored - is merch high position - { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container - ]; - - const mobileAdPositions = getMobileAdPositions(testCollections, 'uk'); - - expect(mobileAdPositions).toEqual([4, 6, 8, 13, 18]); - }); -}); - -describe('Desktop Ads', () => { - it('calculates ad positions correctly for an example of the UK network front', () => { - const adPositions = getDesktopAdPositions(testCollectionsUk, 'uk'); - - expect(adPositions).toEqual([3, 6, 8, 14, 17]); - }); - - it('calculates ad positions correctly for an example of the US network front', () => { - const adPositions = getDesktopAdPositions(testCollectionsUs, 'us'); - - expect(adPositions).toEqual([3, 6, 10, 12, 19]); - }); - - it('does NOT insert ads above or below branded content', () => { - const adPositions = getDesktopAdPositions(brandedTestCollections, 'uk'); - - expect(adPositions).toEqual([]); - }); - - it('does NOT insert ads above secondary level containers', () => { - const adPositions = getDesktopAdPositions( - testCollectionsWithSecondaryLevel, - 'europe', - ); - - expect(adPositions).toEqual([]); - }); - - it('inserts a maximum of 8 ads for fronts', () => { - const adPositions = getDesktopAdPositions( - // 10x number of test collections in fixture to reach maximum level - new Array(10) - .fill(testCollectionsWithSecondaryLevel) - .flat(), - 'europe', - ); - - expect(adPositions.length).toEqual(8); - }); -}); - -describe('inserting an ad after the first collection', () => { - describe('on mobile', () => { - it('inserts an ad after the first collection if it is a LARGE flexible general container', () => { - const adPositions = getMobileAdPositions( - [ - ...largeFlexibleGeneralCollection, - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, - { - ...testCollection, - collectionType: 'scrollable/medium', - containerLevel: 'Secondary', - }, - ], - 'uk', - ); - - expect(adPositions).toContain(0); - expect(adPositions).not.toContain(1); - }); - - it('inserts an ad after the first collection if it is a LARGE flexible special container', () => { - const adPositions = getMobileAdPositions( - [ - ...largeFlexibleSpecialCollection, - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, - { - ...testCollection, - collectionType: 'scrollable/medium', - containerLevel: 'Secondary', - }, - ], - 'uk', - ); - - expect(adPositions).toContain(0); - expect(adPositions).not.toContain(1); - }); - - it('does NOT insert an ad after the first collection if it is a SMALL flexible general container', () => { - const adPositions = getMobileAdPositions( - [ - ...smallFlexibleGeneralCollection, - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, - ], - 'uk', - ); - - expect(adPositions).not.toContain(0); - }); - - it('does NOT insert an ad after the first collection if it is a SMALL flexible special container', () => { - const adPositions = getMobileAdPositions( - [ - ...smallFlexibleSpecialCollection, - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, - ], - 'uk', - ); - - expect(adPositions).not.toContain(0); - }); - }); - - describe('on desktop', () => { - it('inserts an ad before the second collection if it is preceded by a LARGE flexible general container', () => { - const adPositions = getDesktopAdPositions( - [ - ...largeFlexibleGeneralCollection, - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, - { - ...testCollection, - collectionType: 'scrollable/medium', - containerLevel: 'Secondary', - }, - ], - 'uk', - ); - - expect(adPositions).toContain(1); - expect(adPositions).not.toContain(2); - }); - - it('inserts an ad before the second collection if it is preceded by a LARGE flexible special container', () => { - const adPositions = getDesktopAdPositions( - [ - ...largeFlexibleSpecialCollection, - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, - { - ...testCollection, - collectionType: 'scrollable/medium', - containerLevel: 'Secondary', - }, - ], - 'uk', - ); - - expect(adPositions).toContain(1); - expect(adPositions).not.toContain(2); - }); - - it('does NOT insert an ad before the second collection if it is preceded by a SMALL flexible general container', () => { - const adPositions = getDesktopAdPositions( - [ - ...smallFlexibleGeneralCollection, - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, - { - ...testCollection, - collectionType: 'scrollable/medium', - containerLevel: 'Secondary', - }, - ], - 'uk', - ); - - expect(adPositions).not.toContain(1); - }); - - it('does NOT insert an ad before the second collection if it is preceded by a SMALL flexible special container', () => { - const adPositions = getDesktopAdPositions( - [ - ...smallFlexibleSpecialCollection, - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, - { - ...testCollection, - collectionType: 'scrollable/medium', - containerLevel: 'Secondary', - }, - ], - 'uk', - ); - - expect(adPositions).not.toContain(1); - }); - }); -}); - -describe('removeConsecutiveAdSlotsReducer', () => { - it('removes consecutive slots from array of all consecutive numbers', () => { - const arr = [0, 1, 2, 3, 4, 5]; - const result = arr.reduce(removeConsecutiveAdSlotsReducer, []); - expect(result).toEqual([0, 2, 4]); - }); - - it('removes consecutive slots from array of some consecutive numbers', () => { - const arr = [0, 3, 7, 11, 12, 13, 19, 20]; - const result = arr.reduce(removeConsecutiveAdSlotsReducer, []); - expect(result).toEqual([0, 3, 7, 11, 13, 19]); - }); - - it('handles empty array', () => { - const arr: number[] = []; - const result = arr.reduce(removeConsecutiveAdSlotsReducer, []); - expect(result).toEqual([]); - }); -}); diff --git a/dotcom-rendering/src/lib/video.test.ts b/dotcom-rendering/src/lib/video.test.ts deleted file mode 100644 index 8c178a30c19..00000000000 --- a/dotcom-rendering/src/lib/video.test.ts +++ /dev/null @@ -1,439 +0,0 @@ -import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; -import type { FEMediaAsset } from '../frontend/feFront'; -import type { VideoAssets } from '../types/content'; -import type { Source } from './video'; -import { - convertCurrentTimeToProgressPercentage, - convertFEMediaAssetsToVideoAssets, - convertProgressPercentageToCurrentTime, - extractValidSourcesFromAssets, - findOptimisedSourcePerMimeType, - formatTimeForDisplay, - getAspectRatioFromSources, - roundAspectRatio, -} from './video'; - -const mp4Asset480w: VideoAssets = { - url: 'https://guim-example.co.uk/atomID-1_480w.mp4', - mimeType: 'video/mp4', - dimensions: { - height: 384, - width: 480, - }, - aspectRatio: '5:4', - hasAudio: true, -}; - -const mp4Asset720h: VideoAssets = { - url: 'https://guim-example.co.uk/atomID-1_720h.mp4', - mimeType: 'video/mp4', - dimensions: { - height: 720, - width: 900, - }, - aspectRatio: '5:4', - hasAudio: true, -}; - -const m3u8Asset720h: VideoAssets = { - url: 'https://guim-example.co.uk/atomID-1.m3u8', - mimeType: 'application/x-mpegURL', - dimensions: { - height: 720, - width: 900, - }, - aspectRatio: '5:4', - hasAudio: true, -}; -const unsupportedAsset: VideoAssets = { - url: 'https://guim-example.co.uk/atomID-1.mov', - mimeType: 'video/quicktime', - dimensions: { - height: 720, - width: 900, - }, - aspectRatio: '5:4', - hasAudio: true, -}; - -const mp4Src480w: Source = { - src: 'https://guim-example.co.uk/atomID-1_480w.mp4', - mimeType: 'video/mp4', - height: 384, - width: 480, - aspectRatio: '5:4', - hasAudio: true, -}; -const mp4Src720h: Source = { - src: 'https://guim-example.co.uk/atomID-1_720h.mp4', - mimeType: 'video/mp4', - height: 720, - width: 900, - aspectRatio: '5:4', - hasAudio: true, -}; -const m3u8Src480w: Source = { - src: 'https://guim-example.co.uk/atomID-1.m3u8', - mimeType: 'application/x-mpegURL', - height: 384, - width: 480, - aspectRatio: '5:4', - hasAudio: true, -}; -const m3u8Src720h: Source = { - src: 'https://guim-example.co.uk/atomID-1.m3u8', - mimeType: 'application/x-mpegURL', - height: 720, - width: 900, - aspectRatio: '5:4', - hasAudio: true, -}; - -describe('video', () => { - describe('extractValidSourcesFromAssets', () => { - it('should drop unsupported assets', () => { - const assets = [mp4Asset480w, m3u8Asset720h, unsupportedAsset]; - const expected = [mp4Src480w, m3u8Src720h]; - - expect(extractValidSourcesFromAssets(assets, 'Loop')).toEqual( - expected, - ); - }); - - it('should reorder sources by supportedVideoFileTypes order', () => { - const assets = [ - m3u8Asset720h, - mp4Asset480w, - m3u8Asset720h, - mp4Asset720h, - m3u8Asset720h, - ]; - const expected = [ - mp4Src480w, - mp4Src720h, - m3u8Src720h, - m3u8Src720h, - m3u8Src720h, - ]; - expect(extractValidSourcesFromAssets(assets, 'Loop')).toEqual( - expected, - ); - }); - - it('should prefer M3U8 sources for long videos with Default video style', () => { - const assets = [mp4Asset480w, m3u8Asset720h, mp4Asset720h]; - const expected = [m3u8Src720h, mp4Src480w, mp4Src720h]; - - expect( - extractValidSourcesFromAssets(assets, 'Default', 37), - ).toEqual(expected); - }); - - it('should prefer MP4 sources for short videos with Default video style', () => { - const assets = [mp4Asset480w, m3u8Asset720h, mp4Asset720h]; - const expected = [mp4Src480w, mp4Src720h, m3u8Src720h]; - - expect( - extractValidSourcesFromAssets(assets, 'Default', 12), - ).toEqual(expected); - }); - - it('should prefer MP4 sources with Loop video style', () => { - const assets = [mp4Asset480w, m3u8Asset720h, mp4Asset720h]; - const expected = [mp4Src480w, mp4Src720h, m3u8Src720h]; - - expect(extractValidSourcesFromAssets(assets, 'Loop')).toEqual( - expected, - ); - }); - - it('should prefer MP4 sources with Cinemagraph video style', () => { - const assets = [mp4Asset480w, m3u8Asset720h, mp4Asset720h]; - const expected = [mp4Src480w, mp4Src720h, m3u8Src720h]; - - expect( - extractValidSourcesFromAssets(assets, 'Cinemagraph'), - ).toEqual(expected); - }); - }); - - describe('convertFEMediaAssetsToVideoAssets', () => { - const feMediaAsset480w: FEMediaAsset = { - id: 'https://guim-example.co.uk/atomID-1_480w.mp4', - version: 1, - platform: 'Url', - assetType: 'video', - mimeType: 'video/mp4', - dimensions: { - height: 384, - width: 480, - }, - hasAudio: true, - }; - const feMediaAsset720h: FEMediaAsset = { - id: 'https://guim-example.co.uk/atomID-1_720h.mp4', - version: 1, - platform: 'Url', - assetType: 'video', - mimeType: 'video/mp4', - dimensions: { - height: 720, - width: 900, - }, - hasAudio: true, - }; - - it('should convert FE media assets to video assets', () => { - expect( - convertFEMediaAssetsToVideoAssets([ - feMediaAsset480w, - feMediaAsset720h, - ]), - ).toEqual([ - { - url: 'https://guim-example.co.uk/atomID-1_480w.mp4', - mimeType: 'video/mp4', - dimensions: { - height: 384, - width: 480, - }, - hasAudio: true, - }, - { - url: 'https://guim-example.co.uk/atomID-1_720h.mp4', - mimeType: 'video/mp4', - dimensions: { - height: 720, - width: 900, - }, - hasAudio: true, - }, - ]); - }); - - it('should return an empty array when given an empty array', () => { - expect(convertFEMediaAssetsToVideoAssets([])).toEqual([]); - }); - }); - - describe('getAspectRatioFromSources', () => { - it('should return the aspect ratio from the first source if it is defined', () => { - const testSource: Source = { - ...mp4Src480w, - height: 720, - width: 480, - aspectRatio: '5:3', - hasAudio: true, - }; - - const fiveThreeAspectRatio = 1.667; - - expect(getAspectRatioFromSources([testSource])).toEqual( - fiveThreeAspectRatio, - ); - }); - - it('should calculate the aspect ratio from the width and height if aspect ratio is missing', () => { - const testSource: Source = { - ...mp4Src480w, - height: 720, - width: 480, - aspectRatio: undefined, - hasAudio: true, - }; - - const twoThreeAspectRatio = 0.667; - - expect(getAspectRatioFromSources([testSource])).toEqual( - twoThreeAspectRatio, - ); - }); - - it('should return the default aspect ratio if the aspect ratio is undefined and width is 0', () => { - const testSource: Source = { - ...mp4Src480w, - height: 720, - width: 0, - aspectRatio: undefined, - hasAudio: true, - }; - expect(getAspectRatioFromSources([testSource])).toEqual(5 / 4); - }); - - it('should return the default aspect ratio if the aspect ratio is undefined and height is 0', () => { - const testSource: Source = { - ...mp4Src480w, - height: 0, - width: 480, - aspectRatio: undefined, - hasAudio: true, - }; - expect(getAspectRatioFromSources([testSource])).toEqual(5 / 4); - }); - }); - - describe('findOptimisedSourcePerMimeType', () => { - const testSources: Source[] = [ - mp4Src480w, - mp4Src720h, - m3u8Src480w, - m3u8Src720h, - ]; - - it('selects the smaller videos when there are multiple and all are larger than the screen width.', () => { - const screenWidth = 400; - - const sources = findOptimisedSourcePerMimeType( - testSources, - screenWidth, - ); - - expect(sources).toEqual([mp4Src480w, m3u8Src480w]); - }); - - it('selects the larger videos when there are two and one is larger than the screen width and one is smaller.', () => { - const screenWidth = 600; - - const sources = findOptimisedSourcePerMimeType( - testSources, - screenWidth, - ); - - expect(sources).toEqual([mp4Src720h, m3u8Src720h]); - }); - - it('selects the larger videos when there are multiple and all are smaller than the screen width.', () => { - const screenWidth = 800; - - const sources = findOptimisedSourcePerMimeType( - testSources, - screenWidth, - ); - - expect(sources).toEqual([mp4Src720h, m3u8Src720h]); - }); - - it('selects the smaller videos when some are equal to the screen width and others are larger.', () => { - const screenWidth = 480; - - const sources = findOptimisedSourcePerMimeType( - testSources, - screenWidth, - ); - - expect(sources).toEqual([mp4Src480w, m3u8Src480w]); - }); - - it('selects the larger videos when some are equal to the screen width and others are smaller.', () => { - const screenWidth = 720; - - const sources = findOptimisedSourcePerMimeType( - testSources, - screenWidth, - ); - - expect(sources).toEqual([mp4Src720h, m3u8Src720h]); - }); - }); - - describe('convertCurrentTimeToProgressPercentage', () => { - for (const testCase of [ - { currentTime: 0, duration: 23, expectedPercentage: 0 }, - { currentTime: 24, duration: 32, expectedPercentage: 75 }, - { currentTime: 56, duration: 56, expectedPercentage: 100 }, - { currentTime: 12, duration: 11, expectedPercentage: 100 }, - { currentTime: -5, duration: 10, expectedPercentage: null }, - { currentTime: 5, duration: -10, expectedPercentage: null }, - ]) { - void nodeIt( - 'should return the correct progress percentage based on the current time and duration', - () => { - const { currentTime, duration, expectedPercentage } = - testCase; - expect( - convertCurrentTimeToProgressPercentage( - currentTime, - duration, - ), - ).toEqual(expectedPercentage); - }, - ); - } - }); - - describe('convertProgressPercentageToCurrentTime', () => { - for (const testCase of [ - { progressPercentage: 0, duration: 23, expectedCurrentTime: 0 }, - { progressPercentage: 75, duration: 32, expectedCurrentTime: 24 }, - { progressPercentage: 100, duration: 56, expectedCurrentTime: 56 }, - { progressPercentage: 103, duration: 11, expectedCurrentTime: 11 }, - { progressPercentage: 10, duration: 0, expectedCurrentTime: null }, - { progressPercentage: 8, duration: -10, expectedCurrentTime: null }, - { - progressPercentage: -0.1244235, - duration: 10, - expectedCurrentTime: 0, - }, - ]) { - void nodeIt( - 'should return the correct current time based on the progress percentage and duration', - () => { - const { - progressPercentage, - duration, - expectedCurrentTime, - } = testCase; - expect( - convertProgressPercentageToCurrentTime( - progressPercentage, - duration, - ), - ).toEqual(expectedCurrentTime); - }, - ); - } - }); - - describe('formatTimeForDisplay', () => { - for (const testCase of [ - { timeInSeconds: -1.24, expectedFormattedTime: '0:00' }, - { timeInSeconds: 0, expectedFormattedTime: '0:00' }, - { timeInSeconds: 59, expectedFormattedTime: '0:59' }, - { timeInSeconds: 60, expectedFormattedTime: '1:00' }, - { timeInSeconds: 61, expectedFormattedTime: '1:01' }, - { timeInSeconds: 92.5, expectedFormattedTime: '1:32' }, - { timeInSeconds: 1000, expectedFormattedTime: '16:40' }, - { timeInSeconds: 10000, expectedFormattedTime: '166:40' }, - ]) { - void nodeIt( - 'should return the correct formatted time based on the time in seconds', - () => { - const { timeInSeconds, expectedFormattedTime } = testCase; - expect(formatTimeForDisplay(timeInSeconds)).toEqual( - expectedFormattedTime, - ); - }, - ); - } - }); - describe('roundAspectRatio', () => { - for (const testCase of [ - { aspectRatio: 0.56938445, expectedRoundedAspectRatio: 0.569 }, - { aspectRatio: 1.277777, expectedRoundedAspectRatio: 1.278 }, - { aspectRatio: 1.25, expectedRoundedAspectRatio: 1.25 }, - { aspectRatio: 0.8, expectedRoundedAspectRatio: 0.8 }, - ]) { - void nodeIt( - 'should return the correct aspect ratio rounded to 3 decimal places', - () => { - const { aspectRatio, expectedRoundedAspectRatio } = - testCase; - expect(roundAspectRatio(aspectRatio)).toEqual( - expectedRoundedAspectRatio, - ); - }, - ); - } - }); -}); diff --git a/dotcom-rendering/src/model/groupTrailsByDates.test.ts b/dotcom-rendering/src/model/groupTrailsByDates.test.ts deleted file mode 100644 index eb175ee12c0..00000000000 --- a/dotcom-rendering/src/model/groupTrailsByDates.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; -import { trails } from '../../fixtures/manual/trails'; -import type { DCRFrontCard } from '../types/front'; -import { groupTrailsByDates } from './groupTrailsByDates'; - -const datesToTrails = (dates: Date[]): DCRFrontCard[] => { - return dates.map((date) => ({ - ...trails[0], - webPublicationDate: date.toISOString(), - })); -}; - -describe('groupTrailsByDates', () => { - it('Will split trails into days & months depending on the frequency', () => { - const dates = [ - // SHOULD BE GROUPED BY DAY - // 3 on the 23rd of June - new Date(2023, 5, 23, 12), - new Date(2023, 5, 23, 12), - new Date(2023, 5, 23, 12), - // 5 on the 25th of June - new Date(2023, 5, 25, 12), - new Date(2023, 5, 25, 12), - new Date(2023, 5, 25, 12), - new Date(2023, 5, 25, 12), - new Date(2023, 5, 25, 12), - // 7 on the 26th of June - new Date(2023, 5, 26, 12), - new Date(2023, 5, 26, 12), - new Date(2023, 5, 26, 12), - new Date(2023, 5, 26, 12), - new Date(2023, 5, 26, 12), - new Date(2023, 5, 26, 12), - - // SHOULD BE GROUPED BY MONTH - // 1 on the 2nd of May - new Date(2023, 4, 2, 12), - // 3 on 3rd of May - new Date(2023, 4, 3, 12), - new Date(2023, 4, 3, 12), - // 1 on 4th of May - new Date(2023, 4, 4, 12), - // 1 on 5th of May - new Date(2023, 4, 5, 12), - ]; - - const result = groupTrailsByDates(datesToTrails(dates), 'UK'); - - expect(result[0]?.day).toEqual('26'); - expect(result[1]?.day).toEqual('25'); - expect(result[2]?.day).toEqual('23'); - - expect(result[3]?.day).toEqual(undefined); - expect(result[3]?.month).toEqual('May'); - }); - - it('Will handle all editions', () => { - const dates = [ - // The whole of the last day of June (months are 0-indexed) - '2024-06-30T00:00:00Z', - '2024-06-30T01:00:00Z', - '2024-06-30T02:00:00Z', - '2024-06-30T03:00:00Z', - '2024-06-30T04:00:00Z', - '2024-06-30T05:00:00Z', - '2024-06-30T06:00:00Z', - '2024-06-30T07:00:00Z', - '2024-06-30T08:00:00Z', - '2024-06-30T09:00:00Z', - '2024-06-30T10:00:00Z', - '2024-06-30T11:00:00Z', - '2024-06-30T12:00:00Z', - '2024-06-30T13:00:00Z', - '2024-06-30T14:00:00Z', - '2024-06-30T15:00:00Z', - '2024-06-30T16:00:00Z', - '2024-06-30T17:00:00Z', - '2024-06-30T18:00:00Z', - '2024-06-30T19:00:00Z', - '2024-06-30T20:00:00Z', - '2024-06-30T21:00:00Z', - '2024-06-30T22:00:00Z', - '2024-06-30T23:00:00Z', - ].map((date) => new Date(date)); - - const uk = groupTrailsByDates(datesToTrails(dates), 'UK'); - - expect(uk[1]?.day).toEqual('30'); - expect(uk[1]?.month).toEqual('June'); - expect(uk[1]?.trails).toHaveLength(23); - expect(uk[0]?.day).toBeUndefined(); - expect(uk[0]?.month).toEqual('July'); - expect(uk[0]?.trails).toHaveLength(1); - - const au = groupTrailsByDates(datesToTrails(dates), 'AU'); - - expect(au[1]?.day).toEqual('30'); - expect(au[1]?.month).toEqual('June'); - expect(au[1]?.trails).toHaveLength(14); - expect(au[0]?.day).toEqual('1'); - expect(au[0]?.month).toEqual('July'); - expect(au[0]?.trails).toHaveLength(10); - - const us = groupTrailsByDates(datesToTrails(dates), 'US'); - - expect(us[1]?.day).toEqual('29'); - expect(us[1]?.month).toEqual('June'); - expect(us[1]?.trails).toHaveLength(4); - expect(us[0]?.day).toEqual('30'); - expect(us[0]?.month).toEqual('June'); - expect(us[0]?.trails).toHaveLength(20); - }); - - it('Will respect "forceDay" being set to true', () => { - const dates = [ - // This would be grouped by month if left to the pop out frequency - // 1 on the 2nd of May - new Date(2023, 4, 2, 12), - // 3 on 3rd of May - new Date(2023, 4, 3, 12), - new Date(2023, 4, 3, 12), - // 1 on 4th of May - new Date(2023, 4, 4, 12), - // 1 on 5th of May - new Date(2023, 4, 5, 12), - ]; - - const result = groupTrailsByDates(datesToTrails(dates), 'UK', true); - - expect(result[0]?.day).toEqual('5'); - expect(result[1]?.day).toEqual('4'); - expect(result[2]?.day).toEqual('3'); - expect(result[3]?.day).toEqual('2'); - }); -}); diff --git a/dotcom-rendering/src/model/validate.puzzlesPage.test.ts b/dotcom-rendering/src/model/validate.puzzlesPage.test.ts deleted file mode 100644 index 76ac1ce082e..00000000000 --- a/dotcom-rendering/src/model/validate.puzzlesPage.test.ts +++ /dev/null @@ -1,178 +0,0 @@ -import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; -import { validateAsPuzzlesPageType } from './validate'; - -const validPage = () => ({ - id: 'puzzles', - webTitle: 'Puzzles and games', - editionId: 'UK', - canonicalUrl: 'https://www.theguardian.com/puzzles-and-games', - isAdFreeUser: false, - config: { serverSideABTests: { 'puzzles-new-hub': 'variant' } }, - nav: {}, - pageFooter: {}, - layout: { - containers: [ - { - id: 'word-games', - title: 'Word games', - variant: 'standard', - content: { - nestedContainers: [], - items: [ - [ - { - id: 'word-wheel', - title: 'Word wheel', - type: 'word-game', - set: 'all', - cardVariant: 'primary', - cadence: 'Daily', - slug: 'word-wheel', - variant: 'iframe-page', - }, - ], - ], - }, - }, - ], - }, -}); - -describe('validateAsPuzzlesPageType', () => { - it('accepts a valid recursive blueprint contract', () => { - expect( - validateAsPuzzlesPageType(validPage()).layout.containers[0]?.id, - ).toBe('word-games'); - }); - - it('accepts enabled on featured containers and rejects it elsewhere', () => { - const featuredPage = validPage(); - featuredPage.layout.containers[0]!.variant = 'featured'; - (featuredPage.layout.containers[0] as { enabled?: boolean }).enabled = - true; - expect(validateAsPuzzlesPageType(featuredPage)).toBeDefined(); - - featuredPage.layout.containers[0]!.variant = 'standard'; - expect(() => validateAsPuzzlesPageType(featuredPage)).toThrow(); - }); - - for (const [name, mutate] of [ - [ - 'unknown card variant', - (page: ReturnType) => { - page.layout.containers[0]!.content.items[0]![0]!.cardVariant = - 'hero'; - }, - ], - [ - 'missing cadence', - (page: ReturnType) => { - const card = page.layout.containers[0]!.content - .items[0]![0]! as { - cadence?: string; - }; - delete card.cadence; - }, - ], - [ - 'invalid colour', - (page: ReturnType) => { - const card = page.layout.containers[0]!.content - .items[0]![0]! as { - backgroundColour?: string; - }; - card.backgroundColour = 'red'; - }, - ], - [ - 'unsupported span', - (page: ReturnType) => { - const container = page.layout.containers[0]! as { - desktopSpan?: number; - }; - container.desktopSpan = 13; - }, - ], - [ - 'duplicate stable ID', - (page: ReturnType) => { - page.layout.containers[0]!.content.items[0]!.push({ - ...page.layout.containers[0]!.content.items[0]![0]!, - }); - }, - ], - ] as const) { - void nodeIt(`rejects ${name}`, () => { - const page = validPage(); - mutate(page); - expect(() => validateAsPuzzlesPageType(page)).toThrow( - 'Unable to validate request body for puzzles page', - ); - }); - } - - it('accepts supporting content with valid puzzle references', () => { - const page = validPage(); - page.layout.containers.push({ - id: 'supporting', - title: '', - variant: 'supporting', - adSlot: 'mostpop', - content: { items: [], nestedContainers: [] }, - supporting: { - usefulLinksTitle: 'Useful links', - usefulLinks: [ - { - title: 'Archive', - url: '/puzzles-and-games/word-wheel/archive', - }, - ], - popularTitle: 'Most popular puzzles', - popularGroups: [ - { title: 'Most played', itemIds: ['word-wheel'] }, - ], - }, - } as never); - - expect(validateAsPuzzlesPageType(page).layout.containers).toHaveLength( - 2, - ); - }); - - it('rejects supporting content which references an unknown puzzle', () => { - const page = validPage(); - page.layout.containers.push({ - id: 'supporting', - title: '', - variant: 'supporting', - content: { items: [], nestedContainers: [] }, - supporting: { - usefulLinksTitle: 'Useful links', - usefulLinks: [], - popularTitle: 'Most popular puzzles', - popularGroups: [{ title: 'Most played', itemIds: ['missing'] }], - }, - } as never); - - expect(() => validateAsPuzzlesPageType(page)).toThrow(); - }); - - it('accepts a valid top-level ad placement and rejects one nested inside content', () => { - const page = validPage(); - const ad = { - id: 'inline-ad', - title: '', - variant: 'ad', - adSlot: 'inline1', - content: { items: [], nestedContainers: [] }, - }; - page.layout.containers.push(ad as never); - expect(validateAsPuzzlesPageType(page).layout.containers).toHaveLength( - 2, - ); - page.layout.containers.pop(); - page.layout.containers[0]!.content.nestedContainers.push(ad as never); - expect(() => validateAsPuzzlesPageType(page)).toThrow(); - }); -}); diff --git a/dotcom-rendering/src/model/validate.test.ts b/dotcom-rendering/src/model/validate.test.ts deleted file mode 100644 index 491be8c28f6..00000000000 --- a/dotcom-rendering/src/model/validate.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; -import { Comment } from '../../fixtures/generated/fe-articles/Comment'; -import { Feature } from '../../fixtures/generated/fe-articles/Feature'; -import { Live } from '../../fixtures/generated/fe-articles/Live'; -import { MatchReport } from '../../fixtures/generated/fe-articles/MatchReport'; -import { Review } from '../../fixtures/generated/fe-articles/Review'; -import { Standard } from '../../fixtures/generated/fe-articles/Standard'; -import { hostedArticle } from '../../fixtures/manual/hostedArticle'; -import { hostedGallery } from '../../fixtures/manual/hostedGallery'; -import { hostedVideo } from '../../fixtures/manual/hostedVideo'; -import { validateAsFEArticle } from './validate'; - -const articles = [ - { - name: 'Standard', - data: Standard, - }, - { - name: 'Feature', - data: Feature, - }, - { - name: 'Comment', - data: Comment, - }, - { - name: 'Match Report', - data: MatchReport, - }, - { - name: 'Review', - data: Review, - }, - { - name: 'Liveblog', - data: Live, - }, -] as const; - -const hostedContentArticles = [ - { - name: 'Hosted Article', - data: hostedArticle, - }, - { - name: 'Hosted Gallery', - data: hostedGallery, - }, - { - name: 'Hosted Video', - data: hostedVideo, - }, -]; - -describe('validate', () => { - it('throws on invalid data', () => { - const data = { foo: 'bar' }; - expect(() => validateAsFEArticle(data)).toThrow(TypeError); - }); - - for (const article of articles) { - it(`validates data for a ${article.name} article`, () => { - expect(validateAsFEArticle(article.data)).toBe(article.data); - }); - } - - for (const hostedItem of hostedContentArticles) { - it(`validates data for hosted ${hostedItem.name} content`, () => { - expect(validateAsFEArticle(hostedItem.data)).toBe(hostedItem.data); - }); - } -}); From ec0d18989dc711730af13c19bdcfe6c3b02df09a Mon Sep 17 00:00:00 2001 From: Ravi <7014230+arelra@users.noreply.github.com> Date: Mon, 14 Sep 2026 10:12:18 +0100 Subject: [PATCH 5/8] Remove node import aliases --- .../electionComponent.node.test.ts | 14 +- .../src/cricketMatch.node.test.ts | 14 +- .../src/footballMatches.node.test.ts | 175 ++- .../src/lib/acquisitions.node.test.ts | 6 +- .../src/lib/ad-targeting.node.test.ts | 6 +- .../src/lib/affiliateLinksUtils.node.test.ts | 105 +- .../src/lib/age-warning.node.test.ts | 70 +- .../src/lib/alternate-lang-links.node.test.ts | 126 +- .../src/lib/articleMeta.node.test.ts | 53 +- .../src/lib/branding.node.test.ts | 630 +++++----- dotcom-rendering/src/lib/byline.node.test.ts | 275 ++--- .../src/lib/canRenderAds.node.test.ts | 10 +- .../src/lib/cardHelpers.node.test.ts | 19 +- .../src/lib/decide-cation.node.test.ts | 90 +- dotcom-rendering/src/lib/edition.node.test.ts | 14 +- .../src/lib/formatAttrString.node.test.ts | 14 +- .../src/lib/formatCount.node.test.ts | 14 +- .../src/lib/getFrontsAdPositions.node.test.ts | 1047 ++++++++--------- .../lib/getLiveblogAdPositions.node.test.ts | 81 +- .../lib/getTagPageAdPositions.node.test.ts | 42 +- .../src/lib/getZIndex.node.test.ts | 6 +- .../lib/identity-component-event.node.test.ts | 37 +- dotcom-rendering/src/lib/isLight.node.test.ts | 73 +- .../src/lib/isValidUrl.node.test.ts | 19 +- dotcom-rendering/src/lib/labs.node.test.ts | 66 +- dotcom-rendering/src/lib/lang.node.test.ts | 12 +- .../lib/linkNotificationCount.node.test.ts | 8 +- .../src/lib/liveblogAdSlots.node.test.ts | 284 ++--- .../src/lib/notification.node.test.ts | 115 +- .../src/lib/ophan-helpers.node.test.ts | 6 +- .../parseCheckoutOutCookieData.node.test.ts | 44 +- .../src/lib/puzzlesHubExperiment.node.test.ts | 21 +- .../src/lib/querystring.node.test.ts | 6 +- dotcom-rendering/src/lib/result.node.test.ts | 73 +- .../lib/sendTargetingParams.apps.node.test.ts | 142 ++- .../src/lib/theFilter.node.test.ts | 25 +- .../src/lib/transparentColour.node.test.ts | 20 +- dotcom-rendering/src/lib/tuple.node.test.ts | 107 +- dotcom-rendering/src/lib/video.node.test.ts | 431 +++---- .../src/model/article-sections.node.test.ts | 6 +- .../model/buildLightboxImages.node.test.ts | 529 ++++----- .../enhance-ad-placeholders.node.test.ts | 352 +++--- .../src/model/enhance-dots.node.test.ts | 63 +- .../enhance-product-summary.node.test.ts | 168 ++- .../src/model/enhance-videos.node.test.ts | 8 +- .../enhanceCommercialProperties.node.test.ts | 8 +- .../src/model/enhanceLists.node.test.ts | 6 +- .../src/model/enhanceTags.node.test.ts | 6 +- .../src/model/enhanceTimeline.node.test.ts | 45 +- .../model/extractTrendingTopics.node.test.ts | 103 +- .../src/model/groupTrailsByDates.node.test.ts | 97 +- .../src/model/unwrapHtml.node.test.ts | 185 ++- .../src/model/validate.node.test.ts | 20 +- .../model/validate.puzzlesPage.node.test.ts | 165 ++- 54 files changed, 2753 insertions(+), 3308 deletions(-) diff --git a/dotcom-rendering/src/components/ElectionTrackers/electionComponent.node.test.ts b/dotcom-rendering/src/components/ElectionTrackers/electionComponent.node.test.ts index b699276bad9..1cc4beec484 100644 --- a/dotcom-rendering/src/components/ElectionTrackers/electionComponent.node.test.ts +++ b/dotcom-rendering/src/components/ElectionTrackers/electionComponent.node.test.ts @@ -1,4 +1,4 @@ -import { it as nodeIt } from 'node:test'; +import { it } from 'node:test'; import { parse } from 'valibot'; import { euParliament } from '../../../fixtures/manual/electionTrackers/euParliament'; import { ukGeneralExitPoll } from '../../../fixtures/manual/electionTrackers/ukGeneralExitPoll'; @@ -8,26 +8,26 @@ import { usCongressEmpty } from '../../../fixtures/manual/electionTrackers/usCon import { usPresidential } from '../../../fixtures/manual/electionTrackers/usPresidential'; import { ElectionComponents } from './electionComponent'; -void nodeIt('validates US Congress data', () => { +void it('validates US Congress data', () => { parse(ElectionComponents, usCongressEmpty); }); -void nodeIt('validates UK General data', () => { +void it('validates UK General data', () => { parse(ElectionComponents, ukGeneralFinal); }); -void nodeIt('validates UK General Exit Poll data', () => { +void it('validates UK General Exit Poll data', () => { parse(ElectionComponents, ukGeneralExitPoll); }); -void nodeIt('validates UK Local data', () => { +void it('validates UK Local data', () => { parse(ElectionComponents, ukLocal); }); -void nodeIt('validates US Presidential data', () => { +void it('validates US Presidential data', () => { parse(ElectionComponents, usPresidential); }); -void nodeIt('validates EU Parliament data', () => { +void it('validates EU Parliament data', () => { parse(ElectionComponents, euParliament); }); diff --git a/dotcom-rendering/src/cricketMatch.node.test.ts b/dotcom-rendering/src/cricketMatch.node.test.ts index 53651c7bb5f..2c4b45ad2e7 100644 --- a/dotcom-rendering/src/cricketMatch.node.test.ts +++ b/dotcom-rendering/src/cricketMatch.node.test.ts @@ -1,10 +1,10 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { liveMatch, resultMatch } from '../fixtures/manual/cricketMatch'; import { parseCricketMatch } from './cricketMatch'; -void nodeDescribe('parseCricketMatchV2', () => { - void nodeIt('parses a winner result cricket match correctly', () => { +void describe('parseCricketMatchV2', () => { + void it('parses a winner result cricket match correctly', () => { const result = parseCricketMatch(resultMatch).getOrThrow( 'Expected parsing cricket match to succeed', ); @@ -25,7 +25,7 @@ void nodeDescribe('parseCricketMatchV2', () => { ); }); - void nodeIt('parses a cricket match in pre-match status', () => { + void it('parses a cricket match in pre-match status', () => { const result = parseCricketMatch({ ...liveMatch, result: 'pre-match', @@ -36,7 +36,7 @@ void nodeDescribe('parseCricketMatchV2', () => { assert.equal(result.result, undefined); }); - void nodeIt('parses a cricket match in in-play status', () => { + void it('parses a cricket match in in-play status', () => { const result = parseCricketMatch({ ...liveMatch, result: 'in-play', @@ -47,7 +47,7 @@ void nodeDescribe('parseCricketMatchV2', () => { assert.equal(result.result, undefined); }); - void nodeIt('parses an abandoned cricket match correctly', () => { + void it('parses an abandoned cricket match correctly', () => { const result = parseCricketMatch({ ...liveMatch, fullResult: { @@ -63,7 +63,7 @@ void nodeDescribe('parseCricketMatchV2', () => { }); }); - void nodeIt('parses a cricket match with no winner', () => { + void it('parses a cricket match with no winner', () => { const result = parseCricketMatch({ ...liveMatch, fullResult: { diff --git a/dotcom-rendering/src/footballMatches.node.test.ts b/dotcom-rendering/src/footballMatches.node.test.ts index 80f6dabbbbc..cbdee653b5c 100644 --- a/dotcom-rendering/src/footballMatches.node.test.ts +++ b/dotcom-rendering/src/footballMatches.node.test.ts @@ -1,5 +1,5 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { footballData } from '../fixtures/generated/football-live'; import { emptyMatches, @@ -27,8 +27,8 @@ const withMatches = ( })), })); -void nodeDescribe('footballMatches', () => { - void nodeIt('should parse match fixtures correctly', () => { +void describe('footballMatches', () => { + void it('should parse match fixtures correctly', () => { const result = parse(footballData.matchesList).getOrThrow( 'Expected football match parsing to succeed', ); @@ -45,75 +45,66 @@ void nodeDescribe('footballMatches', () => { assert.equal(competition?.tag, 'football/serieafootball'); }); - void nodeIt( - 'should return an error when football days have invalid dates', - () => { - const invalidDate: FEMatchByDateAndCompetition[] = emptyMatches.map( - (day) => ({ - ...day, - date: 'foo', - }), - ); - - const result = parse(invalidDate).getErrorOrThrow( - 'Expected football match parsing to fail', - ); - - assert.equal(result.kind, 'FootballDayInvalidDate'); - }, - ); - - void nodeIt( - 'should return an error when football matches have an invalid date', - () => { - const invalidMatchResult: FEMatchByDateAndCompetition[] = - withMatches([ - matchFixture, - { ...matchResult, date: '' }, - matchDayLive, - ]); - const invalidMatchFixture: FEMatchByDateAndCompetition[] = - withMatches([ - { ...matchFixture, date: '' }, - matchResult, - matchDayLive, - ]); - const invalidLiveMatch: FEMatchByDateAndCompetition[] = withMatches( - [matchResult, matchFixture, { ...matchDayLive, date: '' }], - ); - - const resultOne = parse(invalidMatchResult).getErrorOrThrow( - 'Expected football match parsing to fail', - ); - const resultTwo = parse(invalidMatchFixture).getErrorOrThrow( - 'Expected football match parsing to fail', - ); - const resultThree = parse(invalidLiveMatch).getErrorOrThrow( - 'Expected football match parsing to fail', - ); - - assert.equal(resultOne.kind, 'FootballMatchInvalidDate'); - assert.equal(resultTwo.kind, 'FootballMatchInvalidDate'); - - if (resultThree.kind !== 'InvalidMatchDay') { - throw new Error('Expected an invalid match day error'); - } + void it('should return an error when football days have invalid dates', () => { + const invalidDate: FEMatchByDateAndCompetition[] = emptyMatches.map( + (day) => ({ + ...day, + date: 'foo', + }), + ); + + const result = parse(invalidDate).getErrorOrThrow( + 'Expected football match parsing to fail', + ); + + assert.equal(result.kind, 'FootballDayInvalidDate'); + }); + + void it('should return an error when football matches have an invalid date', () => { + const invalidMatchResult: FEMatchByDateAndCompetition[] = withMatches([ + matchFixture, + { ...matchResult, date: '' }, + matchDayLive, + ]); + const invalidMatchFixture: FEMatchByDateAndCompetition[] = withMatches([ + { ...matchFixture, date: '' }, + matchResult, + matchDayLive, + ]); + const invalidLiveMatch: FEMatchByDateAndCompetition[] = withMatches([ + matchResult, + matchFixture, + { ...matchDayLive, date: '' }, + ]); + + const resultOne = parse(invalidMatchResult).getErrorOrThrow( + 'Expected football match parsing to fail', + ); + const resultTwo = parse(invalidMatchFixture).getErrorOrThrow( + 'Expected football match parsing to fail', + ); + const resultThree = parse(invalidLiveMatch).getErrorOrThrow( + 'Expected football match parsing to fail', + ); - assert.equal( - resultThree.errors[0]!.kind, - 'FootballMatchInvalidDate', - ); - }, - ); + assert.equal(resultOne.kind, 'FootballMatchInvalidDate'); + assert.equal(resultTwo.kind, 'FootballMatchInvalidDate'); + + if (resultThree.kind !== 'InvalidMatchDay') { + throw new Error('Expected an invalid match day error'); + } - void nodeIt('should return an error when it receives a live match', () => { + assert.equal(resultThree.errors[0]!.kind, 'FootballMatchInvalidDate'); + }); + + void it('should return an error when it receives a live match', () => { const result = parse(withMatches([liveMatch])).getErrorOrThrow( 'Expected football match parsing to fail', ); assert.equal(result.kind, 'UnexpectedLiveMatch'); }); - void nodeIt('should return a clean team name', () => { + void it('should return a clean team name', () => { const matchesListWithTeamName = (teamName: string): FEResult => { return { ...matchResult, @@ -150,39 +141,33 @@ void nodeDescribe('footballMatches', () => { assert.equal(match.homeTeam.name, cleanName); } }); - void nodeIt( - 'should replace known live match status with our status', - () => { - const matchDay = parse( - withMatches([matchDayLiveSecondHalf]), - ).getOrThrow('Expected football live match parsing to succeed'); - - const match = matchDay[0]!.competitions[0]!.matches[0]; - if (match?.kind !== 'Live') { - throw new Error('Expected live match'); - } + void it('should replace known live match status with our status', () => { + const matchDay = parse( + withMatches([matchDayLiveSecondHalf]), + ).getOrThrow('Expected football live match parsing to succeed'); + + const match = matchDay[0]!.competitions[0]!.matches[0]; + if (match?.kind !== 'Live') { + throw new Error('Expected live match'); + } - assert.equal(match.status, '2nd'); - }, - ); - void nodeIt( - 'should replace unknown live match status with first two characters', - () => { - const matchDayLiveUnknownStatus = { - ...matchDayLiveSecondHalf, - matchStatus: 'Something odd', - }; + assert.equal(match.status, '2nd'); + }); + void it('should replace unknown live match status with first two characters', () => { + const matchDayLiveUnknownStatus = { + ...matchDayLiveSecondHalf, + matchStatus: 'Something odd', + }; - const matchDay = parse( - withMatches([matchDayLiveUnknownStatus]), - ).getOrThrow('Expected football live match parsing to succeed'); + const matchDay = parse( + withMatches([matchDayLiveUnknownStatus]), + ).getOrThrow('Expected football live match parsing to succeed'); - const match = matchDay[0]!.competitions[0]!.matches[0]; - if (match?.kind !== 'Live') { - throw new Error('Expected live match'); - } + const match = matchDay[0]!.competitions[0]!.matches[0]; + if (match?.kind !== 'Live') { + throw new Error('Expected live match'); + } - assert.equal(match.status, 'So'); - }, - ); + assert.equal(match.status, 'So'); + }); }); diff --git a/dotcom-rendering/src/lib/acquisitions.node.test.ts b/dotcom-rendering/src/lib/acquisitions.node.test.ts index b8a146d1756..f178aae651c 100644 --- a/dotcom-rendering/src/lib/acquisitions.node.test.ts +++ b/dotcom-rendering/src/lib/acquisitions.node.test.ts @@ -1,9 +1,9 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { addTrackingCodesToUrl } from './acquisitions'; -void nodeDescribe('acquisitions', () => { - void nodeIt('should addTrackingCodesToUrl', () => { +void describe('acquisitions', () => { + void it('should addTrackingCodesToUrl', () => { const result = addTrackingCodesToUrl({ base: `https://support.theguardian.com/contribute`, componentType: 'ACQUISITIONS_HEADER', diff --git a/dotcom-rendering/src/lib/ad-targeting.node.test.ts b/dotcom-rendering/src/lib/ad-targeting.node.test.ts index 68357662237..d4f6204fe42 100644 --- a/dotcom-rendering/src/lib/ad-targeting.node.test.ts +++ b/dotcom-rendering/src/lib/ad-targeting.node.test.ts @@ -1,5 +1,5 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { buildAdTargeting } from './ad-targeting'; const sharedAdTargeting = { @@ -14,7 +14,7 @@ const sharedAdTargeting = { url: '/money/2017/mar/10/ministers-to-criminalise-use-of-ticket-tout-harvesting-software', }; -void nodeDescribe('buildAdTargeting', () => { +void describe('buildAdTargeting', () => { const expectedAdTargeting = { adUnit: '/59666047/theguardian.com/money/article/ng', customParams: { @@ -40,7 +40,7 @@ void nodeDescribe('buildAdTargeting', () => { }, }; - void nodeIt('builds adTargeting correctly', () => { + void it('builds adTargeting correctly', () => { assert.deepEqual( buildAdTargeting({ isAdFreeUser: false, diff --git a/dotcom-rendering/src/lib/affiliateLinksUtils.node.test.ts b/dotcom-rendering/src/lib/affiliateLinksUtils.node.test.ts index 9617226ae29..eb3c77c0669 100644 --- a/dotcom-rendering/src/lib/affiliateLinksUtils.node.test.ts +++ b/dotcom-rendering/src/lib/affiliateLinksUtils.node.test.ts @@ -1,13 +1,13 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { buildMergedAbTestString, buildXcustParamForAffiliateLink, extractAbTestParticipationFromUrl, } from './affiliateLinksUtils'; -void nodeDescribe('extractAbTestParticipationFromUrl', () => { - void nodeIt('extracts AB test participations from xcust', () => { +void describe('extractAbTestParticipationFromUrl', () => { + void it('extracts AB test participations from xcust', () => { const url = 'https://go.skimresources.com/?id=114047X1572903&url=https%3A%2F%2Fwww.argos.co.uk%2Fproduct%2F8112969&sref=https://www.theguardian.com/thefilter/2024/nov/21/best-coffee-machines&xcust=referrer%7Cwww.theguardian.com%7CaccountId%7C114047X1572903%7CabTestParticipations%7Cthefilter-at-a-glance-redesign-v2%3Acarousel%7CcomponentId%7Ccarousel-card'; @@ -16,19 +16,16 @@ void nodeDescribe('extractAbTestParticipationFromUrl', () => { }); }); - void nodeIt( - 'returns empty object when xcust has no AB test section', - () => { - const url = - 'https://go.skimresources.com/?id=114047X1572903&url=https%3A%2F%2Fwww.argos.co.uk%2Fproduct%2F8112969&xcust=referrer%7Cwww.theguardian.com%7CaccountId%7C114047X1572903%7CcomponentId%7Ccarousel-card'; + void it('returns empty object when xcust has no AB test section', () => { + const url = + 'https://go.skimresources.com/?id=114047X1572903&url=https%3A%2F%2Fwww.argos.co.uk%2Fproduct%2F8112969&xcust=referrer%7Cwww.theguardian.com%7CaccountId%7C114047X1572903%7CcomponentId%7Ccarousel-card'; - assert.deepEqual(extractAbTestParticipationFromUrl(url), {}); - }, - ); + assert.deepEqual(extractAbTestParticipationFromUrl(url), {}); + }); }); -void nodeDescribe('buildXcustValueForAffiliateLink', () => { - void nodeIt('returns xcust value for skimlinks URLs', () => { +void describe('buildXcustValueForAffiliateLink', () => { + void it('returns xcust value for skimlinks URLs', () => { const xcustResult = buildXcustParamForAffiliateLink({ url: new URL( 'https://go.skimresources.com/?id=1234X9876&url=https%3A%2F%2Fwww.theguardian.com%2Fuk', @@ -45,7 +42,7 @@ void nodeDescribe('buildXcustValueForAffiliateLink', () => { ); }); - void nodeIt('includes optional xcust values when provided', () => { + void it('includes optional xcust values when provided', () => { const xcustResult = buildXcustParamForAffiliateLink({ url: new URL( 'https://go.skimresources.com/?id=1111&url=https%3A%2F%2Fwww.theguardian.com%2Fus-news', @@ -62,7 +59,7 @@ void nodeDescribe('buildXcustValueForAffiliateLink', () => { ); }); - void nodeIt('merges existing and incoming AB test participations', () => { + void it('merges existing and incoming AB test participations', () => { const xcustResult = buildXcustParamForAffiliateLink({ url: new URL( 'https://go.skimresources.com/?id=1111&url=https%3A%2F%2Fwww.theguardian.com%2Fus-news&xcust=referrer%7Cwww.theguardian.com%7CaccountId%7C1111%7CabTestParticipations%7CexistingTest%3Acontrol%2CabTest1%3AoldVariant', @@ -80,51 +77,43 @@ void nodeDescribe('buildXcustValueForAffiliateLink', () => { assert.ok(!xcustResult.includes('abTest1:variantA')); }); - void nodeIt( - 'preserves existing AB participations when url already has xcust', - () => { - const xcustResult = buildXcustParamForAffiliateLink({ - url: new URL( - 'https://go.skimresources.com/?id=1111&url=https%3A%2F%2Fwww.theguardian.com%2Fus-news&xcust=referrer%7Cold.example%7CaccountId%7C1111%7CabTestParticipations%7ColdTest%3AoldVariant', - ), - abTestParticipations: { newTest: 'newVariant' }, - utmParamsString: '', - referrerDomain: 'www.theguardian.com', - xcustComponentId: null, - }); - - assert.ok( - xcustResult.includes( - 'referrer|www.theguardian.com|accountId|1111', - ), - ); - assert.ok(xcustResult.includes('newTest:newVariant')); - assert.ok(xcustResult.includes('oldTest:oldVariant')); - }, - ); + void it('preserves existing AB participations when url already has xcust', () => { + const xcustResult = buildXcustParamForAffiliateLink({ + url: new URL( + 'https://go.skimresources.com/?id=1111&url=https%3A%2F%2Fwww.theguardian.com%2Fus-news&xcust=referrer%7Cold.example%7CaccountId%7C1111%7CabTestParticipations%7ColdTest%3AoldVariant', + ), + abTestParticipations: { newTest: 'newVariant' }, + utmParamsString: '', + referrerDomain: 'www.theguardian.com', + xcustComponentId: null, + }); + + assert.ok( + xcustResult.includes('referrer|www.theguardian.com|accountId|1111'), + ); + assert.ok(xcustResult.includes('newTest:newVariant')); + assert.ok(xcustResult.includes('oldTest:oldVariant')); + }); }); -void nodeDescribe('buildMergedAbTestString', () => { - void nodeIt( - 'returns incoming AB test string when URL has no existing participations', - () => { - const url = - 'https://go.skimresources.com/?id=1111&url=https%3A%2F%2Fwww.theguardian.com%2Fus-news'; - - assert.equal( - buildMergedAbTestString({ - url, - abTestParticipations: { - abTest1: 'variantA', - abTest2: 'variantB', - }, - }), - 'abTest1:variantA,abTest2:variantB', - ); - }, - ); - - void nodeIt('keeps existing URL values when keys collide', () => { +void describe('buildMergedAbTestString', () => { + void it('returns incoming AB test string when URL has no existing participations', () => { + const url = + 'https://go.skimresources.com/?id=1111&url=https%3A%2F%2Fwww.theguardian.com%2Fus-news'; + + assert.equal( + buildMergedAbTestString({ + url, + abTestParticipations: { + abTest1: 'variantA', + abTest2: 'variantB', + }, + }), + 'abTest1:variantA,abTest2:variantB', + ); + }); + + void it('keeps existing URL values when keys collide', () => { const url = 'https://go.skimresources.com/?id=1111&url=https%3A%2F%2Fwww.theguardian.com%2Fus-news&xcust=referrer%7Cwww.theguardian.com%7CaccountId%7C1111%7CabTestParticipations%7ColdTest%3AoldVariant'; diff --git a/dotcom-rendering/src/lib/age-warning.node.test.ts b/dotcom-rendering/src/lib/age-warning.node.test.ts index 92a3f01f7bf..e3d299e2bac 100644 --- a/dotcom-rendering/src/lib/age-warning.node.test.ts +++ b/dotcom-rendering/src/lib/age-warning.node.test.ts @@ -1,9 +1,9 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import type { TagType } from '../types/tag'; import { getAgeWarning } from './age-warning'; -void nodeDescribe('getAgeWarning', () => { +void describe('getAgeWarning', () => { const infoTag: TagType = { id: 'info/info', type: 'info', @@ -29,53 +29,29 @@ void nodeDescribe('getAgeWarning', () => { new Date().setDate(today.getDate() - 750), ).toDateString(); - void nodeIt( - 'shows age warning when publication date is more than 1 month ago', - () => { - assert.equal( - getAgeWarning([studentsTag], oneMonthOld), - '1 month old', - ); - }, - ); + void it('shows age warning when publication date is more than 1 month ago', () => { + assert.equal(getAgeWarning([studentsTag], oneMonthOld), '1 month old'); + }); - void nodeIt( - 'shows age warning when publication date is more than 2 months ago', - () => { - assert.equal( - getAgeWarning([studentsTag], twoMonthsOld), - '2 months old', - ); - }, - ); + void it('shows age warning when publication date is more than 2 months ago', () => { + assert.equal( + getAgeWarning([studentsTag], twoMonthsOld), + '2 months old', + ); + }); - void nodeIt( - 'shows age warning when publication date is more than 1 year ago', - () => { - assert.equal( - getAgeWarning([studentsTag], oneYearOld), - '1 year old', - ); - }, - ); + void it('shows age warning when publication date is more than 1 year ago', () => { + assert.equal(getAgeWarning([studentsTag], oneYearOld), '1 year old'); + }); - void nodeIt( - 'shows age warning when publication date is more than 2 years ago', - () => { - assert.equal( - getAgeWarning([studentsTag], twoYearsOld), - '2 years old', - ); - }, - ); + void it('shows age warning when publication date is more than 2 years ago', () => { + assert.equal(getAgeWarning([studentsTag], twoYearsOld), '2 years old'); + }); - void nodeIt( - 'is undefined if one of the tags is excluded from age warning', - () => { - assert.equal( - getAgeWarning([studentsTag, infoTag], oneMonthOld), - undefined, - ); - }, - ); + void it('is undefined if one of the tags is excluded from age warning', () => { + assert.equal( + getAgeWarning([studentsTag, infoTag], oneMonthOld), + undefined, + ); + }); }); diff --git a/dotcom-rendering/src/lib/alternate-lang-links.node.test.ts b/dotcom-rendering/src/lib/alternate-lang-links.node.test.ts index 0b0cee78198..69371d1311d 100644 --- a/dotcom-rendering/src/lib/alternate-lang-links.node.test.ts +++ b/dotcom-rendering/src/lib/alternate-lang-links.node.test.ts @@ -1,5 +1,5 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { generateAlternateLangLinks } from './alternate-lang-links'; import { editionalisedPages, editionList } from './edition'; @@ -23,42 +23,36 @@ const everyEditionWithNoEditionalisedPages = editionList editionalisedPages.map((page) => `${edition.pageId}/${page}`), ); -void nodeDescribe('alternate lang links', () => { - void nodeIt( - 'generate hreflang links for network fronts with a lang locale', - () => { - for (const edition of everyEditionWithLangLocale) { - const langLinks = generateAlternateLangLinks( +void describe('alternate lang links', () => { + void it('generate hreflang links for network fronts with a lang locale', () => { + for (const edition of everyEditionWithLangLocale) { + const langLinks = generateAlternateLangLinks( + 'https://www.theguardian.com', + edition.pageId, + ); + assert.deepEqual(langLinks, [ + '', + '', + '', + '', + '', + ]); + } + }); + + void it('do NOT generate hreflang links for network fronts with NO lang locale', () => { + for (const edition of everyEditionWithNoLangLocale) { + assert.deepEqual( + generateAlternateLangLinks( 'https://www.theguardian.com', edition.pageId, - ); - assert.deepEqual(langLinks, [ - '', - '', - '', - '', - '', - ]); - } - }, - ); - - void nodeIt( - 'do NOT generate hreflang links for network fronts with NO lang locale', - () => { - for (const edition of everyEditionWithNoLangLocale) { - assert.deepEqual( - generateAlternateLangLinks( - 'https://www.theguardian.com', - edition.pageId, - ), - [], - ); - } - }, - ); + ), + [], + ); + } + }); - void nodeIt('generate hreflang links for editionalised pages', () => { + void it('generate hreflang links for editionalised pages', () => { for (const pageId of everyEditionWithEditionalisedPages) { const langLinks = generateAlternateLangLinks( 'https://www.theguardian.com', @@ -73,40 +67,34 @@ void nodeDescribe('alternate lang links', () => { } }); - void nodeIt( - 'do NOT generate hreflang links for editions with NO editionalised pages', - () => { - for (const pageId of everyEditionWithNoEditionalisedPages) { - assert.deepEqual( - generateAlternateLangLinks( - 'https://www.theguardian.com', - pageId, - ), - [], - ); - } - }, - ); + void it('do NOT generate hreflang links for editions with NO editionalised pages', () => { + for (const pageId of everyEditionWithNoEditionalisedPages) { + assert.deepEqual( + generateAlternateLangLinks( + 'https://www.theguardian.com', + pageId, + ), + [], + ); + } + }); - void nodeIt( - 'do NOT generate hreflang links for NON editionalised pages', - () => { - const pageIdsNotEditionalisedPages = [ - 'uk/something', - 'us/something', - 'au/something', - 'international/something', - 'uk/business/something', - ]; - for (const pageId of pageIdsNotEditionalisedPages) { - assert.deepEqual( - generateAlternateLangLinks( - 'https://www.theguardian.com', - pageId, - ), - [], - ); - } - }, - ); + void it('do NOT generate hreflang links for NON editionalised pages', () => { + const pageIdsNotEditionalisedPages = [ + 'uk/something', + 'us/something', + 'au/something', + 'international/something', + 'uk/business/something', + ]; + for (const pageId of pageIdsNotEditionalisedPages) { + assert.deepEqual( + generateAlternateLangLinks( + 'https://www.theguardian.com', + pageId, + ), + [], + ); + } + }); }); diff --git a/dotcom-rendering/src/lib/articleMeta.node.test.ts b/dotcom-rendering/src/lib/articleMeta.node.test.ts index 1ff85ba1a8d..1d7a38dfbfe 100644 --- a/dotcom-rendering/src/lib/articleMeta.node.test.ts +++ b/dotcom-rendering/src/lib/articleMeta.node.test.ts @@ -1,9 +1,9 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { ArticleDesign, ArticleDisplay, Pillar } from './articleFormat'; import { shouldShowContributor } from './articleMeta'; -void nodeDescribe('shouldShowContributor', () => { +void describe('shouldShowContributor', () => { const standardFormat = { theme: Pillar.News, design: ArticleDesign.Standard, @@ -31,46 +31,31 @@ void nodeDescribe('shouldShowContributor', () => { display: ArticleDisplay.Immersive, }; - void nodeIt( - 'should return true if Standard display and Standard design', - () => { - assert.equal(shouldShowContributor(standardFormat), true); - }, - ); + void it('should return true if Standard display and Standard design', () => { + assert.equal(shouldShowContributor(standardFormat), true); + }); - void nodeIt( - 'should return false if Standard display and Comment design', - () => { - assert.equal(shouldShowContributor(standardComment), false); - }, - ); + void it('should return false if Standard display and Comment design', () => { + assert.equal(shouldShowContributor(standardComment), false); + }); - void nodeIt( - 'should return true if Showcase display and Standard design', - () => { - assert.equal(shouldShowContributor(showcaseStandard), true); - }, - ); + void it('should return true if Showcase display and Standard design', () => { + assert.equal(shouldShowContributor(showcaseStandard), true); + }); - void nodeIt( - 'should return false if Showcase display and Comment design', - () => { - assert.equal(shouldShowContributor(showcaseComment), false); - }, - ); + void it('should return false if Showcase display and Comment design', () => { + assert.equal(shouldShowContributor(showcaseComment), false); + }); - void nodeIt('should return true if Numbered list display', () => { + void it('should return true if Numbered list display', () => { assert.equal(shouldShowContributor(numberedList), true); }); - void nodeIt('should return false if Immersive display', () => { + void it('should return false if Immersive display', () => { assert.equal(shouldShowContributor(immersive), false); }); - void nodeIt( - 'should return true if Immersive display uses the new grid', - () => { - assert.equal(shouldShowContributor(immersive, true), true); - }, - ); + void it('should return true if Immersive display uses the new grid', () => { + assert.equal(shouldShowContributor(immersive, true), true); + }); }); diff --git a/dotcom-rendering/src/lib/branding.node.test.ts b/dotcom-rendering/src/lib/branding.node.test.ts index a197d28314b..e100bf4d06b 100644 --- a/dotcom-rendering/src/lib/branding.node.test.ts +++ b/dotcom-rendering/src/lib/branding.node.test.ts @@ -1,5 +1,5 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import type { Branding } from '../types/branding'; import { decideCollectionBranding, decideTagPageBranding } from './branding'; @@ -18,8 +18,8 @@ const assertMatchObject = (actual: unknown, expected: unknown): void => { } }; -void nodeDescribe('decideCollectionBranding', () => { - void nodeIt('picks branding from a card by their edition', () => { +void describe('decideCollectionBranding', () => { + void it('picks branding from a card by their edition', () => { const cards = [ { properties: { @@ -86,7 +86,7 @@ void nodeDescribe('decideCollectionBranding', () => { }); }); - void nodeIt('is paid content derived from multiple cards', () => { + void it('is paid content derived from multiple cards', () => { const cardBranding = { brandingType: { name: 'paid-content' as const }, sponsorName: 'foo', @@ -138,7 +138,7 @@ void nodeDescribe('decideCollectionBranding', () => { }); }); - void nodeIt('undefined when not all cards have branding', () => { + void it('undefined when not all cards have branding', () => { // The branding we'll apply to each card in this test const collectionBranding = decideCollectionBranding({ frontBranding: undefined, @@ -186,7 +186,7 @@ void nodeDescribe('decideCollectionBranding', () => { assert.equal(collectionBranding, undefined); }); - void nodeIt('is undefined when no cards have branding', () => { + void it('is undefined when no cards have branding', () => { const collectionBranding = decideCollectionBranding({ frontBranding: undefined, couldDisplayFrontBranding: false, @@ -213,7 +213,7 @@ void nodeDescribe('decideCollectionBranding', () => { assert.equal(collectionBranding, undefined); }); - void nodeIt('is undefined when cards have different branding types', () => { + void it('is undefined when cards have different branding types', () => { const collectionBranding = decideCollectionBranding({ frontBranding: undefined, couldDisplayFrontBranding: false, @@ -270,286 +270,268 @@ void nodeDescribe('decideCollectionBranding', () => { assert.equal(collectionBranding, undefined); }); - void nodeIt( - 'is sponsored branding when all of the branding types are sponsored and the names match', - () => { - const cardBranding = { - brandingType: { name: 'sponsored' as const }, - sponsorName: 'foo', - aboutThisLink: '', - logo, - }; - const collectionBranding = decideCollectionBranding({ - frontBranding: undefined, - couldDisplayFrontBranding: false, - cards: [ - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: cardBranding, - }, - ], - }, + void it('is sponsored branding when all of the branding types are sponsored and the names match', () => { + const cardBranding = { + brandingType: { name: 'sponsored' as const }, + sponsorName: 'foo', + aboutThisLink: '', + logo, + }; + const collectionBranding = decideCollectionBranding({ + frontBranding: undefined, + couldDisplayFrontBranding: false, + cards: [ + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: cardBranding, + }, + ], }, - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: cardBranding, - }, - ], - }, + }, + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: cardBranding, + }, + ], }, - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: cardBranding, - }, - ], - }, + }, + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: cardBranding, + }, + ], }, - ], - editionId: 'UK', - isContainerBranding: false, - }); - assert.deepEqual(collectionBranding, { - kind: 'sponsored', - isFrontBranding: false, - branding: cardBranding, - isContainerBranding: false, - hasMultipleBranding: false, - }); - }, - ); + }, + ], + editionId: 'UK', + isContainerBranding: false, + }); + assert.deepEqual(collectionBranding, { + kind: 'sponsored', + isFrontBranding: false, + branding: cardBranding, + isContainerBranding: false, + hasMultipleBranding: false, + }); + }); - void nodeIt( - 'is undefined when branding cards are sponsored and have different sponsor names', - () => { - // The branding we'll apply to each card in this test - const collectionBranding = decideCollectionBranding({ - frontBranding: undefined, - couldDisplayFrontBranding: false, - cards: [ - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: { - brandingType: { name: 'sponsored' }, - sponsorName: 'foo', - aboutThisLink: '', - logo, - }, + void it('is undefined when branding cards are sponsored and have different sponsor names', () => { + // The branding we'll apply to each card in this test + const collectionBranding = decideCollectionBranding({ + frontBranding: undefined, + couldDisplayFrontBranding: false, + cards: [ + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: { + brandingType: { name: 'sponsored' }, + sponsorName: 'foo', + aboutThisLink: '', + logo, }, - ], - }, + }, + ], }, - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: { - brandingType: { name: 'sponsored' }, - sponsorName: 'bar', - aboutThisLink: '', - logo, - }, + }, + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: { + brandingType: { name: 'sponsored' }, + sponsorName: 'bar', + aboutThisLink: '', + logo, }, - ], - }, + }, + ], }, - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: { - brandingType: { name: 'sponsored' }, - sponsorName: 'baz', - aboutThisLink: '', - logo, - }, + }, + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: { + brandingType: { name: 'sponsored' }, + sponsorName: 'baz', + aboutThisLink: '', + logo, }, - ], - }, + }, + ], }, - ], - editionId: 'UK', - isContainerBranding: false, - }); - assert.equal(collectionBranding, undefined); - }, - ); + }, + ], + editionId: 'UK', + isContainerBranding: false, + }); + assert.equal(collectionBranding, undefined); + }); - void nodeIt( - 'is paid content branding when all of the branding types are paid-content and the names match', - () => { - const collectionBranding = decideCollectionBranding({ - frontBranding: undefined, - couldDisplayFrontBranding: false, - cards: [ - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'foo', - aboutThisLink: '', - logo, - }, + void it('is paid content branding when all of the branding types are paid-content and the names match', () => { + const collectionBranding = decideCollectionBranding({ + frontBranding: undefined, + couldDisplayFrontBranding: false, + cards: [ + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: { + brandingType: { name: 'paid-content' }, + sponsorName: 'foo', + aboutThisLink: '', + logo, }, - ], - }, + }, + ], }, - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'foo', - aboutThisLink: '', - logo, - }, + }, + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: { + brandingType: { name: 'paid-content' }, + sponsorName: 'foo', + aboutThisLink: '', + logo, }, - ], - }, + }, + ], }, - ], - editionId: 'UK', - isContainerBranding: false, - }); - assert.deepEqual(collectionBranding, { - kind: 'paid-content', - isFrontBranding: false, - branding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'foo', - aboutThisLink: '', - logo, }, - isContainerBranding: false, - hasMultipleBranding: false, - }); - }, - ); + ], + editionId: 'UK', + isContainerBranding: false, + }); + assert.deepEqual(collectionBranding, { + kind: 'paid-content', + isFrontBranding: false, + branding: { + brandingType: { name: 'paid-content' }, + sponsorName: 'foo', + aboutThisLink: '', + logo, + }, + isContainerBranding: false, + hasMultipleBranding: false, + }); + }); - void nodeIt( - 'is paid content multiple branding when branding cards are paid-content and have different sponsor names', - () => { - const collectionBranding = decideCollectionBranding({ - frontBranding: undefined, - couldDisplayFrontBranding: false, - cards: [ - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'foo', - aboutThisLink: '', - logo, - }, + void it('is paid content multiple branding when branding cards are paid-content and have different sponsor names', () => { + const collectionBranding = decideCollectionBranding({ + frontBranding: undefined, + couldDisplayFrontBranding: false, + cards: [ + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: { + brandingType: { name: 'paid-content' }, + sponsorName: 'foo', + aboutThisLink: '', + logo, }, - ], - }, + }, + ], }, - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'bar', - aboutThisLink: '', - logo, - }, + }, + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: { + brandingType: { name: 'paid-content' }, + sponsorName: 'bar', + aboutThisLink: '', + logo, }, - ], - }, + }, + ], }, - ], - editionId: 'UK', - isContainerBranding: false, - }); - assert.deepEqual(collectionBranding, { - kind: 'paid-content', - isFrontBranding: false, - branding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'foo', - aboutThisLink: '', - logo, }, - isContainerBranding: false, - hasMultipleBranding: true, - }); - }, - ); + ], + editionId: 'UK', + isContainerBranding: false, + }); + assert.deepEqual(collectionBranding, { + kind: 'paid-content', + isFrontBranding: false, + branding: { + brandingType: { name: 'paid-content' }, + sponsorName: 'foo', + aboutThisLink: '', + logo, + }, + isContainerBranding: false, + hasMultipleBranding: true, + }); + }); - void nodeIt( - 'is front branding when present and possible to display', - () => { - const collectionBranding = decideCollectionBranding({ - frontBranding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'bar', - aboutThisLink: '', - logo, - }, - couldDisplayFrontBranding: true, - cards: [], - editionId: 'UK', - isContainerBranding: false, - }); - assert.deepEqual(collectionBranding, { - kind: 'paid-content', - isFrontBranding: true, - branding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'bar', - aboutThisLink: '', - logo, - }, - isContainerBranding: false, - hasMultipleBranding: false, - }); - }, - ); + void it('is front branding when present and possible to display', () => { + const collectionBranding = decideCollectionBranding({ + frontBranding: { + brandingType: { name: 'paid-content' }, + sponsorName: 'bar', + aboutThisLink: '', + logo, + }, + couldDisplayFrontBranding: true, + cards: [], + editionId: 'UK', + isContainerBranding: false, + }); + assert.deepEqual(collectionBranding, { + kind: 'paid-content', + isFrontBranding: true, + branding: { + brandingType: { name: 'paid-content' }, + sponsorName: 'bar', + aboutThisLink: '', + logo, + }, + isContainerBranding: false, + hasMultipleBranding: false, + }); + }); - void nodeIt( - 'is undefined when there is front branding (and no card branding) that is not eligible for display on this collection', - () => { - const collectionBranding = decideCollectionBranding({ - frontBranding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'bar', - aboutThisLink: '', - logo, - }, - couldDisplayFrontBranding: false, - cards: [], - editionId: 'UK', - isContainerBranding: false, - }); - assert.equal(collectionBranding, undefined); - }, - ); + void it('is undefined when there is front branding (and no card branding) that is not eligible for display on this collection', () => { + const collectionBranding = decideCollectionBranding({ + frontBranding: { + brandingType: { name: 'paid-content' }, + sponsorName: 'bar', + aboutThisLink: '', + logo, + }, + couldDisplayFrontBranding: false, + cards: [], + editionId: 'UK', + isContainerBranding: false, + }); + assert.equal(collectionBranding, undefined); + }); - void nodeIt('when cards are present', () => { + void it('when cards are present', () => { const cardBranding = { brandingType: { name: 'paid-content' as const }, sponsorName: 'foo', @@ -608,65 +590,62 @@ void nodeDescribe('decideCollectionBranding', () => { }); }); - void nodeIt( - 'is undefined when front branding matches card branding, but we are not displaying front branding', - () => { - const cardBranding = { - brandingType: { name: 'paid-content' as const }, + void it('is undefined when front branding matches card branding, but we are not displaying front branding', () => { + const cardBranding = { + brandingType: { name: 'paid-content' as const }, + sponsorName: 'foo', + aboutThisLink: '', + logo, + }; + const collectionBranding = decideCollectionBranding({ + frontBranding: { + brandingType: { name: 'paid-content' }, sponsorName: 'foo', aboutThisLink: '', logo, - }; - const collectionBranding = decideCollectionBranding({ - frontBranding: { - brandingType: { name: 'paid-content' }, - sponsorName: 'foo', - aboutThisLink: '', - logo, - }, - couldDisplayFrontBranding: false, - cards: [ - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: cardBranding, - }, - ], - }, + }, + couldDisplayFrontBranding: false, + cards: [ + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: cardBranding, + }, + ], }, - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: cardBranding, - }, - ], - }, + }, + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: cardBranding, + }, + ], }, - { - properties: { - editionBrandings: [ - { - edition: { id: 'UK' }, - branding: cardBranding, - }, - ], - }, + }, + { + properties: { + editionBrandings: [ + { + edition: { id: 'UK' }, + branding: cardBranding, + }, + ], }, - ], - editionId: 'UK', - isContainerBranding: false, - }); - assert.equal(collectionBranding, undefined); - }, - ); + }, + ], + editionId: 'UK', + isContainerBranding: false, + }); + assert.equal(collectionBranding, undefined); + }); }); -void nodeDescribe('decideTagPageBranding', () => { - void nodeIt('picks branding from a tag page by their edition', () => { +void describe('decideTagPageBranding', () => { + void it('picks branding from a tag page by their edition', () => { const branding = { brandingType: { name: 'sponsored' }, sponsorName: 'Guardian.org', @@ -690,19 +669,16 @@ void nodeDescribe('decideTagPageBranding', () => { hasMultipleBranding: false, }); }); - void nodeIt( - 'is undefined when branding does not have a brandingType name present', - () => { - const branding = { - sponsorName: 'Guardian.org', - aboutThisLink: '', - logo, - }; + void it('is undefined when branding does not have a brandingType name present', () => { + const branding = { + sponsorName: 'Guardian.org', + aboutThisLink: '', + logo, + }; - const tagPageBranding = decideTagPageBranding({ - branding, - }); - assert.equal(tagPageBranding, undefined); - }, - ); + const tagPageBranding = decideTagPageBranding({ + branding, + }); + assert.equal(tagPageBranding, undefined); + }); }); diff --git a/dotcom-rendering/src/lib/byline.node.test.ts b/dotcom-rendering/src/lib/byline.node.test.ts index 8495b335746..e6751504d2c 100644 --- a/dotcom-rendering/src/lib/byline.node.test.ts +++ b/dotcom-rendering/src/lib/byline.node.test.ts @@ -1,98 +1,85 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { getBylineComponentsFromTokens, getSoleContributor } from './byline'; -void nodeDescribe('Byline utilities', () => { - void nodeIt( - 'should link a single tag by linking name tokens with Contributor tag titles', - () => { - const bylineTokens = ['Eva Smith', 'and friends']; - const tags = [ - { - id: 'eva-smith', - type: 'Contributor', - title: 'Eva Smith', - }, - ]; +void describe('Byline utilities', () => { + void it('should link a single tag by linking name tokens with Contributor tag titles', () => { + const bylineTokens = ['Eva Smith', 'and friends']; + const tags = [ + { + id: 'eva-smith', + type: 'Contributor', + title: 'Eva Smith', + }, + ]; - const bylineComponents = getBylineComponentsFromTokens( - bylineTokens, - tags, - ); + const bylineComponents = getBylineComponentsFromTokens( + bylineTokens, + tags, + ); - assert.deepEqual(bylineComponents, [ - { tag: tags[0], token: 'Eva Smith' }, - 'and friends', - ]); - }, - ); + assert.deepEqual(bylineComponents, [ + { tag: tags[0], token: 'Eva Smith' }, + 'and friends', + ]); + }); - void nodeIt( - 'should link multiple tags by linking name tokens with Contributor tag titles', - () => { - const bylineTokens = ['Eva Smith', ' and ', 'Duncan Campbell']; - const tags = [ - { - id: 'eva-smith', - type: 'Contributor', - title: 'Eva Smith', - }, - { - id: 'duncan-campbell', - type: 'Contributor', - title: 'Duncan Campbell', - }, - ]; - const bylineComponents = getBylineComponentsFromTokens( - bylineTokens, - tags, - ); + void it('should link multiple tags by linking name tokens with Contributor tag titles', () => { + const bylineTokens = ['Eva Smith', ' and ', 'Duncan Campbell']; + const tags = [ + { + id: 'eva-smith', + type: 'Contributor', + title: 'Eva Smith', + }, + { + id: 'duncan-campbell', + type: 'Contributor', + title: 'Duncan Campbell', + }, + ]; + const bylineComponents = getBylineComponentsFromTokens( + bylineTokens, + tags, + ); - assert.deepEqual(bylineComponents, [ - { tag: tags[0], token: 'Eva Smith' }, - ' and ', - { tag: tags[1], token: 'Duncan Campbell' }, - ]); - }, - ); + assert.deepEqual(bylineComponents, [ + { tag: tags[0], token: 'Eva Smith' }, + ' and ', + { tag: tags[1], token: 'Duncan Campbell' }, + ]); + }); - void nodeIt( - 'should not reuse a contributor tag, to successfully disambiguate identical names', - () => { - const bylineTokens = [ - 'Duncan Campbell', - ' and ', - 'Duncan Campbell', - ]; - const tags = [ - { - id: 'duncan-campbell', - type: 'Contributor', - title: 'Duncan Campbell', - }, - { - id: 'duncan-campbell-1', - type: 'Contributor', - title: 'Duncan Campbell', - }, - ]; + void it('should not reuse a contributor tag, to successfully disambiguate identical names', () => { + const bylineTokens = ['Duncan Campbell', ' and ', 'Duncan Campbell']; + const tags = [ + { + id: 'duncan-campbell', + type: 'Contributor', + title: 'Duncan Campbell', + }, + { + id: 'duncan-campbell-1', + type: 'Contributor', + title: 'Duncan Campbell', + }, + ]; - const bylineComponents = getBylineComponentsFromTokens( - bylineTokens, - tags, - ); + const bylineComponents = getBylineComponentsFromTokens( + bylineTokens, + tags, + ); - assert.deepEqual(bylineComponents, [ - { tag: tags[0], token: 'Duncan Campbell' }, - ' and ', - { tag: tags[1], token: 'Duncan Campbell' }, - ]); - }, - ); + assert.deepEqual(bylineComponents, [ + { tag: tags[0], token: 'Duncan Campbell' }, + ' and ', + { tag: tags[1], token: 'Duncan Campbell' }, + ]); + }); - void nodeDescribe('getSoleContributor', () => { - void nodeDescribe('returns a contributor', () => { - void nodeIt('Sebastian Köhn, as told to Wilfried Chan', () => { + void describe('getSoleContributor', () => { + void describe('returns a contributor', () => { + void it('Sebastian Köhn, as told to Wilfried Chan', () => { // https://www.theguardian.com/world/2022/jul/23/i-literally-screamed-out-loud-in-pain-my-two-weeks-of-monkeypox-hell const soleContributor = getSoleContributor( @@ -109,7 +96,7 @@ void nodeDescribe('Byline utilities', () => { assert.equal(soleContributor?.title, 'Wilfred Chan'); }); - void nodeIt('Jim Waterson Media editor', () => { + void it('Jim Waterson Media editor', () => { // https://www.theguardian.com/media/2021/nov/17/geordie-greig-ousted-as-editor-of-the-daily-mail const soleContributor = getSoleContributor( @@ -134,7 +121,7 @@ void nodeDescribe('Byline utilities', () => { assert.equal(soleContributor?.title, 'Jim Waterson'); }); - void nodeIt('First Dog on the Moon', () => { + void it('First Dog on the Moon', () => { // https://www.theguardian.com/commentisfree/2022/jul/22/europe-is-ablaze-italian-glaciers-are-collapsing-the-climate-crisis-is-here const soleContributor = getSoleContributor( @@ -151,7 +138,7 @@ void nodeDescribe('Byline utilities', () => { assert.equal(soleContributor?.title, 'First Dog on the Moon'); }); - void nodeIt('Sam Levine in New York', () => { + void it('Sam Levine in New York', () => { // https://www.theguardian.com/us-news/2022/jul/22/january-6-panel-american-democracy-nose-dive const soleContributor = getSoleContributor( @@ -169,33 +156,30 @@ void nodeDescribe('Byline utilities', () => { }); }); - void nodeDescribe('returns `undefined`', () => { - void nodeIt( - 'Sam Levin in Los Angeles and Sam Levine in New York', - () => { - // https://www.theguardian.com/us-news/2020/oct/12/republicans-election-2020-unauthorized-ballot-boxes + void describe('returns `undefined`', () => { + void it('Sam Levin in Los Angeles and Sam Levine in New York', () => { + // https://www.theguardian.com/us-news/2020/oct/12/republicans-election-2020-unauthorized-ballot-boxes - const soleContributor = getSoleContributor( - [ - { - id: 'profile/sam-levin', - type: 'Contributor', - title: 'Sam Levin', - twitterHandle: 'SamTLevin', - }, - { - id: 'profile/sam-levine', - type: 'Contributor', - title: 'Sam Levine', - }, - ], - 'Sam Levin in Los Angeles and Sam Levine in New York', - ); - assert.equal(soleContributor, undefined); - }, - ); + const soleContributor = getSoleContributor( + [ + { + id: 'profile/sam-levin', + type: 'Contributor', + title: 'Sam Levin', + twitterHandle: 'SamTLevin', + }, + { + id: 'profile/sam-levine', + type: 'Contributor', + title: 'Sam Levine', + }, + ], + 'Sam Levin in Los Angeles and Sam Levine in New York', + ); + assert.equal(soleContributor, undefined); + }); - void nodeIt('Gabriel Smith', () => { + void it('Gabriel Smith', () => { const soleContributor = getSoleContributor( [ { @@ -210,7 +194,7 @@ void nodeDescribe('Byline utilities', () => { assert.equal(soleContributor, undefined); }); - void nodeIt('Zoe Williams and others', () => { + void it('Zoe Williams and others', () => { // https://www.theguardian.com/commentisfree/2022/jul/20/britain-next-prime-minister-rishi-sunak-liz-truss-conservative-leader const soleContributor = getSoleContributor( @@ -248,42 +232,39 @@ void nodeDescribe('Byline utilities', () => { assert.equal(soleContributor, undefined); }); - void nodeIt( - 'Paul MacInnes, Nesrine Malik, Julie Bindel, Peter Preston', - () => { - // https://www.theguardian.com/commentisfree/2011/dec/30/person-of-2011-writers-verdict + void it('Paul MacInnes, Nesrine Malik, Julie Bindel, Peter Preston', () => { + // https://www.theguardian.com/commentisfree/2011/dec/30/person-of-2011-writers-verdict - const soleContributor = getSoleContributor( - [ - { - id: 'profile/paulmacinnes', - type: 'Contributor', - title: 'Paul MacInnes', - twitterHandle: 'PaulMac', - }, - { - id: 'profile/peterpreston', - type: 'Contributor', - title: 'Peter Preston', - }, - { - id: 'profile/nesrinemalik', - type: 'Contributor', - title: 'Nesrine Malik', - }, - { - id: 'profile/juliebindel', - type: 'Contributor', - title: 'Julie Bindel', - twitterHandle: 'bindelj', - }, - ], - 'Paul MacInnes, Nesrine Malik, Julie Bindel, Peter Preston', - ); + const soleContributor = getSoleContributor( + [ + { + id: 'profile/paulmacinnes', + type: 'Contributor', + title: 'Paul MacInnes', + twitterHandle: 'PaulMac', + }, + { + id: 'profile/peterpreston', + type: 'Contributor', + title: 'Peter Preston', + }, + { + id: 'profile/nesrinemalik', + type: 'Contributor', + title: 'Nesrine Malik', + }, + { + id: 'profile/juliebindel', + type: 'Contributor', + title: 'Julie Bindel', + twitterHandle: 'bindelj', + }, + ], + 'Paul MacInnes, Nesrine Malik, Julie Bindel, Peter Preston', + ); - assert.equal(soleContributor, undefined); - }, - ); + assert.equal(soleContributor, undefined); + }); }); }); }); diff --git a/dotcom-rendering/src/lib/canRenderAds.node.test.ts b/dotcom-rendering/src/lib/canRenderAds.node.test.ts index 724ecbd173d..72a0e61b5ea 100644 --- a/dotcom-rendering/src/lib/canRenderAds.node.test.ts +++ b/dotcom-rendering/src/lib/canRenderAds.node.test.ts @@ -1,24 +1,24 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { Standard } from '../../fixtures/generated/fe-articles/Standard'; import { enhanceArticleType } from '../types/article'; import { canRenderAds } from './canRenderAds'; const standardPage = enhanceArticleType(Standard, 'Web'); -void nodeDescribe('canRenderAds', () => { - void nodeIt('shows ads by default', () => { +void describe('canRenderAds', () => { + void it('shows ads by default', () => { assert.equal(canRenderAds(standardPage.frontendData), true); }); - void nodeIt('does not show ads if user is ad-free', () => { + void it('does not show ads if user is ad-free', () => { const adFreePage = Object.assign({}, standardPage.frontendData); adFreePage.isAdFreeUser = true; assert.equal(canRenderAds(adFreePage), false); }); - void nodeIt('does not show ads if page should not display them', () => { + void it('does not show ads if page should not display them', () => { const adFreePage = Object.assign({}, standardPage.frontendData); adFreePage.shouldHideAds = true; diff --git a/dotcom-rendering/src/lib/cardHelpers.node.test.ts b/dotcom-rendering/src/lib/cardHelpers.node.test.ts index a3e9edc4f2e..ec6dbef7e3f 100644 --- a/dotcom-rendering/src/lib/cardHelpers.node.test.ts +++ b/dotcom-rendering/src/lib/cardHelpers.node.test.ts @@ -1,9 +1,9 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import type { DCRContainerPalette } from '../types/front'; import { cardHasDarkBackground } from './cardHelpers'; -void nodeDescribe('cardHasDarkBackground', () => { +void describe('cardHasDarkBackground', () => { const testCases = [ { containerPalette: undefined, @@ -35,14 +35,11 @@ void nodeDescribe('cardHasDarkBackground', () => { }[]; for (const { containerPalette, expectedResult } of testCases) { - void nodeIt( - `returns ${expectedResult} for $format format, ${containerPalette} containerPalette`, - () => { - assert.equal( - cardHasDarkBackground(containerPalette), - expectedResult, - ); - }, - ); + void it(`returns ${expectedResult} for $format format, ${containerPalette} containerPalette`, () => { + assert.equal( + cardHasDarkBackground(containerPalette), + expectedResult, + ); + }); } }); diff --git a/dotcom-rendering/src/lib/decide-cation.node.test.ts b/dotcom-rendering/src/lib/decide-cation.node.test.ts index acc9991d1c2..43da6caf249 100644 --- a/dotcom-rendering/src/lib/decide-cation.node.test.ts +++ b/dotcom-rendering/src/lib/decide-cation.node.test.ts @@ -1,5 +1,5 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import type { EmbedBlockElement, ImageBlockElement, @@ -7,12 +7,12 @@ import type { } from '../types/content'; import { decideMainMediaCaption } from './decide-caption'; -void nodeDescribe('decideMainMediaCaption', () => { - void nodeDescribe('when mainMedia is not supported', () => { - void nodeIt('undefined returns an empty string', () => { +void describe('decideMainMediaCaption', () => { + void describe('when mainMedia is not supported', () => { + void it('undefined returns an empty string', () => { assert.deepEqual(decideMainMediaCaption(undefined), ''); }); - void nodeIt('a text block returns an empty string', () => { + void it('a text block returns an empty string', () => { assert.deepEqual( decideMainMediaCaption({ elementId: 'test-id', @@ -24,7 +24,7 @@ void nodeDescribe('decideMainMediaCaption', () => { }); }); - void nodeDescribe('ImageBlockElement', () => { + void describe('ImageBlockElement', () => { const mockImageBlockElement = { elementId: 'mock-element-id', data: {}, @@ -32,17 +32,11 @@ void nodeDescribe('decideMainMediaCaption', () => { _type: 'model.dotcomrendering.pageElements.ImageBlockElement', } as ImageBlockElement; - void nodeIt( - 'returns an empty string if there is no caption, displayCredit, or credit', - () => { - assert.deepEqual( - decideMainMediaCaption(mockImageBlockElement), - '', - ); - }, - ); + void it('returns an empty string if there is no caption, displayCredit, or credit', () => { + assert.deepEqual(decideMainMediaCaption(mockImageBlockElement), ''); + }); - void nodeIt('includes the caption, if it exists', () => { + void it('includes the caption, if it exists', () => { assert.deepEqual( decideMainMediaCaption({ ...mockImageBlockElement, @@ -54,7 +48,7 @@ void nodeDescribe('decideMainMediaCaption', () => { ); }); - void nodeIt('includes the credit, if it should be displayed', () => { + void it('includes the credit, if it should be displayed', () => { assert.deepEqual( decideMainMediaCaption({ ...mockImageBlockElement, @@ -67,42 +61,36 @@ void nodeDescribe('decideMainMediaCaption', () => { ); }); - void nodeIt( - 'does not include the credit, if it should not be displayed', - () => { - assert.deepEqual( - decideMainMediaCaption({ - ...mockImageBlockElement, - displayCredit: false, - data: { - credit: 'image block display credit', - }, - }), - '', - ); - }, - ); + void it('does not include the credit, if it should not be displayed', () => { + assert.deepEqual( + decideMainMediaCaption({ + ...mockImageBlockElement, + displayCredit: false, + data: { + credit: 'image block display credit', + }, + }), + '', + ); + }); - void nodeIt( - 'includes both the credit and caption, if they exist', - () => { - assert.deepEqual( - decideMainMediaCaption({ - ...mockImageBlockElement, - displayCredit: true, - data: { - caption: 'mock caption', - credit: 'mock display credit', - }, - }), - 'mock caption mock display credit', - ); - }, - ); + void it('includes both the credit and caption, if they exist', () => { + assert.deepEqual( + decideMainMediaCaption({ + ...mockImageBlockElement, + displayCredit: true, + data: { + caption: 'mock caption', + credit: 'mock display credit', + }, + }), + 'mock caption mock display credit', + ); + }); }); - void nodeDescribe('EmbedBlockElement', () => { - void nodeIt('returns an empty string if there is no caption', () => { + void describe('EmbedBlockElement', () => { + void it('returns an empty string if there is no caption', () => { assert.deepEqual( decideMainMediaCaption({ elementId: 'test-id', @@ -114,7 +102,7 @@ void nodeDescribe('decideMainMediaCaption', () => { ); }); - void nodeIt('returns the correct caption, if exists', () => { + void it('returns the correct caption, if exists', () => { assert.deepEqual( decideMainMediaCaption({ elementId: 'test-id', diff --git a/dotcom-rendering/src/lib/edition.node.test.ts b/dotcom-rendering/src/lib/edition.node.test.ts index afd41dba5ef..56db4a2a398 100644 --- a/dotcom-rendering/src/lib/edition.node.test.ts +++ b/dotcom-rendering/src/lib/edition.node.test.ts @@ -1,5 +1,5 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { editionalisedPages, editionList, @@ -15,14 +15,14 @@ const everyEditionalisedPage = editionList ) .flat(); -void nodeDescribe('is network front', () => { - void nodeIt('returns true if pageId is a network front', () => { +void describe('is network front', () => { + void it('returns true if pageId is a network front', () => { assert.equal( everyNetworkFront.every((page) => isNetworkFront(page)), true, ); }); - void nodeIt('returns false if pageId is NOT a network front', () => { + void it('returns false if pageId is NOT a network front', () => { assert.equal(everyEditionalisedPage.every(isNetworkFront), false); assert.equal(isNetworkFront('eu'), false); assert.equal(isNetworkFront('int'), false); @@ -30,14 +30,14 @@ void nodeDescribe('is network front', () => { }); }); -void nodeDescribe('is editionalised page', () => { - void nodeIt('returns true if pageId is editionalised', () => { +void describe('is editionalised page', () => { + void it('returns true if pageId is editionalised', () => { assert.equal( everyEditionalisedPage.every((page) => isEditionalisedPage(page)), true, ); }); - void nodeIt('returns false if pageId is NOT editionalised', () => { + void it('returns false if pageId is NOT editionalised', () => { assert.equal( everyNetworkFront.every((page) => isEditionalisedPage(page)), false, diff --git a/dotcom-rendering/src/lib/formatAttrString.node.test.ts b/dotcom-rendering/src/lib/formatAttrString.node.test.ts index b23aec41682..2ec3aba6e4b 100644 --- a/dotcom-rendering/src/lib/formatAttrString.node.test.ts +++ b/dotcom-rendering/src/lib/formatAttrString.node.test.ts @@ -1,31 +1,31 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { formatAttrString } from './formatAttrString'; const expectedOutput = 'this-headline-should-be-converted'; -void nodeDescribe('formatAttrString', () => { - void nodeIt('Lowercases all', () => { +void describe('formatAttrString', () => { + void it('Lowercases all', () => { const input = 'This Headline Should Be Converted'; assert.equal(formatAttrString(input), expectedOutput); }); - void nodeIt('Converts spaces to hyphens', () => { + void it('Converts spaces to hyphens', () => { const input = 'this headline should be converted'; assert.equal(formatAttrString(input), expectedOutput); }); - void nodeIt('Removes anything but spaces and letters', () => { + void it('Removes anything but spaces and letters', () => { const input = '/this headline should be converted.'; assert.equal(formatAttrString(input), expectedOutput); }); - void nodeIt('Does not remove numbers', () => { + void it('Does not remove numbers', () => { const input = 'this headline should be converted 12'; assert.equal(formatAttrString(input), `${expectedOutput}-12`); }); - void nodeIt('Puts it all together', () => { + void it('Puts it all together', () => { const input = '/this Headline should be converted. 12'; assert.equal(formatAttrString(input), `${expectedOutput}-12`); }); diff --git a/dotcom-rendering/src/lib/formatCount.node.test.ts b/dotcom-rendering/src/lib/formatCount.node.test.ts index 6ca7b7e734e..b13c5ea64bb 100644 --- a/dotcom-rendering/src/lib/formatCount.node.test.ts +++ b/dotcom-rendering/src/lib/formatCount.node.test.ts @@ -1,21 +1,21 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { formatCount } from './formatCount'; -void nodeDescribe('formatCount', () => { - void nodeIt('formats simple numbers', () => { +void describe('formatCount', () => { + void it('formats simple numbers', () => { assert.deepEqual(formatCount(123), { short: '123', long: '123' }); }); - void nodeIt('formats medium numbers', () => { + void it('formats medium numbers', () => { assert.deepEqual(formatCount(9876), { short: '9876', long: '9,876' }); }); - void nodeIt('formats very long numbers', () => { + void it('formats very long numbers', () => { assert.deepEqual(formatCount(92878), { short: '93k', long: '92,878' }); }); - void nodeIt('returns zero for zero', () => { + void it('returns zero for zero', () => { assert.deepEqual(formatCount(0), { short: '0', long: '0' }); }); - void nodeIt('returns an ellipsis for undefined', () => { + void it('returns an ellipsis for undefined', () => { assert.deepEqual(formatCount(), { short: '…', long: '…' }); }); }); diff --git a/dotcom-rendering/src/lib/getFrontsAdPositions.node.test.ts b/dotcom-rendering/src/lib/getFrontsAdPositions.node.test.ts index 249adc10289..21a37b54d38 100644 --- a/dotcom-rendering/src/lib/getFrontsAdPositions.node.test.ts +++ b/dotcom-rendering/src/lib/getFrontsAdPositions.node.test.ts @@ -1,5 +1,5 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { brandedTestCollections, largeFlexibleGeneralCollection, @@ -34,40 +34,28 @@ const defaultTestCollections: AdCandidate[] = [...Array(12)].map( () => ({ ...testCollection }), ); -void nodeDescribe('Mobile Ads', () => { - void nodeIt( - `Should not insert ad after container if it's the first one and it's a thrasher`, - () => { - const testCollections = [ - { ...testCollection, collectionType: 'fixed/thrasher' }, - ...defaultTestCollections, - ] satisfies AdCandidate[]; - - const mobileAdPositions = getMobileAdPositions( - testCollections, - 'uk', - ); +void describe('Mobile Ads', () => { + void it(`Should not insert ad after container if it's the first one and it's a thrasher`, () => { + const testCollections = [ + { ...testCollection, collectionType: 'fixed/thrasher' }, + ...defaultTestCollections, + ] satisfies AdCandidate[]; - assert.ok(!mobileAdPositions.includes(0)); - }, - ); - - void nodeIt( - `should not insert an ad in the merchandising-high position`, - () => { - const testCollections = [ - ...defaultTestCollections.slice(0, 3), - { ...testCollection, collectionType: 'news/most-popular' }, - ] satisfies AdCandidate[]; - const mobileAdPositions = getMobileAdPositions( - testCollections, - 'uk', - ); - assert.ok(!mobileAdPositions.includes(3)); - }, - ); + const mobileAdPositions = getMobileAdPositions(testCollections, 'uk'); + + assert.ok(!mobileAdPositions.includes(0)); + }); + + void it(`should not insert an ad in the merchandising-high position`, () => { + const testCollections = [ + ...defaultTestCollections.slice(0, 3), + { ...testCollection, collectionType: 'news/most-popular' }, + ] satisfies AdCandidate[]; + const mobileAdPositions = getMobileAdPositions(testCollections, 'uk'); + assert.ok(!mobileAdPositions.includes(3)); + }); - void nodeIt('Should not insert ad before a thrasher container', () => { + void it('Should not insert ad before a thrasher container', () => { const testCollections = [...defaultTestCollections]; testCollections.splice(5, 0, { ...testCollection, @@ -84,373 +72,322 @@ void nodeDescribe('Mobile Ads', () => { assert.ok(!mobileAdPositions.includes(8)); }); - void nodeIt( - `Should allow inserting an ad before a thrasher container if it's a filter page`, - () => { - const testCollections = [...defaultTestCollections]; - testCollections.splice(5, 0, { - ...testCollection, - collectionType: 'fixed/thrasher', - }); - testCollections.splice(9, 0, { - ...testCollection, - collectionType: 'fixed/thrasher', - }); + void it(`Should allow inserting an ad before a thrasher container if it's a filter page`, () => { + const testCollections = [...defaultTestCollections]; + testCollections.splice(5, 0, { + ...testCollection, + collectionType: 'fixed/thrasher', + }); + testCollections.splice(9, 0, { + ...testCollection, + collectionType: 'fixed/thrasher', + }); - const mobileAdPositions = getMobileAdPositions( - testCollections, - 'uk/thefilter', - ); + const mobileAdPositions = getMobileAdPositions( + testCollections, + 'uk/thefilter', + ); - assert.ok(mobileAdPositions.includes(6)); - assert.ok(mobileAdPositions.includes(8)); - }, - ); + assert.ok(mobileAdPositions.includes(6)); + assert.ok(mobileAdPositions.includes(8)); + }); // We used https://www.theguardian.com/uk/commentisfree as a blueprint - void nodeIt( - 'Non-network front, with more than 4 collections, without thrashers', - () => { - const testCollections: AdCandidate[] = [ - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (0) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (2) - { ...testCollection, collectionType: 'static/medium/4' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (4) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (6) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (8) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is before merch high position - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position - { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container - ]; - - const mobileAdPositions = getMobileAdPositions( - testCollections, - 'uk', - ); + void it('Non-network front, with more than 4 collections, without thrashers', () => { + const testCollections: AdCandidate[] = [ + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (0) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (2) + { ...testCollection, collectionType: 'static/medium/4' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (4) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (6) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (8) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is before merch high position + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position + { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container + ]; + + const mobileAdPositions = getMobileAdPositions(testCollections, 'uk'); - assert.deepEqual(mobileAdPositions, [0, 2, 4, 6, 8]); - }, - ); + assert.deepEqual(mobileAdPositions, [0, 2, 4, 6, 8]); + }); // We used https://www.theguardian.com/uk as a blueprint - void nodeIt( - 'UK Network Front, with more than 4 collections, with thrashers at various places', - () => { - const testCollections: AdCandidate[] = [ - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (0) - { ...testCollection, collectionType: 'static/medium/4' }, - { ...testCollection, collectionType: 'flexible/special' }, // Ad position (2) - { ...testCollection, collectionType: 'flexible/special' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (4) - { ...testCollection, collectionType: 'flexible/special' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (8) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (11) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (14) - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, - { ...testCollection, collectionType: 'scrollable/feature' }, // Ad position (17) - { ...testCollection, collectionType: 'static/medium/4' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (19) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - is before merch high position - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position - { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container - ]; - - const mobileAdPositions = getMobileAdPositions( - testCollections, - 'uk', - ); + void it('UK Network Front, with more than 4 collections, with thrashers at various places', () => { + const testCollections: AdCandidate[] = [ + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (0) + { ...testCollection, collectionType: 'static/medium/4' }, + { ...testCollection, collectionType: 'flexible/special' }, // Ad position (2) + { ...testCollection, collectionType: 'flexible/special' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (4) + { ...testCollection, collectionType: 'flexible/special' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (8) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (11) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (14) + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, + { ...testCollection, collectionType: 'scrollable/feature' }, // Ad position (17) + { ...testCollection, collectionType: 'static/medium/4' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (19) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - is before merch high position + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position + { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container + ]; + + const mobileAdPositions = getMobileAdPositions(testCollections, 'uk'); - assert.deepEqual(mobileAdPositions, [0, 2, 4, 8, 11, 14, 17, 19]); - }, - ); + assert.deepEqual(mobileAdPositions, [0, 2, 4, 8, 11, 14, 17, 19]); + }); // We used https://www.theguardian.com/international as a blueprint - void nodeIt( - 'International Network Front, with more than 4 collections, with thrashers at various places', - () => { - const testCollections: AdCandidate[] = [ - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (0) - { ...testCollection, collectionType: 'static/medium/4' }, - { ...testCollection, collectionType: 'flexible/special' }, // Ad position (2) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'flexible/special' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (5) - { ...testCollection, collectionType: 'static/medium/4' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (7) - { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (11) - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (14) - { ...testCollection, collectionType: 'static/medium/4' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (16) - { ...testCollection, collectionType: 'scrollable/feature' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position - { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container - ]; - - const mobileAdPositions = getMobileAdPositions( - testCollections, - 'uk', - ); + void it('International Network Front, with more than 4 collections, with thrashers at various places', () => { + const testCollections: AdCandidate[] = [ + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (0) + { ...testCollection, collectionType: 'static/medium/4' }, + { ...testCollection, collectionType: 'flexible/special' }, // Ad position (2) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/special' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (5) + { ...testCollection, collectionType: 'static/medium/4' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (7) + { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (11) + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (14) + { ...testCollection, collectionType: 'static/medium/4' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (16) + { ...testCollection, collectionType: 'scrollable/feature' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position + { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container + ]; - assert.deepEqual(mobileAdPositions, [0, 2, 5, 7, 11, 14, 16]); - }, - ); + const mobileAdPositions = getMobileAdPositions(testCollections, 'uk'); + + assert.deepEqual(mobileAdPositions, [0, 2, 5, 7, 11, 14, 16]); + }); // We used https://www.theguardian.com/us as a blueprint - void nodeIt( - 'US Network Front, with more than 4 collections, with thrashers at various places', - () => { - const testCollections: AdCandidate[] = [ - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (0) - { ...testCollection, collectionType: 'static/medium/4' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (2) - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, - { ...testCollection, collectionType: 'flexible/special' }, // Ad position (5) - { ...testCollection, collectionType: 'flexible/special' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (9) - { ...testCollection, collectionType: 'static/medium/4' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (12) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (14) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (16) - { ...testCollection, collectionType: 'scrollable/feature' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ignored - is merch high position - { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container - ]; - - const mobileAdPositions = getMobileAdPositions( - testCollections, - 'uk', - ); + void it('US Network Front, with more than 4 collections, with thrashers at various places', () => { + const testCollections: AdCandidate[] = [ + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (0) + { ...testCollection, collectionType: 'static/medium/4' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (2) + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, + { ...testCollection, collectionType: 'flexible/special' }, // Ad position (5) + { ...testCollection, collectionType: 'flexible/special' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (9) + { ...testCollection, collectionType: 'static/medium/4' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (12) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (14) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (16) + { ...testCollection, collectionType: 'scrollable/feature' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ignored - is merch high position + { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container + ]; + + const mobileAdPositions = getMobileAdPositions(testCollections, 'uk'); - assert.deepEqual(mobileAdPositions, [0, 2, 5, 9, 12, 14, 16]); - }, - ); + assert.deepEqual(mobileAdPositions, [0, 2, 5, 9, 12, 14, 16]); + }); // We used https://www.theguardian.com/uk/lifeandstyle as a blueprint - void nodeIt( - 'Lifeandstyle front, with more than 4 collections, with thrashers at various places', - () => { - const testCollections: AdCandidate[] = [ - { ...testCollection, collectionType: 'flexible/special' }, // Ad position (0) - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (3) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (6) - { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (9) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (12) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position - { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container - ]; - - const mobileAdPositions = getMobileAdPositions( - testCollections, - 'uk', - ); + void it('Lifeandstyle front, with more than 4 collections, with thrashers at various places', () => { + const testCollections: AdCandidate[] = [ + { ...testCollection, collectionType: 'flexible/special' }, // Ad position (0) + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (3) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (6) + { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (9) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (12) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position + { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container + ]; + + const mobileAdPositions = getMobileAdPositions(testCollections, 'uk'); - assert.deepEqual(mobileAdPositions, [0, 3, 6, 9, 12]); - }, - ); + assert.deepEqual(mobileAdPositions, [0, 3, 6, 9, 12]); + }); // We used https://www.theguardian.com/tone/recipes as a blueprint - void nodeIt( - 'Recipes front, with more than 4 collections, with thrasher at the first position', - () => { - const testCollections: AdCandidate[] = [ - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ignored - is first container and thrasher - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (1) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (3) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (5) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ad position (7) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (9) - { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is before merch high position - { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position - { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container - ]; - - const mobileAdPositions = getMobileAdPositions( - testCollections, - 'uk', - ); + void it('Recipes front, with more than 4 collections, with thrasher at the first position', () => { + const testCollections: AdCandidate[] = [ + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ignored - is first container and thrasher + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (1) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (3) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (5) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (7) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (9) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is before merch high position + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position + { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container + ]; - assert.deepEqual(mobileAdPositions, [1, 3, 5, 7, 9]); - }, - ); - - void nodeIt( - 'Europe Network Front, with more than 4 collections and thrashers in various places', - () => { - const testCollections: AdCandidate[] = [ - { - ...testCollection, - collectionType: 'flexible/general', - containerLevel: 'Primary', - }, // Ignored - is before secondary container and is not large enough - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, // Ignored - is before secondary container - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, // Ignored - is before secondary container - { - ...testCollection, - collectionType: 'scrollable/medium', - containerLevel: 'Secondary', - }, // Ignored - is before secondary container - { - ...testCollection, - collectionType: 'scrollable/feature', - containerLevel: 'Secondary', - }, // Ad position (4) - { - ...testCollection, - collectionType: 'static/feature/2', - containerLevel: 'Primary', - }, // Ignored - is before secondary container - { - ...testCollection, - collectionType: 'scrollable/medium', - containerLevel: 'Secondary', - }, // Ad position (6) - { - ...testCollection, - collectionType: 'flexible/special', - containerLevel: 'Primary', - }, // Ignored - is before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (8) - { - ...testCollection, - collectionType: 'flexible/general', - containerLevel: 'Primary', - }, // Ignored is consecutive ad after position 8 - { - ...testCollection, - collectionType: 'static/feature/2', - containerLevel: 'Primary', - }, // Ignored - is before secondary container - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, // Ignored - is before secondary container - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, // Ignored - is before secondary container - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, // Ad position (13) - { - ...testCollection, - collectionType: 'static/feature/2', - containerLevel: 'Primary', - }, // Ignored - is before secondary container - { - ...testCollection, - collectionType: 'scrollable/medium', - containerLevel: 'Secondary', - }, // Ignored - is before secondary container - { - ...testCollection, - collectionType: 'scrollable/medium', - containerLevel: 'Secondary', - }, // Ignored - is before secondary container - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, // Ignored - is before thrasher - { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (18) - { - ...testCollection, - collectionType: 'flexible/general', - containerLevel: 'Primary', - }, // Ignored - is before secondary container - { - ...testCollection, - collectionType: 'scrollable/feature', - containerLevel: 'Secondary', - }, // Ignored - is merch high position - { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container - ]; - - const mobileAdPositions = getMobileAdPositions( - testCollections, - 'uk', - ); + const mobileAdPositions = getMobileAdPositions(testCollections, 'uk'); + + assert.deepEqual(mobileAdPositions, [1, 3, 5, 7, 9]); + }); - assert.deepEqual(mobileAdPositions, [4, 6, 8, 13, 18]); - }, - ); + void it('Europe Network Front, with more than 4 collections and thrashers in various places', () => { + const testCollections: AdCandidate[] = [ + { + ...testCollection, + collectionType: 'flexible/general', + containerLevel: 'Primary', + }, // Ignored - is before secondary container and is not large enough + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, // Ignored - is before secondary container + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, // Ignored - is before secondary container + { + ...testCollection, + collectionType: 'scrollable/medium', + containerLevel: 'Secondary', + }, // Ignored - is before secondary container + { + ...testCollection, + collectionType: 'scrollable/feature', + containerLevel: 'Secondary', + }, // Ad position (4) + { + ...testCollection, + collectionType: 'static/feature/2', + containerLevel: 'Primary', + }, // Ignored - is before secondary container + { + ...testCollection, + collectionType: 'scrollable/medium', + containerLevel: 'Secondary', + }, // Ad position (6) + { + ...testCollection, + collectionType: 'flexible/special', + containerLevel: 'Primary', + }, // Ignored - is before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (8) + { + ...testCollection, + collectionType: 'flexible/general', + containerLevel: 'Primary', + }, // Ignored is consecutive ad after position 8 + { + ...testCollection, + collectionType: 'static/feature/2', + containerLevel: 'Primary', + }, // Ignored - is before secondary container + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, // Ignored - is before secondary container + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, // Ignored - is before secondary container + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, // Ad position (13) + { + ...testCollection, + collectionType: 'static/feature/2', + containerLevel: 'Primary', + }, // Ignored - is before secondary container + { + ...testCollection, + collectionType: 'scrollable/medium', + containerLevel: 'Secondary', + }, // Ignored - is before secondary container + { + ...testCollection, + collectionType: 'scrollable/medium', + containerLevel: 'Secondary', + }, // Ignored - is before secondary container + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, // Ignored - is before thrasher + { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (18) + { + ...testCollection, + collectionType: 'flexible/general', + containerLevel: 'Primary', + }, // Ignored - is before secondary container + { + ...testCollection, + collectionType: 'scrollable/feature', + containerLevel: 'Secondary', + }, // Ignored - is merch high position + { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container + ]; + + const mobileAdPositions = getMobileAdPositions(testCollections, 'uk'); + + assert.deepEqual(mobileAdPositions, [4, 6, 8, 13, 18]); + }); }); -void nodeDescribe('Desktop Ads', () => { - void nodeIt( - 'calculates ad positions correctly for an example of the UK network front', - () => { - const adPositions = getDesktopAdPositions(testCollectionsUk, 'uk'); +void describe('Desktop Ads', () => { + void it('calculates ad positions correctly for an example of the UK network front', () => { + const adPositions = getDesktopAdPositions(testCollectionsUk, 'uk'); - assert.deepEqual(adPositions, [3, 6, 8, 14, 17]); - }, - ); + assert.deepEqual(adPositions, [3, 6, 8, 14, 17]); + }); - void nodeIt( - 'calculates ad positions correctly for an example of the US network front', - () => { - const adPositions = getDesktopAdPositions(testCollectionsUs, 'us'); + void it('calculates ad positions correctly for an example of the US network front', () => { + const adPositions = getDesktopAdPositions(testCollectionsUs, 'us'); - assert.deepEqual(adPositions, [3, 6, 10, 12, 19]); - }, - ); + assert.deepEqual(adPositions, [3, 6, 10, 12, 19]); + }); - void nodeIt('does NOT insert ads above or below branded content', () => { + void it('does NOT insert ads above or below branded content', () => { const adPositions = getDesktopAdPositions(brandedTestCollections, 'uk'); assert.deepEqual(adPositions, []); }); - void nodeIt('does NOT insert ads above secondary level containers', () => { + void it('does NOT insert ads above secondary level containers', () => { const adPositions = getDesktopAdPositions( testCollectionsWithSecondaryLevel, 'europe', @@ -459,7 +396,7 @@ void nodeDescribe('Desktop Ads', () => { assert.deepEqual(adPositions, []); }); - void nodeIt('inserts a maximum of 8 ads for fronts', () => { + void it('inserts a maximum of 8 ads for fronts', () => { const adPositions = getDesktopAdPositions( // 10x number of test collections in fixture to reach maximum level new Array(10) @@ -472,218 +409,188 @@ void nodeDescribe('Desktop Ads', () => { }); }); -void nodeDescribe('inserting an ad after the first collection', () => { - void nodeDescribe('on mobile', () => { - void nodeIt( - 'inserts an ad after the first collection if it is a LARGE flexible general container', - () => { - const adPositions = getMobileAdPositions( - [ - ...largeFlexibleGeneralCollection, - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, - { - ...testCollection, - collectionType: 'scrollable/medium', - containerLevel: 'Secondary', - }, - ], - 'uk', - ); - - assert.ok(adPositions.includes(0)); - assert.ok(!adPositions.includes(1)); - }, - ); +void describe('inserting an ad after the first collection', () => { + void describe('on mobile', () => { + void it('inserts an ad after the first collection if it is a LARGE flexible general container', () => { + const adPositions = getMobileAdPositions( + [ + ...largeFlexibleGeneralCollection, + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, + { + ...testCollection, + collectionType: 'scrollable/medium', + containerLevel: 'Secondary', + }, + ], + 'uk', + ); - void nodeIt( - 'inserts an ad after the first collection if it is a LARGE flexible special container', - () => { - const adPositions = getMobileAdPositions( - [ - ...largeFlexibleSpecialCollection, - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, - { - ...testCollection, - collectionType: 'scrollable/medium', - containerLevel: 'Secondary', - }, - ], - 'uk', - ); - - assert.ok(adPositions.includes(0)); - assert.ok(!adPositions.includes(1)); - }, - ); + assert.ok(adPositions.includes(0)); + assert.ok(!adPositions.includes(1)); + }); - void nodeIt( - 'does NOT insert an ad after the first collection if it is a SMALL flexible general container', - () => { - const adPositions = getMobileAdPositions( - [ - ...smallFlexibleGeneralCollection, - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, - ], - 'uk', - ); - - assert.ok(!adPositions.includes(0)); - }, - ); + void it('inserts an ad after the first collection if it is a LARGE flexible special container', () => { + const adPositions = getMobileAdPositions( + [ + ...largeFlexibleSpecialCollection, + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, + { + ...testCollection, + collectionType: 'scrollable/medium', + containerLevel: 'Secondary', + }, + ], + 'uk', + ); - void nodeIt( - 'does NOT insert an ad after the first collection if it is a SMALL flexible special container', - () => { - const adPositions = getMobileAdPositions( - [ - ...smallFlexibleSpecialCollection, - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, - ], - 'uk', - ); - - assert.ok(!adPositions.includes(0)); - }, - ); + assert.ok(adPositions.includes(0)); + assert.ok(!adPositions.includes(1)); + }); + + void it('does NOT insert an ad after the first collection if it is a SMALL flexible general container', () => { + const adPositions = getMobileAdPositions( + [ + ...smallFlexibleGeneralCollection, + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, + ], + 'uk', + ); + + assert.ok(!adPositions.includes(0)); + }); + + void it('does NOT insert an ad after the first collection if it is a SMALL flexible special container', () => { + const adPositions = getMobileAdPositions( + [ + ...smallFlexibleSpecialCollection, + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, + ], + 'uk', + ); + + assert.ok(!adPositions.includes(0)); + }); }); - void nodeDescribe('on desktop', () => { - void nodeIt( - 'inserts an ad before the second collection if it is preceded by a LARGE flexible general container', - () => { - const adPositions = getDesktopAdPositions( - [ - ...largeFlexibleGeneralCollection, - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, - { - ...testCollection, - collectionType: 'scrollable/medium', - containerLevel: 'Secondary', - }, - ], - 'uk', - ); - - assert.ok(adPositions.includes(1)); - assert.ok(!adPositions.includes(2)); - }, - ); + void describe('on desktop', () => { + void it('inserts an ad before the second collection if it is preceded by a LARGE flexible general container', () => { + const adPositions = getDesktopAdPositions( + [ + ...largeFlexibleGeneralCollection, + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, + { + ...testCollection, + collectionType: 'scrollable/medium', + containerLevel: 'Secondary', + }, + ], + 'uk', + ); - void nodeIt( - 'inserts an ad before the second collection if it is preceded by a LARGE flexible special container', - () => { - const adPositions = getDesktopAdPositions( - [ - ...largeFlexibleSpecialCollection, - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, - { - ...testCollection, - collectionType: 'scrollable/medium', - containerLevel: 'Secondary', - }, - ], - 'uk', - ); - - assert.ok(adPositions.includes(1)); - assert.ok(!adPositions.includes(2)); - }, - ); + assert.ok(adPositions.includes(1)); + assert.ok(!adPositions.includes(2)); + }); - void nodeIt( - 'does NOT insert an ad before the second collection if it is preceded by a SMALL flexible general container', - () => { - const adPositions = getDesktopAdPositions( - [ - ...smallFlexibleGeneralCollection, - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, - { - ...testCollection, - collectionType: 'scrollable/medium', - containerLevel: 'Secondary', - }, - ], - 'uk', - ); - - assert.ok(!adPositions.includes(1)); - }, - ); + void it('inserts an ad before the second collection if it is preceded by a LARGE flexible special container', () => { + const adPositions = getDesktopAdPositions( + [ + ...largeFlexibleSpecialCollection, + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, + { + ...testCollection, + collectionType: 'scrollable/medium', + containerLevel: 'Secondary', + }, + ], + 'uk', + ); - void nodeIt( - 'does NOT insert an ad before the second collection if it is preceded by a SMALL flexible special container', - () => { - const adPositions = getDesktopAdPositions( - [ - ...smallFlexibleSpecialCollection, - { - ...testCollection, - collectionType: 'scrollable/small', - containerLevel: 'Secondary', - }, - { - ...testCollection, - collectionType: 'scrollable/medium', - containerLevel: 'Secondary', - }, - ], - 'uk', - ); - - assert.ok(!adPositions.includes(1)); - }, - ); + assert.ok(adPositions.includes(1)); + assert.ok(!adPositions.includes(2)); + }); + + void it('does NOT insert an ad before the second collection if it is preceded by a SMALL flexible general container', () => { + const adPositions = getDesktopAdPositions( + [ + ...smallFlexibleGeneralCollection, + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, + { + ...testCollection, + collectionType: 'scrollable/medium', + containerLevel: 'Secondary', + }, + ], + 'uk', + ); + + assert.ok(!adPositions.includes(1)); + }); + + void it('does NOT insert an ad before the second collection if it is preceded by a SMALL flexible special container', () => { + const adPositions = getDesktopAdPositions( + [ + ...smallFlexibleSpecialCollection, + { + ...testCollection, + collectionType: 'scrollable/small', + containerLevel: 'Secondary', + }, + { + ...testCollection, + collectionType: 'scrollable/medium', + containerLevel: 'Secondary', + }, + ], + 'uk', + ); + + assert.ok(!adPositions.includes(1)); + }); }); }); -void nodeDescribe('removeConsecutiveAdSlotsReducer', () => { - void nodeIt( - 'removes consecutive slots from array of all consecutive numbers', - () => { - const arr = [0, 1, 2, 3, 4, 5]; - const result = arr.reduce(removeConsecutiveAdSlotsReducer, []); - assert.deepEqual(result, [0, 2, 4]); - }, - ); - - void nodeIt( - 'removes consecutive slots from array of some consecutive numbers', - () => { - const arr = [0, 3, 7, 11, 12, 13, 19, 20]; - const result = arr.reduce(removeConsecutiveAdSlotsReducer, []); - assert.deepEqual(result, [0, 3, 7, 11, 13, 19]); - }, - ); - - void nodeIt('handles empty array', () => { +void describe('removeConsecutiveAdSlotsReducer', () => { + void it('removes consecutive slots from array of all consecutive numbers', () => { + const arr = [0, 1, 2, 3, 4, 5]; + const result = arr.reduce(removeConsecutiveAdSlotsReducer, []); + assert.deepEqual(result, [0, 2, 4]); + }); + + void it('removes consecutive slots from array of some consecutive numbers', () => { + const arr = [0, 3, 7, 11, 12, 13, 19, 20]; + const result = arr.reduce(removeConsecutiveAdSlotsReducer, []); + assert.deepEqual(result, [0, 3, 7, 11, 13, 19]); + }); + + void it('handles empty array', () => { const arr: number[] = []; const result = arr.reduce(removeConsecutiveAdSlotsReducer, []); assert.deepEqual(result, []); diff --git a/dotcom-rendering/src/lib/getLiveblogAdPositions.node.test.ts b/dotcom-rendering/src/lib/getLiveblogAdPositions.node.test.ts index 598a7faa235..9f4d0d69faa 100644 --- a/dotcom-rendering/src/lib/getLiveblogAdPositions.node.test.ts +++ b/dotcom-rendering/src/lib/getLiveblogAdPositions.node.test.ts @@ -1,17 +1,17 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { liveBlock as mockBlock } from '../../fixtures/manual/liveBlock'; import type { Block } from '../types/blocks'; import { getLiveblogAdPositions } from './getLiveblogAdPositions'; -void nodeDescribe('get liveblog ad positions', () => { +void describe('get liveblog ad positions', () => { const twoBlocks = Array(2).fill(mockBlock); - void nodeIt('should insert zero ads if zero blocks', () => { + void it('should insert zero ads if zero blocks', () => { assert.deepEqual(getLiveblogAdPositions([]).desktopAdPositions, []); assert.deepEqual(getLiveblogAdPositions([]).mobileAdPositions, []); }); - void nodeIt('should insert zero ads if one block', () => { + void it('should insert zero ads if one block', () => { assert.deepEqual( getLiveblogAdPositions([mockBlock]).desktopAdPositions, [], @@ -21,21 +21,18 @@ void nodeDescribe('get liveblog ad positions', () => { [], ); }); - void nodeIt( - 'should insert an ad after the first block if two blocks', - () => { - assert.deepEqual( - getLiveblogAdPositions(twoBlocks).desktopAdPositions, - [0], - ); - assert.deepEqual( - getLiveblogAdPositions(twoBlocks).mobileAdPositions, - [0], - ); - }, - ); + void it('should insert an ad after the first block if two blocks', () => { + assert.deepEqual( + getLiveblogAdPositions(twoBlocks).desktopAdPositions, + [0], + ); + assert.deepEqual( + getLiveblogAdPositions(twoBlocks).mobileAdPositions, + [0], + ); + }); - void nodeDescribe('many blocks', () => { + void describe('many blocks', () => { const block: Block = { ...mockBlock, elements: [ @@ -49,41 +46,31 @@ void nodeDescribe('get liveblog ad positions', () => { const tenBlocks = Array(10).fill(block); - void nodeIt( - 'On desktop, it should insert an ad after every fourth block given repeated text elements of 1,000 characters', - () => { - assert.deepEqual( - getLiveblogAdPositions(tenBlocks).desktopAdPositions, - [0, 4, 8], - ); - }, - ); + void it('On desktop, it should insert an ad after every fourth block given repeated text elements of 1,000 characters', () => { + assert.deepEqual( + getLiveblogAdPositions(tenBlocks).desktopAdPositions, + [0, 4, 8], + ); + }); - void nodeIt( - 'On mobile, it should insert an ad after every second block given repeated text elements of 1,000 characters', - () => { - assert.deepEqual( - getLiveblogAdPositions(tenBlocks).mobileAdPositions, - [0, 2, 4, 6, 8], - ); - }, - ); + void it('On mobile, it should insert an ad after every second block given repeated text elements of 1,000 characters', () => { + assert.deepEqual( + getLiveblogAdPositions(tenBlocks).mobileAdPositions, + [0, 2, 4, 6, 8], + ); + }); // 40 blocks is enough that without a limit, there would be more than 8 blocks inserted on both mobile and desktop. const fortyBlocks = Array(40).fill(block); - void nodeIt( - 'On desktop, it should not insert more that 8 slots', - () => { - assert.equal( - getLiveblogAdPositions(fortyBlocks).desktopAdPositions - .length, - 8, - ); - }, - ); + void it('On desktop, it should not insert more that 8 slots', () => { + assert.equal( + getLiveblogAdPositions(fortyBlocks).desktopAdPositions.length, + 8, + ); + }); - void nodeIt('On mobile, it should not insert more that 8 slots', () => { + void it('On mobile, it should not insert more that 8 slots', () => { assert.equal( getLiveblogAdPositions(fortyBlocks).mobileAdPositions.length, 8, diff --git a/dotcom-rendering/src/lib/getTagPageAdPositions.node.test.ts b/dotcom-rendering/src/lib/getTagPageAdPositions.node.test.ts index 62f644dd3c9..c810e9b2346 100644 --- a/dotcom-rendering/src/lib/getTagPageAdPositions.node.test.ts +++ b/dotcom-rendering/src/lib/getTagPageAdPositions.node.test.ts @@ -1,37 +1,31 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { getTagPageBannerAdPositions } from './getTagPageAdPositions'; -void nodeDescribe('Tag page fronts-banner ad slots', () => { - void nodeIt( - 'should insert 0 ads if there are less than 5 containers', - () => { - assert.deepEqual(getTagPageBannerAdPositions(1), []); - assert.deepEqual(getTagPageBannerAdPositions(3), []); - }, - ); +void describe('Tag page fronts-banner ad slots', () => { + void it('should insert 0 ads if there are less than 5 containers', () => { + assert.deepEqual(getTagPageBannerAdPositions(1), []); + assert.deepEqual(getTagPageBannerAdPositions(3), []); + }); - void nodeIt('should insert 1 ad if there are 5-7 containers', () => { + void it('should insert 1 ad if there are 5-7 containers', () => { assert.deepEqual(getTagPageBannerAdPositions(4), [2]); assert.deepEqual(getTagPageBannerAdPositions(6), [2]); }); - void nodeIt('should insert 2 ads if there are 8-10 containers', () => { + void it('should insert 2 ads if there are 8-10 containers', () => { assert.deepEqual(getTagPageBannerAdPositions(7), [2, 5]); assert.deepEqual(getTagPageBannerAdPositions(9), [2, 5]); }); - void nodeIt( - 'should insert no more than 8 ads if there are more than 18 containers', - () => { - assert.deepEqual( - getTagPageBannerAdPositions(19), - [2, 5, 8, 11, 14, 17], - ); - assert.deepEqual( - getTagPageBannerAdPositions(25), - [2, 5, 8, 11, 14, 17, 20, 23], - ); - }, - ); + void it('should insert no more than 8 ads if there are more than 18 containers', () => { + assert.deepEqual( + getTagPageBannerAdPositions(19), + [2, 5, 8, 11, 14, 17], + ); + assert.deepEqual( + getTagPageBannerAdPositions(25), + [2, 5, 8, 11, 14, 17, 20, 23], + ); + }); }); diff --git a/dotcom-rendering/src/lib/getZIndex.node.test.ts b/dotcom-rendering/src/lib/getZIndex.node.test.ts index b613caa010f..3e95bd0b0fc 100644 --- a/dotcom-rendering/src/lib/getZIndex.node.test.ts +++ b/dotcom-rendering/src/lib/getZIndex.node.test.ts @@ -1,9 +1,9 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { getZIndex } from './getZIndex'; -void nodeDescribe('getZIndex', () => { - void nodeIt('gets the correct zindex for group and sibling', () => { +void describe('getZIndex', () => { + void it('gets the correct zindex for group and sibling', () => { assert.ok(getZIndex('sticky-video-button') > getZIndex('sticky-video')); assert.ok( getZIndex('expanded-veggie-menu-wrapper') > diff --git a/dotcom-rendering/src/lib/identity-component-event.node.test.ts b/dotcom-rendering/src/lib/identity-component-event.node.test.ts index ed6c00dac87..6791d7dfcf0 100644 --- a/dotcom-rendering/src/lib/identity-component-event.node.test.ts +++ b/dotcom-rendering/src/lib/identity-component-event.node.test.ts @@ -1,28 +1,19 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { createAuthenticationEventParams } from './identity-component-event'; -void nodeDescribe('createAuthenticationEventParams', () => { - void nodeIt( - 'creates authentication event params given a component Id', - () => { - assert.equal( - createAuthenticationEventParams('amp_sidebar_signin'), - 'componentEventParams=componentType%3Didentityauthentication%26componentId%3Damp_sidebar_signin', - ); - }, - ); +void describe('createAuthenticationEventParams', () => { + void it('creates authentication event params given a component Id', () => { + assert.equal( + createAuthenticationEventParams('amp_sidebar_signin'), + 'componentEventParams=componentType%3Didentityauthentication%26componentId%3Damp_sidebar_signin', + ); + }); - void nodeIt( - 'creates authentication event params given a component Id and a page view Id', - () => { - assert.equal( - createAuthenticationEventParams( - 'amp_sidebar_signin', - 'pageViewId', - ), - 'componentEventParams=componentType%3Didentityauthentication%26componentId%3Damp_sidebar_signin%26viewId%3DpageViewId', - ); - }, - ); + void it('creates authentication event params given a component Id and a page view Id', () => { + assert.equal( + createAuthenticationEventParams('amp_sidebar_signin', 'pageViewId'), + 'componentEventParams=componentType%3Didentityauthentication%26componentId%3Damp_sidebar_signin%26viewId%3DpageViewId', + ); + }); }); diff --git a/dotcom-rendering/src/lib/isLight.node.test.ts b/dotcom-rendering/src/lib/isLight.node.test.ts index 72e13edb4e3..53b7122e902 100644 --- a/dotcom-rendering/src/lib/isLight.node.test.ts +++ b/dotcom-rendering/src/lib/isLight.node.test.ts @@ -1,54 +1,45 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { isLight } from './isLight'; -void nodeDescribe('isLight', () => { - void nodeIt( - 'should return the correct response for dark hex colours', - () => { - for (const colour of [ - '#791a4e', - '#644f4e', - '#7f4e2a', - '#aa365e', - '#5e387c', - '#87656e', - '#223cdd', - '#555eee', - '#334cde', - '#b54bbb', - ]) { - assert.equal(isLight(colour), false); - } - }, - ); +void describe('isLight', () => { + void it('should return the correct response for dark hex colours', () => { + for (const colour of [ + '#791a4e', + '#644f4e', + '#7f4e2a', + '#aa365e', + '#5e387c', + '#87656e', + '#223cdd', + '#555eee', + '#334cde', + '#b54bbb', + ]) { + assert.equal(isLight(colour), false); + } + }); - void nodeIt( - 'should return the correct response for light hex colours', - () => { - for (const colour of ['#ea3eee', '#97dc45', '#7ec621', '#54dbb6']) { - assert.equal(isLight(colour), true); - } - }, - ); + void it('should return the correct response for light hex colours', () => { + for (const colour of ['#ea3eee', '#97dc45', '#7ec621', '#54dbb6']) { + assert.equal(isLight(colour), true); + } + }); - void nodeIt( - 'should return the correct response for 3 digit hex colours', - () => { - assert.equal(isLight('#f4e'), true); - assert.equal(isLight('#fff'), true); - assert.equal(isLight('#999'), true); - assert.equal(isLight('#64e'), false); - assert.equal(isLight('#000'), false); - }, - ); + void it('should return the correct response for 3 digit hex colours', () => { + assert.equal(isLight('#f4e'), true); + assert.equal(isLight('#fff'), true); + assert.equal(isLight('#999'), true); + assert.equal(isLight('#64e'), false); + assert.equal(isLight('#000'), false); + }); - void nodeIt('should handle if the # is missing', () => { + void it('should handle if the # is missing', () => { assert.equal(isLight('97dc45'), true); assert.equal(isLight('000'), false); }); - void nodeIt('should handle if the colour string is invalid', () => { + void it('should handle if the colour string is invalid', () => { assert.equal(isLight('wyx'), false); }); }); diff --git a/dotcom-rendering/src/lib/isValidUrl.node.test.ts b/dotcom-rendering/src/lib/isValidUrl.node.test.ts index 863e8a78b0c..b4144202a9f 100644 --- a/dotcom-rendering/src/lib/isValidUrl.node.test.ts +++ b/dotcom-rendering/src/lib/isValidUrl.node.test.ts @@ -1,9 +1,9 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { isValidUrl } from './isValidUrl'; -void nodeDescribe('isValidUrl', () => { - void nodeDescribe('invalidInputs', () => { +void describe('isValidUrl', () => { + void describe('invalidInputs', () => { const invalidInputs = [ '', 'guardian.co', @@ -13,16 +13,13 @@ void nodeDescribe('isValidUrl', () => { ]; for (const input of invalidInputs) { - void nodeIt( - `returns false for invalid input of \`${input}\``, - () => { - assert.equal(isValidUrl(input), false); - }, - ); + void it(`returns false for invalid input of \`${input}\``, () => { + assert.equal(isValidUrl(input), false); + }); } }); - void nodeDescribe('validInputs', () => { + void describe('validInputs', () => { const validInputs = [ 'https://guardian.co.uk/australia-news/series/guardian-australia-s-morning-mail', 'https://regexr.com/39nr7', @@ -30,7 +27,7 @@ void nodeDescribe('isValidUrl', () => { ]; for (const input of validInputs) { - void nodeIt(`returns true for valid input of \`${input}\``, () => { + void it(`returns true for valid input of \`${input}\``, () => { assert.equal(isValidUrl(input), true); }); } diff --git a/dotcom-rendering/src/lib/labs.node.test.ts b/dotcom-rendering/src/lib/labs.node.test.ts index ea057a338a9..e9ff20ca680 100644 --- a/dotcom-rendering/src/lib/labs.node.test.ts +++ b/dotcom-rendering/src/lib/labs.node.test.ts @@ -1,42 +1,36 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import type { Branding } from '../types/branding'; import { getOphanComponents } from './labs'; -void nodeDescribe('getOphanComponents', () => { - void nodeIt( - 'constructs the correct data attributes for branding in article meta', - () => { - const branding = { sponsorName: 'Some Sponsor' } as Branding; - assert.deepEqual( - getOphanComponents({ - branding, - locationPrefix: 'article-meta', - }), - { - ophanComponentName: 'labs-logo | article-meta-some-sponsor', - ophanComponentLink: 'labs-logo-article-meta-some-sponsor', - }, - ); - }, - ); +void describe('getOphanComponents', () => { + void it('constructs the correct data attributes for branding in article meta', () => { + const branding = { sponsorName: 'Some Sponsor' } as Branding; + assert.deepEqual( + getOphanComponents({ + branding, + locationPrefix: 'article-meta', + }), + { + ophanComponentName: 'labs-logo | article-meta-some-sponsor', + ophanComponentLink: 'labs-logo-article-meta-some-sponsor', + }, + ); + }); - void nodeIt( - 'constructs the correct data attributes for branding in related content', - () => { - const branding = { sponsorName: 'Some Sponsor' } as Branding; - assert.deepEqual( - getOphanComponents({ - branding, - locationPrefix: 'article-related-content', - }), - { - ophanComponentName: - 'labs-logo | article-related-content-some-sponsor', - ophanComponentLink: - 'labs-logo-article-related-content-some-sponsor', - }, - ); - }, - ); + void it('constructs the correct data attributes for branding in related content', () => { + const branding = { sponsorName: 'Some Sponsor' } as Branding; + assert.deepEqual( + getOphanComponents({ + branding, + locationPrefix: 'article-related-content', + }), + { + ophanComponentName: + 'labs-logo | article-related-content-some-sponsor', + ophanComponentLink: + 'labs-logo-article-related-content-some-sponsor', + }, + ); + }); }); diff --git a/dotcom-rendering/src/lib/lang.node.test.ts b/dotcom-rendering/src/lib/lang.node.test.ts index e0ac06fb557..9528912b57f 100644 --- a/dotcom-rendering/src/lib/lang.node.test.ts +++ b/dotcom-rendering/src/lib/lang.node.test.ts @@ -1,20 +1,20 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { decideLanguage, decideLanguageDirection } from './lang'; -void nodeDescribe('decideLanguage', () => { - void nodeIt('returns undefined if input is "en"', () => { +void describe('decideLanguage', () => { + void it('returns undefined if input is "en"', () => { assert.equal(decideLanguage('en'), undefined); }); - void nodeIt('returns input if it is not "en"', () => { + void it('returns input if it is not "en"', () => { assert.equal(decideLanguage('at'), 'at'); assert.equal(decideLanguage('fr'), 'fr'); }); }); -void nodeDescribe('describeLanguageDirection', () => { - void nodeIt('returns rtl if input is true', () => { +void describe('describeLanguageDirection', () => { + void it('returns rtl if input is true', () => { assert.equal(decideLanguageDirection(true), 'rtl'); assert.equal(decideLanguageDirection(false), undefined); }); diff --git a/dotcom-rendering/src/lib/linkNotificationCount.node.test.ts b/dotcom-rendering/src/lib/linkNotificationCount.node.test.ts index c374a0a389e..14ef88dc439 100644 --- a/dotcom-rendering/src/lib/linkNotificationCount.node.test.ts +++ b/dotcom-rendering/src/lib/linkNotificationCount.node.test.ts @@ -1,10 +1,10 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import type { DropdownLinkType } from '../components/Dropdown.island'; import { linkNotificationCount } from './linkNotificationCount'; -void nodeDescribe('linksNotificationCount', () => { - void nodeIt('returns the sum of notifications across all links', () => { +void describe('linksNotificationCount', () => { + void it('returns the sum of notifications across all links', () => { const links: DropdownLinkType[] = [ { id: 'one', @@ -45,7 +45,7 @@ void nodeDescribe('linksNotificationCount', () => { assert.equal(linkNotificationCount(links), 3); }); - void nodeIt('returns 0 when there are no notifications', () => { + void it('returns 0 when there are no notifications', () => { const links = [ { id: 'one', diff --git a/dotcom-rendering/src/lib/liveblogAdSlots.node.test.ts b/dotcom-rendering/src/lib/liveblogAdSlots.node.test.ts index 57ef859ec8c..e356deb7915 100644 --- a/dotcom-rendering/src/lib/liveblogAdSlots.node.test.ts +++ b/dotcom-rendering/src/lib/liveblogAdSlots.node.test.ts @@ -1,12 +1,12 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import type { FEElement } from '../types/content'; import { calculateApproximateBlockHeight, shouldDisplayAd, } from './liveblogAdSlots'; -void nodeDescribe('calculateApproximateBlockHeight', () => { +void describe('calculateApproximateBlockHeight', () => { const textElementOneLineDesktop: FEElement[] = [ { elementId: '1', @@ -65,22 +65,19 @@ void nodeDescribe('calculateApproximateBlockHeight', () => { const defaultBlockSpacing = 75; - void nodeDescribe('zero elements', () => { + void describe('zero elements', () => { for (const screenSize of ['mobile', 'desktop']) { - void nodeIt( - `should return zero when there are zero elements on ${screenSize}`, - () => { - const isMobile = screenSize === 'mobile'; - assert.deepEqual( - calculateApproximateBlockHeight([], isMobile), - 0, - ); - }, - ); + void it(`should return zero when there are zero elements on ${screenSize}`, () => { + const isMobile = screenSize === 'mobile'; + assert.deepEqual( + calculateApproximateBlockHeight([], isMobile), + 0, + ); + }); } }); - void nodeDescribe('text block elements', () => { + void describe('text block elements', () => { const textLineHeight = 23.8; const margin = 14; @@ -88,199 +85,172 @@ void nodeDescribe('calculateApproximateBlockHeight', () => { ['mobile', textElementOneLineMobile, textElementTwoLinesMobile], ['desktop', textElementOneLineDesktop, textElementTwoLinesDestkop], ] as const) { - void nodeIt( - `should return the correct height for varying line length on ${screenSize}`, - () => { - const isMobile = screenSize === 'mobile'; + void it(`should return the correct height for varying line length on ${screenSize}`, () => { + const isMobile = screenSize === 'mobile'; - assert.deepEqual( - calculateApproximateBlockHeight( - textElementOneLine, - isMobile, - ), - textLineHeight + margin + defaultBlockSpacing, - ); - assert.deepEqual( - calculateApproximateBlockHeight( - textElementTwoLines, - isMobile, - ), - 2 * textLineHeight + margin + defaultBlockSpacing, - ); - }, - ); + assert.deepEqual( + calculateApproximateBlockHeight( + textElementOneLine, + isMobile, + ), + textLineHeight + margin + defaultBlockSpacing, + ); + assert.deepEqual( + calculateApproximateBlockHeight( + textElementTwoLines, + isMobile, + ), + 2 * textLineHeight + margin + defaultBlockSpacing, + ); + }); } for (const screenSize of ['mobile', 'desktop']) { - void nodeIt( - `should return the correct height when there are multiple elements on ${screenSize}`, - () => { - const isMobile = screenSize === 'mobile'; + void it(`should return the correct height when there are multiple elements on ${screenSize}`, () => { + const isMobile = screenSize === 'mobile'; - assert.deepEqual( - calculateApproximateBlockHeight( - multipleTextElements, - isMobile, - ), - 2 * textLineHeight + 2 * margin + defaultBlockSpacing, - ); - }, - ); + assert.deepEqual( + calculateApproximateBlockHeight( + multipleTextElements, + isMobile, + ), + 2 * textLineHeight + 2 * margin + defaultBlockSpacing, + ); + }); } }); - void nodeDescribe('youtube block elements', () => { + void describe('youtube block elements', () => { for (const [screenSize, heightExcludingText] of [ ['mobile', 195], ['desktop', 350], ] as const) { - void nodeIt( - `should return the correct height on ${screenSize}`, - () => { - const isMobile = screenSize === 'mobile'; - const margin = 12; + void it(`should return the correct height on ${screenSize}`, () => { + const isMobile = screenSize === 'mobile'; + const margin = 12; - assert.deepEqual( - calculateApproximateBlockHeight( - youtubeElement, - isMobile, - ), - heightExcludingText + margin + defaultBlockSpacing, - ); - }, - ); + assert.deepEqual( + calculateApproximateBlockHeight(youtubeElement, isMobile), + heightExcludingText + margin + defaultBlockSpacing, + ); + }); } }); }); -void nodeDescribe('shouldDisplayAd', () => { - void nodeDescribe('The final block of content', () => { +void describe('shouldDisplayAd', () => { + void describe('The final block of content', () => { for (const screenSize of ['mobile', 'desktop']) { - void nodeIt( - `should NOT display an ad if this is the final block on ${screenSize}`, - () => { - const isMobile = screenSize === 'mobile'; + void it(`should NOT display an ad if this is the final block on ${screenSize}`, () => { + const isMobile = screenSize === 'mobile'; - const block = 5; - const totalBlocks = 5; - const numAdsInserted = 1; - const numPixelsWithoutAdvert = 5000; + const block = 5; + const totalBlocks = 5; + const numAdsInserted = 1; + const numPixelsWithoutAdvert = 5000; - const result = shouldDisplayAd( - block, - totalBlocks, - numAdsInserted, - numPixelsWithoutAdvert, - isMobile, - ); + const result = shouldDisplayAd( + block, + totalBlocks, + numAdsInserted, + numPixelsWithoutAdvert, + isMobile, + ); - assert.ok(!result); - }, - ); + assert.ok(!result); + }); } }); - void nodeDescribe('Reaching the ad limit', () => { + void describe('Reaching the ad limit', () => { for (const screenSize of ['mobile', 'desktop']) { - void nodeIt( - `should NOT insert another ad slot if we have reached the limit on ${screenSize}.`, - () => { - const isMobile = screenSize === 'mobile'; - const block = 5; - const totalBlocks = 10; - const numAdsInserted = 8; - const numPixelsWithoutAdvert = 5000; + void it(`should NOT insert another ad slot if we have reached the limit on ${screenSize}.`, () => { + const isMobile = screenSize === 'mobile'; + const block = 5; + const totalBlocks = 10; + const numAdsInserted = 8; + const numPixelsWithoutAdvert = 5000; - const result = shouldDisplayAd( - block, - totalBlocks, - numAdsInserted, - numPixelsWithoutAdvert, - isMobile, - ); + const result = shouldDisplayAd( + block, + totalBlocks, + numAdsInserted, + numPixelsWithoutAdvert, + isMobile, + ); - assert.ok(!result); - }, - ); + assert.ok(!result); + }); } }); - void nodeDescribe('inserting the first ad slot', () => { + void describe('inserting the first ad slot', () => { for (const screenSize of ['mobile', 'desktop']) { - void nodeIt( - `should display ad if this is the first block on ${screenSize}.`, - () => { - const isMobile = screenSize === 'mobile'; - const block = 1; - const totalBlocks = 10; - const numAdsInserted = 0; - const numPixelsWithoutAdvert = 550; + void it(`should display ad if this is the first block on ${screenSize}.`, () => { + const isMobile = screenSize === 'mobile'; + const block = 1; + const totalBlocks = 10; + const numAdsInserted = 0; + const numPixelsWithoutAdvert = 550; - const result = shouldDisplayAd( - block, - totalBlocks, - numAdsInserted, - numPixelsWithoutAdvert, - isMobile, - ); + const result = shouldDisplayAd( + block, + totalBlocks, + numAdsInserted, + numPixelsWithoutAdvert, + isMobile, + ); - assert.ok(result); - }, - ); + assert.ok(result); + }); } }); - void nodeDescribe('inserting further ad slots', () => { + void describe('inserting further ad slots', () => { for (const [pixels, screenSize] of [ [1200, 'mobile'], [1500, 'desktop'], ] as const) { - void nodeIt( - `should display ad if number of pixels without an ad is more than ${pixels} on ${screenSize}`, - () => { - const isMobile = screenSize === 'mobile'; - const block = 5; - const totalBlocks = 10; - const numAdsInserted = 1; - const numPixelsWithoutAdvert = pixels + 50; + void it(`should display ad if number of pixels without an ad is more than ${pixels} on ${screenSize}`, () => { + const isMobile = screenSize === 'mobile'; + const block = 5; + const totalBlocks = 10; + const numAdsInserted = 1; + const numPixelsWithoutAdvert = pixels + 50; - const result = shouldDisplayAd( - block, - totalBlocks, - numAdsInserted, - numPixelsWithoutAdvert, - isMobile, - ); + const result = shouldDisplayAd( + block, + totalBlocks, + numAdsInserted, + numPixelsWithoutAdvert, + isMobile, + ); - assert.ok(result); - }, - ); + assert.ok(result); + }); } for (const [pixels, screenSize] of [ [1200, 'mobile'], [1500, 'desktop'], ] as const) { - void nodeIt( - `should NOT display ad if number of pixels without an ad is less than ${pixels} on ${screenSize}`, - () => { - const isMobile = screenSize === 'mobile'; - const block = 5; - const totalBlocks = 10; - const numAdsInserted = 1; - const numPixelsWithoutAdvert = pixels - 50; + void it(`should NOT display ad if number of pixels without an ad is less than ${pixels} on ${screenSize}`, () => { + const isMobile = screenSize === 'mobile'; + const block = 5; + const totalBlocks = 10; + const numAdsInserted = 1; + const numPixelsWithoutAdvert = pixels - 50; - const result = shouldDisplayAd( - block, - totalBlocks, - numAdsInserted, - numPixelsWithoutAdvert, - isMobile, - ); + const result = shouldDisplayAd( + block, + totalBlocks, + numAdsInserted, + numPixelsWithoutAdvert, + isMobile, + ); - assert.ok(!result); - }, - ); + assert.ok(!result); + }); } }); }); diff --git a/dotcom-rendering/src/lib/notification.node.test.ts b/dotcom-rendering/src/lib/notification.node.test.ts index 6d67df6af9f..dea94ec59f0 100644 --- a/dotcom-rendering/src/lib/notification.node.test.ts +++ b/dotcom-rendering/src/lib/notification.node.test.ts @@ -1,9 +1,9 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { addNotificationsToDropdownLinks } from './notification'; -void nodeDescribe('addNotificationsToDropdownLinks', () => { - void nodeIt('augments dropdown links with notifications', () => { +void describe('addNotificationsToDropdownLinks', () => { + void it('augments dropdown links with notifications', () => { const links = [ { id: 'account_overview', @@ -56,7 +56,7 @@ void nodeDescribe('addNotificationsToDropdownLinks', () => { ]); }); - void nodeIt('adds multiple notification messages to a link', () => { + void it('adds multiple notification messages to a link', () => { const links = [ { id: 'account_overview', @@ -109,61 +109,58 @@ void nodeDescribe('addNotificationsToDropdownLinks', () => { ]); }); - void nodeIt( - 'adds new notifications if target already has notifications', - () => { - const links = [ - { - id: 'account_overview', - url: `https://example.com/account_overview`, - title: 'Account overview', - dataLinkName: 'nav2 : topbar : account overview', - notifications: [ - { - id: 'existing', - message: 'Existing notification message', - target: 'account_overview', - ophanLabel: 'notification-label-1', - }, - ], - }, - ]; - const notifications = [ - { - id: 'new', - message: 'New notification message', - target: 'account_overview', - ophanLabel: 'notification-label-2', - }, - ]; + void it('adds new notifications if target already has notifications', () => { + const links = [ + { + id: 'account_overview', + url: `https://example.com/account_overview`, + title: 'Account overview', + dataLinkName: 'nav2 : topbar : account overview', + notifications: [ + { + id: 'existing', + message: 'Existing notification message', + target: 'account_overview', + ophanLabel: 'notification-label-1', + }, + ], + }, + ]; + const notifications = [ + { + id: 'new', + message: 'New notification message', + target: 'account_overview', + ophanLabel: 'notification-label-2', + }, + ]; - const linksWithNotifications = addNotificationsToDropdownLinks( - links, - notifications, - ); + const linksWithNotifications = addNotificationsToDropdownLinks( + links, + notifications, + ); - assert.deepEqual(linksWithNotifications, [ - { - id: 'account_overview', - url: `https://example.com/account_overview`, - title: 'Account overview', - dataLinkName: 'nav2 : topbar : account overview', - notifications: [ - { - id: 'existing', - message: 'Existing notification message', - target: 'account_overview', - ophanLabel: 'notification-label-1', - }, - { - id: 'new', - message: 'New notification message', - target: 'account_overview', - ophanLabel: 'notification-label-2', - }, - ], - }, - ]); - }, - ); + assert.deepEqual(linksWithNotifications, [ + { + id: 'account_overview', + url: `https://example.com/account_overview`, + title: 'Account overview', + dataLinkName: 'nav2 : topbar : account overview', + notifications: [ + { + id: 'existing', + message: 'Existing notification message', + target: 'account_overview', + ophanLabel: 'notification-label-1', + }, + { + id: 'new', + message: 'New notification message', + target: 'account_overview', + ophanLabel: 'notification-label-2', + }, + ], + }, + ]); + }); }); diff --git a/dotcom-rendering/src/lib/ophan-helpers.node.test.ts b/dotcom-rendering/src/lib/ophan-helpers.node.test.ts index d8a93b51f97..b72b33e42c7 100644 --- a/dotcom-rendering/src/lib/ophan-helpers.node.test.ts +++ b/dotcom-rendering/src/lib/ophan-helpers.node.test.ts @@ -1,9 +1,9 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { nestedOphanComponents } from './ophan-helpers'; -void nodeDescribe('Ophan helpers', () => { - void nodeIt('should handle nested values', () => { +void describe('Ophan helpers', () => { + void it('should handle nested values', () => { assert.equal(nestedOphanComponents('logo'), 'logo'); assert.equal( nestedOphanComponents('nav', 'sub nav', 'final element'), diff --git a/dotcom-rendering/src/lib/parser/parseCheckoutOutCookieData.node.test.ts b/dotcom-rendering/src/lib/parser/parseCheckoutOutCookieData.node.test.ts index 61b34ae9b5d..501886b441f 100644 --- a/dotcom-rendering/src/lib/parser/parseCheckoutOutCookieData.node.test.ts +++ b/dotcom-rendering/src/lib/parser/parseCheckoutOutCookieData.node.test.ts @@ -1,35 +1,29 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { parseCheckoutCompleteCookieData } from './parseCheckoutOutCookieData'; -void nodeDescribe('parseCheckoutCompleteCookieData', () => { +void describe('parseCheckoutCompleteCookieData', () => { const encodeCheckoutCompleteCookieDataObj = ( userType: string, product: string, ) => encodeURIComponent(`{"userType":"${userType}","product":"${product}"}`); - void nodeDescribe('successful parse', () => { - void nodeIt( - 'should successfully parse a url encoded json object with a valid userType and product valid field', - () => { - const cookieString = encodeCheckoutCompleteCookieDataObj( - 'new', - 'SupporterPlus', - ); - assert.deepEqual( - parseCheckoutCompleteCookieData(cookieString), - { - userType: 'new', - product: 'SupporterPlus', - }, - ); - }, - ); + void describe('successful parse', () => { + void it('should successfully parse a url encoded json object with a valid userType and product valid field', () => { + const cookieString = encodeCheckoutCompleteCookieDataObj( + 'new', + 'SupporterPlus', + ); + assert.deepEqual(parseCheckoutCompleteCookieData(cookieString), { + userType: 'new', + product: 'SupporterPlus', + }); + }); }); - void nodeDescribe('unsuccessful parse should return undefined', () => { - void nodeIt('invalid user type', () => { + void describe('unsuccessful parse should return undefined', () => { + void it('invalid user type', () => { const cookieString = encodeCheckoutCompleteCookieDataObj( 'invalid', 'SupporterPlus', @@ -39,7 +33,7 @@ void nodeDescribe('parseCheckoutCompleteCookieData', () => { undefined, ); }); - void nodeIt('invalid product type', () => { + void it('invalid product type', () => { const cookieString = encodeCheckoutCompleteCookieDataObj( 'new', 'undefined', @@ -49,7 +43,7 @@ void nodeDescribe('parseCheckoutCompleteCookieData', () => { undefined, ); }); - void nodeIt('invalid field', () => { + void it('invalid field', () => { const cookieString = encodeURIComponent( `{"invalid":"new", "product": "SupporterPlus"}`, ); @@ -58,7 +52,7 @@ void nodeDescribe('parseCheckoutCompleteCookieData', () => { undefined, ); }); - void nodeIt('invalid json structure', () => { + void it('invalid json structure', () => { const cookieString = encodeURIComponent( `{"userType":"new", "product": "SupporterPlus"`, ); @@ -67,7 +61,7 @@ void nodeDescribe('parseCheckoutCompleteCookieData', () => { undefined, ); }); - void nodeIt('plain string', () => { + void it('plain string', () => { const cookieString = `{"userType":"new", "product": "SupporterPlus"}`; assert.equal( parseCheckoutCompleteCookieData(cookieString), diff --git a/dotcom-rendering/src/lib/puzzlesHubExperiment.node.test.ts b/dotcom-rendering/src/lib/puzzlesHubExperiment.node.test.ts index 9cc32d6fb45..841fe44f9ea 100644 --- a/dotcom-rendering/src/lib/puzzlesHubExperiment.node.test.ts +++ b/dotcom-rendering/src/lib/puzzlesHubExperiment.node.test.ts @@ -1,11 +1,11 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { isPuzzlesHubEnabled, isPuzzlesHubVariant, } from './puzzlesHubExperiment'; -void nodeDescribe('isPuzzlesHubVariant', () => { +void describe('isPuzzlesHubVariant', () => { const testCases = [ ['control', { 'puzzles-new-hub': 'control' }], ['missing', {}], @@ -14,12 +14,12 @@ void nodeDescribe('isPuzzlesHubVariant', () => { ] as const; for (const [name, participations] of testCases) { - void nodeIt(`rejects ${name}`, () => { + void it(`rejects ${name}`, () => { assert.equal(isPuzzlesHubVariant(participations), false); }); } - void nodeIt('accepts only puzzles-new-hub:variant', () => { + void it('accepts only puzzles-new-hub:variant', () => { assert.equal( isPuzzlesHubVariant({ 'puzzles-new-hub': 'variant' }), true, @@ -27,15 +27,12 @@ void nodeDescribe('isPuzzlesHubVariant', () => { }); }); -void nodeDescribe('isPuzzlesHubEnabled', () => { - void nodeIt( - 'allow local development without an experiment participation', - () => { - assert.equal(isPuzzlesHubEnabled({}, true), true); - }, - ); +void describe('isPuzzlesHubEnabled', () => { + void it('allow local development without an experiment participation', () => { + assert.equal(isPuzzlesHubEnabled({}, true), true); + }); - void nodeIt('requires the variant outside local development', () => { + void it('requires the variant outside local development', () => { assert.equal(isPuzzlesHubEnabled({}, false), false); assert.equal( isPuzzlesHubEnabled({ 'puzzles-new-hub': 'variant' }, false), diff --git a/dotcom-rendering/src/lib/querystring.node.test.ts b/dotcom-rendering/src/lib/querystring.node.test.ts index ee082a59965..d243fb85183 100644 --- a/dotcom-rendering/src/lib/querystring.node.test.ts +++ b/dotcom-rendering/src/lib/querystring.node.test.ts @@ -1,9 +1,9 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { constructQuery } from './querystring'; -void nodeDescribe('constructQuery', () => { - void nodeIt('constructs the correct query string from an object', () => { +void describe('constructQuery', () => { + void it('constructs the correct query string from an object', () => { const testParams = { sens: 'f', si: 'f', diff --git a/dotcom-rendering/src/lib/result.node.test.ts b/dotcom-rendering/src/lib/result.node.test.ts index ab4a5e5cd9c..dc8e9269cf8 100644 --- a/dotcom-rendering/src/lib/result.node.test.ts +++ b/dotcom-rendering/src/lib/result.node.test.ts @@ -1,10 +1,10 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { literal, safeParse } from 'valibot'; import { error, fromValibot, ok, type Result } from './result'; -void nodeDescribe('ok', () => { - void nodeIt('creates an instance of Ok', () => { +void describe('ok', () => { + void it('creates an instance of Ok', () => { const result = ok(3); const value = result.getOrThrow('Expected an Ok'); @@ -13,8 +13,8 @@ void nodeDescribe('ok', () => { }); }); -void nodeDescribe('error', () => { - void nodeIt('creates an instance of Err', () => { +void describe('error', () => { + void it('creates an instance of Err', () => { const result = error('An error'); const err = result.getErrorOrThrow('Expected an Err'); @@ -23,22 +23,19 @@ void nodeDescribe('error', () => { }); }); -void nodeDescribe('flatMap', () => { +void describe('flatMap', () => { const f = (a: number): Result => ok(a + 1); const h = (): Result => error('h error'); - void nodeIt( - 'runs the function and unwraps the result when both Results are Ok', - () => { - const result = ok(3).flatMap(f); - const value = result.getOrThrow('Expected an Ok'); + void it('runs the function and unwraps the result when both Results are Ok', () => { + const result = ok(3).flatMap(f); + const value = result.getOrThrow('Expected an Ok'); - assert.equal(result.ok, true); - assert.equal(value, 4); - }, - ); + assert.equal(result.ok, true); + assert.equal(value, 4); + }); - void nodeIt('passes through the Err when the first Result is Err', () => { + void it('passes through the Err when the first Result is Err', () => { const result = error('error message').flatMap(f); const err = result.getErrorOrThrow('Expected an Err'); @@ -46,7 +43,7 @@ void nodeDescribe('flatMap', () => { assert.equal(err, 'error message'); }); - void nodeIt('passes through the Err when the second Result is Err', () => { + void it('passes through the Err when the second Result is Err', () => { const result = ok(3).flatMap(h); const err = result.getErrorOrThrow('Expected an Err'); @@ -54,7 +51,7 @@ void nodeDescribe('flatMap', () => { assert.equal(err, 'h error'); }); - void nodeIt('passes through the first Err when both are Err', () => { + void it('passes through the first Err when both are Err', () => { const result = error('error message').flatMap(h); const err = result.getErrorOrThrow('Expected an Err'); @@ -62,19 +59,19 @@ void nodeDescribe('flatMap', () => { assert.equal(err, 'error message'); }); - void nodeIt('obeys left identity law', () => { + void it('obeys left identity law', () => { const value = 3; assert.deepEqual(ok(value).flatMap(f), f(value)); }); - void nodeIt('obeys right identity law', () => { + void it('obeys right identity law', () => { const result = ok(3); assert.deepEqual(result.flatMap(ok), result); }); - void nodeIt('obeys associativity law', () => { + void it('obeys associativity law', () => { const result = ok(3); const g = (a: number): Result => ok(a * 3); @@ -85,10 +82,10 @@ void nodeDescribe('flatMap', () => { }); }); -void nodeDescribe('map', () => { +void describe('map', () => { const f = (a: number): number => a + 1; - void nodeIt('runs the function when Result is Ok', () => { + void it('runs the function when Result is Ok', () => { const result = ok(3).map(f); const value = result.getOrThrow('Expected an Ok'); @@ -96,7 +93,7 @@ void nodeDescribe('map', () => { assert.equal(value, 4); }); - void nodeIt('passes the error through when Result is Err', () => { + void it('passes the error through when Result is Err', () => { const result = error('error message').map(f); const err = result.getErrorOrThrow('Expected an Err'); @@ -104,7 +101,7 @@ void nodeDescribe('map', () => { assert.equal(err, 'error message'); }); - void nodeIt('obeys identity', () => { + void it('obeys identity', () => { const identity =
    (a: A): A => a; const value = 3; const result = ok(value); @@ -112,7 +109,7 @@ void nodeDescribe('map', () => { assert.deepEqual(result.map(identity), result); }); - void nodeIt('obeys composition', () => { + void it('obeys composition', () => { const g = (a: number): number => a * 3; const result = ok(3); @@ -123,30 +120,30 @@ void nodeDescribe('map', () => { }); }); -void nodeDescribe('mapError', () => { +void describe('mapError', () => { const f = (err: string): string => `An error: ${err}`; - void nodeIt('produces a new error if Err', () => { + void it('produces a new error if Err', () => { const err = error('error details'); assert.deepEqual(err.mapError(f), error('An error: error details')); }); - void nodeIt('does nothing if Ok', () => { + void it('does nothing if Ok', () => { const result = ok(3); assert.deepEqual(result.mapError(f), result); }); }); -void nodeDescribe('getOrThrow', () => { - void nodeIt('gets the value if Ok', () => { +void describe('getOrThrow', () => { + void it('gets the value if Ok', () => { const value = ok(3).getOrThrow('Expected an Ok'); assert.equal(value, 3); }); - void nodeIt('throws if Err', () => { + void it('throws if Err', () => { const result = error('An error'); assert.throws( @@ -156,14 +153,14 @@ void nodeDescribe('getOrThrow', () => { }); }); -void nodeDescribe('getErrorOrThrow', () => { - void nodeIt('gets the value if Err', () => { +void describe('getErrorOrThrow', () => { + void it('gets the value if Err', () => { const err = error('An error').getErrorOrThrow('Expected an Err'); assert.equal(err, 'An error'); }); - void nodeIt('throws if Ok', () => { + void it('throws if Ok', () => { const result = ok(3); assert.throws( @@ -173,10 +170,10 @@ void nodeDescribe('getErrorOrThrow', () => { }); }); -void nodeDescribe('fromValibot', () => { +void describe('fromValibot', () => { const schema = literal('string literal'); - void nodeIt('creates an Ok from a successful parse result', () => { + void it('creates an Ok from a successful parse result', () => { const valibotResult = safeParse(schema, 'string literal'); const result = fromValibot(valibotResult); @@ -185,7 +182,7 @@ void nodeDescribe('fromValibot', () => { assert.equal(value, 'string literal'); }); - void nodeIt('creates an Err from an unsuccessful parse result', () => { + void it('creates an Err from an unsuccessful parse result', () => { const valibotResult = safeParse(schema, 'invalid literal'); const result = fromValibot(valibotResult); diff --git a/dotcom-rendering/src/lib/sendTargetingParams.apps.node.test.ts b/dotcom-rendering/src/lib/sendTargetingParams.apps.node.test.ts index abc8e293b66..7adcf443677 100644 --- a/dotcom-rendering/src/lib/sendTargetingParams.apps.node.test.ts +++ b/dotcom-rendering/src/lib/sendTargetingParams.apps.node.test.ts @@ -1,80 +1,74 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { getTargetingParams } from './sendTargetingParams.apps'; -void nodeDescribe('getTargetingParams', () => { - void nodeIt( - 'extracts ad targeting params from editionCommercialProperties in the format Bridget consumes', - () => { - const testEditionCommercialProperties = { - adTargeting: [ - { - name: 'su', - value: ['0'], - }, - { - name: 'k', - value: [ - 'us-politics', - 'state-of-georgia', - 'us-crime', - 'us-news', - 'donaldtrump', - ], - }, - { - name: 'edition', - value: 'uk', - }, - { - name: 'tn', - value: ['news'], - }, - { - name: 'co', - value: ['sam-levin', 'hugo-lowell'], - }, - { - name: 'sh', - value: 'https://www.theguardian.com/p/zm6gk', - }, - { - name: 'p', - value: 'ng', - }, - { - name: 'ct', - value: 'article', - }, - { - name: 'url', - value: '/us-news/2023/aug/24/trump-surrender-georgia-jail-overturn-2020-election', - }, - ], - }; +void describe('getTargetingParams', () => { + void it('extracts ad targeting params from editionCommercialProperties in the format Bridget consumes', () => { + const testEditionCommercialProperties = { + adTargeting: [ + { + name: 'su', + value: ['0'], + }, + { + name: 'k', + value: [ + 'us-politics', + 'state-of-georgia', + 'us-crime', + 'us-news', + 'donaldtrump', + ], + }, + { + name: 'edition', + value: 'uk', + }, + { + name: 'tn', + value: ['news'], + }, + { + name: 'co', + value: ['sam-levin', 'hugo-lowell'], + }, + { + name: 'sh', + value: 'https://www.theguardian.com/p/zm6gk', + }, + { + name: 'p', + value: 'ng', + }, + { + name: 'ct', + value: 'article', + }, + { + name: 'url', + value: '/us-news/2023/aug/24/trump-surrender-georgia-jail-overturn-2020-election', + }, + ], + }; - const expectedValue = new Map([ - ['ct', 'article'], - ['co', 'sam-levin,hugo-lowell'], - [ - 'url', - '/us-news/2023/aug/24/trump-surrender-georgia-jail-overturn-2020-election', - ], - ['su', '0'], - ['edition', 'uk'], - ['tn', 'news'], - ['p', 'app'], - ['rp', 'dotcom-rendering'], - [ - 'k', - 'us-politics,state-of-georgia,us-crime,us-news,donaldtrump', - ], - ]); + const expectedValue = new Map([ + ['ct', 'article'], + ['co', 'sam-levin,hugo-lowell'], + [ + 'url', + '/us-news/2023/aug/24/trump-surrender-georgia-jail-overturn-2020-election', + ], + ['su', '0'], + ['edition', 'uk'], + ['tn', 'news'], + ['p', 'app'], + ['rp', 'dotcom-rendering'], + ['k', 'us-politics,state-of-georgia,us-crime,us-news,donaldtrump'], + ]); - assert.deepEqual( - getTargetingParams(testEditionCommercialProperties), - expectedValue, - ); - }, - ); + assert.deepEqual( + getTargetingParams(testEditionCommercialProperties), + expectedValue, + ); + }); }); diff --git a/dotcom-rendering/src/lib/theFilter.node.test.ts b/dotcom-rendering/src/lib/theFilter.node.test.ts index 54cb8e70d7c..d30986f96f6 100644 --- a/dotcom-rendering/src/lib/theFilter.node.test.ts +++ b/dotcom-rendering/src/lib/theFilter.node.test.ts @@ -1,9 +1,9 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { isFilterPageId } from './theFilter'; -void nodeDescribe('isFilterPageId', () => { - void nodeIt('returns true for a UK Filter article pageId', () => { +void describe('isFilterPageId', () => { + void it('returns true for a UK Filter article pageId', () => { assert.equal( isFilterPageId( 'thefilter/2026/jul/02/jess-cartner-morleys-july-style-essentials-2026', @@ -12,7 +12,7 @@ void nodeDescribe('isFilterPageId', () => { ); }); - void nodeIt('returns true for a US Filter article pageId', () => { + void it('returns true for a US Filter article pageId', () => { assert.equal( isFilterPageId( 'thefilter-us/2025/dec/27/best-wine-subscriptions-us', @@ -21,20 +21,17 @@ void nodeDescribe('isFilterPageId', () => { ); }); - void nodeIt('returns false for a non-Filter pageId', () => { + void it('returns false for a non-Filter pageId', () => { assert.equal( isFilterPageId('technology/2026/jan/01/some-other-article'), false, ); }); - void nodeIt( - 'returns false for a pageId that merely contains "thefilter" mid-string', - () => { - assert.equal( - isFilterPageId('lifestyle/thefilter-mentioned/some-article'), - false, - ); - }, - ); + void it('returns false for a pageId that merely contains "thefilter" mid-string', () => { + assert.equal( + isFilterPageId('lifestyle/thefilter-mentioned/some-article'), + false, + ); + }); }); diff --git a/dotcom-rendering/src/lib/transparentColour.node.test.ts b/dotcom-rendering/src/lib/transparentColour.node.test.ts index 97dd2e8b539..0f0049786de 100644 --- a/dotcom-rendering/src/lib/transparentColour.node.test.ts +++ b/dotcom-rendering/src/lib/transparentColour.node.test.ts @@ -1,8 +1,8 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { transparentColour } from './transparentColour'; -void nodeDescribe('transparentColour', () => { +void describe('transparentColour', () => { const validHexColours = [ ['#000000', 'rgba(0, 0, 0, 0.5)'], ['#C70000', 'rgba(199, 0, 0, 0.5)'], @@ -11,7 +11,7 @@ void nodeDescribe('transparentColour', () => { ] as const; for (const [hex, output] of validHexColours) { - void nodeIt(`For valid hex ${hex}, return ${output}`, () => { + void it(`For valid hex ${hex}, return ${output}`, () => { assert.equal(transparentColour(hex), output); }); } @@ -24,7 +24,7 @@ void nodeDescribe('transparentColour', () => { ] as const; for (const [hex, output] of shortHexColours) { - void nodeIt(`For short hex ${hex}, return ${output}`, () => { + void it(`For short hex ${hex}, return ${output}`, () => { assert.equal(transparentColour(hex), output); }); } @@ -39,14 +39,8 @@ void nodeDescribe('transparentColour', () => { ]; for (const hex of invalidHexColours) { - void nodeIt( - `For invalid hex ${hex}, return rgba(127, 127, 127, 0.5)`, - () => { - assert.equal( - transparentColour(hex), - 'rgba(127, 127, 127, 0.5)', - ); - }, - ); + void it(`For invalid hex ${hex}, return rgba(127, 127, 127, 0.5)`, () => { + assert.equal(transparentColour(hex), 'rgba(127, 127, 127, 0.5)'); + }); } }); diff --git a/dotcom-rendering/src/lib/tuple.node.test.ts b/dotcom-rendering/src/lib/tuple.node.test.ts index ca9d9753dfc..f15ef9c3a9f 100644 --- a/dotcom-rendering/src/lib/tuple.node.test.ts +++ b/dotcom-rendering/src/lib/tuple.node.test.ts @@ -1,65 +1,62 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { isNonEmptyArray, takeFirst } from './tuple'; -void nodeDescribe('takeFirst', () => { - void nodeIt( - 'Always returns the correct array length when the array is one less, the same as, or one more than n', - () => { - const results = [ - // Format, from 1 to 12, cover length n - 1, n & n+1 - takeFirst([0, 1], 1), - takeFirst([0], 1), - takeFirst([], 1), - takeFirst([0, 1, 2], 2), - takeFirst([0, 1], 2), - takeFirst([0], 2), - takeFirst([0, 1, 2, 3], 3), - takeFirst([0, 1, 2], 3), - takeFirst([0, 1], 3), - takeFirst([0, 1, 2, 3, 4], 4), - takeFirst([0, 1, 2, 3], 4), - takeFirst([0, 1, 2], 4), - takeFirst([0, 1, 2, 3, 4, 5], 5), - takeFirst([0, 1, 2, 3, 4], 5), - takeFirst([0, 1, 2, 3], 5), - takeFirst([0, 1, 2, 3, 4, 5, 6], 6), - takeFirst([0, 1, 2, 3, 4, 5], 6), - takeFirst([0, 1, 2, 3, 4], 6), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7], 7), - takeFirst([0, 1, 2, 3, 4, 5, 6], 7), - takeFirst([0, 1, 2, 3, 4, 5], 7), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8], 8), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7], 8), - takeFirst([0, 1, 2, 3, 4, 5, 6], 8), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 9), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8], 9), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7], 9), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 10), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 10), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8], 10), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 11), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 11), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 11), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 12), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 12), - takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 12), - ] as const; +void describe('takeFirst', () => { + void it('Always returns the correct array length when the array is one less, the same as, or one more than n', () => { + const results = [ + // Format, from 1 to 12, cover length n - 1, n & n+1 + takeFirst([0, 1], 1), + takeFirst([0], 1), + takeFirst([], 1), + takeFirst([0, 1, 2], 2), + takeFirst([0, 1], 2), + takeFirst([0], 2), + takeFirst([0, 1, 2, 3], 3), + takeFirst([0, 1, 2], 3), + takeFirst([0, 1], 3), + takeFirst([0, 1, 2, 3, 4], 4), + takeFirst([0, 1, 2, 3], 4), + takeFirst([0, 1, 2], 4), + takeFirst([0, 1, 2, 3, 4, 5], 5), + takeFirst([0, 1, 2, 3, 4], 5), + takeFirst([0, 1, 2, 3], 5), + takeFirst([0, 1, 2, 3, 4, 5, 6], 6), + takeFirst([0, 1, 2, 3, 4, 5], 6), + takeFirst([0, 1, 2, 3, 4], 6), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7], 7), + takeFirst([0, 1, 2, 3, 4, 5, 6], 7), + takeFirst([0, 1, 2, 3, 4, 5], 7), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8], 8), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7], 8), + takeFirst([0, 1, 2, 3, 4, 5, 6], 8), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 9), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8], 9), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7], 9), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 10), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 10), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8], 10), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 11), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 11), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 11), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 12), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 12), + takeFirst([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 12), + ] as const; - const expectedLengths = [ - 1, 1, 0, 2, 2, 1, 3, 3, 2, 4, 4, 3, 5, 5, 4, 6, 6, 5, 7, 7, 6, - 8, 8, 7, 9, 9, 8, 10, 10, 9, 11, 11, 10, 12, 12, 11, - ]; + const expectedLengths = [ + 1, 1, 0, 2, 2, 1, 3, 3, 2, 4, 4, 3, 5, 5, 4, 6, 6, 5, 7, 7, 6, 8, 8, + 7, 9, 9, 8, 10, 10, 9, 11, 11, 10, 12, 12, 11, + ]; - assert.deepEqual( - results.map((result) => result.length), - expectedLengths, - ); - }, - ); + assert.deepEqual( + results.map((result) => result.length), + expectedLengths, + ); + }); }); -void nodeIt('isNonEmptyArray', () => { +void it('isNonEmptyArray', () => { assert.equal(isNonEmptyArray([]), false); assert.equal(isNonEmptyArray([1]), true); assert.equal(isNonEmptyArray([1, 2, 3]), true); diff --git a/dotcom-rendering/src/lib/video.node.test.ts b/dotcom-rendering/src/lib/video.node.test.ts index 70939fc4afb..9fd51331b8c 100644 --- a/dotcom-rendering/src/lib/video.node.test.ts +++ b/dotcom-rendering/src/lib/video.node.test.ts @@ -1,5 +1,5 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import type { FEMediaAsset } from '../frontend/feFront'; import type { VideoAssets } from '../types/content'; import type { Source } from './video'; @@ -90,9 +90,9 @@ const m3u8Src720h: Source = { hasAudio: true, }; -void nodeDescribe('video', () => { - void nodeDescribe('extractValidSourcesFromAssets', () => { - void nodeIt('should drop unsupported assets', () => { +void describe('video', () => { + void describe('extractValidSourcesFromAssets', () => { + void it('should drop unsupported assets', () => { const assets = [mp4Asset480w, m3u8Asset720h, unsupportedAsset]; const expected = [mp4Src480w, m3u8Src720h]; @@ -102,57 +102,48 @@ void nodeDescribe('video', () => { ); }); - void nodeIt( - 'should reorder sources by supportedVideoFileTypes order', - () => { - const assets = [ - m3u8Asset720h, - mp4Asset480w, - m3u8Asset720h, - mp4Asset720h, - m3u8Asset720h, - ]; - const expected = [ - mp4Src480w, - mp4Src720h, - m3u8Src720h, - m3u8Src720h, - m3u8Src720h, - ]; - assert.deepEqual( - extractValidSourcesFromAssets(assets, 'Loop'), - expected, - ); - }, - ); + void it('should reorder sources by supportedVideoFileTypes order', () => { + const assets = [ + m3u8Asset720h, + mp4Asset480w, + m3u8Asset720h, + mp4Asset720h, + m3u8Asset720h, + ]; + const expected = [ + mp4Src480w, + mp4Src720h, + m3u8Src720h, + m3u8Src720h, + m3u8Src720h, + ]; + assert.deepEqual( + extractValidSourcesFromAssets(assets, 'Loop'), + expected, + ); + }); - void nodeIt( - 'should prefer M3U8 sources for long videos with Default video style', - () => { - const assets = [mp4Asset480w, m3u8Asset720h, mp4Asset720h]; - const expected = [m3u8Src720h, mp4Src480w, mp4Src720h]; + void it('should prefer M3U8 sources for long videos with Default video style', () => { + const assets = [mp4Asset480w, m3u8Asset720h, mp4Asset720h]; + const expected = [m3u8Src720h, mp4Src480w, mp4Src720h]; - assert.deepEqual( - extractValidSourcesFromAssets(assets, 'Default', 37), - expected, - ); - }, - ); + assert.deepEqual( + extractValidSourcesFromAssets(assets, 'Default', 37), + expected, + ); + }); - void nodeIt( - 'should prefer MP4 sources for short videos with Default video style', - () => { - const assets = [mp4Asset480w, m3u8Asset720h, mp4Asset720h]; - const expected = [mp4Src480w, mp4Src720h, m3u8Src720h]; + void it('should prefer MP4 sources for short videos with Default video style', () => { + const assets = [mp4Asset480w, m3u8Asset720h, mp4Asset720h]; + const expected = [mp4Src480w, mp4Src720h, m3u8Src720h]; - assert.deepEqual( - extractValidSourcesFromAssets(assets, 'Default', 12), - expected, - ); - }, - ); + assert.deepEqual( + extractValidSourcesFromAssets(assets, 'Default', 12), + expected, + ); + }); - void nodeIt('should prefer MP4 sources with Loop video style', () => { + void it('should prefer MP4 sources with Loop video style', () => { const assets = [mp4Asset480w, m3u8Asset720h, mp4Asset720h]; const expected = [mp4Src480w, mp4Src720h, m3u8Src720h]; @@ -162,21 +153,18 @@ void nodeDescribe('video', () => { ); }); - void nodeIt( - 'should prefer MP4 sources with Cinemagraph video style', - () => { - const assets = [mp4Asset480w, m3u8Asset720h, mp4Asset720h]; - const expected = [mp4Src480w, mp4Src720h, m3u8Src720h]; + void it('should prefer MP4 sources with Cinemagraph video style', () => { + const assets = [mp4Asset480w, m3u8Asset720h, mp4Asset720h]; + const expected = [mp4Src480w, mp4Src720h, m3u8Src720h]; - assert.deepEqual( - extractValidSourcesFromAssets(assets, 'Cinemagraph'), - expected, - ); - }, - ); + assert.deepEqual( + extractValidSourcesFromAssets(assets, 'Cinemagraph'), + expected, + ); + }); }); - void nodeDescribe('convertFEMediaAssetsToVideoAssets', () => { + void describe('convertFEMediaAssetsToVideoAssets', () => { const feMediaAsset480w: FEMediaAsset = { id: 'https://guim-example.co.uk/atomID-1_480w.mp4', version: 1, @@ -202,7 +190,7 @@ void nodeDescribe('video', () => { hasAudio: true, }; - void nodeIt('should convert FE media assets to video assets', () => { + void it('should convert FE media assets to video assets', () => { assert.deepEqual( convertFEMediaAssetsToVideoAssets([ feMediaAsset480w, @@ -233,91 +221,70 @@ void nodeDescribe('video', () => { ); }); - void nodeIt( - 'should return an empty array when given an empty array', - () => { - assert.deepEqual(convertFEMediaAssetsToVideoAssets([]), []); - }, - ); + void it('should return an empty array when given an empty array', () => { + assert.deepEqual(convertFEMediaAssetsToVideoAssets([]), []); + }); }); - void nodeDescribe('getAspectRatioFromSources', () => { - void nodeIt( - 'should return the aspect ratio from the first source if it is defined', - () => { - const testSource: Source = { - ...mp4Src480w, - height: 720, - width: 480, - aspectRatio: '5:3', - hasAudio: true, - }; + void describe('getAspectRatioFromSources', () => { + void it('should return the aspect ratio from the first source if it is defined', () => { + const testSource: Source = { + ...mp4Src480w, + height: 720, + width: 480, + aspectRatio: '5:3', + hasAudio: true, + }; - const fiveThreeAspectRatio = 1.667; + const fiveThreeAspectRatio = 1.667; - assert.deepEqual( - getAspectRatioFromSources([testSource]), - fiveThreeAspectRatio, - ); - }, - ); + assert.deepEqual( + getAspectRatioFromSources([testSource]), + fiveThreeAspectRatio, + ); + }); - void nodeIt( - 'should calculate the aspect ratio from the width and height if aspect ratio is missing', - () => { - const testSource: Source = { - ...mp4Src480w, - height: 720, - width: 480, - aspectRatio: undefined, - hasAudio: true, - }; + void it('should calculate the aspect ratio from the width and height if aspect ratio is missing', () => { + const testSource: Source = { + ...mp4Src480w, + height: 720, + width: 480, + aspectRatio: undefined, + hasAudio: true, + }; - const twoThreeAspectRatio = 0.667; + const twoThreeAspectRatio = 0.667; - assert.deepEqual( - getAspectRatioFromSources([testSource]), - twoThreeAspectRatio, - ); - }, - ); - - void nodeIt( - 'should return the default aspect ratio if the aspect ratio is undefined and width is 0', - () => { - const testSource: Source = { - ...mp4Src480w, - height: 720, - width: 0, - aspectRatio: undefined, - hasAudio: true, - }; - assert.deepEqual( - getAspectRatioFromSources([testSource]), - 5 / 4, - ); - }, - ); - - void nodeIt( - 'should return the default aspect ratio if the aspect ratio is undefined and height is 0', - () => { - const testSource: Source = { - ...mp4Src480w, - height: 0, - width: 480, - aspectRatio: undefined, - hasAudio: true, - }; - assert.deepEqual( - getAspectRatioFromSources([testSource]), - 5 / 4, - ); - }, - ); + assert.deepEqual( + getAspectRatioFromSources([testSource]), + twoThreeAspectRatio, + ); + }); + + void it('should return the default aspect ratio if the aspect ratio is undefined and width is 0', () => { + const testSource: Source = { + ...mp4Src480w, + height: 720, + width: 0, + aspectRatio: undefined, + hasAudio: true, + }; + assert.deepEqual(getAspectRatioFromSources([testSource]), 5 / 4); + }); + + void it('should return the default aspect ratio if the aspect ratio is undefined and height is 0', () => { + const testSource: Source = { + ...mp4Src480w, + height: 0, + width: 480, + aspectRatio: undefined, + hasAudio: true, + }; + assert.deepEqual(getAspectRatioFromSources([testSource]), 5 / 4); + }); }); - void nodeDescribe('findOptimisedSourcePerMimeType', () => { + void describe('findOptimisedSourcePerMimeType', () => { const testSources: Source[] = [ mp4Src480w, mp4Src720h, @@ -325,78 +292,63 @@ void nodeDescribe('video', () => { m3u8Src720h, ]; - void nodeIt( - 'selects the smaller videos when there are multiple and all are larger than the screen width.', - () => { - const screenWidth = 400; + void it('selects the smaller videos when there are multiple and all are larger than the screen width.', () => { + const screenWidth = 400; - const sources = findOptimisedSourcePerMimeType( - testSources, - screenWidth, - ); + const sources = findOptimisedSourcePerMimeType( + testSources, + screenWidth, + ); - assert.deepEqual(sources, [mp4Src480w, m3u8Src480w]); - }, - ); + assert.deepEqual(sources, [mp4Src480w, m3u8Src480w]); + }); - void nodeIt( - 'selects the larger videos when there are two and one is larger than the screen width and one is smaller.', - () => { - const screenWidth = 600; + void it('selects the larger videos when there are two and one is larger than the screen width and one is smaller.', () => { + const screenWidth = 600; - const sources = findOptimisedSourcePerMimeType( - testSources, - screenWidth, - ); + const sources = findOptimisedSourcePerMimeType( + testSources, + screenWidth, + ); - assert.deepEqual(sources, [mp4Src720h, m3u8Src720h]); - }, - ); + assert.deepEqual(sources, [mp4Src720h, m3u8Src720h]); + }); - void nodeIt( - 'selects the larger videos when there are multiple and all are smaller than the screen width.', - () => { - const screenWidth = 800; + void it('selects the larger videos when there are multiple and all are smaller than the screen width.', () => { + const screenWidth = 800; - const sources = findOptimisedSourcePerMimeType( - testSources, - screenWidth, - ); + const sources = findOptimisedSourcePerMimeType( + testSources, + screenWidth, + ); - assert.deepEqual(sources, [mp4Src720h, m3u8Src720h]); - }, - ); + assert.deepEqual(sources, [mp4Src720h, m3u8Src720h]); + }); - void nodeIt( - 'selects the smaller videos when some are equal to the screen width and others are larger.', - () => { - const screenWidth = 480; + void it('selects the smaller videos when some are equal to the screen width and others are larger.', () => { + const screenWidth = 480; - const sources = findOptimisedSourcePerMimeType( - testSources, - screenWidth, - ); + const sources = findOptimisedSourcePerMimeType( + testSources, + screenWidth, + ); - assert.deepEqual(sources, [mp4Src480w, m3u8Src480w]); - }, - ); + assert.deepEqual(sources, [mp4Src480w, m3u8Src480w]); + }); - void nodeIt( - 'selects the larger videos when some are equal to the screen width and others are smaller.', - () => { - const screenWidth = 720; + void it('selects the larger videos when some are equal to the screen width and others are smaller.', () => { + const screenWidth = 720; - const sources = findOptimisedSourcePerMimeType( - testSources, - screenWidth, - ); + const sources = findOptimisedSourcePerMimeType( + testSources, + screenWidth, + ); - assert.deepEqual(sources, [mp4Src720h, m3u8Src720h]); - }, - ); + assert.deepEqual(sources, [mp4Src720h, m3u8Src720h]); + }); }); - void nodeDescribe('convertCurrentTimeToProgressPercentage', () => { + void describe('convertCurrentTimeToProgressPercentage', () => { for (const testCase of [ { currentTime: 0, duration: 23, expectedPercentage: 0 }, { currentTime: 24, duration: 32, expectedPercentage: 75 }, @@ -405,24 +357,20 @@ void nodeDescribe('video', () => { { currentTime: -5, duration: 10, expectedPercentage: null }, { currentTime: 5, duration: -10, expectedPercentage: null }, ]) { - void nodeIt( - 'should return the correct progress percentage based on the current time and duration', - () => { - const { currentTime, duration, expectedPercentage } = - testCase; - assert.deepEqual( - convertCurrentTimeToProgressPercentage( - currentTime, - duration, - ), - expectedPercentage, - ); - }, - ); + void it('should return the correct progress percentage based on the current time and duration', () => { + const { currentTime, duration, expectedPercentage } = testCase; + assert.deepEqual( + convertCurrentTimeToProgressPercentage( + currentTime, + duration, + ), + expectedPercentage, + ); + }); } }); - void nodeDescribe('convertProgressPercentageToCurrentTime', () => { + void describe('convertProgressPercentageToCurrentTime', () => { for (const testCase of [ { progressPercentage: 0, duration: 23, expectedCurrentTime: 0 }, { progressPercentage: 75, duration: 32, expectedCurrentTime: 24 }, @@ -436,27 +384,21 @@ void nodeDescribe('video', () => { expectedCurrentTime: 0, }, ]) { - void nodeIt( - 'should return the correct current time based on the progress percentage and duration', - () => { - const { + void it('should return the correct current time based on the progress percentage and duration', () => { + const { progressPercentage, duration, expectedCurrentTime } = + testCase; + assert.deepEqual( + convertProgressPercentageToCurrentTime( progressPercentage, duration, - expectedCurrentTime, - } = testCase; - assert.deepEqual( - convertProgressPercentageToCurrentTime( - progressPercentage, - duration, - ), - expectedCurrentTime, - ); - }, - ); + ), + expectedCurrentTime, + ); + }); } }); - void nodeDescribe('formatTimeForDisplay', () => { + void describe('formatTimeForDisplay', () => { for (const testCase of [ { timeInSeconds: -1.24, expectedFormattedTime: '0:00' }, { timeInSeconds: 0, expectedFormattedTime: '0:00' }, @@ -467,36 +409,29 @@ void nodeDescribe('video', () => { { timeInSeconds: 1000, expectedFormattedTime: '16:40' }, { timeInSeconds: 10000, expectedFormattedTime: '166:40' }, ]) { - void nodeIt( - 'should return the correct formatted time based on the time in seconds', - () => { - const { timeInSeconds, expectedFormattedTime } = testCase; - assert.deepEqual( - formatTimeForDisplay(timeInSeconds), - expectedFormattedTime, - ); - }, - ); + void it('should return the correct formatted time based on the time in seconds', () => { + const { timeInSeconds, expectedFormattedTime } = testCase; + assert.deepEqual( + formatTimeForDisplay(timeInSeconds), + expectedFormattedTime, + ); + }); } }); - void nodeDescribe('roundAspectRatio', () => { + void describe('roundAspectRatio', () => { for (const testCase of [ { aspectRatio: 0.56938445, expectedRoundedAspectRatio: 0.569 }, { aspectRatio: 1.277777, expectedRoundedAspectRatio: 1.278 }, { aspectRatio: 1.25, expectedRoundedAspectRatio: 1.25 }, { aspectRatio: 0.8, expectedRoundedAspectRatio: 0.8 }, ]) { - void nodeIt( - 'should return the correct aspect ratio rounded to 3 decimal places', - () => { - const { aspectRatio, expectedRoundedAspectRatio } = - testCase; - assert.deepEqual( - roundAspectRatio(aspectRatio), - expectedRoundedAspectRatio, - ); - }, - ); + void it('should return the correct aspect ratio rounded to 3 decimal places', () => { + const { aspectRatio, expectedRoundedAspectRatio } = testCase; + assert.deepEqual( + roundAspectRatio(aspectRatio), + expectedRoundedAspectRatio, + ); + }); } }); }); diff --git a/dotcom-rendering/src/model/article-sections.node.test.ts b/dotcom-rendering/src/model/article-sections.node.test.ts index 2bb70a722e8..e635459e027 100644 --- a/dotcom-rendering/src/model/article-sections.node.test.ts +++ b/dotcom-rendering/src/model/article-sections.node.test.ts @@ -1,8 +1,8 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { findBySubsection } from './article-sections'; -void nodeDescribe('returns section for each subsection', () => { +void describe('returns section for each subsection', () => { const testCases = [ [[], 'Guardian'], [['books', 'childrens-books-site'], 'Books'], @@ -91,7 +91,7 @@ void nodeDescribe('returns section for each subsection', () => { [['tv-and-radio'], 'TvRadio'], ] as const; - void nodeIt('returns correct Section for each test case', () => { + void it('returns correct Section for each test case', () => { for (const [subsections, section] of testCases) { for (const subsection of subsections) { assert.equal(findBySubsection(subsection).name, section); diff --git a/dotcom-rendering/src/model/buildLightboxImages.node.test.ts b/dotcom-rendering/src/model/buildLightboxImages.node.test.ts index fc6e83ee503..a263a9bd793 100644 --- a/dotcom-rendering/src/model/buildLightboxImages.node.test.ts +++ b/dotcom-rendering/src/model/buildLightboxImages.node.test.ts @@ -1,5 +1,5 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { Standard as ExampleArticle } from '../../fixtures/generated/fe-articles/Standard'; import { images } from '../../fixtures/generated/images'; import type { Block } from '../types/blocks'; @@ -77,42 +77,35 @@ const buildBlock = (elements: FEElement[]): Block => ({ secondaryDateLine: '', }); -void nodeDescribe('buildLightboxImages', () => { - void nodeIt( - "includes a product's own image when it is large enough", - () => { - const product: ProductBlockElement = { - ...baseProduct, - image: largeProductImage, - }; - - const result = buildLightboxImages( - format, - [buildBlock([product])], - [], - ); - - assert.equal(result.length, 1); - assert.deepEqual( - { - masterUrl: result[0]?.masterUrl, - elementId: result[0]?.elementId, - width: result[0]?.width, - height: result[0]?.height, - position: result[0]?.position, - }, - { - masterUrl: largeProductImage.url, - elementId: product.elementId, - width: largeProductImage.width, - height: largeProductImage.height, - position: 1, - }, - ); - }, - ); - - void nodeIt("excludes a product's own image when it is too small", () => { +void describe('buildLightboxImages', () => { + void it("includes a product's own image when it is large enough", () => { + const product: ProductBlockElement = { + ...baseProduct, + image: largeProductImage, + }; + + const result = buildLightboxImages(format, [buildBlock([product])], []); + + assert.equal(result.length, 1); + assert.deepEqual( + { + masterUrl: result[0]?.masterUrl, + elementId: result[0]?.elementId, + width: result[0]?.width, + height: result[0]?.height, + position: result[0]?.position, + }, + { + masterUrl: largeProductImage.url, + elementId: product.elementId, + width: largeProductImage.width, + height: largeProductImage.height, + position: 1, + }, + ); + }); + + void it("excludes a product's own image when it is too small", () => { const product: ProductBlockElement = { ...baseProduct, image: smallProductImage, @@ -123,7 +116,7 @@ void nodeDescribe('buildLightboxImages', () => { assert.deepEqual(result, []); }); - void nodeIt('excludes a product with no image', () => { + void it('excludes a product with no image', () => { const result = buildLightboxImages( format, [buildBlock([baseProduct])], @@ -133,7 +126,7 @@ void nodeDescribe('buildLightboxImages', () => { assert.deepEqual(result, []); }); - void nodeIt("includes images nested inside a product's content", () => { + void it("includes images nested inside a product's content", () => { const product: ProductBlockElement = { ...baseProduct, content: [largeImage], @@ -145,34 +138,27 @@ void nodeDescribe('buildLightboxImages', () => { assert.deepEqual(result[0]?.elementId, largeImage.elementId); }); - void nodeIt( - 'assigns positions in document order across regular and product images', - () => { - const product: ProductBlockElement = { - ...baseProduct, - elementId: 'product-2', - image: largeProductImage, - content: [largeImage], - }; - - const result = buildLightboxImages( - format, - [buildBlock([product])], - [], - ); - - assert.deepEqual( - result.map((image) => image.elementId), - [largeImage.elementId, product.elementId], - ); - assert.deepEqual( - result.map((image) => image.position), - [1, 2], - ); - }, - ); - - void nodeIt("includes a product's own CTAs on its card image", () => { + void it('assigns positions in document order across regular and product images', () => { + const product: ProductBlockElement = { + ...baseProduct, + elementId: 'product-2', + image: largeProductImage, + content: [largeImage], + }; + + const result = buildLightboxImages(format, [buildBlock([product])], []); + + assert.deepEqual( + result.map((image) => image.elementId), + [largeImage.elementId, product.elementId], + ); + assert.deepEqual( + result.map((image) => image.position), + [1, 2], + ); + }); + + void it("includes a product's own CTAs on its card image", () => { const product: ProductBlockElement = { ...baseProduct, image: largeProductImage, @@ -184,7 +170,7 @@ void nodeDescribe('buildLightboxImages', () => { assert.deepEqual(result[0]?.productCtas, productCtas); }); - void nodeIt('omits productCtas entirely when a product has none', () => { + void it('omits productCtas entirely when a product has none', () => { const product: ProductBlockElement = { ...baseProduct, image: largeProductImage, @@ -196,227 +182,190 @@ void nodeDescribe('buildLightboxImages', () => { assert.equal(result[0]?.productCtas, undefined); }); - void nodeIt( - "includes the owning product's CTAs on an image nested inside its content", - () => { - const product: ProductBlockElement = { - ...baseProduct, - content: [largeImage], - productCtas, - }; - - const result = buildLightboxImages( - format, - [buildBlock([product])], - [], - ); - - assert.equal(result.length, 1); - assert.deepEqual(result[0]?.productCtas, productCtas); - }, - ); - - void nodeIt( - "uses the innermost product's CTAs for an image nested inside a product nested in another product's content", - () => { - const innerCtas: ProductCta[] = [ - { - url: 'https://example.com/inner', - text: '', - retailer: 'Inner', - price: '£5', - }, - ]; - const outerCtas: ProductCta[] = [ - { - url: 'https://example.com/outer', - text: '', - retailer: 'Outer', - price: '£50', - }, - ]; - const innerProduct: ProductBlockElement = { - ...baseProduct, - elementId: 'inner-product', - content: [largeImage], - productCtas: innerCtas, - }; - const outerProduct: ProductBlockElement = { - ...baseProduct, - elementId: 'outer-product', - content: [innerProduct], - productCtas: outerCtas, - }; - - const result = buildLightboxImages( - format, - [buildBlock([outerProduct])], - [], - ); - - assert.equal(result.length, 1); - assert.deepEqual(result[0]?.productCtas, innerCtas); - }, - ); - - void nodeIt( - "falls back to the product's own caption for a content image with no caption of its own", - () => { - const product: ProductBlockElement = { - ...baseProduct, - image: largeProductImage, - content: [largeImage], - }; - - const result = buildLightboxImages( - format, - [buildBlock([product])], - [], - ); - - const contentEntry = result.find( - (image) => image.elementId === largeImage.elementId, - ); - assert.deepEqual(contentEntry?.caption, largeProductImage.caption); - }, - ); - - void nodeIt( - "keeps a content image's own caption instead of falling back to the product's", - () => { - const imageWithOwnCaption = { - ...largeImage, - data: { - ...largeImage.data, - caption: "The image's own caption", - }, - }; - const product: ProductBlockElement = { - ...baseProduct, - image: largeProductImage, - content: [imageWithOwnCaption], - }; - - const result = buildLightboxImages( - format, - [buildBlock([product])], - [], - ); - - const contentEntry = result.find( - (image) => image.elementId === largeImage.elementId, - ); - assert.deepEqual(contentEntry?.caption, "The image's own caption"); - }, - ); - - void nodeIt( - "uses the innermost product's caption for an image nested inside a product nested in another product's content", - () => { - const innerProductImage: ProductImage = { - ...largeProductImage, - caption: 'Inner product caption', - }; - const outerProductImage: ProductImage = { + void it("includes the owning product's CTAs on an image nested inside its content", () => { + const product: ProductBlockElement = { + ...baseProduct, + content: [largeImage], + productCtas, + }; + + const result = buildLightboxImages(format, [buildBlock([product])], []); + + assert.equal(result.length, 1); + assert.deepEqual(result[0]?.productCtas, productCtas); + }); + + void it("uses the innermost product's CTAs for an image nested inside a product nested in another product's content", () => { + const innerCtas: ProductCta[] = [ + { + url: 'https://example.com/inner', + text: '', + retailer: 'Inner', + price: '£5', + }, + ]; + const outerCtas: ProductCta[] = [ + { + url: 'https://example.com/outer', + text: '', + retailer: 'Outer', + price: '£50', + }, + ]; + const innerProduct: ProductBlockElement = { + ...baseProduct, + elementId: 'inner-product', + content: [largeImage], + productCtas: innerCtas, + }; + const outerProduct: ProductBlockElement = { + ...baseProduct, + elementId: 'outer-product', + content: [innerProduct], + productCtas: outerCtas, + }; + + const result = buildLightboxImages( + format, + [buildBlock([outerProduct])], + [], + ); + + assert.equal(result.length, 1); + assert.deepEqual(result[0]?.productCtas, innerCtas); + }); + + void it("falls back to the product's own caption for a content image with no caption of its own", () => { + const product: ProductBlockElement = { + ...baseProduct, + image: largeProductImage, + content: [largeImage], + }; + + const result = buildLightboxImages(format, [buildBlock([product])], []); + + const contentEntry = result.find( + (image) => image.elementId === largeImage.elementId, + ); + assert.deepEqual(contentEntry?.caption, largeProductImage.caption); + }); + + void it("keeps a content image's own caption instead of falling back to the product's", () => { + const imageWithOwnCaption = { + ...largeImage, + data: { + ...largeImage.data, + caption: "The image's own caption", + }, + }; + const product: ProductBlockElement = { + ...baseProduct, + image: largeProductImage, + content: [imageWithOwnCaption], + }; + + const result = buildLightboxImages(format, [buildBlock([product])], []); + + const contentEntry = result.find( + (image) => image.elementId === largeImage.elementId, + ); + assert.deepEqual(contentEntry?.caption, "The image's own caption"); + }); + + void it("uses the innermost product's caption for an image nested inside a product nested in another product's content", () => { + const innerProductImage: ProductImage = { + ...largeProductImage, + caption: 'Inner product caption', + }; + const outerProductImage: ProductImage = { + ...largeProductImage, + caption: 'Outer product caption', + }; + const innerProduct: ProductBlockElement = { + ...baseProduct, + elementId: 'inner-product', + content: [largeImage], + image: innerProductImage, + }; + const outerProduct: ProductBlockElement = { + ...baseProduct, + elementId: 'outer-product', + content: [innerProduct], + image: outerProductImage, + }; + + const result = buildLightboxImages( + format, + [buildBlock([outerProduct])], + [], + ); + + const contentEntry = result.find( + (image) => image.elementId === largeImage.elementId, + ); + assert.deepEqual(contentEntry?.caption, innerProductImage.caption); + }); + + void it("keeps a product's content image and card image adjacent, rather than grouping all content images before all card images", () => { + const secondImage = images[1]; + + const productA: ProductBlockElement = { + ...baseProduct, + elementId: 'product-a', + image: largeProductImage, + content: [largeImage], + productCtas, + }; + const productB: ProductBlockElement = { + ...baseProduct, + elementId: 'product-b', + image: { ...largeProductImage, - caption: 'Outer product caption', - }; - const innerProduct: ProductBlockElement = { - ...baseProduct, - elementId: 'inner-product', - content: [largeImage], - image: innerProductImage, - }; - const outerProduct: ProductBlockElement = { - ...baseProduct, - elementId: 'outer-product', - content: [innerProduct], - image: outerProductImage, - }; - - const result = buildLightboxImages( - format, - [buildBlock([outerProduct])], - [], - ); - - const contentEntry = result.find( - (image) => image.elementId === largeImage.elementId, - ); - assert.deepEqual(contentEntry?.caption, innerProductImage.caption); - }, - ); - - void nodeIt( - "keeps a product's content image and card image adjacent, rather than grouping all content images before all card images", - () => { - const secondImage = images[1]; - - const productA: ProductBlockElement = { - ...baseProduct, - elementId: 'product-a', - image: largeProductImage, - content: [largeImage], - productCtas, - }; - const productB: ProductBlockElement = { - ...baseProduct, - elementId: 'product-b', - image: { - ...largeProductImage, - url: 'https://media.guim.co.uk/large-product-b/900.jpg', - }, - content: [secondImage], - productCtas, - }; - - const result = buildLightboxImages( - format, - [buildBlock([productA, productB])], - [], - ); - - assert.deepEqual( - result.map((image) => image.elementId), - [ - largeImage.elementId, - productA.elementId, - secondImage.elementId, - productB.elementId, - ], - ); - assert.deepEqual( - result.map((image) => image.position), - [1, 2, 3, 4], - ); - }, - ); - - void nodeIt( - "gives every sub-image of a MultiImageBlockElement the owning product's CTAs", - () => { - const multiImage: MultiImageBlockElement = { - _type: 'model.dotcomrendering.pageElements.MultiImageBlockElement', - elementId: 'multi-1', - images: [largeImage, { ...largeImage, elementId: 'image-2' }], - }; - const product: ProductBlockElement = { - ...baseProduct, - content: [multiImage], - productCtas, - }; - - const result = buildLightboxImages( - format, - [buildBlock([product])], - [], - ); - - assert.equal(result.length, 2); - assert.equal( - result.every((image) => image.productCtas === productCtas), - true, - ); - }, - ); + url: 'https://media.guim.co.uk/large-product-b/900.jpg', + }, + content: [secondImage], + productCtas, + }; + + const result = buildLightboxImages( + format, + [buildBlock([productA, productB])], + [], + ); + + assert.deepEqual( + result.map((image) => image.elementId), + [ + largeImage.elementId, + productA.elementId, + secondImage.elementId, + productB.elementId, + ], + ); + assert.deepEqual( + result.map((image) => image.position), + [1, 2, 3, 4], + ); + }); + + void it("gives every sub-image of a MultiImageBlockElement the owning product's CTAs", () => { + const multiImage: MultiImageBlockElement = { + _type: 'model.dotcomrendering.pageElements.MultiImageBlockElement', + elementId: 'multi-1', + images: [largeImage, { ...largeImage, elementId: 'image-2' }], + }; + const product: ProductBlockElement = { + ...baseProduct, + content: [multiImage], + productCtas, + }; + + const result = buildLightboxImages(format, [buildBlock([product])], []); + + assert.equal(result.length, 2); + assert.equal( + result.every((image) => image.productCtas === productCtas), + true, + ); + }); }); diff --git a/dotcom-rendering/src/model/enhance-ad-placeholders.node.test.ts b/dotcom-rendering/src/model/enhance-ad-placeholders.node.test.ts index 5d8fd1babfe..cebcfaad171 100644 --- a/dotcom-rendering/src/model/enhance-ad-placeholders.node.test.ts +++ b/dotcom-rendering/src/model/enhance-ad-placeholders.node.test.ts @@ -1,5 +1,5 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { ArticleDesign, ArticleDisplay, Pillar } from '../lib/articleFormat'; import type { AdPlaceholderBlockElement, @@ -75,8 +75,8 @@ const elementIsAdPlaceholder = ( 'model.dotcomrendering.pageElements.AdPlaceholderBlockElement'; // Tests -void nodeDescribe('enhanceAdPlaceholders', () => { - void nodeDescribe('for general articles', () => { +void describe('enhanceAdPlaceholders', () => { + void describe('for general articles', () => { const testCases = [ { paragraphs: 0, expectedPositions: [] }, { paragraphs: 1, expectedPositions: [] }, @@ -110,60 +110,9 @@ void nodeDescribe('enhanceAdPlaceholders', () => { ] satisfies Array<{ paragraphs: number; expectedPositions: number[] }>; for (const { paragraphs, expectedPositions } of testCases) { - void nodeDescribe( - `for ${paragraphs} paragraph(s) in an article`, - () => { - const elements = getTestParagraphElements(paragraphs); - const expectedPlaceholders = expectedPositions.length; - const input: FEElement[] = elements; - - const output = enhanceAdPlaceholders( - exampleFormat, - 'Apps', - false, - )(input); - const placeholderIndices = output.flatMap((el, idx) => - elementIsAdPlaceholder(el) ? [idx] : [], - ); - - void nodeIt( - `should insert ${expectedPlaceholders} ad placeholder(s)`, - () => { - assert.deepEqual( - placeholderIndices.length, - expectedPlaceholders, - ); - }, - ); - - if (expectedPlaceholders > 0) { - void nodeIt( - `should insert ad placeholder(s) in the expected position(s): ${expectedPositions.join( - ',', - )}`, - () => { - assert.deepEqual( - placeholderIndices, - expectedPositions, - ); - }, - ); - } - }, - ); - } - - void nodeIt( - 'should not insert an ad placeholder before an inline image element, but can insert it after the image', - () => { - const threeParagraphs = getTestParagraphElements(3); - - const elements = [ - ...threeParagraphs, - getInlineImageElement(), - ...threeParagraphs, - ]; - + void describe(`for ${paragraphs} paragraph(s) in an article`, () => { + const elements = getTestParagraphElements(paragraphs); + const expectedPlaceholders = expectedPositions.length; const input: FEElement[] = elements; const output = enhanceAdPlaceholders( @@ -171,107 +120,126 @@ void nodeDescribe('enhanceAdPlaceholders', () => { 'Apps', false, )(input); - const outputPlaceholders = output.filter( - elementIsAdPlaceholder, - ); - - assert.deepEqual(outputPlaceholders.length, 1); - const placeholderIndices = output.flatMap((el, idx) => elementIsAdPlaceholder(el) ? [idx] : [], ); - // Expect one placeholder to be present after the fourth element only - assert.deepEqual(placeholderIndices, [4]); - }, - ); + void it(`should insert ${expectedPlaceholders} ad placeholder(s)`, () => { + assert.deepEqual( + placeholderIndices.length, + expectedPlaceholders, + ); + }); + + if (expectedPlaceholders > 0) { + void it(`should insert ad placeholder(s) in the expected position(s): ${expectedPositions.join( + ',', + )}`, () => { + assert.deepEqual(placeholderIndices, expectedPositions); + }); + } + }); + } - void nodeIt( - 'should not insert an ad placeholder after a thumbnail image element', - () => { - const threeParagraphs = getTestParagraphElements(3); + void it('should not insert an ad placeholder before an inline image element, but can insert it after the image', () => { + const threeParagraphs = getTestParagraphElements(3); - const elements = [ - ...threeParagraphs, - getThumbnailImageElement(), - ...threeParagraphs, - ]; + const elements = [ + ...threeParagraphs, + getInlineImageElement(), + ...threeParagraphs, + ]; - const input: FEElement[] = elements; + const input: FEElement[] = elements; - const output = enhanceAdPlaceholders( - exampleFormat, - 'Apps', - false, - )(input); - const outputPlaceholders = output.filter( - elementIsAdPlaceholder, - ); + const output = enhanceAdPlaceholders( + exampleFormat, + 'Apps', + false, + )(input); + const outputPlaceholders = output.filter(elementIsAdPlaceholder); - assert.deepEqual(outputPlaceholders.length, 1); + assert.deepEqual(outputPlaceholders.length, 1); - const placeholderIndices = output.flatMap((el, idx) => - elementIsAdPlaceholder(el) ? [idx] : [], - ); + const placeholderIndices = output.flatMap((el, idx) => + elementIsAdPlaceholder(el) ? [idx] : [], + ); - // Expect one placeholder to be present after the fifth element only - assert.deepEqual(placeholderIndices, [5]); - }, - ); + // Expect one placeholder to be present after the fourth element only + assert.deepEqual(placeholderIndices, [4]); + }); - void nodeIt( - 'should not insert an ad placeholder after an element which is not an image or text', - () => { - const threeParagraphs = getTestParagraphElements(3); + void it('should not insert an ad placeholder after a thumbnail image element', () => { + const threeParagraphs = getTestParagraphElements(3); - const elements = [ - ...threeParagraphs, - getSubheadingElement(), - ...threeParagraphs, - ]; + const elements = [ + ...threeParagraphs, + getThumbnailImageElement(), + ...threeParagraphs, + ]; - const input: FEElement[] = elements; + const input: FEElement[] = elements; - const output = enhanceAdPlaceholders( - exampleFormat, - 'Apps', - false, - )(input); - const outputPlaceholders = output.filter( - elementIsAdPlaceholder, - ); + const output = enhanceAdPlaceholders( + exampleFormat, + 'Apps', + false, + )(input); + const outputPlaceholders = output.filter(elementIsAdPlaceholder); - assert.deepEqual(outputPlaceholders.length, 1); + assert.deepEqual(outputPlaceholders.length, 1); - const placeholderIndices = output.flatMap((el, idx) => - elementIsAdPlaceholder(el) ? [idx] : [], - ); + const placeholderIndices = output.flatMap((el, idx) => + elementIsAdPlaceholder(el) ? [idx] : [], + ); - // Expect one placeholder to be present after the fifth element only - assert.deepEqual(placeholderIndices, [5]); - }, - ); + // Expect one placeholder to be present after the fifth element only + assert.deepEqual(placeholderIndices, [5]); + }); - void nodeIt( - 'should not insert ad placeholders if shouldHideAds is true', - () => { - const input: FEElement[] = getTestParagraphElements(6); + void it('should not insert an ad placeholder after an element which is not an image or text', () => { + const threeParagraphs = getTestParagraphElements(3); - const output = enhanceAdPlaceholders( - exampleFormat, - 'Apps', - true, - )(input); - const outputPlaceholders = output.filter( - elementIsAdPlaceholder, - ); + const elements = [ + ...threeParagraphs, + getSubheadingElement(), + ...threeParagraphs, + ]; - assert.deepEqual(outputPlaceholders.length, 0); - }, - ); + const input: FEElement[] = elements; + + const output = enhanceAdPlaceholders( + exampleFormat, + 'Apps', + false, + )(input); + const outputPlaceholders = output.filter(elementIsAdPlaceholder); + + assert.deepEqual(outputPlaceholders.length, 1); + + const placeholderIndices = output.flatMap((el, idx) => + elementIsAdPlaceholder(el) ? [idx] : [], + ); + + // Expect one placeholder to be present after the fifth element only + assert.deepEqual(placeholderIndices, [5]); + }); + + void it('should not insert ad placeholders if shouldHideAds is true', () => { + const input: FEElement[] = getTestParagraphElements(6); + + const output = enhanceAdPlaceholders( + exampleFormat, + 'Apps', + true, + )(input); + const outputPlaceholders = output.filter(elementIsAdPlaceholder); + + assert.deepEqual(outputPlaceholders.length, 0); + }); }); - void nodeDescribe('for gallery articles', () => { + void describe('for gallery articles', () => { const testCases = [ { images: 0, expectedPositions: [] }, { images: 1, expectedPositions: [] }, @@ -289,83 +257,61 @@ void nodeDescribe('enhanceAdPlaceholders', () => { ] satisfies Array<{ images: number; expectedPositions: number[] }>; for (const { images, expectedPositions } of testCases) { - void nodeDescribe( - `for ${images} images(s) in a gallery article`, - () => { - const elements = getTestImageBlockElements(images); - const expectedPlaceholders = expectedPositions.length; - const input: FEElement[] = elements; - - const output = enhanceAdPlaceholders( - galleryFormat, - 'Apps', - false, - )(input); - const placeholderIndices = output.flatMap((el, idx) => - elementIsAdPlaceholder(el) ? [idx] : [], - ); - - void nodeIt( - `should insert ${expectedPlaceholders} ad placeholder(s)`, - () => { - assert.deepEqual( - placeholderIndices.length, - expectedPlaceholders, - ); - }, - ); - - if (expectedPlaceholders > 0) { - void nodeIt( - `should insert ad placeholder(s) in the expected position(s): ${expectedPositions.join( - ',', - )}`, - () => { - assert.deepEqual( - placeholderIndices, - expectedPositions, - ); - }, - ); - } - }, - ); - } - - void nodeIt( - 'should not insert ad placeholders if shouldHideAds is true', - () => { - const input: FEElement[] = getTestParagraphElements(6); + void describe(`for ${images} images(s) in a gallery article`, () => { + const elements = getTestImageBlockElements(images); + const expectedPlaceholders = expectedPositions.length; + const input: FEElement[] = elements; const output = enhanceAdPlaceholders( galleryFormat, 'Apps', - true, + false, )(input); - const outputPlaceholders = output.filter( - elementIsAdPlaceholder, + const placeholderIndices = output.flatMap((el, idx) => + elementIsAdPlaceholder(el) ? [idx] : [], ); - assert.deepEqual(outputPlaceholders.length, 0); - }, - ); + void it(`should insert ${expectedPlaceholders} ad placeholder(s)`, () => { + assert.deepEqual( + placeholderIndices.length, + expectedPlaceholders, + ); + }); + + if (expectedPlaceholders > 0) { + void it(`should insert ad placeholder(s) in the expected position(s): ${expectedPositions.join( + ',', + )}`, () => { + assert.deepEqual(placeholderIndices, expectedPositions); + }); + } + }); + } - void nodeIt( - 'should still insert ad placeholders if renderingTarget is web', - () => { - const input: FEElement[] = getTestParagraphElements(6); + void it('should not insert ad placeholders if shouldHideAds is true', () => { + const input: FEElement[] = getTestParagraphElements(6); - const output = enhanceAdPlaceholders( - galleryFormat, - 'Web', - false, - )(input); - const outputPlaceholders = output.filter( - elementIsAdPlaceholder, - ); + const output = enhanceAdPlaceholders( + galleryFormat, + 'Apps', + true, + )(input); + const outputPlaceholders = output.filter(elementIsAdPlaceholder); - assert.ok(outputPlaceholders.length > 0); - }, - ); + assert.deepEqual(outputPlaceholders.length, 0); + }); + + void it('should still insert ad placeholders if renderingTarget is web', () => { + const input: FEElement[] = getTestParagraphElements(6); + + const output = enhanceAdPlaceholders( + galleryFormat, + 'Web', + false, + )(input); + const outputPlaceholders = output.filter(elementIsAdPlaceholder); + + assert.ok(outputPlaceholders.length > 0); + }); }); }); diff --git a/dotcom-rendering/src/model/enhance-dots.node.test.ts b/dotcom-rendering/src/model/enhance-dots.node.test.ts index 07359deb207..2a98da3019c 100644 --- a/dotcom-rendering/src/model/enhance-dots.node.test.ts +++ b/dotcom-rendering/src/model/enhance-dots.node.test.ts @@ -1,43 +1,40 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import type { FEElement } from '../types/content'; import { enhanceDots } from './enhance-dots'; -void nodeDescribe('Middot Tests', () => { - void nodeIt( - 'Output should not be the same as input as dot has been replaced', - () => { - const input: FEElement[] = [ - { - _type: 'model.dotcomrendering.pageElements.TextBlockElement', - elementId: 'mockId', - html: '

    I am the first paragraph

    ', - }, - { - _type: 'model.dotcomrendering.pageElements.TextBlockElement', - elementId: 'mockId', - html: '

    • I should have a dot.

    ', - }, - ]; +void describe('Middot Tests', () => { + void it('Output should not be the same as input as dot has been replaced', () => { + const input: FEElement[] = [ + { + _type: 'model.dotcomrendering.pageElements.TextBlockElement', + elementId: 'mockId', + html: '

    I am the first paragraph

    ', + }, + { + _type: 'model.dotcomrendering.pageElements.TextBlockElement', + elementId: 'mockId', + html: '

    • I should have a dot.

    ', + }, + ]; - const expectedOutput: FEElement[] = [ - { - _type: 'model.dotcomrendering.pageElements.TextBlockElement', - elementId: 'mockId', - html: '

    I am the first paragraph

    ', - }, - { - _type: 'model.dotcomrendering.pageElements.TextBlockElement', - elementId: 'mockId', - html: '

    I should have have a dot.

    ', - }, - ]; + const expectedOutput: FEElement[] = [ + { + _type: 'model.dotcomrendering.pageElements.TextBlockElement', + elementId: 'mockId', + html: '

    I am the first paragraph

    ', + }, + { + _type: 'model.dotcomrendering.pageElements.TextBlockElement', + elementId: 'mockId', + html: '

    I should have have a dot.

    ', + }, + ]; - assert.notEqual(enhanceDots(input), expectedOutput); - }, - ); + assert.notEqual(enhanceDots(input), expectedOutput); + }); - void nodeIt('It does not incorrectly replace * with dot spans', () => { + void it('It does not incorrectly replace * with dot spans', () => { const input: FEElement[] = [ { _type: 'model.dotcomrendering.pageElements.TextBlockElement', diff --git a/dotcom-rendering/src/model/enhance-product-summary.node.test.ts b/dotcom-rendering/src/model/enhance-product-summary.node.test.ts index d4812538e03..e0ac850f68c 100644 --- a/dotcom-rendering/src/model/enhance-product-summary.node.test.ts +++ b/dotcom-rendering/src/model/enhance-product-summary.node.test.ts @@ -1,5 +1,5 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { enhanceProductSummary } from './enhance-product-summary'; import { findEnhancedProductSummary, @@ -7,97 +7,91 @@ import { productSummaryElement, } from './enhance-product-summary.test-helpers'; -void nodeDescribe('enhanceProductSummary', () => { - void nodeIt( - 'enhances product summary elements with its selected product elements', - () => { - const selectedIds = ['1', '2']; - const input = [ - productElement( - [ - 'https://www.homebase.co.uk/en-uk/tower-airx-t17166-5l-grey-single-basket-air-fryer-digital-air-fryer/p/0757395', - ], - '1', - ), - productElement( - [ - 'https://www.lakeland.co.uk/27537/lakeland-slimline-air-fryer-black-8l', - ], - '2', - ), - productElement( - [ - 'https://ninjakitchen.co.uk/product/ninja-double-stack-xl-9-5l-air-fryer-sl400uk-zidSL400UK', - ], - '3', - ), - productSummaryElement( - selectedIds.map((id) => ({ productId: id, ctaIndex: 0 })), - ), - ]; +void describe('enhanceProductSummary', () => { + void it('enhances product summary elements with its selected product elements', () => { + const selectedIds = ['1', '2']; + const input = [ + productElement( + [ + 'https://www.homebase.co.uk/en-uk/tower-airx-t17166-5l-grey-single-basket-air-fryer-digital-air-fryer/p/0757395', + ], + '1', + ), + productElement( + [ + 'https://www.lakeland.co.uk/27537/lakeland-slimline-air-fryer-black-8l', + ], + '2', + ), + productElement( + [ + 'https://ninjakitchen.co.uk/product/ninja-double-stack-xl-9-5l-air-fryer-sl400uk-zidSL400UK', + ], + '3', + ), + productSummaryElement( + selectedIds.map((id) => ({ productId: id, ctaIndex: 0 })), + ), + ]; - const output = enhanceProductSummary(input); + const output = enhanceProductSummary(input); - const enhancedProductSummaryElement = - findEnhancedProductSummary(output); + const enhancedProductSummaryElement = + findEnhancedProductSummary(output); - assert.equal(enhancedProductSummaryElement?.products.length, 2); - assert.deepEqual( - enhancedProductSummaryElement?.products.map( - (mapping) => mapping.productBlock.id, - ), - selectedIds, - ); - }, - ); + assert.equal(enhancedProductSummaryElement?.products.length, 2); + assert.deepEqual( + enhancedProductSummaryElement?.products.map( + (mapping) => mapping.productBlock.id, + ), + selectedIds, + ); + }); - void nodeIt( - 'enhances product summary elements with the correct CTA indices', - () => { - const summaryProducts = [ - { productId: '3', ctaIndex: 1 }, - { productId: '1', ctaIndex: 0 }, - ]; - const input = [ - productElement( - [ - 'https://www.homebase.co.uk/en-uk/tower-airx-t17166-5l-grey-single-basket-air-fryer-digital-air-fryer/p/0757395', - ], - '1', - ), - productElement( - [ - 'https://www.lakeland.co.uk/27537/lakeland-slimline-air-fryer-black-8l', - ], - '2', - ), - productElement( - [ - 'https://ninjakitchen.co.uk/product/ninja-double-stack-xl-9-5l-air-fryer-sl400uk-zidSL400UK', - ], - '3', - ), - productSummaryElement(summaryProducts), - ]; + void it('enhances product summary elements with the correct CTA indices', () => { + const summaryProducts = [ + { productId: '3', ctaIndex: 1 }, + { productId: '1', ctaIndex: 0 }, + ]; + const input = [ + productElement( + [ + 'https://www.homebase.co.uk/en-uk/tower-airx-t17166-5l-grey-single-basket-air-fryer-digital-air-fryer/p/0757395', + ], + '1', + ), + productElement( + [ + 'https://www.lakeland.co.uk/27537/lakeland-slimline-air-fryer-black-8l', + ], + '2', + ), + productElement( + [ + 'https://ninjakitchen.co.uk/product/ninja-double-stack-xl-9-5l-air-fryer-sl400uk-zidSL400UK', + ], + '3', + ), + productSummaryElement(summaryProducts), + ]; - const output = enhanceProductSummary(input); + const output = enhanceProductSummary(input); - const enhancedProductSummaryElement = - findEnhancedProductSummary(output); + const enhancedProductSummaryElement = + findEnhancedProductSummary(output); - assert.equal(enhancedProductSummaryElement?.products.length, 2); - assert.deepEqual( - enhancedProductSummaryElement?.products.map( - (mapping) => mapping.ctaIndex, - ), - [1, 0], - ); - assert.deepEqual( - enhancedProductSummaryElement?.products.map( - (mapping) => mapping.productBlock.id, - ), - ['3', '1'], - ); - }, - ); + assert.equal(enhancedProductSummaryElement?.products.length, 2); + assert.deepEqual( + enhancedProductSummaryElement?.products.map( + (mapping) => mapping.ctaIndex, + ), + [1, 0], + ); + assert.deepEqual( + enhancedProductSummaryElement?.products.map( + (mapping) => mapping.productBlock.id, + ), + ['3', '1'], + ); + }); }); diff --git a/dotcom-rendering/src/model/enhance-videos.node.test.ts b/dotcom-rendering/src/model/enhance-videos.node.test.ts index a4ebfde5b09..a2213b2d1c8 100644 --- a/dotcom-rendering/src/model/enhance-videos.node.test.ts +++ b/dotcom-rendering/src/model/enhance-videos.node.test.ts @@ -1,12 +1,12 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { ArticleDesign, type ArticleFormat } from '../lib/articleFormat'; import type { FEElement } from '../types/content'; import { enhanceGuVideos } from './enhance-videos'; -void nodeDescribe('Enhance Videos', () => { - void nodeDescribe('for GuVideoElement', () => { - void nodeIt('sets the html of the GuVideoBlockElement', () => { +void describe('Enhance Videos', () => { + void describe('for GuVideoElement', () => { + void it('sets the html of the GuVideoBlockElement', () => { const html = ''; const videoElement: FEElement = { diff --git a/dotcom-rendering/src/model/enhanceCommercialProperties.node.test.ts b/dotcom-rendering/src/model/enhanceCommercialProperties.node.test.ts index 10c5a976a8f..41d38fc8753 100644 --- a/dotcom-rendering/src/model/enhanceCommercialProperties.node.test.ts +++ b/dotcom-rendering/src/model/enhanceCommercialProperties.node.test.ts @@ -1,5 +1,5 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { Labs } from '../../fixtures/generated/fe-articles/Labs'; import { Standard } from '../../fixtures/generated/fe-articles/Standard'; import type { CommercialProperties } from '../types/commercial'; @@ -7,8 +7,8 @@ import { enhanceCommercialProperties } from './enhanceCommercialProperties'; const isNumber = (width: unknown): width is number => typeof width === 'number'; -void nodeDescribe('Enhance Branding', () => { - void nodeIt('does not change properties if they have no branding', () => { +void describe('Enhance Branding', () => { + void it('does not change properties if they have no branding', () => { const { commercialProperties } = Standard; assert.deepEqual( enhanceCommercialProperties(commercialProperties), @@ -16,7 +16,7 @@ void nodeDescribe('Enhance Branding', () => { ); }); - void nodeIt('should have no widths above 140', () => { + void it('should have no widths above 140', () => { const { commercialProperties: partial } = Labs; const commercialProperties: CommercialProperties = { ...partial, diff --git a/dotcom-rendering/src/model/enhanceLists.node.test.ts b/dotcom-rendering/src/model/enhanceLists.node.test.ts index d8cd0b0ca52..90842a34d17 100644 --- a/dotcom-rendering/src/model/enhanceLists.node.test.ts +++ b/dotcom-rendering/src/model/enhanceLists.node.test.ts @@ -1,11 +1,11 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import type { FEElement } from '../types/content'; import type { TagType } from '../types/tag'; import { enhanceLists } from './enhanceLists'; -void nodeDescribe('Enhance lists', () => { - void nodeIt('enhances a multi-byline element correctly', () => { +void describe('Enhance lists', () => { + void it('enhances a multi-byline element correctly', () => { const elementsEnhancer = (elements: FEElement[]): FEElement[] => elements; diff --git a/dotcom-rendering/src/model/enhanceTags.node.test.ts b/dotcom-rendering/src/model/enhanceTags.node.test.ts index 53ce635531f..54186af09e4 100644 --- a/dotcom-rendering/src/model/enhanceTags.node.test.ts +++ b/dotcom-rendering/src/model/enhanceTags.node.test.ts @@ -1,10 +1,10 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import type { FETagType } from '../types/tag'; import { enhanceTags } from './enhanceTags'; -void nodeDescribe('enhanceTags', () => { - void nodeIt('maps a list of FETagType to TagType', () => { +void describe('enhanceTags', () => { + void it('maps a list of FETagType to TagType', () => { const feTags: FETagType[] = [ { properties: { diff --git a/dotcom-rendering/src/model/enhanceTimeline.node.test.ts b/dotcom-rendering/src/model/enhanceTimeline.node.test.ts index 070fe785034..6050e307ccc 100644 --- a/dotcom-rendering/src/model/enhanceTimeline.node.test.ts +++ b/dotcom-rendering/src/model/enhanceTimeline.node.test.ts @@ -1,5 +1,5 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { images } from '../../fixtures/generated/images'; import type { FEElement } from '../../src/types/content'; import { enhanceTimeline } from './enhanceTimeline'; @@ -141,8 +141,8 @@ const elementsWithMultipleSections: FEElement[] = [ }, ]; -void nodeDescribe('enhanceTimeline', () => { - void nodeIt('keeps a main media with a role that is valid', () => { +void describe('enhanceTimeline', () => { + void it('keeps a main media with a role that is valid', () => { const enhanced = enhanceTimeline(identity)(elementsWithNoSections); assert.equal( enhanced[0]?._type, @@ -154,7 +154,7 @@ void nodeDescribe('enhanceTimeline', () => { assert.notEqual(timelineEvent?.main, undefined); }); - void nodeIt('drops a main media with a role that is not valid', () => { + void it('drops a main media with a role that is not valid', () => { const enhanced = enhanceTimeline(identity)(elementsWithNoSections); assert.equal( enhanced[0]?._type, @@ -166,7 +166,7 @@ void nodeDescribe('enhanceTimeline', () => { assert.equal(timelineEvent?.main, undefined); }); - void nodeIt('keeps a main media without a role', () => { + void it('keeps a main media without a role', () => { const enhanced = enhanceTimeline(identity)(elementsWithNoSections); assert.equal( enhanced[0]?._type, @@ -177,7 +177,7 @@ void nodeDescribe('enhanceTimeline', () => { assert.notEqual(timelineEvent, undefined); assert.notEqual(timelineEvent?.main, undefined); }); - void nodeIt('keeps a body element with a role that is valid', () => { + void it('keeps a body element with a role that is valid', () => { const enhanced = enhanceTimeline(identity)(elementsWithNoSections); assert.equal( enhanced[0]?._type, @@ -189,7 +189,7 @@ void nodeDescribe('enhanceTimeline', () => { assert.deepEqual(timelineEvent?.body, [images[1]]); }); - void nodeIt('drops a body element with a role that is not valid', () => { + void it('drops a body element with a role that is not valid', () => { const enhanced = enhanceTimeline(identity)(elementsWithNoSections); assert.equal( enhanced[0]?._type, @@ -201,7 +201,7 @@ void nodeDescribe('enhanceTimeline', () => { assert.deepEqual(timelineEvent?.body, []); }); - void nodeIt('keeps a body element without a role', () => { + void it('keeps a body element without a role', () => { const enhanced = enhanceTimeline(identity)(elementsWithNoSections); assert.equal( enhanced[0]?._type, @@ -220,7 +220,7 @@ void nodeDescribe('enhanceTimeline', () => { ]); }); - void nodeIt('enhances a timeline with one section appropriately', () => { + void it('enhances a timeline with one section appropriately', () => { const enhanced = enhanceTimeline(identity)(elementsWithOneSection); assert.equal( enhanced[0]?._type, @@ -232,19 +232,16 @@ void nodeDescribe('enhanceTimeline', () => { assert.deepEqual(timelineSection?.title, 'Section 1'); }); - void nodeIt( - 'enhances a timeline with multiple sections appropriately', - () => { - const enhanced = enhanceTimeline(identity)( - elementsWithMultipleSections, - ); - assert.equal( - enhanced[0]?._type, - 'model.dotcomrendering.pageElements.DCRSectionedTimelineBlockElement', - ); - - const timelineSections = enhanced[0].sections; - assert.equal(timelineSections.length, 2); - }, - ); + void it('enhances a timeline with multiple sections appropriately', () => { + const enhanced = enhanceTimeline(identity)( + elementsWithMultipleSections, + ); + assert.equal( + enhanced[0]?._type, + 'model.dotcomrendering.pageElements.DCRSectionedTimelineBlockElement', + ); + + const timelineSections = enhanced[0].sections; + assert.equal(timelineSections.length, 2); + }); }); diff --git a/dotcom-rendering/src/model/extractTrendingTopics.node.test.ts b/dotcom-rendering/src/model/extractTrendingTopics.node.test.ts index a06854b0145..344beb78336 100644 --- a/dotcom-rendering/src/model/extractTrendingTopics.node.test.ts +++ b/dotcom-rendering/src/model/extractTrendingTopics.node.test.ts @@ -1,5 +1,5 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import type { FETagType } from '../types/tag'; import type { NarrowedFECollectionType, @@ -45,8 +45,8 @@ const tagD = tag('d'); const tagE = tag('e'); const tagF = tag('f'); -void nodeDescribe('extractTrendingTopics', () => { - void nodeIt('returns tags in correct order with normal input', () => { +void describe('extractTrendingTopics', () => { + void it('returns tags in correct order with normal input', () => { const collection: NarrowedFECollectionType = { curated: [ card('a', [tagA]), @@ -66,7 +66,7 @@ void nodeDescribe('extractTrendingTopics', () => { ); }); - void nodeIt('deduplicates cards', () => { + void it('deduplicates cards', () => { const collection: NarrowedFECollectionType = { curated: [ card('a', [tagA]), @@ -94,7 +94,7 @@ void nodeDescribe('extractTrendingTopics', () => { ); }); - void nodeIt('removes cards with id matching pageId', () => { + void it('removes cards with id matching pageId', () => { const tagWithPageId = tag('au/environment'); const collection: NarrowedFECollectionType = { curated: [ @@ -114,55 +114,52 @@ void nodeDescribe('extractTrendingTopics', () => { ); }); - void nodeIt( - 'removes cards without paidContentType or tagType being Keyword or Topics', - () => { - const tagWithTopicsPaidContentType = tag( - 'tagWithTopicsPaidContentType', - '', - 'Topics', - ); - const tagWithKeywordPaidContentType = tag( - 'tagWithKeywordPaidContentType', - '', - 'Keyword', - ); - const tagWithKeywordTagType = tag('tagWithKeywordTagType'); - const tagWithNoneOfTheAbove = tag( - 'tagWithNoneOfTheAbove', - 'Series', - 'Series', - ); - const collection: NarrowedFECollectionType = { - curated: [ - card('a', [tagWithNoneOfTheAbove]), - card('b', [ - tagWithNoneOfTheAbove, - tagWithTopicsPaidContentType, - ]), - ], - backfill: [ - card('c', [ - tagWithNoneOfTheAbove, - tagWithTopicsPaidContentType, - tagWithKeywordPaidContentType, - ]), - card('d', [ - tagWithNoneOfTheAbove, - tagWithTopicsPaidContentType, - tagWithKeywordPaidContentType, - tagWithKeywordTagType, - ]), - ], - }; - assert.deepEqual( - extractTrendingTopicsFomFront([collection], 'au/environment'), - [ + void it('removes cards without paidContentType or tagType being Keyword or Topics', () => { + const tagWithTopicsPaidContentType = tag( + 'tagWithTopicsPaidContentType', + '', + 'Topics', + ); + const tagWithKeywordPaidContentType = tag( + 'tagWithKeywordPaidContentType', + '', + 'Keyword', + ); + const tagWithKeywordTagType = tag('tagWithKeywordTagType'); + const tagWithNoneOfTheAbove = tag( + 'tagWithNoneOfTheAbove', + 'Series', + 'Series', + ); + const collection: NarrowedFECollectionType = { + curated: [ + card('a', [tagWithNoneOfTheAbove]), + card('b', [ + tagWithNoneOfTheAbove, + tagWithTopicsPaidContentType, + ]), + ], + backfill: [ + card('c', [ + tagWithNoneOfTheAbove, + tagWithTopicsPaidContentType, + tagWithKeywordPaidContentType, + ]), + card('d', [ + tagWithNoneOfTheAbove, tagWithTopicsPaidContentType, tagWithKeywordPaidContentType, tagWithKeywordTagType, - ], - ); - }, - ); + ]), + ], + }; + assert.deepEqual( + extractTrendingTopicsFomFront([collection], 'au/environment'), + [ + tagWithTopicsPaidContentType, + tagWithKeywordPaidContentType, + tagWithKeywordTagType, + ], + ); + }); }); diff --git a/dotcom-rendering/src/model/groupTrailsByDates.node.test.ts b/dotcom-rendering/src/model/groupTrailsByDates.node.test.ts index d88c14d2c3d..0f72fc1b7ef 100644 --- a/dotcom-rendering/src/model/groupTrailsByDates.node.test.ts +++ b/dotcom-rendering/src/model/groupTrailsByDates.node.test.ts @@ -1,5 +1,5 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { trails } from '../../fixtures/manual/trails'; import type { DCRFrontCard } from '../types/front'; import { groupTrailsByDates } from './groupTrailsByDates'; @@ -11,54 +11,51 @@ const datesToTrails = (dates: Date[]): DCRFrontCard[] => { })); }; -void nodeDescribe('groupTrailsByDates', () => { - void nodeIt( - 'Will split trails into days & months depending on the frequency', - () => { - const dates = [ - // SHOULD BE GROUPED BY DAY - // 3 on the 23rd of June - new Date(2023, 5, 23, 12), - new Date(2023, 5, 23, 12), - new Date(2023, 5, 23, 12), - // 5 on the 25th of June - new Date(2023, 5, 25, 12), - new Date(2023, 5, 25, 12), - new Date(2023, 5, 25, 12), - new Date(2023, 5, 25, 12), - new Date(2023, 5, 25, 12), - // 7 on the 26th of June - new Date(2023, 5, 26, 12), - new Date(2023, 5, 26, 12), - new Date(2023, 5, 26, 12), - new Date(2023, 5, 26, 12), - new Date(2023, 5, 26, 12), - new Date(2023, 5, 26, 12), - - // SHOULD BE GROUPED BY MONTH - // 1 on the 2nd of May - new Date(2023, 4, 2, 12), - // 3 on 3rd of May - new Date(2023, 4, 3, 12), - new Date(2023, 4, 3, 12), - // 1 on 4th of May - new Date(2023, 4, 4, 12), - // 1 on 5th of May - new Date(2023, 4, 5, 12), - ]; - - const result = groupTrailsByDates(datesToTrails(dates), 'UK'); - - assert.deepEqual(result[0]?.day, '26'); - assert.deepEqual(result[1]?.day, '25'); - assert.deepEqual(result[2]?.day, '23'); - - assert.deepEqual(result[3]?.day, undefined); - assert.deepEqual(result[3]?.month, 'May'); - }, - ); - - void nodeIt('Will handle all editions', () => { +void describe('groupTrailsByDates', () => { + void it('Will split trails into days & months depending on the frequency', () => { + const dates = [ + // SHOULD BE GROUPED BY DAY + // 3 on the 23rd of June + new Date(2023, 5, 23, 12), + new Date(2023, 5, 23, 12), + new Date(2023, 5, 23, 12), + // 5 on the 25th of June + new Date(2023, 5, 25, 12), + new Date(2023, 5, 25, 12), + new Date(2023, 5, 25, 12), + new Date(2023, 5, 25, 12), + new Date(2023, 5, 25, 12), + // 7 on the 26th of June + new Date(2023, 5, 26, 12), + new Date(2023, 5, 26, 12), + new Date(2023, 5, 26, 12), + new Date(2023, 5, 26, 12), + new Date(2023, 5, 26, 12), + new Date(2023, 5, 26, 12), + + // SHOULD BE GROUPED BY MONTH + // 1 on the 2nd of May + new Date(2023, 4, 2, 12), + // 3 on 3rd of May + new Date(2023, 4, 3, 12), + new Date(2023, 4, 3, 12), + // 1 on 4th of May + new Date(2023, 4, 4, 12), + // 1 on 5th of May + new Date(2023, 4, 5, 12), + ]; + + const result = groupTrailsByDates(datesToTrails(dates), 'UK'); + + assert.deepEqual(result[0]?.day, '26'); + assert.deepEqual(result[1]?.day, '25'); + assert.deepEqual(result[2]?.day, '23'); + + assert.deepEqual(result[3]?.day, undefined); + assert.deepEqual(result[3]?.month, 'May'); + }); + + void it('Will handle all editions', () => { const dates = [ // The whole of the last day of June (months are 0-indexed) '2024-06-30T00:00:00Z', @@ -115,7 +112,7 @@ void nodeDescribe('groupTrailsByDates', () => { assert.equal(us[0]?.trails.length, 20); }); - void nodeIt('Will respect "forceDay" being set to true', () => { + void it('Will respect "forceDay" being set to true', () => { const dates = [ // This would be grouped by month if left to the pop out frequency // 1 on the 2nd of May diff --git a/dotcom-rendering/src/model/unwrapHtml.node.test.ts b/dotcom-rendering/src/model/unwrapHtml.node.test.ts index 3f0d4fcf2d7..d2dc09e182d 100644 --- a/dotcom-rendering/src/model/unwrapHtml.node.test.ts +++ b/dotcom-rendering/src/model/unwrapHtml.node.test.ts @@ -1,11 +1,11 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { unwrapHtml } from './unwrapHtml'; type Params = Parameters[0]; -void nodeDescribe('unwrapHtml', () => { - void nodeIt('Returns unwrapped HTML if prefix and suffix match', () => { +void describe('unwrapHtml', () => { + void it('Returns unwrapped HTML if prefix and suffix match', () => { // Blockquote, elements inside const bqUnwrap: Params = { html: '

    inner

    ', @@ -40,106 +40,99 @@ void nodeDescribe('unwrapHtml', () => { assert.equal(pUnwrappedHtml, 'inner'); }); - void nodeIt( - 'Returns non-unwrapped HTML if prefix and suffix do not match', - () => { - const bqUnwrap: Params = { - html: '

    inner

    ', - fixes: [ - { - prefix: '
    ', - suffix: '
    ', - }, - ], - }; - const { willUnwrap: isUnwrapped, unwrappedHtml } = - unwrapHtml(bqUnwrap); + void it('Returns non-unwrapped HTML if prefix and suffix do not match', () => { + const bqUnwrap: Params = { + html: '

    inner

    ', + fixes: [ + { + prefix: '
    ', + suffix: '
    ', + }, + ], + }; + const { willUnwrap: isUnwrapped, unwrappedHtml } = unwrapHtml(bqUnwrap); - assert.ok(!isUnwrapped); - assert.equal(unwrappedHtml, bqUnwrap.html); - }, - ); + assert.ok(!isUnwrapped); + assert.equal(unwrappedHtml, bqUnwrap.html); + }); - void nodeIt( - 'Returns wrapped HTML if prefix and suffix of one "fix" match from multiple options', - () => { - const bqUnwrap: Params = { - html: '

    inner

    ', - fixes: [ - { - prefix: '
    ', - suffix: '
    ', - unwrappedElement: 'blockquote', - }, - { - prefix: '

    ', - suffix: '

    ', - unwrappedElement: 'p', - }, - ], - }; + void it('Returns wrapped HTML if prefix and suffix of one "fix" match from multiple options', () => { + const bqUnwrap: Params = { + html: '

    inner

    ', + fixes: [ + { + prefix: '
    ', + suffix: '
    ', + unwrappedElement: 'blockquote', + }, + { + prefix: '

    ', + suffix: '

    ', + unwrappedElement: 'p', + }, + ], + }; - const { - willUnwrap: bqIsUnwrapped, - unwrappedHtml: bqUnwrappedHtml, - unwrappedElement: bqUnwrappedElement, - } = unwrapHtml(bqUnwrap); + const { + willUnwrap: bqIsUnwrapped, + unwrappedHtml: bqUnwrappedHtml, + unwrappedElement: bqUnwrappedElement, + } = unwrapHtml(bqUnwrap); - const pUnwrap: Params = { - html: '

    inner

    ', - fixes: [ - { - prefix: '

    ', - suffix: '

    ', - unwrappedElement: 'p', - }, - { - prefix: '
      ', - suffix: '
    ', - unwrappedElement: 'ul', - }, - ], - }; - const { - willUnwrap: pIsUnwrapped, - unwrappedHtml: pUnwrappedHtml, - unwrappedElement: pUnwrappedElement, - } = unwrapHtml(pUnwrap); + const pUnwrap: Params = { + html: '

    inner

    ', + fixes: [ + { + prefix: '

    ', + suffix: '

    ', + unwrappedElement: 'p', + }, + { + prefix: '
      ', + suffix: '
    ', + unwrappedElement: 'ul', + }, + ], + }; + const { + willUnwrap: pIsUnwrapped, + unwrappedHtml: pUnwrappedHtml, + unwrappedElement: pUnwrappedElement, + } = unwrapHtml(pUnwrap); - const ulUnwrap: Params = { - html: '
    • Test
    • test2
    ', - fixes: [ - { - prefix: '

    ', - suffix: '

    ', - unwrappedElement: 'p', - }, - { - prefix: '
      ', - suffix: '
    ', - unwrappedElement: 'ul', - }, - ], - }; + const ulUnwrap: Params = { + html: '
    • Test
    • test2
    ', + fixes: [ + { + prefix: '

    ', + suffix: '

    ', + unwrappedElement: 'p', + }, + { + prefix: '
      ', + suffix: '
    ', + unwrappedElement: 'ul', + }, + ], + }; - // Unwrap Unordered lists - const { - willUnwrap: ulIsUnwrapped, - unwrappedHtml: ulUnwrappedHtml, - unwrappedElement: ulUnwrappedElement, - } = unwrapHtml(ulUnwrap); + // Unwrap Unordered lists + const { + willUnwrap: ulIsUnwrapped, + unwrappedHtml: ulUnwrappedHtml, + unwrappedElement: ulUnwrappedElement, + } = unwrapHtml(ulUnwrap); - assert.ok(bqIsUnwrapped); - assert.equal(bqUnwrappedHtml, '

    inner

    '); - assert.equal(bqUnwrappedElement, 'blockquote'); + assert.ok(bqIsUnwrapped); + assert.equal(bqUnwrappedHtml, '

    inner

    '); + assert.equal(bqUnwrappedElement, 'blockquote'); - assert.ok(pIsUnwrapped); - assert.equal(pUnwrappedHtml, 'inner'); - assert.equal(pUnwrappedElement, 'p'); + assert.ok(pIsUnwrapped); + assert.equal(pUnwrappedHtml, 'inner'); + assert.equal(pUnwrappedElement, 'p'); - assert.ok(ulIsUnwrapped); - assert.equal(ulUnwrappedHtml, '
  • Test
  • test2
  • '); - assert.equal(ulUnwrappedElement, 'ul'); - }, - ); + assert.ok(ulIsUnwrapped); + assert.equal(ulUnwrappedHtml, '
  • Test
  • test2
  • '); + assert.equal(ulUnwrappedElement, 'ul'); + }); }); diff --git a/dotcom-rendering/src/model/validate.node.test.ts b/dotcom-rendering/src/model/validate.node.test.ts index 71c9b2a0e44..f189a04d640 100644 --- a/dotcom-rendering/src/model/validate.node.test.ts +++ b/dotcom-rendering/src/model/validate.node.test.ts @@ -1,5 +1,5 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { Comment } from '../../fixtures/generated/fe-articles/Comment'; import { Feature } from '../../fixtures/generated/fe-articles/Feature'; import { Live } from '../../fixtures/generated/fe-articles/Live'; @@ -53,27 +53,21 @@ const hostedContentArticles = [ }, ]; -void nodeDescribe('validate', () => { - void nodeIt('throws on invalid data', () => { +void describe('validate', () => { + void it('throws on invalid data', () => { const data = { foo: 'bar' }; assert.throws(() => validateAsFEArticle(data), TypeError); }); for (const article of articles) { - void nodeIt(`validates data for a ${article.name} article`, () => { + void it(`validates data for a ${article.name} article`, () => { assert.equal(validateAsFEArticle(article.data), article.data); }); } for (const hostedItem of hostedContentArticles) { - void nodeIt( - `validates data for hosted ${hostedItem.name} content`, - () => { - assert.equal( - validateAsFEArticle(hostedItem.data), - hostedItem.data, - ); - }, - ); + void it(`validates data for hosted ${hostedItem.name} content`, () => { + assert.equal(validateAsFEArticle(hostedItem.data), hostedItem.data); + }); } }); diff --git a/dotcom-rendering/src/model/validate.puzzlesPage.node.test.ts b/dotcom-rendering/src/model/validate.puzzlesPage.node.test.ts index d1315ea3dc2..1029111ed50 100644 --- a/dotcom-rendering/src/model/validate.puzzlesPage.node.test.ts +++ b/dotcom-rendering/src/model/validate.puzzlesPage.node.test.ts @@ -1,5 +1,5 @@ import assert from 'node:assert/strict'; -import { describe as nodeDescribe, it as nodeIt } from 'node:test'; +import { describe, it } from 'node:test'; import { validateAsPuzzlesPageType } from './validate'; const validPage = () => ({ @@ -39,28 +39,24 @@ const validPage = () => ({ }, }); -void nodeDescribe('validateAsPuzzlesPageType', () => { - void nodeIt('accepts a valid recursive blueprint contract', () => { +void describe('validateAsPuzzlesPageType', () => { + void it('accepts a valid recursive blueprint contract', () => { assert.equal( validateAsPuzzlesPageType(validPage()).layout.containers[0]?.id, 'word-games', ); }); - void nodeIt( - 'accepts enabled on featured containers and rejects it elsewhere', - () => { - const featuredPage = validPage(); - featuredPage.layout.containers[0]!.variant = 'featured'; - ( - featuredPage.layout.containers[0] as { enabled?: boolean } - ).enabled = true; - assert.notEqual(validateAsPuzzlesPageType(featuredPage), undefined); + void it('accepts enabled on featured containers and rejects it elsewhere', () => { + const featuredPage = validPage(); + featuredPage.layout.containers[0]!.variant = 'featured'; + (featuredPage.layout.containers[0] as { enabled?: boolean }).enabled = + true; + assert.notEqual(validateAsPuzzlesPageType(featuredPage), undefined); - featuredPage.layout.containers[0]!.variant = 'standard'; - assert.throws(() => validateAsPuzzlesPageType(featuredPage)); - }, - ); + featuredPage.layout.containers[0]!.variant = 'standard'; + assert.throws(() => validateAsPuzzlesPageType(featuredPage)); + }); for (const [name, mutate] of [ [ @@ -108,7 +104,7 @@ void nodeDescribe('validateAsPuzzlesPageType', () => { }, ], ] as const) { - void nodeIt(`rejects ${name}`, () => { + void it(`rejects ${name}`, () => { const page = validPage(); mutate(page); assert.throws(() => validateAsPuzzlesPageType(page), { @@ -117,82 +113,69 @@ void nodeDescribe('validateAsPuzzlesPageType', () => { }); } - void nodeIt( - 'accepts supporting content with valid puzzle references', - () => { - const page = validPage(); - page.layout.containers.push({ - id: 'supporting', - title: '', - variant: 'supporting', - adSlot: 'mostpop', - content: { items: [], nestedContainers: [] }, - supporting: { - usefulLinksTitle: 'Useful links', - usefulLinks: [ - { - title: 'Archive', - url: '/puzzles-and-games/word-wheel/archive', - }, - ], - popularTitle: 'Most popular puzzles', - popularGroups: [ - { title: 'Most played', itemIds: ['word-wheel'] }, - ], - }, - } as never); + void it('accepts supporting content with valid puzzle references', () => { + const page = validPage(); + page.layout.containers.push({ + id: 'supporting', + title: '', + variant: 'supporting', + adSlot: 'mostpop', + content: { items: [], nestedContainers: [] }, + supporting: { + usefulLinksTitle: 'Useful links', + usefulLinks: [ + { + title: 'Archive', + url: '/puzzles-and-games/word-wheel/archive', + }, + ], + popularTitle: 'Most popular puzzles', + popularGroups: [ + { title: 'Most played', itemIds: ['word-wheel'] }, + ], + }, + } as never); - assert.equal( - validateAsPuzzlesPageType(page).layout.containers.length, - 2, - ); - }, - ); + assert.equal( + validateAsPuzzlesPageType(page).layout.containers.length, + 2, + ); + }); - void nodeIt( - 'rejects supporting content which references an unknown puzzle', - () => { - const page = validPage(); - page.layout.containers.push({ - id: 'supporting', - title: '', - variant: 'supporting', - content: { items: [], nestedContainers: [] }, - supporting: { - usefulLinksTitle: 'Useful links', - usefulLinks: [], - popularTitle: 'Most popular puzzles', - popularGroups: [ - { title: 'Most played', itemIds: ['missing'] }, - ], - }, - } as never); + void it('rejects supporting content which references an unknown puzzle', () => { + const page = validPage(); + page.layout.containers.push({ + id: 'supporting', + title: '', + variant: 'supporting', + content: { items: [], nestedContainers: [] }, + supporting: { + usefulLinksTitle: 'Useful links', + usefulLinks: [], + popularTitle: 'Most popular puzzles', + popularGroups: [{ title: 'Most played', itemIds: ['missing'] }], + }, + } as never); - assert.throws(() => validateAsPuzzlesPageType(page)); - }, - ); + assert.throws(() => validateAsPuzzlesPageType(page)); + }); - void nodeIt( - 'accepts a valid top-level ad placement and rejects one nested inside content', - () => { - const page = validPage(); - const ad = { - id: 'inline-ad', - title: '', - variant: 'ad', - adSlot: 'inline1', - content: { items: [], nestedContainers: [] }, - }; - page.layout.containers.push(ad as never); - assert.equal( - validateAsPuzzlesPageType(page).layout.containers.length, - 2, - ); - page.layout.containers.pop(); - page.layout.containers[0]!.content.nestedContainers.push( - ad as never, - ); - assert.throws(() => validateAsPuzzlesPageType(page)); - }, - ); + void it('accepts a valid top-level ad placement and rejects one nested inside content', () => { + const page = validPage(); + const ad = { + id: 'inline-ad', + title: '', + variant: 'ad', + adSlot: 'inline1', + content: { items: [], nestedContainers: [] }, + }; + page.layout.containers.push(ad as never); + assert.equal( + validateAsPuzzlesPageType(page).layout.containers.length, + 2, + ); + page.layout.containers.pop(); + page.layout.containers[0]!.content.nestedContainers.push(ad as never); + assert.throws(() => validateAsPuzzlesPageType(page)); + }); }); From ba000d7db2f86458c99b0989c078eee5ce63bc19 Mon Sep 17 00:00:00 2001 From: Ravi <7014230+arelra@users.noreply.github.com> Date: Mon, 14 Sep 2026 10:45:55 +0100 Subject: [PATCH 6/8] Remove custom object matcher and use assert.deepEqual --- .../src/lib/branding.node.test.ts | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/dotcom-rendering/src/lib/branding.node.test.ts b/dotcom-rendering/src/lib/branding.node.test.ts index e100bf4d06b..5b229e3c9d7 100644 --- a/dotcom-rendering/src/lib/branding.node.test.ts +++ b/dotcom-rendering/src/lib/branding.node.test.ts @@ -6,18 +6,6 @@ import { decideCollectionBranding, decideTagPageBranding } from './branding'; // For the purpose of these tests we don't care about the contents of the logo objects const logo = {} as Branding['logo']; -const assertMatchObject = (actual: unknown, expected: unknown): void => { - if (expected === null || typeof expected !== 'object') { - assert.deepEqual(actual, expected); - return; - } - - assert.ok(actual !== null && typeof actual === 'object'); - for (const [key, value] of Object.entries(expected)) { - assertMatchObject((actual as Record)[key], value); - } -}; - void describe('decideCollectionBranding', () => { void it('picks branding from a card by their edition', () => { const cards = [ @@ -53,7 +41,7 @@ void describe('decideCollectionBranding', () => { editionId: 'UK', isContainerBranding: false, }); - assertMatchObject(ukBranding, { + assert.deepEqual(ukBranding, { kind: 'paid-content', isFrontBranding: false, branding: { @@ -72,7 +60,7 @@ void describe('decideCollectionBranding', () => { editionId: 'US', isContainerBranding: false, }); - assertMatchObject(usBranding, { + assert.deepEqual(usBranding, { kind: 'sponsored', isFrontBranding: false, branding: { @@ -131,10 +119,12 @@ void describe('decideCollectionBranding', () => { editionId: 'UK', isContainerBranding: false, }); - assertMatchObject(collectionBranding, { + assert.deepEqual(collectionBranding, { kind: 'paid-content', isFrontBranding: false, branding: cardBranding, + isContainerBranding: false, + hasMultipleBranding: false, }); }); @@ -657,13 +647,14 @@ void describe('decideTagPageBranding', () => { branding, }); - assertMatchObject(tagPageBranding, { + assert.deepEqual(tagPageBranding, { kind: 'sponsored', isFrontBranding: true, branding: { brandingType: { name: 'sponsored' }, sponsorName: 'Guardian.org', aboutThisLink: '', + logo, }, isContainerBranding: false, hasMultipleBranding: false, From aff3afe8a08a4a60e1151dc1dbc96c9b8235d73a Mon Sep 17 00:00:00 2001 From: Ravi <7014230+arelra@users.noreply.github.com> Date: Mon, 14 Sep 2026 12:09:22 +0100 Subject: [PATCH 7/8] Don't apply eslint Jest globals to Node test files --- dotcom-rendering/eslint.config.mjs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dotcom-rendering/eslint.config.mjs b/dotcom-rendering/eslint.config.mjs index a5085a9a3b7..ec9e3fd6fcb 100644 --- a/dotcom-rendering/eslint.config.mjs +++ b/dotcom-rendering/eslint.config.mjs @@ -65,7 +65,11 @@ const rulesToEnforce = { export default defineConfig([ ...guardian.configs.recommended, - ...guardian.configs.jest, + ...guardian.configs.jest.map((config) => ({ + ...config, + // Don't apply Jest globals to Node test files + ignores: [...(config.ignores ?? []), '**/*.node.test.ts'], + })), ...guardian.configs.react, ...guardian.configs.storybook, // eslint-plugin-prettier/recommended should be the last item in the configuration array so that eslint-config-prettier has the opportunity to override other configs From beec3af1aca67dfbe20a38d6a544abd9984c876d Mon Sep 17 00:00:00 2001 From: Ravi <7014230+arelra@users.noreply.github.com> Date: Mon, 14 Sep 2026 12:15:18 +0100 Subject: [PATCH 8/8] Replace assert.ok with shorter plain assert as they are equivalent --- .../src/lib/affiliateLinksUtils.node.test.ts | 16 ++++----- .../src/lib/getFrontsAdPositions.node.test.ts | 36 +++++++++---------- .../src/lib/getZIndex.node.test.ts | 14 ++++---- .../src/lib/liveblogAdSlots.node.test.ts | 10 +++--- .../enhance-ad-placeholders.node.test.ts | 2 +- .../enhanceCommercialProperties.node.test.ts | 4 +-- .../src/model/unwrapHtml.node.test.ts | 12 +++---- 7 files changed, 47 insertions(+), 47 deletions(-) diff --git a/dotcom-rendering/src/lib/affiliateLinksUtils.node.test.ts b/dotcom-rendering/src/lib/affiliateLinksUtils.node.test.ts index eb3c77c0669..25a786c1fb7 100644 --- a/dotcom-rendering/src/lib/affiliateLinksUtils.node.test.ts +++ b/dotcom-rendering/src/lib/affiliateLinksUtils.node.test.ts @@ -70,11 +70,11 @@ void describe('buildXcustValueForAffiliateLink', () => { xcustComponentId: null, }); - assert.ok(xcustResult.includes('|abTestParticipations|')); - assert.ok(xcustResult.includes('existingTest:control')); - assert.ok(xcustResult.includes('newTest:variantB')); - assert.ok(xcustResult.includes('abTest1:oldVariant')); - assert.ok(!xcustResult.includes('abTest1:variantA')); + assert(xcustResult.includes('|abTestParticipations|')); + assert(xcustResult.includes('existingTest:control')); + assert(xcustResult.includes('newTest:variantB')); + assert(xcustResult.includes('abTest1:oldVariant')); + assert(!xcustResult.includes('abTest1:variantA')); }); void it('preserves existing AB participations when url already has xcust', () => { @@ -88,11 +88,11 @@ void describe('buildXcustValueForAffiliateLink', () => { xcustComponentId: null, }); - assert.ok( + assert( xcustResult.includes('referrer|www.theguardian.com|accountId|1111'), ); - assert.ok(xcustResult.includes('newTest:newVariant')); - assert.ok(xcustResult.includes('oldTest:oldVariant')); + assert(xcustResult.includes('newTest:newVariant')); + assert(xcustResult.includes('oldTest:oldVariant')); }); }); diff --git a/dotcom-rendering/src/lib/getFrontsAdPositions.node.test.ts b/dotcom-rendering/src/lib/getFrontsAdPositions.node.test.ts index 21a37b54d38..efa9950309f 100644 --- a/dotcom-rendering/src/lib/getFrontsAdPositions.node.test.ts +++ b/dotcom-rendering/src/lib/getFrontsAdPositions.node.test.ts @@ -43,7 +43,7 @@ void describe('Mobile Ads', () => { const mobileAdPositions = getMobileAdPositions(testCollections, 'uk'); - assert.ok(!mobileAdPositions.includes(0)); + assert(!mobileAdPositions.includes(0)); }); void it(`should not insert an ad in the merchandising-high position`, () => { @@ -52,7 +52,7 @@ void describe('Mobile Ads', () => { { ...testCollection, collectionType: 'news/most-popular' }, ] satisfies AdCandidate[]; const mobileAdPositions = getMobileAdPositions(testCollections, 'uk'); - assert.ok(!mobileAdPositions.includes(3)); + assert(!mobileAdPositions.includes(3)); }); void it('Should not insert ad before a thrasher container', () => { @@ -68,8 +68,8 @@ void describe('Mobile Ads', () => { const mobileAdPositions = getMobileAdPositions(testCollections, 'uk'); - assert.ok(!mobileAdPositions.includes(6)); - assert.ok(!mobileAdPositions.includes(8)); + assert(!mobileAdPositions.includes(6)); + assert(!mobileAdPositions.includes(8)); }); void it(`Should allow inserting an ad before a thrasher container if it's a filter page`, () => { @@ -88,8 +88,8 @@ void describe('Mobile Ads', () => { 'uk/thefilter', ); - assert.ok(mobileAdPositions.includes(6)); - assert.ok(mobileAdPositions.includes(8)); + assert(mobileAdPositions.includes(6)); + assert(mobileAdPositions.includes(8)); }); // We used https://www.theguardian.com/uk/commentisfree as a blueprint @@ -429,8 +429,8 @@ void describe('inserting an ad after the first collection', () => { 'uk', ); - assert.ok(adPositions.includes(0)); - assert.ok(!adPositions.includes(1)); + assert(adPositions.includes(0)); + assert(!adPositions.includes(1)); }); void it('inserts an ad after the first collection if it is a LARGE flexible special container', () => { @@ -451,8 +451,8 @@ void describe('inserting an ad after the first collection', () => { 'uk', ); - assert.ok(adPositions.includes(0)); - assert.ok(!adPositions.includes(1)); + assert(adPositions.includes(0)); + assert(!adPositions.includes(1)); }); void it('does NOT insert an ad after the first collection if it is a SMALL flexible general container', () => { @@ -468,7 +468,7 @@ void describe('inserting an ad after the first collection', () => { 'uk', ); - assert.ok(!adPositions.includes(0)); + assert(!adPositions.includes(0)); }); void it('does NOT insert an ad after the first collection if it is a SMALL flexible special container', () => { @@ -484,7 +484,7 @@ void describe('inserting an ad after the first collection', () => { 'uk', ); - assert.ok(!adPositions.includes(0)); + assert(!adPositions.includes(0)); }); }); @@ -507,8 +507,8 @@ void describe('inserting an ad after the first collection', () => { 'uk', ); - assert.ok(adPositions.includes(1)); - assert.ok(!adPositions.includes(2)); + assert(adPositions.includes(1)); + assert(!adPositions.includes(2)); }); void it('inserts an ad before the second collection if it is preceded by a LARGE flexible special container', () => { @@ -529,8 +529,8 @@ void describe('inserting an ad after the first collection', () => { 'uk', ); - assert.ok(adPositions.includes(1)); - assert.ok(!adPositions.includes(2)); + assert(adPositions.includes(1)); + assert(!adPositions.includes(2)); }); void it('does NOT insert an ad before the second collection if it is preceded by a SMALL flexible general container', () => { @@ -551,7 +551,7 @@ void describe('inserting an ad after the first collection', () => { 'uk', ); - assert.ok(!adPositions.includes(1)); + assert(!adPositions.includes(1)); }); void it('does NOT insert an ad before the second collection if it is preceded by a SMALL flexible special container', () => { @@ -572,7 +572,7 @@ void describe('inserting an ad after the first collection', () => { 'uk', ); - assert.ok(!adPositions.includes(1)); + assert(!adPositions.includes(1)); }); }); }); diff --git a/dotcom-rendering/src/lib/getZIndex.node.test.ts b/dotcom-rendering/src/lib/getZIndex.node.test.ts index 3e95bd0b0fc..8e30a07ebdd 100644 --- a/dotcom-rendering/src/lib/getZIndex.node.test.ts +++ b/dotcom-rendering/src/lib/getZIndex.node.test.ts @@ -4,18 +4,18 @@ import { getZIndex } from './getZIndex'; void describe('getZIndex', () => { void it('gets the correct zindex for group and sibling', () => { - assert.ok(getZIndex('sticky-video-button') > getZIndex('sticky-video')); - assert.ok( + assert(getZIndex('sticky-video-button') > getZIndex('sticky-video')); + assert( getZIndex('expanded-veggie-menu-wrapper') > getZIndex('expanded-veggie-menu'), ); - assert.ok( + assert( getZIndex('stickyAdWrapperLabsHeader') > getZIndex('stickyAdWrapper'), ); - assert.ok(getZIndex('tableOfContents') > getZIndex('articleHeadline')); - assert.ok(getZIndex('subNavBanner') > getZIndex('articleHeadline')); - assert.ok(getZIndex('subNavBanner') > getZIndex('bodyArea')); - assert.ok(getZIndex('card-nested-link') > getZIndex('card-link')); + assert(getZIndex('tableOfContents') > getZIndex('articleHeadline')); + assert(getZIndex('subNavBanner') > getZIndex('articleHeadline')); + assert(getZIndex('subNavBanner') > getZIndex('bodyArea')); + assert(getZIndex('card-nested-link') > getZIndex('card-link')); }); }); diff --git a/dotcom-rendering/src/lib/liveblogAdSlots.node.test.ts b/dotcom-rendering/src/lib/liveblogAdSlots.node.test.ts index e356deb7915..cbc34c58b52 100644 --- a/dotcom-rendering/src/lib/liveblogAdSlots.node.test.ts +++ b/dotcom-rendering/src/lib/liveblogAdSlots.node.test.ts @@ -157,7 +157,7 @@ void describe('shouldDisplayAd', () => { isMobile, ); - assert.ok(!result); + assert(!result); }); } }); @@ -179,7 +179,7 @@ void describe('shouldDisplayAd', () => { isMobile, ); - assert.ok(!result); + assert(!result); }); } }); @@ -201,7 +201,7 @@ void describe('shouldDisplayAd', () => { isMobile, ); - assert.ok(result); + assert(result); }); } }); @@ -226,7 +226,7 @@ void describe('shouldDisplayAd', () => { isMobile, ); - assert.ok(result); + assert(result); }); } @@ -249,7 +249,7 @@ void describe('shouldDisplayAd', () => { isMobile, ); - assert.ok(!result); + assert(!result); }); } }); diff --git a/dotcom-rendering/src/model/enhance-ad-placeholders.node.test.ts b/dotcom-rendering/src/model/enhance-ad-placeholders.node.test.ts index cebcfaad171..596cc7c3839 100644 --- a/dotcom-rendering/src/model/enhance-ad-placeholders.node.test.ts +++ b/dotcom-rendering/src/model/enhance-ad-placeholders.node.test.ts @@ -311,7 +311,7 @@ void describe('enhanceAdPlaceholders', () => { )(input); const outputPlaceholders = output.filter(elementIsAdPlaceholder); - assert.ok(outputPlaceholders.length > 0); + assert(outputPlaceholders.length > 0); }); }); }); diff --git a/dotcom-rendering/src/model/enhanceCommercialProperties.node.test.ts b/dotcom-rendering/src/model/enhanceCommercialProperties.node.test.ts index 41d38fc8753..251c37e6da5 100644 --- a/dotcom-rendering/src/model/enhanceCommercialProperties.node.test.ts +++ b/dotcom-rendering/src/model/enhanceCommercialProperties.node.test.ts @@ -46,7 +46,7 @@ void describe('Enhance Branding', () => { .map((p) => p.branding?.logo.dimensions.width) .filter(isNumber); - assert.ok(Math.max(...dimensionsFail) > 140); + assert(Math.max(...dimensionsFail) > 140); const dimensionsPass = Object.values( enhanceCommercialProperties(commercialProperties), @@ -54,6 +54,6 @@ void describe('Enhance Branding', () => { .map((p) => p.branding?.logo.dimensions.width) .filter(isNumber); - assert.ok(Math.max(...dimensionsPass) <= 140); + assert(Math.max(...dimensionsPass) <= 140); }); }); diff --git a/dotcom-rendering/src/model/unwrapHtml.node.test.ts b/dotcom-rendering/src/model/unwrapHtml.node.test.ts index d2dc09e182d..72a54fe0567 100644 --- a/dotcom-rendering/src/model/unwrapHtml.node.test.ts +++ b/dotcom-rendering/src/model/unwrapHtml.node.test.ts @@ -34,9 +34,9 @@ void describe('unwrapHtml', () => { unwrapHtml(pUnwrap); // Testy test - assert.ok(bqIsUnwrapped); + assert(bqIsUnwrapped); assert.equal(bqUnwrappedHtml, '

    inner

    '); - assert.ok(pIsUnwrapped); + assert(pIsUnwrapped); assert.equal(pUnwrappedHtml, 'inner'); }); @@ -52,7 +52,7 @@ void describe('unwrapHtml', () => { }; const { willUnwrap: isUnwrapped, unwrappedHtml } = unwrapHtml(bqUnwrap); - assert.ok(!isUnwrapped); + assert(!isUnwrapped); assert.equal(unwrappedHtml, bqUnwrap.html); }); @@ -123,15 +123,15 @@ void describe('unwrapHtml', () => { unwrappedElement: ulUnwrappedElement, } = unwrapHtml(ulUnwrap); - assert.ok(bqIsUnwrapped); + assert(bqIsUnwrapped); assert.equal(bqUnwrappedHtml, '

    inner

    '); assert.equal(bqUnwrappedElement, 'blockquote'); - assert.ok(pIsUnwrapped); + assert(pIsUnwrapped); assert.equal(pUnwrappedHtml, 'inner'); assert.equal(pUnwrappedElement, 'p'); - assert.ok(ulIsUnwrapped); + assert(ulIsUnwrapped); assert.equal(ulUnwrappedHtml, '
  • Test
  • test2
  • '); assert.equal(ulUnwrappedElement, 'ul'); });