@jrpool/kilotest 60.1.0 → 60.1.2
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.
- package/.htmlhintrc +29 -0
- package/error.html +1 -5
- package/index.test.ts +8 -2
- package/index.ts +5 -5
- package/lhm.plugin.json +1 -1
- package/linkValidation.test.ts +1 -0
- package/logo.svg +15 -0
- package/openapi-3.0.yaml +1 -1
- package/openapi.yaml +1 -1
- package/package.json +3 -2
- package/server.json +1 -1
- package/smokeTest.ts +1 -1
- package/style.css +199 -0
- package/util.ts +22 -3
- package/web/ai0BalanceForm/index.html +1 -6
- package/web/deleteNotesForm/index.html +3 -8
- package/web/deleteNotesForm/index.ts +1 -1
- package/web/enqueue/index.html +1 -6
- package/web/enqueueForm/index.html +3 -8
- package/web/enqueueForm/index.ts +1 -1
- package/web/expungeReportsForm/index.html +3 -8
- package/web/head.partial +6 -0
- package/web/hideReportForm/index.html +1 -6
- package/web/home/home.test.ts +1 -1
- package/web/home/index.html +53 -41
- package/web/home/index.ts +2 -3
- package/web/listDiagnoses/index.html +1 -6
- package/web/listDiagnoses/index.ts +3 -3
- package/web/listIssues/index.html +1 -6
- package/web/listPages/index.html +1 -6
- package/web/listRules/index.html +1 -6
- package/web/listTopIssues/index.html +1 -6
- package/web/listViolators/index.html +1 -6
- package/web/listViolators/index.ts +1 -1
- package/web/manage/index.html +1 -6
- package/web/manage/index.ts +2 -3
- package/web/metrics/index.html +1 -6
- package/web/privacy/index.html +1 -6
- package/web/privacy/index.ts +2 -3
- package/web/pruneReportsForm/index.html +3 -8
- package/web/reannotate/index.html +1 -6
- package/web/reannotate/index.ts +2 -2
- package/web/reannotateForm/index.html +1 -6
- package/web/renewWCAG/index.html +1 -6
- package/web/renewWCAG/index.ts +2 -2
- package/web/renewWCAGForm/index.html +2 -7
- package/web/renewWCAGForm/index.ts +2 -3
- package/web/reportDeletion.ts +1 -1
- package/web/requestNewTest/index.html +1 -6
- package/web/requestNewTestForm/index.html +1 -6
- package/web/requestNewTestForm/index.ts +2 -3
- package/web/requestRetest/index.html +1 -6
- package/web/requestRetestForm/index.html +1 -6
- package/web/rewindReportsForm/index.html +3 -8
- package/web/showHiddenReportsForm/index.html +2 -7
- package/web/tutorialAI/index.html +1 -6
- package/web/tutorialAI/index.ts +9 -2
- package/web/tutorialWeb/index.html +1 -6
- package/web/tutorialWeb/index.ts +9 -2
- package/web/unhideReportForm/index.html +2 -7
- package/favicon.ico +0 -0
package/.htmlhintrc
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"doctype-first": true,
|
|
3
|
+
"doctype-html5": true,
|
|
4
|
+
"title-require": true,
|
|
5
|
+
"tagname-lowercase": true,
|
|
6
|
+
"tag-pair": true,
|
|
7
|
+
"tags-check": false,
|
|
8
|
+
"attr-lowercase": true,
|
|
9
|
+
"attr-value-double-quotes": true,
|
|
10
|
+
"attr-no-duplication": true,
|
|
11
|
+
"attr-unsafe-chars": true,
|
|
12
|
+
"attr-whitespace": true,
|
|
13
|
+
"id-unique": true,
|
|
14
|
+
"src-not-empty": true,
|
|
15
|
+
"spec-char-escape": true,
|
|
16
|
+
"alt-require": true,
|
|
17
|
+
"space-tab-mixed-disabled": "space",
|
|
18
|
+
"attr-value-not-empty": false,
|
|
19
|
+
"tag-self-close": false,
|
|
20
|
+
"head-script-disabled": false,
|
|
21
|
+
"script-disabled": false,
|
|
22
|
+
"style-disabled": false,
|
|
23
|
+
"inline-style-disabled": false,
|
|
24
|
+
"inline-script-disabled": false,
|
|
25
|
+
"href-abs-or-rel": false,
|
|
26
|
+
"attr-sorted": false,
|
|
27
|
+
"id-class-ad-disabled": false,
|
|
28
|
+
"id-class-value": false
|
|
29
|
+
}
|
package/error.html
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en-US">
|
|
3
3
|
<head>
|
|
4
|
-
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<meta name="publisher" content="Jonathan Robert Pool">
|
|
7
|
-
<meta name="creator" content="Jonathan Robert Pool">
|
|
4
|
+
__head__
|
|
8
5
|
<meta name="keywords" content="report,accessibility,a11y">
|
|
9
6
|
<title>Error | Kilotest</title>
|
|
10
|
-
<link rel="stylesheet" href="/style.css">
|
|
11
7
|
</head>
|
|
12
8
|
<body>
|
|
13
9
|
<main>
|
package/index.test.ts
CHANGED
|
@@ -326,10 +326,16 @@ test('GET /style.css serves the stylesheet as CSS', async () => {
|
|
|
326
326
|
assert.ok(res.headers['content-type'].includes('text/css'));
|
|
327
327
|
});
|
|
328
328
|
|
|
329
|
-
test('GET /favicon.ico serves the
|
|
329
|
+
test('GET /favicon.ico serves the logo as SVG', async () => {
|
|
330
330
|
const res = await request('GET', '/favicon.ico');
|
|
331
331
|
assert.equal(res.statusCode, 200);
|
|
332
|
-
assert.ok(res.headers['content-type'].includes('image/
|
|
332
|
+
assert.ok(res.headers['content-type'].includes('image/svg+xml'));
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test('GET /favicon.svg serves the logo as SVG', async () => {
|
|
336
|
+
const res = await request('GET', '/favicon.svg');
|
|
337
|
+
assert.equal(res.statusCode, 200);
|
|
338
|
+
assert.ok(res.headers['content-type'].includes('image/svg+xml'));
|
|
333
339
|
});
|
|
334
340
|
|
|
335
341
|
test('GET /api/listPages returns JSON with report data and records an API-operation metric', async () => {
|
package/index.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
isValidAuthCode,
|
|
30
30
|
jobsPath,
|
|
31
31
|
metricsExclusionCookieName,
|
|
32
|
+
populateFile,
|
|
32
33
|
recordMetric,
|
|
33
34
|
reportsPath,
|
|
34
35
|
unusableReportsPath
|
|
@@ -367,9 +368,8 @@ export const serveError = async (error: Record<string, unknown>, response: Serve
|
|
|
367
368
|
response.setHeader('content-location', '/error.html');
|
|
368
369
|
response.setHeader('Access-Control-Allow-Origin', '*');
|
|
369
370
|
response.setHeader('Cache-Control', 'public, max-age=300, stale-while-revalidate=3000');
|
|
370
|
-
const errorTemplate = await fs.readFile('error.html', 'utf8');
|
|
371
371
|
const errorMessage = typeof error.message === 'string' ? error.message : 'ERROR';
|
|
372
|
-
const errorPage =
|
|
372
|
+
const errorPage = await populateFile('error.html', {error: errorMessage});
|
|
373
373
|
response.end(errorPage);
|
|
374
374
|
}
|
|
375
375
|
// Otherwise, i.e. if it is from an agent:
|
|
@@ -942,10 +942,10 @@ const handleRequest = async (request: IncomingMessage, response: ServerResponse)
|
|
|
942
942
|
}
|
|
943
943
|
// Otherwise, if it is for the application icon:
|
|
944
944
|
else if (pathname.includes('favicon.')) {
|
|
945
|
-
// Get the site icon.
|
|
946
|
-
const icon = await fs.readFile(path.join(import.meta.dirname, '
|
|
945
|
+
// Get the site icon (logo.svg, the only format served).
|
|
946
|
+
const icon = await fs.readFile(path.join(import.meta.dirname, 'logo.svg'));
|
|
947
947
|
// Serve it.
|
|
948
|
-
setHeaders('image/
|
|
948
|
+
setHeaders('image/svg+xml', null, 'low');
|
|
949
949
|
response.write(icon, 'binary');
|
|
950
950
|
response.end('');
|
|
951
951
|
}
|
package/lhm.plugin.json
CHANGED
package/linkValidation.test.ts
CHANGED
package/logo.svg
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" aria-hidden="true">
|
|
2
|
+
<style>
|
|
3
|
+
.lens { stroke: #086b6b; }
|
|
4
|
+
.zero { fill: #12a05c; }
|
|
5
|
+
@media (prefers-color-scheme: dark) {
|
|
6
|
+
.lens { stroke: #3fb8b8; }
|
|
7
|
+
.zero { fill: #4ce0a0; }
|
|
8
|
+
}
|
|
9
|
+
</style>
|
|
10
|
+
<circle class="lens" cx="14" cy="14" r="8.6" fill="none" stroke-width="2.6"/>
|
|
11
|
+
<path class="lens" d="M20.3 20.3L26.8 26.8" fill="none" stroke-width="2.8" stroke-linecap="round"/>
|
|
12
|
+
<ellipse class="zero" cx="9.9" cy="14" rx="1.5" ry="1.9"/>
|
|
13
|
+
<ellipse class="zero" cx="14" cy="14" rx="1.5" ry="1.9"/>
|
|
14
|
+
<ellipse class="zero" cx="18.1" cy="14" rx="1.5" ry="1.9"/>
|
|
15
|
+
</svg>
|
package/openapi-3.0.yaml
CHANGED
|
@@ -2,7 +2,7 @@ openapi: 3.0.3
|
|
|
2
2
|
info:
|
|
3
3
|
title: Kilotest API
|
|
4
4
|
description: 'Kilotest is an ensemble testing and reporting service for front-end web quality. It uses an ensemble of 12 rule engines to test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG). Kilotest acts as a web server for human users, an MCP server for AI platforms, and an API for programmatic access. With this API you can request that a page be tested or retested, discover available reports, incrementally retrieve facts from a report: from what reports are available, to what issues were reported in one report, to what elements violated one issue, to what diagnoses rule engines gave for one violation.'
|
|
5
|
-
version: 60.1.
|
|
5
|
+
version: 60.1.2
|
|
6
6
|
license:
|
|
7
7
|
name: MIT
|
|
8
8
|
url: https://github.com/jrpool/kilotest/blob/main/LICENSE
|
package/openapi.yaml
CHANGED
|
@@ -2,7 +2,7 @@ openapi: 3.1.0
|
|
|
2
2
|
info:
|
|
3
3
|
title: Kilotest API
|
|
4
4
|
description: 'Kilotest is an ensemble testing and reporting service for front-end web quality. It uses an ensemble of 12 rule engines to test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG). Kilotest acts as a web server for human users, an MCP server for AI platforms, and an API for programmatic access. With this API you can request that a page be tested or retested, discover available reports, incrementally retrieve facts from a report: from what reports are available, to what issues were reported in one report, to what elements violated one issue, to what diagnoses rule engines gave for one violation.'
|
|
5
|
-
version: 60.1.
|
|
5
|
+
version: 60.1.2
|
|
6
6
|
license:
|
|
7
7
|
name: MIT
|
|
8
8
|
identifier: MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jrpool/kilotest",
|
|
3
|
-
"version": "60.1.
|
|
3
|
+
"version": "60.1.2",
|
|
4
4
|
"description": "Ensemble testing for front-end web quality",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "node index.ts",
|
|
12
12
|
"typecheck": "tsc --noEmit",
|
|
13
|
-
"lint": "npx eslint && out=$(npx markdownlint-cli2 2>&1); code=$?; if [ $code -ne 0 ]; then echo \"$out\"; exit $code; fi",
|
|
13
|
+
"lint": "npx eslint && npx htmlhint web error.html && out=$(npx markdownlint-cli2 2>&1); code=$?; if [ $code -ne 0 ]; then echo \"$out\"; exit $code; fi",
|
|
14
14
|
"test": "c8 node --require ./test/setup.ts --experimental-test-module-mocks --test --test-concurrency=1 --test-randomize '**/*.test.ts'",
|
|
15
15
|
"test:smoke": "node smokeTest.ts",
|
|
16
16
|
"checkinfo": "node checkinfo.ts",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"c8": "*",
|
|
56
56
|
"eslint": "*",
|
|
57
57
|
"globals": "*",
|
|
58
|
+
"htmlhint": "^1.9.2",
|
|
58
59
|
"js-yaml": "*",
|
|
59
60
|
"markdownlint-cli2": "^0.23.2",
|
|
60
61
|
"node-html-parser": "*",
|
package/server.json
CHANGED
package/smokeTest.ts
CHANGED
|
@@ -19,7 +19,7 @@ const concretePaths: Record<string, Record<string, string>> = {
|
|
|
19
19
|
'/api-docs': '/api-docs',
|
|
20
20
|
'/api/*': '/api/listPages',
|
|
21
21
|
'/capability.md': '/capability.md',
|
|
22
|
-
'/favicon.*': '/favicon.
|
|
22
|
+
'/favicon.*': '/favicon.svg',
|
|
23
23
|
'/fullReport.json/*': '/fullReport.json/260101T0000/mix',
|
|
24
24
|
'/index.html': '/index.html',
|
|
25
25
|
'/llms-full.txt': '/llms-full.txt',
|
package/style.css
CHANGED
|
@@ -208,3 +208,202 @@ ul.xPathList li {
|
|
|
208
208
|
padding-left: 0;
|
|
209
209
|
overflow-wrap: break-word;
|
|
210
210
|
}
|
|
211
|
+
body.home {
|
|
212
|
+
--ink: #18212b;
|
|
213
|
+
--muted: #4c5a66;
|
|
214
|
+
--accent: #086b6b;
|
|
215
|
+
--accent-dark: #064f4f;
|
|
216
|
+
--bg: #f6f8f8;
|
|
217
|
+
--card: #fff;
|
|
218
|
+
--line: #dde4e4;
|
|
219
|
+
--focus: #005fcc;
|
|
220
|
+
margin: 0;
|
|
221
|
+
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
222
|
+
font-size: 1.0625rem;
|
|
223
|
+
line-height: 1.6;
|
|
224
|
+
color: var(--ink);
|
|
225
|
+
background: var(--bg);
|
|
226
|
+
}
|
|
227
|
+
body.home, body.home *, body.home *::before, body.home *::after {
|
|
228
|
+
box-sizing: border-box;
|
|
229
|
+
}
|
|
230
|
+
body.home a {
|
|
231
|
+
color: var(--accent);
|
|
232
|
+
text-underline-offset: 0.15em;
|
|
233
|
+
}
|
|
234
|
+
body.home a:hover {
|
|
235
|
+
color: var(--accent-dark);
|
|
236
|
+
background-color: transparent;
|
|
237
|
+
}
|
|
238
|
+
body.home a:focus-visible, body.home summary:focus-visible {
|
|
239
|
+
outline: 3px solid var(--focus);
|
|
240
|
+
outline-offset: 2px;
|
|
241
|
+
border-radius: 4px;
|
|
242
|
+
}
|
|
243
|
+
body.home h2 {
|
|
244
|
+
color: var(--ink);
|
|
245
|
+
}
|
|
246
|
+
body.home li, body.home p {
|
|
247
|
+
line-height: 1.6;
|
|
248
|
+
}
|
|
249
|
+
body.home p {
|
|
250
|
+
margin: 0.6rem 0;
|
|
251
|
+
}
|
|
252
|
+
body.home header {
|
|
253
|
+
background: var(--card);
|
|
254
|
+
border-bottom: 1px solid var(--line);
|
|
255
|
+
}
|
|
256
|
+
body.home .header-inner {
|
|
257
|
+
max-width: 64rem;
|
|
258
|
+
margin-inline: auto;
|
|
259
|
+
padding: 1.1rem 1.5rem;
|
|
260
|
+
display: flex;
|
|
261
|
+
align-items: baseline;
|
|
262
|
+
gap: 0.75rem;
|
|
263
|
+
}
|
|
264
|
+
body.home .brand {
|
|
265
|
+
display: inline-flex;
|
|
266
|
+
align-items: center;
|
|
267
|
+
gap: 0.55rem;
|
|
268
|
+
font-weight: 800;
|
|
269
|
+
font-size: 1.25rem;
|
|
270
|
+
letter-spacing: -0.02em;
|
|
271
|
+
}
|
|
272
|
+
body.home .brand svg {
|
|
273
|
+
width: 1.35rem;
|
|
274
|
+
height: 1.35rem;
|
|
275
|
+
}
|
|
276
|
+
body.home .brand-tag {
|
|
277
|
+
font-size: 0.85rem;
|
|
278
|
+
color: var(--muted);
|
|
279
|
+
}
|
|
280
|
+
body.home .hero {
|
|
281
|
+
max-width: 64rem;
|
|
282
|
+
margin-inline: auto;
|
|
283
|
+
padding: 4rem 1.5rem 0;
|
|
284
|
+
}
|
|
285
|
+
body.home .eyebrow {
|
|
286
|
+
margin: 0 0 0.75rem;
|
|
287
|
+
font-size: 0.85rem;
|
|
288
|
+
font-weight: 700;
|
|
289
|
+
letter-spacing: 0.04em;
|
|
290
|
+
color: var(--accent);
|
|
291
|
+
}
|
|
292
|
+
body.home .eyebrow::before {
|
|
293
|
+
content: "";
|
|
294
|
+
display: inline-block;
|
|
295
|
+
width: 1.4rem;
|
|
296
|
+
height: 0.16rem;
|
|
297
|
+
margin: 0 0.5rem 0.22rem 0;
|
|
298
|
+
background: var(--accent);
|
|
299
|
+
}
|
|
300
|
+
body.home .hero h1 {
|
|
301
|
+
margin: 0 0 1rem;
|
|
302
|
+
max-width: 24ch;
|
|
303
|
+
font-size: clamp(2rem, 4.5vw, 3.1rem);
|
|
304
|
+
line-height: 1.12;
|
|
305
|
+
letter-spacing: -0.03em;
|
|
306
|
+
}
|
|
307
|
+
body.home .hero .lede {
|
|
308
|
+
margin: 0 0 1.75rem;
|
|
309
|
+
max-width: 52ch;
|
|
310
|
+
font-size: 1.2rem;
|
|
311
|
+
color: var(--muted);
|
|
312
|
+
}
|
|
313
|
+
body.home details {
|
|
314
|
+
max-width: 52rem;
|
|
315
|
+
margin: 0 0 1rem;
|
|
316
|
+
padding: 0;
|
|
317
|
+
background: var(--card);
|
|
318
|
+
border: 1px solid var(--line);
|
|
319
|
+
border-radius: 0.75rem;
|
|
320
|
+
}
|
|
321
|
+
body.home summary {
|
|
322
|
+
min-height: 44px;
|
|
323
|
+
padding: 0.9rem 1.25rem;
|
|
324
|
+
font-weight: 600;
|
|
325
|
+
cursor: pointer;
|
|
326
|
+
}
|
|
327
|
+
body.home summary:hover {
|
|
328
|
+
background: #eef3f3;
|
|
329
|
+
border-radius: 0.75rem;
|
|
330
|
+
}
|
|
331
|
+
body.home details[open] summary {
|
|
332
|
+
border-bottom: 1px solid var(--line);
|
|
333
|
+
border-radius: 0.75rem 0.75rem 0 0;
|
|
334
|
+
}
|
|
335
|
+
body.home details > :not(summary) {
|
|
336
|
+
margin-inline: 1.5rem;
|
|
337
|
+
}
|
|
338
|
+
body.home details h2 {
|
|
339
|
+
margin: 1.5rem 0 0.5rem;
|
|
340
|
+
font-size: 1.3rem;
|
|
341
|
+
}
|
|
342
|
+
body.home details h3 {
|
|
343
|
+
margin: 1.35rem 0 0.35rem;
|
|
344
|
+
font-size: 1.08rem;
|
|
345
|
+
}
|
|
346
|
+
body.home .nav-section {
|
|
347
|
+
max-width: 64rem;
|
|
348
|
+
margin-inline: auto;
|
|
349
|
+
padding: 2.25rem 1.5rem 0;
|
|
350
|
+
}
|
|
351
|
+
body.home .nav-section:last-child {
|
|
352
|
+
padding-bottom: 5rem;
|
|
353
|
+
}
|
|
354
|
+
body.home .nav-section h2 {
|
|
355
|
+
margin: 0 0 1.25rem;
|
|
356
|
+
font-size: 1.4rem;
|
|
357
|
+
letter-spacing: -0.01em;
|
|
358
|
+
}
|
|
359
|
+
body.home ul.nav {
|
|
360
|
+
margin: 0;
|
|
361
|
+
padding: 0;
|
|
362
|
+
list-style: none;
|
|
363
|
+
display: grid;
|
|
364
|
+
grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
|
|
365
|
+
gap: 1rem;
|
|
366
|
+
}
|
|
367
|
+
body.home ul.nav a {
|
|
368
|
+
display: block;
|
|
369
|
+
min-height: 44px;
|
|
370
|
+
height: 100%;
|
|
371
|
+
padding: 1.2rem 1.35rem;
|
|
372
|
+
background: var(--card);
|
|
373
|
+
border: 1px solid var(--line);
|
|
374
|
+
border-radius: 0.75rem;
|
|
375
|
+
text-decoration: none;
|
|
376
|
+
transition: background-color 0.12s ease-out, border-color 0.12s ease-out;
|
|
377
|
+
}
|
|
378
|
+
body.home ul.nav a:hover {
|
|
379
|
+
background: #eef4f4;
|
|
380
|
+
border-color: var(--accent);
|
|
381
|
+
}
|
|
382
|
+
body.home .card-title {
|
|
383
|
+
font-weight: 700;
|
|
384
|
+
font-size: 1.05rem;
|
|
385
|
+
color: var(--accent);
|
|
386
|
+
text-decoration: underline;
|
|
387
|
+
text-decoration-thickness: 0.09em;
|
|
388
|
+
text-underline-offset: 0.2em;
|
|
389
|
+
}
|
|
390
|
+
body.home .card-title::after {
|
|
391
|
+
content: "\00a0→";
|
|
392
|
+
display: inline-block;
|
|
393
|
+
text-decoration: none;
|
|
394
|
+
font-weight: 700;
|
|
395
|
+
}
|
|
396
|
+
body.home ul.nav a:hover .card-title {
|
|
397
|
+
text-decoration-thickness: 0.18em;
|
|
398
|
+
}
|
|
399
|
+
body.home .card-desc {
|
|
400
|
+
display: block;
|
|
401
|
+
margin: 0.4rem 0 0;
|
|
402
|
+
font-size: 0.9rem;
|
|
403
|
+
color: var(--muted);
|
|
404
|
+
}
|
|
405
|
+
@media (prefers-reduced-motion: reduce) {
|
|
406
|
+
body.home ul.nav a {
|
|
407
|
+
transition: none;
|
|
408
|
+
}
|
|
409
|
+
}
|
package/util.ts
CHANGED
|
@@ -375,10 +375,26 @@ export const getNowStamp = (): string => {
|
|
|
375
375
|
export const getPlainText = (string: string): string => string
|
|
376
376
|
.replace(/&/g, '+')
|
|
377
377
|
.replace(/[<>"']/g, ' ');
|
|
378
|
-
//
|
|
379
|
-
|
|
378
|
+
// Markup shared by populated templates, lazily read once from files.
|
|
379
|
+
let sharedContent: {head: string, logo: string} | undefined;
|
|
380
|
+
// Returns the markup shared by populated templates.
|
|
381
|
+
const getSharedContent = async (): Promise<{head: string, logo: string}> => {
|
|
382
|
+
// Trailing newlines are stripped so injection does not add blank lines.
|
|
383
|
+
sharedContent ??= {
|
|
384
|
+
head: (await fs.readFile(path.join(import.meta.dirname, 'web', 'head.partial'), 'utf8')).trimEnd(),
|
|
385
|
+
logo: (await fs.readFile(path.join(import.meta.dirname, 'logo.svg'), 'utf8')).trimEnd()
|
|
386
|
+
};
|
|
387
|
+
return sharedContent;
|
|
388
|
+
};
|
|
389
|
+
// Populates an HTML template file with named values.
|
|
390
|
+
export const populateFile = async (filePath: string, query: Record<string, string>): Promise<string> => {
|
|
380
391
|
// Get the template.
|
|
381
|
-
let answerPage = await fs.readFile(
|
|
392
|
+
let answerPage = await fs.readFile(filePath, 'utf8');
|
|
393
|
+
// Inject the markup shared by all templates.
|
|
394
|
+
const shared = await getSharedContent();
|
|
395
|
+
answerPage = answerPage
|
|
396
|
+
.replace(/__head__/g, () => shared.head)
|
|
397
|
+
.replace(/__logo__/g, () => shared.logo);
|
|
382
398
|
// Replace its placeholders.
|
|
383
399
|
Object.keys(query).forEach(param => {
|
|
384
400
|
// A replacer function keeps $-patterns in a value from being interpreted.
|
|
@@ -386,6 +402,9 @@ export const populateTemplate = async (dirName: string, query: Record<string, st
|
|
|
386
402
|
});
|
|
387
403
|
return answerPage;
|
|
388
404
|
};
|
|
405
|
+
// Populates an index.html template in a directory with named values.
|
|
406
|
+
export const populateTemplate = (dirName: string, query: Record<string, string>): Promise<string> =>
|
|
407
|
+
populateFile(path.join(dirName, 'index.html'), query);
|
|
389
408
|
// Returns the data from a POST request. Resolves null for an unrecognized content-type or a
|
|
390
409
|
// malformed JSON body; callers treat null as an unreadable request.
|
|
391
410
|
export const getPOSTData = (request: import('node:http').IncomingMessage): Promise<unknown> => new Promise(resolve => {
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en-US">
|
|
3
3
|
<head>
|
|
4
|
-
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<meta name="publisher" content="Jonathan Robert Pool">
|
|
7
|
-
<meta name="creator" content="Jonathan Robert Pool">
|
|
4
|
+
__head__
|
|
8
5
|
<meta name="keywords" content="report,accessibility,a11y">
|
|
9
6
|
<title>Record AI service 0 balance | Kilotest</title>
|
|
10
|
-
<link rel="icon" href="/favicon.ico">
|
|
11
|
-
<link rel="stylesheet" href="/style.css">
|
|
12
7
|
</head>
|
|
13
8
|
<body>
|
|
14
9
|
<main>
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en-US">
|
|
3
3
|
<head>
|
|
4
|
-
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<meta name="publisher" content="Jonathan Robert Pool">
|
|
7
|
-
<meta name="creator" content="Jonathan Robert Pool">
|
|
4
|
+
__head__
|
|
8
5
|
<meta name="keywords" content="report,accessibility,a11y">
|
|
9
6
|
<title>Delete notes | Kilotest</title>
|
|
10
|
-
<link rel="icon" href="/favicon.ico">
|
|
11
|
-
<link rel="stylesheet" href="/style.css">
|
|
12
7
|
</head>
|
|
13
8
|
<body>
|
|
14
9
|
<main>
|
|
@@ -20,9 +15,9 @@
|
|
|
20
15
|
__notes__
|
|
21
16
|
</fieldset>
|
|
22
17
|
<p><label>
|
|
23
|
-
Authorization code: <input size="3"
|
|
18
|
+
Authorization code: <input size="3" minlength="3" maxlength="3" name="authCode" required __disabled__>
|
|
24
19
|
</label></p>
|
|
25
|
-
<p><button type="submit"__disabled__>Submit</button></p>
|
|
20
|
+
<p><button type="submit" __disabled__>Submit</button></p>
|
|
26
21
|
</form>
|
|
27
22
|
</main>
|
|
28
23
|
</body>
|
|
@@ -164,7 +164,7 @@ export const answer = async (
|
|
|
164
164
|
intro: anyNotes ?
|
|
165
165
|
'Choose the comments and feature requests to delete.' :
|
|
166
166
|
'There are no comments or feature requests to delete.',
|
|
167
|
-
disabled: anyNotes ? '' : '
|
|
167
|
+
disabled: anyNotes ? '' : 'disabled'
|
|
168
168
|
};
|
|
169
169
|
// Return the populated page.
|
|
170
170
|
return {
|
package/web/enqueue/index.html
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en-US">
|
|
3
3
|
<head>
|
|
4
|
-
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<meta name="publisher" content="Jonathan Robert Pool">
|
|
7
|
-
<meta name="creator" content="Jonathan Robert Pool">
|
|
4
|
+
__head__
|
|
8
5
|
<meta name="keywords" content="report,accessibility,a11y">
|
|
9
6
|
<title>Test order | Kilotest</title>
|
|
10
|
-
<link rel="icon" href="/favicon.ico">
|
|
11
|
-
<link rel="stylesheet" href="/style.css">
|
|
12
7
|
</head>
|
|
13
8
|
<body>
|
|
14
9
|
<main>
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en-US">
|
|
3
3
|
<head>
|
|
4
|
-
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<meta name="publisher" content="Jonathan Robert Pool">
|
|
7
|
-
<meta name="creator" content="Jonathan Robert Pool">
|
|
4
|
+
__head__
|
|
8
5
|
<meta name="keywords" content="report,accessibility,a11y">
|
|
9
6
|
<title>Request approval or rejection | Kilotest</title>
|
|
10
|
-
<link rel="icon" href="/favicon.ico">
|
|
11
|
-
<link rel="stylesheet" href="/style.css">
|
|
12
7
|
</head>
|
|
13
8
|
<body>
|
|
14
9
|
<main>
|
|
@@ -24,9 +19,9 @@
|
|
|
24
19
|
</fieldset>
|
|
25
20
|
<p><label>
|
|
26
21
|
Authorization code:
|
|
27
|
-
<input size="3"
|
|
22
|
+
<input size="3" minlength="3" maxlength="3" name="authCode" required __disabled__>
|
|
28
23
|
</label></p>
|
|
29
|
-
<p><button type="submit"__disabled__>Submit</button></p>
|
|
24
|
+
<p><button type="submit" __disabled__>Submit</button></p>
|
|
30
25
|
</form>
|
|
31
26
|
</main>
|
|
32
27
|
</body>
|
package/web/enqueueForm/index.ts
CHANGED
|
@@ -31,7 +31,7 @@ export const answer = async () => {
|
|
|
31
31
|
const query: Record<string, string> = {
|
|
32
32
|
requests: lines.join('\n'),
|
|
33
33
|
noRequests: urls.length ? '' : 'No requests exist now.',
|
|
34
|
-
disabled: urls.length ? '' : '
|
|
34
|
+
disabled: urls.length ? '' : 'disabled'
|
|
35
35
|
};
|
|
36
36
|
// Get the populated order form template.
|
|
37
37
|
const answerPage = await populateTemplate(import.meta.dirname, query);
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en-US">
|
|
3
3
|
<head>
|
|
4
|
-
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<meta name="publisher" content="Jonathan Robert Pool">
|
|
7
|
-
<meta name="creator" content="Jonathan Robert Pool">
|
|
4
|
+
__head__
|
|
8
5
|
<meta name="keywords" content="report,accessibility,a11y">
|
|
9
6
|
<title>Expunge reports | Kilotest</title>
|
|
10
|
-
<link rel="icon" href="/favicon.ico">
|
|
11
|
-
<link rel="stylesheet" href="/style.css">
|
|
12
7
|
</head>
|
|
13
8
|
<body>
|
|
14
9
|
<main>
|
|
@@ -20,9 +15,9 @@
|
|
|
20
15
|
__reports__
|
|
21
16
|
</fieldset>
|
|
22
17
|
<p><label>
|
|
23
|
-
Authorization code: <input size="3"
|
|
18
|
+
Authorization code: <input size="3" minlength="3" maxlength="3" name="authCode" required __disabled__>
|
|
24
19
|
</label></p>
|
|
25
|
-
<p><button type="submit"__disabled__>Submit</button></p>
|
|
20
|
+
<p><button type="submit" __disabled__>Submit</button></p>
|
|
26
21
|
</form>
|
|
27
22
|
</main>
|
|
28
23
|
</body>
|
package/web/head.partial
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<meta charset="utf-8">
|
|
2
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
3
|
+
<meta name="publisher" content="Jonathan Robert Pool">
|
|
4
|
+
<meta name="creator" content="Jonathan Robert Pool">
|
|
5
|
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
|
6
|
+
<link rel="stylesheet" href="/style.css">
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en-US">
|
|
3
3
|
<head>
|
|
4
|
-
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<meta name="publisher" content="Jonathan Robert Pool">
|
|
7
|
-
<meta name="creator" content="Jonathan Robert Pool">
|
|
4
|
+
__head__
|
|
8
5
|
<meta name="keywords" content="report,accessibility,a11y">
|
|
9
6
|
<title>Classify report as experimental | Kilotest</title>
|
|
10
|
-
<link rel="icon" href="/favicon.ico">
|
|
11
|
-
<link rel="stylesheet" href="/style.css">
|
|
12
7
|
</head>
|
|
13
8
|
<body>
|
|
14
9
|
<main>
|
package/web/home/home.test.ts
CHANGED
|
@@ -14,6 +14,6 @@ import {answer} from './index.ts';
|
|
|
14
14
|
test('answer returns the home page', async () => {
|
|
15
15
|
const result = await answer();
|
|
16
16
|
assert.equal(result.status, 'ok');
|
|
17
|
-
assert.ok(result.answerPage.includes('
|
|
17
|
+
assert.ok(result.answerPage.includes('More than a thousand tests'));
|
|
18
18
|
assert.ok(result.answerPage.includes('application/ld+json'));
|
|
19
19
|
});
|