@jrpool/kilotest 59.1.4 → 60.0.1
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/README.md +2 -5
- package/api/awaitTest.test.ts +9 -9
- package/api/awaitTest.ts +12 -12
- package/api/getReport.test.ts +4 -4
- package/api/getReport.ts +9 -9
- package/api/listDiagnoses.test.ts +14 -14
- package/api/listDiagnoses.ts +8 -8
- package/api/listIssues.test.ts +15 -15
- package/api/listIssues.ts +18 -20
- package/api/listPages.test.ts +2 -2
- package/api/listPages.ts +4 -4
- package/api/listViolators.test.ts +10 -10
- package/api/listViolators.ts +10 -10
- package/api/orderNewTest.test.ts +1 -2
- package/api/orderNewTest.ts +3 -4
- package/api/orderRetest.test.ts +10 -10
- package/api/orderRetest.ts +13 -14
- package/api/requestRetest.test.ts +11 -11
- package/api/requestRetest.ts +11 -11
- package/api/routes.ts +9 -11
- package/api/schemas.ts +18 -28
- package/api/util.test.ts +2 -2
- package/api/util.ts +7 -7
- package/capability.md +4 -4
- package/docs/AI-TOOLS.md +43 -1
- package/docs/decisions/009-report-id.md +16 -0
- package/index.test.ts +91 -41
- package/index.ts +66 -15
- package/lhm.plugin.json +33 -119
- package/llms-full.txt +18 -24
- package/llms.txt +12 -12
- package/mcp.test.ts +11 -20
- package/mcp.ts +18 -18
- package/openapi-3.0.yaml +1847 -0
- package/openapi.yaml +38 -97
- package/package.json +1 -1
- package/routing.test.ts +1 -1
- package/scripts/generate-openapi.ts +83 -1
- package/server.json +1 -1
- package/util.test.ts +32 -33
- package/util.ts +47 -43
- package/web/hideReportForm/index.ts +5 -6
- package/web/listDiagnoses/index.ts +4 -5
- package/web/listDiagnoses/listDiagnoses.test.ts +13 -13
- package/web/listIssues/index.ts +11 -12
- package/web/listIssues/listIssues.test.ts +24 -24
- package/web/listPages/index.ts +2 -2
- package/web/listPages/listPages.test.ts +2 -2
- package/web/listTopIssues/index.ts +2 -2
- package/web/listViolators/index.ts +5 -6
- package/web/listViolators/listViolators.test.ts +12 -12
- package/web/reannotate/index.ts +3 -3
- package/web/reannotateForm/index.ts +1 -2
- package/web/reportDeletion.ts +9 -12
- package/web/requestRetest/index.ts +2 -2
- package/web/requestRetest/requestRetest.test.ts +7 -7
- package/web/requestRetestForm/index.html +2 -2
- package/web/requestRetestForm/index.ts +3 -4
- package/web/requestRetestForm/requestRetestForm.test.ts +7 -8
- package/web/unhideReportForm/index.ts +4 -6
- package/web/util.test.ts +29 -29
- package/web/util.ts +1 -1
|
@@ -64,7 +64,7 @@ after(() => {
|
|
|
64
64
|
// TESTS
|
|
65
65
|
|
|
66
66
|
test('listDiagnoses returns an ok status with valid HTML for catalogIndex 0, linkNoText, in the mixed report', async () => {
|
|
67
|
-
const result = await answer('linkNoText/260101T0000
|
|
67
|
+
const result = await answer('linkNoText/260101T0000-mix/0');
|
|
68
68
|
assert.equal(result.status, 'ok');
|
|
69
69
|
assert.ok(result.answerPage);
|
|
70
70
|
const html = parse(result.answerPage);
|
|
@@ -72,34 +72,34 @@ test('listDiagnoses returns an ok status with valid HTML for catalogIndex 0, lin
|
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
test('listDiagnoses includes the issue summary in the HTML', async () => {
|
|
75
|
-
const result = await answer('linkNoText/260101T0000
|
|
75
|
+
const result = await answer('linkNoText/260101T0000-mix/0');
|
|
76
76
|
assert.ok(result.answerPage.includes('link not named'));
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
test('listDiagnoses includes the element tag name and text from the catalog', async () => {
|
|
80
|
-
const result = await answer('linkNoText/260101T0000
|
|
80
|
+
const result = await answer('linkNoText/260101T0000-mix/0');
|
|
81
81
|
assert.ok(result.answerPage.includes('About Us'));
|
|
82
82
|
});
|
|
83
83
|
|
|
84
84
|
test('listDiagnoses includes diagnosis descriptions from the rule engines', async () => {
|
|
85
|
-
const result = await answer('linkNoText/260101T0000
|
|
85
|
+
const result = await answer('linkNoText/260101T0000-mix/0');
|
|
86
86
|
assert.ok(result.answerPage.includes('The link does not have an accessible name'));
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
test('listDiagnoses excludes cantTell instances from the diagnoses list', async () => {
|
|
90
|
-
const result = await answer('focusIndicationBad/260101T0000
|
|
90
|
+
const result = await answer('focusIndicationBad/260101T0000-mix/0');
|
|
91
91
|
assert.equal(result.status, 'ok');
|
|
92
92
|
assert.ok(!result.answerPage.includes('Focus Visible'));
|
|
93
93
|
});
|
|
94
94
|
|
|
95
95
|
|
|
96
96
|
test('listDiagnoses returns an error status for a nonexistent report', async () => {
|
|
97
|
-
const result = await answer('linkNoText/999999T9999
|
|
97
|
+
const result = await answer('linkNoText/999999T9999-xyz/0');
|
|
98
98
|
assert.equal(result.status, 'error');
|
|
99
99
|
});
|
|
100
100
|
|
|
101
101
|
test('listDiagnoses returns ok with no diagnoses for a nonexistent catalog index', async () => {
|
|
102
|
-
const result = await answer('linkNoText/260101T0000
|
|
102
|
+
const result = await answer('linkNoText/260101T0000-mix/999');
|
|
103
103
|
assert.equal(result.status, 'ok');
|
|
104
104
|
const html = parse(result.answerPage);
|
|
105
105
|
const diagnosisItems = html.querySelectorAll('li');
|
|
@@ -112,7 +112,7 @@ test('listDiagnoses returns an error when getReport fails', async () => {
|
|
|
112
112
|
getReportCallCount = 0;
|
|
113
113
|
failGetReportOnCall = 1;
|
|
114
114
|
try {
|
|
115
|
-
const result = await answer('linkNoText/260101T0000
|
|
115
|
+
const result = await answer('linkNoText/260101T0000-mix/0');
|
|
116
116
|
assert.equal(result.status, 'error');
|
|
117
117
|
assert.ok(result.message.includes('invalid'));
|
|
118
118
|
}
|
|
@@ -124,7 +124,7 @@ test('listDiagnoses returns an error when getReport fails', async () => {
|
|
|
124
124
|
test('listDiagnoses returns an error when getPageDataStrings fails after getReport succeeds', async () => {
|
|
125
125
|
pageDataStringsOverride = {error: 'Page data strings error'};
|
|
126
126
|
try {
|
|
127
|
-
const result = await answer('linkNoText/260101T0000
|
|
127
|
+
const result = await answer('linkNoText/260101T0000-mix/0');
|
|
128
128
|
assert.equal(result.status, 'error');
|
|
129
129
|
assert.equal(result.message, 'Page data strings error');
|
|
130
130
|
}
|
|
@@ -183,7 +183,7 @@ test('listDiagnoses includes a take-me-there link for a text-linkable catalog it
|
|
|
183
183
|
checkpoints: []
|
|
184
184
|
};
|
|
185
185
|
await fs.writeFile(reportPath, JSON.stringify(report, null, 2));
|
|
186
|
-
const result = await answer('linkNoText/260101T0003
|
|
186
|
+
const result = await answer('linkNoText/260101T0003-tlk/0');
|
|
187
187
|
assert.equal(result.status, 'ok');
|
|
188
188
|
assert.ok(result.answerPage.includes('Take me there'));
|
|
189
189
|
}
|
|
@@ -193,7 +193,7 @@ test('listDiagnoses includes a take-me-there link for a text-linkable catalog it
|
|
|
193
193
|
});
|
|
194
194
|
|
|
195
195
|
test('listDiagnoses returns an error for an unknown issue with a valid report', async () => {
|
|
196
|
-
const result = await answer('nonexistentIssue/260101T0000
|
|
196
|
+
const result = await answer('nonexistentIssue/260101T0000-mix/0');
|
|
197
197
|
assert.equal(result.status, 'error');
|
|
198
198
|
assert.equal(result.message, 'Issue not found');
|
|
199
199
|
});
|
|
@@ -205,7 +205,7 @@ test('listDiagnoses returns an error when report facts are not obtained', async
|
|
|
205
205
|
urlLink: '<a href="https://example.com/mixed">https://example.com/mixed</a>'
|
|
206
206
|
};
|
|
207
207
|
try {
|
|
208
|
-
const result = await answer('linkNoText/260101T0000
|
|
208
|
+
const result = await answer('linkNoText/260101T0000-mix/0');
|
|
209
209
|
assert.equal(result.status, 'error');
|
|
210
210
|
assert.equal(result.message, 'Report facts not obtained');
|
|
211
211
|
}
|
|
@@ -248,7 +248,7 @@ test('listDiagnoses handles acts with no standardResult instances gracefully', a
|
|
|
248
248
|
};
|
|
249
249
|
await fs.writeFile(reportPath, JSON.stringify(report));
|
|
250
250
|
try {
|
|
251
|
-
const result = await answer('linkNoText/260101T0004
|
|
251
|
+
const result = await answer('linkNoText/260101T0004-nst/0');
|
|
252
252
|
assert.equal(result.status, 'ok');
|
|
253
253
|
}
|
|
254
254
|
finally {
|
package/web/listIssues/index.ts
CHANGED
|
@@ -125,15 +125,14 @@ const getIssuesData = (report: UsableReport): ResultsSummary & Record<string, an
|
|
|
125
125
|
};
|
|
126
126
|
// Returns data and HTML lines about the history of the reports about the page of a report.
|
|
127
127
|
const getHistory = async (
|
|
128
|
-
|
|
128
|
+
reportID: string, description: string, margin: string = ''
|
|
129
129
|
): Promise<{isLatest: boolean, lines: string[]}> => {
|
|
130
130
|
// Get extracts of all reports about the page, from oldest to latest.
|
|
131
131
|
const pageExtracts = await getPageReportExtracts(description);
|
|
132
132
|
// The report's own extract may be absent if the report was quarantined between the
|
|
133
133
|
// report read and the extract listing, so an empty list means "not latest".
|
|
134
134
|
const lastExtract = pageExtracts[pageExtracts.length - 1];
|
|
135
|
-
const isLatest = lastExtract !== undefined
|
|
136
|
-
&& lastExtract.timeStamp === timeStamp && lastExtract.jobID === jobID;
|
|
135
|
+
const isLatest = lastExtract !== undefined && lastExtract.id === reportID;
|
|
137
136
|
// Initialize the history.
|
|
138
137
|
const history = {
|
|
139
138
|
isLatest,
|
|
@@ -142,7 +141,7 @@ const getHistory = async (
|
|
|
142
141
|
// For each report about the page, from oldest to latest:
|
|
143
142
|
pageExtracts.forEach((extract, index) => {
|
|
144
143
|
// Get whether it is this report.
|
|
145
|
-
const isThis = extract.
|
|
144
|
+
const isThis = extract.id === reportID;
|
|
146
145
|
// Get the tags that identify its position and whether it is this report.
|
|
147
146
|
const tags = [
|
|
148
147
|
index === 0 ? 'oldest' : '',
|
|
@@ -158,7 +157,7 @@ const getHistory = async (
|
|
|
158
157
|
content = label;
|
|
159
158
|
}
|
|
160
159
|
else {
|
|
161
|
-
const href = `/listIssues.html/${extract.
|
|
160
|
+
const href = `/listIssues.html/${extract.id}`;
|
|
162
161
|
content = `<a href="${href}">${label}</a>`;
|
|
163
162
|
}
|
|
164
163
|
// Add a list item describing the report, linked if not this report, to the history.
|
|
@@ -172,7 +171,7 @@ const getHistory = async (
|
|
|
172
171
|
if (isLatest) {
|
|
173
172
|
// Add a link to the form for requesting a retest to the lines.
|
|
174
173
|
lines.push(
|
|
175
|
-
`${margin}<p><a href="/requestRetestForm.html/${
|
|
174
|
+
`${margin}<p><a href="/requestRetestForm.html/${reportID}">Request a retest</a></p>`
|
|
176
175
|
);
|
|
177
176
|
}
|
|
178
177
|
// Replace the bare list items with the assembled section lines.
|
|
@@ -181,9 +180,9 @@ const getHistory = async (
|
|
|
181
180
|
return history;
|
|
182
181
|
};
|
|
183
182
|
// Adds parameters to a query for the answer page.
|
|
184
|
-
const populateQuery = async (
|
|
183
|
+
const populateQuery = async (reportID: string, query: Record<string, any>) => {
|
|
185
184
|
// Get the report.
|
|
186
|
-
const report = await getReport(
|
|
185
|
+
const report = await getReport(reportID);
|
|
187
186
|
// If this failed:
|
|
188
187
|
if (isReportError(report)) {
|
|
189
188
|
// Populate the query with the reason.
|
|
@@ -207,7 +206,7 @@ const populateQuery = async (timeStamp: string, jobID: string, query: Record<str
|
|
|
207
206
|
query.target = description;
|
|
208
207
|
query.testInfo = testInfo;
|
|
209
208
|
const margin = ' '.repeat(6);
|
|
210
|
-
const history = await getHistory(
|
|
209
|
+
const history = await getHistory(reportID, description, ' '.repeat(8));
|
|
211
210
|
// Add a warning if the report is not the latest about the page.
|
|
212
211
|
query.oldWarn = history.isLatest
|
|
213
212
|
? ''
|
|
@@ -248,7 +247,7 @@ const populateQuery = async (timeStamp: string, jobID: string, query: Record<str
|
|
|
248
247
|
detailsLines.push(`${margin} <ul class="nav">`);
|
|
249
248
|
const whereQuestionString = 'Where was the issue found?';
|
|
250
249
|
const labelString = `Where was the ${summary} issue found on the ${description} page?`;
|
|
251
|
-
const href = `href="/listViolators.html/${issueID}/${
|
|
250
|
+
const href = `href="/listViolators.html/${issueID}/${reportID}"`;
|
|
252
251
|
const label = `aria-label="${labelString}"`;
|
|
253
252
|
const whereLink = `<a ${href} ${label}>${whereQuestionString}</a>`;
|
|
254
253
|
// Add a violations link to the lines.
|
|
@@ -269,10 +268,10 @@ const populateQuery = async (timeStamp: string, jobID: string, query: Record<str
|
|
|
269
268
|
};
|
|
270
269
|
// Returns a page answering the target-issues question.
|
|
271
270
|
export const answer = async (pageArgs: string) => {
|
|
272
|
-
const
|
|
271
|
+
const reportID = pageArgs;
|
|
273
272
|
const query: Record<string, any> = {};
|
|
274
273
|
// Create a query to replace the placeholders.
|
|
275
|
-
await populateQuery(
|
|
274
|
+
await populateQuery(reportID, query);
|
|
276
275
|
// If this failed:
|
|
277
276
|
if (query.error) {
|
|
278
277
|
// Return the error.
|
|
@@ -49,7 +49,7 @@ after(() => {
|
|
|
49
49
|
// TESTS
|
|
50
50
|
|
|
51
51
|
test('listIssues returns an ok status with valid HTML for the mixed report', async () => {
|
|
52
|
-
const result = await answer('260101T0000
|
|
52
|
+
const result = await answer('260101T0000-mix');
|
|
53
53
|
assert.equal(result.status, 'ok');
|
|
54
54
|
assert.ok(result.answerPage);
|
|
55
55
|
const html = parse(result.answerPage);
|
|
@@ -57,12 +57,12 @@ test('listIssues returns an ok status with valid HTML for the mixed report', asy
|
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
test('listIssues includes the page description in the HTML for the mixed report', async () => {
|
|
60
|
-
const result = await answer('260101T0000
|
|
60
|
+
const result = await answer('260101T0000-mix');
|
|
61
61
|
assert.ok(result.answerPage.includes('Mixed Outcomes'));
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
test('listIssues interpolates the page description into the AI prompt block quote', async () => {
|
|
65
|
-
const result = await answer('260101T0000
|
|
65
|
+
const result = await answer('260101T0000-mix');
|
|
66
66
|
const html = parse(result.answerPage);
|
|
67
67
|
const blockQuote = html.querySelector('blockquote');
|
|
68
68
|
assert.ok(blockQuote);
|
|
@@ -74,7 +74,7 @@ test('listIssues interpolates the page description into the AI prompt block quot
|
|
|
74
74
|
});
|
|
75
75
|
|
|
76
76
|
test('listIssues includes links to listViolators for issues in the mixed report', async () => {
|
|
77
|
-
const result = await answer('260101T0000
|
|
77
|
+
const result = await answer('260101T0000-mix');
|
|
78
78
|
const html = parse(result.answerPage);
|
|
79
79
|
const violatorLinks = html.querySelectorAll('a[href*="listViolators.html"]');
|
|
80
80
|
assert.ok(violatorLinks.length >= 2);
|
|
@@ -84,12 +84,12 @@ test('listIssues includes links to listViolators for issues in the mixed report'
|
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
test('listIssues returns an error status for a nonexistent report', async () => {
|
|
87
|
-
const result = await answer('999999T9999
|
|
87
|
+
const result = await answer('999999T9999-xyz');
|
|
88
88
|
assert.equal(result.status, 'error');
|
|
89
89
|
});
|
|
90
90
|
|
|
91
91
|
test('listIssues for the all-cantTell report shows no issue links', async () => {
|
|
92
|
-
const result = await answer('260101T0001
|
|
92
|
+
const result = await answer('260101T0001-ct');
|
|
93
93
|
assert.equal(result.status, 'ok');
|
|
94
94
|
const html = parse(result.answerPage);
|
|
95
95
|
const violatorLinks = html.querySelectorAll('a[href*="listViolators.html"]');
|
|
@@ -97,7 +97,7 @@ test('listIssues for the all-cantTell report shows no issue links', async () =>
|
|
|
97
97
|
});
|
|
98
98
|
|
|
99
99
|
test('listIssues for the empty report shows no issue links', async () => {
|
|
100
|
-
const result = await answer('260101T0005
|
|
100
|
+
const result = await answer('260101T0005-emp');
|
|
101
101
|
assert.equal(result.status, 'ok');
|
|
102
102
|
const html = parse(result.answerPage);
|
|
103
103
|
const violatorLinks = html.querySelectorAll('a[href*="listViolators.html"]');
|
|
@@ -106,7 +106,7 @@ test('listIssues for the empty report shows no issue links', async () => {
|
|
|
106
106
|
|
|
107
107
|
|
|
108
108
|
test('listIssues counts the prevented rule engine as called but not able to test', async () => {
|
|
109
|
-
const result = await answer('260101T0006
|
|
109
|
+
const result = await answer('260101T0006-prv');
|
|
110
110
|
assert.equal(result.status, 'ok');
|
|
111
111
|
assert.ok(result.answerPage.includes('<li>Called: 2</li>'));
|
|
112
112
|
assert.ok(result.answerPage.includes('<li>Were able to test: 1</li>'));
|
|
@@ -114,7 +114,7 @@ test('listIssues counts the prevented rule engine as called but not able to test
|
|
|
114
114
|
});
|
|
115
115
|
|
|
116
116
|
test('listIssues counts a rule engine that reported no violations as called and able to test', async () => {
|
|
117
|
-
const result = await answer('260101T0005
|
|
117
|
+
const result = await answer('260101T0005-emp');
|
|
118
118
|
assert.equal(result.status, 'ok');
|
|
119
119
|
assert.ok(result.answerPage.includes('<li>Called: 1</li>'));
|
|
120
120
|
assert.ok(result.answerPage.includes('<li>Were able to test: 1</li>'));
|
|
@@ -129,7 +129,7 @@ test('listIssues counts an unknown prevented engine as called but not able to te
|
|
|
129
129
|
const reportPath = path.join(realUtil.reportsPath(), '260103T0000-unk.json');
|
|
130
130
|
await fs.writeFile(reportPath, JSON.stringify(report));
|
|
131
131
|
try {
|
|
132
|
-
const result = await answer('260103T0000
|
|
132
|
+
const result = await answer('260103T0000-unk');
|
|
133
133
|
assert.equal(result.status, 'ok');
|
|
134
134
|
assert.ok(result.answerPage.includes('<li>Called: 3</li>'));
|
|
135
135
|
assert.ok(result.answerPage.includes('<li>Were able to test: 1</li>'));
|
|
@@ -142,7 +142,7 @@ test('listIssues counts an unknown prevented engine as called but not able to te
|
|
|
142
142
|
test('listIssues returns an error when getPageDataStrings fails after getReport succeeds', async () => {
|
|
143
143
|
pageDataStringsOverride = {error: 'Page data strings error'};
|
|
144
144
|
try {
|
|
145
|
-
const result = await answer('260101T0000
|
|
145
|
+
const result = await answer('260101T0000-mix');
|
|
146
146
|
assert.equal(result.status, 'error');
|
|
147
147
|
assert.equal(result.message, 'Page data strings error');
|
|
148
148
|
}
|
|
@@ -158,7 +158,7 @@ test('listIssues returns an error when report facts are not obtained', async ()
|
|
|
158
158
|
urlLink: '<a href="https://example.com/mixed">https://example.com/mixed</a>'
|
|
159
159
|
};
|
|
160
160
|
try {
|
|
161
|
-
const result = await answer('260101T0000
|
|
161
|
+
const result = await answer('260101T0000-mix');
|
|
162
162
|
assert.equal(result.status, 'error');
|
|
163
163
|
assert.equal(result.message, 'Report facts not obtained');
|
|
164
164
|
}
|
|
@@ -169,7 +169,7 @@ test('listIssues returns an error when report facts are not obtained', async ()
|
|
|
169
169
|
|
|
170
170
|
test('listIssues shows violation and violator counts for an issue with multiple violators', async () => {
|
|
171
171
|
// The mul report has linkNoText with 4 violations by 3 violators.
|
|
172
|
-
const result = await answer('260101T0008
|
|
172
|
+
const result = await answer('260101T0008-mul');
|
|
173
173
|
assert.equal(result.status, 'ok');
|
|
174
174
|
assert.ok(result.answerPage.includes('<li>Violations: 4</li>'));
|
|
175
175
|
assert.ok(result.answerPage.includes('<li>Violators: 3</li>'));
|
|
@@ -198,7 +198,7 @@ test('listIssues handles acts with no standardResult instances', async () => {
|
|
|
198
198
|
};
|
|
199
199
|
await fs.writeFile(reportPath, JSON.stringify(report));
|
|
200
200
|
try {
|
|
201
|
-
const result = await answer('260101T0004
|
|
201
|
+
const result = await answer('260101T0004-nsi');
|
|
202
202
|
assert.equal(result.status, 'ok');
|
|
203
203
|
}
|
|
204
204
|
finally {
|
|
@@ -223,38 +223,38 @@ const getHistoryItems = (history: ReturnType<typeof parse>) => history
|
|
|
223
223
|
}));
|
|
224
224
|
|
|
225
225
|
test('listIssues History of an older report lists all reports, with this one unlinked and no retest question', async () => {
|
|
226
|
-
const result = await answer('260101T0000
|
|
226
|
+
const result = await answer('260101T0000-mix');
|
|
227
227
|
const history = getHistory(result.answerPage);
|
|
228
228
|
const items = getHistoryItems(history);
|
|
229
229
|
assert.equal(items.length, 2);
|
|
230
230
|
assert.match(items[0]!.text, /^\d+ days? ago \(oldest; this report\)$/);
|
|
231
231
|
assert.equal(items[0]!.href, null);
|
|
232
232
|
assert.match(items[1]!.text, /^\d+ days? ago \(latest\)$/);
|
|
233
|
-
assert.equal(items[1]!.href, '/listIssues.html/260202T0000
|
|
233
|
+
assert.equal(items[1]!.href, '/listIssues.html/260202T0000-new');
|
|
234
234
|
assert.ok(!result.answerPage.includes('Request a retest'));
|
|
235
235
|
});
|
|
236
236
|
|
|
237
237
|
test('listIssues History of the latest report ends with the retest question', async () => {
|
|
238
|
-
const result = await answer('260202T0000
|
|
238
|
+
const result = await answer('260202T0000-new');
|
|
239
239
|
const history = getHistory(result.answerPage);
|
|
240
240
|
const items = getHistoryItems(history);
|
|
241
241
|
assert.equal(items.length, 2);
|
|
242
242
|
assert.match(items[0]!.text, /^\d+ days? ago \(oldest\)$/);
|
|
243
|
-
assert.equal(items[0]!.href, '/listIssues.html/260101T0000
|
|
243
|
+
assert.equal(items[0]!.href, '/listIssues.html/260101T0000-mix');
|
|
244
244
|
assert.match(items[1]!.text, /^\d+ days? ago \(latest; this report\)$/);
|
|
245
245
|
assert.equal(items[1]!.href, null);
|
|
246
246
|
const retestLink = history.querySelector('ol + p > a');
|
|
247
247
|
assert.equal(retestLink?.text, 'Request a retest');
|
|
248
|
-
assert.equal(retestLink?.getAttribute('href'), '/requestRetestForm.html/260202T0000
|
|
248
|
+
assert.equal(retestLink?.getAttribute('href'), '/requestRetestForm.html/260202T0000-new');
|
|
249
249
|
});
|
|
250
250
|
|
|
251
251
|
test('listIssues History of the only report about a page says so and asks about retesting', async () => {
|
|
252
|
-
const result = await answer('260101T0001
|
|
252
|
+
const result = await answer('260101T0001-ct');
|
|
253
253
|
const history = getHistory(result.answerPage);
|
|
254
254
|
assert.equal(getHistoryItems(history).length, 0);
|
|
255
255
|
const paragraphs = history.querySelectorAll('p');
|
|
256
256
|
assert.equal(paragraphs[0]?.text, 'This is the only available report about the page.');
|
|
257
|
-
assert.equal(paragraphs[1]?.querySelector('a')?.getAttribute('href'), '/requestRetestForm.html/260101T0001
|
|
257
|
+
assert.equal(paragraphs[1]?.querySelector('a')?.getAttribute('href'), '/requestRetestForm.html/260101T0001-ct');
|
|
258
258
|
});
|
|
259
259
|
|
|
260
260
|
test('listIssues History tags only the ends of a list of 3 reports and the current report', async () => {
|
|
@@ -266,16 +266,16 @@ test('listIssues History tags only the ends of a list of 3 reports and the curre
|
|
|
266
266
|
await fs.writeFile(reportPath, JSON.stringify(report));
|
|
267
267
|
try {
|
|
268
268
|
// From the middle report:
|
|
269
|
-
let items = getHistoryItems(getHistory((await answer('260115T0000
|
|
269
|
+
let items = getHistoryItems(getHistory((await answer('260115T0000-mid')).answerPage));
|
|
270
270
|
assert.equal(items.length, 3);
|
|
271
271
|
assert.match(items[0]!.text, /\(oldest\)$/);
|
|
272
272
|
assert.match(items[1]!.text, /^\d+ days? ago \(this report\)$/);
|
|
273
273
|
assert.equal(items[1]!.href, null);
|
|
274
274
|
assert.match(items[2]!.text, /\(latest\)$/);
|
|
275
275
|
// From the latest report:
|
|
276
|
-
items = getHistoryItems(getHistory((await answer('260202T0000
|
|
276
|
+
items = getHistoryItems(getHistory((await answer('260202T0000-new')).answerPage));
|
|
277
277
|
assert.match(items[1]!.text, /^\d+ days? ago$/);
|
|
278
|
-
assert.equal(items[1]!.href, '/listIssues.html/260115T0000
|
|
278
|
+
assert.equal(items[1]!.href, '/listIssues.html/260115T0000-mid');
|
|
279
279
|
}
|
|
280
280
|
finally {
|
|
281
281
|
await fs.unlink(reportPath);
|
package/web/listPages/index.ts
CHANGED
|
@@ -69,9 +69,9 @@ const populateQuery = async (query: Record<string, any>) => {
|
|
|
69
69
|
const pageCount = latestExtracts.length;
|
|
70
70
|
// For each tested page:
|
|
71
71
|
for (const extract of latestExtracts) {
|
|
72
|
-
const {
|
|
72
|
+
const {id, description} = extract;
|
|
73
73
|
// Add a link to the issues in the latest report about it to the lines.
|
|
74
|
-
const link = `<a href="listIssues.html/${
|
|
74
|
+
const link = `<a href="listIssues.html/${id}" aria-label="Get test results for ${description}">${description}</a>`;
|
|
75
75
|
lines.tested.push(`${margin} <li>${link}</li>`);
|
|
76
76
|
}
|
|
77
77
|
// Add the lines to the query, as an introduced list if there are any, or else a no-pages message.
|
|
@@ -76,9 +76,9 @@ test('listPages links each page to the issues in its latest report', async () =>
|
|
|
76
76
|
const result: any = await answer();
|
|
77
77
|
const links = getPageLinks(result.answerPage);
|
|
78
78
|
const mixedLink = links.find(a => a.text === 'Mixed Outcomes');
|
|
79
|
-
assert.equal(mixedLink?.getAttribute('href'), 'listIssues.html/260202T0000
|
|
79
|
+
assert.equal(mixedLink?.getAttribute('href'), 'listIssues.html/260202T0000-new');
|
|
80
80
|
const canttellLink = links.find(a => a.text === 'All CantTell Page');
|
|
81
|
-
assert.equal(canttellLink?.getAttribute('href'), 'listIssues.html/260101T0001
|
|
81
|
+
assert.equal(canttellLink?.getAttribute('href'), 'listIssues.html/260101T0001-ct');
|
|
82
82
|
});
|
|
83
83
|
|
|
84
84
|
test('listPages contains no report summaries', async () => {
|
|
@@ -36,9 +36,9 @@ const getIssuesSummary = async () => {
|
|
|
36
36
|
const latestReportExtracts = await getReportExtracts(true);
|
|
37
37
|
// For each of them:
|
|
38
38
|
for (const reportExtract of latestReportExtracts) {
|
|
39
|
-
const {
|
|
39
|
+
const {id} = reportExtract;
|
|
40
40
|
// Get the corresponding report.
|
|
41
|
-
const report = await getReport(
|
|
41
|
+
const report = await getReport(id);
|
|
42
42
|
// If this failed:
|
|
43
43
|
if (isReportError(report)) {
|
|
44
44
|
// Populate the summary with the reason.
|
|
@@ -27,12 +27,11 @@ import {issues as issueSpecs} from 'testaro-issues';
|
|
|
27
27
|
// Adds parameters to a query for the answer page.
|
|
28
28
|
const populateQuery = async (
|
|
29
29
|
issueID: string,
|
|
30
|
-
|
|
31
|
-
jobID: string,
|
|
30
|
+
reportID: string,
|
|
32
31
|
query: Record<string, any>
|
|
33
32
|
) => {
|
|
34
33
|
// Get the report.
|
|
35
|
-
const report = await getReport(
|
|
34
|
+
const report = await getReport(reportID);
|
|
36
35
|
// If this failed:
|
|
37
36
|
if (isReportError(report)) {
|
|
38
37
|
// Populate the query with the reason.
|
|
@@ -110,7 +109,7 @@ const populateQuery = async (
|
|
|
110
109
|
}
|
|
111
110
|
}
|
|
112
111
|
const href
|
|
113
|
-
= `/listDiagnoses.html/${issueID}/${
|
|
112
|
+
= `/listDiagnoses.html/${issueID}/${reportID}/${catalogIndex}?pathID=${pathID}`;
|
|
114
113
|
const questionString = 'What diagnoses were reported';
|
|
115
114
|
const labelString = `${questionString} for violator ${index + 1}?`;
|
|
116
115
|
lines.push(
|
|
@@ -130,10 +129,10 @@ const populateQuery = async (
|
|
|
130
129
|
};
|
|
131
130
|
// Returns a page answering the violators question.
|
|
132
131
|
export const answer = async (pageArgs: string) => {
|
|
133
|
-
const [issueID,
|
|
132
|
+
const [issueID, reportID] = pageArgs.split('/') as [string, string];
|
|
134
133
|
const query: Record<string, any> = {};
|
|
135
134
|
// Create a query to replace the placeholders.
|
|
136
|
-
await populateQuery(issueID,
|
|
135
|
+
await populateQuery(issueID, reportID, query);
|
|
137
136
|
// If this failed:
|
|
138
137
|
if (query.error) {
|
|
139
138
|
// Return the error.
|
|
@@ -64,7 +64,7 @@ after(() => {
|
|
|
64
64
|
// TESTS
|
|
65
65
|
|
|
66
66
|
test('listViolators returns an ok status with valid HTML for linkNoText in the mixed report', async () => {
|
|
67
|
-
const result = await answer('linkNoText/260101T0000
|
|
67
|
+
const result = await answer('linkNoText/260101T0000-mix');
|
|
68
68
|
assert.equal(result.status, 'ok');
|
|
69
69
|
assert.ok(result.answerPage);
|
|
70
70
|
const html = parse(result.answerPage);
|
|
@@ -72,19 +72,19 @@ test('listViolators returns an ok status with valid HTML for linkNoText in the m
|
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
test('listViolators includes the issue summary in the HTML', async () => {
|
|
75
|
-
const result = await answer('linkNoText/260101T0000
|
|
75
|
+
const result = await answer('linkNoText/260101T0000-mix');
|
|
76
76
|
assert.ok(result.answerPage.includes('link not named'));
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
test('listViolators includes links to listDiagnoses for violators', async () => {
|
|
80
|
-
const result = await answer('linkNoText/260101T0000
|
|
80
|
+
const result = await answer('linkNoText/260101T0000-mix');
|
|
81
81
|
const html = parse(result.answerPage);
|
|
82
82
|
const diagnosisLinks = html.querySelectorAll('a[href*="listDiagnoses.html"]');
|
|
83
83
|
assert.ok(diagnosisLinks.length > 0);
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
test('listViolators excludes cantTell instances from the violator count', async () => {
|
|
87
|
-
const result = await answer('focusIndicationBad/260101T0000
|
|
87
|
+
const result = await answer('focusIndicationBad/260101T0000-mix');
|
|
88
88
|
assert.equal(result.status, 'ok');
|
|
89
89
|
assert.ok(result.answerPage.includes('<li>Violators: 0</li>'));
|
|
90
90
|
assert.ok(!result.answerPage.includes('<h3>Element'));
|
|
@@ -92,12 +92,12 @@ test('listViolators excludes cantTell instances from the violator count', async
|
|
|
92
92
|
|
|
93
93
|
|
|
94
94
|
test('listViolators returns an error status for a nonexistent report', async () => {
|
|
95
|
-
const result = await answer('linkNoText/999999T9999
|
|
95
|
+
const result = await answer('linkNoText/999999T9999-xyz');
|
|
96
96
|
assert.equal(result.status, 'error');
|
|
97
97
|
});
|
|
98
98
|
|
|
99
99
|
test('listViolators returns an error status for an unknown issue', async () => {
|
|
100
|
-
const result = await answer('nonexistentIssue/260101T0000
|
|
100
|
+
const result = await answer('nonexistentIssue/260101T0000-mix');
|
|
101
101
|
assert.equal(result.status, 'error');
|
|
102
102
|
});
|
|
103
103
|
|
|
@@ -105,7 +105,7 @@ test('listViolators returns an error when getReport fails', async () => {
|
|
|
105
105
|
getReportCallCount = 0;
|
|
106
106
|
failGetReportOnCall = 1;
|
|
107
107
|
try {
|
|
108
|
-
const result = await answer('linkNoText/260101T0000
|
|
108
|
+
const result = await answer('linkNoText/260101T0000-mix');
|
|
109
109
|
assert.equal(result.status, 'error');
|
|
110
110
|
assert.ok(result.message.includes('invalid'));
|
|
111
111
|
}
|
|
@@ -117,7 +117,7 @@ test('listViolators returns an error when getReport fails', async () => {
|
|
|
117
117
|
test('listViolators returns an error when getPageDataStrings fails after getReport succeeds', async () => {
|
|
118
118
|
pageDataStringsOverride = {error: 'Page data strings error'};
|
|
119
119
|
try {
|
|
120
|
-
const result = await answer('linkNoText/260101T0000
|
|
120
|
+
const result = await answer('linkNoText/260101T0000-mix');
|
|
121
121
|
assert.equal(result.status, 'error');
|
|
122
122
|
assert.equal(result.message, 'Page data strings error');
|
|
123
123
|
}
|
|
@@ -176,7 +176,7 @@ test('listViolators includes take-me-there links for text-linkable violators', a
|
|
|
176
176
|
checkpoints: []
|
|
177
177
|
};
|
|
178
178
|
await fs.writeFile(reportPath, JSON.stringify(report, null, 2));
|
|
179
|
-
const result = await answer('linkNoText/260101T0003
|
|
179
|
+
const result = await answer('linkNoText/260101T0003-tlk');
|
|
180
180
|
assert.equal(result.status, 'ok');
|
|
181
181
|
assert.ok(result.answerPage.includes('Take me there'));
|
|
182
182
|
assert.ok(result.answerPage.includes('Take me there</q> links will open'));
|
|
@@ -193,7 +193,7 @@ test('listViolators returns an error when report facts are not obtained', async
|
|
|
193
193
|
urlLink: '<a href="https://example.com/mixed">https://example.com/mixed</a>'
|
|
194
194
|
};
|
|
195
195
|
try {
|
|
196
|
-
const result = await answer('linkNoText/260101T0000
|
|
196
|
+
const result = await answer('linkNoText/260101T0000-mix');
|
|
197
197
|
assert.equal(result.status, 'error');
|
|
198
198
|
assert.equal(result.message, 'Report facts not obtained');
|
|
199
199
|
}
|
|
@@ -269,7 +269,7 @@ test('listViolators handles instances with missing catalogIndex and acts with no
|
|
|
269
269
|
};
|
|
270
270
|
await fs.writeFile(reportPath, JSON.stringify(report));
|
|
271
271
|
try {
|
|
272
|
-
const result = await answer('linkNoText/260101T0004
|
|
272
|
+
const result = await answer('linkNoText/260101T0004-nvi');
|
|
273
273
|
assert.equal(result.status, 'ok');
|
|
274
274
|
// Should show plural violator count.
|
|
275
275
|
assert.ok(result.answerPage.includes('<li>Violators: 2</li>'));
|
|
@@ -336,7 +336,7 @@ test('listViolators uses HTML fallback when catalog has no tagName and pathID is
|
|
|
336
336
|
};
|
|
337
337
|
await fs.writeFile(reportPath, JSON.stringify(report));
|
|
338
338
|
try {
|
|
339
|
-
const result = await answer('linkNoText/260101T0005
|
|
339
|
+
const result = await answer('linkNoText/260101T0005-htm');
|
|
340
340
|
assert.equal(result.status, 'ok');
|
|
341
341
|
// The tagName should fall back to HTML.
|
|
342
342
|
assert.ok(result.answerPage.includes('HTML'));
|
package/web/reannotate/index.ts
CHANGED
|
@@ -31,9 +31,9 @@ export const answer = async (authCode: string) => {
|
|
|
31
31
|
if (reportFileNames.length) {
|
|
32
32
|
// For each report file:
|
|
33
33
|
for (const reportFileName of reportFileNames) {
|
|
34
|
-
const
|
|
34
|
+
const reportID = reportFileName.slice(0, -5);
|
|
35
35
|
// Get the report.
|
|
36
|
-
const report = await getReport(
|
|
36
|
+
const report = await getReport(reportID);
|
|
37
37
|
// If this failed:
|
|
38
38
|
if (isReportError(report)) {
|
|
39
39
|
// Return an error page.
|
|
@@ -45,7 +45,7 @@ export const answer = async (authCode: string) => {
|
|
|
45
45
|
// Otherwise, i.e. if it succeeded, reannotate it in place.
|
|
46
46
|
await annotateReportObject(report);
|
|
47
47
|
// Save the reannotated report.
|
|
48
|
-
await fs.writeFile(getReportPath(
|
|
48
|
+
await fs.writeFile(getReportPath(reportID), getJSON(report));
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
// Otherwise, i.e. if it failed:
|
|
@@ -20,9 +20,8 @@ const populateQuery = async (query: Record<string, any>) => {
|
|
|
20
20
|
const reportExtracts = await getReportExtracts();
|
|
21
21
|
// For each report:
|
|
22
22
|
for (const reportExtract of reportExtracts) {
|
|
23
|
-
const {jobID, timeStamp} = reportExtract;
|
|
24
23
|
// Get it.
|
|
25
|
-
const report = await getReport(
|
|
24
|
+
const report = await getReport(reportExtract.id);
|
|
26
25
|
// If this failed:
|
|
27
26
|
if (isReportError(report)) {
|
|
28
27
|
// Populate the query with the reason.
|
package/web/reportDeletion.ts
CHANGED
|
@@ -25,8 +25,7 @@ import path from 'node:path';
|
|
|
25
25
|
|
|
26
26
|
// A summary of an available report, as shown in a deletion form.
|
|
27
27
|
type ReportSpec = {
|
|
28
|
-
|
|
29
|
-
jobID: string;
|
|
28
|
+
id: string;
|
|
30
29
|
issueCount: number;
|
|
31
30
|
preventedEngineCount: number;
|
|
32
31
|
url: string;
|
|
@@ -91,9 +90,9 @@ export const reportDeletionForm = async (
|
|
|
91
90
|
const reportSpecs: ReportSpec[] = [];
|
|
92
91
|
// For each report:
|
|
93
92
|
for (const reportName of reportNames) {
|
|
94
|
-
const
|
|
93
|
+
const reportID = reportName.slice(0, -5);
|
|
95
94
|
// Get the report.
|
|
96
|
-
const report = await getReport(
|
|
95
|
+
const report = await getReport(reportID);
|
|
97
96
|
// If this failed:
|
|
98
97
|
if (isReportError(report)) {
|
|
99
98
|
// Return why.
|
|
@@ -107,29 +106,27 @@ export const reportDeletionForm = async (
|
|
|
107
106
|
const {issueCount, preventedEngineCount, url} = reportFacts;
|
|
108
107
|
// Otherwise, i.e. if it succeeded, add the summary to the array.
|
|
109
108
|
reportSpecs.push({
|
|
110
|
-
|
|
111
|
-
jobID,
|
|
109
|
+
id: reportID,
|
|
112
110
|
issueCount,
|
|
113
111
|
preventedEngineCount,
|
|
114
112
|
url
|
|
115
113
|
});
|
|
116
114
|
}
|
|
117
|
-
// Sort the summaries primarily by URL and secondarily by
|
|
118
|
-
objectSort(reportSpecs, '
|
|
115
|
+
// Sort the summaries primarily by URL and secondarily by report identifier.
|
|
116
|
+
objectSort(reportSpecs, 'id', 'alpha');
|
|
119
117
|
objectSort(reportSpecs, 'url', 'alpha');
|
|
120
118
|
const lines: string[] = [];
|
|
121
119
|
const margin = ' '.repeat(12);
|
|
122
120
|
let anyDeletable = false;
|
|
123
121
|
// For each summary:
|
|
124
122
|
reportSpecs.forEach((reportSpec, index) => {
|
|
125
|
-
const {
|
|
126
|
-
const
|
|
127
|
-
const specString = `<code>${url}</code> (<code>${jobName}</code>): preventions ${preventedEngineCount}, issues ${issueCount}`;
|
|
123
|
+
const {id, issueCount, preventedEngineCount, url} = reportSpec;
|
|
124
|
+
const specString = `<code>${url}</code> (<code>${id}</code>): preventions ${preventedEngineCount}, issues ${issueCount}`;
|
|
128
125
|
// If its report is deletable:
|
|
129
126
|
if (isDeletable(reportSpecs, index)) {
|
|
130
127
|
// Add a line with a deletion checkbox.
|
|
131
128
|
lines.push(
|
|
132
|
-
`${margin}<p><input type="checkbox" name="report" value="${
|
|
129
|
+
`${margin}<p><input type="checkbox" name="report" value="${id}"> ${specString}</p>`
|
|
133
130
|
);
|
|
134
131
|
anyDeletable = true;
|
|
135
132
|
}
|