@jrpool/kilotest 57.2.0 → 57.2.3
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/package.json +1 -1
- package/util.test.ts +0 -205
- package/util.ts +2 -240
- package/web/deleteNotesForm/index.ts +8 -1
- package/web/listDiagnoses/index.ts +6 -4
- package/web/listDiagnoses/listDiagnoses.test.ts +8 -2
- package/web/listIssues/index.ts +7 -5
- package/web/listIssues/listIssues.test.ts +4 -3
- package/web/listReports/index.ts +6 -4
- package/web/listRules/index.ts +3 -1
- package/web/listTopIssues/index.ts +6 -4
- package/web/listViolators/index.ts +9 -7
- package/web/listViolators/listViolators.test.ts +8 -2
- package/web/metrics/index.ts +9 -2
- package/web/reportDeletion.ts +8 -1
- package/web/requestRetestForm/index.ts +2 -1
- package/web/tutorialAI/index.html +25 -22
- package/web/tutorialAI/index.ts +2 -1
- package/web/tutorialWeb/index.ts +2 -1
- package/web/util.test.ts +240 -0
- package/web/util.ts +266 -0
|
@@ -14,6 +14,7 @@ import {parse} from 'node-html-parser';
|
|
|
14
14
|
// Mock the util functions called by index.ts before requiring it, so that
|
|
15
15
|
// it imports the mocked versions. Other exports delegate to the real module.
|
|
16
16
|
import * as realUtil from '../../util.ts';
|
|
17
|
+
import * as realWebUtil from '../util.ts';
|
|
17
18
|
let getReportCallCount = 0;
|
|
18
19
|
let failGetReportOnCall = -1;
|
|
19
20
|
let pageDataStringsOverride: any = null;
|
|
@@ -26,12 +27,17 @@ mock.module('../../util.ts', {
|
|
|
26
27
|
return {error: 'Report is invalid (test override)'};
|
|
27
28
|
}
|
|
28
29
|
return (realUtil.getReport as any)(...args);
|
|
29
|
-
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
} as any);
|
|
33
|
+
mock.module('../util.ts', {
|
|
34
|
+
exports: {
|
|
35
|
+
...realWebUtil,
|
|
30
36
|
getPageDataStrings: async (...args: any[]) => {
|
|
31
37
|
if (pageDataStringsOverride !== null) {
|
|
32
38
|
return pageDataStringsOverride;
|
|
33
39
|
}
|
|
34
|
-
return (
|
|
40
|
+
return (realWebUtil.getPageDataStrings as any)(...args);
|
|
35
41
|
}
|
|
36
42
|
}
|
|
37
43
|
} as any);
|
package/web/listIssues/index.ts
CHANGED
|
@@ -6,19 +6,21 @@
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
-
getPageData,
|
|
10
|
-
getPageDataStrings,
|
|
11
9
|
getReport,
|
|
12
|
-
getEngineNamesString,
|
|
13
10
|
getTestActInstances,
|
|
14
|
-
getWCAGLink,
|
|
15
|
-
getWeightName,
|
|
16
11
|
htmlSafe,
|
|
17
12
|
isUsableReport,
|
|
18
13
|
objectSort,
|
|
19
14
|
populateTemplate,
|
|
20
15
|
ruleEngines
|
|
21
16
|
} from '../../util.ts';
|
|
17
|
+
import {
|
|
18
|
+
getPageData,
|
|
19
|
+
getPageDataStrings,
|
|
20
|
+
getEngineNamesString,
|
|
21
|
+
getWCAGLink,
|
|
22
|
+
getWeightName
|
|
23
|
+
} from '../util.ts';
|
|
22
24
|
import {issues as issueSpecs} from 'testaro-issues';
|
|
23
25
|
|
|
24
26
|
// FUNCTIONS
|
|
@@ -13,15 +13,16 @@ import {parse} from 'node-html-parser';
|
|
|
13
13
|
// Mock the util functions called by index.ts before requiring it, so that
|
|
14
14
|
// it imports the mocked versions. Other exports delegate to the real module.
|
|
15
15
|
import * as realUtil from '../../util.ts';
|
|
16
|
+
import * as realWebUtil from '../util.ts';
|
|
16
17
|
let pageDataStringsOverride: any = null;
|
|
17
|
-
mock.module('
|
|
18
|
+
mock.module('../util.ts', {
|
|
18
19
|
exports: {
|
|
19
|
-
...
|
|
20
|
+
...realWebUtil,
|
|
20
21
|
getPageDataStrings: async (...args: any[]) => {
|
|
21
22
|
if (pageDataStringsOverride !== null) {
|
|
22
23
|
return pageDataStringsOverride;
|
|
23
24
|
}
|
|
24
|
-
return (
|
|
25
|
+
return (realWebUtil.getPageDataStrings as any)(...args);
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
} as any);
|
package/web/listReports/index.ts
CHANGED
|
@@ -7,18 +7,20 @@
|
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
getAgoDays,
|
|
10
|
-
getCountString,
|
|
11
10
|
getJobNames,
|
|
12
|
-
getMultiReportWhats,
|
|
13
11
|
getObject,
|
|
14
|
-
getPageDataStrings,
|
|
15
12
|
getTestRequests,
|
|
16
|
-
getReportData,
|
|
17
13
|
getReportExtracts,
|
|
18
14
|
jobsPath,
|
|
19
15
|
objectSort,
|
|
20
16
|
populateTemplate
|
|
21
17
|
} from '../../util.ts';
|
|
18
|
+
import {
|
|
19
|
+
getCountString,
|
|
20
|
+
getMultiReportWhats,
|
|
21
|
+
getPageDataStrings,
|
|
22
|
+
getReportData
|
|
23
|
+
} from '../util.ts';
|
|
22
24
|
import path from 'node:path';
|
|
23
25
|
|
|
24
26
|
// FUNCTIONS
|
package/web/listRules/index.ts
CHANGED
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
-
getWeightName,
|
|
10
9
|
htmlSafe,
|
|
11
10
|
populateTemplate,
|
|
12
11
|
ruleEngines
|
|
13
12
|
} from '../../util.ts';
|
|
13
|
+
import {
|
|
14
|
+
getWeightName
|
|
15
|
+
} from '../util.ts';
|
|
14
16
|
import {issues as issueSpecs, issueRules, rules as ruleSpecs} from 'testaro-issues';
|
|
15
17
|
|
|
16
18
|
// FUNCTIONS
|
|
@@ -7,16 +7,18 @@
|
|
|
7
7
|
|
|
8
8
|
import {sendAlert} from '../../alerts.ts';
|
|
9
9
|
import {
|
|
10
|
-
getEngineNamesString,
|
|
11
10
|
getReportExtracts,
|
|
12
11
|
getReport,
|
|
13
12
|
getTestActInstances,
|
|
14
|
-
getWCAGLink,
|
|
15
|
-
getWeightName,
|
|
16
13
|
isReportError,
|
|
17
14
|
objectSort,
|
|
18
|
-
populateTemplate
|
|
15
|
+
populateTemplate
|
|
19
16
|
} from '../../util.ts';
|
|
17
|
+
import {
|
|
18
|
+
getEngineNamesString,
|
|
19
|
+
getWCAGLink,
|
|
20
|
+
getWeightName
|
|
21
|
+
} from '../util.ts';
|
|
20
22
|
import {issues as issueSpecs} from 'testaro-issues';
|
|
21
23
|
|
|
22
24
|
// FUNCTIONS
|
|
@@ -5,20 +5,22 @@
|
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
+
import {
|
|
9
|
+
getReport,
|
|
10
|
+
getTestActInstances,
|
|
11
|
+
htmlSafe,
|
|
12
|
+
isReportError,
|
|
13
|
+
populateTemplate
|
|
14
|
+
} from '../../util.ts';
|
|
8
15
|
import {
|
|
9
16
|
getPageDataStrings,
|
|
10
17
|
getPathID,
|
|
11
|
-
getReport,
|
|
12
18
|
getEngineNamesString,
|
|
13
|
-
getTestActInstances,
|
|
14
19
|
getTextFragmentHref,
|
|
15
20
|
getWCAGLink,
|
|
16
21
|
getWeightName,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
makeBreakable,
|
|
20
|
-
populateTemplate,
|
|
21
|
-
} from '../../util.ts';
|
|
22
|
+
makeBreakable
|
|
23
|
+
} from '../util.ts';
|
|
22
24
|
import {issues as issueSpecs} from 'testaro-issues';
|
|
23
25
|
|
|
24
26
|
// FUNCTIONS
|
|
@@ -14,6 +14,7 @@ import {parse} from 'node-html-parser';
|
|
|
14
14
|
// Mock the util functions called by index.ts before requiring it, so that
|
|
15
15
|
// it imports the mocked versions. Other exports delegate to the real module.
|
|
16
16
|
import * as realUtil from '../../util.ts';
|
|
17
|
+
import * as realWebUtil from '../util.ts';
|
|
17
18
|
let getReportCallCount = 0;
|
|
18
19
|
let failGetReportOnCall = -1;
|
|
19
20
|
let pageDataStringsOverride: any = null;
|
|
@@ -26,12 +27,17 @@ mock.module('../../util.ts', {
|
|
|
26
27
|
return {error: 'Report is invalid (test override)'};
|
|
27
28
|
}
|
|
28
29
|
return (realUtil.getReport as any)(...args);
|
|
29
|
-
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
} as any);
|
|
33
|
+
mock.module('../util.ts', {
|
|
34
|
+
exports: {
|
|
35
|
+
...realWebUtil,
|
|
30
36
|
getPageDataStrings: async (...args: any[]) => {
|
|
31
37
|
if (pageDataStringsOverride !== null) {
|
|
32
38
|
return pageDataStringsOverride;
|
|
33
39
|
}
|
|
34
|
-
return (
|
|
40
|
+
return (realWebUtil.getPageDataStrings as any)(...args);
|
|
35
41
|
}
|
|
36
42
|
}
|
|
37
43
|
} as any);
|
package/web/metrics/index.ts
CHANGED
|
@@ -7,9 +7,16 @@
|
|
|
7
7
|
// IMPORTS
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
|
-
clearMetrics,
|
|
11
|
-
|
|
10
|
+
clearMetrics,
|
|
11
|
+
getExclusionCookieValue,
|
|
12
|
+
getMetrics,
|
|
13
|
+
isValidAuthCode,
|
|
14
|
+
metricsExclusionCookieName,
|
|
15
|
+
populateTemplate
|
|
12
16
|
} from '../../util.ts';
|
|
17
|
+
import {
|
|
18
|
+
getDateTimeString
|
|
19
|
+
} from '../util.ts';
|
|
13
20
|
|
|
14
21
|
// FUNCTIONS
|
|
15
22
|
|
package/web/reportDeletion.ts
CHANGED
|
@@ -6,9 +6,16 @@
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
-
errorMessage,
|
|
9
|
+
errorMessage,
|
|
10
|
+
isValidAuthCode,
|
|
11
|
+
objectSort,
|
|
12
|
+
populateTemplate,
|
|
13
|
+
readdirOrCreate,
|
|
10
14
|
reportsPath
|
|
11
15
|
} from '../util.ts';
|
|
16
|
+
import {
|
|
17
|
+
getReportData
|
|
18
|
+
} from './util.ts';
|
|
12
19
|
import fs from 'node:fs/promises';
|
|
13
20
|
import path from 'node:path';
|
|
14
21
|
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {getReportExtracts, populateTemplate} from '../../util.ts';
|
|
9
|
+
import {getAgoString, getDateTimeString} from '../util.ts';
|
|
9
10
|
|
|
10
11
|
// FUNCTIONS
|
|
11
12
|
|
|
@@ -17,47 +17,34 @@
|
|
|
17
17
|
<h2>Purpose</h2>
|
|
18
18
|
<p>This tutorial shows you how to make Kilotest available to AI assistants. Suppose:</p>
|
|
19
19
|
<ul>
|
|
20
|
-
<li>You use an AI platform, like Claude,
|
|
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>
|
|
22
22
|
</ul>
|
|
23
23
|
<p>Then the models on your AI platform can use Kilotest <strong>if</strong> you make Kilotest available on the platform.</p>
|
|
24
24
|
<h2>What AI platform do you use?</h2>
|
|
25
25
|
<ul>
|
|
26
26
|
<li><a href="#claude">Claude.ai in a browser or Claude Desktop</a></li>
|
|
27
|
-
<li><a href="#chatgpt">ChatGPT</a></li>
|
|
28
|
-
<li><a href="#perplexity">Perplexity</a></li>
|
|
29
27
|
<li><a href="#vibe">Mistral Vibe</a></li>
|
|
28
|
+
<li><a href="#perplexity">Perplexity Pro in a browser</a></li>
|
|
30
29
|
</ul>
|
|
31
|
-
<h2 id="claude"
|
|
32
|
-
<p>To make Kilotest available on either Claude platform:</p>
|
|
30
|
+
<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>
|
|
33
31
|
<ol>
|
|
34
32
|
<li><a href="https://claude.ai/new">Start a new task</a>.</li>
|
|
35
33
|
<li>Expand the left sidebar to see the labels.</li>
|
|
36
|
-
<li>Click <q>Customize</q
|
|
37
|
-
<li>Click <q>Connectors</q
|
|
38
|
-
<li>In the <q>Add</q> list, click <q>Add custom connector</q
|
|
34
|
+
<li>Click the <q>Customize</q> button.</li>
|
|
35
|
+
<li>Click the <q>Connectors</q> button.</li>
|
|
36
|
+
<li>In the <q>Add</q> list, click the <q>Add custom connector</q> button.</li>
|
|
39
37
|
<li>Enter <q>Kilotest</q> into the <q>Name</q> input.</li>
|
|
40
38
|
<li>Enter <q>https://kilotest.com/mcp</q> into the <q>Remote MCP server URL</q> input.</li>
|
|
41
39
|
<li>Leave the OAuth inputs empty.</li>
|
|
42
|
-
<li>Click <q>Add</q
|
|
40
|
+
<li>Click the <q>Add</q> button.</li>
|
|
43
41
|
<li>For maximum autonomy, click <q>Always allow</q> for each tool.</li>
|
|
44
42
|
<li>Click the <code>x</code> button or press the <code>Escape</code> key to close the dialog.</li>
|
|
45
43
|
</ol>
|
|
46
|
-
<h2 id="
|
|
47
|
-
<p>To make Kilotest available on ChatGPT:</p>
|
|
48
|
-
<ol>
|
|
49
|
-
<li>Forthcoming</li>
|
|
50
|
-
</ol>
|
|
51
|
-
<h2 id="perplexity">Perplexity</h2>
|
|
52
|
-
<p>To make Kilotest available on Perplexity:</p>
|
|
53
|
-
<ol>
|
|
54
|
-
<li>Forthcoming</li>
|
|
55
|
-
</ol>
|
|
56
|
-
<h2 id="Vibe">Mistral Vibe</h2>
|
|
57
|
-
<p>To make Kilotest available on Mistral Vibe:</p>
|
|
44
|
+
<h2 id="vibe">Mistral Vibe</h2>
|
|
58
45
|
<ol>
|
|
59
46
|
<li>Choose the <a href="https://chat.mistral.ai/work">Work tab</a>.</li>
|
|
60
|
-
<li>Click <q>Connect your apps for better answers</q
|
|
47
|
+
<li>Click <q>Connect your apps for better answers</q>.</li>
|
|
61
48
|
<li>Click the <q>+ Add connector</q> button.</li>
|
|
62
49
|
<li>Click the <q>Add a custom connector</q> button.</li>
|
|
63
50
|
<li>Enter <q>Kilotest</q> into the <q>Connector name</q> input.</li>
|
|
@@ -65,6 +52,22 @@
|
|
|
65
52
|
<li>Click the <q>Add connector</q> button.</li>
|
|
66
53
|
<li>Toggle the <q>Ask/Allow</q> buttons to show <q>Allow</q>.</li>
|
|
67
54
|
</ol>
|
|
55
|
+
<h2 id="perplexity">Perplexity Pro in a browser</h2>
|
|
56
|
+
<ol>
|
|
57
|
+
<li>After signing in, click the <q>Customize</q> button in the left sidebar.</li>
|
|
58
|
+
<li>Click the <q>+ Custom connector</q> menu button.</li>
|
|
59
|
+
<li>Click the <q>Add MCP connector</q> menu item.</li>
|
|
60
|
+
<li>Enter <q>Kilotest</q> into the <q>Name</q> input.</li>
|
|
61
|
+
<li>Enter <q>https://kilotest.com/mcp</q> into the <q>MCP server URL</q> input.</li>
|
|
62
|
+
<li>Click the <q>Advanced</q> menu button to open the advanced settings.</li>
|
|
63
|
+
<li>Under <q>Authentication</q>, change the authentication type to <q>None</q>.</li>
|
|
64
|
+
<li>Check the <q>I understand …</q> checkbox.</li>
|
|
65
|
+
<li>Click the <q>Add</q> button.</li>
|
|
66
|
+
</ol>
|
|
67
|
+
<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). If you discover an AI platform that should be added to this page, please submit a comment below.</p>
|
|
70
|
+
<h2>Trouble?</h2>
|
|
68
71
|
<p>What if this did not work? Use the comment form below to tell us about it.</p>
|
|
69
72
|
<p>You can also submit an issue on the <a href="https://github.com/jrpool/kilotest/issues">Kilotest GitHub repository</a>.</p>
|
|
70
73
|
<section id="suggest-improvement" class="main">
|
package/web/tutorialAI/index.ts
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
8
|
import {sendAlert} from '../../alerts.ts';
|
|
9
|
-
import {checkCommentDuplicate,
|
|
9
|
+
import {checkCommentDuplicate, describeMax, getEnvMax, getJSON, getNowStamp} from '../../util.ts';
|
|
10
|
+
import {checkCommentLength} from '../util.ts';
|
|
10
11
|
import fs from 'node:fs/promises';
|
|
11
12
|
import path from 'node:path';
|
|
12
13
|
|
package/web/tutorialWeb/index.ts
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
8
|
import {sendAlert} from '../../alerts.ts';
|
|
9
|
-
import {checkCommentDuplicate,
|
|
9
|
+
import {checkCommentDuplicate, describeMax, getEnvMax, getJSON, getNowStamp} from '../../util.ts';
|
|
10
|
+
import {checkCommentLength} from '../util.ts';
|
|
10
11
|
import fs from 'node:fs/promises';
|
|
11
12
|
import path from 'node:path';
|
|
12
13
|
|
package/web/util.test.ts
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/*
|
|
2
|
+
util.test.ts
|
|
3
|
+
Tests of the utility functions used only by the web pages.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
import {test, before, after} 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 {reportsPath} from '../util.ts';
|
|
13
|
+
import {
|
|
14
|
+
checkCommentLength,
|
|
15
|
+
getAgoString,
|
|
16
|
+
getCountString,
|
|
17
|
+
getEngineNamesString,
|
|
18
|
+
getMultiReportWhats,
|
|
19
|
+
getPageData,
|
|
20
|
+
getPageDataStrings,
|
|
21
|
+
getTextFragmentHref,
|
|
22
|
+
getWCAGLink,
|
|
23
|
+
getWeightName,
|
|
24
|
+
makeBreakable
|
|
25
|
+
} from './util.ts';
|
|
26
|
+
|
|
27
|
+
// SETUP AND TEARDOWN
|
|
28
|
+
|
|
29
|
+
// These tests use the fixture database directory.
|
|
30
|
+
import {fixtureDBDir as fixtureDbDir} from '../test/dbFixture.ts';
|
|
31
|
+
const savedDbDir = process.env.DB_DIR;
|
|
32
|
+
|
|
33
|
+
before(() => {
|
|
34
|
+
process.env.DB_DIR = fixtureDbDir;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
after(() => {
|
|
38
|
+
if (savedDbDir !== undefined) {
|
|
39
|
+
process.env.DB_DIR = savedDbDir;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
delete process.env.DB_DIR;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// TESTS
|
|
47
|
+
|
|
48
|
+
test('checkCommentLength accepts a comment of exactly 20 characters', () => {
|
|
49
|
+
const result = checkCommentLength('a'.repeat(20));
|
|
50
|
+
assert.deepEqual(result, {status: 'ok'});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('checkCommentLength accepts a comment of exactly 1000 characters', () => {
|
|
54
|
+
const result = checkCommentLength('a'.repeat(1000));
|
|
55
|
+
assert.deepEqual(result, {status: 'ok'});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('checkCommentLength rejects a comment shorter than 20 characters', () => {
|
|
59
|
+
const result = checkCommentLength('a'.repeat(19));
|
|
60
|
+
assert.deepEqual(result, {
|
|
61
|
+
status: 'error',
|
|
62
|
+
message: 'Your comment was shorter than 20 characters'
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('checkCommentLength rejects a comment longer than 1000 characters', () => {
|
|
67
|
+
const result = checkCommentLength('a'.repeat(1001));
|
|
68
|
+
assert.deepEqual(result, {
|
|
69
|
+
status: 'error',
|
|
70
|
+
message: 'Your comment was longer than 1000 characters'
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('getAgoString returns "1 day" for a 1-day-old timestamp', () => {
|
|
75
|
+
const date = new Date(Date.now() - 1 * 24 * 60 * 60 * 1000);
|
|
76
|
+
assert.equal(
|
|
77
|
+
getAgoString(date.toISOString().slice(2).replace(/[-:]/g, '').slice(0, 11)), '1 day'
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('getAgoString returns "3 days" for a 3-day-old timestamp', () => {
|
|
82
|
+
const date = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000);
|
|
83
|
+
assert.equal(
|
|
84
|
+
getAgoString(date.toISOString().slice(2).replace(/[-:]/g, '').slice(0, 11)), '3 days'
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('getCountString returns singular for count 1', () => {
|
|
89
|
+
assert.equal(getCountString(1, 'report', 'reports'), '1 report');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('getCountString returns plural for count 0', () => {
|
|
93
|
+
assert.equal(getCountString(0, 'report', 'reports'), '0 reports');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('getTextFragmentHref returns a text-fragment URL', () => {
|
|
97
|
+
const href = getTextFragmentHref('Hello', 'https://example.com/page');
|
|
98
|
+
assert.ok(href.startsWith('https://example.com/page#:~:text='));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('getWCAGLink returns a URL for a numeric WCAG ID', () => {
|
|
102
|
+
const link = getWCAGLink('4.1.2');
|
|
103
|
+
assert.ok(link.startsWith('https://www.w3.org/WAI/WCAG22/Understanding/'));
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('getWeightName returns the correct name for each weight', () => {
|
|
107
|
+
assert.equal(getWeightName(1), 'lowest');
|
|
108
|
+
assert.equal(getWeightName(2), 'low');
|
|
109
|
+
assert.equal(getWeightName(3), 'high');
|
|
110
|
+
assert.equal(getWeightName(4), 'highest');
|
|
111
|
+
assert.equal(getWeightName(5), 'unknown');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('makeBreakable inserts wbr before non-initial slashes', () => {
|
|
115
|
+
assert.equal(makeBreakable('/api/listReports'), '/api<wbr>/listReports');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('getPageData returns page data for a valid report', async () => {
|
|
119
|
+
const data = await getPageData('260101T0000', 'mix') as any;
|
|
120
|
+
assert.equal(data.description, 'Mixed Outcomes Page');
|
|
121
|
+
assert.equal(data.url, 'https://example.com/mixed');
|
|
122
|
+
assert.equal(typeof data.daysAgo, 'number');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('getPageData returns an error for a nonexistent report', async () => {
|
|
126
|
+
const data = await getPageData('999999T9999', 'xxx') as any;
|
|
127
|
+
assert.ok(data.error);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('getPageDataStrings returns HTML strings for a valid report', async () => {
|
|
131
|
+
const strings = await getPageDataStrings('260101T0000', 'mix') as any;
|
|
132
|
+
assert.equal(strings.description, 'Mixed Outcomes Page');
|
|
133
|
+
assert.equal(strings.url, 'https://example.com/mixed');
|
|
134
|
+
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>'));
|
|
136
|
+
assert.ok(strings.testInfo.includes('2026-01-01 at 00:00'));
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('getPageDataStrings returns different testInfo for a different timeStamp', async () => {
|
|
140
|
+
const strings = await getPageDataStrings('260101T0001', 'ct') as any;
|
|
141
|
+
assert.equal(strings.description, 'All CantTell Page');
|
|
142
|
+
assert.ok(strings.testInfo.includes('by job <code>ct</code>'));
|
|
143
|
+
assert.ok(strings.testInfo.includes('2026-01-01 at 00:01'));
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('getPageDataStrings returns an error for a nonexistent report', async () => {
|
|
147
|
+
const strings = await getPageDataStrings('999999T9999', 'xxx');
|
|
148
|
+
assert.ok(strings.error);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('getPageDataStrings uses provided pageData instead of reading the report', async () => {
|
|
152
|
+
const strings = await getPageDataStrings('260101T0000', 'mix', {
|
|
153
|
+
description: 'Custom Page',
|
|
154
|
+
url: 'https://custom.com',
|
|
155
|
+
daysAgo: 1
|
|
156
|
+
}) as any;
|
|
157
|
+
assert.equal(strings.description, 'Custom Page');
|
|
158
|
+
assert.equal(strings.url, 'https://custom.com');
|
|
159
|
+
assert.ok(strings.testInfo.includes('1 day ago'));
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('getReportData returns an error for a nonexistent report', async () => {
|
|
163
|
+
const {getReportData} = await import('./util.ts');
|
|
164
|
+
const result: any = await getReportData('990101T0000', 'xxx');
|
|
165
|
+
assert.ok(result.error);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('getReportData falls back to the engine ID for an unknown prevented engine', async () => {
|
|
169
|
+
const {getReportData} = await import('./util.ts');
|
|
170
|
+
const prvJSON = await fs.readFile(path.join(reportsPath(), '260101T0006-prv.json'), 'utf8');
|
|
171
|
+
const report = JSON.parse(prvJSON);
|
|
172
|
+
report.jobData.preventions.unknownEngine = 'mystery failure';
|
|
173
|
+
const reportPath = path.join(reportsPath(), '260103T0000-unk.json');
|
|
174
|
+
await fs.writeFile(reportPath, JSON.stringify(report));
|
|
175
|
+
try {
|
|
176
|
+
const result: any = await getReportData('260103T0000', 'unk');
|
|
177
|
+
assert.ok(result.preventedEngineNames.includes('unknownEngine'));
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
await fs.unlink(reportPath);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('getAgoString returns "1 day" for exactly 1 day ago', () => {
|
|
185
|
+
// Construct a time stamp 1 day and 1 hour ago, so Math.round gives exactly 1.
|
|
186
|
+
const date = new Date(Date.now() - (86400000 + 3600000));
|
|
187
|
+
const stamp = date.toISOString().slice(2).replace(/[-:]/g, '').slice(0, 11);
|
|
188
|
+
assert.equal(getAgoString(stamp), '1 day');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('getDateTimeString describes an invalid time stamp as unknown', async () => {
|
|
192
|
+
const {getDateTimeString} = await import('./util.ts');
|
|
193
|
+
const result = getDateTimeString('999999T9999');
|
|
194
|
+
assert.equal(result, 'an unknown date at an unknown time');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('getEngineNamesString falls back to the engine ID for an unknown engine', async () => {
|
|
198
|
+
const {getEngineNamesString} = await import('./util.ts');
|
|
199
|
+
const result = getEngineNamesString(new Set(['unknownEngine']));
|
|
200
|
+
assert.equal(result, 'unknownEngine');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('getPathID returns the catalog pathID when catalogIndex is truthy', async () => {
|
|
204
|
+
const {getPathID} = await import('./util.ts');
|
|
205
|
+
const catalog = {'0': {tagName: 'div', pathID: '/html/body/div'}};
|
|
206
|
+
assert.equal(getPathID(catalog, '0', '/fallback'), '/html/body/div');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('getPathID returns the fallback pathID when catalogIndex is truthy but catalogItem has no pathID', async () => {
|
|
210
|
+
const {getPathID} = await import('./util.ts');
|
|
211
|
+
const catalog = {'0': {tagName: 'div', pathID: ''}};
|
|
212
|
+
assert.equal(getPathID(catalog, '0', '/fallback'), '/fallback');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('getPathID returns /html when catalogIndex is truthy but catalogItem and pathID are both missing', async () => {
|
|
216
|
+
const {getPathID} = await import('./util.ts');
|
|
217
|
+
const catalog = {};
|
|
218
|
+
assert.equal(getPathID(catalog, '0', null as any), '/html');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test('getPathID returns /html when catalogIndex is falsy and pathID is null', async () => {
|
|
222
|
+
const {getPathID} = await import('./util.ts');
|
|
223
|
+
assert.equal(getPathID({}, null as any, null as any), '/html');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('getEngineNamesString returns a sorted +-delimited list of engine names', () => {
|
|
227
|
+
const result = getEngineNamesString(new Set(['axe', 'wave', 'nuVal']));
|
|
228
|
+
const names = result.split(' + ');
|
|
229
|
+
assert.ok(names.length === 3);
|
|
230
|
+
assert.ok(names.includes('WAVE'));
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test('getEngineNamesString falls back to the ID for an unknown engine', () => {
|
|
234
|
+
assert.equal(getEngineNamesString(new Set(['unknownEngine'])), 'unknownEngine');
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test('getMultiReportWhats returns descriptions that have multiple reports', async () => {
|
|
238
|
+
const whats = await getMultiReportWhats();
|
|
239
|
+
assert.ok(whats.includes('Mixed Outcomes Page'));
|
|
240
|
+
});
|