testilo 31.3.1 → 32.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/README.md CHANGED
@@ -230,13 +230,52 @@ The `call` module will retrieve the named target list.
230
230
  The `batch` module will convert the target list to a batch.
231
231
  The `call` module will save the batch as a JSON file named `x.json` (replacing `x` with the list ID) in the `batches` subdirectory of the `SPECDIR` directory.
232
232
 
233
- ### Issues to script
233
+ ### Script creation
234
234
 
235
235
  You can use the `script()` function of the `script` module to simplify the creation of scripts.
236
236
 
237
- In its simplest form, `script()` requires only one argument, a string that will serve as the ID of the script. Called in this way, `script()` produces a script that tells Testaro to perform all of the tests defined by all of the tools integrated by Testaro.
237
+ #### Without options
238
238
 
239
- If you want a more focused script, you can add additional arguments to `script()`. First you must add the ID of a Testilo issue classification (such as `tic99`). After that, you must add one or more arguments giving the IDs of issues in that classification. The latest Testilo issue classification classifies about a thousand rules of the 10 Testaro tools into about 300 _issues_. The classification is found in a file whose name begins with `tic` in the `procs/score` directory. For example, one issue in the `tic40.js` file is `mainNot1`. Four rules are classified as belonging to that issue: rule `main_element_only_one` of the `aslint` tool and 3 more rules defined by 3 other tools. You can also create custom classifications and save them in a `score` subdirectory of the `FUNCTIONDIR` directory.
239
+ In its simplest form, `script()` requires two string arguments:
240
+ - An ID for the script
241
+ - A description of the script
242
+
243
+ Called in this way, `script()` produces a script that tells Testaro to perform the tests for all of the evaluation rules defined by all of the tools integrated by Testaro.
244
+
245
+ #### With options
246
+
247
+ If you want a more focused script, you can add an additional option argument to `script()`. The option argument lets you restrict the rules to be tested for. You may choose between restrictions of two types:
248
+ - Tools
249
+ - Issues
250
+
251
+ The option argument is an object. Its properties depend on the restriction type.
252
+
253
+ For a tool restriction, it has this structure:
254
+
255
+ ```javascript
256
+ {
257
+ type: 'tools',
258
+ specs: ['toolID0', 'toolID1', 'toolIDn']
259
+ }
260
+ ```
261
+
262
+ For an issue restriction, it has this structure:
263
+
264
+ ```javascript
265
+ {
266
+ type: 'issues',
267
+ specs: {
268
+ issues: issueClassification,
269
+ issueIDs: ['issue0', 'issue1', 'issueN']
270
+ }
271
+ }
272
+ ```
273
+
274
+ If you specify tool options, the script will prescribe the tests for all evaluation rules of the tools that you specify.
275
+
276
+ If you specify issue options, the script will prescribe the tests for all evaluation rules that are classified in the issues whose IDs you specify. Any tools that do not have any of those rules will be omitted. The value of `specs/issues` is an issue classification object, with a structure like the one in `procs/score/tic40.js`. That one classifies about 1000 rules into about 300 issues.
277
+
278
+ For example, one issue in the `tic40.js` file is `mainNot1`. Four rules are classified as belonging to that issue: rule `main_element_only_one` of the `aslint` tool and 3 more rules defined by 3 other tools. You can also create custom classifications and save them in a `score` subdirectory of the `FUNCTIONDIR` directory.
240
279
 
241
280
  #### Invocation
242
281
 
@@ -244,34 +283,40 @@ There are two ways to use the `script` module.
244
283
 
245
284
  ##### By a module
246
285
 
247
- A module can invoke `script()` in this way:
286
+ A module can invoke `script()` in one of these ways:
287
+
288
+ ```javaScript
289
+ const {script} = require('testilo/script');
290
+ const scriptObj = script('monthly', 'landmarks');
291
+ ```
248
292
 
249
293
  ```javaScript
250
294
  const {script} = require('testilo/script');
251
- const {issues} = require('testilo/procs/score/tic99');
252
- const scriptObj = script('monthly', 'landmarks', issues, 'regionNoText', 'mainNot1');
295
+ const options = {…};
296
+ const scriptObj = script('monthly', 'landmarks', options);
253
297
  ```
254
298
 
255
- In this example, the script will have `'monthly'` as its ID and `'landmarks'` as its description. It will tell Testaro to test for all, and only, the rules that are classified into either the `regionNoText` or the `mainNot1` issue.
299
+ In this example, the script will have `'monthly'` as its ID and `'landmarks'` as its description. It will tell Testaro to test for all evaluation rules if the first form is used, or for all rules specified by the options argument if the second form is used.
256
300
 
