Command line tool to compare a custom implementation of govuk-frontend templates with the reference Nunjucks.
The tools works by rendering the govuk-frontend Nunjucks templates using the example data provided by govuk-frontend for each component. It then passes this same data to an http server (which you need to provide) and expects to receive html responses. It then compares the two using https://github.com/markedjs/html-differ.
NOTE: This repository will soon be all but obselete, since govuk-frontend includes fixtures directly in the package from 3.9.0 upwards. There are currently some issues with these (See alphagov/govuk-frontend#1962) but once these are resolved, I will be moving my repositories to using these and deprecating this tool. I will of course leave it here in case anyone's using it, but it will no longer receive ongoing support
Binaries for OSX, Linux and Windows are provided. The latest binaries can be found here.
Using the binaries does not require NodeJS to be installed.
If you are have NodeJS available it is more optimal to run the script directly with NodeJS as shown below, since the binaries are quite large (Due to packaging up NodeJS and the required node_modules in the binary).
If you wish, you can install govuk-frontend-diff globally: npm install -g govuk-frontend-diff
or alternatively you can install it locally to your package and create a script entry in your package.json to run govuk-frontend-diff
(Since npm automatically resolves paths to node_modules/bin
scripts when they're run from package.json script entries).
As with any cli tool published to the npm registry, you can simply run npx govuk-frontend-diff
.
Please note this tool will only work with versions of govuk-frontend later than v5.0.0
(December 2023).
Usage: govuk-frontend-diff http://localhost:3000 --govuk-frontend-version=v5.7.1
Usage: govuk-frontend-diff http://localhost:3000 --govuk-frontend-version=v5.7.1
--exclude=page-template
Commands:
govuk-frontend-diff <url> URL to a server which will render your templates
for each component/template/params combination.
The html output from this will then be compared
against the reference Nunjucks templates.
This server must:
- Have /component/<component-name> and /template
routes which accept data POSTed as JSON
- Respond with rendered html
A reference server and more detailed instructions
can be found in the readme at
https://github.com/matthew-shaw/govuk-frontend-diff
Options:
--help Show help [boolean]
--version Show version number [boolean]
--govuk-frontend-version Version of govuk-frontend to test against.
This will normally be references to tags like v5.7.1
but this will accept any commit-ish such as branches
or even commit identifiers.
If not specified, the most recent govuk-frontend tag
will be used.
--force-refresh Force a re-download of govuk-frontend, bypassing the
cache. Useful if the version you are specifying
represents a branch. [boolean]
--include Specify a subset of the tests to run. Should be one
or more names of components, space separated
For example --include accordion button [array]
--exclude Specify a subset of the tests to exclude. Should be
one or more names of components, space separated
[array]
--verbose, --ci Verbose logging instead of a simple progress bar.
More suitable for CI situations where the progress
bar is not useful. [boolean]
--hide-diffs Hide the html diffs from output [boolean]
--ignore-attributes Attributes to exclude from html diffing [array]
--skip-hidden Skip examples from govuk-frontend that are marked as
"hidden" [boolean]
Below is a Python/Flask reference server (Lifted directly from https://github.com/LandRegistry/govuk-frontend-jinja/tests/utils/app.py).
You will need to create an equivalent of this in your desired Language / Framework.
The key points are:
- A
/template
route - A
/components/<component-name>
route (where<component-name>
will be the hyphenated name such ascharacter-count
) - Both routes need to respond to POST requests and accept JSON payloads
- For the component route, the JSON payload contains
macro_name
andparams
keys.
macro_name
is the camelcased version of the component name, such asCharacterCount
.
params
is the data which needs to be passed to the component macro / function / template or whatever construct the target language uses - The component route should respond with the bare html for the component, with no wrapping
<html>
,<body>
elements etc - For the template route, the JSON payload contains the complete data which should be passed to the page template.
import os
from jinja2 import FileSystemLoader, PrefixLoader
from flask import Flask, render_template_string, request
app = Flask(__name__)
app.jinja_loader = PrefixLoader({
'govuk_frontend_jinja': FileSystemLoader(searchpath=os.path.join(os.path.dirname(__file__),
'../../govuk_frontend_jinja/templates'))
})
# Template route
@app.route('/template', methods=['POST'])
def template():
data = request.json
# Construct a page template which can override any of the blocks if they are specified
# This doesn't need to be inline - it could be it's own file
template = '''
{% extends "govuk_frontend_jinja/template.html" %}
{% block pageTitle %}{% if pageTitle %}{{ pageTitle }}{% else %}{{ super() }}{% endif %}{% endblock %}
{% block headIcons %}{% if headIcons %}{{ headIcons }}{% else %}{{ super() }}{% endif %}{% endblock %}
{% block head %}{% if head %}{{ head }}{% else %}{{ super() }}{% endif %}{% endblock %}
{% block bodyStart %}{% if bodyStart %}{{ bodyStart }}{% else %}{{ super() }}{% endif %}{% endblock %}
{% block skipLink %}{% if skipLink %}{{ skipLink }}{% else %}{{ super() }}{% endif %}{% endblock %}
{% block header %}{% if header %}{{ header }}{% else %}{{ super() }}{% endif %}{% endblock %}
{% block main %}{% if main %}{{ main }}{% else %}{{ super() }}{% endif %}{% endblock %}
{% block beforeContent %}{% if beforeContent %}{{ beforeContent }}{% else %}{{ super() }}{% endif %}{% endblock %} # noqa: E501
{% block content %}{% if content %}{{ content }}{% else %}{{ super() }}{% endif %}{% endblock %}
{% block footer %}{% if footer %}{{ footer }}{% else %}{{ super() }}{% endif %}{% endblock %}
{% block bodyEnd %}{% if bodyEnd %}{{ bodyEnd }}{% else %}{{ super() }}{% endif %}{% endblock %}
'''
# Render the full html template
return render_template_string(template, **data)
# Component route
@app.route('/component/<component>', methods=['POST'])
def component(component):
data = request.json
# Render the component using the data provided
# component is the hyphenated component name e.g. character-count
# data['macro_name'] is the camelcased name e.g. CharacterCount
# data['params] are the params that will be passed to the macro
# Returns an html response that is just the template in question - no wrapping <html>, <body> elements etc
return render_template_string('''
{{% from "govuk_frontend_jinja/components/" ~ component ~ "/macro.html" import govuk{macro_name} %}}
{{{{ govuk{macro_name}(params) }}}}
'''.format(macro_name=data['macro_name']),
component=component,
params=data['params']
)