pa11y-ci-reporter-runner 2.0.4 → 3.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2022 Aaron Goldenthal
3
+ Copyright (c) 2022 - 2023 Aaron Goldenthal
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/index.d.ts ADDED
@@ -0,0 +1,99 @@
1
+ type RunnerStates = 'afterAll' | 'beforeAll' | 'beginUrl' | 'urlResults';
2
+
3
+ export type RunnerState = {
4
+ state: RunnerStates;
5
+ url: string;
6
+ };
7
+
8
+ export type Runner = {
9
+ /**
10
+ * Get the current state (state, url).
11
+ *
12
+ * @instance
13
+ * @returns The current runner state.
14
+ * @public
15
+ */
16
+ getCurrentState(): RunnerState;
17
+
18
+ /**
19
+ * Get the next state (state, url).
20
+ *
21
+ * @instance
22
+ * @returns The next runner state.
23
+ * @public
24
+ */
25
+ getNextState(): RunnerState;
26
+
27
+ /**
28
+ * Resets the runner and reporter to the initial states.
29
+ *
30
+ * @instance
31
+ * @async
32
+ * @public
33
+ */
34
+ reset(): void;
35
+
36
+ /**
37
+ * Executes the entire Pa11y CI sequence, calling all reporter functions.
38
+ *
39
+ * @instance
40
+ * @async
41
+ * @public
42
+ */
43
+ runAll(): void;
44
+
45
+ /**
46
+ * Executes the next event in the Pa11y CI sequence, calling the
47
+ * appropriate reporter function.
48
+ *
49
+ * @instance
50
+ * @async
51
+ * @public
52
+ */
53
+ runNext(): void;
54
+
55
+ /**
56
+ * Executes the entire Pa11y CI sequence, calling all reporter functions,
57
+ * until the specified current state and optional URL are reached. If a URL is not
58
+ * specified, the run completes on the first occurrence of the target state.
59
+ *
60
+ * @instance
61
+ * @param targetState The target state to run to.
62
+ * @param targetUrl The target URL to run to.
63
+ * @async
64
+ * @public
65
+ */
66
+ runUntil(state: RunnerStates, url?: string): void;
67
+
68
+ /**
69
+ * Executes the entire Pa11y CI sequence, calling all reporter functions,
70
+ * until the specified next state and optional URL are reached. If a URL is not
71
+ * specified, the run completes on the first occurrence of the target state.
72
+ *
73
+ * @instance
74
+ * @param targetState The target state to run to.
75
+ * @param targetUrl The target URL to run to.
76
+ * @async
77
+ * @public
78
+ */
79
+ runUntilNext(state: RunnerStates, url?: string): void;
80
+ };
81
+
82
+ /**
83
+ * Factory to create a pa11y-ci reporter runner that can execute
84
+ * a reporter with the specified pa11y-ci JSON results file.
85
+ *
86
+ * @param resultsFileName Pa11y CI JSON results file.
87
+ * @param reporterName Name of the reporter to execute (module or path).
88
+ * @param options The reporter options.
89
+ * @param config The Pa11y CI configuration.
90
+ * @returns A Pa11y CI reporter runner.
91
+ * @static
92
+ * @public
93
+ */
94
+ export function createRunner(
95
+ resultsFileName: string,
96
+ reporterName: string,
97
+ options?: object,
98
+ config?: object
99
+ ): Runner;
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /* eslint-disable max-lines */
1
+ /* eslint-disable max-lines -- over 9 lines */
2
2
  'use strict';
3
3
 
4
4
  /**
@@ -29,6 +29,8 @@ const loadPa11yciResults = (fileName) => {
29
29
  }
30
30
 
31
31
  try {
32
+ // Application requires user-supplied file name
33
+ // nosemgrep: detect-non-literal-fs-filename, eslint.detect-non-literal-fs-filename
32
34
  const results = JSON.parse(fs.readFileSync(fileName, 'utf8'));
33
35
  return formatter.convertJsonToResultsObject(results);
34
36
  } catch (error) {
@@ -111,7 +113,7 @@ const isError = (results) =>
111
113
  * @static
112
114
  * @public
113
115
  */
