@jrpool/kilotest 24.0.8 → 24.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jrpool/kilotest",
3
- "version": "24.0.8",
3
+ "version": "24.1.1",
4
4
  "description": "An ensemble testing service with a focus on accessibility",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -0,0 +1,259 @@
1
+ /*
2
+ index.js
3
+ Answers the report-issues question.
4
+ */
5
+
6
+ // IMPORTS
7
+
8
+ const {
9
+ getPageDataStrings,
10
+ getReport,
11
+ getToolNamesString,
12
+ getWCAGLink,
13
+ getWeightName,
14
+ htmlSafe,
15
+ isHidden,
16
+ isValidReport,
17
+ objectSort,
18
+ tools
19
+ } = require('../util');
20
+ const {issues} = require('testilo/procs/score/tic');
21
+ const fs = require('fs/promises');
22
+ const path = require('path');
23
+
24
+ // FUNCTIONS
25
+
26
+ // Gets data on the issues reported in a report.
27
+ const getIssuesData = async (timeStamp, jobID) => {
28
+ // Get the report.
29
+ const report = await getReport(timeStamp, jobID);
30
+ // If it is valid:
31
+ if (typeof report === 'object' && isValidReport(report)) {
32
+ const issuesData = {
33
+ reporters: new Set(),
34
+ reporterCount: 0,
35
+ reportersString: '',
36
+ violators: new Set(),
37
+ violatorCount: 0,
38
+ preventions: {},
39
+ issuesObject: {},
40
+ issueCount: 0,
41
+ issues: []
42
+ };
43
+ const {issuesObject, reporters, violators} = issuesData;
44
+ // For each act in it:
45
+ report.acts.forEach(act => {
46
+ // If it is a test act:
47
+ if (act.type === 'test') {
48
+ const {result, which} = act;
49
+ const instances = result?.standardResult?.instances ?? [];
50
+ // For each of its standard instances:
51
+ instances.forEach(instance => {
52
+ const {issueID} = instance;
53
+ // If the instance has a non-ignorable classified issue:
54
+ if (issueID && issues[issueID] && issueID !== 'ignorable') {
55
+ // Ensure that the issues data include data on the issue.
56
+ issuesObject[issueID] ??= {
57
+ issueID,
58
+ weight: issues[issueID].weight ?? 0,
59
+ reporters: new Set(),
60
+ reporterCount: 0,
61
+ reportersString: '',
62
+ violators: new Set(),
63
+ violatorCount: 0
64
+ };
65
+ // Ensure that the tool is in the issues data.
66
+ reporters.add(which);
67
+ // Ensure that it is in the issue data.
68
+ issuesObject[issueID].reporters.add(which);
69
+ const {catalogIndex} = instance;
70
+ // If the instance has a catalog index:
71
+ if (catalogIndex) {
72
+ // Ensure that the violator is in the issues data.
73
+ violators.add(catalogIndex);
74
+ // Ensure that it is in the issue data.
75
+ issuesObject[issueID].violators.add(catalogIndex);
76
+ }
77
+ }
78
+ });
79
+ }
80
+ });
81
+ // Populate the unpopulated subproperties of the issues data.
82
+ issuesData.reporterCount = issuesData.reporters.size;
83
+ issuesData.reportersString = getToolNamesString(issuesData.reporters);
84
+ issuesData.violatorCount = issuesData.violators.size;
85
+ issuesData.preventions = report.jobData.preventions;
86
+ issuesData.issueCount = Object.keys(issuesData.issuesObject).length;
87
+ issuesData.issues = Object.values(issuesData.issuesObject);
88
+ // For each issue in the issues data:
89
+ issuesData.issues.forEach(issue => {
90
+ // Populate its unpopulated properties.
91
+ issue.reporterCount = issue.reporters.size;
92
+ issue.reportersString = getToolNamesString(issue.reporters);
93
+ issue.violatorCount = issue.violators.size;
94
+ });
95
+ // Sort the issues alphabetically by reporters string.
96
+ objectSort(issuesData.issues, 'reportersString', 'alpha');
97
+ // Sort the issues again in descending reporter-count order, making this the primary order.
98
+ objectSort(issuesData.issues, 'reporterCount', 'numericDown');
99
+ // Return the issues data.
100
+ return issuesData;
101
+ }
102
+ };
103
+ // Adds parameters to a query for the answer page.
104
+ const populateQuery = async (timeStamp, jobID, query) => {
105
+ // Get fact descriptions for the report.
106
+ const pageDataStrings = await getPageDataStrings(timeStamp, jobID);
107
+ const {what, urlLink, testInfo} = pageDataStrings;
108
+ const issuesData = await getIssuesData(timeStamp, jobID);
109
+ // If this failed:
110
+ if (typeof issuesData === 'string') {
111
+ // Return this.
112
+ return issuesData;
113
+ }
114
+ const {
115
+ issueCount,
116
+ preventions,
117
+ reporterCount,
118
+ reportersString,
119
+ violatorCount
120
+ } = issuesData;
121
+ // Add an issue count description to the query.
122
+ query.issueCount = issueCount === 1 ? '1 issue was' : `${issueCount} issues were`;
123
+ query.reporterCount = reporterCount === 1 ? '1 tool' : `${reporterCount} tools`;
124
+ // Add a reporter count and list to the query.
125
+ query.reporters = reportersString;
126
+ // Add a violator count to the query.
127
+ query.violatorCount = violatorCount === 1 ? '1 violator was' : `${violatorCount} violators were`;
128
+ // Add page data to the query.
129
+ query.target = what;
130
+ query.urlLink = urlLink;
131
+ query.testInfo = testInfo;
132
+ query.timeStamp = timeStamp;
133
+ query.jobID = jobID;
134
+ const preventionStrings = [];
135
+ const margin = ' '.repeat(6);
136
+ Object.keys(preventions).forEach(preventedToolID => {
137
+ const toolName = tools[preventedToolID];
138
+ const toolNameString = `${toolName[0]} (${toolName[1]})`;
139
+ const causeString = htmlSafe(preventions[preventedToolID]);
140
+ const preventionString = `${margin}<li>Page not testable by ${toolNameString}: ${causeString}</li>`;
141
+ preventionStrings.push(preventionString);
142
+ });
143
+ query.preventions = preventionStrings.join('\n');
144
+ // For each weight:
145
+ [4, 3, 2, 1].forEach(weight => {
146
+ // Initialize data on issues having the weight.
147
+ const weightData = [];
148
+ // Initialize the lines for the weight.
149
+ const weightLines = [];
150
+ // For each issue:
151
+ issuesData.issues.forEach(issueData => {
152
+ const {
153
+ issueID, reporterCount, reportersString, violatorCount, weight: issueWeight
154
+ } = issueData;
155
+ // If it has the weight:
156
+ if (issueWeight === weight) {
157
+ const issue = issues[issueID];
158
+ const {wcag, why} = issue;
159
+ const wcagLink = `<a href="${getWCAGLink(wcag)}">${wcag}</a>`;
160
+ // Add data on it to the weight data.
161
+ weightData.push({
162
+ issueID,
163
+ summary: issue.summary,
164
+ why,
165
+ wcag: wcagLink,
166
+ reporterCount,
167
+ reportersString,
168
+ violatorCount
169
+ });
170
+ }
171
+ });
172
+ const weightName = getWeightName(weight);
173
+ // Add the issue count to the query.
174
+ query[`${weightName}Count`] = weightData.length;
175
+ // If any reported issues have the weight:
176
+ if (weightData.length) {
177
+ // Add the start of a list of the issues with the weight to the lines.
178
+ weightLines.push(`${margin}<ul class="headed">`);
179
+ // For each issue with the weight:
180
+ weightData.forEach(weightIssue => {
181
+ const {issueID, reporterCount, reportersString, violatorCount, wcag, why} = weightIssue;
182
+ // Add the start of a list item to the lines.
183
+ weightLines.push(`${margin} <li>`);
184
+ // Add a heading summarizing the issue to the lines.
185
+ weightLines.push(`${margin} <h5>${weightIssue.summary}</h5>`);
186
+ // Add the start of alist of facts about the issue to the lines.
187
+ weightLines.push(`${margin} <ul class="pseudoTopLevel">`);
188
+ // Add the issue facts to the lines.
189
+ weightLines.push(`${margin} <li>Why it matters: ${why}`);
190
+ weightLines.push(`${margin} <li>Related WCAG standard: ${wcag}`);
191
+ const reporterCountString = reporterCount === 1 ? '1 tool' : `${reporterCount} tools`;
192
+ weightLines.push(
193
+ `${margin} <li>Reported by ${reporterCountString} (${reportersString})</li>`
194
+ );
195
+ const violatorCountString = violatorCount === 1
196
+ ? '1 violator was'
197
+ : `${violatorCount} violators were`;
198
+ weightLines.push(`${margin} <li>${violatorCountString} reported</li>`);
199
+ // Add the end of the fact list to the lines.
200
+ weightLines.push(`${margin} </ul>`);
201
+ // Add the start of a link list to the lines.
202
+ weightLines.push(`${margin} <ul class="nav">`);
203
+ const whereQuestionString = 'Where was the issue found?';
204
+ const labelString = `Where was the ${weightIssue.summary} issue found on the ${what} page?`;
205
+ const href = `href="/reportIssue.html/${issueID}/${timeStamp}/${jobID}"`;
206
+ const label = `aria-label="${labelString}"`;
207
+ const whereLink = `<a ${href} ${label}>${whereQuestionString}</a>`;
208
+ // Add a violations link to the lines.
209
+ weightLines.push(`${margin} <li>${whereLink}</li>`);
210
+ // Add the end of the link list to the lines.
211
+ weightLines.push(`${margin} </ul>`);
212
+ // Add the end of the list item to the lines.
213
+ weightLines.push(`${margin} </li>`);
214
+ });
215
+ // Add the end of the list of issues with the weight to the lines.
216
+ weightLines.push(`${margin}</ul>`);
217
+ // Add the lines documenting the issues with the weight to the query.
218
+ query[`${weightName}Details`] = weightLines.join('\n');
219
+ }
220
+ // Otherwise, i.e. if no reported issues have the weight:
221
+ else {
222
+ query[`${weightName}Details`] = `${margin} <p>None</p>`;
223
+ }
224
+ });
225
+ };
226
+ // Returns a page answering the target-issues question.
227
+ exports.answer = async pageArgs => {
228
+ const [timeStamp, jobID] = pageArgs.split('/');
229
+ const reportIsHidden = await isHidden(timeStamp, jobID);
230
+ // If the report is not available:
231
+ if (reportIsHidden) {
232
+ return {
233
+ status: 'error',
234
+ message: 'Report not available'
235
+ };
236
+ }
237
+ const query = {};
238
+ // Create a query to replace the placeholders.
239
+ await populateQuery(timeStamp, jobID, query);
240
+ // If the report facts were obtained:
241
+ if (query.testInfo) {
242
+ // Get the template.
243
+ let answerPage = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8');
244
+ // Replace its placeholders.
245
+ Object.keys(query).forEach(param => {
246
+ answerPage = answerPage.replace(new RegExp(`__${param}__`, 'g'), query[param]);
247
+ });
248
+ // Return the populated page.
249
+ return {
250
+ status: 'ok',
251
+ answerPage
252
+ };
253
+ }
254
+ // Otherwise, i.e. if they were not obtained, report this.
255
+ return {
256
+ status: 'error',
257
+ error: 'Report processing failed'
258
+ };
259
+ };
@@ -29,19 +29,27 @@
29
29
  <h3>Details</h3>