257
301
  The invoking module can further modify and use the script (`scriptObj`) as needed.
258
302
 
259
- To create a script **without** issue restrictions, a module can call `script()` with only the first two arguments (the ID and the description).
260
-
261
303
  ##### By a user
262
304
 
263
305
  A user can invoke `script()` by executing one of these statements in the Testilo project directory:
264
306
 
265
307
  ```javascript
266
- node call script id what ticnn issuea issueb …
267
308
  node call script id what
309
+ node call script id what tools toolID0 toolID1 toolID2 …
310
+ node call script id what issues tic99 issueID0 issueID1 …
268
311
  ```
269
312
 
270
- In this statement, replace `id` with an ID for the script, such as `headings1`, consisting of ASCII alphanumeric characters.
313
+ The first form will create a script with no restrictions.
271
314
 
272
- If specifying issues:
273
- - Replace `ticnn` with the base, such as `tic99`, of the name of an issue classification file in the `score` subdirectory of the `FUNCTIONDIR` directory.
274
- - Replace the remaining arguments (`issuea` etc.) with issue names from that classification file.
315
+ The second form will create a script that prescribes tests for all the rules of the specified tools.
316
+
317
+ The third form will create a script that prescribes tests for all the rules classified by the specified issue classification into any of the specified issues.
318
+
319
+ In this statement, replace `id` with an ID for the script, such as `headings1`, consisting of ASCII alphanumeric characters.
275
320
 
276
321
  The `call` module will retrieve the named classification, if any.
277
322
  The `script` module will create a script.
@@ -281,7 +326,7 @@ The `call` module will save the script as a JSON file in the `scripts` subdirect
281
326
 
282
327
  The `script` module will use the value of the `SEND_REPORT_TO` environment variable as the value of the `sendReportTo` property of the script, if that variable exists, and otherwise will leave that property with an empty-string value.
283
328
 
284
- When the `script` module creates a script for you, it does not ask you for all of the options that the script may require. Instead, it chooses default options. For example, it sets the values of `isolate` and `strict` to `true`. After you invoke `script`, you can edit the script that it creates to revise options.
329
+ When the `script` module creates a script for you, it does not ask you for all of the other options that the script may require. Instead, it chooses default options. For example, it sets the values of `isolate` and `strict` to `true`. After you invoke `script`, you can edit the script that it creates to revise options.
285
330
 
286
331
  ### Merge
287
332
 
package/call.js CHANGED
@@ -23,7 +23,7 @@ const fs = require('fs/promises');
23
23
  // Function to process a list-to-batch conversion.
24
24
  const {batch} = require('./batch');
25
25
  // Function to create a script from rule specifications.
26
- const {script} = require('./script');
26
+ const {script, toolIDs} = require('./script');
27
27
  // Function to process a merger.
28
28
  const {merge} = require('./merge');
29
29
  // Function to score reports.
@@ -96,27 +96,77 @@ const callBatch = async (id, what) => {
96
96
  }
97
97
  };
98
98
  // Fulfills a script-creation request.
