@caweb/a11y-webpack-plugin 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +2 -1
  2. package/reporter.js +390 -0
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@caweb/a11y-webpack-plugin",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "CAWebPublishing Webpack Plugin to run Accessibility Scans",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "files": [
8
8
  "aceconfig.js",
9
9
  "index.js",
10
+ "reporter.js",
10
11
  "README.md"
11
12
  ],
12
13
  "scripts": {
package/reporter.js ADDED
@@ -0,0 +1,390 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import path from 'path';
5
+ import fs from 'fs';
6
+ import HandleBars from 'handlebars';
7
+ import htmlFormat from 'html-format';
8
+
9
+ import endsWith from '@caweb/webpack/helpers/logic/endsWith.js';
10
+
11
+
12
+ const templateDir = path.resolve( 'node_modules', '@caweb', 'template' );
13
+
14
+ let templatePartials = {
15
+ 'header': 'semantics/header.html',
16
+ 'footer': 'semantics/footer.html',
17
+ 'utilityHeader': 'semantics/utility-header.html',
18
+ 'branding': 'semantics/branding.html',
19
+ 'mobileControls': 'semantics/mobile-controls.html',
20
+ 'navHeader': 'semantics/nav-header.html',
21
+ 'navFooter': 'semantics/nav-footer.html',
22
+ 'alert': 'components/alert/alert.html',
23
+ 'searchForm': 'forms/search.html'
24
+ }
25
+
26
+ let sortedReport = {
27
+ errors: [],
28
+ warnings: [],
29
+ info: []
30
+ };
31
+
32
+ let title = `IBM Accessibility Equal Access Toolkit: Accessibility Checker Report`;
33
+
34
+
35
+ /**
36
+ * Process data
37
+ *
38
+ * Data Object
39
+ * {
40
+ * functions,
41
+ * options,
42
+ * errors,
43
+ * globals,
44
+ * unused,
45
+ * member,
46
+ * file
47
+ * }
48
+ */
49
+ function addBreakdown({
50
+ functions,
51
+ options,
52
+ errors,
53
+ implieds,
54
+ globals,
55
+ unused,
56
+ member,
57
+ file
58
+ }){
59
+ let functionList = [];
60
+ let errorList = [];
61
+ let unusedList = [];
62
+
63
+ /**
64
+ * Process function data
65
+ *
66
+ * Function Data Object
67
+ * {
68
+ * name,
69
+ * param,
70
+ * line,
71
+ * character,
72
+ * last,
73
+ * lastcharacter,
74
+ * metrics {
75
+ * complexity,
76
+ * parameters,
77
+ * statements
78
+ * }
79
+ * }
80
+ */
81
+ if( functions ){
82
+ functions.forEach(({ name, param, line, character, metrics }) => {
83
+ let { complexity, parameters, statements } = metrics;
84
+
85
+ functionList.push(
86
+ '<li>',
87
+ `<p><b>Name:</b> ${name}</p>`,
88
+ param && param.length ? `<p><b>Parameters:</b> ${param}</p>` : '',
89
+ `<p><b>Line:</b> ${line}</p>`,
90
+ `<p><b>Col:</b> ${character}</p>`,
91
+ `<p><b>Metrics:</b></p>`,
92
+ '<ul>',
93
+ `<li><b>Cyclomatic Complexity Number:</b> ${complexity}</li>`,
94
+ `<li><b>Arguments:</b> ${parameters}</li>`,
95
+ `<li><b>Statements:</b> ${statements}</li>`,
96
+ '</ul>',
97
+ '</li>'
98
+ )
99
+ })
100
+ }
101
+
102
+
103
+ /**
104
+ * Process error data
105
+ *
106
+ * Error Data Object
107
+ * {
108
+ * id,
109
+ * raw,
110
+ * code,
111
+ * evidence,
112
+ * line,
113
+ * character,
114
+ * scope,
115
+ * a,
116
+ * b,
117
+ * c,
118
+ * d,
119
+ * reason
120
+ * }
121
+ */
122
+ if( errors ){
123
+ errors.forEach(({reason, evidence, line, character}) => {
124
+ errorList.push(
125
+ '<li>',
126
+ `<p><b>Reason:</b> ${reason}</p>`,
127
+ `<p><b>Evidence:</b> ${evidence}</p>`,
128
+ `<p><b>Line:</b> ${line}</p>`,
129
+ `<p><b>Col:</b> ${character}</p>`,
130
+ '</li>'
131
+ )
132
+ })
133
+ }
134
+
135
+ /**
136
+ * Unused Data
137
+ * {
138
+ * name,
139
+ * line,
140
+ * character
141
+ * }
142
+ */
143
+ if( unused ){
144
+ unused.forEach(({name, line, character}) => {
145
+ unusedList.push(
146
+ '<li>',
147
+ `<p><b>Name:</b> ${name}</p>`,
148
+ `<p><b>Line:</b> ${line}</p>`,
149
+ `<p><b>Col:</b> ${character}</p>`,
150
+ '</li>'
151
+ )
152
+ })
153
+ }
154
+
155
+ return `<section id="${file.replace(/[\\:\.]/g, '-').toLowerCase()}" class="mb-5 border border-2">
156
+ <div class="bg-light p-4"><h4>File: <a href="file://${file}" target="_blank" class="fst-italic fs-md text-break">${file}</a></h4></div>
157
+ <div class="p-4">
158
+ <h5>Functions: <span class="bg-light rounded-circle p-2">${functions.length}</span></h5>
159
+ ${ functionList.length ? `<ol>${functionList.join('\n')}</ol>` : ''}
160
+ <h5>Errors: <span class="bg-light rounded-circle p-2">${errors ? errors.length : 0}</span></h5>
161
+ ${ errorList.length ? `<ol>${errorList.join('\n')}</ol>` : '' }
162
+ <h5>Unused: <span class="bg-light rounded-circle p-2">${unused ? unused.length : 0}</span></h5>
163
+ ${ unusedList.length ? `<ol>${unusedList.join('\n')}</ol>` : '' }
164
+ </div>
165
+ </section>`;
166
+ }
167
+
168
+ function initHandleBars(){
169
+ // Register partials.
170
+ Object.entries(templatePartials).forEach(([p, f]) => HandleBars.registerPartial(p, fs.readFileSync(path.resolve(templateDir, f )).toString() ) );
171
+
172
+
173
+ // Register custom helpers.
174
+ HandleBars.registerHelper('endsWith', endsWith )
175
+
176
+ return HandleBars.compile(fs.readFileSync(path.resolve(templateDir, 'patterns', 'index.html')).toString() )
177
+
178
+ }
179
+
180
+ /**
181
+ * JSHint Reporter
182
+ *
183
+ * @param {*} results
184
+ * @param {*} data
185
+ * @param {*} opts
186
+ */
187
+ function reporter(data, opts){
188
+ let output = [];
189
+
190
+ let {
191
+ outputFolder,
192
+ outputFilename
193
+ } = opts;
194
+
195
+ let {counts, startScan, URL } = data?.summary;
196
+
197
+ let totalIssues = (counts?.violation || 0) +
198
+ (counts?.potentialviolation || 0) +
199
+ (counts?.recommendation || 0) +
200
+ (counts?.potentialrecommendation || 0) +
201
+ (counts?.manual || 0);
202
+ let totalViolations = counts?.violation || 0;
203
+ let totalReviewsNeeded = counts?.potentialviolation || 0;
204
+ let totalRecommendations = (counts?.recommendation || 0) +
205
+ (counts?.potentialrecommendation || 0) +
206
+ (counts?.manual || 0);
207
+
208
+ // currentStatus is the total number of elements minus
209
+ // the number of elementsViolationReview minus the recommendations
210
+ // all divided by the total number of elements
211
+ let currentStatus = (
212
+ (counts?.elements || 0) -
213
+ ( (counts?.elementsViolationReview || 0) - (counts?.recommendation || 0) )
214
+ ) / (counts?.elements || 0);
215
+
216
+ let violationIcon = '<span class="ca-gov-icon-close-line text-danger align-bottom mx-2"></span>';
217
+ let reviewIcon = '<span class="ca-gov-icon-warning-triangle text-warning align-bottom mx-2"></span>';
218
+ let recommendationIcon = '<span class="ca-gov-icon-info text-primary align-bottom mx-2"></span>';
219
+
220
+ output.push(
221
+ '<div class="container">', // open container
222
+ '<div class="row">', // open row
223
+ '<div class="col-12">', // open column
224
+ `<h1 class="page-title my-4">${title}</h1>`,
225
+ '</div>', // end col-12
226
+ '</div>', // end row
227
+ '<div class="row">', // open row
228
+ '<div class="col-3">', // open column 3
229
+ `<p>${ new Date(startScan).toLocaleString() }</p>`,
230
+ '<strong>Scanned page:</strong>',
231
+ `<p class="text-break">${ URL }</p>`,
232
+ '</div>', // end col-3
233
+ '<div class="col-9">', // open column 9
234
+ '<div class="d-flex p-4" style="background-color: #e8daff; border: 1px solid #8a3ffc">', // open div
235
+ '<div class="w-25">', // open div
236
+ '<p class="fw-bold">Current status</p>',
237
+ `<strong class="fs-1">${Math.ceil(currentStatus * 100)}%</strong>`,
238
+ '<p>Percentage of elements with no detected violations or items to review</p>',
239
+ '</div>', // end div
240
+ '<div class="ps-4 w-75">', // open div
241
+ '<p>This report summarizes automated tests and is generated by <a href="https://www.ibm.com/able/toolkit/tools/#develop" target="_blank">IBM Equal Access Tools</a>. You have to perform additional manual tests to complete accessibility assessments. Use the <a href="https://ibm.com/able/toolkit" target="_blank">IBM Equal Access Toolkit</a> to guide you.</p>',
242
+ '<p class="mb-0">More resources:</p>',
243
+ '<ul class="list-group list-group-flush">',
244
+ '<li class="list-group-item bg-transparent p-0 border-0"><a href="https://www.ibm.com/able/toolkit/develop/overview/#unit-testing" target="_blank">Quick unit test for developers</a></li>',
245
+ '<li class="list-group-item bg-transparent p-0 border-0"><a href="https://www.ibm.com/able/toolkit/verify/overview" target="_blank">Full accessibility test process</a></li>',
246
+ '</ul>',
247
+ '</div>', // end div
248
+ '</div>', // end div
249
+ '<div class="d-flex my-4">', // open div
250
+ '<div class="flex-grow-1 cursor-pointer border border-2 p-2 me-2">', // open div
251
+ `<strong>Violations${violationIcon}</strong>`,
252
+ `<strong class="fs-1 d-block">${totalViolations}</strong>`,
253
+ '<span>Accessibility failures that need to be corrected</span>',
254
+ '</div>', // end div
255
+ '<div class="flex-grow-1 cursor-pointer border border-2 p-2 me-2">', // open div
256
+ `<strong>Needs review${reviewIcon}</strong>`,
257
+ `<strong class="fs-1 d-block">${totalReviewsNeeded}</strong>`,
258
+ '<span>Issues that may not be a violation; manual review is needed</span>',
259
+ '</div>', // end div
260
+ '<div class="flex-grow-1 cursor-pointer border border-2 p-2 me-2">', // open div
261
+ `<strong>Recommendations${recommendationIcon}</strong>`,
262
+ `<strong class="fs-1 d-block">${totalRecommendations}</strong>`,
263
+ '<span>Opportunities to apply best practices to further improve accessibility</span>',
264
+ '</div>', // end div
265
+ '</div>', // end div
266
+ '<div class="d-flex">', // open div
267
+ // '<select>', // open select
268
+ // '<option value="review"><span class="ca-gov-icon-warning-triangle text-warning align-bottom me-2"></span>Needs review</option>', // option
269
+ // '<option value="recommendations"><span class="ca-gov-icon-info text-primary align-bottom me-2"></span>Recommendations</option>', // option
270
+ // '<option value="rules"><span class="ca-gov-icon-close-line text-danger align-bottom me-2"></span>Violations</option>', // option
271
+ // '</select>', // end select
272
+ `<p class="ms-auto me-2">${violationIcon}${totalViolations}</p>`,
273
+ `<p class="mx-2">${reviewIcon}${totalReviewsNeeded}</p>`,
274
+ `<p class="mx-2">${recommendationIcon}${totalRecommendations}</p>`,
275
+ `<p></p>`,
276
+ `<p class="ms-5">${totalIssues} issues found</p>`,
277
+ '</div>', // end div
278
+ '</div>', // end col-9
279
+ '</div>', // end row
280
+ '<div class="row">', // open row
281
+ '<div class="col-12">', // open column
282
+ '<table class="table">', // open table
283
+ '<thead>', // open thead
284
+ '<th>Issues</th>', // th
285
+ '<th>Element roles</th>', // th
286
+ '<th>Requirements</th>', // th
287
+ '<th>Rules</th>', // th
288
+ '</thead>', // end thead
289
+ '<tbody>', // open tbody
290
+ ...data?.results?.map( result => {
291
+ if( 'pass' === result.level ){
292
+ return null; // we skip the passed results
293
+ }
294
+
295
+ let icon = recommendationIcon;
296
+
297
+ if( 'violation' === result.level ){
298
+ icon = violationIcon;
299
+ }else if( 'potentialviolation' === result.level ){
300
+ icon = reviewIcon;
301
+ }
302
+
303
+ return `<tr><td>${icon}</td><td>${result.path.aria}</td><td>${result.category}</td><td>${result.ruleId}</td></tr>`
304
+ }).filter(Boolean),
305
+ '</tbody>', // end tbody
306
+ '</table>', // end table
307
+ '</div>', // end col-12
308
+ '</div>', // end row
309
+ '</div>', // end container
310
+ )
311
+
312
+ HandleBars.registerPartial('index', output.join('\n') );
313
+
314
+ let template = initHandleBars();
315
+
316
+ fs.mkdirSync( outputFolder, {recursive: true} );
317
+
318
+
319
+ // we generate the outputFilename
320
+ fs.writeFileSync(
321
+ path.join(outputFolder, outputFilename),
322
+ htmlFormat(
323
+ template({
324
+ title,
325
+ scheme: 'oceanside',
326
+ assets: [
327
+ fs.existsSync( path.join(outputFolder, 'a11y.update.js' ) ) ?
328
+ 'a11y.update.js' : '' // if the hot module update file exists, add it
329
+ ].filter(Boolean)
330
+ }),
331
+ " ".repeat(4), 250
332
+ )
333
+ );
334
+ }
335
+
336
+ function landingPage(data, opts ){
337
+ let output = [];
338
+
339
+ let {
340
+ outputFolder,
341
+ outputFilename
342
+ } = opts;
343
+
344
+
345
+ output.push(
346
+ `<div class="container"><div class="row"><div class="col-12"><h1 class="page-title my-4">${title} for ${process.cwd().split('\\').pop()}</h1></div></div></div>`,
347
+ '<div class="container"><div class="row"><div class="col-12">',
348
+ '<table class="table"><thead><tr><th>Page Auditted</th><th>Audit</th></thead><tbody>',
349
+ ...data.sort().map(file => {
350
+ // remove the .json extension from the file name
351
+ file = file.replace(/\.json$/, '');
352
+
353
+ return `<tr><td><a href="/${file}" target="_blank">/${file}</a></td><td><a href="${file}" target="_blank">${file}</a></td></tr>`
354
+ }),
355
+ '</tbody></table>',
356
+ '</div></div></div>'
357
+ )
358
+
359
+ HandleBars.registerPartial('index', output.join('\n') );
360
+
361
+ let template = initHandleBars();
362
+
363
+ fs.mkdirSync( outputFolder, {recursive: true} );
364
+
365
+ // we generate the outputFilename
366
+ fs.writeFileSync(
367
+ path.join(outputFolder, `${outputFilename}.html`),
368
+ htmlFormat(
369
+ template({
370
+ title: `${title} for ${process.cwd().split('\\').pop()}`,
371
+ scheme: 'oceanside',
372
+ assets: [
373
+ fs.existsSync( path.join(outputFolder, 'a11y.update.js' ) ) ?
374
+ 'a11y.update.js' : '' // if the hot module update file exists, add it
375
+ ].filter(Boolean)
376
+ }),
377
+ " ".repeat(4), 250
378
+ )
379
+ );
380
+
381
+ }
382
+
383
+ function capitalCase(str){
384
+ return str.charAt(0).toUpperCase() + str.slice(1)
385
+ }
386
+
387
+ export {
388
+ reporter,
389
+ landingPage
390
+ }