From fcd4fa054c4a475bea9ef8277940e51ce671de18 Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Wed, 18 Oct 2023 11:23:56 +0100 Subject: [PATCH 1/4] Align options between render functions This is preparation work for a new `renderTemplate()` function which will be compatible with the same options E.g. Nunjucks render `context` is universally `options.context` whether rendered by Nunjucks or called by a Puppeteer test --- packages/govuk-frontend-review/src/app.mjs | 9 +- .../common/nunjucks/globals/get-html-code.mjs | 14 +- .../nunjucks/globals/get-nunjucks-code.mjs | 8 +- .../src/views/macros/showExamples.njk | 8 +- .../character-count.puppeteer.test.js | 2 +- .../components/components.template.test.js | 2 +- .../govuk/components/details/template.test.js | 10 +- .../components/error-summary/template.test.js | 10 +- .../components/fieldset/template.test.js | 10 +- .../components/inset-text/template.test.js | 10 +- .../notification-banner/template.test.js | 10 +- .../govuk/components/panel/template.test.js | 10 +- .../skip-link/skip-link.puppeteer.test.js | 12 +- .../src/govuk/macros/i18n.unit.test.mjs | 22 +- .../govuk-frontend/src/govuk/template.test.js | 194 +++++++++++++----- shared/helpers/nunjucks.js | 30 +-- shared/helpers/puppeteer.js | 6 +- shared/lib/components.js | 39 ++-- shared/tasks/components.mjs | 5 +- 19 files changed, 249 insertions(+), 162 deletions(-) diff --git a/packages/govuk-frontend-review/src/app.mjs b/packages/govuk-frontend-review/src/app.mjs index 4d33813a5d..51ab47c6c9 100644 --- a/packages/govuk-frontend-review/src/app.mjs +++ b/packages/govuk-frontend-review/src/app.mjs @@ -158,11 +158,10 @@ export default async () => { } // Construct and evaluate the component with the data for this example - res.locals.componentView = renderComponent( - componentName, - fixture.options, - { env } - ) + res.locals.componentView = renderComponent(componentName, { + context: fixture.options, + env + }) let bodyClasses = 'app-template__body' diff --git a/packages/govuk-frontend-review/src/common/nunjucks/globals/get-html-code.mjs b/packages/govuk-frontend-review/src/common/nunjucks/globals/get-html-code.mjs index a0e4ca2849..8312e3e5ac 100644 --- a/packages/govuk-frontend-review/src/common/nunjucks/globals/get-html-code.mjs +++ b/packages/govuk-frontend-review/src/common/nunjucks/globals/get-html-code.mjs @@ -6,21 +6,19 @@ import beautify from 'js-beautify' * * @this {{ env: import('nunjucks').Environment }} * @param {string} componentName - Component name - * @param {MacroOptions} [params] - Nunjucks macro options (or params) + * @param {MacroRenderOptions} [options] - Nunjucks macro render options * @returns {string} HTML rendered by the component */ -export function getHTMLCode(componentName, params) { - const html = renderComponent(componentName, params, { - env: this.env - }) +export function getHTMLCode(componentName, options) { + const html = renderComponent(componentName, { ...options, env: this.env }) // Default beautify options - const options = beautify.html.defaultOptions() + const beautifyOptions = beautify.html.defaultOptions() return beautify.html(html, { indent_size: 2, // Ensure nested labels in headings are indented properly - inline: options.inline.filter((tag) => !['label'].includes(tag)), + inline: beautifyOptions.inline.filter((tag) => !['label'].includes(tag)), // Remove blank lines max_preserve_newlines: 0, // Ensure attribute wrapping in header SVG is preserved @@ -29,5 +27,5 @@ export function getHTMLCode(componentName, params) { } /** - * @typedef {import('@govuk-frontend/lib/components').MacroOptions} MacroOptions + * @typedef {import('@govuk-frontend/lib/components').MacroRenderOptions} MacroRenderOptions */ diff --git a/packages/govuk-frontend-review/src/common/nunjucks/globals/get-nunjucks-code.mjs b/packages/govuk-frontend-review/src/common/nunjucks/globals/get-nunjucks-code.mjs index 1c2c40155f..95fc9f3a15 100644 --- a/packages/govuk-frontend-review/src/common/nunjucks/globals/get-nunjucks-code.mjs +++ b/packages/govuk-frontend-review/src/common/nunjucks/globals/get-nunjucks-code.mjs @@ -9,14 +9,14 @@ import { componentNameToMacroName } from '../filters/index.mjs' * Component Nunjucks code (formatted) * * @param {string} componentName - Component name - * @param {MacroOptions} [params] - Nunjucks macro options (or params) + * @param {MacroRenderOptions} [options] - Nunjucks macro render options * @returns {string} Nunjucks code for the component */ -export function getNunjucksCode(componentName, params) { +export function getNunjucksCode(componentName, options) { const macroName = componentNameToMacroName(componentName) // Allow nested HTML strings to wrap at `\n` - const paramsFormatted = inspect(params, { + const paramsFormatted = inspect(options.context, { compact: false, depth: Infinity, maxArrayLength: Infinity, @@ -39,5 +39,5 @@ export function getNunjucksCode(componentName, params) { } /** - * @typedef {import('@govuk-frontend/lib/components').MacroOptions} MacroOptions + * @typedef {import('@govuk-frontend/lib/components').MacroRenderOptions} MacroRenderOptions */ diff --git a/packages/govuk-frontend-review/src/views/macros/showExamples.njk b/packages/govuk-frontend-review/src/views/macros/showExamples.njk index 4ad7ad4202..b778cb8581 100644 --- a/packages/govuk-frontend-review/src/views/macros/showExamples.njk +++ b/packages/govuk-frontend-review/src/views/macros/showExamples.njk @@ -38,12 +38,16 @@ {% set codeExamplesHtml %}

Markup


-          {{- getHTMLCode(componentName, example.options) | highlight("html") | safe -}}
+          {{- getHTMLCode(componentName, {
+            context: example.options
+          }) | highlight("html") | safe -}}
         

Macro


-          {{- getNunjucksCode(componentName, example.options) | highlight("js") | safe -}}
+          {{- getNunjucksCode(componentName, {
+            context: example.options
+          }) | highlight("js") | safe -}}
         
