Liquid Template Unit Testing Framework
The Liquid Template Unit Testing Framework was created to replace Cypress for unit testing the logic in liquid
templates because Cypress is slow, heavy, and is overkill for this purpose. More importantly, this framework provides us with total control over the test data which is critical for testing liquid
templates, something that Cypress does not provide. For example, a you can test a given liquid
template against any number of fixtures that represent different entries into the Drupal CMS, so bugs in liquid
templates
End-to-end (e2e) tests on VA.gov use Cypress; Cypress has NOT been replaced by this tool.
How to Use the Framework
To test a liquid
template, use the parseFixture()
and renderHTML()
functions in src/site/tests/support/index.js
to create an HTML
document. Run accessibility checks using the axeCheck()
function in src/site/tests/support/axe.js
.
Be sure to import the functions into your spec file like so:
import { parseFixture, renderHTML } from '~/site/tests/support';
import axeCheck from '~/site/tests/support/axe';
Folder and File Naming Conventions
To create a new test for a template in the layouts
directory:
Create a new folder in the
tests
directory underlayouts
and give the folder the same name as the template under test (but don't include the.drupal.liquid
extension).Create a new spec file using the following naming convention: 'template name' (without the
.drupal.liquid
extension) +.unit.spec.js
.Create a
fixtures
folder in the same directory where theJSON
fixtures can live.
Here's an illustration of this folder structure:
/layouts
/tests
/example_page
/fixtures
example_fixture_1.json
example_fixture_2.json
example_page.unit.spec.js
example_page.drupal.liquid
Use the same pattern for templates under test in other directories like includes
, navigation
, etc.
parseFixture(filePath)
parseFixture()
takes a JSON
fixtures path starting from src/
and returns a JavaScript
object.
renderHTML(layoutPath, data, dataName)
renderHTML()
takes a liquid
template path starting from src/
, the JavaScript
object returned by parseFixture()
, and an optional dataName
and renders an HTML
document. We can then run the usual Mocha assertions on the result. This function uses the same code as our build process, so all of our custom liquid
filters can be used.
This technique can be used to generate tests of varying complexity, ranging from simple rendering sanity checks to complex logic. Since we control the JSON
test data, we can easily test different scenarios.
axeCheck(container)
axeCheck()
takes the HTML
document returned by renderHTML()
and returns an array of accessibility violations.
Disabled Axe Checks
The following Axe Checks are disabled:
bypass
The 'bypass' check has been disabled because it may give a false positive for lists of 4-5 links. VA.gov includes a global skip to content link that is evaluated in full page e2e tests.
color-contrast
The CSS
file for the static pages found on VA.gov is generated during the build process and, therefore, cannot be referenced by the HTML
files generated by this tool. The color-contrast
check has been disabled because it requires the CSS
file.
document-title
The document-title
check has been disabled because liquid
template include
files (not entire page templates) can also be tested by this tool and those templates are missing the title
tag (along with the html
, head
, and body
sections, etc., etc., etc.).
Here's the problem:
If you want to test the breadcrumbs
include
file (src/site/includes/breadcrumbs.drupal.liquid
) the HTML
used to instantiate a new JSDOM
object looks like this:
<nav data-template="includes/breadcrumbs" aria-label="Breadcrumb" aria-live="polite" class="va-nav-breadcrumbs"
<!-- lots of liquid code here -->
</nav>
The resulting html
document looks like this:
<html>
<head></head>
<body>
<nav data-template="includes/breadcrumbs" aria-label="Breadcrumb" aria-live="polite" class="va-nav-breadcrumbs"
<!-- lots of liquid code here -->
</nav>
</body>
</html>
Just like a browser, JSDOM
adds whatever HTML
is missing to make the document valid.
Notice there's no title
tag in the head
of the document. Because of this, we can't perform Axe Checks to make sure a title
is present, so we need to disable the document-title
check.
In addition to the reason outlined above, we experienced false-positives on pages that had a valid title
tag when running the document-title
check.
If you want to test the presence of a title
tag, and that the title
isn't an empty string, you'll have write unit tests for those 'the old fashion way'.
How to Run an Axe Check
Run an accessibility check like this:
it('reports no axe violations', async () => {
const violations = await axeCheck(container);
expect(violations.length).to.equal(0);
});
Accessibility violations are logged to the console and look like this:
axe-core
Smoke Tests
A smoke test was created to ensure axe-core
is picking up the expected violiations. This tests acts as a circuit breaker for potential silent failures.
The liquid template for the smoke test is located at src/site/layouts/liquid_template_axe_check_smoke_test.drupal.liquid
. axe-core
currently reports 6 violations on this page.
The spec file and fixture is in the src/site/layouts/tests/liquid_template_axe_check_smoke_test
directory.
DOM Testing Library
To perform queries using the DOM Testing Library, simply import the queries or functions you want to use. Example:
import { getByText } from '@testing-library/dom';
From the Dom Testing Library docs: "All of the queries exported by DOM Testing Library accept a container as the first argument."
In the Liquid Template Testing Framework examples found below, the HTML
document generated is assigned to a variable called container
in the spec.js
files. Use this variable as the first argument to the imported DOM Testing Library functions, like this:
const node = getByText(container, '3500 Ludington Street');
Rendered HTML
Is Saved to Disk
For convenience, the HTML
that's generated from each liquid
template is automatically saved to src/site/tests/html
when tests are executed so the HTML
can be inspected when writing tests. These files are gitignored.
The name of the HTML
file is created from liquid
template path.
Example:
Given the path src/site/components/phone-number.drupal.liquid
, an HTML
file called phone-number.html
will be created.
However, if you're calling renderHTML()
many times using different JavaScript
objects returned by parseFixture()
, you might want to save an HTML
file for each JavaScript
object. In that case, pass in a third, optional argument to renderHTML()
called dataName
to add dataName
to the HTML
filename.
Example:
Given the path src/site/components/phone-number.drupal.liquid
, and labelAndLocationName
for dataName
an HTML
file called phone-number.labelAndLocationName.html
will be created.
Sample Test
Here is a sample test:
Sample Spec Files
Liquid unit tests have have largely been removed. One example is available here --
Help and feedback
Get help from the Platform Support Team in Slack.
Submit a feature idea to the Platform.