@jrpool/kilotest 57.3.0 → 58.2.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/api/orderNewTest.test.ts +1 -1
- package/api/orderNewTest.ts +1 -1
- package/api/requestNewTest.test.ts +1 -1
- package/api/requestNewTest.ts +1 -1
- package/index.test.ts +1 -1
- package/index.ts +11 -20
- package/package.json +1 -1
- package/test/fixtures/buildFixtures.ts +10 -1
- package/test/fixtures/db/reports/260101T0006-prv.json +10 -0
- package/util.test.ts +46 -0
- package/util.ts +16 -18
- package/web/listDiagnoses/index.html +6 -19
- package/web/listDiagnoses/index.ts +21 -43
- package/web/listDiagnoses/listDiagnoses.test.ts +14 -2
- package/web/listIssues/index.html +12 -12
- package/web/listIssues/index.ts +112 -168
- package/web/listIssues/listIssues.test.ts +33 -10
- package/web/listReports/index.ts +18 -50
- package/web/listReports/listReports.test.ts +31 -22
- package/web/listViolators/index.html +4 -13
- package/web/listViolators/index.ts +30 -64
- package/web/listViolators/listViolators.test.ts +16 -4
- package/web/reportDeletion.ts +8 -4
- package/web/requestNewTest/index.ts +2 -2
- package/web/requestNewTest/requestNewTest.test.ts +1 -1
- package/web/tutorialAI/index.html +6 -6
- package/web/util.test.ts +123 -27
- package/web/util.ts +159 -46
|
@@ -8,18 +8,17 @@
|
|
|
8
8
|
import {
|
|
9
9
|
getReport,
|
|
10
10
|
getTestActInstances,
|
|
11
|
-
htmlSafe,
|
|
12
11
|
isReportError,
|
|
13
12
|
populateTemplate
|
|
14
13
|
} from '../../util.ts';
|
|
15
14
|
import {
|
|
15
|
+
getIssueFactsLines,
|
|
16
16
|
getPageDataStrings,
|
|
17
|
+
getPageFactsLines,
|
|
17
18
|
getPathID,
|
|
18
|
-
|
|
19
|
+
getReportData,
|
|
19
20
|
getTextFragmentHref,
|
|
20
|
-
|
|
21
|
-
getWeightName,
|
|
22
|
-
makeBreakable
|
|
21
|
+
getViolatorFactsLines
|
|
23
22
|
} from '../util.ts';
|
|
24
23
|
import {issues as issueSpecs} from 'testaro-issues';
|
|
25
24
|
|
|
@@ -32,8 +31,17 @@ const populateQuery = async (
|
|
|
32
31
|
jobID: string,
|
|
33
32
|
query: Record<string, any>
|
|
34
33
|
) => {
|
|
35
|
-
// Get
|
|
36
|
-
const
|
|
34
|
+
// Get the report.
|
|
35
|
+
const report = await getReport(timeStamp, jobID);
|
|
36
|
+
// If this failed:
|
|
37
|
+
if (isReportError(report)) {
|
|
38
|
+
// Populate the query with the reason.
|
|
39
|
+
query.error = report.error;
|
|
40
|
+
// Stop populating the query.
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
// Otherwise, i.e. if it succeeded, get descriptions of the page facts.
|
|
44
|
+
const pageDataStrings = getPageDataStrings(report);
|
|
37
45
|
// If this failed:
|
|
38
46
|
if (pageDataStrings.error !== undefined) {
|
|
39
47
|
// Populate the query with the reason.
|
|
@@ -50,58 +58,28 @@ const populateQuery = async (
|
|
|
50
58
|
// Stop populating the query.
|
|
51
59
|
return;
|
|
52
60
|
}
|
|
53
|
-
const {testInfo, url,
|
|
61
|
+
const {testInfo, url, description} = pageDataStrings;
|
|
54
62
|
// Add page facts to the query.
|
|
55
63
|
query.target = description;
|
|
56
|
-
query.urlLink = urlLink;
|
|
57
64
|
query.testInfo = testInfo;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
query.
|
|
62
|
-
query.
|
|
63
|
-
// Initialize
|
|
64
|
-
query.count = 0;
|
|
65
|
-
query.reporters = new Set();
|
|
65
|
+
query.pageFacts = getPageFactsLines(
|
|
66
|
+
pageDataStrings, getReportData(report), ' '.repeat(6)
|
|
67
|
+
).join('\n');
|
|
68
|
+
// Add the issue facts to the query.
|
|
69
|
+
query.issueFacts = getIssueFactsLines(report, issueID, ' '.repeat(6)).join('\n');
|
|
70
|
+
// Initialize the violator data.
|
|
66
71
|
let violators: any = {};
|
|
67
|
-
// Get the report.
|
|
68
|
-
const report = await getReport(timeStamp, jobID);
|
|
69
|
-
// If this failed:
|
|
70
|
-
if (isReportError(report)) {
|
|
71
|
-
// Populate the query with the reason.
|
|
72
|
-
query.error = report.error;
|
|
73
|
-
// Stop populating the query.
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
72
|
const {catalog} = report;
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
const testActInstances = getTestActInstances(report, {violationsOnly: true, issueID});
|
|
74
|
+
// For each violating standard instance of the issue:
|
|
75
|
+
testActInstances.forEach(({instance}) => {
|
|
79
76
|
const pathID = instance.pathID || '/html';
|
|
80
77
|
const catalogIndex = String(instance.catalogIndex || '0');
|
|
81
|
-
|
|
82
|
-
?? pathID.split('/').pop()!.replace(/\[.+$/, '').toUpperCase();
|
|
78
|
+
// Initialize the violator data if necessary.
|
|
83
79
|
violators[catalogIndex] ??= {
|
|
84
|
-
pathID: getPathID(catalog, catalogIndex, pathID)
|
|
85
|
-
tagName,
|
|
86
|
-
text: catalog[catalogIndex]?.text ?? '',
|
|
87
|
-
reporters: new Set()
|
|
80
|
+
pathID: getPathID(catalog, catalogIndex, pathID)
|
|
88
81
|
};
|
|
89
|
-
// Ensure that the rule engine is in the sets of reporters of the violator and the issue.
|
|
90
|
-
violators[catalogIndex].reporters.add(act.which);
|
|
91
|
-
query.reporters.add(act.which);
|
|
92
82
|
});
|
|
93
|
-
// Populate the violator count.
|
|
94
|
-
const violatorCount = Object.keys(violators).length;
|
|
95
|
-
query.violatorCount = violatorCount === 1 ? '1 violator was' : `${violatorCount} violators were`;
|
|
96
|
-
// For each violator:
|
|
97
|
-
Object.values(violators).forEach((violatorData: any) => {
|
|
98
|
-
// Convert the set of its reporters to a string.
|
|
99
|
-
violatorData.reporters = getEngineNamesString(violatorData.reporters);
|
|
100
|
-
});
|
|
101
|
-
const reporterCount = query.reporters.size;
|
|
102
|
-
query.reporterCount = reporterCount === 1 ? '1 rule engine' : `${reporterCount} rule engines`;
|
|
103
|
-
// Convert the set of issue reporters to a string.
|
|
104
|
-
query.reporters = getEngineNamesString(query.reporters);
|
|
105
83
|
// Convert the violator data to an array.
|
|
106
84
|
violators = Object.entries(violators).map((entry: [string, any]) => ({
|
|
107
85
|
catalogIndex: entry[0],
|
|
@@ -115,23 +93,11 @@ const populateQuery = async (
|
|
|
115
93
|
let takeMeAdviceNeeded = false;
|
|
116
94
|
// For each violator:
|
|
117
95
|
violators.forEach((violator: any, index: number) => {
|
|
118
|
-
const {catalogIndex, pathID
|
|
96
|
+
const {catalogIndex, pathID} = violator;
|
|
119
97
|
// Add a heading to the lines.
|
|
120
98
|
lines.push(`${margin}<li><h3>Element ${catalogIndex}</h3>`);
|
|
121
|
-
lines.
|
|
122
|
-
|
|
123
|
-
if (pathID) {
|
|
124
|
-
lines.push(`${margin} <li>XPath: <code>${makeBreakable(pathID)}</code></li>`);
|
|
125
|
-
}
|
|
126
|
-
if (tagName) {
|
|
127
|
-
lines.push(`${margin} <li>Tag name: <code>${tagName}</code></li>`);
|
|
128
|
-
}
|
|
129
|
-
if (text && !['HTML', 'HEAD', 'BODY', 'MAIN', 'NOSCRIPT'].includes(tagName)) {
|
|
130
|
-
const textString = text.split('\n').join(' … ');
|
|
131
|
-
lines.push(`${margin} <li>Text: <q>${htmlSafe(textString)}</q></li>`);
|
|
132
|
-
}
|
|
133
|
-
lines.push(`${margin} <li>Reported by ${reporters}</li>`);
|
|
134
|
-
lines.push(`${margin} </ul>`);
|
|
99
|
+
// Add the violator facts to the lines.
|
|
100
|
+
lines.push(...getViolatorFactsLines(report, issueID, catalogIndex, pathID, `${margin} `, 'pseudoTopLevel'));
|
|
135
101
|
lines.push(`${margin} <ul class="nav">`);
|
|
136
102
|
if (catalogIndex) {
|
|
137
103
|
const catalogItem = catalog[catalogIndex];
|
|
@@ -33,7 +33,7 @@ mock.module('../../util.ts', {
|
|
|
33
33
|
mock.module('../util.ts', {
|
|
34
34
|
exports: {
|
|
35
35
|
...realWebUtil,
|
|
36
|
-
getPageDataStrings:
|
|
36
|
+
getPageDataStrings: (...args: any[]) => {
|
|
37
37
|
if (pageDataStringsOverride !== null) {
|
|
38
38
|
return pageDataStringsOverride;
|
|
39
39
|
}
|
|
@@ -86,7 +86,7 @@ test('listViolators includes links to listDiagnoses for violators', async () =>
|
|
|
86
86
|
test('listViolators excludes cantTell instances from the violator count', async () => {
|
|
87
87
|
const result = await answer('focusIndicationBad/260101T0000/mix');
|
|
88
88
|
assert.equal(result.status, 'ok');
|
|
89
|
-
assert.ok(result.answerPage.includes('0
|
|
89
|
+
assert.ok(result.answerPage.includes('<li>Violators: 0</li>'));
|
|
90
90
|
assert.ok(!result.answerPage.includes('<h3>Element'));
|
|
91
91
|
});
|
|
92
92
|
|
|
@@ -101,7 +101,7 @@ test('listViolators returns an error status for an unknown issue', async () => {
|
|
|
101
101
|
assert.equal(result.status, 'error');
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
-
test('listViolators returns an error when getReport fails
|
|
104
|
+
test('listViolators returns an error when getReport fails', async () => {
|
|
105
105
|
getReportCallCount = 0;
|
|
106
106
|
failGetReportOnCall = 1;
|
|
107
107
|
try {
|
|
@@ -114,6 +114,18 @@ test('listViolators returns an error when getReport fails after getPageDataStrin
|
|
|
114
114
|
}
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
+
test('listViolators returns an error when getPageDataStrings fails after getReport succeeds', async () => {
|
|
118
|
+
pageDataStringsOverride = {error: 'Page data strings error'};
|
|
119
|
+
try {
|
|
120
|
+
const result = await answer('linkNoText/260101T0000/mix');
|
|
121
|
+
assert.equal(result.status, 'error');
|
|
122
|
+
assert.equal(result.message, 'Page data strings error');
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
pageDataStringsOverride = null;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
117
129
|
test('listViolators includes take-me-there links for text-linkable violators', async () => {
|
|
118
130
|
const dbDir = (await import('../../test/dbFixture.ts')).fixtureDBDir;
|
|
119
131
|
const reportsDir = path.join(dbDir, 'reports');
|
|
@@ -260,7 +272,7 @@ test('listViolators handles instances with missing catalogIndex and acts with no
|
|
|
260
272
|
const result = await answer('linkNoText/260101T0004/nvi');
|
|
261
273
|
assert.equal(result.status, 'ok');
|
|
262
274
|
// Should show plural violator count.
|
|
263
|
-
assert.ok(result.answerPage.includes('2
|
|
275
|
+
assert.ok(result.answerPage.includes('<li>Violators: 2</li>'));
|
|
264
276
|
}
|
|
265
277
|
finally {
|
|
266
278
|
await fs.unlink(reportPath).catch(() => {});
|
package/web/reportDeletion.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
errorMessage,
|
|
10
|
+
getReport,
|
|
11
|
+
isReportError,
|
|
10
12
|
isValidAuthCode,
|
|
11
13
|
objectSort,
|
|
12
14
|
populateTemplate,
|
|
@@ -90,16 +92,18 @@ export const reportDeletionForm = async (
|
|
|
90
92
|
// For each report:
|
|
91
93
|
for (const reportName of reportNames) {
|
|
92
94
|
const [timeStamp, jobID] = reportName.slice(0, -5).split('-') as [string, string];
|
|
93
|
-
// Get
|
|
94
|
-
const
|
|
95
|
+
// Get the report.
|
|
96
|
+
const report = await getReport(timeStamp, jobID);
|
|
95
97
|
// If this failed:
|
|
96
|
-
if (
|
|
98
|
+
if (isReportError(report)) {
|
|
97
99
|
// Return why.
|
|
98
100
|
return {
|
|
99
101
|
status: 'error',
|
|
100
|
-
message:
|
|
102
|
+
message: report.error
|
|
101
103
|
};
|
|
102
104
|
}
|
|
105
|
+
// Otherwise, i.e. if it succeeded, get a summary of it.
|
|
106
|
+
const reportFacts = getReportData(report);
|
|
103
107
|
const {issueCount, preventedEngineCount, url} = reportFacts;
|
|
104
108
|
// Otherwise, i.e. if it succeeded, add the summary to the array.
|
|
105
109
|
reportSpecs.push({
|
|
@@ -33,12 +33,12 @@ export const answer = async (description: string, url: string, reason: string) =
|
|
|
33
33
|
message: 'Test request duplicates an already submitted request'
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
-
// Otherwise, if a report is available about a page with the same description
|
|
36
|
+
// Otherwise, if a report is available about a page with the same description or URL:
|
|
37
37
|
else if (result === 'reportExists') {
|
|
38
38
|
// Report this.
|
|
39
39
|
return {
|
|
40
40
|
status: 'error',
|
|
41
|
-
message: 'A report about
|
|
41
|
+
message: 'A report about a page with the same description or URL is already available'
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
// Otherwise, if the queue of requests awaiting approval is full:
|
|
@@ -126,7 +126,7 @@ test('answer returns an error when a report already exists for the page', {timeo
|
|
|
126
126
|
'Mixed Outcomes Page', 'https://example.com/mixed', 'Because accessibility'
|
|
127
127
|
);
|
|
128
128
|
assert.equal(result.status, 'error');
|
|
129
|
-
assert.equal(result.message, 'A report about
|
|
129
|
+
assert.equal(result.message, 'A report about a page with the same description or URL is already available');
|
|
130
130
|
});
|
|
131
131
|
|
|
132
132
|
test('answer returns an error with a fallback channel when the request queue is full', {timeout: 500}, async () => {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<main>
|
|
16
16
|
<h1><a href="/">Kilotest</a>: Connect an AI Platform</h1>
|
|
17
17
|
<h2>Purpose</h2>
|
|
18
|
-
<p>This tutorial shows you how to make Kilotest available to
|
|
18
|
+
<p>This tutorial shows you how to make Kilotest available to assistants powered by artificial intelligence. Suppose:</p>
|
|
19
19
|
<ul>
|
|
20
20
|
<li>You use an AI platform, like Claude, Mistral Vibe, or Perplexity Pro.</li>
|
|
21
21
|
<li>You want to audit the quality of a web page.</li>
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
<li><a href="#claude">Claude.ai in a browser or Claude Desktop</a></li>
|
|
27
27
|
<li><a href="#vibe">Mistral Vibe</a></li>
|
|
28
28
|
<li><a href="#perplexity">Perplexity Pro in a browser</a></li>
|
|
29
|
+
<li><a href="#other">Other platforms</a></li>
|
|
29
30
|
</ul>
|
|
30
31
|
<h2 id="claude"><a href="https://claude.ai">Claude.ai in a browser</a> or <a href="https://downloads.claude.com">Claude Desktop</a></h2>
|
|
31
32
|
<ol>
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
<li>Click the <q>Add connector</q> button.</li>
|
|
53
54
|
<li>Toggle the <q>Ask/Allow</q> buttons to show <q>Allow</q>.</li>
|
|
54
55
|
</ol>
|
|
55
|
-
<h2 id="perplexity">Perplexity Pro in a browser</h2>
|
|
56
|
+
<h2 id="perplexity"><a href="https://www.perplexity.ai/">Perplexity</a> Pro in a browser</h2>
|
|
56
57
|
<ol>
|
|
57
58
|
<li>After signing in, click the <q>Customize</q> button in the left sidebar.</li>
|
|
58
59
|
<li>Click the <q>+ Custom connector</q> menu button.</li>
|
|
@@ -65,10 +66,9 @@
|
|
|
65
66
|
<li>Click the <q>Add</q> button.</li>
|
|
66
67
|
</ol>
|
|
67
68
|
<p>Once you add Kilotest, it <strong>may</strong> become available in the Perplexity app on your computer or mobile device, too.</p>
|
|
68
|
-
<h2>Other AI platforms</h2>
|
|
69
|
-
<p>You can connect Kilotest to some other AI platforms, too. The instructions above are limited to platforms that are either free or relatively inexpensive (US$20 per month or less).
|
|
70
|
-
<
|
|
71
|
-
<p>What if this did not work? Use the comment form below to tell us about it.</p>
|
|
69
|
+
<h2 id="other">Other AI platforms</h2>
|
|
70
|
+
<p>You can connect Kilotest to some other AI platforms, too. The instructions above are limited to consumer platforms that are either free or relatively inexpensive (US$20 per month or less). Business platforms such as ChatGPT Business, and developer platforms such as Devin Desktop, also permit you to enable Kilotest.</p>
|
|
71
|
+
<p>Feel free to submit a comment below about empowering any AI platform to use Kilotest.</p>
|
|
72
72
|
<p>You can also submit an issue on the <a href="https://github.com/jrpool/kilotest/issues">Kilotest GitHub repository</a>.</p>
|
|
73
73
|
<section id="suggest-improvement" class="main">
|
|
74
74
|
<h2>Suggest improvements</h2>
|
package/web/util.test.ts
CHANGED
|
@@ -9,15 +9,17 @@ import {test, before, after} from 'node:test';
|
|
|
9
9
|
import assert from 'node:assert/strict';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import fs from 'node:fs/promises';
|
|
12
|
-
import {reportsPath} from '../util.ts';
|
|
12
|
+
import {getReport, isReportError, reportsPath} from '../util.ts';
|
|
13
13
|
import {
|
|
14
14
|
checkCommentLength,
|
|
15
15
|
getAgoString,
|
|
16
16
|
getCountString,
|
|
17
|
+
getEngineIDs,
|
|
17
18
|
getEngineNamesString,
|
|
18
19
|
getMultiReportWhats,
|
|
19
20
|
getPageData,
|
|
20
21
|
getPageDataStrings,
|
|
22
|
+
getPageFactsLines,
|
|
21
23
|
getTextFragmentHref,
|
|
22
24
|
getWCAGLink,
|
|
23
25
|
getWeightName,
|
|
@@ -115,65 +117,79 @@ test('makeBreakable inserts wbr before non-initial slashes', () => {
|
|
|
115
117
|
assert.equal(makeBreakable('/api/listReports'), '/api<wbr>/listReports');
|
|
116
118
|
});
|
|
117
119
|
|
|
120
|
+
// Returns a fixture report, failing the test if it is not usable.
|
|
121
|
+
const getFixtureReport = async (timeStamp: string, jobID: string) => {
|
|
122
|
+
const report = await getReport(timeStamp, jobID);
|
|
123
|
+
if (isReportError(report)) {
|
|
124
|
+
assert.fail(report.error);
|
|
125
|
+
}
|
|
126
|
+
return report;
|
|
127
|
+
};
|
|
128
|
+
|
|
118
129
|
test('getPageData returns page data for a valid report', async () => {
|
|
119
|
-
const data = await
|
|
130
|
+
const data = getPageData(await getFixtureReport('260101T0000', 'mix'));
|
|
120
131
|
assert.equal(data.description, 'Mixed Outcomes Page');
|
|
121
132
|
assert.equal(data.url, 'https://example.com/mixed');
|
|
122
133
|
assert.equal(typeof data.daysAgo, 'number');
|
|
123
134
|
});
|
|
124
135
|
|
|
125
|
-
test('
|
|
126
|
-
const
|
|
127
|
-
assert.ok(
|
|
136
|
+
test('getReport returns an error for a nonexistent report', async () => {
|
|
137
|
+
const report = await getReport('999999T9999', 'xxx');
|
|
138
|
+
assert.ok(isReportError(report));
|
|
128
139
|
});
|
|
129
140
|
|
|
130
141
|
test('getPageDataStrings returns HTML strings for a valid report', async () => {
|
|
131
|
-
const strings = await
|
|
142
|
+
const strings = getPageDataStrings(await getFixtureReport('260101T0000', 'mix'));
|
|
132
143
|
assert.equal(strings.description, 'Mixed Outcomes Page');
|
|
133
144
|
assert.equal(strings.url, 'https://example.com/mixed');
|
|
134
145
|
assert.equal(strings.urlLink, '<a href="https://example.com/mixed">https://example.com/mixed</a>');
|
|
135
|
-
assert.ok(strings.testInfo.includes('by job <code>mix</code>'));
|
|
146
|
+
assert.ok(strings.testInfo.includes('by job <code>260101T0000-mix</code>'));
|
|
136
147
|
assert.ok(strings.testInfo.includes('2026-01-01 at 00:00'));
|
|
137
148
|
});
|
|
138
149
|
|
|
139
150
|
test('getPageDataStrings returns different testInfo for a different timeStamp', async () => {
|
|
140
|
-
const strings = await
|
|
151
|
+
const strings = getPageDataStrings(await getFixtureReport('260101T0001', 'ct'));
|
|
141
152
|
assert.equal(strings.description, 'All CantTell Page');
|
|
142
|
-
assert.ok(strings.testInfo.includes('by job <code>ct</code>'));
|
|
153
|
+
assert.ok(strings.testInfo.includes('by job <code>260101T0001-ct</code>'));
|
|
143
154
|
assert.ok(strings.testInfo.includes('2026-01-01 at 00:01'));
|
|
144
155
|
});
|
|
145
156
|
|
|
146
|
-
test('getPageDataStrings
|
|
147
|
-
const
|
|
148
|
-
|
|
157
|
+
test('getPageDataStrings derives the strings from the report object', async () => {
|
|
158
|
+
const report = await getFixtureReport('260101T0000', 'mix');
|
|
159
|
+
report.target.what = 'Custom Page';
|
|
160
|
+
const strings = getPageDataStrings(report);
|
|
161
|
+
assert.equal(strings.description, 'Custom Page');
|
|
149
162
|
});
|
|
150
163
|
|
|
151
|
-
test('getPageDataStrings
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
164
|
+
test('getPageDataStrings reports a 1-day-old report as tested 1 day ago', () => {
|
|
165
|
+
// Construct an end time 1 day and 1 hour ago, so Math.round gives exactly 1.
|
|
166
|
+
const date = new Date(Date.now() - (86400000 + 3600000));
|
|
167
|
+
const endTime = date.toISOString().slice(2, 16);
|
|
168
|
+
const report = {
|
|
169
|
+
id: '260101T0000-mix',
|
|
170
|
+
target: {what: 'Some Page', url: 'https://example.com'},
|
|
171
|
+
jobData: {endTime}
|
|
172
|
+
} as any;
|
|
173
|
+
const strings = getPageDataStrings(report);
|
|
159
174
|
assert.ok(strings.testInfo.includes('1 day ago'));
|
|
160
175
|
});
|
|
161
176
|
|
|
162
|
-
test('getReportData returns
|
|
177
|
+
test('getReportData returns data for a valid report', async () => {
|
|
163
178
|
const {getReportData} = await import('./util.ts');
|
|
164
|
-
const result
|
|
165
|
-
assert.
|
|
179
|
+
const result = getReportData(await getFixtureReport('260101T0000', 'mix'));
|
|
180
|
+
assert.equal(result.url, 'https://example.com/mixed');
|
|
181
|
+
assert.equal(result.jobName, '260101T0000-mix');
|
|
166
182
|
});
|
|
167
183
|
|
|
168
184
|
test('getReportData falls back to the engine ID for an unknown prevented engine', async () => {
|
|
169
185
|
const {getReportData} = await import('./util.ts');
|
|
170
186
|
const prvJSON = await fs.readFile(path.join(reportsPath(), '260101T0006-prv.json'), 'utf8');
|
|
171
|
-
const
|
|
172
|
-
|
|
187
|
+
const reportJSON = JSON.parse(prvJSON);
|
|
188
|
+
reportJSON.jobData.preventions.unknownEngine = 'mystery failure';
|
|
173
189
|
const reportPath = path.join(reportsPath(), '260103T0000-unk.json');
|
|
174
|
-
await fs.writeFile(reportPath, JSON.stringify(
|
|
190
|
+
await fs.writeFile(reportPath, JSON.stringify(reportJSON));
|
|
175
191
|
try {
|
|
176
|
-
const result
|
|
192
|
+
const result = getReportData(await getFixtureReport('260103T0000', 'unk'));
|
|
177
193
|
assert.ok(result.preventedEngineNames.includes('unknownEngine'));
|
|
178
194
|
}
|
|
179
195
|
finally {
|
|
@@ -181,6 +197,44 @@ test('getReportData falls back to the engine ID for an unknown prevented engine'
|
|
|
181
197
|
}
|
|
182
198
|
});
|
|
183
199
|
|
|
200
|
+
test('getPageFactsLines returns the about-page list lines for a report', async () => {
|
|
201
|
+
const {getReportData} = await import('./util.ts');
|
|
202
|
+
const report = await getFixtureReport('260101T0000', 'mix');
|
|
203
|
+
const lines = getPageFactsLines(getPageDataStrings(report), getReportData(report), ' ');
|
|
204
|
+
const html = lines.join('\n');
|
|
205
|
+
assert.ok(html.includes('<li>URL: <a href="https://example.com/mixed">'));
|
|
206
|
+
assert.ok(html.includes('<li>Summary of results:'));
|
|
207
|
+
assert.ok(html.includes('<li>Called: 2</li>'));
|
|
208
|
+
assert.ok(html.includes('<li>Violations: 3</li>'));
|
|
209
|
+
assert.ok(html.includes('<li>Issues: 2</li>'));
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('getIssueFactsLines returns the about-issue list lines for a report', async () => {
|
|
213
|
+
const {getIssueFactsLines} = await import('./util.ts');
|
|
214
|
+
const report = await getFixtureReport('260101T0000', 'mix');
|
|
215
|
+
const lines = getIssueFactsLines(report, 'linkNoText', ' ');
|
|
216
|
+
const html = lines.join('\n');
|
|
217
|
+
assert.ok(html.includes('<li>Why it matters:'));
|
|
218
|
+
assert.ok(html.includes('<li>Priority:'));
|
|
219
|
+
assert.ok(html.includes('<li>Related WCAG standard: <a href='));
|
|
220
|
+
assert.ok(html.includes('<li>Reported by'));
|
|
221
|
+
assert.ok(html.includes('<li>Violations:'));
|
|
222
|
+
assert.ok(html.includes('<li>Violators:'));
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test('getViolatorFactsLines returns the about-violator list lines for a report', async () => {
|
|
226
|
+
const {getViolatorFactsLines} = await import('./util.ts');
|
|
227
|
+
const report = await getFixtureReport('260101T0000', 'mix');
|
|
228
|
+
const lines = getViolatorFactsLines(report, 'linkNoText', '0', null, ' ');
|
|
229
|
+
const html = lines.join('\n');
|
|
230
|
+
assert.ok(html.includes('<li>Tag name: <code>'));
|
|
231
|
+
assert.ok(html.includes('<li>Text:'));
|
|
232
|
+
assert.ok(html.includes('<li>Start tag: <code>'));
|
|
233
|
+
assert.ok(html.includes('<li>XPath: <code>'));
|
|
234
|
+
assert.ok(html.includes('<li>Bounding box:'));
|
|
235
|
+
assert.ok(html.includes('<li>Reported by'));
|
|
236
|
+
});
|
|
237
|
+
|
|
184
238
|
test('getAgoString returns "1 day" for exactly 1 day ago', () => {
|
|
185
239
|
// Construct a time stamp 1 day and 1 hour ago, so Math.round gives exactly 1.
|
|
186
240
|
const date = new Date(Date.now() - (86400000 + 3600000));
|
|
@@ -238,3 +292,45 @@ test('getMultiReportWhats returns descriptions that have multiple reports', asyn
|
|
|
238
292
|
const whats = await getMultiReportWhats();
|
|
239
293
|
assert.ok(whats.includes('Mixed Outcomes Page'));
|
|
240
294
|
});
|
|
295
|
+
|
|
296
|
+
// Returns a minimal report for testing rule-engine identification.
|
|
297
|
+
const makeEngineReport = (whiches: string[], preventions: Record<string, string>) => ({
|
|
298
|
+
acts: whiches.map(which => ({type: 'test', which})),
|
|
299
|
+
jobData: {preventions}
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test('getEngineIDs counts a prevented engine, which has an act, once', () => {
|
|
303
|
+
const ids = getEngineIDs(makeEngineReport(['axe', 'wave'], {wave: 'failed'}));
|
|
304
|
+
assert.deepEqual(ids.calledIDs.sort(), ['axe', 'wave']);
|
|
305
|
+
assert.deepEqual(ids.preventedIDs, ['wave']);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test('getEngineIDs counts a prevented engine that has no act', () => {
|
|
309
|
+
const ids = getEngineIDs(makeEngineReport(['axe'], {alfa: 'failed'}));
|
|
310
|
+
assert.deepEqual(ids.calledIDs.sort(), ['alfa', 'axe']);
|
|
311
|
+
assert.deepEqual(ids.preventedIDs, ['alfa']);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test('getEngineIDs counts nuVal and nuVnu as one engine when nuVal succeeds', () => {
|
|
315
|
+
const ids = getEngineIDs(makeEngineReport(['nuVal', 'nuVnu'], {}));
|
|
316
|
+
assert.deepEqual(ids.calledIDs, ['nuVal']);
|
|
317
|
+
assert.deepEqual(ids.preventedIDs, []);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test('getEngineIDs ignores a nuVal prevention when nuVnu succeeds', () => {
|
|
321
|
+
const ids = getEngineIDs(makeEngineReport(['nuVal', 'nuVnu'], {nuVal: 'failed'}));
|
|
322
|
+
assert.deepEqual(ids.calledIDs, ['nuVal']);
|
|
323
|
+
assert.deepEqual(ids.preventedIDs, []);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test('getEngineIDs counts one prevention when both nuVal and nuVnu are prevented', () => {
|
|
327
|
+
const ids = getEngineIDs(makeEngineReport(['nuVal', 'nuVnu'], {nuVal: 'failed', nuVnu: 'failed'}));
|
|
328
|
+
assert.deepEqual(ids.calledIDs, ['nuVal']);
|
|
329
|
+
assert.deepEqual(ids.preventedIDs, ['nuVal']);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test('getEngineIDs counts a nuVal prevention when nuVnu was not called', () => {
|
|
333
|
+
const ids = getEngineIDs(makeEngineReport(['nuVal'], {nuVal: 'failed'}));
|
|
334
|
+
assert.deepEqual(ids.calledIDs, ['nuVal']);
|
|
335
|
+
assert.deepEqual(ids.preventedIDs, ['nuVal']);
|
|
336
|
+
});
|