30
30
  <details>
31
31
  <summary>Highest priority: __highestCount__</summary>
32
- __highestDetails__
32
+ <ul class="headed">
33
+ __highestDetails__
34
+ </ul>
33
35
  </details>
34
36
  <details>
35
37
  <summary>High priority: __highCount__</summary>
36
- __highDetails__
38
+ <ul class="headed">
39
+ __highDetails__
40
+ </ul>
37
41
  </details>
38
42
  <details>
39
43
  <summary>Low priority: __lowCount__</summary>
40
- __lowDetails__
44
+ <ul class="headed">
45
+ __lowDetails__
46
+ </ul>
41
47
  </details>
42
48
  <details>
43
49
  <summary>Lowest priority: __lowestCount__</summary>
44
- __lowestDetails__
50
+ <ul class="headed">
51
+ __lowestDetails__
52
+ </ul>
45
53
  </details>
46
54
  </main>
47
55
  </body>
@@ -5,16 +5,13 @@
5
5
 
6
6
  // IMPORTS
7
7
 
8
+ const {getData} = require('./util');
8
9
  const {
9
10
  getPageDataStrings,
10
- getReport,
11
- getToolNamesString,
12
11
  getWCAGLink,
13
12
  getWeightName,
14
13
  htmlSafe,
15
14
  isHidden,
16
- isValidReport,
17
- objectSort,
18
15
  tools
19
16
  } = require('../util');