{% endset %} diff --git a/packages/govuk-frontend/src/govuk/components/character-count/character-count.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/character-count/character-count.puppeteer.test.js index 40d2b47d6e..35c989f96c 100644 --- a/packages/govuk-frontend/src/govuk/components/character-count/character-count.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/character-count/character-count.puppeteer.test.js @@ -908,7 +908,7 @@ describe('Character count', () => { it('throws when receiving invalid configuration', async () => { await expect( - renderAndInitialise(page, 'character-count') + renderAndInitialise(page, 'character-count', { context: {} }) ).rejects.toEqual({ name: 'ConfigError', message: diff --git a/packages/govuk-frontend/src/govuk/components/components.template.test.js b/packages/govuk-frontend/src/govuk/components/components.template.test.js index c4d333176a..c10aab7abf 100644 --- a/packages/govuk-frontend/src/govuk/components/components.template.test.js +++ b/packages/govuk-frontend/src/govuk/components/components.template.test.js @@ -136,7 +136,7 @@ describe('Components', () => { for (const { component: componentName, fixtures } of componentsFixtures) { const fixtureTasks = fixtures.map( async ({ name: exampleName, options }) => { - const html = renderComponent(componentName, options) + const html = renderComponent(componentName, { context: options }) // Validate HTML return expect({ diff --git a/packages/govuk-frontend/src/govuk/components/details/template.test.js b/packages/govuk-frontend/src/govuk/components/details/template.test.js index 1de37393f6..acb03bdd4a 100644 --- a/packages/govuk-frontend/src/govuk/components/details/template.test.js +++ b/packages/govuk-frontend/src/govuk/components/details/template.test.js @@ -45,13 +45,9 @@ describe('Details', () => { }) it('renders nested components using `call`', () => { - const $ = render( - 'details', - {}, - { - callBlock: '
' - } - ) + const $ = render('details', { + callBlock: '
' + }) expect($('.govuk-details .app-nested-component').length).toBeTruthy() }) diff --git a/packages/govuk-frontend/src/govuk/components/error-summary/template.test.js b/packages/govuk-frontend/src/govuk/components/error-summary/template.test.js index e01fa73ed3..39b17e3b2d 100644 --- a/packages/govuk-frontend/src/govuk/components/error-summary/template.test.js +++ b/packages/govuk-frontend/src/govuk/components/error-summary/template.test.js @@ -88,13 +88,9 @@ describe('Error-summary', () => { }) it('renders nested components in description using `call`', () => { - const $ = render( - 'error-summary', - {}, - { - callBlock: '
' - } - ) + const $ = render('error-summary', { + callBlock: '
' + }) expect( $('.govuk-error-summary .app-nested-component').length diff --git a/packages/govuk-frontend/src/govuk/components/fieldset/template.test.js b/packages/govuk-frontend/src/govuk/components/fieldset/template.test.js index da74e099d9..dfadcd6b8a 100644 --- a/packages/govuk-frontend/src/govuk/components/fieldset/template.test.js +++ b/packages/govuk-frontend/src/govuk/components/fieldset/template.test.js @@ -73,13 +73,9 @@ describe('fieldset', () => { }) it('renders nested components using `call`', () => { - const $ = render( - 'fieldset', - {}, - { - callBlock: '
' - } - ) + const $ = render('fieldset', { + callBlock: '
' + }) expect($('.govuk-fieldset .app-nested-component').length).toBeTruthy() }) diff --git a/packages/govuk-frontend/src/govuk/components/inset-text/template.test.js b/packages/govuk-frontend/src/govuk/components/inset-text/template.test.js index 44ce7ea061..b237171744 100644 --- a/packages/govuk-frontend/src/govuk/components/inset-text/template.test.js +++ b/packages/govuk-frontend/src/govuk/components/inset-text/template.test.js @@ -26,13 +26,9 @@ describe('Inset text', () => { }) it('renders nested components using `call`', () => { - const $ = render( - 'inset-text', - {}, - { - callBlock: '
' - } - ) + const $ = render('inset-text', { + callBlock: '
' + }) expect($('.govuk-inset-text .app-nested-component').length).toBeTruthy() }) diff --git a/packages/govuk-frontend/src/govuk/components/notification-banner/template.test.js b/packages/govuk-frontend/src/govuk/components/notification-banner/template.test.js index 5bf4941228..f21049cb67 100644 --- a/packages/govuk-frontend/src/govuk/components/notification-banner/template.test.js +++ b/packages/govuk-frontend/src/govuk/components/notification-banner/template.test.js @@ -169,13 +169,9 @@ describe('Notification-banner', () => { }) it('renders nested components using `call`', () => { - const $ = render( - 'notification-banner', - {}, - { - callBlock: '
' - } - ) + const $ = render('notification-banner', { + callBlock: '
' + }) expect( $('.govuk-notification-banner .app-nested-component').length diff --git a/packages/govuk-frontend/src/govuk/components/panel/template.test.js b/packages/govuk-frontend/src/govuk/components/panel/template.test.js index d6e2bed681..cc9b3b361e 100644 --- a/packages/govuk-frontend/src/govuk/components/panel/template.test.js +++ b/packages/govuk-frontend/src/govuk/components/panel/template.test.js @@ -63,13 +63,9 @@ describe('Panel', () => { }) it('renders nested components using `call`', () => { - const $ = render( - 'panel', - {}, - { - callBlock: '
' - } - ) + const $ = render('panel', { + callBlock: '
' + }) expect($('.govuk-panel .app-nested-component').length).toBeTruthy() }) diff --git a/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.puppeteer.test.js index ec7e260f03..a373524b59 100644 --- a/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.puppeteer.test.js @@ -112,8 +112,10 @@ describe('Skip Link', () => { it('throws when the linked element is missing', async () => { await expect( renderAndInitialise(page, 'skip-link', { - text: 'Skip to main content', - href: '#this-element-does-not-exist' + context: { + text: 'Skip to main content', + href: '#this-element-does-not-exist' + } }) ).rejects.toEqual({ name: 'ElementError', @@ -125,8 +127,10 @@ describe('Skip Link', () => { it('throws when the href does not contain a hash', async () => { await expect( renderAndInitialise(page, 'skip-link', { - text: 'Skip to main content', - href: 'this-element-does-not-exist' + context: { + text: 'Skip to main content', + href: 'this-element-does-not-exist' + } }) ).rejects.toEqual({ name: 'ElementError', diff --git a/packages/govuk-frontend/src/govuk/macros/i18n.unit.test.mjs b/packages/govuk-frontend/src/govuk/macros/i18n.unit.test.mjs index 8d952629b2..182674ca9f 100644 --- a/packages/govuk-frontend/src/govuk/macros/i18n.unit.test.mjs +++ b/packages/govuk-frontend/src/govuk/macros/i18n.unit.test.mjs @@ -7,9 +7,11 @@ describe('i18n.njk', () => { 'govukI18nAttributes', 'govuk/macros/i18n.njk', { - key: 'translation-key', - messages: { - other: 'You have %{count} characters remaining.' + context: { + key: 'translation-key', + messages: { + other: 'You have %{count} characters remaining.' + } } } ) @@ -25,10 +27,12 @@ describe('i18n.njk', () => { 'govukI18nAttributes', 'govuk/macros/i18n.njk', { - key: 'translation-key', - messages: { - other: 'You have %{count} characters remaining.', - one: 'One character remaining' + context: { + key: 'translation-key', + messages: { + other: 'You have %{count} characters remaining.', + one: 'One character remaining' + } } } ) @@ -43,7 +47,9 @@ describe('i18n.njk', () => { 'govukI18nAttributes', 'govuk/macros/i18n.njk', { - key: 'translation-key' + context: { + key: 'translation-key' + } } ) diff --git a/packages/govuk-frontend/src/govuk/template.test.js b/packages/govuk-frontend/src/govuk/template.test.js index c1c42c5d9e..6b323a7424 100644 --- a/packages/govuk-frontend/src/govuk/template.test.js +++ b/packages/govuk-frontend/src/govuk/template.test.js @@ -30,26 +30,38 @@ describe('Template', () => { describe('', () => { it('defaults to lang="en"', () => { - const $ = renderTemplate() + const $ = renderTemplate('govuk/template.njk') expect($('html').attr('lang')).toEqual('en') }) it('can have a custom lang set using htmlLang', () => { - const $ = renderTemplate({ htmlLang: 'zu' }) + const $ = renderTemplate('govuk/template.njk', { + context: { + htmlLang: 'zu' + } + }) + expect($('html').attr('lang')).toEqual('zu') }) it('can have custom classes added using htmlClasses', () => { - const $ = renderTemplate({ htmlClasses: 'my-custom-class' }) + const $ = renderTemplate('govuk/template.njk', { + context: { + htmlClasses: 'my-custom-class' + } + }) + expect($('html').hasClass('my-custom-class')).toBeTruthy() }) }) describe('', () => { it('can have custom social media icons specified using the headIcons block', () => { - const headIcons = '' - - const $ = renderTemplate({}, { headIcons }) + const $ = renderTemplate('govuk/template.njk', { + blocks: { + headIcons: '' + } + }) // Build a list of the rel values of all links with a rel ending 'icon' const icons = $('link[rel$="icon"]') @@ -59,22 +71,29 @@ describe('Template', () => { }) it('can have additional content added to the using the head block', () => { - const head = '' - - const $ = renderTemplate({}, { head }) + const $ = renderTemplate('govuk/template.njk', { + blocks: { + head: '' + } + }) expect($('head meta[property="foo"]').attr('content')).toEqual('bar') }) it('uses a default assets path of /assets', () => { - const $ = renderTemplate() + const $ = renderTemplate('govuk/template.njk') const $icon = $('link[rel="shortcut icon"]') expect($icon.attr('href')).toEqual('/assets/images/favicon.ico') }) it('can have the assets path overridden using assetPath', () => { - const $ = renderTemplate({ assetPath: '/whatever' }) + const $ = renderTemplate('govuk/template.njk', { + context: { + assetPath: '/whatever' + } + }) + const $icon = $('link[rel="shortcut icon"]') expect($icon.attr('href')).toEqual('/whatever/images/favicon.ico') @@ -82,14 +101,19 @@ describe('Template', () => { describe('opengraph image', () => { it('is not included if neither assetUrl nor opengraphImageUrl are set ', () => { - const $ = renderTemplate({}) + const $ = renderTemplate('govuk/template.njk') const $ogImage = $('meta[property="og:image"]') expect($ogImage.length).toBe(0) }) it('is included using default path and filename if assetUrl is set', () => { - const $ = renderTemplate({ assetUrl: 'https://foo.com/my-assets' }) + const $ = renderTemplate('govuk/template.njk', { + context: { + assetUrl: 'https://foo.com/my-assets' + } + }) + const $ogImage = $('meta[property="og:image"]') expect($ogImage.attr('content')).toEqual( @@ -98,9 +122,12 @@ describe('Template', () => { }) it('is included if opengraphImageUrl is set', () => { - const $ = renderTemplate({ - opengraphImageUrl: 'https://foo.com/custom/og-image.png' + const $ = renderTemplate('govuk/template.njk', { + context: { + opengraphImageUrl: 'https://foo.com/custom/og-image.png' + } }) + const $ogImage = $('meta[property="og:image"]') expect($ogImage.attr('content')).toEqual( @@ -111,12 +138,17 @@ describe('Template', () => { describe('', () => { it('has a default content of #0b0c0c', () => { - const $ = renderTemplate() + const $ = renderTemplate('govuk/template.njk') expect($('meta[name="theme-color"]').attr('content')).toEqual('#0b0c0c') }) it('can be overridden using themeColor', () => { - const $ = renderTemplate({ themeColor: '#ff69b4' }) + const $ = renderTemplate('govuk/template.njk', { + context: { + themeColor: '#ff69b4' + } + }) + expect($('meta[name="theme-color"]').attr('content')).toEqual('#ff69b4') }) }) @@ -124,23 +156,34 @@ describe('Template', () => { describe('', () => { const expectedTitle = 'GOV.UK - The best place to find government services and information' + it(`defaults to '${expectedTitle}'`, () => { - const $ = renderTemplate() + const $ = renderTemplate('govuk/template.njk') expect($('title').text()).toEqual(expectedTitle) }) it('can be overridden using the pageTitle block', () => { - const $ = renderTemplate({}, { pageTitle: 'Foo' }) + const $ = renderTemplate('govuk/template.njk', { + blocks: { + pageTitle: 'Foo' + } + }) + expect($('title').text()).toEqual('Foo') }) it('does not have a lang attribute by default', () => { - const $ = renderTemplate() + const $ = renderTemplate('govuk/template.njk') expect($('title').attr('lang')).toBeUndefined() }) it('can have a lang attribute specified using pageTitleLang', () => { - const $ = renderTemplate({ pageTitleLang: 'zu' }) + const $ = renderTemplate('govuk/template.njk', { + context: { + pageTitleLang: 'zu' + } + }) + expect($('title').attr('lang')).toEqual('zu') }) }) @@ -148,34 +191,48 @@ describe('Template', () => { describe('<body>', () => { it('can have custom classes added using bodyClasses', () => { - const $ = renderTemplate({ bodyClasses: 'custom-body-class' }) + const $ = renderTemplate('govuk/template.njk', { + context: { + bodyClasses: 'custom-body-class' + } + }) + expect($('body').hasClass('custom-body-class')).toBeTruthy() }) it('can have custom attributes added using bodyAttributes', () => { - const $ = renderTemplate({ bodyAttributes: { 'data-foo': 'bar' } }) + const $ = renderTemplate('govuk/template.njk', { + context: { + bodyAttributes: { 'data-foo': 'bar' } + } + }) + expect($('body').attr('data-foo')).toEqual('bar') }) it('can have additional content added after the opening tag using bodyStart block', () => { - const bodyStart = '<div>bodyStart</div>' - - const $ = renderTemplate({}, { bodyStart }) + const $ = renderTemplate('govuk/template.njk', { + blocks: { + bodyStart: '<div>bodyStart</div>' + } + }) expect($('body > div:first-of-type').text()).toEqual('bodyStart') }) it('can have additional content added before the closing tag using bodyEnd block', () => { - const bodyEnd = '<div>bodyEnd</div>' - - const $ = renderTemplate({}, { bodyEnd }) + const $ = renderTemplate('govuk/template.njk', { + blocks: { + bodyEnd: '<div>bodyEnd</div>' + } + }) expect($('body > div:last-of-type').text()).toEqual('bodyEnd') }) describe('inline script that adds "js-enabled" and "govuk-frontend-supported" classes', () => { it('should match the hash published in docs', () => { - const $ = renderTemplate() + const $ = renderTemplate('govuk/template.njk') const script = $('body > script').first().html() // Create a base64 encoded hash of the contents of the script tag @@ -187,14 +244,21 @@ describe('Template', () => { 'sha256-GUQ5ad8JK5KmEWmROf3LZd9ge94daqNvd8xy9YS1iDw=' ) }) + it('should not have a nonce attribute by default', () => { - const $ = renderTemplate() + const $ = renderTemplate('govuk/template.njk') const scriptTag = $('body > script').first() expect(scriptTag.attr('nonce')).toEqual(undefined) }) + it('should have a nonce attribute when nonce is provided', () => { - const $ = renderTemplate({ cspNonce: 'abcdef' }) + const $ = renderTemplate('govuk/template.njk', { + context: { + cspNonce: 'abcdef' + } + }) + const scriptTag = $('body > script').first() expect(scriptTag.attr('nonce')).toEqual('abcdef') @@ -203,9 +267,11 @@ describe('Template', () => { describe('skip link', () => { it('can be overridden using the skipLink block', () => { - const skipLink = '<div class="my-skip-link">skipLink</div>' - - const $ = renderTemplate({}, { skipLink }) + const $ = renderTemplate('govuk/template.njk', { + blocks: { + skipLink: '<div class="my-skip-link">skipLink</div>' + } + }) expect($('.my-skip-link').length).toEqual(1) expect($('.govuk-skip-link').length).toEqual(0) @@ -214,9 +280,11 @@ describe('Template', () => { describe('header', () => { it('can be overridden using the header block', () => { - const header = '<div class="my-header">header</div>' - - const $ = renderTemplate({}, { header }) + const $ = renderTemplate('govuk/template.njk', { + blocks: { + header: '<div class="my-header">header</div>' + } + }) expect($('.my-header').length).toEqual(1) expect($('.govuk-header').length).toEqual(0) @@ -225,46 +293,62 @@ describe('Template', () => { describe('<main>', () => { it('has role="main", supporting browsers that do not natively support HTML5 elements', () => { - const $ = renderTemplate() + const $ = renderTemplate('govuk/template.njk') expect($('main').attr('role')).toEqual('main') }) it('can have custom classes added using mainClasses', () => { - const $ = renderTemplate({ mainClasses: 'custom-main-class' }) + const $ = renderTemplate('govuk/template.njk', { + context: { + mainClasses: 'custom-main-class' + } + }) + expect($('main').hasClass('custom-main-class')).toBeTruthy() }) it('does not have a lang attribute by default', () => { - const $ = renderTemplate() + const $ = renderTemplate('govuk/template.njk') expect($('main').attr('lang')).toBeUndefined() }) it('can have a lang attribute specified using mainLang', () => { - const $ = renderTemplate({ mainLang: 'zu' }) + const $ = renderTemplate('govuk/template.njk', { + context: { + mainLang: 'zu' + } + }) + expect($('main').attr('lang')).toEqual('zu') }) it('can be overridden using the main block', () => { - const main = '<main class="my-main">header</main>' - - const $ = renderTemplate({}, { main }) + const $ = renderTemplate('govuk/template.njk', { + blocks: { + main: '<main class="my-main">header</main>' + } + }) expect($('main').length).toEqual(1) expect($('main').hasClass('my-main')).toBe(true) }) it('can have content injected before it using the beforeContent block', () => { - const beforeContent = '<div class="before-content">beforeContent</div>' - - const $ = renderTemplate({}, { beforeContent }) + const $ = renderTemplate('govuk/template.njk', { + blocks: { + beforeContent: '<div class="before-content">beforeContent</div>' + } + }) expect($('.before-content').next().is('main')).toBe(true) }) it('can have content specified using the content block', () => { - const content = 'Foo' - - const $ = renderTemplate({}, { content }) + const $ = renderTemplate('govuk/template.njk', { + blocks: { + content: 'Foo' + } + }) expect($('main').text().trim()).toEqual('Foo') }) @@ -272,9 +356,11 @@ describe('Template', () => { describe('footer', () => { it('can be overridden using the footer block', () => { - const footer = '<div class="my-footer">footer</div>' - - const $ = renderTemplate({}, { footer }) + const $ = renderTemplate('govuk/template.njk', { + blocks: { + footer: '<div class="my-footer">footer</div>' + } + }) expect($('.my-footer').length).toEqual(1) expect($('.govuk-footer').length).toEqual(0) diff --git a/shared/helpers/nunjucks.js b/shared/helpers/nunjucks.js index 8a37991641..5b948b8b21 100644 --- a/shared/helpers/nunjucks.js +++ b/shared/helpers/nunjucks.js @@ -9,33 +9,34 @@ const { outdent } = require('outdent') * Render component HTML into cheerio * * @param {string} componentName - Component name - * @param {MacroOptions} [params] - Nunjucks macro options (or params) * @param {MacroRenderOptions} [options] - Nunjucks macro render options * @returns {import('cheerio').CheerioAPI} HTML rendered by the macro */ -function render(componentName, params, options) { - return cheerio.load(renderComponent(componentName, params, options)) +function render(componentName, options) { + return cheerio.load(renderComponent(componentName, options)) } /** - * Render Nunjucks template HTML into cheerio + * Render template HTML into cheerio * - * @param {object} [context] - Nunjucks context - * @param {{ [blockName: string]: string }} [blocks] - Nunjucks blocks + * @param {string} templatePath - Nunjucks template path + * @param {TemplateRenderOptions} [options] - Nunjucks template render options * @returns {import('cheerio').CheerioAPI} Nunjucks template output */ -function renderTemplate(context = {}, blocks = {}) { - let viewString = '{% extends "govuk/template.njk" %}' +function renderTemplate(templatePath, options) { + let viewString = `{% extends "${templatePath}" %}` - for (const [blockName, blockContent] of Object.entries(blocks)) { - viewString += outdent` + if (options?.blocks) { + for (const [name, content] of Object.entries(options.blocks)) { + viewString += outdent` - {% block ${blockName} -%} - ${blockContent} - {%- endblock %}` + {% block ${name} -%} + ${content} + {%- endblock %}` + } } - return cheerio.load(renderString(viewString, context)) + return cheerio.load(renderString(viewString, options)) } module.exports = { @@ -46,4 +47,5 @@ module.exports = { /** * @typedef {import('@govuk-frontend/lib/components').MacroOptions} MacroOptions * @typedef {import('@govuk-frontend/lib/components').MacroRenderOptions} MacroRenderOptions + * @typedef {import('@govuk-frontend/lib/components').TemplateRenderOptions} TemplateRenderOptions */ diff --git a/shared/helpers/puppeteer.js b/shared/helpers/puppeteer.js index 447e042461..dfa63741da 100644 --- a/shared/helpers/puppeteer.js +++ b/shared/helpers/puppeteer.js @@ -71,8 +71,8 @@ async function axe(page, overrides = {}) { * @template {object} HandlerContext * @param {import('puppeteer').Page} page - Puppeteer page object * @param {string} componentName - The kebab-cased name of the component - * @param {MacroOptions} [renderOptions] - Component options - * @param {BrowserRenderOptions<HandlerContext>} [browserOptions] - Component options + * @param {MacroRenderOptions} [renderOptions] - Nunjucks macro render options + * @param {BrowserRenderOptions<HandlerContext>} [browserOptions] - Puppeteer browser render options * @returns {Promise<import('puppeteer').Page>} Puppeteer page object */ async function renderAndInitialise( @@ -280,7 +280,7 @@ module.exports = { */ /** - * @typedef {import('@govuk-frontend/lib/components').MacroOptions} MacroOptions + * @typedef {import('@govuk-frontend/lib/components').MacroRenderOptions} MacroRenderOptions * @typedef {import('govuk-frontend').Config} Config - Config for all components via `initAll()` * @typedef {import('govuk-frontend').ConfigKey} ConfigKey - Component config keys, e.g. `accordion` and `characterCount` */ diff --git a/shared/lib/components.js b/shared/lib/components.js index dee8939988..972046daed 100644 --- a/shared/lib/components.js +++ b/shared/lib/components.js @@ -115,16 +115,18 @@ async function getComponentNamesFiltered(filter, packageOptions) { * * @param {string} componentName - Component name * @param {import('./names').PackageOptions} [packageOptions] - Package options (optional) - * @returns {Promise<{ [name: string]: ComponentFixture['options'] }>} Component examples as an object + * @returns {Promise<{ [name: string]: MacroRenderOptions }>} Component examples as an object */ async function getExamples(componentName, packageOptions) { const { fixtures } = await getComponentFixtures(componentName, packageOptions) - /** @type {{ [name: string]: ComponentFixture['options'] }} */ + /** @type {{ [name: string]: MacroRenderOptions }} */ const examples = {} - for (const example of fixtures) { - examples[example.name] = example.options + for (const fixture of fixtures) { + examples[fixture.name] = { + context: fixture.options + } } return examples @@ -134,15 +136,14 @@ async function getExamples(componentName, packageOptions) { * Render component HTML * * @param {string} componentName - Component name - * @param {MacroOptions} [params] - Nunjucks macro options (or params) * @param {MacroRenderOptions} [options] - Nunjucks macro render options * @returns {string} HTML rendered by the component */ -function renderComponent(componentName, params, options) { +function renderComponent(componentName, options) { const macroName = componentNameToMacroName(componentName) const macroPath = `govuk/components/${componentName}/macro.njk` - return renderMacro(macroName, macroPath, params, options) + return renderMacro(macroName, macroPath, options) } /** @@ -150,12 +151,11 @@ function renderComponent(componentName, params, options) { * * @param {string} macroName - The name of the macro * @param {string} macroPath - The path to the file containing the macro - * @param {MacroOptions} [params] - Nunjucks macro options (or params) * @param {MacroRenderOptions} [options] - Nunjucks macro render options * @returns {string} HTML rendered by the macro */ -function renderMacro(macroName, macroPath, params = {}, options) { - const paramsFormatted = JSON.stringify(params, undefined, 2) +function renderMacro(macroName, macroPath, options) { + const paramsFormatted = JSON.stringify(options?.context ?? {}, undefined, 2) let macroString = `{%- from "${macroPath}" import ${macroName} -%}` @@ -165,20 +165,19 @@ function renderMacro(macroName, macroPath, params = {}, options) { ? `{%- call ${macroName}(${paramsFormatted}) -%}${options.callBlock}{%- endcall -%}` : `{{- ${macroName}(${paramsFormatted}) -}}` - return renderString(macroString, {}, options) + return renderString(macroString, options) } /** * Render string * * @param {string} string - Nunjucks string to render - * @param {object} [context] - Nunjucks context object (optional) - * @param {MacroRenderOptions} [options] - Nunjucks macro render options + * @param {MacroRenderOptions | TemplateRenderOptions} [options] - Nunjucks render options * @returns {string} HTML rendered from the Nunjucks string */ -function renderString(string, context = {}, options) { +function renderString(string, options) { const nunjucksEnv = options?.env ?? env - return nunjucksEnv.renderString(string, context) + return nunjucksEnv.renderString(string, options?.context ?? {}) } module.exports = { @@ -244,10 +243,20 @@ module.exports = { * Nunjucks macro render options * * @typedef {object} MacroRenderOptions + * @property {MacroOptions} [context] - Nunjucks context object (optional) * @property {string} [callBlock] - Nunjucks macro `caller()` content (optional) * @property {import('nunjucks').Environment} [env] - Nunjucks environment (optional) */ +/** + * Nunjucks template render options + * + * @typedef {object} TemplateRenderOptions + * @property {object} [context] - Nunjucks context object (optional) + * @property {{ [blockName: string]: string }} [blocks] - Nunjucks blocks (optional) + * @property {import('nunjucks').Environment} [env] - Nunjucks environment (optional) + */ + /** * Component fixtures * (used by the Design System website) diff --git a/shared/tasks/components.mjs b/shared/tasks/components.mjs index 9368e0ef1d..97a97eea8c 100644 --- a/shared/tasks/components.mjs +++ b/shared/tasks/components.mjs @@ -112,7 +112,10 @@ async function generateFixture(componentDataPath, options) { previewLayoutModifiers: example.previewLayoutModifiers ?? [], // Render Nunjucks example - html: renderComponent(componentName, example.options, { env }).trim() + html: renderComponent(componentName, { + context: example.options, + env + }).trim() }) ) From 0f1a980a128ec925134495376106683cbb1b7f33 Mon Sep 17 00:00:00 2001 From: Colin Rotherham <work@colinr.com> Date: Fri, 13 Oct 2023 09:16:49 +0100 Subject: [PATCH 2/4] Add standalone lib `renderTemplate()` for Nunjucks template rendering This makes `renderTemplate()` available to the Review app by adding it to `@govuk-frontend/lib` Like the `render()` test helper, the Cheerio version stays in `@govuk-frontend/helpers` --- package-lock.json | 1 + shared/helpers/nunjucks.js | 22 +++------------------- shared/lib/components.js | 27 ++++++++++++++++++++++++++- shared/lib/package.json | 1 + 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 02fe21df93..f57d896759 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30519,6 +30519,7 @@ "js-yaml": "^4.1.0", "minimatch": "^9.0.3", "nunjucks": "^3.2.4", + "outdent": "^0.8.0", "slash": "^3.0.0" }, "engines": { diff --git a/shared/helpers/nunjucks.js b/shared/helpers/nunjucks.js index 5b948b8b21..67922180d0 100644 --- a/shared/helpers/nunjucks.js +++ b/shared/helpers/nunjucks.js @@ -1,9 +1,5 @@ -const { - renderComponent, - renderString -} = require('@govuk-frontend/lib/components') +const components = require('@govuk-frontend/lib/components') const cheerio = require('cheerio') -const { outdent } = require('outdent') /** * Render component HTML into cheerio @@ -13,7 +9,7 @@ const { outdent } = require('outdent') * @returns {import('cheerio').CheerioAPI} HTML rendered by the macro */ function render(componentName, options) { - return cheerio.load(renderComponent(componentName, options)) + return cheerio.load(components.renderComponent(componentName, options)) } /** @@ -24,19 +20,7 @@ function render(componentName, options) { * @returns {import('cheerio').CheerioAPI} Nunjucks template output */ function renderTemplate(templatePath, options) { - let viewString = `{% extends "${templatePath}" %}` - - if (options?.blocks) { - for (const [name, content] of Object.entries(options.blocks)) { - viewString += outdent` - - {% block ${name} -%} - ${content} - {%- endblock %}` - } - } - - return cheerio.load(renderString(viewString, options)) + return cheerio.load(components.renderTemplate(templatePath, options)) } module.exports = { diff --git a/shared/lib/components.js b/shared/lib/components.js index 972046daed..137259b367 100644 --- a/shared/lib/components.js +++ b/shared/lib/components.js @@ -1,6 +1,7 @@ const { dirname, join } = require('path') const nunjucks = require('nunjucks') +const { outdent } = require('outdent') const { getListing, getDirectories } = require('./files') const { packageTypeToPath, componentNameToMacroName } = require('./names') @@ -180,6 +181,29 @@ function renderString(string, options) { return nunjucksEnv.renderString(string, options?.context ?? {}) } +/** + * Render template HTML + * + * @param {string} templatePath - Nunjucks template path + * @param {TemplateRenderOptions} [options] - Nunjucks template render options + * @returns {string} HTML rendered by template.njk + */ +function renderTemplate(templatePath, options) { + let viewString = `{% extends "${templatePath}" %}` + + if (options?.blocks) { + for (const [name, content] of Object.entries(options.blocks)) { + viewString += outdent` + + {% block ${name} -%} + ${content} + {%- endblock %}` + } + } + + return renderString(viewString, options) +} + module.exports = { getComponentFixtures, getComponentsFixtures, @@ -190,7 +214,8 @@ module.exports = { nunjucksEnv, renderComponent, renderMacro, - renderString + renderString, + renderTemplate } /** diff --git a/shared/lib/package.json b/shared/lib/package.json index c1fc4f3659..fb54156b12 100644 --- a/shared/lib/package.json +++ b/shared/lib/package.json @@ -19,6 +19,7 @@ "js-yaml": "^4.1.0", "minimatch": "^9.0.3", "nunjucks": "^3.2.4", + "outdent": "^0.8.0", "slash": "^3.0.0" } } From 461f03c00fb69415736853f65bcd9c715a3c682a Mon Sep 17 00:00:00 2001 From: Colin Rotherham <work@colinr.com> Date: Mon, 16 Oct 2023 15:28:30 +0100 Subject: [PATCH 3/4] Add standalone lib `renderPreview()` for Nunjucks boilerplate preview This will be used to bypass the Review app in future --- packages/govuk-frontend-review/src/app.mjs | 9 ++-- .../src/common/middleware/assets.mjs | 5 +- .../src/views/tests/boilerplate.njk | 25 --------- shared/lib/components.js | 51 +++++++++++++++++++ 4 files changed, 61 insertions(+), 29 deletions(-) delete mode 100644 packages/govuk-frontend-review/src/views/tests/boilerplate.njk diff --git a/packages/govuk-frontend-review/src/app.mjs b/packages/govuk-frontend-review/src/app.mjs index 51ab47c6c9..6abd786169 100644 --- a/packages/govuk-frontend-review/src/app.mjs +++ b/packages/govuk-frontend-review/src/app.mjs @@ -5,7 +5,8 @@ import { getComponentsFixtures, getComponentNames, getComponentNamesFiltered, - renderComponent + renderComponent, + renderPreview } from '@govuk-frontend/lib/components' import { filterPath, hasPath } from '@govuk-frontend/lib/files' import { getStats, modulePaths } from '@govuk-frontend/stats' @@ -200,9 +201,11 @@ export default async () => { // Test view for injecting rendered components // and testing specific JavaScript configurations - // Example view app.get('/tests/boilerplate', function (req, res) { - res.render('tests/boilerplate') + const componentName = undefined + + // Render blank component preview + res.send(renderPreview(componentName, { env })) }) // Full page example views diff --git a/packages/govuk-frontend-review/src/common/middleware/assets.mjs b/packages/govuk-frontend-review/src/common/middleware/assets.mjs index 762f18f902..c12ca15ff2 100644 --- a/packages/govuk-frontend-review/src/common/middleware/assets.mjs +++ b/packages/govuk-frontend-review/src/common/middleware/assets.mjs @@ -19,6 +19,9 @@ const frontendPath = packageTypeToPath('govuk-frontend', { router.use('/assets', express.static(join(frontendPath, 'assets'))) router.use('/javascripts', express.static(frontendPath)) -router.use('/stylesheets', express.static(join(paths.app, 'dist/stylesheets'))) +router.use('/stylesheets', [ + express.static(frontendPath), + express.static(join(paths.app, 'dist/stylesheets')) +]) export default router diff --git a/packages/govuk-frontend-review/src/views/tests/boilerplate.njk b/packages/govuk-frontend-review/src/views/tests/boilerplate.njk deleted file mode 100644 index c7b91a372f..0000000000 --- a/packages/govuk-frontend-review/src/views/tests/boilerplate.njk +++ /dev/null @@ -1,25 +0,0 @@ -{% extends "component-preview.njk" %} - -{% set pageTitle = "Test boilerplate" %} -{% block pageTitle %}{{ pageTitle }} - GOV.UK{% endblock %} - -{% block head %} - {{ super() }} - <script type="importmap"> - { "imports": { "govuk-frontend": "/javascripts/govuk-frontend.min.js" } } - </script> -{% endblock %} - -{% block content %} - <div class="app-whitespace-highlight"> - <h1 class="govuk-heading-l">Test boilerplate</h1> - <p class="govuk-body"> - Used during testing to inject rendered components and test specific configurations - </p> - <div id="slot"></div> - </div> -{% endblock %} - -{% block bodyEnd %} - <script type="module" src="/javascripts/govuk-frontend.min.js"></script> -{% endblock %} diff --git a/shared/lib/components.js b/shared/lib/components.js index 137259b367..51cb83841e 100644 --- a/shared/lib/components.js +++ b/shared/lib/components.js @@ -169,6 +169,56 @@ function renderMacro(macroName, macroPath, options) { return renderString(macroString, options) } +/** + * Render component preview on boilerplate page + * + * @param {string} [componentName] - Component name + * @param {MacroRenderOptions} [options] - Nunjucks macro render options + * @returns {string} HTML rendered from the Nunjucks template + */ +function renderPreview(componentName, options) { + const stylesPath = '/stylesheets/govuk-frontend.min.css' + const scriptsPath = '/javascripts/govuk-frontend.min.js' + + // Render page template + return renderTemplate('govuk/template.njk', { + blocks: { + pageTitle: 'Test boilerplate - GOV.UK', + head: outdent` + <link rel="stylesheet" href="${stylesPath}"> + + <script type="importmap"> + { "imports": { "govuk-frontend": "${scriptsPath}" } } + </script> + `, + + // Remove default template blocks + skipLink: '', + bodyStart: '', + header: '', + footer: '', + + main: outdent` + <div class="govuk-width-container"> + <h1 class="govuk-heading-l">Test boilerplate</h1> + <p class="govuk-body">Used during testing to inject rendered components and test specific configurations</p> + + <div id="slot" class="govuk-!-margin-top-6"> + ${componentName ? renderComponent(componentName, options) : ''} + </div> + </div> + `, + + bodyEnd: outdent` + <script type="module" src="${scriptsPath}"></script> + ` + }, + context: { + mainClasses: 'govuk-main-wrapper--auto-spacing' + } + }) +} + /** * Render string * @@ -214,6 +264,7 @@ module.exports = { nunjucksEnv, renderComponent, renderMacro, + renderPreview, renderString, renderTemplate } From 699dcccab5c3f2fb532201c6c5fff46b38553984 Mon Sep 17 00:00:00 2001 From: Colin Rotherham <work@colinr.com> Date: Mon, 16 Oct 2023 15:29:17 +0100 Subject: [PATCH 4/4] Rename component render functions to `render()` Test helpers and lib functions are now consistent Library functions always return strings, but test helpers are available for Nunjucks (via Cheerio) and Google Chrome (via Puppeteer page) ``` import { render } from '@govuk-frontend/lib/components' import { render } from '@govuk-frontend/helpers/nunjucks' import { render } from '@govuk-frontend/helpers/puppeteer' ``` --- packages/govuk-frontend-review/src/app.mjs | 4 +- .../common/nunjucks/globals/get-html-code.mjs | 4 +- .../accordion/accordion.puppeteer.test.js | 10 +-- .../button/button.puppeteer.test.js | 28 +++---- .../character-count.jsdom.test.mjs | 7 +- .../character-count.puppeteer.test.js | 75 ++++++++----------- .../checkboxes/checkboxes.puppeteer.test.js | 29 +++---- .../components/components.template.test.js | 4 +- .../error-summary.puppeteer.test.js | 14 ++-- .../exit-this-page.puppeteer.test.js | 10 +-- .../header/header.puppeteer.test.js | 15 ++-- .../notification-banner.puppeteer.test.js | 17 ++--- .../radios/radios.puppeteer.test.js | 23 +++--- .../skip-link/skip-link.puppeteer.test.js | 15 ++-- .../components/tabs/tabs.puppeteer.test.js | 12 +-- shared/helpers/nunjucks.js | 2 +- shared/helpers/puppeteer.js | 15 ++-- shared/lib/components.js | 6 +- shared/tasks/components.mjs | 4 +- 19 files changed, 125 insertions(+), 169 deletions(-) diff --git a/packages/govuk-frontend-review/src/app.mjs b/packages/govuk-frontend-review/src/app.mjs index 6abd786169..937b07b6de 100644 --- a/packages/govuk-frontend-review/src/app.mjs +++ b/packages/govuk-frontend-review/src/app.mjs @@ -5,7 +5,7 @@ import { getComponentsFixtures, getComponentNames, getComponentNamesFiltered, - renderComponent, + render, renderPreview } from '@govuk-frontend/lib/components' import { filterPath, hasPath } from '@govuk-frontend/lib/files' @@ -159,7 +159,7 @@ export default async () => { } // Construct and evaluate the component with the data for this example - res.locals.componentView = renderComponent(componentName, { + res.locals.componentView = render(componentName, { context: fixture.options, env }) diff --git a/packages/govuk-frontend-review/src/common/nunjucks/globals/get-html-code.mjs b/packages/govuk-frontend-review/src/common/nunjucks/globals/get-html-code.mjs index 8312e3e5ac..1357727838 100644 --- a/packages/govuk-frontend-review/src/common/nunjucks/globals/get-html-code.mjs +++ b/packages/govuk-frontend-review/src/common/nunjucks/globals/get-html-code.mjs @@ -1,4 +1,4 @@ -import { renderComponent } from '@govuk-frontend/lib/components' +import { render } from '@govuk-frontend/lib/components' import beautify from 'js-beautify' /** @@ -10,7 +10,7 @@ import beautify from 'js-beautify' * @returns {string} HTML rendered by the component */ export function getHTMLCode(componentName, options) { - const html = renderComponent(componentName, { ...options, env: this.env }) + const html = render(componentName, { ...options, env: this.env }) // Default beautify options const beautifyOptions = beautify.html.defaultOptions() diff --git a/packages/govuk-frontend/src/govuk/components/accordion/accordion.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/accordion/accordion.puppeteer.test.js index 659c27ab00..03a86cee2c 100644 --- a/packages/govuk-frontend/src/govuk/components/accordion/accordion.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/accordion/accordion.puppeteer.test.js @@ -1,7 +1,7 @@ const { goToComponent, goToExample, - renderAndInitialise, + render, getAccessibleName } = require('@govuk-frontend/helpers/puppeteer') const { getExamples } = require('@govuk-frontend/lib/components') @@ -685,7 +685,7 @@ describe('/components/accordion', () => { }) it('injects the localised strings as text not HTML', async () => { - await renderAndInitialise(page, 'accordion', examples.default, { + await render(page, 'accordion', examples.default, { config: { i18n: { showAllSections: 'Show <strong>all sections</strong>', @@ -721,7 +721,7 @@ describe('/components/accordion', () => { it('throws when GOV.UK Frontend is not supported', async () => { await expect( - renderAndInitialise(page, 'accordion', examples.default, { + render(page, 'accordion', examples.default, { beforeInitialisation() { document.body.classList.remove('govuk-frontend-supported') } @@ -734,7 +734,7 @@ describe('/components/accordion', () => { it('throws when $module is not set', async () => { await expect( - renderAndInitialise(page, 'accordion', examples.default, { + render(page, 'accordion', examples.default, { beforeInitialisation($module) { $module.remove() } @@ -747,7 +747,7 @@ describe('/components/accordion', () => { it('throws when receiving the wrong type for $module', async () => { await expect( - renderAndInitialise(page, 'accordion', examples.default, { + render(page, 'accordion', examples.default, { beforeInitialisation($module) { // Replace with an `<svg>` element which is not an `HTMLElement` in the DOM (but an `SVGElement`) $module.outerHTML = `<svg data-module="govuk-accordion"></svg>` diff --git a/packages/govuk-frontend/src/govuk/components/button/button.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/button/button.puppeteer.test.js index ac0638d102..48e4cad772 100644 --- a/packages/govuk-frontend/src/govuk/components/button/button.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/button/button.puppeteer.test.js @@ -1,7 +1,4 @@ -const { - goToComponent, - renderAndInitialise -} = require('@govuk-frontend/helpers/puppeteer') +const { goToComponent, render } = require('@govuk-frontend/helpers/puppeteer') const { getExamples } = require('@govuk-frontend/lib/components') describe('/components/button', () => { @@ -178,7 +175,7 @@ describe('/components/button', () => { let $button beforeEach(async () => { - await renderAndInitialise(page, 'button', examples.default, { + await render(page, 'button', examples.default, { config: { preventDoubleClick: true } @@ -237,16 +234,11 @@ describe('/components/button', () => { let $button beforeEach(async () => { - await renderAndInitialise( - page, - 'button', - examples["don't prevent double click"], - { - config: { - preventDoubleClick: true - } + await render(page, 'button', examples["don't prevent double click"], { + config: { + preventDoubleClick: true } - ) + }) $button = await setButtonTracking(await page.$('button')) }) @@ -266,7 +258,7 @@ describe('/components/button', () => { let $button beforeEach(async () => { - await renderAndInitialise(page, 'button', examples.default, { + await render(page, 'button', examples.default, { config: { preventDoubleClick: true } @@ -329,7 +321,7 @@ describe('/components/button', () => { it('throws when GOV.UK Frontend is not supported', async () => { await expect( - renderAndInitialise(page, 'button', examples.default, { + render(page, 'button', examples.default, { beforeInitialisation() { document.body.classList.remove('govuk-frontend-supported') } @@ -342,7 +334,7 @@ describe('/components/button', () => { it('throws when $module is not set', async () => { await expect( - renderAndInitialise(page, 'button', examples.default, { + render(page, 'button', examples.default, { beforeInitialisation($module) { $module.remove() } @@ -355,7 +347,7 @@ describe('/components/button', () => { it('throws when receiving the wrong type for $module', async () => { await expect( - renderAndInitialise(page, 'button', examples.default, { + render(page, 'button', examples.default, { beforeInitialisation($module) { // Replace with an `<svg>` element which is not an `HTMLElement` in the DOM (but an `SVGElement`) $module.outerHTML = `<svg data-module="govuk-button"></svg>` diff --git a/packages/govuk-frontend/src/govuk/components/character-count/character-count.jsdom.test.mjs b/packages/govuk-frontend/src/govuk/components/character-count/character-count.jsdom.test.mjs index 4226992a45..5a5a1c1d5d 100644 --- a/packages/govuk-frontend/src/govuk/components/character-count/character-count.jsdom.test.mjs +++ b/packages/govuk-frontend/src/govuk/components/character-count/character-count.jsdom.test.mjs @@ -1,4 +1,4 @@ -import { getExamples, renderComponent } from '@govuk-frontend/lib/components' +import { getExamples, render } from '@govuk-frontend/lib/components' import { CharacterCount } from './character-count.mjs' @@ -7,10 +7,7 @@ describe('CharacterCount', () => { beforeAll(async () => { const examples = await getExamples('character-count') - html = renderComponent( - 'character-count', - examples['to configure in JavaScript'] - ) + html = render('character-count', examples['to configure in JavaScript']) }) beforeEach(async () => { diff --git a/packages/govuk-frontend/src/govuk/components/character-count/character-count.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/character-count/character-count.puppeteer.test.js index 35c989f96c..fe339cf098 100644 --- a/packages/govuk-frontend/src/govuk/components/character-count/character-count.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/character-count/character-count.puppeteer.test.js @@ -1,7 +1,4 @@ -const { - goToComponent, - renderAndInitialise -} = require('@govuk-frontend/helpers/puppeteer') +const { goToComponent, render } = require('@govuk-frontend/helpers/puppeteer') const { getExamples } = require('@govuk-frontend/lib/components') describe('Character count', () => { @@ -496,7 +493,7 @@ describe('Character count', () => { describe('JavaScript configuration', () => { describe('at instantiation', () => { it('configures the number of characters', async () => { - await renderAndInitialise( + await render( page, 'character-count', examples['to configure in JavaScript'], @@ -519,7 +516,7 @@ describe('Character count', () => { }) it('configures the number of words', async () => { - await renderAndInitialise( + await render( page, 'character-count', examples['to configure in JavaScript'], @@ -542,7 +539,7 @@ describe('Character count', () => { }) it('configures the threshold', async () => { - await renderAndInitialise( + await render( page, 'character-count', examples['to configure in JavaScript'], @@ -569,7 +566,7 @@ describe('Character count', () => { // This tests that a description can be provided through JavaScript attributes // and interpolated with the limit provided to the character count in JS. - await renderAndInitialise( + await render( page, 'character-count', examples[ @@ -597,7 +594,7 @@ describe('Character count', () => { describe('via `initAll`', () => { it('configures the number of characters', async () => { - await renderAndInitialise( + await render( page, 'character-count', examples['to configure in JavaScript'], @@ -620,7 +617,7 @@ describe('Character count', () => { }) it('configures the number of words', async () => { - await renderAndInitialise( + await render( page, 'character-count', examples['to configure in JavaScript'], @@ -643,7 +640,7 @@ describe('Character count', () => { }) it('configures the threshold', async () => { - await renderAndInitialise( + await render( page, 'character-count', examples['to configure in JavaScript'], @@ -669,7 +666,7 @@ describe('Character count', () => { describe('when data-attributes are present', () => { it('uses `maxlength` data attribute instead of the JS one', async () => { - await renderAndInitialise(page, 'character-count', examples.default, { + await render(page, 'character-count', examples.default, { config: { maxlength: 12 // JS configuration that would tell 1 character remaining } @@ -687,7 +684,7 @@ describe('Character count', () => { }) it("uses `maxlength` data attribute instead of JS's `maxwords`", async () => { - await renderAndInitialise(page, 'character-count', examples.default, { + await render(page, 'character-count', examples.default, { config: { maxwords: 12 } @@ -705,16 +702,11 @@ describe('Character count', () => { }) it('uses `maxwords` data attribute instead of the JS one', async () => { - await renderAndInitialise( - page, - 'character-count', - examples['with word count'], - { - config: { - maxwords: 12 // JS configuration that would tell 1 word remaining - } + await render(page, 'character-count', examples['with word count'], { + config: { + maxwords: 12 // JS configuration that would tell 1 word remaining } - ) + }) await page.type('.govuk-js-character-count', 'Hello '.repeat(11), { delay: 50 @@ -728,16 +720,11 @@ describe('Character count', () => { }) it("uses `maxwords` data attribute instead of the JS's `maxlength`", async () => { - await renderAndInitialise( - page, - 'character-count', - examples['with word count'], - { - config: { - maxlength: 10 - } + await render(page, 'character-count', examples['with word count'], { + config: { + maxlength: 10 } - ) + }) await page.type('.govuk-js-character-count', 'Hello '.repeat(11), { delay: 50 @@ -757,7 +744,7 @@ describe('Character count', () => { // element holding the textarea's accessible description // (and interpolated to replace `%{count}` with the maximum) - await renderAndInitialise( + await render( page, 'character-count', examples['when neither maxlength nor maxwords are set'], @@ -779,7 +766,7 @@ describe('Character count', () => { describe('Cross Side Scripting prevention', () => { it('injects the localised strings as text not HTML', async () => { - await renderAndInitialise( + await render( page, 'character-count', examples['to configure in JavaScript'], @@ -814,7 +801,7 @@ describe('Character count', () => { it('throws when GOV.UK Frontend is not supported', async () => { await expect( - renderAndInitialise(page, 'character-count', examples.default, { + render(page, 'character-count', examples.default, { beforeInitialisation() { document.body.classList.remove('govuk-frontend-supported') } @@ -827,7 +814,7 @@ describe('Character count', () => { it('throws when $module is not set', async () => { await expect( - renderAndInitialise(page, 'character-count', examples.default, { + render(page, 'character-count', examples.default, { beforeInitialisation($module) { $module.remove() } @@ -840,7 +827,7 @@ describe('Character count', () => { it('throws when receiving the wrong type for $module', async () => { await expect( - renderAndInitialise(page, 'character-count', examples.default, { + render(page, 'character-count', examples.default, { beforeInitialisation($module) { // Replace with an `<svg>` element which is not an `HTMLElement` in the DOM (but an `SVGElement`) $module.outerHTML = `<svg data-module="govuk-character-count"></svg>` @@ -855,7 +842,7 @@ describe('Character count', () => { it('throws when the textarea is missing', async () => { await expect( - renderAndInitialise(page, 'character-count', examples.default, { + render(page, 'character-count', examples.default, { beforeInitialisation($module, { selector }) { $module.querySelector(selector).remove() }, @@ -872,7 +859,7 @@ describe('Character count', () => { it('throws when the textarea is not the right type', async () => { await expect( - renderAndInitialise(page, 'character-count', examples.default, { + render(page, 'character-count', examples.default, { beforeInitialisation($module, { selector }) { // Replace with a tag that's neither an `<input>` or `<textarea>` $module.querySelector(selector).outerHTML = @@ -891,7 +878,7 @@ describe('Character count', () => { it('throws when the textarea description is missing', async () => { await expect( - renderAndInitialise(page, 'character-count', examples.default, { + render(page, 'character-count', examples.default, { beforeInitialisation($module, { selector }) { $module.querySelector(selector).remove() }, @@ -906,9 +893,13 @@ describe('Character count', () => { }) }) - it('throws when receiving invalid configuration', async () => { + it('throws when receiving invalid JavaScript configuration', async () => { await expect( - renderAndInitialise(page, 'character-count', { context: {} }) + render( + page, + 'character-count', + examples['to configure in JavaScript'] + ) ).rejects.toEqual({ name: 'ConfigError', message: @@ -924,7 +915,7 @@ describe('Character count', () => { const pageErrorListener = jest.fn() page.on('pageerror', pageErrorListener) - await renderAndInitialise(page, 'character-count', examples.default, { + await render(page, 'character-count', examples.default, { config: { // Override maxlength to 10 maxlength: 10 diff --git a/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.puppeteer.test.js index 133c130957..eb8c92373a 100644 --- a/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.puppeteer.test.js @@ -4,7 +4,7 @@ const { getAttribute, getProperty, isVisible, - renderAndInitialise + render } = require('@govuk-frontend/helpers/puppeteer') const { getExamples } = require('@govuk-frontend/lib/components') @@ -328,7 +328,7 @@ describe('Checkboxes with multiple groups and a "None" checkbox and conditional it('throws when GOV.UK Frontend is not supported', async () => { await expect( - renderAndInitialise(page, 'checkboxes', examples.default, { + render(page, 'checkboxes', examples.default, { beforeInitialisation() { document.body.classList.remove('govuk-frontend-supported') } @@ -341,7 +341,7 @@ describe('Checkboxes with multiple groups and a "None" checkbox and conditional it('throws when $module is not set', async () => { await expect( - renderAndInitialise(page, 'checkboxes', examples.default, { + render(page, 'checkboxes', examples.default, { beforeInitialisation($module) { $module.remove() } @@ -354,7 +354,7 @@ describe('Checkboxes with multiple groups and a "None" checkbox and conditional it('throws when receiving the wrong type for $module', async () => { await expect( - renderAndInitialise(page, 'checkboxes', examples.default, { + render(page, 'checkboxes', examples.default, { beforeInitialisation($module) { // Replace with an `<svg>` element which is not an `HTMLElement` in the DOM (but an `SVGElement`) $module.outerHTML = `<svg data-module="govuk-checkboxes"></svg>` @@ -369,7 +369,7 @@ describe('Checkboxes with multiple groups and a "None" checkbox and conditional it('throws when the input list is empty', async () => { await expect( - renderAndInitialise(page, 'checkboxes', examples.default, { + render(page, 'checkboxes', examples.default, { beforeInitialisation($module, { selector }) { $module .querySelectorAll(selector) @@ -388,19 +388,14 @@ describe('Checkboxes with multiple groups and a "None" checkbox and conditional it('throws when a conditional target element is not found', async () => { await expect( - renderAndInitialise( - page, - 'checkboxes', - examples['with conditional items'], - { - beforeInitialisation($module, { selector }) { - $module.querySelector(selector).remove() - }, - context: { - selector: '.govuk-checkboxes__conditional' - } + render(page, 'checkboxes', examples['with conditional items'], { + beforeInitialisation($module, { selector }) { + $module.querySelector(selector).remove() + }, + context: { + selector: '.govuk-checkboxes__conditional' } - ) + }) ).rejects.toEqual({ name: 'ElementError', message: diff --git a/packages/govuk-frontend/src/govuk/components/components.template.test.js b/packages/govuk-frontend/src/govuk/components/components.template.test.js index c10aab7abf..b2ba1ba13c 100644 --- a/packages/govuk-frontend/src/govuk/components/components.template.test.js +++ b/packages/govuk-frontend/src/govuk/components/components.template.test.js @@ -5,7 +5,7 @@ const { getComponentsFixtures, getComponentNames, nunjucksEnv, - renderComponent + render } = require('@govuk-frontend/lib/components') const { HtmlValidate } = require('html-validate') @@ -136,7 +136,7 @@ describe('Components', () => { for (const { component: componentName, fixtures } of componentsFixtures) { const fixtureTasks = fixtures.map( async ({ name: exampleName, options }) => { - const html = renderComponent(componentName, { context: options }) + const html = render(componentName, { context: options }) // Validate HTML return expect({ diff --git a/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.puppeteer.test.js index ce8c606a65..aa72e05b3f 100644 --- a/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.puppeteer.test.js @@ -1,7 +1,7 @@ const { goToComponent, goToExample, - renderAndInitialise + render } = require('@govuk-frontend/helpers/puppeteer') const { getExamples } = require('@govuk-frontend/lib/components') @@ -68,7 +68,7 @@ describe('Error Summary', () => { describe('using JavaScript configuration', () => { beforeAll(async () => { - await renderAndInitialise(page, 'error-summary', examples.default, { + await render(page, 'error-summary', examples.default, { config: { disableAutoFocus: true } @@ -94,7 +94,7 @@ describe('Error Summary', () => { describe('using JavaScript configuration, but enabled via data-attributes', () => { beforeAll(async () => { - await renderAndInitialise( + await render( page, 'error-summary', examples['autofocus explicitly enabled'] @@ -118,7 +118,7 @@ describe('Error Summary', () => { describe('using `initAll`', () => { beforeAll(async () => { - await renderAndInitialise(page, 'error-summary', examples.default, { + await render(page, 'error-summary', examples.default, { config: { disableAutoFocus: true } @@ -219,7 +219,7 @@ describe('Error Summary', () => { it('throws when GOV.UK Frontend is not supported', async () => { await expect( - renderAndInitialise(page, 'error-summary', examples.default, { + render(page, 'error-summary', examples.default, { beforeInitialisation() { document.body.classList.remove('govuk-frontend-supported') } @@ -232,7 +232,7 @@ describe('Error Summary', () => { it('throws when $module is not set', async () => { await expect( - renderAndInitialise(page, 'error-summary', examples.default, { + render(page, 'error-summary', examples.default, { beforeInitialisation($module) { $module.remove() } @@ -245,7 +245,7 @@ describe('Error Summary', () => { it('throws when receiving the wrong type for $module', async () => { await expect( - renderAndInitialise(page, 'error-summary', examples.default, { + render(page, 'error-summary', examples.default, { beforeInitialisation($module) { // Replace with an `<svg>` element which is not an `HTMLElement` in the DOM (but an `SVGElement`) $module.outerHTML = `<svg data-module="govuk-error-summary"></svg>` diff --git a/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.puppeteer.test.js index 681108b40e..0eb23d7284 100644 --- a/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.puppeteer.test.js @@ -1,7 +1,7 @@ const { goToComponent, goToExample, - renderAndInitialise + render } = require('@govuk-frontend/helpers/puppeteer') const { getExamples } = require('@govuk-frontend/lib/components') @@ -197,7 +197,7 @@ describe('/components/exit-this-page', () => { it('throws when GOV.UK Frontend is not supported', async () => { await expect( - renderAndInitialise(page, 'exit-this-page', examples.default, { + render(page, 'exit-this-page', examples.default, { beforeInitialisation() { document.body.classList.remove('govuk-frontend-supported') } @@ -210,7 +210,7 @@ describe('/components/exit-this-page', () => { it('throws when $module is not set', async () => { await expect( - renderAndInitialise(page, 'exit-this-page', examples.default, { + render(page, 'exit-this-page', examples.default, { beforeInitialisation($module) { $module.remove() } @@ -223,7 +223,7 @@ describe('/components/exit-this-page', () => { it('throws when receiving the wrong type for $module', async () => { await expect( - renderAndInitialise(page, 'exit-this-page', examples.default, { + render(page, 'exit-this-page', examples.default, { beforeInitialisation($module) { // Replace with an `<svg>` element which is not an `HTMLElement` in the DOM (but an `SVGElement`) $module.outerHTML = `<svg data-module="govuk-exit-this-page"></svg>` @@ -238,7 +238,7 @@ describe('/components/exit-this-page', () => { it('throws when the button is missing', async () => { await expect( - renderAndInitialise(page, 'exit-this-page', examples.default, { + render(page, 'exit-this-page', examples.default, { beforeInitialisation($module, { selector }) { $module.querySelector(selector).remove() }, diff --git a/packages/govuk-frontend/src/govuk/components/header/header.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/header/header.puppeteer.test.js index 2513c23190..fbfa3e4c70 100644 --- a/packages/govuk-frontend/src/govuk/components/header/header.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/header/header.puppeteer.test.js @@ -1,7 +1,4 @@ -const { - goToComponent, - renderAndInitialise -} = require('@govuk-frontend/helpers/puppeteer') +const { goToComponent, render } = require('@govuk-frontend/helpers/puppeteer') const { getExamples } = require('@govuk-frontend/lib/components') const { KnownDevices } = require('puppeteer') @@ -177,7 +174,7 @@ describe('Header navigation', () => { it('throws when GOV.UK Frontend is not supported', async () => { await expect( - renderAndInitialise(page, 'header', examples.default, { + render(page, 'header', examples.default, { beforeInitialisation() { document.body.classList.remove('govuk-frontend-supported') } @@ -190,7 +187,7 @@ describe('Header navigation', () => { it('throws when $module is not set', async () => { await expect( - renderAndInitialise(page, 'header', examples.default, { + render(page, 'header', examples.default, { beforeInitialisation($module) { // Remove the root of the components as a way // for the constructor to receive the wrong type for `$module` @@ -208,12 +205,12 @@ describe('Header navigation', () => { // and should keep rendering. No expectations as the JavaScript // will just return early. All we ask of that test is for it not // to throw during the initialisation - await renderAndInitialise(page, 'header', examples.default) + await render(page, 'header', examples.default) }) it("throws when the toggle's aria-control attribute is missing", async () => { await expect( - renderAndInitialise(page, 'header', examples['with navigation'], { + render(page, 'header', examples['with navigation'], { beforeInitialisation($module, { selector }) { $module.querySelector(selector).removeAttribute('aria-controls') }, @@ -230,7 +227,7 @@ describe('Header navigation', () => { it('throws when the menu is missing, but a toggle is present', async () => { await expect( - renderAndInitialise(page, 'header', examples['with navigation'], { + render(page, 'header', examples['with navigation'], { beforeInitialisation($module, { selector }) { // Remove the menu `<ul>` referenced by $menuButton's `aria-controls` $module.querySelector(selector).remove() diff --git a/packages/govuk-frontend/src/govuk/components/notification-banner/notification-banner.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/notification-banner/notification-banner.puppeteer.test.js index 0bf002d75f..e30a901733 100644 --- a/packages/govuk-frontend/src/govuk/components/notification-banner/notification-banner.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/notification-banner/notification-banner.puppeteer.test.js @@ -1,7 +1,4 @@ -const { - renderAndInitialise, - goToComponent -} = require('@govuk-frontend/helpers/puppeteer') +const { render, goToComponent } = require('@govuk-frontend/helpers/puppeteer') const { getExamples } = require('@govuk-frontend/lib/components') describe('Notification banner', () => { @@ -78,7 +75,7 @@ describe('Notification banner', () => { describe('and auto-focus is disabled using JavaScript configuration', () => { beforeAll(async () => { - await renderAndInitialise( + await render( page, 'notification-banner', examples['with type as success'], @@ -109,7 +106,7 @@ describe('Notification banner', () => { describe('and auto-focus is disabled using options passed to initAll', () => { beforeAll(async () => { - await renderAndInitialise( + await render( page, 'notification-banner', examples['with type as success'], @@ -140,7 +137,7 @@ describe('Notification banner', () => { describe('and autofocus is disabled in JS but enabled in data attributes', () => { beforeAll(async () => { - await renderAndInitialise( + await render( page, 'notification-banner', examples['auto-focus explicitly enabled, with type as success'], @@ -218,7 +215,7 @@ describe('Notification banner', () => { describe('errors at instantiation', () => { it('throws when GOV.UK Frontend is not supported', async () => { await expect( - renderAndInitialise(page, 'notification-banner', examples.default, { + render(page, 'notification-banner', examples.default, { beforeInitialisation() { document.body.classList.remove('govuk-frontend-supported') } @@ -231,7 +228,7 @@ describe('Notification banner', () => { it('throws when $module is not set', async () => { await expect( - renderAndInitialise(page, 'notification-banner', examples.default, { + render(page, 'notification-banner', examples.default, { beforeInitialisation($module) { $module.remove() } @@ -244,7 +241,7 @@ describe('Notification banner', () => { it('throws when receiving the wrong type for $module', async () => { await expect( - renderAndInitialise(page, 'notification-banner', examples.default, { + render(page, 'notification-banner', examples.default, { beforeInitialisation($module) { // Replace with an `<svg>` element which is not an `HTMLElement` in the DOM (but an `SVGElement`) $module.outerHTML = `<svg data-module="govuk-notification-banner"></svg>` diff --git a/packages/govuk-frontend/src/govuk/components/radios/radios.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/radios/radios.puppeteer.test.js index 31ef748c5e..25a6d7940a 100644 --- a/packages/govuk-frontend/src/govuk/components/radios/radios.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/radios/radios.puppeteer.test.js @@ -4,7 +4,7 @@ const { getProperty, getAttribute, isVisible, - renderAndInitialise + render } = require('@govuk-frontend/helpers/puppeteer') const { getExamples } = require('@govuk-frontend/lib/components') @@ -279,7 +279,7 @@ describe('Radios', () => { it('throws when GOV.UK Frontend is not supported', async () => { await expect( - renderAndInitialise(page, 'radios', examples.default, { + render(page, 'radios', examples.default, { beforeInitialisation() { document.body.classList.remove('govuk-frontend-supported') } @@ -292,7 +292,7 @@ describe('Radios', () => { it('throws when $module is not set', async () => { await expect( - renderAndInitialise(page, 'radios', examples.default, { + render(page, 'radios', examples.default, { beforeInitialisation($module) { $module.remove() } @@ -305,7 +305,7 @@ describe('Radios', () => { it('throws when receiving the wrong type for $module', async () => { await expect( - renderAndInitialise(page, 'radios', examples.default, { + render(page, 'radios', examples.default, { beforeInitialisation($module) { // Replace with an `<svg>` element which is not an `HTMLElement` in the DOM (but an `SVGElement`) $module.outerHTML = `<svg data-module="govuk-radios"></svg>` @@ -319,7 +319,7 @@ describe('Radios', () => { it('throws when the input list is empty', async () => { await expect( - renderAndInitialise(page, 'radios', examples.default, { + render(page, 'radios', examples.default, { beforeInitialisation($module, { selector }) { $module.querySelectorAll(selector).forEach((item) => item.remove()) }, @@ -335,16 +335,11 @@ describe('Radios', () => { it('throws when a conditional target element is not found', async () => { await expect( - renderAndInitialise( - page, - 'radios', - examples['with conditional items'], - { - beforeInitialisation($module) { - $module.querySelector('.govuk-radios__conditional').remove() - } + render(page, 'radios', examples['with conditional items'], { + beforeInitialisation($module) { + $module.querySelector('.govuk-radios__conditional').remove() } - ) + }) ).rejects.toEqual({ name: 'ElementError', message: diff --git a/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.puppeteer.test.js index a373524b59..1b2b7b86ba 100644 --- a/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.puppeteer.test.js @@ -1,7 +1,4 @@ -const { - goToExample, - renderAndInitialise -} = require('@govuk-frontend/helpers/puppeteer') +const { goToExample, render } = require('@govuk-frontend/helpers/puppeteer') const { getExamples } = require('@govuk-frontend/lib/components') describe('Skip Link', () => { @@ -70,7 +67,7 @@ describe('Skip Link', () => { it('throws when GOV.UK Frontend is not supported', async () => { await expect( - renderAndInitialise(page, 'skip-link', examples.default, { + render(page, 'skip-link', examples.default, { beforeInitialisation() { document.body.classList.remove('govuk-frontend-supported') } @@ -83,7 +80,7 @@ describe('Skip Link', () => { it('throws when $module is not set', async () => { await expect( - renderAndInitialise(page, 'skip-link', examples.default, { + render(page, 'skip-link', examples.default, { beforeInitialisation($module) { $module.remove() } @@ -96,7 +93,7 @@ describe('Skip Link', () => { it('throws when receiving the wrong type for $module', async () => { await expect( - renderAndInitialise(page, 'skip-link', examples.default, { + render(page, 'skip-link', examples.default, { beforeInitialisation($module) { // Replace with an `<svg>` element which is not an `HTMLElement` in the DOM (but an `SVGElement`) $module.outerHTML = `<svg data-module="govuk-skip-link"></svg>` @@ -111,7 +108,7 @@ describe('Skip Link', () => { it('throws when the linked element is missing', async () => { await expect( - renderAndInitialise(page, 'skip-link', { + render(page, 'skip-link', { context: { text: 'Skip to main content', href: '#this-element-does-not-exist' @@ -126,7 +123,7 @@ describe('Skip Link', () => { it('throws when the href does not contain a hash', async () => { await expect( - renderAndInitialise(page, 'skip-link', { + render(page, 'skip-link', { context: { text: 'Skip to main content', href: 'this-element-does-not-exist' diff --git a/packages/govuk-frontend/src/govuk/components/tabs/tabs.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/tabs/tabs.puppeteer.test.js index 7dd7c833ee..88c8f34c03 100644 --- a/packages/govuk-frontend/src/govuk/components/tabs/tabs.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/tabs/tabs.puppeteer.test.js @@ -1,7 +1,7 @@ const { goTo, goToComponent, - renderAndInitialise + render } = require('@govuk-frontend/helpers/puppeteer') const { getExamples } = require('@govuk-frontend/lib/components') const { KnownDevices } = require('puppeteer') @@ -257,7 +257,7 @@ describe('/components/tabs', () => { it('throws when GOV.UK Frontend is not supported', async () => { await expect( - renderAndInitialise(page, 'tabs', examples.default, { + render(page, 'tabs', examples.default, { beforeInitialisation() { document.body.classList.remove('govuk-frontend-supported') } @@ -270,7 +270,7 @@ describe('/components/tabs', () => { it('throws when $module is not set', async () => { await expect( - renderAndInitialise(page, 'tabs', examples.default, { + render(page, 'tabs', examples.default, { beforeInitialisation($module) { $module.remove() } @@ -283,7 +283,7 @@ describe('/components/tabs', () => { it('throws when there are no tabs', async () => { await expect( - renderAndInitialise(page, 'tabs', examples.default, { + render(page, 'tabs', examples.default, { beforeInitialisation($module, { selector }) { $module .querySelectorAll(selector) @@ -301,7 +301,7 @@ describe('/components/tabs', () => { it('throws when the tab list is missing', async () => { await expect( - renderAndInitialise(page, 'tabs', examples.default, { + render(page, 'tabs', examples.default, { beforeInitialisation($module, { selector }) { $module .querySelector(selector) @@ -319,7 +319,7 @@ describe('/components/tabs', () => { it('throws when there the tab list is empty', async () => { await expect( - renderAndInitialise(page, 'tabs', examples.default, { + render(page, 'tabs', examples.default, { beforeInitialisation($module, { selector, className }) { $module .querySelectorAll(selector) diff --git a/shared/helpers/nunjucks.js b/shared/helpers/nunjucks.js index 67922180d0..98cd30984f 100644 --- a/shared/helpers/nunjucks.js +++ b/shared/helpers/nunjucks.js @@ -9,7 +9,7 @@ const cheerio = require('cheerio') * @returns {import('cheerio').CheerioAPI} HTML rendered by the macro */ function render(componentName, options) { - return cheerio.load(components.renderComponent(componentName, options)) + return cheerio.load(components.render(componentName, options)) } /** diff --git a/shared/helpers/puppeteer.js b/shared/helpers/puppeteer.js index dfa63741da..1724b765d2 100644 --- a/shared/helpers/puppeteer.js +++ b/shared/helpers/puppeteer.js @@ -1,6 +1,6 @@ const { AxePuppeteer } = require('@axe-core/puppeteer') const { ports } = require('@govuk-frontend/config') -const { renderComponent } = require('@govuk-frontend/lib/components') +const components = require('@govuk-frontend/lib/components') const { componentNameToClassName } = require('@govuk-frontend/lib/names') const slug = require('slug') @@ -58,7 +58,7 @@ async function axe(page, overrides = {}) { } /** - * Render and initialise a component within test boilerplate HTML + * Render component HTML with browser preview * * Renders a component's Nunjucks macro with the given params, injects it into * the test boilerplate page, and instantiates the component class, passing the @@ -75,12 +75,7 @@ async function axe(page, overrides = {}) { * @param {BrowserRenderOptions<HandlerContext>} [browserOptions] - Puppeteer browser render options * @returns {Promise<import('puppeteer').Page>} Puppeteer page object */ -async function renderAndInitialise( - page, - componentName, - renderOptions, - browserOptions -) { +async function render(page, componentName, renderOptions, browserOptions) { await goTo(page, '/tests/boilerplate') const exportName = componentNameToClassName(componentName) @@ -90,7 +85,7 @@ async function renderAndInitialise( await page.$eval( '#slot', // See boilerplate.njk `<div id="slot">` (slot, html) => (slot.innerHTML = html), - renderComponent(componentName, renderOptions) + components.render(componentName, renderOptions) ) // Call `beforeInitialisation` in a separate `$eval` call @@ -252,7 +247,7 @@ async function isVisible($element) { module.exports = { axe, - renderAndInitialise, + render, goTo, goToComponent, goToExample, diff --git a/shared/lib/components.js b/shared/lib/components.js index 51cb83841e..816092d0b8 100644 --- a/shared/lib/components.js +++ b/shared/lib/components.js @@ -140,7 +140,7 @@ async function getExamples(componentName, packageOptions) { * @param {MacroRenderOptions} [options] - Nunjucks macro render options * @returns {string} HTML rendered by the component */ -function renderComponent(componentName, options) { +function render(componentName, options) { const macroName = componentNameToMacroName(componentName) const macroPath = `govuk/components/${componentName}/macro.njk` @@ -204,7 +204,7 @@ function renderPreview(componentName, options) { <p class="govuk-body">Used during testing to inject rendered components and test specific configurations</p> <div id="slot" class="govuk-!-margin-top-6"> - ${componentName ? renderComponent(componentName, options) : ''} + ${componentName ? render(componentName, options) : ''} </div> </div> `, @@ -262,7 +262,7 @@ module.exports = { getComponentNamesFiltered, getExamples, nunjucksEnv, - renderComponent, + render, renderMacro, renderPreview, renderString, diff --git a/shared/tasks/components.mjs b/shared/tasks/components.mjs index 97a97eea8c..ddeb8a7157 100644 --- a/shared/tasks/components.mjs +++ b/shared/tasks/components.mjs @@ -1,6 +1,6 @@ import { basename, dirname, join } from 'path' -import { nunjucksEnv, renderComponent } from '@govuk-frontend/lib/components' +import { nunjucksEnv, render } from '@govuk-frontend/lib/components' import { getListing, getYaml } from '@govuk-frontend/lib/files' import { files } from './index.mjs' @@ -112,7 +112,7 @@ async function generateFixture(componentDataPath, options) { previewLayoutModifiers: example.previewLayoutModifiers ?? [], // Render Nunjucks example - html: renderComponent(componentName, { + html: render(componentName, { context: example.options, env }).trim()