@jrpool/kilotest 24.0.4
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/.claude/settings.local.json +9 -0
- package/DEVELOPMENT.md +5860 -0
- package/LICENSE +21 -0
- package/README.md +44 -0
- package/SERVICE.md +268 -0
- package/aceconfig.js +28 -0
- package/ai0BalanceForm/index.html +30 -0
- package/ai0BalanceForm/index.js +79 -0
- package/alerts.js +73 -0
- package/diagnoses/index.html +46 -0
- package/diagnoses/index.js +140 -0
- package/env.example +21 -0
- package/env.testaro +17 -0
- package/error.html +18 -0
- package/eslint.config.mjs +53 -0
- package/favicon.ico +0 -0
- package/index.html +39 -0
- package/index.js +639 -0
- package/issues/index.html +20 -0
- package/issues/index.js +173 -0
- package/job.json +100 -0
- package/manage/index.html +32 -0
- package/manage/index.js +22 -0
- package/package.json +38 -0
- package/pm2.config.js +15 -0
- package/reannotate/index.html +19 -0
- package/reannotate/index.js +39 -0
- package/reannotateForm/index.html +29 -0
- package/reannotateForm/index.js +114 -0
- package/recActionForm/index.html +33 -0
- package/recActionForm/index.js +49 -0
- package/reportHideForm/index.html +29 -0
- package/reportHideForm/index.js +89 -0
- package/reportIssue/index.html +38 -0
- package/reportIssue/index.js +181 -0
- package/reportIssues/index.html +47 -0
- package/reportIssues/index.js +259 -0
- package/reportUnhideForm/index.html +29 -0
- package/reportUnhideForm/index.js +89 -0
- package/reportsExpungeForm/index.html +29 -0
- package/reportsExpungeForm/index.js +105 -0
- package/reportsPruneForm/index.html +29 -0
- package/reportsPruneForm/index.js +105 -0
- package/reportsRewindForm/index.html +29 -0
- package/reportsRewindForm/index.js +105 -0
- package/retestRec/index.html +23 -0
- package/retestRec/index.js +19 -0
- package/retestRecForm/index.html +27 -0
- package/retestRecForm/index.js +36 -0
- package/rules/index.html +28 -0
- package/rules/index.js +71 -0
- package/style.css +196 -0
- package/targets/index.html +37 -0
- package/targets/index.js +170 -0
- package/testOrder/index.html +23 -0
- package/testOrder/index.js +62 -0
- package/testRec/index.html +23 -0
- package/testRec/index.js +25 -0
- package/testRecForm/index.html +34 -0
- package/testRecForm/index.js +22 -0
- package/tutorial/images/newsletter-form.png +0 -0
- package/tutorial/index.html +796 -0
- package/tutorial/index.js +53 -0
- package/util.js +686 -0
- package/wcagMap.json +102 -0
- package/wcagRenew/index.html +19 -0
- package/wcagRenew/index.js +70 -0
- package/wcagRenewForm/index.html +25 -0
- package/wcagRenewForm/index.js +22 -0
package/rules/index.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*
|
|
2
|
+
index.js
|
|
3
|
+
Answers the issue-rules question.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
getWeightName,
|
|
10
|
+
htmlSafe,
|
|
11
|
+
tools
|
|
12
|
+
} = require('../util');
|
|
13
|
+
const {issues} = require('testilo/procs/score/tic');
|
|
14
|
+
const fs = require('fs/promises');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
// FUNCTIONS
|
|
18
|
+
|
|
19
|
+
// Adds parameters to a query for the answer page.
|
|
20
|
+
const populateQuery = async (issueID, query) => {
|
|
21
|
+
// Add facts about the issue to the query.
|
|
22
|
+
query.issue = issues[issueID].summary;
|
|
23
|
+
const issue = issues[issueID];
|
|
24
|
+
const {wcag, weight, why} = issue;
|
|
25
|
+
query.why = why;
|
|
26
|
+
query.priority = getWeightName(weight);
|
|
27
|
+
query.wcag = wcag;
|
|
28
|
+
// Initialize the lines.
|
|
29
|
+
const lines = [];
|
|
30
|
+
const margin = ' '.repeat(6);
|
|
31
|
+
// For each tool with any rules belonging to the issue:
|
|
32
|
+
Object.keys(issue.tools).forEach(toolID => {
|
|
33
|
+
// Add a line.
|
|
34
|
+
lines.push(`${margin}<li><h3>${tools[toolID][0]} rules</h3>`);
|
|
35
|
+
lines.push(`${margin} <ul>`);
|
|
36
|
+
const tool = issue.tools[toolID];
|
|
37
|
+
// For each rule of the tool belonging to the issue:
|
|
38
|
+
Object.keys(tool).forEach(ruleID => {
|
|
39
|
+
const rule = tool[ruleID];
|
|
40
|
+
const {what} = rule;
|
|
41
|
+
// Add facts about the rule.
|
|
42
|
+
if (what === ruleID) {
|
|
43
|
+
lines.push(`${margin} <li><code>${htmlSafe(ruleID)}</code></li>`);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
lines.push(`${margin} <li><code>${htmlSafe(ruleID)}</code>: ${htmlSafe(what)}</li>`);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
lines.push(`${margin} </ul>`);
|
|
50
|
+
lines.push(`${margin}</li>`);
|
|
51
|
+
});
|
|
52
|
+
// Add the lines to the query.
|
|
53
|
+
query.rules = lines.join('\n');
|
|
54
|
+
};
|
|
55
|
+
// Returns a page answering the violators question.
|
|
56
|
+
exports.answer = async issueID => {
|
|
57
|
+
const query = {};
|
|
58
|
+
// Create a query to replace the placeholders.
|
|
59
|
+
await populateQuery(issueID, query);
|
|
60
|
+
// Get the template.
|
|
61
|
+
let answerPage = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8');
|
|
62
|
+
// Replace its placeholders.
|
|
63
|
+
Object.keys(query).forEach(param => {
|
|
64
|
+
answerPage = answerPage.replace(new RegExp(`__${param}__`, 'g'), query[param]);
|
|
65
|
+
});
|
|
66
|
+
// Return the populated page.
|
|
67
|
+
return {
|
|
68
|
+
status: 'ok',
|
|
69
|
+
answerPage
|
|
70
|
+
};
|
|
71
|
+
};
|
package/style.css
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
*:focus {
|
|
2
|
+
outline: solid 0.2rem #00f;
|
|
3
|
+
outline-offset: 0.2rem;
|
|
4
|
+
}
|
|
5
|
+
a:hover {
|
|
6
|
+
background-color: #ddd;
|
|
7
|
+
}
|
|
8
|
+
body {
|
|
9
|
+
margin: 2rem;
|
|
10
|
+
font-size: large;
|
|
11
|
+
}
|
|
12
|
+
.bold, .componentID {
|
|
13
|
+
font-weight: 700;
|
|
14
|
+
}
|
|
15
|
+
button {
|
|
16
|
+
min-height: 44px;
|
|
17
|
+
min-width: 44px;
|
|
18
|
+
padding: 0.5rem;
|
|
19
|
+
border: solid 0.2rem #03a;
|
|
20
|
+
border-radius: 1rem;
|
|
21
|
+
font-size: inherit;
|
|
22
|
+
font-weight: 700;
|
|
23
|
+
color: #fff;
|
|
24
|
+
background-color: #03a;
|
|
25
|
+
}
|
|
26
|
+
button:disabled, button:disabled:hover {
|
|
27
|
+
color: #666;
|
|
28
|
+
background-color: #ccc;
|
|
29
|
+
border-color: #666;
|
|
30
|
+
}
|
|
31
|
+
button:hover {
|
|
32
|
+
color: #000;
|
|
33
|
+
background-color: #9df;
|
|
34
|
+
}
|
|
35
|
+
caption {
|
|
36
|
+
margin: 1rem 0;
|
|
37
|
+
font-weight: 700;
|
|
38
|
+
font-style: italic;
|
|
39
|
+
}
|
|
40
|
+
.center {
|
|
41
|
+
text-align: center;
|
|
42
|
+
}
|
|
43
|
+
code {
|
|
44
|
+
font-family: sans-serif;
|
|
45
|
+
color: #832703;
|
|
46
|
+
}
|
|
47
|
+
code.thin {
|
|
48
|
+
font-weight: 400;
|
|
49
|
+
}
|
|
50
|
+
details {
|
|
51
|
+
padding: rem;
|
|
52
|
+
border: solid #000 1px;
|
|
53
|
+
}
|
|
54
|
+
.error {
|
|
55
|
+
font-weight: 700;
|
|
56
|
+
color: #c00;
|
|
57
|
+
}
|
|
58
|
+
fieldset {
|
|
59
|
+
width: max-content;
|
|
60
|
+
margin-top: 1rem;
|
|
61
|
+
}
|
|
62
|
+
.firstCellRight td:nth-child(1) {
|
|
63
|
+
text-align: right;
|
|
64
|
+
}
|
|
65
|
+
form {
|
|
66
|
+
margin-top: 1rem;
|
|
67
|
+
}
|
|
68
|
+
h4, h5, h6 {
|
|
69
|
+
margin-top: 1rem;
|
|
70
|
+
margin-bottom: 0;
|
|
71
|
+
}
|
|
72
|
+
h2 {
|
|
73
|
+
font-size: 1.8rem;
|
|
74
|
+
margin: 1rem auto 0.5rem auto;
|
|
75
|
+
color: #006666;
|
|
76
|
+
}
|
|
77
|
+
h3 {
|
|
78
|
+
font-size: 1.6rem;
|
|
79
|
+
margin: 1rem auto 0.5rem auto;
|
|
80
|
+
}
|
|
81
|
+
h3.bars {
|
|
82
|
+
border-top: solid black 0.1rem;
|
|
83
|
+
padding-top: 1rem;
|
|
84
|
+
}
|
|
85
|
+
h3.priority {
|
|
86
|
+
display: inline;
|
|
87
|
+
margin: 0.5rem 0;
|
|
88
|
+
}
|
|
89
|
+
h4 {
|
|
90
|
+
font-size: 1.4rem;
|
|
91
|
+
}
|
|
92
|
+
h4.priority {
|
|
93
|
+
display: inline;
|
|
94
|
+
margin: 0.25rem 0;
|
|
95
|
+
}
|
|
96
|
+
h5 {
|
|
97
|
+
font-size: 1.2rem;
|
|
98
|
+
}
|
|
99
|
+
h6 {
|
|
100
|
+
font-size: 1rem;
|
|
101
|
+
}
|
|
102
|
+
input {
|
|
103
|
+
margin: 0.5rem 0;
|
|
104
|
+
font-size: inherit;
|
|
105
|
+
}
|
|
106
|
+
legend {
|
|
107
|
+
font-weight: 700;
|
|
108
|
+
}
|
|
109
|
+
li, p {
|
|
110
|
+
line-height: 1.5;
|
|
111
|
+
}
|
|
112
|
+
p {
|
|
113
|
+
margin: 0.5rem 0;
|
|
114
|
+
}
|
|
115
|
+
pre {
|
|
116
|
+
white-space: break-spaces;
|
|
117
|
+
}
|
|
118
|
+
.secondCellRight td:nth-child(2) {
|
|
119
|
+
text-align: right;
|
|
120
|
+
}
|
|
121
|
+
section:not(.wide) {
|
|
122
|
+
max-width: 36rem;
|
|
123
|
+
}
|
|
124
|
+
summary {
|
|
125
|
+
box-sizing: border-box;
|
|
126
|
+
min-height: 44px;
|
|
127
|
+
min-width: 44px;
|
|
128
|
+
padding: 0.75rem 0.5rem 0 0.5rem;
|
|
129
|
+
}
|
|
130
|
+
summary:focus, ul.nav > li > a:focus {
|
|
131
|
+
outline-offset: -6px;
|
|
132
|
+
}
|
|
133
|
+
summary:hover {
|
|
134
|
+
background-color: #ddd;
|
|
135
|
+
}
|
|
136
|
+
#synopsis > p {
|
|
137
|
+
margin: 0;
|
|
138
|
+
padding: 0;
|
|
139
|
+
}
|
|
140
|
+
table {
|
|
141
|
+
border-collapse: collapse;
|
|
142
|
+
}
|
|
143
|
+
table.allBorder th {
|
|
144
|
+
padding-bottom: 0;
|
|
145
|
+
}
|
|
146
|
+
table.allBorder th, table.allBorder td, tbody td {
|
|
147
|
+
border: 1px gray solid;
|
|
148
|
+
}
|
|
149
|
+
tbody th {
|
|
150
|
+
padding-right: 1rem;
|
|
151
|
+
text-align: right;
|
|
152
|
+
}
|
|
153
|
+
td, th {
|
|
154
|
+
padding: 0 0.5rem;
|
|
155
|
+
}
|
|
156
|
+
thead th {
|
|
157
|
+
padding-bottom: 1rem;
|
|
158
|
+
}
|
|
159
|
+
.thirdCellRight td:nth-child(3) {
|
|
160
|
+
text-align: right;
|
|
161
|
+
}
|
|
162
|
+
ul {
|
|
163
|
+
margin-top: 0;
|
|
164
|
+
margin-bottom: 0;
|
|
165
|
+
}
|
|
166
|
+
ul.pseudoTopLevel {
|
|
167
|
+
list-style: disc;
|
|
168
|
+
}
|
|
169
|
+
ul.nav {
|
|
170
|
+
padding-inline-start: 0.25rem;
|
|
171
|
+
}
|
|
172
|
+
ul.nav > li, ul.headed > li {
|
|
173
|
+
list-style: none;
|
|
174
|
+
}
|
|
175
|
+
ul.nav > li > a {
|
|
176
|
+
display: block;
|
|
177
|
+
box-sizing: border-box;
|
|
178
|
+
min-height: 44px;
|
|
179
|
+
min-width: 44px;
|
|
180
|
+
margin-right: 1rem;
|
|
181
|
+
padding-left: 1rem;
|
|
182
|
+
padding-top: 0.5rem;
|
|
183
|
+
}
|
|
184
|
+
ul.whereList {
|
|
185
|
+
margin-left: 2rem;
|
|
186
|
+
padding-left: 0;
|
|
187
|
+
}
|
|
188
|
+
ul.xPathList {
|
|
189
|
+
margin-left: 0;
|
|
190
|
+
padding-left: 0;
|
|
191
|
+
}
|
|
192
|
+
ul.xPathList li {
|
|
193
|
+
margin-left: 1rem;
|
|
194
|
+
padding-left: 0;
|
|
195
|
+
overflow-wrap: break-word;
|
|
196
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en-US">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
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">
|
|
8
|
+
<meta name="keywords" content="report,accessibility,a11y">
|
|
9
|
+
<title>Pages tested | Kilotest</title>
|
|
10
|
+
<link rel="icon" href="/favicon.ico">
|
|
11
|
+
<link rel="stylesheet" href="/style.css">
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<main>
|
|
15
|
+
<h1>Pages tested by <a href="/">Kilotest</a></h1>
|
|
16
|
+
<h2>Recommended for testing</h2>
|
|
17
|
+
<ol>
|
|
18
|
+
__recs__
|
|
19
|
+
</ol>
|
|
20
|
+
<p>__noRecs__</p>
|
|
21
|
+
<p>Should Kilotest <a href="testRecForm.html">test a page</a> that is not listed below?</p>
|
|
22
|
+
<h2>Queued for testing</h2>
|
|
23
|
+
<ol>
|
|
24
|
+
__queue__
|
|
25
|
+
</ol>
|
|
26
|
+
<p>__noQueued__</p>
|
|
27
|
+
<h2>Being tested now</h2>
|
|
28
|
+
<ul>
|
|
29
|
+
__claimed__
|
|
30
|
+
</ul>
|
|
31
|
+
<p>__noClaimed__</p>
|
|
32
|
+
<h2>Already tested</h2>
|
|
33
|
+
<p>Kilotest has tested __which__ pages.</p>
|
|
34
|
+
__testedPages__
|
|
35
|
+
</main>
|
|
36
|
+
</body>
|
|
37
|
+
</html>
|
package/targets/index.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/*
|
|
2
|
+
index.js
|
|
3
|
+
Answers the targets question.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
getAgoDays,
|
|
10
|
+
getJobNames,
|
|
11
|
+
getObject,
|
|
12
|
+
getPageDataStrings,
|
|
13
|
+
getRecs,
|
|
14
|
+
getToolNamesString,
|
|
15
|
+
getLogs,
|
|
16
|
+
getTargetData,
|
|
17
|
+
isRecommendable,
|
|
18
|
+
jobsPath
|
|
19
|
+
} = require('../util');
|
|
20
|
+
const fs = require('fs/promises');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
|
|
23
|
+
// FUNCTIONS
|
|
24
|
+
|
|
25
|
+
// Returns a description of a tool count.
|
|
26
|
+
const getToolCountString = toolCount => toolCount === 1 ? '1 tool' : `${toolCount} tools`;
|
|
27
|
+
// Adds parameters to a query for the answer page.
|
|
28
|
+
const populateQuery = async query => {
|
|
29
|
+
const margin = ' '.repeat(8);
|
|
30
|
+
// Initialize the classes of lines.
|
|
31
|
+
const lines = {
|
|
32
|
+
recs: [],
|
|
33
|
+
queue: [],
|
|
34
|
+
claimed: [],
|
|
35
|
+
tested: []
|
|
36
|
+
};
|
|
37
|
+
// Get the recommendations.
|
|
38
|
+
const recs = await getRecs();
|
|
39
|
+
// For each recommended URL:
|
|
40
|
+
Object.keys(recs).forEach(url => {
|
|
41
|
+
// For each of its recommendations:
|
|
42
|
+
recs[url].forEach(rec => {
|
|
43
|
+
const {what, why} = rec;
|
|
44
|
+
// Add a line.
|
|
45
|
+
lines.recs.push(`${margin}<li><code>${url}</code> (${what}): ${why}</li>`);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
// Sort the lines in alphabetical order by URL and secondarily by proposed name.
|
|
49
|
+
lines.recs.sort();
|
|
50
|
+
// Add the lines to the query.
|
|
51
|
+
query.recs = lines.recs.join('\n');
|
|
52
|
+
// Add a no-recommendations message, if applicable, to the query.
|
|
53
|
+
query.noRecs = lines.recs.length
|
|
54
|
+
? 'Kilotest managers can <a href="recActionForm.html">approve or reject a recommendation</a>.'
|
|
55
|
+
: 'No recommendations await approval now.';
|
|
56
|
+
// Get the file names of all queued and claimed jobs.
|
|
57
|
+
const jobFileNames = await getJobNames();
|
|
58
|
+
// For each job category:
|
|
59
|
+
for (const category of ['queue', 'claimed']) {
|
|
60
|
+
// For each job in the category:
|
|
61
|
+
for (const fileName of jobFileNames[category]) {
|
|
62
|
+
// Get the job.
|
|
63
|
+
const job = await getObject(path.join(jobsPath, category, fileName));
|
|
64
|
+
// Add a line.
|
|
65
|
+
lines[category].push(`${margin}<li><code>${job.target.url}</code> (${job.target.what})</li>`);
|
|
66
|
+
}
|
|
67
|
+
// Add the lines to the query.
|
|
68
|
+
query[category] = lines[category].join('\n');
|
|
69
|
+
}
|
|
70
|
+
// Add a no-queued message, if applicable, to the query.
|
|
71
|
+
query.noQueued = lines.queue.length ? '' : 'No pages are queued for testing.';
|
|
72
|
+
// Add a no-claimed message, if applicable, to the query.
|
|
73
|
+
query.noClaimed = lines.claimed.length ? '' : 'No pages are being tested now.';
|
|
74
|
+
// Get the logs of the reports.
|
|
75
|
+
const targetLogs = (await getLogs()).filter(log => ! log.hidden);
|
|
76
|
+
query.which = targetLogs.length ? 'the following' : 'no';
|
|
77
|
+
query.some = (targetLogs.length || jobFileNames.queue.length || jobFileNames.claimed.length)
|
|
78
|
+
? 'another'
|
|
79
|
+
: 'a';
|
|
80
|
+
const multiReportTargets = new Set(targetLogs.filter(log => log.superseded).map(log => log.what));
|
|
81
|
+
// For each log that is not hidden:
|
|
82
|
+
for (const targetLog of targetLogs) {
|
|
83
|
+
const {jobName, url, what} = targetLog;
|
|
84
|
+
const [timeStamp, jobID] = jobName.split('-');
|
|
85
|
+
const reportData = await getTargetData(timeStamp, jobID);
|
|
86
|
+
const {issueSet, preventedTools, reporterSet, violatorSet} = reportData;
|
|
87
|
+
lines.tested.push(`${margin}<details>`);
|
|
88
|
+
const daysAgo = getAgoDays(timeStamp);
|
|
89
|
+
const pageDataStrings = await getPageDataStrings(timeStamp, jobID, {what, url, daysAgo});
|
|
90
|
+
const {urlLink, testInfo} = pageDataStrings;
|
|
91
|
+
const testText = multiReportTargets.has(what) ? ` (${testInfo.toLowerCase()})` : '';
|
|
92
|
+
lines.tested.push(`${margin} <summary>${what}${testText}</summary>`);
|
|
93
|
+
lines.tested.push(`${margin} <ul>`);
|
|
94
|
+
// Add the URL of the target to the lines.
|
|
95
|
+
lines.tested.push(`${margin} <li>URL: ${urlLink}</li>`);
|
|
96
|
+
// Add facts about the report to the lines.
|
|
97
|
+
lines.tested.push(`${margin} <li>${testInfo}</li>`);
|
|
98
|
+
// If the page prevented any tool from performing its tests:
|
|
99
|
+
if (preventedTools?.length) {
|
|
100
|
+
// Add this to the lines.
|
|
101
|
+
const preventedToolSet = new Set(preventedTools);
|
|
102
|
+
const toolCountString = getToolCountString(preventedToolSet.size);
|
|
103
|
+
const toolsString = getToolNamesString(preventedToolSet);
|
|
104
|
+
lines.tested.push(
|
|
105
|
+
`${margin} <li>Page not testable by ${toolCountString} (${toolsString})</li>`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
// Add facts about the test results to the lines.
|
|
109
|
+
const reporterCount = reporterSet.size;
|
|
110
|
+
const reporterCountString = getToolCountString(reporterCount);
|
|
111
|
+
let reporterString = reporterCountString;
|
|
112
|
+
if (reporterCount) {
|
|
113
|
+
const reporterNamesString = getToolNamesString(reporterSet);
|
|
114
|
+
reporterString = `${reporterCountString} (${reporterNamesString})`;
|
|
115
|
+
}
|
|
116
|
+
const issueCountString = issueSet.size === 1 ? '1 issue was' : `${issueSet.size} issues were`;
|
|
117
|
+
const violatorString = violatorSet.size === 1
|
|
118
|
+
? '1 violator was'
|
|
119
|
+
: `${violatorSet.size} violators were`;
|
|
120
|
+
lines.tested.push(`${margin} <li>${reporterString} reported issues</li>`);
|
|
121
|
+
lines.tested.push(`${margin} <li>${issueCountString} reported</li>`);
|
|
122
|
+
lines.tested.push(`${margin} <li>${violatorString} reported</li>`);
|
|
123
|
+
lines.tested.push(`${margin} </ul>`);
|
|
124
|
+
lines.tested.push(`${margin}<ul class="nav">`);
|
|
125
|
+
// If any issues were reported:
|
|
126
|
+
if (issueSet.size) {
|
|
127
|
+
// Add a question link about the reported issues to the lines.
|
|
128
|
+
const href = `href="reportIssues.html/${timeStamp}/${jobID}"`;
|
|
129
|
+
const label = `aria-label="What ${issueCountString} reported for the ${what} page?"`;
|
|
130
|
+
const questionString = issueSet.size === 1 ? 'was the issue' : 'were the issues';
|
|
131
|
+
const link = `<a ${href} ${label}>What ${questionString}?</a>`;
|
|
132
|
+
lines.tested.push(`${margin} <li>${link}</li>`);
|
|
133
|
+
}
|
|
134
|
+
// Add the status of, and if necessary a question link about, retesting to the lines.
|
|
135
|
+
const status = await isRecommendable(url);
|
|
136
|
+
let retestString;
|
|
137
|
+
if (status === 'claimed') {
|
|
138
|
+
retestString = 'Currently being retested';
|
|
139
|
+
}
|
|
140
|
+
else if (status === 'queued') {
|
|
141
|
+
retestString = 'Currently in the queue for retesting';
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
const href = `/retestRecForm.html/${timeStamp}/${jobID}`;
|
|
145
|
+
const retestContent = 'Should Kilotest retest the page?';
|
|
146
|
+
retestString = `<a href="${href}">${retestContent}</a>`;
|
|
147
|
+
}
|
|
148
|
+
lines.tested.push(`${margin} <li>${retestString}</li>`);
|
|
149
|
+
lines.tested.push(`${margin} </ul>`);
|
|
150
|
+
lines.tested.push(`${margin}</details>`);
|
|
151
|
+
}
|
|
152
|
+
query.testedPages = lines.tested.join('\n');
|
|
153
|
+
};
|
|
154
|
+
// Returns a page answering the targets question.
|
|
155
|
+
exports.answer = async () => {
|
|
156
|
+
const query = {};
|
|
157
|
+
// Create a query to replace placeholders.
|
|
158
|
+
await populateQuery(query);
|
|
159
|
+
// Get the template.
|
|
160
|
+
let answerPage = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8');
|
|
161
|
+
// Replace its placeholders.
|
|
162
|
+
Object.keys(query).forEach(param => {
|
|
163
|
+
answerPage = answerPage.replace(new RegExp(`__${param}__`, 'g'), query[param]);
|
|
164
|
+
});
|
|
165
|
+
// Return the populated page.
|
|
166
|
+
return {
|
|
167
|
+
status: 'ok',
|
|
168
|
+
answerPage
|
|
169
|
+
};
|
|
170
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en-US">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
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">
|
|
8
|
+
<meta name="keywords" content="report,accessibility,a11y">
|
|
9
|
+
<title>Test order | Kilotest</title>
|
|
10
|
+
<link rel="icon" href="/favicon.ico">
|
|
11
|
+
<link rel="stylesheet" href="/style.css">
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<main>
|
|
15
|
+
<h1><a href="/">Kilotest</a>: Test order for __target__</h1>
|
|
16
|
+
<p>A job that will test the __target__ page has been placed into the job queue.</p>
|
|
17
|
+
<p>The job name is <code>__jobName__</code>.</p>
|
|
18
|
+
<p>When Kilotest is not busy, a job is typically completed within about a day after submission.</p>
|
|
19
|
+
<p>Once the job has been completed, it will appear in the answer to the question, <q>Which web pages have been tested?</q>.</p>
|
|
20
|
+
<p>All recommendations for the retest of the __target__ page have been deleted.</p>
|
|
21
|
+
</main>
|
|
22
|
+
</body>
|
|
23
|
+
</html>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/*
|
|
2
|
+
index.js
|
|
3
|
+
Implements a retest order.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
const {getJSON, getNowStamp, getRecs, isURL} = require('../util');
|
|
9
|
+
const fs = require('fs/promises');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
// FUNCTIONS
|
|
13
|
+
|
|
14
|
+
// Implements a test order and returns an acknowledgement page.
|
|
15
|
+
exports.answer = async (url, what, authCode) => {
|
|
16
|
+
// If the arguments are valid:
|
|
17
|
+
if (isURL(url) && what && authCode === process.env.AUTH_CODE) {
|
|
18
|
+
// Get the job template.
|
|
19
|
+
const jobTemplateJSON = await fs.readFile(path.join(__dirname, '..', 'job.json'), 'utf8');
|
|
20
|
+
const job = JSON.parse(jobTemplateJSON);
|
|
21
|
+
const nowStamp = getNowStamp();
|
|
22
|
+
// Populate the template with job properties.
|
|
23
|
+
const jobIDSuffix = Date.now().toString(36).slice(5);
|
|
24
|
+
const jobName = `${nowStamp}-${jobIDSuffix}`;
|
|
25
|
+
job.id = jobName;
|
|
26
|
+
job.creationTimeStamp = nowStamp;
|
|
27
|
+
job.executionTimeStamp = nowStamp;
|
|
28
|
+
job.target.what = what;
|
|
29
|
+
job.target.url = url;
|
|
30
|
+
const query = {
|
|
31
|
+
target: what,
|
|
32
|
+
jobName
|
|
33
|
+
};
|
|
34
|
+
// Save the job in the queue.
|
|
35
|
+
await fs.writeFile(
|
|
36
|
+
path.join(__dirname, '..', 'jobs', 'queue', `${jobName}.json`), getJSON(job)
|
|
37
|
+
);
|
|
38
|
+
console.log(`Retest queued for ${what} as job ${jobName}`);
|
|
39
|
+
// Get the recommendations.
|
|
40
|
+
const recs = await getRecs();
|
|
41
|
+
// Delete the recommendations to retest the target.
|
|
42
|
+
delete recs[url];
|
|
43
|
+
// Save the revised recommendations.
|
|
44
|
+
await fs.writeFile(path.join(__dirname, '..', 'jobs', 'recs.json'), getJSON(recs));
|
|
45
|
+
// Get the answer template.
|
|
46
|
+
let answerPage = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8');
|
|
47
|
+
// Replace its placeholders.
|
|
48
|
+
Object.keys(query).forEach(param => {
|
|
49
|
+
answerPage = answerPage.replace(new RegExp(`__${param}__`, 'g'), query[param]);
|
|
50
|
+
});
|
|
51
|
+
// Return the populated page.
|
|
52
|
+
return {
|
|
53
|
+
status: 'ok',
|
|
54
|
+
answerPage
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// Otherwise, i.e. if the authorization code is invalid, return an error page.
|
|
58
|
+
return {
|
|
59
|
+
status: 'error',
|
|
60
|
+
error: 'Invalid authorization code'
|
|
61
|
+
};
|
|
62
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en-US">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
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">
|
|
8
|
+
<meta name="keywords" content="report,accessibility,a11y">
|
|
9
|
+
<title>__target__ test recommendation received | Kilotest</title>
|
|
10
|
+
<link rel="icon" href="/favicon.ico">
|
|
11
|
+
<link rel="stylesheet" href="/style.css">
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<main>
|
|
15
|
+
<h1><a href="/">Kilotest</a>: Test recommendation received for __target__</h1>
|
|
16
|
+
<p>Your recommendation to test the __target__ page has been received.</p>
|
|
17
|
+
<p>The reason for your recommendation has been recorded as <q>__why__</q>.</p>
|
|
18
|
+
<p>Thank you for your recommendation.</p>
|
|
19
|
+
<p>If a <a href="/recActionForm.html">Kilotest manager approves this recommendation</a>, a test order will be added to the queue.</p>
|
|
20
|
+
<p>In most cases, if you check Kilotest again in 1 day, you will find that the recommended job has been performed.</p>
|
|
21
|
+
</main>
|
|
22
|
+
</body>
|
|
23
|
+
</html>
|
package/testRec/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
index.js
|
|
3
|
+
Processes a test recommendation.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
const {isRecommendable, processRec} = require('../util');
|
|
9
|
+
|
|
10
|
+
// FUNCTIONS
|
|
11
|
+
|
|
12
|
+
// Records a test recommendation and returns an acknowledgement page.
|
|
13
|
+
exports.answer = async (what, url, why) => {
|
|
14
|
+
const status = await isRecommendable(url);
|
|
15
|
+
// If the target is already claimed or queued and is thus not recommendable:
|
|
16
|
+
if (status) {
|
|
17
|
+
// Return an answer reporting this.
|
|
18
|
+
return {
|
|
19
|
+
status: 'error',
|
|
20
|
+
error: `Page is already ${status}`
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
// Otherwise, i.e. if it is recommendable, process the recommendation.
|
|
24
|
+
return await processRec('test', __dirname, what, url, why);
|
|
25
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en-US">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
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">
|
|
8
|
+
<meta name="keywords" content="report,accessibility,a11y">
|
|
9
|
+
<title>Test recommendation | Kilotest</title>
|
|
10
|
+
<link rel="icon" href="/favicon.ico">
|
|
11
|
+
<link rel="stylesheet" href="/style.css">
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<main>
|
|
15
|
+
<h1><a href="/">Kilotest</a>: Test recommendation</h1>
|
|
16
|
+
<p>You may recommend a web page for testing by submitting the following form. All answers are required.</p>
|
|
17
|
+
<form action="/testRec.html" method="post">
|
|
18
|
+
<p><label>
|
|
19
|
+
Propose a name for the page.<br>
|
|
20
|
+
<input size="50" maxlength="70" name="what" required>
|
|
21
|
+
</label></p>
|
|
22
|
+
<p><label>
|
|
23
|
+
What is the URL (starting with <code>https://</code>)?<br>
|
|
24
|
+
<input type="url" size="60" maxlength="80" name="url" pattern="https://.+\..+" required>
|
|
25
|
+
</label></p>
|
|
26
|
+
<p><label>
|
|
27
|
+
Why test the page?<br>
|
|
28
|
+
<input size="70" minlength="5" maxlength="100" name="why" required>
|
|
29
|
+
</label></p>
|
|
30
|
+
<p><button type="submit">Submit</button></p>
|
|
31
|
+
</form>
|
|
32
|
+
</main>
|
|
33
|
+
</body>
|
|
34
|
+
</html>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*
|
|
2
|
+
index.js
|
|
3
|
+
Answers the test question.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
const fs = require('fs/promises');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
// FUNCTIONS
|
|
12
|
+
|
|
13
|
+
// Returns a test recommendation form.
|
|
14
|
+
exports.answer = async () => {
|
|
15
|
+
// Get the recommendation form template.
|
|
16
|
+
let answerPage = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8');
|
|
17
|
+
// Return it.
|
|
18
|
+
return {
|
|
19
|
+
status: 'ok',
|
|
20
|
+
answerPage
|
|
21
|
+
};
|
|
22
|
+
};
|
|
Binary file
|