Skip to content

Commit

Permalink
Merge pull request #3500 from alphagov/render-helpers-cheerio
Browse files Browse the repository at this point in the history
Return `render()` and `renderTemplate()` helpers via cheerio
  • Loading branch information
colinrotherham authored Apr 18, 2023
2 parents ed335d6 + b0680ac commit ed3f083
Show file tree
Hide file tree
Showing 39 changed files with 785 additions and 794 deletions.
9 changes: 5 additions & 4 deletions app/src/app.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import express from 'express'
import { paths } from 'govuk-frontend-config'
import { getDirectories, getComponentsData, getFullPageExamples } from 'govuk-frontend-lib/files'
import { componentNameToMacroName } from 'govuk-frontend-lib/names'
import { outdent } from 'outdent'

import * as middleware from './common/middleware/index.mjs'
import * as nunjucks from './common/nunjucks/index.mjs'
Expand Down Expand Up @@ -115,10 +116,10 @@ export default async () => {
const macroName = componentNameToMacroName(componentName)
const macroParameters = JSON.stringify(exampleConfig.data, null, '\t')

res.locals.componentView = env.renderString(
`{% from '${componentName}/macro.njk' import ${macroName} %}
{{ ${macroName}(${macroParameters}) }}`
)
res.locals.componentView = env.renderString(outdent`
{% from '${componentName}/macro.njk' import ${macroName} %}
{{ ${macroName}(${macroParameters}) }}
`, {})

let bodyClasses = ''

Expand Down
9 changes: 8 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@
"@types/gulp": "^4.0.10",
"@types/jest": "^29.5.0",
"@types/jest-axe": "^3.5.5",
"@types/node": "^18.15.11"
"@types/node": "^18.15.11",
"@types/nunjucks": "^3.2.2"
},
"overrides": {
"chokidar@^2": {
Expand Down
27 changes: 21 additions & 6 deletions shared/helpers/nunjucks.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const { join } = require('path')

const cheerio = require('cheerio')
const { paths } = require('govuk-frontend-config')
const { componentNameToMacroName } = require('govuk-frontend-lib/names')
const nunjucks = require('nunjucks')
Expand All @@ -16,21 +17,34 @@ const nunjucksEnv = nunjucks.configure(nunjucksPaths, {
})

/**
* Render the raw HTML for a component
* Render component HTML
*
* @param {string} componentName - Component name
* @param {object} options - options to pass to the component macro
* @param {string} [callBlock] - if provided, the macro is called using the
* Nunjucks call tag, with the callBlock passed as the contents of the block
* @returns {string} HTML rendered by the macro
*/
function render (componentName, options, callBlock) {
function renderHTML (componentName, options, callBlock) {
const macroName = componentNameToMacroName(componentName)
const macroPath = `${componentName}/macro.njk`

return callMacro(macroName, macroPath, [options], callBlock)
}

/**
* Render component HTML into cheerio
*
* @param {string} componentName - Component name
* @param {object} options - options to pass to the component macro
* @param {string} [callBlock] - if provided, the macro is called using the
* Nunjucks call tag, with the callBlock passed as the contents of the block
* @returns {import('cheerio').CheerioAPI} HTML rendered by the macro
*/
function render (componentName, options, callBlock) {
return cheerio.load(renderHTML(componentName, options, callBlock))
}

/**
* Returns the string result from calling a macro
*
Expand All @@ -53,15 +67,15 @@ function callMacro (macroName, macroPath, params = [], callBlock) {
macroString += `{{- ${macroName}(${macroParams}) -}}`
}

return nunjucksEnv.renderString(macroString)
return nunjucksEnv.renderString(macroString, {})
}

/**
* Render Nunjucks template
* Render Nunjucks template HTML into cheerio
*
* @param {object} [context] - Nunjucks context
* @param {Object<string, string>} [blocks] - Nunjucks blocks
* @returns {string} Nunjucks template output
* @returns {import('cheerio').CheerioAPI} Nunjucks template output
*/
function renderTemplate (context = {}, blocks = {}) {
let viewString = '{% extends "template.njk" %}'
Expand All @@ -74,12 +88,13 @@ function renderTemplate (context = {}, blocks = {}) {
{%- endblock %}`
}

return nunjucksEnv.renderString(viewString, context)
return cheerio.load(nunjucksEnv.renderString(viewString, context))
}

module.exports = {
nunjucksEnv,
callMacro,
render,
renderHTML,
renderTemplate
}
4 changes: 2 additions & 2 deletions shared/helpers/puppeteer.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { ports } = require('govuk-frontend-config')
const { componentNameToClassName } = require('govuk-frontend-lib/names')

const { render } = require('./nunjucks')
const { renderHTML } = require('./nunjucks')

/**
* Render and initialise a component within test boilerplate HTML
Expand All @@ -28,7 +28,7 @@ const { render } = require('./nunjucks')
async function renderAndInitialise (page, componentName, options) {
await goTo(page, '/tests/boilerplate')

const html = render(componentName, options.params)
const html = renderHTML(componentName, options.params)

// Inject rendered HTML into the page
await page.$eval('#slot', (slot, htmlForSlot) => {
Expand Down
29 changes: 14 additions & 15 deletions src/govuk/components/accordion/template.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const cheerio = require('cheerio')
const { render } = require('govuk-frontend-helpers/nunjucks')
const { axe } = require('govuk-frontend-helpers/tests')
const { getExamples } = require('govuk-frontend-lib/files')
Expand All @@ -12,37 +11,37 @@ describe('Accordion', () => {

describe('default example', () => {
it('passes accessibility tests', async () => {
const $ = cheerio.load(render('accordion', examples.default))
const $ = render('accordion', examples.default)

const results = await axe($.html())
expect(results).toHaveNoViolations()
})

it('renders with heading button text', () => {
const $ = cheerio.load(render('accordion', examples.default))
const $ = render('accordion', examples.default)
const $componentHeadingButton = $('.govuk-accordion__section-button')

expect($componentHeadingButton.html().trim()).toEqual('Section A')
})

it('renders with content as text, wrapped in styled paragraph', () => {
const $ = cheerio.load(render('accordion', examples.default))
const $ = render('accordion', examples.default)
const $componentContent = $('.govuk-accordion__section-content').first()

expect($componentContent.find('p').hasClass('govuk-body')).toBeTruthy()
expect($componentContent.text().trim()).toEqual('We need to know your nationality so we can work out which elections you’re entitled to vote in. If you cannot provide your nationality, you’ll have to send copies of identity documents through the post.')
})

it('renders with content as html', () => {
const $ = cheerio.load(render('accordion', examples.default))
const $ = render('accordion', examples.default)
const $componentContent = $('.govuk-accordion__section-content').last()

expect($componentContent.find('p.gvouk-body').length).toEqual(0)
expect($componentContent.text().trim()).toEqual('Example item 2')
})

it('renders with id', () => {
const $ = cheerio.load(render('accordion', examples.default))
const $ = render('accordion', examples.default)

const $component = $('.govuk-accordion')
expect($component.attr('id')).toEqual('default-example')
Expand All @@ -51,56 +50,56 @@ describe('Accordion', () => {

describe('custom options', () => {
it('renders with classes', () => {
const $ = cheerio.load(render('accordion', examples.classes))
const $ = render('accordion', examples.classes)

const $component = $('.govuk-accordion')
expect($component.hasClass('myClass')).toBeTruthy()
})

it('renders with attributes', () => {
const $ = cheerio.load(render('accordion', examples.attributes))
const $ = render('accordion', examples.attributes)
const $component = $('.govuk-accordion')
expect($component.attr('data-attribute')).toEqual('value')
})

it('renders with specified heading level', () => {
const $ = cheerio.load(render('accordion', examples['custom heading level']))
const $ = render('accordion', examples['custom heading level'])
const $componentHeading = $('.govuk-accordion__section-heading')

expect($componentHeading.get(0).tagName).toEqual('h3')
})

it('renders with heading button html', () => {
const $ = cheerio.load(render('accordion', examples['heading html']))
const $ = render('accordion', examples['heading html'])
const $componentHeadingButton = $('.govuk-accordion__section-button')

expect($componentHeadingButton.html().trim()).toEqual('<span class="myClass">Section A</span>')
})

it('renders with section expanded class', () => {
const $ = cheerio.load(render('accordion', examples['with one section open']))
const $ = render('accordion', examples['with one section open'])
const $componentSection = $('.govuk-accordion__section').first()

expect($componentSection.hasClass('govuk-accordion__section--expanded')).toBeTruthy()
})

it('renders with summary', () => {
const $ = cheerio.load(render('accordion', examples['with additional descriptions']))
const $ = render('accordion', examples['with additional descriptions'])
const $componentSummary = $('.govuk-accordion__section-summary').first()

expect($componentSummary.text().trim()).toEqual('Additional description')
})

it('renders list without falsely values', () => {
const $ = cheerio.load(render('accordion', examples['with falsey values']))
const $ = render('accordion', examples['with falsey values'])
const $component = $('.govuk-accordion')
const $items = $component.find('.govuk-accordion__section')

expect($items.length).toEqual(2)
})

it('renders with localisation data attributes', () => {
const $ = cheerio.load(render('accordion', examples['with translations']))
const $ = render('accordion', examples['with translations'])
const $component = $('.govuk-accordion')

expect($component.attr('data-i18n.hide-all-sections')).toEqual('Collapse all sections')
Expand All @@ -112,7 +111,7 @@ describe('Accordion', () => {
})

it('renders with remember expanded data attribute', () => {
const $ = cheerio.load(render('accordion', examples['with remember expanded off']))
const $ = render('accordion', examples['with remember expanded off'])
const $component = $('.govuk-accordion')

expect($component.attr('data-remember-expanded')).toEqual('false')
Expand Down
17 changes: 8 additions & 9 deletions src/govuk/components/back-link/template.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const cheerio = require('cheerio')
const { render } = require('govuk-frontend-helpers/nunjucks')
const { axe } = require('govuk-frontend-helpers/tests')
const { getExamples } = require('govuk-frontend-lib/files')
Expand All @@ -11,14 +10,14 @@ describe('back-link component', () => {
})

it('default example passes accessibility tests', async () => {
const $ = cheerio.load(render('back-link', examples.default))
const $ = render('back-link', examples.default)

const results = await axe($.html())
expect(results).toHaveNoViolations()
})

it('renders the default example with an anchor, href and text correctly', () => {
const $ = cheerio.load(render('back-link', examples.default))
const $ = render('back-link', examples.default)

const $component = $('.govuk-back-link')
expect($component.get(0).tagName).toEqual('a')
Expand All @@ -27,42 +26,42 @@ describe('back-link component', () => {
})

it('renders classes correctly', () => {
const $ = cheerio.load(render('back-link', examples.classes))
const $ = render('back-link', examples.classes)

const $component = $('.govuk-back-link')
expect($component.hasClass('app-back-link--custom-class')).toBeTruthy()
})

it('renders custom text correctly', () => {
const $ = cheerio.load(render('back-link', examples['with custom text']))
const $ = render('back-link', examples['with custom text'])

const $component = $('.govuk-back-link')
expect($component.html()).toEqual('Back to home')
})

it('renders escaped html when passed to text', () => {
const $ = cheerio.load(render('back-link', examples['html as text']))
const $ = render('back-link', examples['html as text'])

const $component = $('.govuk-back-link')
expect($component.html()).toEqual('&lt;b&gt;Home&lt;/b&gt;')
})

it('renders html correctly', () => {
const $ = cheerio.load(render('back-link', examples.html))
const $ = render('back-link', examples.html)

const $component = $('.govuk-back-link')
expect($component.html()).toEqual('<b>Back</b>')
})

it('renders default text correctly', () => {
const $ = cheerio.load(render('back-link', examples.default))
const $ = render('back-link', examples.default)

const $component = $('.govuk-back-link')
expect($component.html()).toEqual('Back')
})

it('renders attributes correctly', () => {
const $ = cheerio.load(render('back-link', examples.attributes))
const $ = render('back-link', examples.attributes)

const $component = $('.govuk-back-link')
expect($component.attr('data-test')).toEqual('attribute')
Expand Down
Loading

0 comments on commit ed3f083

Please sign in to comment.