accented 1.0.1 → 1.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.
Files changed (50) hide show
  1. package/README.md +33 -19
  2. package/dist/accented.d.ts.map +1 -1
  3. package/dist/accented.js +3 -2
  4. package/dist/accented.js.map +1 -1
  5. package/dist/common/tokens.d.ts +9 -0
  6. package/dist/common/tokens.d.ts.map +1 -1
  7. package/dist/common/tokens.js +28 -0
  8. package/dist/common/tokens.js.map +1 -1
  9. package/dist/constants.d.ts +2 -0
  10. package/dist/constants.d.ts.map +1 -1
  11. package/dist/constants.js +1 -0
  12. package/dist/constants.js.map +1 -1
  13. package/dist/elements/accented-dialog.d.ts +20 -8
  14. package/dist/elements/accented-dialog.d.ts.map +1 -1
  15. package/dist/elements/accented-dialog.js +6 -25
  16. package/dist/elements/accented-dialog.js.map +1 -1
  17. package/dist/elements/accented-trigger.d.ts +24 -12
  18. package/dist/elements/accented-trigger.d.ts.map +1 -1
  19. package/dist/logger.d.ts.map +1 -1
  20. package/dist/logger.js +196 -17
  21. package/dist/logger.js.map +1 -1
  22. package/dist/types.d.ts +31 -1
  23. package/dist/types.d.ts.map +1 -1
  24. package/dist/types.js.map +1 -1
  25. package/dist/utils/are-issue-sets-equal.d.ts.map +1 -1
  26. package/dist/utils/are-issue-sets-equal.js +2 -2
  27. package/dist/utils/are-issue-sets-equal.js.map +1 -1
  28. package/dist/utils/are-issues-equal.d.ts +3 -0
  29. package/dist/utils/are-issues-equal.d.ts.map +1 -0
  30. package/dist/utils/are-issues-equal.js +5 -0
  31. package/dist/utils/are-issues-equal.js.map +1 -0
  32. package/dist/utils/shadow-dom-aware-mutation-observer.d.ts +4 -4
  33. package/dist/utils/shadow-dom-aware-mutation-observer.d.ts.map +1 -1
  34. package/dist/utils/transform-violations.d.ts.map +1 -1
  35. package/dist/utils/transform-violations.js +7 -4
  36. package/dist/utils/transform-violations.js.map +1 -1
  37. package/dist/validate-options.d.ts.map +1 -1
  38. package/dist/validate-options.js +3 -0
  39. package/dist/validate-options.js.map +1 -1
  40. package/package.json +4 -4
  41. package/src/accented.ts +3 -2
  42. package/src/common/tokens.ts +36 -0
  43. package/src/constants.ts +4 -0
  44. package/src/elements/accented-dialog.ts +16 -25
  45. package/src/logger.ts +276 -22
  46. package/src/types.ts +32 -1
  47. package/src/utils/are-issue-sets-equal.ts +2 -5
  48. package/src/utils/are-issues-equal.ts +7 -0
  49. package/src/utils/transform-violations.ts +12 -6
  50. package/src/validate-options.ts +5 -0
@@ -1,5 +1,15 @@
1
1
  import type { Signal } from '@preact/signals-core';
2
- import { colorDark, colorLight, fontSystemMono, fontSystemSans } from '../common/tokens.js';
2
+ import {
3
+ colorDark,
4
+ colorFocus,
5
+ colorImpactCritical,
6
+ colorImpactMinor,
7
+ colorImpactModerate,
8
+ colorImpactSerious,
9
+ colorLight,
10
+ fontSystemMono,
11
+ fontSystemSans,
12
+ } from '../common/tokens.js';
3
13
  import { accentedUrl } from '../constants.js';
4
14
  import { logAndRethrow } from '../log-and-rethrow.js';
5
15
  import type { Issue } from '../types.ts';