99
- const callScript = async (scriptID, what, classificationID = null, ... issueIDs) => {
100
- try {
101
- // Get any issue classification.
102
- const issues = classificationID
103
- ? require(`${functionDir}/score/${classificationID}`).issues
104
- : null;
105
- // Sanitize the ID.
106
- scriptID = scriptID.replace(/[^a-zA-Z0-9]/g, '');
107
- if (scriptID === '') {
108
- scriptID = `script-${getRandomString(2)}`;
99
+ const callScript = async (scriptID, what, optionType, ... optionDetails) => {
100
+ // Sanitize the script ID.
101
+ scriptID = scriptID.replace(/[^a-zA-Z0-9]/g, '');
102
+ if (scriptID === '') {
103
+ scriptID = `script-${getRandomString(2)}`;
104
+ }
105
+ // Create the option argument.
106
+ const optionArg = {};
107
+ if (optionType) {
108
+ if (optionType === 'tools') {
109
+ if (
110
+ optionDetails.length === new Set(optionDetails).size
111
+ && optionDetails.every(toolID => toolIDs.includes(toolID))
112
+ ) {
113
+ optionArg.type = 'tools';
114
+ optionArg.specs = optionDetails;
115
+ }
116
+ else {
117
+ console.log('ERROR: Tool IDs invalid');
118
+ return;
119
+ }
109
120
  }
110
- // Create a script.
111
- const scriptObj = script(scriptID, what, issues, ... issueIDs);
112
- // Save the script.
121
+ else if (optionType === 'issues') {
122
+ if (optionDetails.length > 1) {
123
+ if (optionDetails[0].startsWith('tic')) {
124
+ try {
125
+ const {issues} = require(`${functionDir}/score/${optionDetails[0]}`);
126
+ const issueIDs = Object.keys(issues);
127
+ if (optionDetails.slice(1).every(issueID => issueIDs.includes(issueID))) {
128
+ optionArg.type = 'issues';
129
+ optionArg.specs = {
130
+ issues,
131
+ issueIDs: optionDetails.slice(1)
132
+ };
133
+ }
134
+ else {
135
+ console.log('ERROR: Issue IDs invalid');
136
+ return;
137
+ }
138
+ }
139
+ catch(error) {
140
+ console.log(`ERROR getting issue classification (${error.message})`);
141
+ return;
142
+ }
143
+ }
144
+ else {
145
+ console.log('ERROR: Issue classification ID invalid');
146
+ return;
147
+ }
148
+ }
149
+ else {
150
+ console.log('ERROR: No issue IDs specified');
151
+ return;
152
+ }
153
+ }
154
+ else {
155
+ console.log('ERROR: Option type invalid');
156
+ return;
157
+ }
158
+ }
159
+ // Create a script.
160
+ const scriptObj = script(scriptID, what, optionArg);
161
+ try {
162
+ // Save it.
113
163
  const scriptJSON = JSON.stringify(scriptObj, null, 2);
114
164
  const scriptPath = `${specDir}/scripts/${scriptID}.json`;
115
165
  await fs.writeFile(scriptPath, `${scriptJSON}\n`);
116
166
  console.log(`Script created and saved as ${scriptPath}`);
117
167
  }
118
168
  catch(error) {
119
- console.log(`ERROR creating script (${error.message})`);
169
+ console.log(`ERROR saving script (${error.message})`);
120
170
  }
121
171
  };
122
172
  // Fulfills a merging request.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testilo",
3
- "version": "31.3.1",
3
+ "version": "32.0.0",
4
4
  "description": "Prepares and processes Testaro reports",
5
5
  "main": "call.js",
6
6
  "scripts": {
@@ -289,7 +289,7 @@ exports.issues = {
289
289
  }
290
290
  }
291
291
  },
292
- IDUnique: {
292
+ duplicateID: {
293
293
  summary: 'ID not unique',
294
294
  why: 'User may be pointed to the wrong item',
295
295
  wcag: '4.1.1',
package/script.js CHANGED
@@ -3,145 +3,164 @@
3
3
  Creates and returns a script to perform the tests for issues.
4
4
  */
5
5
 
6
- // ########## IMPORTS
7
-
8
- // Module to keep secrets.
9
- require('dotenv').config();
10
-
11
6
  // ########## VARIABLES
12
7
 
13
- // List of presumptively needed tools.
14
- let toolIDs = [
8
+ // Testaro tool IDs.
9
+ const toolIDs = exports.toolIDs = [
15
10
  'alfa', 'aslint', 'axe', 'ed11y', 'htmlcs', 'ibm', 'nuVal', 'qualWeb', 'testaro', 'wave'
16
11
  ];
17
12
 
18
13
  // ########## FUNCTIONS
19
14
 
20
15
  // Creates and returns a script.
21
- exports.script = (id, what, issues = null, ... issueIDs) => {
22
- // Initialize data on the tools and their rules for the specified issues, if any.
23
- const neededTools = {};
24
- // If an issue classification and any issues were specified:
25
- if (issues && issueIDs.length) {
26
- // For each specified issue:
27
- issueIDs.forEach(issueID => {
28
- // If it exists in the classification:
29
- const issueData = issues[issueID];
30
- if (issueData) {
31
- // For each tool that tests for the issue:
32
- const issueToolIDs = Object.keys(issueData.tools);
33
- issueToolIDs.forEach(issueToolID => {
34
- // For each of the rules of the tool for the issue:
35
- if (! neededTools[issueToolID]) {
36
- neededTools[issueToolID] = [];
37
- }
38
- Object.keys(issueData.tools[issueToolID]).forEach(ruleID => {
39
- // Add data on the rule.
40
- const ruleData = issueData.tools[issueToolID][ruleID];
41
- if (issueToolID === 'nuVal') {
42
- if (ruleData.variable) {
43
- neededTools[issueToolID].push(`~${ruleID}`);
16
+ exports.script = (id, what, options = {}) => {
17
+ const toolsRulesData = {};
18
+ // If options are specified:
19
+ if (options.type && options.specs) {
20
+ const {type, specs} = options;
21
+ // If the option type is tools and is valid:
22
+ if (
23
+ type === 'tools'
24
+ && Array.isArray(specs)
25
+ && specs.length
26
+ && specs.every(spec => toolIDs.includes(spec))
27
+ ) {
28
+ // Populate the data on tools and rules.
29
+ specs.forEach(spec => {
30
+ toolsRulesData[spec] = [];
31
+ });
32
+ }
33
+ // Otherwise, if the option type is issues and is valid:
34
+ else if (
35
+ type === 'issues'
36
+ && typeof specs === 'object'
37
+ && specs.issues
38
+ && specs.issueIDs
39
+ && typeof specs.issues === 'object'
40
+ && Array.isArray(specs.issueIDs)
41
+ && specs.issueIDs.length
42
+ ) {
43
+ // For each specified issue:
44
+ const {issueIDs, issues} = specs;
45
+ issueIDs.forEach(issueID => {
46
+ // If it exists in the classification:
47
+ const issueData = issues[issueID];
48
+ if (issueData) {
49
+ // For each tool that tests for the issue:
50
+ const issueToolIDs = Object.keys(issueData.tools);
51
+ issueToolIDs.forEach(issueToolID => {
52
+ // For each of the rules of the tool for the issue:
53
+ toolsRulesData[issueToolID] ??= [];
54
+ const toolRuleIDs = toolsRulesData[issueToolID];
55
+ const toolData = issueData.tools[issueToolID];
56
+ Object.keys(toolData).forEach(ruleID => {
57
+ // Add the rule to the data on tools and rules.
58
+ let rulePrefix = '';
59
+ if (issueToolID === 'nuVal') {
60
+ rulePrefix = toolData[ruleID].variable ? '~' : '=';
44
61
  }
45
- else {
46
- neededTools[issueToolID].push(`=${ruleID}`);
62
+ const fullRuleID = `${rulePrefix}${ruleID}`;
63
+ if (! toolRuleIDs.includes(fullRuleID)) {
64
+ toolRuleIDs.push(fullRuleID);
47
65
  }
48
- }
49
- else {
50
- neededTools[issueToolID].push(ruleID);
51
- }
66
+ });
52
67
  });
53
- });
54
- // Remove unneeded tools from the tool list.
55
- toolIDs = Object.keys(neededTools);
56
- }
57
- // Otherwise, i.e. if it does not exist in the classification:
58
- else {
59
- // Report this and quit.
60
- console.log(`ERROR: Issue ${issueID} not in issue classification`);
61
- return {};
62
- }
63
- });
64
- }
65
- // If, after any issue-based pruning, any needed tools remain:
66
- if (toolIDs.length) {
67
- // Initialize a script.
68
- const scriptObj = {
69
- id,
70
- what,
71
- strict: true,
72
- isolate: true,
73
- timeLimit: Math.round(30 + (issueIDs.length || 300) / 2 + 20 * toolIDs.length),
74
- acts: [
75
- {
76
- "type": "placeholder",
77
- "which": "main",
78
- "launch": "webkit"
79
68
  }
80
- ]
81
- };
82
- // For each needed tool:
83
- toolIDs.forEach(toolID => {
84
- // Initialize a test act for it.
85
- const toolAct = {
86
- type: 'test',
87
- which: toolID
88
- };
89
- // If issues were specified:
90
- if (issues && issueIDs.length) {
91
- // Add a rules array as a property to the act.
92
- toolAct.rules = neededTools[toolID];
93
- // If the tool is QualWeb:
94
- if (toolID === 'qualWeb') {
95
- // For each QualWeb module:
96
- const specs = [];
97
- const prefixes = {
98
- act: 'QW-ACT-R',
99
- wcag: 'QW-WCAG-T',
100
- best: 'QW-BP'
101
- };
102
- Object.keys(prefixes).forEach(prefix => {
103
- // Specify the rules of that module to be tested for.
104
- const ids = toolAct.rules.filter(id => id.startsWith(prefixes[prefix]));
105
- const integers = ids.map(id => id.slice(prefixes[prefix].length));
106
- specs.push(`${prefix}:${integers.join(',')}`);
107
- });
108
- // Replace the generic rule list with the QualWeb-format list.
109
- toolAct.rules = specs;
69
+ // Otherwise, i.e. if it does not exist in the classification:
70
+ else {
71
+ // Report this and quit.
72
+ console.log(`ERROR: Issue ${issueID} not in issue classification`);
73
+ return {};
110
74
  }
111
- // Otherwise, if the tool is Testaro:
112
- else if (toolID === 'testaro') {
113
- // Prepend the inclusion option to the rule array.
114
- toolAct.rules.unshift('y');
115
- }
116
- }
117
- // Add any needed option defaults to the act.
118
- if (toolID === 'axe') {
119
- toolAct.detailLevel = 2;
120
- }
121
- else if (toolID === 'ibm') {
122
- toolAct.withItems = true;
123
- toolAct.withNewContent = true;
75
+ });
76
+ }
77
+ // Otherwise, i.e. if the option specification is invalid:
78
+ else {
79
+ // Report this and quit.
80
+ console.log(`ERROR: Options invalid`);
81
+ return {};
82
+ }
83
+ }
84
+ // Otherwise, i.e. if options are not specified:
85
+ else {
86
+ // Populate the data on tools and rules.
87
+ toolIDs.forEach(toolID => {
88
+ toolsRulesData[toolID] = [];
89
+ });
90
+ }
91
+ // Initialize a script.
92
+ const timeLimit = Math.round(50 + 30 * Object.keys(toolsRulesData).length);
93
+ const scriptObj = {
94
+ id,
95
+ what,
96
+ strict: true,
97
+ isolate: true,
98
+ timeLimit,
99
+ acts: [
100
+ {
101
+ "type": "placeholder",
102
+ "which": "main",
103
+ "launch": "webkit"
124
104
  }
125
- else if (toolID === 'qualWeb') {
126
- toolAct.withNewContent = false;
105
+ ]
106
+ };
107
+ // For each tool used:
108
+ Object.keys(toolsRulesData).forEach(toolID => {
109
+ // Initialize a test act for it.
110
+ const toolAct = {
111
+ type: 'test',
112
+ which: toolID
113
+ };
114
+ // If rules were specified:
115
+ const ruleIDs = toolsRulesData[toolID];
116
+ if (ruleIDs.length) {
117
+ // Add a rules array as a property to the act.
118
+ toolAct.rules = ruleIDs;
119
+ // If the tool is QualWeb:
120
+ if (toolID === 'qualWeb') {
121
+ // For each QualWeb module:
122
+ const specs = [];
123
+ const prefixes = {
124
+ act: 'QW-ACT-R',
125
+ wcag: 'QW-WCAG-T',
126
+ best: 'QW-BP'
127
+ };
128
+ Object.keys(prefixes).forEach(prefix => {
129
+ // Specify the rules of that module to be tested for.
130
+ const ids = toolAct.rules.filter(id => id.startsWith(prefixes[prefix]));
131
+ const integers = ids.map(id => id.slice(prefixes[prefix].length));
132
+ specs.push(`${prefix}:${integers.join(',')}`);
133
+ });
134
+ // Replace the generic rule list with the QualWeb-format list.
135
+ toolAct.rules = specs;
127
136
  }
137
+ // Otherwise, if the tool is Testaro:
128
138
  else if (toolID === 'testaro') {
129
- toolAct.withItems = true;
130
- toolAct.stopOnFail = false;
131
- }
132
- else if (toolID === 'wave') {
133
- toolAct.reportType = 4;
139
+ // Prepend the inclusion option to the rule array.
140
+ toolAct.rules.unshift('y');
134
141
  }
135
- // Add the act to the script.
136
- scriptObj.acts.push(toolAct);
137
- });
138
- // Return the script.
139
- return scriptObj;
140
- }
141
- // Otherwise, i.e. if no rules have been identified:
142
- else {
143
- // Report this.
144
- console.log(`ERROR: No rules for the specified issues found`);
145
- return {};
146
- }
147
- };
142
+ }
143
+ // Add any needed option defaults to the act.
144
+ if (toolID === 'axe') {
145
+ toolAct.detailLevel = 2;
146
+ }
147
+ else if (toolID === 'ibm') {
148
+ toolAct.withItems = true;
149
+ toolAct.withNewContent = true;
150
+ }
151
+ else if (toolID === 'qualWeb') {
152
+ toolAct.withNewContent = false;
153
+ }
154
+ else if (toolID === 'testaro') {
155
+ toolAct.withItems = true;
156
+ toolAct.stopOnFail = false;
157
+ }
158
+ else if (toolID === 'wave') {
159
+ toolAct.reportType = 4;
160
+ }
161
+ // Add the act to the script.
162
+ scriptObj.acts.push(toolAct);
163
+ });
164
+ // Return the script.
165
+ return scriptObj;
166
+ }