20
17
  const {issues} = require('testilo/procs/score/tic');
@@ -23,114 +20,37 @@ const path = require('path');
23
20
 
24
21
  // FUNCTIONS
25
22
 
26
- // Gets data on the issues reported in a report.
27
- const getIssuesData = async (timeStamp, jobID) => {
28
- // Get the report.
29
- const report = await getReport(timeStamp, jobID);
30
- // If it is valid:
31
- if (typeof report === 'object' && isValidReport(report)) {
32
- const issuesData = {
33
- reporters: new Set(),
34
- reporterCount: 0,
35
- reportersString: '',
36
- violators: new Set(),
37
- violatorCount: 0,
38
- preventions: {},
39
- issuesObject: {},
40
- issueCount: 0,
41
- issues: []
42
- };
43
- const {issuesObject, reporters, violators} = issuesData;
44
- // For each act in it:
45
- report.acts.forEach(act => {
46
- // If it is a test act:
47
- if (act.type === 'test') {
48
- const {result, which} = act;
49
- const instances = result?.standardResult?.instances ?? [];
50
- // For each of its standard instances:
51
- instances.forEach(instance => {
52
- const {issueID} = instance;
53
- // If the instance has a non-ignorable classified issue:
54
- if (issueID && issues[issueID] && issueID !== 'ignorable') {
55
- // Ensure that the issues data include data on the issue.
56
- issuesObject[issueID] ??= {
57
- issueID,
58
- weight: issues[issueID].weight ?? 0,
59
- reporters: new Set(),
60
- reporterCount: 0,
61
- reportersString: '',
62
- violators: new Set(),
63
- violatorCount: 0
64
- };
65
- // Ensure that the tool is in the issues data.
66
- reporters.add(which);
67
- // Ensure that it is in the issue data.
68
- issuesObject[issueID].reporters.add(which);
69
- const {catalogIndex} = instance;
70
- // If the instance has a catalog index:
71
- if (catalogIndex) {
72
- // Ensure that the violator is in the issues data.
73
- violators.add(catalogIndex);
74
- // Ensure that it is in the issue data.
75
- issuesObject[issueID].violators.add(catalogIndex);
76
- }
77
- }
78
- });
79
- }
80
- });
81
- // Populate the unpopulated subproperties of the issues data.
82
- issuesData.reporterCount = issuesData.reporters.size;
83
- issuesData.reportersString = getToolNamesString(issuesData.reporters);
84
- issuesData.violatorCount = issuesData.violators.size;
85
- issuesData.preventions = report.jobData.preventions;
86
- issuesData.issueCount = Object.keys(issuesData.issuesObject).length;
87
- issuesData.issues = Object.values(issuesData.issuesObject);
88
- // For each issue in the issues data:
89
- issuesData.issues.forEach(issue => {
90
- // Populate its unpopulated properties.
91
- issue.reporterCount = issue.reporters.size;
92
- issue.reportersString = getToolNamesString(issue.reporters);
93
- issue.violatorCount = issue.violators.size;
94
- });
95
- // Sort the issues alphabetically by reporters string.
96
- objectSort(issuesData.issues, 'reportersString', 'alpha');
97
- // Sort the issues again in descending reporter-count order, making this the primary order.
98
- objectSort(issuesData.issues, 'reporterCount', 'numericDown');
99
- // Return the issues data.
100
- return issuesData;
101
- }
102
- };
103
23
  // Adds parameters to a query for the answer page.
