@jrpool/kilotest 46.1.2 → 48.0.0
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 +13 -1
- package/.devin/config.local.json +5 -1
- package/.github/workflows/ci.yml +0 -3
- package/.github/workflows/periodic-smoke-tests.yml +30 -0
- package/.github/workflows/publish-mcp.yml +4 -4
- package/{alerts.test.js → alerts.test.ts} +17 -29
- package/{alerts.js → alerts.ts} +10 -13
- package/api/{getReport.test.js → getReport.test.ts} +9 -10
- package/api/{getReport.js → getReport.ts} +23 -24
- package/api/{listDiagnoses.test.js → listDiagnoses.test.ts} +26 -27
- package/api/{listDiagnoses.js → listDiagnoses.ts} +46 -70
- package/api/{listIssues.test.js → listIssues.test.ts} +21 -14
- package/api/{listIssues.js → listIssues.ts} +75 -74
- package/api/{listReports.test.js → listReports.test.ts} +17 -16
- package/api/listReports.ts +87 -0
- package/api/{listViolators.test.js → listViolators.test.ts} +19 -20
- package/api/{listViolators.js → listViolators.ts} +45 -70
- package/api/requestFeature.test.ts +54 -0
- package/api/{requestFeature.js → requestFeature.ts} +18 -15
- package/api/requestRetest.test.ts +144 -0
- package/api/requestRetest.ts +119 -0
- package/api/requestTest.test.ts +153 -0
- package/api/requestTest.ts +123 -0
- package/api/{routes.test.js → routes.test.ts} +5 -5
- package/api/{routes.js → routes.ts} +6 -6
- package/api/{schemas.js → schemas.ts} +69 -50
- package/api/{util.test.js → util.test.ts} +35 -14
- package/api/{util.js → util.ts} +54 -51
- package/api/version.ts +8 -0
- package/{apiIntegration.test.js → apiIntegration.test.ts} +12 -13
- package/balances.ts +87 -0
- package/{checkinfo.js → checkinfo.ts} +24 -20
- package/docs/AI-TOOLS.md +4 -4
- package/docs/BACKLOG.md +360 -0
- package/docs/SERVICE.md +170 -17
- package/docs/decisions/002-coverage-tool.md +40 -0
- package/docs/decisions/003-adr-threshold.md +34 -0
- package/docs/decisions/004-docs-directory.md +23 -0
- package/docs/decisions/005-observability.md +28 -0
- package/docs/decisions/006-git-workflow.md +28 -0
- package/docs/decisions/007-internal-testing.md +34 -0
- package/docs/decisions/008-dependencies.md +28 -0
- package/docs/decisions/009-qai-integration.md +40 -0
- package/docs/decisions/QAI-HISTORICAL-CONTEXT.md +42 -0
- package/eslint.config.mjs +27 -0
- package/index.html +3 -3
- package/{index.test.js → index.test.ts} +397 -152
- package/{index.js → index.ts} +386 -285
- package/{indexBalances.test.js → indexBalances.test.ts} +21 -17
- package/{indexServe.test.js → indexServe.test.ts} +9 -9
- package/linkValidation.test.ts +190 -0
- package/{mcp.test.js → mcp.test.ts} +53 -26
- package/{mcp.js → mcp.ts} +29 -20
- package/memory/MEMORY.md +7 -0
- package/memory/user_maintainer_context.md +24 -0
- package/openapi.yaml +115 -68
- package/package.json +35 -15
- package/pm2.config.cjs +29 -0
- package/{routes.test.js → routes.test.ts} +8 -7
- package/{routing.test.js → routing.test.ts} +11 -12
- package/scripts/{generate-openapi.js → generate-openapi.ts} +13 -13
- package/serve.ts +15 -0
- package/smokeTest.ts +234 -0
- package/test/dbFixture.ts +21 -0
- package/test/fixtures/{buildFixtures.js → buildFixtures.ts} +14 -13
- package/test/{setup.cjs → setup.ts} +2 -2
- package/tsconfig.json +16 -0
- package/util.test.ts +1129 -0
- package/util.ts +1017 -0
- package/web/ai0BalanceForm/{ai0BalanceForm.test.js → ai0BalanceForm.test.ts} +15 -15
- package/web/ai0BalanceForm/{index.js → index.ts} +10 -18
- package/web/enqueue/index.ts +56 -0
- package/web/enqueueForm/index.html +9 -9
- package/web/enqueueForm/index.ts +43 -0
- package/web/expungeReportsForm/{expungeReportsForm.test.js → expungeReportsForm.test.ts} +18 -20
- package/web/expungeReportsForm/index.ts +18 -0
- package/web/hideReportForm/{hideReportForm.test.js → hideReportForm.test.ts} +14 -14
- package/web/hideReportForm/{index.js → index.ts} +17 -21
- package/web/listDiagnoses/{index.js → index.ts} +48 -58
- package/web/listDiagnoses/{listDiagnoses.test.js → listDiagnoses.test.ts} +39 -34
- package/web/listIssues/{index.js → index.ts} +67 -92
- package/web/listIssues/{listIssues.test.js → listIssues.test.ts} +45 -51
- package/web/listReports/index.html +3 -3
- package/web/listReports/{index.js → index.ts} +74 -58
- package/web/listReports/{listReports.test.js → listReports.test.ts} +41 -43
- package/web/listRules/{index.js → index.ts} +18 -23
- package/web/listRules/{listRules.test.js → listRules.test.ts} +26 -19
- package/web/listTopIssues/{index.js → index.ts} +36 -47
- package/web/listTopIssues/{listTopIssues.test.js → listTopIssues.test.ts} +26 -17
- package/web/listViolators/{index.js → index.ts} +51 -71
- package/web/listViolators/{listViolators.test.js → listViolators.test.ts} +43 -36
- package/web/manage/index.ts +22 -0
- package/web/pruneReportsForm/index.ts +18 -0
- package/web/pruneReportsForm/{pruneReportsForm.test.js → pruneReportsForm.test.ts} +11 -11
- package/web/reannotate/index.ts +71 -0
- package/web/reannotate/{reannotate.test.js → reannotate.test.ts} +19 -22
- package/web/reannotateForm/{index.js → index.ts} +41 -50
- package/web/reannotateForm/{reannotateForm.test.js → reannotateForm.test.ts} +13 -16
- package/web/renewWCAG/{index.js → index.ts} +9 -9
- package/web/renewWCAG/{renewWCAG.test.js → renewWCAG.test.ts} +17 -20
- package/web/renewWCAGForm/index.ts +22 -0
- package/web/{expungeReportsForm/index.js → reportDeletion.ts} +57 -39
- package/web/requestRetest/index.html +7 -7
- package/web/requestRetest/index.ts +62 -0
- package/web/requestRetest/requestRetest.test.ts +130 -0
- package/web/requestRetestForm/{index.js → index.ts} +10 -16
- package/web/requestRetestForm/{requestRetestForm.test.js → requestRetestForm.test.ts} +11 -12
- package/web/requestTest/index.html +7 -7
- package/web/requestTest/index.ts +52 -0
- package/web/requestTest/{requestTest.test.js → requestTest.test.ts} +39 -24
- package/web/requestTestForm/index.html +4 -4
- package/web/requestTestForm/index.ts +21 -0
- package/web/requestTestForm/{requestTestForm.test.js → requestTestForm.test.ts} +9 -9
- package/web/rewindReportsForm/index.ts +18 -0
- package/web/rewindReportsForm/{rewindReportsForm.test.js → rewindReportsForm.test.ts} +11 -11
- package/web/tutorialAI/index.html +184 -0
- package/web/tutorialAI/index.ts +80 -0
- package/web/tutorialAI/tutorialAI.test.ts +192 -0
- package/web/{tutorial → tutorialWeb}/index.html +9 -8
- package/web/tutorialWeb/index.ts +78 -0
- package/web/tutorialWeb/tutorialWeb.test.ts +195 -0
- package/web/unhideReportForm/{index.js → index.ts} +18 -22
- package/web/unhideReportForm/{unhideReportForm.test.js → unhideReportForm.test.ts} +16 -16
- package/aceconfig.js +0 -28
- package/api/listReports.js +0 -89
- package/api/requestFeature.test.js +0 -58
- package/api/requestRetest.js +0 -96
- package/api/requestRetest.test.js +0 -80
- package/api/requestTest.js +0 -106
- package/api/requestTest.test.js +0 -92
- package/api/version.js +0 -8
- package/indexFallback.test.js +0 -33
- package/indexWorkers.test.js +0 -79
- package/jsconfig.json +0 -8
- package/pm2.config.js +0 -18
- package/smokeTest.js +0 -128
- package/util.js +0 -744
- package/util.test.js +0 -968
- package/web/enqueue/index.js +0 -67
- package/web/enqueueForm/index.js +0 -49
- package/web/manage/index.js +0 -22
- package/web/pruneReportsForm/index.js +0 -123
- package/web/reannotate/index.js +0 -58
- package/web/renewWCAGForm/index.js +0 -22
- package/web/requestRetest/index.js +0 -31
- package/web/requestRetest/requestRetest.test.js +0 -119
- package/web/requestTest/index.js +0 -24
- package/web/requestTestForm/index.js +0 -21
- package/web/rewindReportsForm/index.js +0 -121
- package/web/tutorial/index.js +0 -63
- package/web/tutorial/tutorial.test.js +0 -97
- /package/test/fixtures/db/jobs/{recs.json → testRequests.json} +0 -0
- /package/web/{tutorial → tutorialWeb}/images/newsletter-form.png +0 -0
|
@@ -1,56 +1,66 @@
|
|
|
1
1
|
/*
|
|
2
|
-
index.
|
|
2
|
+
index.ts
|
|
3
3
|
Lists the diagnoses of a violator of an issue in a report.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
import {
|
|
9
9
|
getPageDataStrings,
|
|
10
10
|
getReport,
|
|
11
|
+
getTestActInstances,
|
|
11
12
|
getTextFragmentHref,
|
|
12
13
|
getWCAGLink,
|
|
13
14
|
getWeightName,
|
|
14
15
|
htmlSafe,
|
|
15
|
-
|
|
16
|
+
isReportError,
|
|
17
|
+
populateTemplate,
|
|
16
18
|
ruleEngines
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const fs = require('fs/promises');
|
|
20
|
-
const path = require('path');
|
|
19
|
+
} from '../../util.ts';
|
|
20
|
+
import {issues as issueSpecs} from 'testaro-issues';
|
|
21
21
|
|
|
22
22
|
// FUNCTIONS
|
|
23
23
|
|
|
24
24
|
// Adds parameters to a query for the answer page.
|
|
25
|
-
const populateQuery = async (
|
|
25
|
+
const populateQuery = async (
|
|
26
|
+
issueID: string,
|
|
27
|
+
timeStamp: string,
|
|
28
|
+
jobID: string,
|
|
29
|
+
catalogIndex: string,
|
|
30
|
+
pathID: string | null,
|
|
31
|
+
query: Record<string, any>
|
|
32
|
+
) => {
|
|
26
33
|
// Get descriptions of the page facts.
|
|
27
34
|
const pageDataStrings = await getPageDataStrings(timeStamp, jobID);
|
|
28
35
|
// If this failed:
|
|
29
|
-
if (pageDataStrings.error) {
|
|
36
|
+
if (pageDataStrings.error !== undefined) {
|
|
30
37
|
// Populate the query with the reason.
|
|
31
38
|
query.error = pageDataStrings.error;
|
|
32
39
|
// Stop populating the query.
|
|
33
40
|
return;
|
|
34
41
|
}
|
|
35
|
-
const {testInfo, url, urlLink,
|
|
42
|
+
const {testInfo, url, urlLink, description} = pageDataStrings;
|
|
36
43
|
// Otherwise, i.e. if it succeeded, get the report.
|
|
37
44
|
const report = await getReport(timeStamp, jobID);
|
|
38
|
-
const {acts, catalog} = report;
|
|
39
45
|
// If this failed:
|
|
40
|
-
if (report
|
|
46
|
+
if (isReportError(report)) {
|
|
41
47
|
// Populate the query with the reason.
|
|
42
48
|
query.error = report.error;
|
|
43
49
|
// Stop populating the query.
|
|
44
50
|
return;
|
|
45
51
|
}
|
|
52
|
+
const {catalog} = report;
|
|
46
53
|
// Otherwise, i.e. if it succeeded, get the catalog item of the specified violator.
|
|
47
|
-
const catalogItem = catalog[catalogIndex]
|
|
48
|
-
const
|
|
54
|
+
const catalogItem = catalog[catalogIndex];
|
|
55
|
+
const boxID = catalogItem?.boxID;
|
|
56
|
+
const startTag = catalogItem?.startTag;
|
|
57
|
+
const tagName = catalogItem?.tagName;
|
|
58
|
+
const text = catalogItem?.text;
|
|
49
59
|
query.catalogIndex = catalogIndex;
|
|
50
|
-
const lines = [];
|
|
60
|
+
const lines: string[] = [];
|
|
51
61
|
const margin = ' '.repeat(6);
|
|
52
|
-
if (catalogIndex && catalogItem
|
|
53
|
-
const href = getTextFragmentHref(text, url);
|
|
62
|
+
if (catalogIndex && catalogItem?.textLinkable) {
|
|
63
|
+
const href = getTextFragmentHref(text as string, url);
|
|
54
64
|
const label = `Take me to element ${catalogIndex} on the page (in a new tab)`;
|
|
55
65
|
const link = `<a href="${href}" target="_blank" aria-label="${label}">Take me there</a>`;
|
|
56
66
|
query.takeMeThere = `${margin} <p>${link}</p>`;
|
|
@@ -59,7 +69,7 @@ const populateQuery = async (issueID, timeStamp, jobID, catalogIndex, pathID, qu
|
|
|
59
69
|
query.takeMeThere = '';
|
|
60
70
|
}
|
|
61
71
|
// Add facts about the issue to the query.
|
|
62
|
-
query.target =
|
|
72
|
+
query.target = description;
|
|
63
73
|
query.urlLink = urlLink;
|
|
64
74
|
query.testInfo = testInfo;
|
|
65
75
|
query.issue = issueSpecs[issueID]?.summary;
|
|
@@ -71,20 +81,20 @@ const populateQuery = async (issueID, timeStamp, jobID, catalogIndex, pathID, qu
|
|
|
71
81
|
return;
|
|
72
82
|
}
|
|
73
83
|
// Otherwise, i.e. if it succeeded, get the issue details.
|
|
74
|
-
const issue = issueSpecs[issueID]
|
|
84
|
+
const issue = issueSpecs[issueID]!;
|
|
75
85
|
const {wcag, weight, why} = issue;
|
|
76
86
|
query.why = why;
|
|
77
87
|
query.priority = getWeightName(weight);
|
|
78
88
|
query.wcag = `<a href="${getWCAGLink(wcag)}">${wcag}</a>`;
|
|
79
89
|
query.tagName = tagName || 'HTML';
|
|
80
|
-
if (text && !['HTML', 'BODY', 'HEAD', 'SCRIPT', 'STYLE', 'NOSCRIPT'].includes(tagName)) {
|
|
90
|
+
if (text && !['HTML', 'BODY', 'HEAD', 'SCRIPT', 'STYLE', 'NOSCRIPT'].includes(tagName as string)) {
|
|
81
91
|
const textString = text.split('\n').join(' … ');
|
|
82
92
|
query.text = `<q>${htmlSafe(textString)}</q>`;
|
|
83
93
|
}
|
|
84
94
|
else {
|
|
85
95
|
query.text = '[not applicable]';
|
|
86
96
|
}
|
|
87
|
-
query.startTag = htmlSafe(startTag) || '[not obtained]';
|
|
97
|
+
query.startTag = htmlSafe(startTag ?? '') || '[not obtained]';
|
|
88
98
|
query.pathID = pathID || '[not obtained]';
|
|
89
99
|
if (boxID) {
|
|
90
100
|
const dims = boxID.split(':');
|
|
@@ -94,23 +104,15 @@ const populateQuery = async (issueID, timeStamp, jobID, catalogIndex, pathID, qu
|
|
|
94
104
|
query.box = '[not obtained]';
|
|
95
105
|
}
|
|
96
106
|
// Initialize an array of diagnoses.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
caseInstances.forEach(instance => {
|
|
107
|
-
const {ruleID, what} = instance;
|
|
108
|
-
// Add lines for it to the array.
|
|
109
|
-
diagnoses.push({
|
|
110
|
-
engineID: which,
|
|
111
|
-
ruleID,
|
|
112
|
-
what
|
|
113
|
-
});
|
|
107
|
+
const diagnoses: any[] = [];
|
|
108
|
+
// For each violating standard instance that pertains to this combination of issue and violator:
|
|
109
|
+
getTestActInstances(report, {violationsOnly: true, issueID, catalogIndex}).forEach(({act, instance}) => {
|
|
110
|
+
const {ruleID, what} = instance;
|
|
111
|
+
// Add lines for it to the array.
|
|
112
|
+
diagnoses.push({
|
|
113
|
+
engineID: act.which,
|
|
114
|
+
ruleID,
|
|
115
|
+
what
|
|
114
116
|
});
|
|
115
117
|
});
|
|
116
118
|
// For each diagnosis:
|
|
@@ -118,9 +120,9 @@ const populateQuery = async (issueID, timeStamp, jobID, catalogIndex, pathID, qu
|
|
|
118
120
|
const {engineID, ruleID, what} = diagnosis;
|
|
119
121
|
// Add lines.
|
|
120
122
|
lines.push(`${margin}<li>${htmlSafe(what)}`);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
);
|
|
123
|
+
// engineID is act.which, guaranteed by isUsableReport to be a key in ruleEngines.
|
|
124
|
+
const [engineName, engineOrg] = ruleEngines[engineID]!;
|
|
125
|
+
lines.push(`${margin} <p>Rule engine: ${engineName} (${engineOrg})</p>`);
|
|
124
126
|
if (ruleID !== what) {
|
|
125
127
|
lines.push(`${margin} <p>Rule: <code>${ruleID}</code></p>`);
|
|
126
128
|
}
|
|
@@ -130,19 +132,11 @@ const populateQuery = async (issueID, timeStamp, jobID, catalogIndex, pathID, qu
|
|
|
130
132
|
query.diagnoses = lines.join('\n');
|
|
131
133
|
};
|
|
132
134
|
// Returns a page answering the diagnoses question.
|
|
133
|
-
|
|
134
|
-
const [issueID, timeStamp, jobID, catalogIndex] = pageArgs.split('/');
|
|
135
|
-
const reportIsHidden = await isHidden(timeStamp, jobID);
|
|
136
|
-
// If the report is not available:
|
|
137
|
-
if (reportIsHidden) {
|
|
138
|
-
return {
|
|
139
|
-
status: 'error',
|
|
140
|
-
message: 'Report not available'
|
|
141
|
-
};
|
|
142
|
-
}
|
|
135
|
+
export const answer = async (pageArgs: string, search: string) => {
|
|
136
|
+
const [issueID, timeStamp, jobID, catalogIndex] = pageArgs.split('/') as [string, string, string, string];
|
|
143
137
|
const params = new URLSearchParams(search);
|
|
144
138
|
const pathID = params.get('pathID');
|
|
145
|
-
const query = {};
|
|
139
|
+
const query: Record<string, any> = {};
|
|
146
140
|
// Create a query to replace the placeholders.
|
|
147
141
|
await populateQuery(issueID, timeStamp, jobID, catalogIndex, pathID, query);
|
|
148
142
|
// If this failed:
|
|
@@ -155,12 +149,8 @@ exports.answer = async (pageArgs, search) => {
|
|
|
155
149
|
}
|
|
156
150
|
// Otherwise, if it succeeded and the report facts were obtained:
|
|
157
151
|
if (query.testInfo) {
|
|
158
|
-
// Get the template.
|
|
159
|
-
|
|
160
|
-
// Replace its placeholders.
|
|
161
|
-
Object.keys(query).forEach(param => {
|
|
162
|
-
answerPage = answerPage.replace(new RegExp(`__${param}__`, 'g'), query[param]);
|
|
163
|
-
});
|
|
152
|
+
// Get the populated template.
|
|
153
|
+
const answerPage = await populateTemplate(import.meta.dirname, query);
|
|
164
154
|
// Return the populated page.
|
|
165
155
|
return {
|
|
166
156
|
status: 'ok',
|
|
@@ -1,46 +1,49 @@
|
|
|
1
1
|
/*
|
|
2
|
-
listDiagnoses.test.
|
|
3
|
-
UI tests for web/listDiagnoses/index.
|
|
2
|
+
listDiagnoses.test.ts
|
|
3
|
+
UI tests for web/listDiagnoses/index.ts using the fixture corpus.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
const realGetReport = util.getReport;
|
|
18
|
-
const realGetPageDataStrings = util.getPageDataStrings;
|
|
8
|
+
import {test, before, after, mock} from 'node:test';
|
|
9
|
+
import assert from 'node:assert/strict';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import fs from 'node:fs/promises';
|
|
12
|
+
import {parse} from 'node-html-parser';
|
|
13
|
+
|
|
14
|
+
// Mock the util functions called by index.ts before requiring it, so that
|
|
15
|
+
// it imports the mocked versions. Other exports delegate to the real module.
|
|
16
|
+
import * as realUtil from '../../util.ts';
|
|
19
17
|
let getReportCallCount = 0;
|
|
20
18
|
let failGetReportOnCall = -1;
|
|
21
|
-
let pageDataStringsOverride = null;
|
|
22
|
-
util.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
19
|
+
let pageDataStringsOverride: any = null;
|
|
20
|
+
mock.module('../../util.ts', {
|
|
21
|
+
exports: {
|
|
22
|
+
...realUtil,
|
|
23
|
+
getReport: async (...args: any[]) => {
|
|
24
|
+
getReportCallCount++;
|
|
25
|
+
if (getReportCallCount === failGetReportOnCall) {
|
|
26
|
+
return {error: 'Report is invalid (test override)'};
|
|
27
|
+
}
|
|
28
|
+
return (realUtil.getReport as any)(...args);
|
|
29
|
+
},
|
|
30
|
+
getPageDataStrings: async (...args: any[]) => {
|
|
31
|
+
if (pageDataStringsOverride !== null) {
|
|
32
|
+
return pageDataStringsOverride;
|
|
33
|
+
}
|
|
34
|
+
return (realUtil.getPageDataStrings as any)(...args);
|
|
35
|
+
}
|
|
32
36
|
}
|
|
33
|
-
|
|
34
|
-
};
|
|
37
|
+
} as any);
|
|
35
38
|
|
|
36
|
-
const {answer} =
|
|
39
|
+
const {answer} = await import('./index.ts') as any;
|
|
37
40
|
|
|
38
41
|
// SETUP AND TEARDOWN
|
|
39
42
|
|
|
40
43
|
const savedDBDir = process.env.DB_DIR;
|
|
41
44
|
|
|
42
|
-
before(() => {
|
|
43
|
-
process.env.DB_DIR =
|
|
45
|
+
before(async () => {
|
|
46
|
+
process.env.DB_DIR = (await import('../../test/dbFixture.ts')).fixtureDBDir;
|
|
44
47
|
});
|
|
45
48
|
|
|
46
49
|
after(() => {
|
|
@@ -77,11 +80,13 @@ test('listDiagnoses includes diagnosis descriptions from the rule engines', asyn
|
|
|
77
80
|
assert.ok(result.answerPage.includes('The link does not have an accessible name'));
|
|
78
81
|
});
|
|
79
82
|
|
|
80
|
-
test('listDiagnoses
|
|
81
|
-
const result = await answer('
|
|
82
|
-
assert.equal(result.status, '
|
|
83
|
+
test('listDiagnoses excludes cantTell instances from the diagnoses list', async () => {
|
|
84
|
+
const result = await answer('focusIndicationBad/260101T0000/mix/0');
|
|
85
|
+
assert.equal(result.status, 'ok');
|
|
86
|
+
assert.ok(!result.answerPage.includes('Focus Visible'));
|
|
83
87
|
});
|
|
84
88
|
|
|
89
|
+
|
|
85
90
|
test('listDiagnoses returns an error status for a nonexistent report', async () => {
|
|
86
91
|
const result = await answer('linkNoText/999999T9999/xyz/0');
|
|
87
92
|
assert.equal(result.status, 'error');
|
|
@@ -111,7 +116,7 @@ test('listDiagnoses returns an error when getReport fails after getPageDataStrin
|
|
|
111
116
|
});
|
|
112
117
|
|
|
113
118
|
test('listDiagnoses includes a take-me-there link for a text-linkable catalog item', async () => {
|
|
114
|
-
const dbDir =
|
|
119
|
+
const dbDir = (await import('../../test/dbFixture.ts')).fixtureDBDir;
|
|
115
120
|
const reportsDir = path.join(dbDir, 'reports');
|
|
116
121
|
const reportPath = path.join(reportsDir, '260101T0003-tlk.json');
|
|
117
122
|
try {
|
|
@@ -193,7 +198,7 @@ test('listDiagnoses returns an error when report facts are not obtained', async
|
|
|
193
198
|
|
|
194
199
|
test('listDiagnoses handles acts with no standardResult instances gracefully', async () => {
|
|
195
200
|
// Create a temporary report where an act has no standardResult.
|
|
196
|
-
const dbDir =
|
|
201
|
+
const dbDir = (await import('../../test/dbFixture.ts')).fixtureDBDir;
|
|
197
202
|
const reportPath = path.join(dbDir, 'reports', '260101T0004-nst.json');
|
|
198
203
|
const report = {
|
|
199
204
|
id: '260101T0004-nst',
|
|
@@ -1,48 +1,42 @@
|
|
|
1
1
|
/*
|
|
2
|
-
index.
|
|
2
|
+
index.ts
|
|
3
3
|
List the issues in a report.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
import {
|
|
9
9
|
getPageData,
|
|
10
10
|
getPageDataStrings,
|
|
11
11
|
getReport,
|
|
12
|
-
|
|
12
|
+
getEngineNamesString,
|
|
13
|
+
getTestActInstances,
|
|
13
14
|
getWCAGLink,
|
|
14
15
|
getWeightName,
|
|
15
16
|
htmlSafe,
|
|
16
|
-
|
|
17
|
-
isValidReport,
|
|
17
|
+
isUsableReport,
|
|
18
18
|
objectSort,
|
|
19
|
+
populateTemplate,
|
|
19
20
|
ruleEngines
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const fs = require('fs/promises');
|
|
23
|
-
const path = require('path');
|
|
21
|
+
} from '../../util.ts';
|
|
22
|
+
import {issues as issueSpecs} from 'testaro-issues';
|
|
24
23
|
|
|
25
24
|
// FUNCTIONS
|
|
26
25
|
|
|
27
26
|
// Returns data on the issues reported by a report.
|
|
28
|
-
const getIssuesData = async (timeStamp, jobID) => {
|
|
27
|
+
const getIssuesData = async (timeStamp: string, jobID: string) => {
|
|
29
28
|
// Get the report.
|
|
30
29
|
const report = await getReport(timeStamp, jobID);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (reportIsHidden) {
|
|
34
|
-
return {error: 'Report is not available'}
|
|
35
|
-
}
|
|
36
|
-
// Otherwise, if it exists and is valid:
|
|
37
|
-
if (isValidReport(report)) {
|
|
30
|
+
// If it exists and is valid:
|
|
31
|
+
if (isUsableReport(report)) {
|
|
38
32
|
// Initialize the temporary data.
|
|
39
33
|
const temp = {
|
|
40
|
-
issues: {},
|
|
41
|
-
reporters: new Set(),
|
|
42
|
-
violators: new Set()
|
|
34
|
+
issues: {} as Record<string, any>,
|
|
35
|
+
reporters: new Set<string>(),
|
|
36
|
+
violators: new Set<string>()
|
|
43
37
|
};
|
|
44
38
|
// Initialize the final data.
|
|
45
|
-
const final = {
|
|
39
|
+
const final: Record<string, any> = {
|
|
46
40
|
reporters: [],
|
|
47
41
|
reporterList: '',
|
|
48
42
|
reporterCount: 0,
|
|
@@ -56,60 +50,53 @@ const getIssuesData = async (timeStamp, jobID) => {
|
|
|
56
50
|
},
|
|
57
51
|
issueCount: 0
|
|
58
52
|
};
|
|
59
|
-
// For each
|
|
60
|
-
report.
|
|
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
|
-
// If the instance has a catalog index:
|
|
89
|
-
if (catalogIndex) {
|
|
90
|
-
// Ensure the violator is in the temporary data.
|
|
91
|
-
temp.issues[issueID].violators.add(catalogIndex);
|
|
92
|
-
temp.violators.add(catalogIndex);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
53
|
+
// For each violating standard instance of each test act:
|
|
54
|
+
getTestActInstances(report, {violationsOnly: true}).forEach(({act, instance}) => {
|
|
55
|
+
const {catalogIndex, issueID} = instance;
|
|
56
|
+
const which = act.which!;
|
|
57
|
+
// If it identifies a non-ignorable issue:
|
|
58
|
+
if (issueID && issueID !== 'ignorable') {
|
|
59
|
+
const issueClassification = issueSpecs[issueID];
|
|
60
|
+
// If the issue has a current weighted classification:
|
|
61
|
+
if (issueClassification && [1, 2, 3, 4].includes(issueClassification.weight)) {
|
|
62
|
+
const {summary, wcag, weight, why} = issueClassification;
|
|
63
|
+
// Initialize the temporary data on the issue if necessary.
|
|
64
|
+
temp.issues[issueID] ??= {
|
|
65
|
+
issueID,
|
|
66
|
+
summary,
|
|
67
|
+
wcag,
|
|
68
|
+
why,
|
|
69
|
+
weight,
|
|
70
|
+
reporters: new Set(),
|
|
71
|
+
reporterList: '',
|
|
72
|
+
violators: new Set()
|
|
73
|
+
};
|
|
74
|
+
// Ensure the rule engine is in the temporary data.
|
|
75
|
+
temp.issues[issueID].reporters.add(which);
|
|
76
|
+
temp.reporters.add(which);
|
|
77
|
+
// If the instance has a catalog index:
|
|
78
|
+
if (catalogIndex) {
|
|
79
|
+
// Ensure the violator is in the temporary data.
|
|
80
|
+
temp.issues[issueID].violators.add(catalogIndex);
|
|
81
|
+
temp.violators.add(String(catalogIndex));
|
|
95
82
|
}
|
|
96
|
-
}
|
|
83
|
+
}
|
|
97
84
|
}
|
|
98
85
|
});
|
|
99
86
|
// Finish populating the final data.
|
|
100
|
-
final.reporterList =
|
|
87
|
+
final.reporterList = getEngineNamesString(temp.reporters);
|
|
101
88
|
final.reporterCount = temp.reporters.size;
|
|
102
89
|
final.violatorCount = temp.violators.size;
|
|
103
90
|
Object.values(temp.issues).forEach(issue => {
|
|
104
91
|
const {issueID, summary, wcag, why, weight} = issue;
|
|
105
|
-
const finalIssue = {
|
|
92
|
+
const finalIssue: Record<string, any> = {
|
|
106
93
|
issueID,
|
|
107
94
|
summary,
|
|
108
95
|
wcag,
|
|
109
96
|
why,
|
|
110
97
|
weight
|
|
111
98
|
};
|
|
112
|
-
finalIssue.reporterList =
|
|
99
|
+
finalIssue.reporterList = getEngineNamesString(issue.reporters);
|
|
113
100
|
finalIssue.reporterCount = issue.reporters.size;
|
|
114
101
|
finalIssue.violatorCount = issue.violators.size;
|
|
115
102
|
final.issues[issue.weight].push(finalIssue);
|
|
@@ -129,10 +116,10 @@ const getIssuesData = async (timeStamp, jobID) => {
|
|
|
129
116
|
return {error: 'Report missing or invalid.'};
|
|
130
117
|
};
|
|
131
118
|
// Get page and issues data from a report.
|
|
132
|
-
const getData = async (timeStamp, jobID) => {
|
|
119
|
+
const getData = async (timeStamp: string, jobID: string) => {
|
|
133
120
|
const pageData = await getPageData(timeStamp, jobID);
|
|
134
|
-
const issuesData = await getIssuesData(timeStamp, jobID);
|
|
135
|
-
const pageError = pageData.error
|
|
121
|
+
const issuesData: any = await getIssuesData(timeStamp, jobID);
|
|
122
|
+
const pageError = ('error' in pageData ? pageData.error : '');
|
|
136
123
|
const issuesError = issuesData.error || '';
|
|
137
124
|
const errors = [pageError, issuesError].filter(Boolean).join('; ');
|
|
138
125
|
// If the data of either type are missing or invalid:
|
|
@@ -147,9 +134,9 @@ const getData = async (timeStamp, jobID) => {
|
|
|
147
134
|
};
|
|
148
135
|
};
|
|
149
136
|
// Adds parameters to a query for the answer page.
|
|
150
|
-
const populateQuery = async (timeStamp, jobID, query) => {
|
|
137
|
+
const populateQuery = async (timeStamp: string, jobID: string, query: Record<string, any>) => {
|
|
151
138
|
// Get data on the target and its issues according to the report.
|
|
152
|
-
const data = await getData(timeStamp, jobID);
|
|
139
|
+
const data: any = await getData(timeStamp, jobID);
|
|
153
140
|
const {pageData, issuesData} = data;
|
|
154
141
|
// If this failed:
|
|
155
142
|
if (data.error) {
|
|
@@ -160,16 +147,16 @@ const populateQuery = async (timeStamp, jobID, query) => {
|
|
|
160
147
|
}
|
|
161
148
|
// Otherwise, i.e. if it succeeded, get fact descriptions for the target.
|
|
162
149
|
const pageInfo = await getPageDataStrings(timeStamp, jobID, pageData);
|
|
163
|
-
const {testInfo, urlLink, what} = pageInfo;
|
|
164
150
|
// If this failed:
|
|
165
|
-
if (pageInfo.error) {
|
|
151
|
+
if (pageInfo.error !== undefined) {
|
|
166
152
|
// Populate the query with the reason.
|
|
167
153
|
query.error = pageInfo.error;
|
|
168
154
|
// Stop populating the query.
|
|
169
155
|
return;
|
|
170
156
|
}
|
|
157
|
+
const {testInfo, urlLink, description} = pageInfo;
|
|
171
158
|
// Otherwise, i.e. if it succeeded, add target data to the query.
|
|
172
|
-
query.target =
|
|
159
|
+
query.target = description;
|
|
173
160
|
query.urlLink = urlLink;
|
|
174
161
|
query.testInfo = testInfo;
|
|
175
162
|
const {
|
|
@@ -181,11 +168,11 @@ const populateQuery = async (timeStamp, jobID, query) => {
|
|
|
181
168
|
issues
|
|
182
169
|
} = issuesData;
|
|
183
170
|
// Initialize strings for the prevention notices query property.
|
|
184
|
-
const preventionStrings = [];
|
|
171
|
+
const preventionStrings: string[] = [];
|
|
185
172
|
const margin = ' '.repeat(6);
|
|
186
173
|
Object.keys(preventions).forEach(preventedEngineID => {
|
|
187
|
-
const engineName = ruleEngines[preventedEngineID];
|
|
188
|
-
const engineNameString = `${engineName
|
|
174
|
+
const [engineName, engineSponsor] = ruleEngines[preventedEngineID] ?? [preventedEngineID, 'unknown sponsor'];
|
|
175
|
+
const engineNameString = `${engineName} (${engineSponsor})`;
|
|
189
176
|
const causeString = htmlSafe(preventions[preventedEngineID]);
|
|
190
177
|
const preventionString = `${margin}<li>Page not testable by ${engineNameString}: ${causeString}</li>`;
|
|
191
178
|
preventionStrings.push(preventionString);
|
|
@@ -213,9 +200,9 @@ const populateQuery = async (timeStamp, jobID, query) => {
|
|
|
213
200
|
// If any reported issues have the weight:
|
|
214
201
|
if (weightIssues.length) {
|
|
215
202
|
// Initialize lines for the weight details query property.
|
|
216
|
-
const detailsLines = [];
|
|
203
|
+
const detailsLines: string[] = [];
|
|
217
204
|
// For each issue with the weight:
|
|
218
|
-
weightIssues.forEach(issueData => {
|
|
205
|
+
weightIssues.forEach((issueData: any) => {
|
|
219
206
|
const weightIssueCount = weightIssues.length;
|
|
220
207
|
// Add the issue count to the query.
|
|
221
208
|
query[`${weightName}Count`] = weightIssueCount;
|
|
@@ -251,7 +238,7 @@ const populateQuery = async (timeStamp, jobID, query) => {
|
|
|
251
238
|
// Add the start of a link list to the lines.
|
|
252
239
|
detailsLines.push(`${margin} <ul class="nav">`);
|
|
253
240
|
const whereQuestionString = 'Where was the issue found?';
|
|
254
|
-
const labelString = `Where was the ${summary} issue found on the ${
|
|
241
|
+
const labelString = `Where was the ${summary} issue found on the ${description} page?`;
|
|
255
242
|
const href = `href="/listViolators.html/${issueID}/${timeStamp}/${jobID}"`;
|
|
256
243
|
const label = `aria-label="${labelString}"`;
|
|
257
244
|
const whereLink = `<a ${href} ${label}>${whereQuestionString}</a>`;
|
|
@@ -272,17 +259,9 @@ const populateQuery = async (timeStamp, jobID, query) => {
|
|
|
272
259
|
});
|
|
273
260
|
};
|
|
274
261
|
// Returns a page answering the target-issues question.
|
|
275
|
-
|
|
276
|
-
const [timeStamp, jobID] = pageArgs.split('/');
|
|
277
|
-
const
|
|
278
|
-
// If the report is not available:
|
|
279
|
-
if (reportIsHidden) {
|
|
280
|
-
return {
|
|
281
|
-
status: 'error',
|
|
282
|
-
message: 'Report not available'
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
const query = {};
|
|
262
|
+
export const answer = async (pageArgs: string) => {
|
|
263
|
+
const [timeStamp, jobID] = pageArgs.split('/') as [string, string];
|
|
264
|
+
const query: Record<string, any> = {};
|
|
286
265
|
// Create a query to replace the placeholders.
|
|
287
266
|
await populateQuery(timeStamp, jobID, query);
|
|
288
267
|
// If this failed:
|
|
@@ -295,12 +274,8 @@ exports.answer = async pageArgs => {
|
|
|
295
274
|
}
|
|
296
275
|
// Otherwise, if it succeeded and the report facts were obtained:
|
|
297
276
|
if (query.testInfo) {
|
|
298
|
-
// Get the template.
|
|
299
|
-
|
|
300
|
-
// Replace its placeholders.
|
|
301
|
-
Object.keys(query).forEach(param => {
|
|
302
|
-
answerPage = answerPage.replace(new RegExp(`__${param}__`, 'g'), query[param]);
|
|
303
|
-
});
|
|
277
|
+
// Get the populated template.
|
|
278
|
+
const answerPage = await populateTemplate(import.meta.dirname, query);
|
|
304
279
|
// Return the populated page.
|
|
305
280
|
return {
|
|
306
281
|
status: 'ok',
|