@@ -31,8 +41,6 @@ export const getAccentedDialog = () => {
31
41
  <p>
32
42
  Powered by
33
43
  <a href="${accentedUrl}" target="_blank" aria-description="Opens in new tab">Accented</a>
34
- and
35
- <a href="https://github.com/dequelabs/axe-core" target="_blank" aria-description="Opens in new tab">axe-core</a>.
36
44
  </p>
37
45
  </section>
38
46
  </dialog>
@@ -65,30 +73,13 @@ export const getAccentedDialog = () => {
65
73
  --background-color: light-dark(var(--light-color), var(--dark-color));
66
74
  --text-color: light-dark(var(--dark-color), var(--light-color));
67
75
 
68
- --impact-lightness: 0.80;
69
- --focus-lightness: 0.45;
70
- @media (prefers-color-scheme: dark) {
71
- --impact-lightness: 0.45;
72
- --focus-lightness: 0.80;
73
- }
74
-
75
- --blue-hue: 230;
76
- --gold-hue: 90;
77
- --red-hue: 0;
78
-
79
76
  /* Contrasts with background. */
80
- --focus-color: oklch(var(--focus-lightness) 0.25 var(--blue-hue));
81
-
82
- --impact-chroma: 0.16;
83
-
84
- --impact-moderate-hue: var(--blue-hue);
85
- --impact-serious-hue: var(--gold-hue);
86
- --impact-critical-hue: var(--red-hue);
77
+ --focus-color: ${colorFocus};
87
78
 
88
- --impact-minor-color: oklch(var(--impact-lightness) 0 0);
89
- --impact-moderate-color: oklch(var(--impact-lightness) var(--impact-chroma) var(--impact-moderate-hue));
90
- --impact-serious-color: oklch(var(--impact-lightness) var(--impact-chroma) var(--impact-serious-hue));
91
- --impact-critical-color: oklch(var(--impact-lightness) var(--impact-chroma) var(--impact-critical-hue));
79
+ --impact-minor-color: ${colorImpactMinor};
80
+ --impact-moderate-color: ${colorImpactModerate};
81
+ --impact-serious-color: ${colorImpactSerious};
82
+ --impact-critical-color: ${colorImpactCritical};
92
83
 
93
84
  --base-size: max(1rem, 16px);
94
85
 
package/src/logger.ts CHANGED
@@ -1,36 +1,290 @@
1
1
  import { effect } from '@preact/signals-core';
2
- import { accentedUrl } from './constants.js';
2
+ import {
3
+ consoleColorImpactCritical,
4
+ consoleColorImpactMinor,
5
+ consoleColorImpactModerate,
6
+ consoleColorImpactSerious,
7
+ } from './common/tokens.js';
8
+ import { accentedUrl, orderedImpacts } from './constants.js';
3
9
  import { elementsWithIssues, enabled } from './state.js';
4
- import type { ElementWithIssues } from './types.ts';
10
+ import type { ElementWithIssues, Issue } from './types.ts';
11
+ import { areElementsWithIssuesEqual } from './utils/are-elements-with-issues-equal.js';
12
+ import { areIssueSetsEqual } from './utils/are-issue-sets-equal.js';
13
+ import { areIssuesEqual } from './utils/are-issues-equal.js';
5
14
 
6
- function filterPropsForOutput(elements: Array<ElementWithIssues>) {
7
- return elements.map(({ element, issues }) => ({ element, issues }));
15
+ // For user friendliness, we want to balance two things:
16
+ // * the user shouldn't have to click in the console too many times to get to the info they need;
17
+ // * the output should be concise and not overwhelm the user with too much information at once.
18
+ // This number is chosen as a compromise between these two factors.
19
+ const MAX_ISSUES_BEFORE_OUTPUT_COLLAPSE = 5;
20
+
21
+ // Groups have bold color by default in the console.
22
+ // This doesn't seem appropriate for our purposes, so we have to explicitly set the normal font weight.
23
+ const defaultStyle = 'font-weight: normal;';
24
+
25
+ // We'll use the same colors in the console as in the dialog UI
26
+ // (except the theme will be reversed since in the dialog, the color needs to have enough contrast against text color,
27
+ // and in the console, the color needs to have enough contrast against the background color).
28
+ const colors = {
29
+ minor: consoleColorImpactMinor,
30
+ moderate: consoleColorImpactModerate,
31
+ serious: consoleColorImpactSerious,
32
+ critical: consoleColorImpactCritical,
33
+ };
34
+
35
+ const uppercasedImpactText = (impact: Issue['impact']) =>
36
+ impact.charAt(0).toUpperCase() + impact.slice(1);
37
+
38
+ const titleAndUrl = (issue: { title: Issue['title']; url: Issue['url'] }) =>
39
+ `${issue.title} ${issue.url}`;
40
+
41
+ // This sorting is not ideal since it doesn't work very well with shadow DOM.
42
+ const sortByElementPositions = (
43
+ a: ElementWithIssues['element'],
44
+ b: ElementWithIssues['element'],
45
+ ) => (a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_PRECEDING ? 1 : -1);
46
+
47
+ type IssueType = {
48
+ title: Issue['title'];
49
+ url: Issue['url'];
50
+ impact: Issue['impact'];
51
+ elements: Array<{ element: ElementWithIssues['element']; description: Issue['description'] }>;
52
+ };
53
+
54
+ type GroupedByIssueType = Record<string, IssueType>;
55
+
56
+ const getIssueTypeGroups = (elementsWithIssues: Array<ElementWithIssues>) => {
57
+ const groupedByIssueType = elementsWithIssues.reduce((acc, { element, issues }) => {
58
+ for (const issue of issues) {
59
+ if (!acc[issue.id]) {
60
+ acc[issue.id] = {
61
+ title: issue.title,
62
+ url: issue.url,
63
+ impact: issue.impact,
64
+ elements: [],
65
+ };
66
+ }
67
+ acc[issue.id]?.elements.push({ element, description: issue.description });
68
+ }
69
+ return acc;
70
+ }, {} as GroupedByIssueType);
71
+
72
+ const sorted = Object.values(groupedByIssueType).sort((a, b) => {
73
+ const impactComparison = orderedImpacts.indexOf(b.impact) - orderedImpacts.indexOf(a.impact);
74
+ if (impactComparison !== 0) {
75
+ return impactComparison;
76
+ }
77
+ return b.elements.length - a.elements.length;
78
+ });
79
+
80
+ return sorted;
81
+ };
82
+
83
+ function logIssuesByElement(elementsWithIssues: Array<ElementWithIssues>) {
84
+ // Elements with more severe issues (or with a higher number of issues of the same severity)
85
+ // will appear higher in the output.
86
+ // This way, issues with a higher severity will be prioritized.
87
+ const sortedElementsWithIssues = elementsWithIssues.toSorted((a, b) => {
88
+ const impacts = orderedImpacts.toReversed();
89
+ const impactWithDifferentIssueCount = impacts.find((impact) => {
90
+ const aCount = a.issues.filter((issue) => issue.impact === impact).length;
91
+ const bCount = b.issues.filter((issue) => issue.impact === impact).length;
92
+ return aCount !== bCount;
93
+ });
94
+ if (impactWithDifferentIssueCount) {
95
+ const aCount = a.issues.filter(
96
+ (issue) => issue.impact === impactWithDifferentIssueCount,
97
+ ).length;
98
+ const bCount = b.issues.filter(
99
+ (issue) => issue.impact === impactWithDifferentIssueCount,
100
+ ).length;
101
+ return bCount - aCount; // Sort by count of issues with the same impact
102
+ }
103
+ return sortByElementPositions(a.element, b.element);
104
+ });
105
+
106
+ for (const { element, issues } of sortedElementsWithIssues) {
107
+ const sortedAndFilteredImpacts = orderedImpacts
108
+ .toReversed()
109
+ .filter((impact) => issues.some((issue) => issue.impact === impact));
110
+
111
+ const issuesWithImpacts = sortedAndFilteredImpacts
112
+ .map((impact) => `%c${issues.filter((issue) => issue.impact === impact).length} ${impact}`)
113
+ .join(', ');
114
+
115
+ const baseOutput = `${issuesWithImpacts}%c`;
116
+ const output =
117
+ issues.length === 1 && issues[0]
118
+ ? `${baseOutput}\n${titleAndUrl(issues[0])}\n%o`
119
+ : `${baseOutput}\n%o`;
120
+
121
+ console.groupCollapsed(
122
+ output,
123
+ ...sortedAndFilteredImpacts.map((impact) => `color: ${colors[impact]};`),
124
+ defaultStyle,
125
+ element,
126
+ );
127
+
128
+ if (issues.length === 1) {
129
+ // If an element has just one issue, output that issue inline, to reduce the number of clicks in the console for the user.
130
+ console.log(issues[0]?.description);
131
+ } else {
132
+ for (const issue of issues) {
133
+ console.groupCollapsed(
134
+ `%c${uppercasedImpactText(issue.impact)}:%c\n${titleAndUrl(issue)}`,
135
+ `color: ${colors[issue.impact]};`,
136
+ defaultStyle,
137
+ );
138
+ console.log(issue.description);
139
+ console.groupEnd();
140
+ }
141
+ }
142
+ console.groupEnd();
143
+ }
144
+ }
145
+
146
+ function logIssuesByType(issueTypeGroups: Array<IssueType>) {
147
+ for (const { title, url, impact, elements } of issueTypeGroups) {
148
+ // We'll output the element itself next to the issue description if there's just one associated element,
149
+ // to reduce the number of clicks in the console for the user.
150
+ const shouldOutputElementInline = elements.length === 1;
151
+ const baseOutput = `%c${uppercasedImpactText(impact)} (${elements.length} element${elements.length === 1 ? '' : 's'}):%c\n${titleAndUrl({ title, url })}`;
152
+ const output = shouldOutputElementInline ? `${baseOutput}\n%o` : baseOutput;
153
+ console.groupCollapsed(
154
+ output,
155
+ `color: ${colors[impact]};`,
156
+ defaultStyle,
157
+ ...(shouldOutputElementInline ? [elements[0]?.element] : []),
158
+ );
159
+ if (shouldOutputElementInline) {
160
+ console.log(elements[0]?.description);
161
+ } else {
162
+ for (const { element, description } of elements.sort((elementContainer1, elementContainer2) =>
163
+ sortByElementPositions(elementContainer1.element, elementContainer2.element),
164
+ )) {
165
+ console.groupCollapsed('%o', element);
166
+ console.log(description);
167
+ console.groupEnd();
168
+ }
169
+ }
170
+ console.groupEnd();
171
+ }
172
+ }
173
+
174
+ function logNewIssues(
175
+ elementsWithIssues: Array<ElementWithIssues>,
176
+ previousElementsWithIssues: Array<ElementWithIssues>,
177
+ ) {
178
+ // The elements with accessibility issues that didn't have any associated issues
179
+ // or that weren't in the DOM at the time of last scan.
180
+ const addedElements = elementsWithIssues.filter((elementWithIssues) => {
181
+ return !previousElementsWithIssues.some((previousElementWithIssues) =>
182
+ areElementsWithIssuesEqual(previousElementWithIssues, elementWithIssues),
183
+ );
184
+ });
185
+
186
+ // The elements that now have more issues than at the time of last scan,
187
+ // with just the new issues (previously existing issues are filtered out).
188
+ const existingElementsWithNewIssues = elementsWithIssues.reduce<Array<ElementWithIssues>>(
189
+ (acc, elementWithIssues) => {
190
+ let foundElementWithIssues: ElementWithIssues | null = null;
191
+ for (const previousElementWithIssues of previousElementsWithIssues) {
192
+ if (
193
+ areElementsWithIssuesEqual(previousElementWithIssues, elementWithIssues) &&
194
+ !areIssueSetsEqual(previousElementWithIssues.issues, elementWithIssues.issues)
195
+ ) {
196
+ const newIssues = elementWithIssues.issues.filter((issue) => {
197
+ return !previousElementWithIssues.issues.some((prevIssue) =>
198
+ areIssuesEqual(prevIssue, issue),
199
+ );
200
+ });
201
+ if (newIssues.length > 0) {
202
+ foundElementWithIssues = {
203
+ ...elementWithIssues,
204
+ issues: newIssues,
205
+ };
206
+ acc.push(foundElementWithIssues);
207
+ }
208
+ break;
209
+ }
210
+ }
211
+ return acc;
212
+ },
213
+ [],
214
+ );
215
+
216
+ const elementsWithNewIssues = [...addedElements, ...existingElementsWithNewIssues];
217
+ const newIssueCount = elementsWithNewIssues.reduce((acc, { issues }) => acc + issues.length, 0);
218
+ if (newIssueCount === 0) {
219
+ console.log('No new issues');
220
+ } else {
221
+ const newIssuesMessage = `%cNew issues (${newIssueCount} in ${elementsWithNewIssues.length} element${elementsWithNewIssues.length === 1 ? '' : 's'})`;
222
+ if (newIssueCount <= MAX_ISSUES_BEFORE_OUTPUT_COLLAPSE) {
223
+ // Don't collapse the new issues if there are not too many (this hopefully helps user avoid unnecessary clicks in the console).
224
+ console.group(newIssuesMessage, defaultStyle);
225
+ } else {
226
+ console.groupCollapsed(newIssuesMessage, defaultStyle);
227
+ }
228
+ // Output by element (no specific reason for this choice, just a preference).
229
+ logIssuesByElement(elementsWithNewIssues);
230
+ console.groupEnd();
231
+ }
232
+ }
233
+
234
+ function logIssues(
235
+ elementsWithIssues: Array<ElementWithIssues>,
236
+ previousElementsWithIssues: Array<ElementWithIssues>,
237
+ ) {
238
+ const elementCount = elementsWithIssues.length;
239
+
240
+ if (elementCount === 0) {
241
+ console.log(`No accessibility issues (Accented, ${accentedUrl}).`);
242
+ return;
243
+ }
244
+
245
+ const issueCount = elementsWithIssues.reduce((acc, { issues }) => acc + issues.length, 0);
246
+ console.group(
247
+ `%c${issueCount} accessibility issue${issueCount === 1 ? '' : 's'} in ${elementCount} element${elementCount === 1 ? '' : 's'} (Accented, ${accentedUrl}):\n`,
248
+ defaultStyle,
249
+ );
250
+
251
+ if (issueCount <= MAX_ISSUES_BEFORE_OUTPUT_COLLAPSE) {
252
+ // Don't collapse issues if there are not too many (this hopefully helps user avoid unnecessary clicks in the console).
253
+ // Output by element (no specific reason for this choice, just a preference).
254
+ logIssuesByElement(elementsWithIssues);
255
+ } else {
256
+ // When there are many issues, outputting them all would probably make the console too noisy,
257
+ // so we collapse them.
258
+ // Moreover, we output all issues twice, by element and by issue type, to give users more choice.
259
+ console.groupCollapsed(`%cAll by element (${elementsWithIssues.length})`, defaultStyle);
260
+ logIssuesByElement(elementsWithIssues);
261
+ console.groupEnd();
262
+
263
+ const issueTypeGroups = getIssueTypeGroups(elementsWithIssues);
264
+ console.groupCollapsed(`%cAll by issue type (${issueTypeGroups.length})`, defaultStyle);
265
+ logIssuesByType(issueTypeGroups);
266
+ console.groupEnd();
267
+ }
268
+
269
+ if (previousElementsWithIssues.length > 0) {
270
+ // Log new issues separately, to make it easier for the user to know what issues
271
+ // were introduced recently.
272
+ logNewIssues(elementsWithIssues, previousElementsWithIssues);
273
+ }
274
+
275
+ console.groupEnd();
8
276
  }
9
277
 
10
278
  export function createLogger() {
11
- let firstRun = true;
279
+ let previousElementsWithIssues: Array<ElementWithIssues> = [];
12
280
 
13
281
  return effect(() => {
14
282
  if (!enabled.value) {
15
283
  return;
16
284
  }
17
285
 
18
- const elementCount = elementsWithIssues.value.length;
19
- if (elementCount > 0) {
20
- const issueCount = elementsWithIssues.value.reduce(
21
- (acc, { issues }) => acc + issues.length,
22
- 0,
23
- );
24
- console.log(
25
- `${issueCount} accessibility issue${issueCount === 1 ? '' : 's'} found in ${elementCount} element${issueCount === 1 ? '' : 's'} (Accented, ${accentedUrl}):\n`,
26
- filterPropsForOutput(elementsWithIssues.value),
27
- );
28
- } else {
29
- if (firstRun) {
30
- firstRun = false;
31
- } else {
32
- console.log(`No accessibility issues found (Accented, ${accentedUrl}).`);
33
- }
34
- }
286
+ logIssues(elementsWithIssues.value, previousElementsWithIssues);
287
+
288
+ previousElementsWithIssues = elementsWithIssues.value;
35
289
  });
36
290
  }
package/src/types.ts CHANGED
@@ -36,9 +36,40 @@ export type Output = {
36
36
  /**
37
37
  * Whether the list of elements with issues should be printed to the browser console whenever issues are added, removed, or changed.
38
38
  *
39
+ * **Example:**
40
+ *
41
+ * ```js
42
+ * accented({
43
+ * output: {
44
+ * console: false
45
+ * }
46
+ * });
47
+ * ```
48
+ *
49
+ * In the example, the issues will not be logged to the console, while elements on the page will still be highlighted.
50
+ *
39
51
  * @default true
40
52
  * */
41
53
  console?: boolean;
54
+
55
+ /**
56
+ * Whether Accented should highlight elements with issues on the page.
57
+ *
58
+ * **Example:**
59
+ *
60
+ * ```js
61
+ * accented({
62
+ * output: {
63
+ * page: false
64
+ * }
65
+ * });
66
+ * ```
67
+ *
68
+ * In the example, the issues will only be logged to the console, with no highlights on the page.
69
+ *
70
+ * @default true
71
+ * */
72
+ page?: boolean;
42
73
  };
43
74
 
44
75
  /**
@@ -229,7 +260,7 @@ export type Issue = {
229
260
  title: string;
230
261
  description: string;
231
262
  url: string;
232
- impact: axe.ImpactValue;
263
+ impact: Exclude<axe.ImpactValue, null>;
233
264
  };
234
265
 
235
266
  export type BaseElementWithIssues = {
@@ -1,12 +1,9 @@
1
1
  import type { Issue } from '../types.ts';
2
-
3
- const issueProps: Array<keyof Issue> = ['id', 'title', 'description', 'url', 'impact'];
2
+ import { areIssuesEqual } from './are-issues-equal.js';
4
3
 
5
4
  export function areIssueSetsEqual(issues1: Array<Issue>, issues2: Array<Issue>) {
6
5
  return (
7
6
  issues1.length === issues2.length &&
8
- issues1.every((issue1) =>
9
- Boolean(issues2.find((issue2) => issueProps.every((prop) => issue2[prop] === issue1[prop]))),
10
- )
7
+ issues1.every((issue1) => Boolean(issues2.find((issue2) => areIssuesEqual(issue1, issue2))))
11
8
  );
12
9
  }
@@ -0,0 +1,7 @@
1
+ import type { Issue } from '../types.ts';
2
+
3
+ const issueProps: Array<keyof Issue> = ['id', 'title', 'description', 'url', 'impact'];
4
+
5
+ export function areIssuesEqual(issue1: Issue, issue2: Issue) {
6
+ return issueProps.every((prop) => issue2[prop] === issue1[prop]);
7
+ }
@@ -1,4 +1,5 @@
1
- import type { AxeResults, ImpactValue } from 'axe-core';
1
+ import type { AxeResults } from 'axe-core';
2
+ import { issuesUrl, orderedImpacts } from '../constants.js';
2
3
  import type { ElementWithIssues, Issue } from '../types.ts';
3
4
 
4
5
  // This is a list of axe-core violations (their ids) that may be flagged by axe-core
@@ -20,9 +21,8 @@ function maybeCausedByAccented(violationId: string, element: HTMLElement, name:
20
21
  );
21
22
  }
22
23
 
23
- function impactCompare(a: ImpactValue, b: ImpactValue) {
24
- const impactOrder = [null, 'minor', 'moderate', 'serious', 'critical'];
25
- return impactOrder.indexOf(a) - impactOrder.indexOf(b);
24
+ function impactCompare(a: Issue['impact'], b: Issue['impact']) {
25
+ return orderedImpacts.indexOf(a) - orderedImpacts.indexOf(b);
26
26
  }
27
27
 
28
28
  export function transformViolations(violations: typeof AxeResults.violations, name: string) {
@@ -41,14 +41,20 @@ export function transformViolations(violations: typeof AxeResults.violations, na
41
41
  const isInIframe = target.length > 1;
42
42
 
43
43
  if (element && !isInIframe && !maybeCausedByAccented(violation.id, element, name)) {
44
+ if (!violation.impact) {
45
+ console.warn(
46
+ `Accented: axe-core (the accessibility testing engine) returned a violation with an empty impact. This may be a bug in axe-core or in Accented. Please report it at ${issuesUrl}.`,
47
+ violation,
48
+ );
49
+ continue;
50
+ }
44
51
  const issue: Issue = {
45
52
  id: violation.id,
46
53
  title: violation.help,
47
54
  // See https://github.com/pomerantsev/accented/issues/203
48
55
  description: node.failureSummary ?? violation.description,
49
56
  url: violation.helpUrl,
50
- // See https://github.com/pomerantsev/accented/issues/203
51
- impact: violation.impact ?? null,
57
+ impact: violation.impact,
52
58
  };
53
59
  const existingElement = elementsWithIssues.find(
54
60
  (elementWithIssues) => elementWithIssues.element === element,
@@ -149,6 +149,11 @@ export function validateOptions(options: AccentedOptions) {
149
149
  `Accented: invalid argument. \`output.console\` option is expected to be a boolean. It’s currently set to ${options.output.console}.`,
150
150
  );
151
151
  }
152
+ if (options.output.page !== undefined && typeof options.output.page !== 'boolean') {
153
+ console.warn(
154
+ `Accented: invalid argument. \`output.page\` option is expected to be a boolean. It’s currently set to ${options.output.page}.`,
155
+ );
156
+ }
152
157
  }
153
158
  if (options.callback !== undefined && typeof options.callback !== 'function') {
154
159
  throw new TypeError(