104
24
  const populateQuery = async (timeStamp, jobID, query) => {
105
- // Get fact descriptions for the report.
106
- const pageDataStrings = await getPageDataStrings(timeStamp, jobID);
107
- const {what, urlLink, testInfo} = pageDataStrings;
108
- const issuesData = await getIssuesData(timeStamp, jobID);
109
- // If this failed:
25
+ // Get data on the page and its issues according to the report.
26
+ const data = await getData(timeStamp, jobID);
27
+ const {pageData, issuesData} = data;
28
+ // If the page data are invalid:
29
+ if (typeof pageData === 'string') {
30
+ // Return this.
31
+ return pageData;
32
+ }
33
+ // Otherwise, if the issues data are invalid:
110
34
  if (typeof issuesData === 'string') {
111
35
  // Return this.
112
36
  return issuesData;
113
37
  }
114
- const {
115
- issueCount,
116
- preventions,
117
- reporterCount,
118
- reportersString,
119
- violatorCount
120
- } = issuesData;
121
- // Add an issue count description to the query.
122
- query.issueCount = issueCount === 1 ? '1 issue was' : `${issueCount} issues were`;
123
- query.reporterCount = reporterCount === 1 ? '1 tool' : `${reporterCount} tools`;
124
- // Add a reporter count and list to the query.
125
- query.reporters = reportersString;
126
- // Add a violator count to the query.
127
- query.violatorCount = violatorCount === 1 ? '1 violator was' : `${violatorCount} violators were`;
38
+ // Otherwise, get fact descriptions for the page.
39
+ const pageInfo = await getPageDataStrings(timeStamp, jobID, pageData);
40
+ const {what, urlLink, testInfo} = pageInfo;
128
41
  // Add page data to the query.
129
42
  query.target = what;
130
43
  query.urlLink = urlLink;
131
44
  query.testInfo = testInfo;
132
- query.timeStamp = timeStamp;
133
- query.jobID = jobID;
45
+ const {
46
+ reporterList,
47
+ reporterCount,
48
+ violatorCount,
49
+ issueCount,
50
+ preventions,
51
+ issues
52
+ } = issuesData;
53
+ // Initialize strings for the prevention notices query property.
134
54
  const preventionStrings = [];
135
55
  const margin = ' '.repeat(6);
136
56
  Object.keys(preventions).forEach(preventedToolID => {
@@ -140,86 +60,84 @@ const populateQuery = async (timeStamp, jobID, query) => {
140
60
  const preventionString = `${margin}<li>Page not testable by ${toolNameString}: ${causeString}</li>`;
141
61
  preventionStrings.push(preventionString);
142
62
  });
63
+ // Add prevention notices to the query.
143
64
  query.preventions = preventionStrings.join('\n');
65
+ // Add report data to the query.
66
+ query.timeStamp = timeStamp;
67
+ query.jobID = jobID;
68
+ // Add reporter information to the query.
69
+ query.reporterCount = reporterCount === 1 ? '1 tool' : `${reporterCount} tools`;
70
+ query.reporters = reporterList;
71
+ // Add a summary of the issues to the query.
72
+ query.issueCount = issueCount === 1 ? '1 issue was' : `${issueCount} issues were`;
73
+ query.highestCount = issues[4].length;
74
+ query.highCount = issues[3].length;
75
+ query.lowCount = issues[2].length;
76
+ query.lowestCount = issues[1].length;
77
+ // Add a violator count to the query.
78
+ query.violatorCount = violatorCount === 1 ? '1 violator was' : `${violatorCount} violators were`;
144
79
  // For each weight:
145
80
  [4, 3, 2, 1].forEach(weight => {
146
- // Initialize data on issues having the weight.
147
- const weightData = [];
148
- // Initialize the lines for the weight.
149
- const weightLines = [];
150
- // For each issue:
151
- issuesData.issues.forEach(issueData => {
152
- const {
153
- issueID, reporterCount, reportersString, violatorCount, weight: issueWeight
154
- } = issueData;
155
- // If it has the weight:
156
- if (issueWeight === weight) {
157
- const issue = issues[issueID];
158
- const {wcag, why} = issue;
159
- const wcagLink = `<a href="${getWCAGLink(wcag)}">${wcag}</a>`;
160
- // Add data on it to the weight data.
161
- weightData.push({
162
- issueID,
163
- summary: issue.summary,
164
- why,
165
- wcag: wcagLink,
166
- reporterCount,
167
- reportersString,
168
- violatorCount
169
- });
170
- }
171
- });
172
81
  const weightName = getWeightName(weight);
173
- // Add the issue count to the query.
174
- query[`${weightName}Count`] = weightData.length;
82
+ const weightIssues = issues[weight];
175
83
  // If any reported issues have the weight:
176
- if (weightData.length) {
177
- // Add the start of a list of the issues with the weight to the lines.
178
- weightLines.push(`${margin}<ul class="headed">`);
84
+ if (weightIssues.length) {
85
+ // Initialize lines for the weight details query property.
86
+ const detailsLines = [];
179
87
  // For each issue with the weight:
180
- weightData.forEach(weightIssue => {
181
- const {issueID, reporterCount, reportersString, violatorCount, wcag, why} = weightIssue;
88
+ weightIssues.forEach(issueData => {
89
+ const weightIssueCount = weightIssues.length;
90
+ // Add the issue count to the query.
91
+ query[`${weightName}Count`] = weightIssueCount;
92
+ const {
93
+ issueID,
94
+ reporterCount,
95
+ reporterList,
96
+ summary,
97
+ violatorCount,
98
+ wcag,
99
+ why
100
+ } = issueData;
101
+ const wcagLink = `<a href="${getWCAGLink(wcag)}">${wcag}</a>`;
182
102
  // Add the start of a list item to the lines.
183
- weightLines.push(`${margin} <li>`);
103
+ detailsLines.push(`${margin} <li>`);
184
104
  // Add a heading summarizing the issue to the lines.
185
- weightLines.push(`${margin} <h5>${weightIssue.summary}</h5>`);
186
- // Add the start of alist of facts about the issue to the lines.
187
- weightLines.push(`${margin} <ul class="pseudoTopLevel">`);
105
+ detailsLines.push(`${margin} <h5>${summary}</h5>`);
106
+ // Add the start of a fact list about the issue to the lines.
107
+ detailsLines.push(`${margin} <ul class="pseudoTopLevel">`);
188
108
  // Add the issue facts to the lines.
189
- weightLines.push(`${margin} <li>Why it matters: ${why}`);
190
- weightLines.push(`${margin} <li>Related WCAG standard: ${wcag}`);
109
+ detailsLines.push(`${margin} <li>Why it matters: ${why}`);
110
+ detailsLines.push(`${margin} <li>Related WCAG standard: ${wcagLink}`);
191
111
  const reporterCountString = reporterCount === 1 ? '1 tool' : `${reporterCount} tools`;
192
- weightLines.push(
193
- `${margin} <li>Reported by ${reporterCountString} (${reportersString})</li>`
112
+ detailsLines.push(
113
+ `${margin} <li>Reported by ${reporterCountString} (${reporterList})</li>`
194
114
  );
195
115
  const violatorCountString = violatorCount === 1
196
116
  ? '1 violator was'
197
117
  : `${violatorCount} violators were`;
198
- weightLines.push(`${margin} <li>${violatorCountString} reported</li>`);
118
+ detailsLines.push(`${margin} <li>${violatorCountString} reported</li>`);
199
119
  // Add the end of the fact list to the lines.
200
- weightLines.push(`${margin} </ul>`);
120
+ detailsLines.push(`${margin} </ul>`);
201
121
  // Add the start of a link list to the lines.
202
- weightLines.push(`${margin} <ul class="nav">`);
122
+ detailsLines.push(`${margin} <ul class="nav">`);
203
123
  const whereQuestionString = 'Where was the issue found?';
204
- const labelString = `Where was the ${weightIssue.summary} issue found on the ${what} page?`;
124
+ const labelString = `Where was the ${summary} issue found on the ${what} page?`;
205
125
  const href = `href="/reportIssue.html/${issueID}/${timeStamp}/${jobID}"`;
206
126
  const label = `aria-label="${labelString}"`;
207
127
  const whereLink = `<a ${href} ${label}>${whereQuestionString}</a>`;
208
128
  // Add a violations link to the lines.
209
- weightLines.push(`${margin} <li>${whereLink}</li>`);
129
+ detailsLines.push(`${margin} <li>${whereLink}</li>`);
210
130
  // Add the end of the link list to the lines.
211
- weightLines.push(`${margin} </ul>`);
131
+ detailsLines.push(`${margin} </ul>`);
212
132
  // Add the end of the list item to the lines.
213
- weightLines.push(`${margin} </li>`);
133
+ detailsLines.push(`${margin} </li>`);
214
134
  });
215
- // Add the end of the list of issues with the weight to the lines.
216
- weightLines.push(`${margin}</ul>`);
217
- // Add the lines documenting the issues with the weight to the query.
218
- query[`${weightName}Details`] = weightLines.join('\n');
135
+ // Add the weight details lines to the query.
136
+ query[`${weightName}Details`] = detailsLines.join('\n');
219
137
  }
220
138
  // Otherwise, i.e. if no reported issues have the weight:
221
139
  else {
222
- query[`${weightName}Details`] = `${margin} <p>None</p>`;
140
+ query[`${weightName}Details`] = `${margin} <li>None</li>`;
223
141
  }
224
142
  });
225
143
  };