@jrpool/kilotest 45.0.4 → 46.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/api/listDiagnoses.js +13 -9
- package/api/listIssues.js +26 -23
- package/api/listViolators.js +20 -17
- package/api/requestRetest.js +3 -5
- package/api/testSuite.js +20 -7
- package/api/util.js +16 -19
- package/docs/IDEAS.md +67 -0
- package/package.json +1 -1
- package/util.js +3 -3
package/api/listDiagnoses.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
8
|
const {
|
|
9
|
-
|
|
9
|
+
getIssueSpec,
|
|
10
10
|
getReportBasics,
|
|
11
11
|
getResponseMetadata,
|
|
12
12
|
getToolsFacts,
|
|
@@ -41,10 +41,10 @@ exports.response = async args => {
|
|
|
41
41
|
// Add them to the response content.
|
|
42
42
|
responseContent['basics about the report'] = reportBasics;
|
|
43
43
|
}
|
|
44
|
-
// Get the
|
|
45
|
-
const
|
|
44
|
+
// Get the specification of the issue.
|
|
45
|
+
const issueSpec = issueID ? getIssueSpec(issueID) : null;
|
|
46
46
|
// If the issue is ignorable or not fully classified:
|
|
47
|
-
if (!
|
|
47
|
+
if (!issueSpec) {
|
|
48
48
|
// Add this to the response content.
|
|
49
49
|
responseContent['basics about the issue'] = {
|
|
50
50
|
'error': 'No information about the specified issue is available'
|
|
@@ -55,7 +55,7 @@ exports.response = async args => {
|
|
|
55
55
|
}
|
|
56
56
|
// Otherwise, i.e. if it is non-ignorable and fully classified:
|
|
57
57
|
else {
|
|
58
|
-
const {summary, wcag, weight, why} =
|
|
58
|
+
const {summary, wcag, weight, why} = issueSpec;
|
|
59
59
|
// Initialize the basics about it.
|
|
60
60
|
const issueBasics = {
|
|
61
61
|
'identifier': issueID,
|
|
@@ -99,7 +99,7 @@ exports.response = async args => {
|
|
|
99
99
|
// Add them to the response content.
|
|
100
100
|
responseContent['details about the element'] = violatorDetails;
|
|
101
101
|
// If the issue is non-ignorable and fully classified:
|
|
102
|
-
if (
|
|
102
|
+
if (issueSpec) {
|
|
103
103
|
// Initialize data about the diagnoses of the violation of the issue.
|
|
104
104
|
const diagnoses = [];
|
|
105
105
|
// For each act in the report:
|
|
@@ -110,9 +110,13 @@ exports.response = async args => {
|
|
|
110
110
|
if (type === 'test' && which) {
|
|
111
111
|
// For each standard instance of the act:
|
|
112
112
|
instances.forEach(instance => {
|
|
113
|
-
const {count, ordinalSeverity, ruleID, what} = instance;
|
|
114
|
-
// If the instance
|
|
115
|
-
if (
|
|
113
|
+
const {count, ordinalSeverity, outcome, ruleID, what} = instance;
|
|
114
|
+
// If the instance reports a violation of the issue by the violator:
|
|
115
|
+
if (
|
|
116
|
+
outcome !== 'cantTell'
|
|
117
|
+
&& instance.issueID === issueID
|
|
118
|
+
&& instance.catalogIndex === catalogIndex
|
|
119
|
+
) {
|
|
116
120
|
// Get the diagnosis.
|
|
117
121
|
const diagnosis = {
|
|
118
122
|
'identifier of the violated rule': ruleID !== what ? ruleID : null,
|
package/api/listIssues.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
8
|
const {
|
|
9
|
-
|
|
9
|
+
getIssueSpec,
|
|
10
10
|
getReportBasics,
|
|
11
11
|
getResponseMetadata,
|
|
12
12
|
getRuleEngineFacts,
|
|
@@ -75,29 +75,32 @@ exports.response = async args => {
|
|
|
75
75
|
const instances = result?.standardResult?.instances ?? [];
|
|
76
76
|
// For each standard instance of the act:
|
|
77
77
|
instances.forEach(instance => {
|
|
78
|
-
const {catalogIndex, issueID} = instance;
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
//
|
|
89
|
-
|
|
78
|
+
const {catalogIndex, issueID, outcome} = instance;
|
|
79
|
+
// If the instance reports a violation:
|
|
80
|
+
if (outcome !== 'cantTell') {
|
|
81
|
+
// Get the specification of its issue.
|
|
82
|
+
const issueSpec = issueID ? getIssueSpec(issueID) : null;
|
|
83
|
+
// If the instance has a non-ignorable and fully classified issue:
|
|
84
|
+
if (issueSpec) {
|
|
85
|
+
const {summary, weight, why} = issueSpec;
|
|
86
|
+
// Ensure the rule-engine ID is in the reporters data.
|
|
87
|
+
reporterIDs.add(which);
|
|
88
|
+
// If the instance has a catalog index:
|
|
89
|
+
if (catalogIndex) {
|
|
90
|
+
// Ensure the index of the violator is in the results data.
|
|
91
|
+
violatorIndexes.add(catalogIndex);
|
|
92
|
+
}
|
|
93
|
+
// Ensure the data about the issue are in the results data.
|
|
94
|
+
issuesData[issueID] ??= {
|
|
95
|
+
id: issueID,
|
|
96
|
+
summary,
|
|
97
|
+
weight,
|
|
98
|
+
why,
|
|
99
|
+
reporterIDs: new Set()
|
|
100
|
+
};
|
|
101
|
+
// Ensure the reporter ID is in the data about the issue.
|
|
102
|
+
issuesData[issueID].reporterIDs.add(which);
|
|
90
103
|
}
|
|
91
|
-
// Ensure the data about the issue are in the results data.
|
|
92
|
-
issuesData[issueID] ??= {
|
|
93
|
-
id: issueID,
|
|
94
|
-
summary,
|
|
95
|
-
weight,
|
|
96
|
-
why,
|
|
97
|
-
reporterIDs: new Set()
|
|
98
|
-
};
|
|
99
|
-
// Ensure the reporter ID is in the data about the issue.
|
|
100
|
-
issuesData[issueID].reporterIDs.add(which);
|
|
101
104
|
}
|
|
102
105
|
});
|
|
103
106
|
}
|
package/api/listViolators.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
8
|
const {
|
|
9
|
-
|
|
9
|
+
getIssueSpec,
|
|
10
10
|
getReportBasics,
|
|
11
11
|
getResponseMetadata,
|
|
12
12
|
getRuleEnginesFacts,
|
|
@@ -41,10 +41,10 @@ exports.response = async args => {
|
|
|
41
41
|
// Add them to the response content.
|
|
42
42
|
responseContent['basics about the report'] = reportBasics;
|
|
43
43
|
}
|
|
44
|
-
// Get the
|
|
45
|
-
const
|
|
44
|
+
// Get the specification of the issue.
|
|
45
|
+
const issueSpec = issueID ? getIssueSpec(issueID) : null;
|
|
46
46
|
// If the issue is ignorable or not fully classified:
|
|
47
|
-
if (!
|
|
47
|
+
if (!issueSpec) {
|
|
48
48
|
// Add this to the response content.
|
|
49
49
|
responseContent['basics about the issue'] = {
|
|
50
50
|
'error': 'No information about the specified issue is available'
|
|
@@ -52,7 +52,7 @@ exports.response = async args => {
|
|
|
52
52
|
}
|
|
53
53
|
// Otherwise, i.e. if it is non-ignorable and fully classified:
|
|
54
54
|
else {
|
|
55
|
-
const {summary, wcag, weight, why} =
|
|
55
|
+
const {summary, wcag, weight, why} = issueSpec;
|
|
56
56
|
// Initialize the basics and details about it.
|
|
57
57
|
const issueBasics = {
|
|
58
58
|
'identifier': issueID,
|
|
@@ -81,18 +81,21 @@ exports.response = async args => {
|
|
|
81
81
|
instances.forEach(instance => {
|
|
82
82
|
// If the instance has the issue ID:
|
|
83
83
|
if (instance.issueID === issueID) {
|
|
84
|
-
const {catalogIndex} = instance;
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
84
|
+
const {catalogIndex, outcome} = instance;
|
|
85
|
+
// If the instance reports a violation:
|
|
86
|
+
if (outcome !== 'cantTell') {
|
|
87
|
+
// Ensure the rule-engine ID is in the reporters data.
|
|
88
|
+
reporterIDs.add(which);
|
|
89
|
+
// If the instance has a catalog index:
|
|
90
|
+
if (catalogIndex) {
|
|
91
|
+
// Ensure the catalog index is in the violators data.
|
|
92
|
+
violators[catalogIndex] ??= {
|
|
93
|
+
catalogIndex,
|
|
94
|
+
reporters: new Set()
|
|
95
|
+
};
|
|
96
|
+
// Ensure the reporter ID is in the data about the violator.
|
|
97
|
+
violators[catalogIndex].reporters.add(which);
|
|
98
|
+
}
|
|
96
99
|
}
|
|
97
100
|
}
|
|
98
101
|
});
|
package/api/requestRetest.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
8
|
const {getResponseMetadata, getToolsFacts, processTestRequest, thisHost} = require('./util');
|
|
9
|
-
const {getReportsExtract} = require('../util');
|
|
9
|
+
const {getLatestReportExtracts, getReportsExtract} = require('../util');
|
|
10
10
|
|
|
11
11
|
// FUNCTIONS
|
|
12
12
|
|
|
@@ -35,10 +35,8 @@ exports.response = async args => {
|
|
|
35
35
|
}
|
|
36
36
|
// Otherwise, if the report has been superseded:
|
|
37
37
|
else if (
|
|
38
|
-
|
|
39
|
-
.
|
|
40
|
-
.filter(extract => extract.what === what && extract.url === url)
|
|
41
|
-
.length > 1
|
|
38
|
+
(await getLatestReportExtracts())
|
|
39
|
+
.every(extract => extract.timeStamp !== timeStamp || extract.jobID !== jobID)
|
|
42
40
|
) {
|
|
43
41
|
// Add this to the response content.
|
|
44
42
|
responseContent['details about your request'] = {
|
package/api/testSuite.js
CHANGED
|
@@ -137,6 +137,8 @@ const requestService = async () => {
|
|
|
137
137
|
const reportsBasicsIndex = Math.floor(reportsBasics.length * Math.random());
|
|
138
138
|
// Choose one report at random.
|
|
139
139
|
reportBasics = reportsBasics[reportsBasicsIndex];
|
|
140
|
+
// Get whether the report has been superseded by a later report about the same page.
|
|
141
|
+
const isSuperseded = reportBasics['whether a later report about the same page exists'];
|
|
140
142
|
[timeStamp, jobID] = reportBasics.identifier?.split('-') || [null, null];
|
|
141
143
|
if (!(timeStamp && jobID)) {
|
|
142
144
|
console.log(`reportBasicsIndex: ${reportsBasicsIndex}`);
|
|
@@ -263,13 +265,24 @@ const requestService = async () => {
|
|
|
263
265
|
responseContent = body?.['response content'] ?? {};
|
|
264
266
|
requestDetails = responseContent['details about your request'] ?? {};
|
|
265
267
|
requestDisposition = responseContent['disposition of your request'] ?? {};
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
268
|
+
// If the report has been superseded:
|
|
269
|
+
if (isSuperseded) {
|
|
270
|
+
// Expect the request to be rejected.
|
|
271
|
+
if (!requestDetails.error?.includes('a later report about the page exists')) {
|
|
272
|
+
console.log(`requestDetails: ${JSON.stringify(requestDetails, null, 2)}`);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// Otherwise, i.e. if the report has not been superseded:
|
|
277
|
+
else {
|
|
278
|
+
if (
|
|
279
|
+
!requestDetails['reason why the page should be retested']
|
|
280
|
+
|| !requestDisposition['how you can check for completion']
|
|
281
|
+
) {
|
|
282
|
+
console.log(`requestDetails: ${JSON.stringify(requestDetails, null, 2)}`);
|
|
283
|
+
console.log(`requestDisposition: ${JSON.stringify(requestDisposition, null, 2)}`);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
273
286
|
}
|
|
274
287
|
console.log('======================\nRequest: Make a feature request');
|
|
275
288
|
path = '/api/requestFeature';
|
package/api/util.js
CHANGED
|
@@ -14,11 +14,11 @@ const {
|
|
|
14
14
|
getRandomString,
|
|
15
15
|
getReportExtract,
|
|
16
16
|
getReportStats,
|
|
17
|
-
issuesClassification,
|
|
18
17
|
objectSort,
|
|
19
18
|
ruleEngines,
|
|
20
19
|
updateRecs
|
|
21
20
|
} = require('../util');
|
|
21
|
+
const {issues: issueSpecs} = require('testaro-issues');
|
|
22
22
|
|
|
23
23
|
// CONSTANTS
|
|
24
24
|
|
|
@@ -106,25 +106,22 @@ exports.getReportBasics = async (timeStamp, jobID) => {
|
|
|
106
106
|
// Return them.
|
|
107
107
|
return basics;
|
|
108
108
|
};
|
|
109
|
-
// Returns the
|
|
110
|
-
exports.
|
|
111
|
-
// Get the issue
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
) {
|
|
124
|
-
// Return the classification.
|
|
125
|
-
return issueClassification;
|
|
109
|
+
// Returns the specification of an issue.
|
|
110
|
+
exports.getIssueSpec = issueID => {
|
|
111
|
+
// Get the issue specification.
|
|
112
|
+
const issueSpec = issueSpecs[issueID];
|
|
113
|
+
// If it exists:
|
|
114
|
+
if (issueSpec) {
|
|
115
|
+
const {summary, wcag, weight, why} = issueSpec;
|
|
116
|
+
// If the issue is non-ignorable and fully classified:
|
|
117
|
+
if (issueID !== 'ignorable' && summary && wcag && [1, 2, 3, 4].includes(weight) && why) {
|
|
118
|
+
// Return its specification.
|
|
119
|
+
return issueSpec;
|
|
120
|
+
}
|
|
121
|
+
// Otherwise, return this.
|
|
122
|
+
return null;
|
|
126
123
|
}
|
|
127
|
-
// Otherwise, i.e. if it
|
|
124
|
+
// Otherwise, i.e. if it does not exist, return this.
|
|
128
125
|
return null;
|
|
129
126
|
};
|
|
130
127
|
// Processes a test or retest request.
|
package/docs/IDEAS.md
CHANGED
|
@@ -5858,3 +5858,70 @@ The gamble is **not** on whether the concept is sound (it is), but on **executio
|
|
|
5858
5858
|
|
|
5859
5859
|
#### Summary
|
|
5860
5860
|
Your architectural shift from static digests to interactive FAQ + LLM-driven Q&A is **well-grounded in current practice** and **solves real UX problems**. The risks are manageable with proper scoping and validation. I'd recommend a **phased rollout** starting with FAQs + manual custom-question handling, then adding LLM assistance once you understand user intent patterns.
|
|
5861
|
+
|
|
5862
|
+
## Testing
|
|
5863
|
+
|
|
5864
|
+
Now let's check the testSuite.js more fully to see how it picks fixtures (confirming the deficiency the user described), and check schemas.js/util.js in api for outcome handling.
|
|
5865
|
+
|
|
5866
|
+
Here is a plan for building a rigorous testing system for Kilotest, informed by how QAI is structured. I read the QAI source (`src/util.test.ts`, `src/requestHandler.test.ts`, `package.json`) and the relevant parts of Kilotest (`api/*.js`, root `util.js`, `db/`, `web/listReports`) to ground this in what actually exists today, rather than in the abstract.
|
|
5867
|
+
|
|
5868
|
+
### What QAI does that Kilotest should converge toward
|
|
5869
|
+
|
|
5870
|
+
1. Colocated `*.test.ts` files next to the modules they test, run with the built-in `node:test` runner and `node:assert/strict`, no third-party test framework or mocking library.
|
|
5871
|
+
2. Coverage enforced by `c8` with 100% thresholds (lines/functions/branches/statements) as a gate, wired into `npm run check` alongside `typecheck`, `lint`, and `hint`.
|
|
5872
|
+
3. Server-level tests spin up a real ephemeral `http.Server` per test (`server.listen(0, ...)`) using `makeHandler(commentsFilePath, sendAlert)`, i.e. the handler factory takes its data-file path and side-effecting collaborators (like the alert sender) as parameters instead of importing hardcoded module-level constants. That is what makes deterministic, isolated tests possible: each test gets its own temp file/dir (`mkdtempDisposable`) and its own server instance, so there is no shared mutable fixture and no test order dependency.
|
|
5873
|
+
4. HTML responses are asserted with `node-html-parser`, not regex-only, so tests check structure (`querySelector('title')`, absence of `<script>`/`<img>` after sanitization) as well as text content.
|
|
5874
|
+
5. Failure paths are tested deliberately, not incidentally: unreadable/unwritable files (via `chmod`), missing fields, oversized/undersized input, duplicate detection, a broken `sendAlert` collaborator, etc. Every branch has a dedicated test, which is what makes 100% coverage meaningful rather than a vanity number.
|
|
5875
|
+
6. `node:test`'s built-in `testContext.mock.property(...)` is used for surgical monkey-patching (e.g. redirecting a route's file path) instead of a mocking library.
|
|
5876
|
+
|
|
5877
|
+
### Why Kilotest's current setup cannot get there yet
|
|
5878
|
+
|
|
5879
|
+
- `db/`, `db/reports`, `db/jobs`, etc. are computed once at module load as `path.join(__dirname, 'db')` in the top-level `util.js` (`dbPath`, `reportsPath`, `hiddenReportsPath`, `reportsExtractPath`, `jobsPath`, `recsPath`). Every downstream function (`getReportExtracts`, `getReportBasics`, job-claiming code, etc.) closes over these constants. There is currently no way to point any of this at a fixture directory the way QAI's `makeHandler(commentsFilePath, ...)` accepts a path.
|
|
5880
|
+
- `api/testSuite.js` is an end-to-end script, not a test suite: it talks to a live running server (local or deployed), picks a **random real report** (`Math.floor(reportsBasics.length * Math.random())`), and only asserts loose shape/presence conditions (`!issuesBasics.length`, `!issueBasics['impact on a user']`) because it cannot know in advance what the "correct" content of a real report is. It cannot catch a case where, say, `outcome === 'error'` is silently mapped to the wrong impact label, because it never encodes what the right answer should be.
|
|
5881
|
+
- There is no `test/` or `*.test.js` infrastructure, no `c8`, and no UI test of any kind.
|
|
5882
|
+
|
|
5883
|
+
That correctly diagnoses the deficiency: you cannot write an assertion like "a report whose act has `outcome: 'error'` must be classified as X" against a randomly-selected real report, because you would first have to reverse-engineer what X should be from the code you are trying to test — that is circular.
|
|
5884
|
+
|
|
5885
|
+
### Proposed plan
|
|
5886
|
+
|
|
5887
|
+
#### Phase 0: Establish the toolchain
|
|
5888
|
+
- Add `c8` as a dev dependency; add an `npm test` script (`c8 --check-coverage node --test`) that actually runs something, replacing the current `"echo \"Error…\""` placeholder. Keep `test:api` for now as a separate, explicitly-labeled smoke/E2E script (see Phase 4) rather than deleting it immediately.
|
|
5889
|
+
- Decide coverage thresholds up front (QAI uses 100%; Kilotest may want to start lower, e.g. 80%, and ratchet up, given the amount of legacy code to backfill). This is a judgment call worth confirming with you before locking in a number in `package.json`.
|
|
5890
|
+
- Since Kilotest is CommonJS (`sourceType: "commonjs"` in `eslint.config.mjs`) and plain JS (not TypeScript), tests will be `*.test.js` using `require('node:test')` / `require('node:assert/strict')` — same runner and assertion library as QAI, just without the TS layer. This keeps the eventual TS migration (if you pursue it) a mechanical follow-up rather than a rewrite.
|
|
5891
|
+
|
|
5892
|
+
#### Phase 1: Make data locations injectable (prerequisite for everything else)
|
|
5893
|
+
This is the load-bearing architectural change. Without it, no module can be tested against crafted fixtures without touching the real `db/`.
|
|
5894
|
+
- Refactor root `util.js` (and anything relying on its exported path constants) so the base data directory is resolved from a parameter or an environment variable read at call time (e.g. `KILOTEST_DB_DIR`, defaulting to today's `path.join(__dirname, 'db')`), rather than baked into module-load-time constants.
|
|
5895
|
+
- Where feasible, prefer explicit parameters over env vars for the innermost functions (mirroring `makeHandler(commentsFilePath, ...)`), and only use an env var at the outermost/server-bootstrap layer, so unit tests can call functions directly with a fixture path and never need to touch process env or the module cache.
|
|
5896
|
+
|
|
5897
|
+
#### Phase 2: Build a crafted fixture corpus
|
|
5898
|
+
- Create `test/fixtures/db/` (or similar) with hand-authored, minimal Testaro reports covering the `outcome` cases you need verified: `success`, `failure`, `error`, mixed-outcome act arrays, an act with a missing/undefined `outcome`, a superseded report, an empty-violations report, etc.
|
|
5899
|
+
- Each fixture should be small enough that the expected API response can be hand-computed and hard-coded into the test as the "known-good" answer, exactly the property the current `test:api` script lacks.
|
|
5900
|
+
|
|
5901
|
+
#### Phase 3: Unit tests, module by module
|
|
5902
|
+
Priority order, since these are the modules you just changed and where correctness of `outcome` handling matters most:
|
|
5903
|
+
1. `api/util.js` and root `util.js` (pure/near-pure helper functions: `getReportBasics`, `getReportExtracts`, `getToolsFacts`, response-metadata helpers).
|
|
5904
|
+
2. `api/listIssues.js`, `api/listViolators.js`, `api/listDiagnoses.js` (the modules that already reference `outcome`).
|
|
5905
|
+
3. `api/listReports.js`, `api/getReport.js`.
|
|
5906
|
+
4. `api/requestTest.js`, `api/requestRetest.js`, `api/requestFeature.js`, `api/schemas.js` (input validation edge cases: too-short/long fields, malformed bodies).
|
|
5907
|
+
Each test file lives next to its module (`api/listIssues.test.js`) and exercises it directly against Phase 2 fixtures, no server involved.
|
|
5908
|
+
|
|
5909
|
+
#### Phase 4: Rework the API-level integration test
|
|
5910
|
+
Replace `test:api`'s "start from a live server, pick a random real report" approach with a QAI-style handler test:
|
|
5911
|
+
- Start an ephemeral server per test (or a shared one where safe) pointed at the fixture `db/` from Phase 2.
|
|
5912
|
+
- Walk through the same request sequence `testSuite.js` currently performs, but with hard-coded fixture identifiers and asserted exact expected values instead of "does this look non-empty."
|
|
5913
|
+
- Keep a much smaller, separate "smoke test against a real running deployment" script if you still want a pre-deploy sanity check, but do not conflate it with correctness testing.
|
|
5914
|
+
|
|
5915
|
+
#### Phase 5: UI/web testing
|
|
5916
|
+
Kilotest's `web/*` pages are plain HTML + vanilla JS (no framework), analogous to QAI's server-rendered pages. Two complementary layers, matching QAI's toolset:
|
|
5917
|
+
- **Static/structural**: use `node-html-parser` (add as a dev dependency, same as QAI) to assert on the HTML served for each page/form (titles, presence of expected form fields, absence of raw unescaped user content).
|
|
5918
|
+
- **Behavioral**: for pages with client-side JS (`web/listReports/index.js` etc.) that fetch from the API and render results, either (a) extract the rendering logic into pure, testable functions that take API response JSON and return DOM-ready data/markup, so they can be unit-tested with `node:test` + `node:assert` without a browser, and/or (b) introduce a small number of real-browser tests (e.g. Playwright) only for the handful of interactions that cannot be verified any other way (form submission, client-side validation feedback). I would only reach for a browser automation tool if pure-function extraction proves impractical, since it adds real complexity and flakiness risk to a JS-only project.
|
|
5919
|
+
|
|
5920
|
+
#### Phase 6: CI wiring and ratcheting
|
|
5921
|
+
- Add the test/coverage run to `.github/workflows/` (there is already a `.github/workflows/` dir) so `npm test` (and `npm run lint`) gate merges.
|
|
5922
|
+
- Track coverage percentage over time and raise the `c8` threshold as backfill work completes, rather than requiring 100% from day one across legacy code.
|
|
5923
|
+
|
|
5924
|
+
#### On architectural convergence with QAI more broadly
|
|
5925
|
+
Beyond testing, actually adopting the QAI architecture would additionally involve: TypeScript with `--strict`, ESM (`"type": "module"`), the `@stylistic/eslint-plugin` + `@typescript-eslint/parser` lint stack, and the dependency-injection pattern (`makeHandler(path, collaborators)`) throughout, not just at the edges touched for testing. I would treat that as a separate, larger migration decided after the testing foundation above exists, since attempting both at once (rewrite the language/module system *and* build the test suite that is supposed to protect the rewrite) inverts the safety order.
|
|
5926
|
+
|
|
5927
|
+
I have not made any changes yet. Given the phased nature of this, I would suggest reviewing/adjusting each phase before I implement it, starting with Phase 1 (the data-path injection refactor), since every later phase depends on it.
|
package/package.json
CHANGED
package/util.js
CHANGED
|
@@ -560,9 +560,9 @@ exports.getReportData = async (timeStamp, jobID) => {
|
|
|
560
560
|
const instances = result?.standardResult?.instances ?? [];
|
|
561
561
|
// For each standard instance of the act:
|
|
562
562
|
instances.forEach(instance => {
|
|
563
|
-
const {catalogIndex, issueID} = instance;
|
|
564
|
-
// If it has a non-ignorable classified issue ID:
|
|
565
|
-
if (issueID && issueSpecs[issueID] && issueID !== 'ignorable') {
|
|
563
|
+
const {catalogIndex, issueID, outcome} = instance;
|
|
564
|
+
// If it reports a violation and has a non-ignorable classified issue ID:
|
|
565
|
+
if (outcome !== 'cantTell' && issueID && issueSpecs[issueID] && issueID !== 'ignorable') {
|
|
566
566
|
// Ensure that the rule engine is in the temporary data.
|
|
567
567
|
reporterIDSet.add(which);
|
|
568
568
|
// Ensure that the issue is in the temporary data.
|