-
Notifications
You must be signed in to change notification settings - Fork 328
/
components.template.test.js
133 lines (111 loc) · 4.84 KB
/
components.template.test.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
const { join } = require('path')
const { paths } = require('govuk-frontend-config')
const { nunjucksEnv, renderHTML } = require('govuk-frontend-helpers/nunjucks')
const { getDirectories, getComponentsData } = require('govuk-frontend-lib/files')
const { HtmlValidate } = require('html-validate')
// We can't use the render function from jest-helpers, because we need control
// over the nunjucks environment.
const nunjucks = require('nunjucks')
describe('Components', () => {
let nunjucksEnvCustom
let nunjucksEnvDefault
let componentNames
beforeAll(async () => {
// Create a new Nunjucks environment that uses the src directory as its
// base path, rather than the components folder itself
nunjucksEnvCustom = nunjucks.configure(join(paths.src, 'govuk'))
nunjucksEnvDefault = nunjucksEnv
// Components list
componentNames = await getDirectories(join(paths.src, 'govuk/components'))
})
describe('Nunjucks environment', () => {
it('renders template for each component', () => {
return Promise.all(componentNames.map((componentName) =>
expect(nunjucksEnvDefault.render(`${componentName}/template.njk`, {})).resolves
))
})
it('renders template for each component (different base path)', () => {
return Promise.all(componentNames.map((componentName) =>
expect(nunjucksEnvCustom.render(`components/${componentName}/template.njk`, {})).resolves
))
})
})
describe('Nunjucks HTML validation', () => {
let validator
beforeAll(() => {
validator = new HtmlValidate({
extends: ['html-validate:recommended'],
rules: {
// We don't use boolean attributes consistently – buttons currently
// use disabled="disabled"
'attribute-boolean-style': 'off',
// Allow for multiple buttons in the same form to have the same name
// (as in the cookie banner examples)
'form-dup-name': ['error', { shared: ['radio', 'checkbox', 'submit'] }],
// Allow pattern attribute on input type="number"
'input-attributes': 'off',
// Allow for conditional comments (used in header for fallback png)
'no-conditional-comment': 'off',
// Allow inline styles for testing purposes
'no-inline-style': 'off',
// Allow for explicit roles on regions that have implict roles
// We do this to better support AT with older versions of IE that
// have partial support for HTML5 semantic elements
'no-redundant-role': 'off',
// More hassle than it's worth 👾
'no-trailing-whitespace': 'off',
// We still support creating `input type=button` with the button
// component, but you have to explicitly choose to use them over
// buttons
'prefer-button': 'off',
// Allow use of roles where there are native elements that would give
// us that role automatically, e.g. <section> instead of
// <div role="region">
//
// This is mainly needed for links styled as buttons, but we do this
// in the cookie banner and notification banner too
'prefer-native-element': 'off',
// HTML Validate is opinionated about IDs beginning with a letter and
// only containing letters, numbers, underscores and dashes – which is
// more restrictive than the spec allows.
//
// Relax the rule to allow anything that is valid according to the
// spec.
'valid-id': ['error', { relaxed: true }]
},
elements: [
'html5',
{
// Allow textarea autocomplete attribute to be street-address
// (html-validate only allows on/off in default rules)
textarea: {
attributes: {
autocomplete: { enum: ['on', 'off', 'street-address'] }
}
},
// Allow buttons to omit the type attribute (defaults to 'submit')
button: {
attributes: {
type: { required: false }
}
}
}
]
})
})
it('renders valid HTML for each component example', async () => {
const componentsData = await getComponentsData()
// Validate component examples
for (const { name: componentName, examples } of componentsData) {
const exampleTasks = examples.map(async ({ name: exampleName, data }) => {
const html = renderHTML(componentName, data)
// Validate HTML
return expect({ componentName, exampleName, report: validator.validateString(html) })
.toEqual({ componentName, exampleName, report: expect.objectContaining({ valid: true }) })
})
// Validate all component examples in parallel
await Promise.all(exampleTasks)
}
}, 30000)
})
})