114
- // eslint-disable-next-line max-lines-per-function
116
+ // eslint-disable-next-line max-lines-per-function -- factory function with state
115
117
  const createRunner = (
116
118
  resultsFileName,
117
119
  reporterName,
@@ -170,6 +172,8 @@ const createRunner = (
170
172
  * @private
171
173
  */
172
174
  const urlResults = async (url) => {
175
+ // User supplied input used to index user supplied data file
176
+ // nosemgrep: eslint.detect-object-injection
173
177
  const results = pa11yciResults.results[url];
174
178
  const urlConfig = pa11yciConfig.getConfigForUrl(url);
175
179
  await (isError(results)
@@ -195,10 +199,10 @@ const createRunner = (
195
199
 
196
200
  // Collection of runner actions to be passed to the state service.
197
201
  const actions = {
202
+ afterAll,
198
203
  beforeAll,
199
204
  beginUrl,
200
- urlResults,
201
- afterAll
205
+ urlResults
202
206
  };
203
207
 
204
208
  // URLs for the service is always the array of result URLs since they
@@ -277,7 +281,7 @@ const createRunner = (
277
281
  * Get the current state (state, url).
278
282
  *
279
283
  * @instance
280
- * @returns {object} The current state.
284
+ * @returns {object} The current runner state.
281
285
  * @public
282
286
  */
283
287
  // eslint-disable-next-line prefer-destructuring -- required for jsdoc
@@ -287,7 +291,7 @@ const createRunner = (
287
291
  * Get the next state (state, url).
288
292
  *
289
293
  * @instance
290
- * @returns {object} The next state.
294
+ * @returns {object} The next runner state.
291
295
  * @public
292
296
  */
293
297
  // eslint-disable-next-line prefer-destructuring -- required for jsdoc
@@ -9,9 +9,9 @@
9
9
  const defaultColumnWidth = 80;
10
10
 
11
11
  const defaultConfig = {
12
- wrapWidth: process.stdout.columns || defaultColumnWidth,
13
12
  concurrency: 1,
14
- useIncognitoBrowserContext: true
13
+ useIncognitoBrowserContext: true,
14
+ wrapWidth: process.stdout.columns || defaultColumnWidth
15
15
  };
16
16
 
17
17
  module.exports = defaultConfig;
package/lib/formatter.js CHANGED
@@ -22,17 +22,21 @@ const isError = (issues) =>
22
22
  */
23
23
  const convertJsonToResultsObject = (jsonResults) => {
24
24
  const results = {
25
- total: jsonResults.total,
26
- passes: jsonResults.passes,
27
25
  errors: jsonResults.errors,
28
- results: {}
26
+ passes: jsonResults.passes,
27
+ results: {},
28
+ total: jsonResults.total
29
29
  };
30
30
 
31
31
  for (const url of Object.keys(jsonResults.results)) {
32
+ // User supplied input used to index user supplied data file
33
+ // nosemgrep: eslint.detect-object-injection
32
34
  const issues = jsonResults.results[url];
33
35
  const formattedIssues = isError(issues)
34
36
  ? [new Error(issues[0].message)]
35
37
  : issues;
38
+ // User supplied input used as object key
39
+ // nosemgrep: eslint.detect-object-injection
36
40
  results.results[url] = formattedIssues;
37
41
  }
38
42
 
@@ -51,6 +55,8 @@ const convertJsonToResultsObject = (jsonResults) => {
51
55
  * @static
52
56
  */
53
57
  const getPa11yResultsFromPa11yCiResults = (url, results) => {
58
+ // User supplied input used to index user supplied data file
59
+ // nosemgrep: eslint.detect-object-injection
54
60
  const issues = results.results[url];
55
61
  if (!issues) {
56
62
  throw new Error(`Results for url "${url} not found`);
@@ -58,8 +64,8 @@ const getPa11yResultsFromPa11yCiResults = (url, results) => {
58
64
 
59
65
  return {
60
66
  documentTitle: `Title for ${url}`,
61
- pageUrl: url,
62
- issues
67
+ issues,
68
+ pageUrl: url
63
69
  };
64
70
  };
65
71
 
@@ -13,17 +13,18 @@ const { createMachine, assign } = require('xstate');
13
13
  */
14
14
  const pa11yciMachine = createMachine(
15
15
  {
16
- id: 'pa11yci-runner',
17
- initial: 'init',
18
16
  context: {
19
17
  urlIndex: 0,
20
18
  urls: []
21
19
  },
20
+ id: 'pa11yci-runner',
21
+ initial: 'init',
22
22
  // Opting in for v4, will be default in xstate v5
23
23
  predictableActionArguments: true,
24
24
  // Opting in for v4, will be default in xstate v5
25
25
  preserveActionOrder: true,
26
26
  states: {
27
+ /* eslint-disable sort-keys -- order by sequence */
27
28
  init: {
28
29
  entry: 'setInitialUrlIndex',
29
30
  on: {
@@ -59,6 +60,7 @@ const pa11yciMachine = createMachine(
59
60
  RESET: { target: 'init' }
60
61
  }
61
62
  }
63
+ /* eslint-enable sort-keys -- order by sequence */
62
64
  }
63
65
  },
64
66
  {
@@ -1,4 +1,4 @@
1
- /* eslint-disable max-lines */
1
+ /* eslint-disable max-lines -- over 22 lines */
2
2
  'use strict';
3
3
 
4
4
  /**
@@ -69,7 +69,9 @@ const hasUrl = (state) =>
69
69
  */
70
70
  const getUrlForState = (machineState) =>
71
71
  hasUrl(machineState.value)
72
- ? machineState.context.urls[machineState.context.urlIndex]
72
+ ? // User supplied input used to index user supplied data file
73
+ // nosemgrep: eslint.detect-object-injection
74
+ machineState.context.urls[machineState.context.urlIndex]
73
75
  : undefined;
74
76
 
75
77
  /**
@@ -91,21 +93,18 @@ const validateRunnerState = (state) => {
91
93
  *
92
94
  * @param {object} machineState The machine state to check against.
93
95
  * @param {RunnerStates} targetState The target runner state.
94
- * @param {string} [targetUrl] The target URL.
96
+ * @param {string} [targetUrl] The target URL for the state.
95
97
  * @returns {boolean} True if the context matches the target, otherwise false.
96
98
  * @private
97
99
  */
98
- const isAtTarget = (machineState, targetState, targetUrl) => {
99
- return (
100
- machineState.value === targetState &&
101
- (!targetUrl || getUrlForState(machineState) === targetUrl)
102
- );
103
- };
100
+ const isAtTarget = (machineState, targetState, targetUrl) =>
101
+ machineState.value === targetState &&
102
+ (!targetUrl || getUrlForState(machineState) === targetUrl);
104
103
 
105
104
  /**
106
105
  * Gets a summary of the machine state (state, url).
107
106
  *
108
- * @param {object} machineState The machine state.
107
+ * @param {object} machineState The machine state object.
109
108
  * @returns {object} The machine state summary.
110
109
  * @private
111
110
  */
@@ -123,7 +122,7 @@ const getStateSummary = (machineState) => ({
123
122
  * @static
124
123
  * @public
125
124
  */
126
- // eslint-disable-next-line max-lines-per-function
125
+ // eslint-disable-next-line max-lines-per-function -- factory function with state
127
126
  const serviceFactory = (urls, actions) => {
128
127
  let pendingCommand;
129
128
 
@@ -131,7 +130,7 @@ const serviceFactory = (urls, actions) => {
131
130
  // and the next state, which is required for some control functions.
132
131
  const pa11yMachine = machine.withContext(getInitialContext(urls));
133
132
  let currentState = pa11yMachine.initialState;
134
- // machine.transition is a pure function, so only retrieves the state
133
+ // Machine.transition is a pure function, so only retrieves the state
135
134
  let nextState = machine.transition(currentState, MachineEvents.NEXT);
136
135
 
137
136
  /**
@@ -171,6 +170,8 @@ const serviceFactory = (urls, actions) => {
171
170
  // current state (except in init, which has no action)
172
171
  currentState = machine.transition(currentState, event);
173
172
  if (currentState.value !== 'init') {
173
+ // Value is internal object state
174
+ // nosemgrep: eslint.detect-object-injection, unsafe-dynamic-method
174
175
  await actions[currentState.value](getUrlForState(currentState));
175
176
  }
176
177
 
@@ -216,7 +217,7 @@ const serviceFactory = (urls, actions) => {
216
217
  /**
217
218
  * Gets the state for the given state type (current or next).
218
219
  *
219
- * @param {StateTypes} stateType The state type.
220
+ * @param {StateTypes} stateType The runner service state type.
220
221
  * @returns {object} The state object for teh given type.
221
222
  * @private
222
223
  */
@@ -230,7 +231,7 @@ const serviceFactory = (urls, actions) => {
230
231
  * specified state and optional URL are reached. If a URL is not specified,
231
232
  * the run completes on the first occurrence of the target state.
232
233
  *
233
- * @param {StateTypes} stateType The state type.
234
+ * @param {StateTypes} stateType The runner service state type.
234
235
  * @param {RunnerStates} targetState The target state to run to.
235
236
  * @param {string} [targetUrl] The target URL to run to.
236
237
  * @async
@@ -245,6 +246,8 @@ const serviceFactory = (urls, actions) => {
245
246
  !isAtTarget(getState(stateType), targetState, targetUrl) &&
246
247
  getState(stateType).value !== finalState
247
248
  ) {
249
+ /* eslint-disable-next-line no-await-in-loop -- required to be
250
+ done in sequence */
248
251
  await sendEvent(MachineEvents.NEXT);
249
252
  }
250
253
 
@@ -292,7 +295,7 @@ const serviceFactory = (urls, actions) => {
292
295
  * Get the current state (state, url).
293
296
  *
294
297
  * @instance
295
- * @returns {object} The current state.
298
+ * @returns {object} The current runner state.
296
299
  * @public
297
300
  */
298
301
  const getCurrentState = () => getStateSummary(currentState);
@@ -301,7 +304,7 @@ const serviceFactory = (urls, actions) => {
301
304
  * Get the next state (state, url).
302
305
  *
303
306
  * @instance
304
- * @returns {object} The next state.
307
+ * @returns {object} The next runner state.
305
308
  * @public
306
309
  */
307
310
  const getNextState = () => getStateSummary(nextState);
@@ -5,15 +5,19 @@ const path = require('path');
5
5
  const reporterMethods = ['beforeAll', 'begin', 'results', 'error', 'afterAll'];
6
6
  const noop = () => {};
7
7
 
8
+ /* eslint-disable n/global-require -- load models pa11y-ci behavior for loading
9
+ user-provided reporters */
8
10
  const loadReporter = (reporterName) => {
9
11
  try {
10
- // eslint-disable-next-line n/global-require
12
+ // nosemgrep: detect-non-literal-require, eslint.detect-non-literal-require
11
13
  return require(reporterName);
12
14
  } catch {
13
- // eslint-disable-next-line n/global-require
15
+ // nosemgrep: detect-non-literal-require, eslint.detect-non-literal-require
14
16
  return require(path.resolve(process.cwd(), reporterName));
15
17
  }
16
18
  };
19
+ /* eslint-enable n/global-require -- load models pa11y-ci behavior for loading
20
+ user-provided reporters */
17
21
 
18
22
  /**
19
23
  * Creates a reporter object for the specified path, following the logic
@@ -22,7 +26,7 @@ const loadReporter = (reporterName) => {
22
26
  *
23
27
  * @param {string} reporterName Name of the reporter to execute
24
28
  * (module or path).
25
- * @param {object} options The reporter options.
29
+ * @param {object} options The reporter options object.
26
30
  * @param {object} config The Pa11y CI configuration.
27
31
  * @returns {object} The reporter object.
28
32
  * @throws {TypeError} Throws if reporterName is not a string.
@@ -39,7 +43,11 @@ const buildReporter = (reporterName, options, config) => {
39
43
  reporter = reporter(options, config);
40
44
  }
41
45
  for (const method of reporterMethods) {
46
+ // Value is internal object state
47
+ // nosemgrep: eslint.detect-object-injection
42
48
  if (typeof reporter[method] !== 'function') {
49
+ // Value is internal object state
50
+ // nosemgrep: eslint.detect-object-injection
43
51
  reporter[method] = noop;
44
52
  }
45
53
  }
@@ -14,10 +14,10 @@
14
14
  * @static
15
15
  */
16
16
  const RunnerStates = Object.freeze({
17
+ afterAll: 'afterAll',
17
18
  beforeAll: 'beforeAll',
18
19
  beginUrl: 'beginUrl',
19
- urlResults: 'urlResults',
20
- afterAll: 'afterAll'
20
+ urlResults: 'urlResults'
21
21
  });
22
22
 
23
23
  module.exports = RunnerStates;
package/package.json CHANGED
@@ -1,21 +1,23 @@
1
1
  {
2
2
  "name": "pa11y-ci-reporter-runner",
3
- "version": "2.0.4",
3
+ "version": "3.0.0",
4
4
  "description": "Pa11y CI Reporter Runner is designed to facilitate testing of Pa11y CI reporters. Given a Pa11y CI JSON results file and optional configuration it simulates the Pa11y CI calls to the reporter.",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
+ "build-types": "tsc index.js --declaration --allowJs --emitDeclarationOnly",
7
8
  "hooks-pre-commit": "npm run lint && npm run prettier-check",
8
9
  "hooks-pre-push": "npm audit --audit-level=critical && npm test",
9
10
  "lint": "npm run lint-js && npm run lint-md",
10
11
  "lint-js": "eslint .",
11
- "lint-md": "markdownlint **/*.md --ignore node_modules --ignore Archive",
12
+ "lint-md": "markdownlint-cli2 \"**/*.md\" \"#node_modules\" \"#Archive\"",
12
13
  "prettier-check": "prettier --check .",
13
14
  "prettier-fix": "prettier --write .",
14
- "test": "jest --ci"
15
+ "test": "jest --ci",
16
+ "test-types": "tsd"
15
17
  },
16
18
  "repository": {
17
19
  "type": "git",
18
- "url": "git+https://gitlab.com/gitlab-ci-utils/pa11y-ci-reporter-runner.git"
20
+ "url": "https://gitlab.com/gitlab-ci-utils/pa11y-ci-reporter-runner.git"
19
21
  },
20
22
  "keywords": [
21
23
  "pa11y-ci",
@@ -27,9 +29,10 @@
27
29
  "author": "Aaron Goldenthal <npm@aarongoldenthal.com>",
28
30
  "license": "MIT",
29
31
  "engines": {
30
- "node": "^14.15.0 || ^16.13.0 || >=18.0.0"
32
+ "node": "^16.13.0 || ^18.12.0 || >=20.0.0"
31
33
  },
32
34
  "files": [
35
+ "index.d.ts",
33
36
  "index.js",
34
37
  "lib/"
35
38
  ],
@@ -38,16 +41,18 @@
38
41
  },
39
42
  "homepage": "https://gitlab.com/gitlab-ci-utils/pa11y-ci-reporter-runner",
40
43
  "devDependencies": {
41
- "@aarongoldenthal/eslint-config-standard": "^19.0.1",
42
- "eslint": "^8.30.0",
43
- "jest": "^29.3.1",
44
- "jest-junit": "^15.0.0",
45
- "markdownlint-cli": "^0.32.2",
44
+ "@aarongoldenthal/eslint-config-standard": "^22.1.0",
45
+ "eslint": "^8.42.0",
46
+ "jest": "^29.5.0",
47
+ "jest-junit": "^16.0.0",
48
+ "markdownlint-cli2": "^0.7.1",
46
49
  "pa11y-ci-reporter-cli-summary": "^2.0.2",
47
- "prettier": "^2.8.1"
50
+ "prettier": "^2.8.8",
51
+ "tsd": "^0.28.1",
52
+ "typescript": "^5.1.3"
48
53
  },
49
54
  "dependencies": {
50
55
  "lodash": "^4.17.21",
51
- "xstate": "^4.35.1"
56
+ "xstate": "^4.37.2"
52
57
  }
53
58
  }