diff --git a/packages/report-e2e-tests/src/examples/axe-results-with-issues.snap.html b/packages/report-e2e-tests/src/examples/axe-results-with-issues.snap.html
index b63fa2cc98..96899b3151 100644
--- a/packages/report-e2e-tests/src/examples/axe-results-with-issues.snap.html
+++ b/packages/report-e2e-tests/src/examples/axe-results-with-issues.snap.html
@@ -957,6 +957,12 @@
display: flex;
}
+ /* css-modules:src/common/components/cards/related-paths-card-row */
+ .path-list--MxxKM {
+ padding-left: 16px;
+ list-style-type: disc;
+ }
+
/* css-modules:src/common/components/cards/rich-resolution-content */
.multi-line-text-yes-bullet--TXyX- {
padding-left: 16px;
@@ -2113,6 +2119,14 @@
><a
href="#">About</a>
Related paths | |
---|
How to fix | <a
href="#">Academics</a> |
---|
Related paths | |
---|
How to fix | <a
href="#">Admissions</a> |
---|
Related paths | |
---|
How to fix | <a
href="#">Visitors</a> |
---|
Related paths | |
---|
How to fix | <html
class="deque-axe-is-ready"> |
---|
Related paths | - #logo
- li:nth-child(1) >
+ a[href="\#"]
- li:nth-child(2) >
+ a[href="\#"]
- li:nth-child(3) >
+ a[href="\#"]
- li:nth-child(4) >
+ a[href="\#"]
- img[src$="slide2\.jpg"]
- a[href="somepage\.html\?ref\=Slide\%202"]
+ > .description
- img[src$="arrow-left\.png"]
- img[src$="arrow-right\.png"]
- .heading:nth-child(2)
- p:nth-child(3)
- .hr[src$="line_gradient\.gif"][alt="horizontal\
+ line\ graphic"]:nth-child(4)
- .heading:nth-child(5)
- p:nth-child(6)
- .hr[src$="line_gradient\.gif"][alt="horizontal\
+ line\ graphic"]:nth-child(7)
- .heading:nth-child(8)
- p:nth-child(9)
- .hr[src$="line_gradient\.gif"][alt="horizontal\
+ line\ graphic"]:nth-child(10)
- .heading:nth-child(11)
- td[colspan="\36
+ "]:nth-child(2) > b
- td[colspan="\36
+ "]:nth-child(3) > b
- tr:nth-child(2) > td:nth-child(2)
+ > b
- tr:nth-child(2) > td:nth-child(3)
+ > b
- td:nth-child(4) > b
- td:nth-child(5) > b
- td:nth-child(6) > b
- td:nth-child(7) > b
- td:nth-child(8) > b
- td:nth-child(9) > b
- td:nth-child(10) > b
- td:nth-child(11) > b
- td:nth-child(12) > b
- td:nth-child(13) > b
- td:nth-child(1) > b
- tr:nth-child(3) >
+ td:nth-child(2)
- tr:nth-child(3) >
+ td:nth-child(3)
- tr:nth-child(3) >
+ td:nth-child(4)
- tr:nth-child(3) >
+ td:nth-child(5)
- tr:nth-child(3) >
+ td:nth-child(6)
- tr:nth-child(3) >
+ td:nth-child(7)
- tr:nth-child(3) >
+ td:nth-child(8)
- tr:nth-child(3) >
+ td:nth-child(9)
- tr:nth-child(3) >
+ td:nth-child(10)
- tr:nth-child(3) >
+ td:nth-child(11)
- tr:nth-child(3) >
+ td:nth-child(12)
- tr:nth-child(3) >
+ td:nth-child(13)
- tr:nth-child(4) >
+ td:nth-child(1)
- tr:nth-child(4) >
+ td:nth-child(2)
- tr:nth-child(4) >
+ td:nth-child(3)
- tr:nth-child(4) >
+ td:nth-child(4)
- tr:nth-child(4) >
+ td:nth-child(5)
- tr:nth-child(4) >
+ td:nth-child(6)
- tr:nth-child(4) >
+ td:nth-child(7)
- tr:nth-child(4) >
+ td:nth-child(8)
- tr:nth-child(4) >
+ td:nth-child(9)
- tr:nth-child(4) >
+ td:nth-child(10)
- tr:nth-child(4) >
+ td:nth-child(11)
- tr:nth-child(4) >
+ td:nth-child(12)
- tr:nth-child(4) >
+ td:nth-child(13)
- tr:nth-child(5) >
+ td:nth-child(1)
- tr:nth-child(5) >
+ td:nth-child(2)
- tr:nth-child(5) >
+ td:nth-child(3)
- tr:nth-child(5) >
+ td:nth-child(4)
- tr:nth-child(5) >
+ td:nth-child(5)
- tr:nth-child(5) >
+ td:nth-child(6)
- tr:nth-child(5) >
+ td:nth-child(7)
- tr:nth-child(5) >
+ td:nth-child(8)
- tr:nth-child(5) >
+ td:nth-child(9)
- tr:nth-child(5) >
+ td:nth-child(10)
- tr:nth-child(5) >
+ td:nth-child(11)
- tr:nth-child(5) >
+ td:nth-child(12)
- tr:nth-child(5) >
+ td:nth-child(13)
- #appForm > .heading
- .reqExample
- form > .required:nth-child(2)
- .required:nth-child(3)
- form > div:nth-child(4)
- form > div:nth-child(5)
- form > div:nth-child(6)
- div:nth-child(7)
- div:nth-child(8) > b
- .major:nth-child(1)
- .major:nth-child(2)
- .major:nth-child(3)
- .major:nth-child(4)
- .major:nth-child(5)
- .major:nth-child(6)
- #captcha > b
- #captcha > p
- input[name="captcha"]
- img[src$="captcha\.png"]
- #submit
- #footer
|
---|
How to fix | <div>snippet</div> |
---|
Related paths | |
---|
How to fix | <div>snippet</div>
---|
Related paths | |
---|
How to fix | (
+ 'RelatedPathsCardRow',
+ ({ index, propertyData }) => {
+ if (isEmpty(propertyData)) {
+ return null;
+ }
+
+ return (
+
+ {propertyData.map(selector => (
+ {selector}
+ ))}
+
+ }
+ rowKey={`related-paths-row-${index}`}
+ />
+ );
+ },
+);
diff --git a/src/common/components/fix-instruction-processor.tsx b/src/common/components/fix-instruction-processor.tsx
index fee3f63456..23fd875806 100644
--- a/src/common/components/fix-instruction-processor.tsx
+++ b/src/common/components/fix-instruction-processor.tsx
@@ -46,7 +46,42 @@ export class FixInstructionProcessor {
private readonly originalMiddleSentence = ' and the original foreground color: ';
public process(fixInstruction: string, recommendColor: RecommendColor): JSX.Element {
+ return (
+ this.tryProcessAsColorContrastRecommendation(fixInstruction, recommendColor) ??
+ this.tryProcessAsRelatedNodesReference(fixInstruction) ??
+ this.processAsNoop(fixInstruction)
+ );
+ }
+
+ private processAsNoop(fixInstruction: string): JSX.Element {
+ return <>{fixInstruction}>;
+ }
+
+ // We perform this replacement because what axe-core exposes as a "relatedNodes" property
+ // is presented in our cards views as a "Related paths" row. This only comes up in practice
+ // with the aria-required-children rule and is likely to be obsoleted with the resolution of
+ // https://github.com/dequelabs/axe-core/issues/3842
+ private tryProcessAsRelatedNodesReference(input: string): JSX.Element | null {
+ if (!input.endsWith(' (see related nodes)')) {
+ return null;
+ }
+
+ const inputBody = input.slice(0, input.length - ' (see related nodes)'.length);
+ return (
+ <>
+ {inputBody} (see Related paths)
+ >
+ );
+ }
+
+ private tryProcessAsColorContrastRecommendation(
+ fixInstruction: string,
+ recommendColor: RecommendColor,
+ ): JSX.Element | null {
const matches = this.getColorMatches(fixInstruction);
+ if (matches.length === 0) {
+ return null;
+ }
let recommendationSentences: string[] = [];
if (matches.length === 2 && fixInstruction != null) {
diff --git a/src/common/configs/unified-result-property-configurations.tsx b/src/common/configs/unified-result-property-configurations.tsx
index cc509a662b..6ae3cb36d1 100644
--- a/src/common/configs/unified-result-property-configurations.tsx
+++ b/src/common/configs/unified-result-property-configurations.tsx
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
import { ClassNameCardRow } from 'common/components/cards/class-name-card-row';
import { ContentDescriptionCardRow } from 'common/components/cards/content-description-card-row';
+import { RelatedPathsCardRow } from 'common/components/cards/related-paths-card-row';
import { RichResolutionCardRow } from 'common/components/cards/rich-resolution-card-row';
import { TextCardRow } from 'common/components/cards/text-card-row';
import { UrlsCardRow } from 'common/components/cards/urls-card-row';
@@ -13,15 +14,7 @@ import { SnippetCardRow } from '../components/cards/snippet-card-row';
import { FixInstructionProcessor } from '../components/fix-instruction-processor';
import { ReactFCWithDisplayName } from '../react/named-fc';
-export type PropertyType =
- | 'css-selector'
- | 'how-to-fix-web'
- | 'richResolution'
- | 'snippet'
- | 'className'
- | 'contentDescription'
- | 'text';
-export const AllPropertyTypes: PropertyType[] = [
+export const AllPropertyTypes = [
'css-selector',
'how-to-fix-web',
'richResolution',
@@ -29,7 +22,9 @@ export const AllPropertyTypes: PropertyType[] = [
'className',
'contentDescription',
'text',
-];
+ 'relatedCssSelectors',
+] as const;
+export type PropertyType = (typeof AllPropertyTypes)[number];
export interface CardRowDeps {
fixInstructionProcessor: FixInstructionProcessor;
@@ -59,6 +54,10 @@ export const cssSelectorConfiguration: PropertyConfiguration = {
cardRow: PathCardRow,
};
+export const relatedCssSelectorsConfiguration: PropertyConfiguration = {
+ cardRow: RelatedPathsCardRow,
+};
+
export const snippetConfiguration: PropertyConfiguration = {
cardRow: SnippetCardRow,
};
@@ -91,6 +90,7 @@ const propertyIdToConfigurationMap: PropertyIdToConfigurationMap = {
contentDescription: contentDescriptionConfiguration,
text: textConfiguration,
urls: urlsConfiguration,
+ relatedCssSelectors: relatedCssSelectorsConfiguration,
};
export function getPropertyConfiguration(id: string): Readonly {
diff --git a/src/common/types/store-data/unified-data-interface.ts b/src/common/types/store-data/unified-data-interface.ts
index ae240bb6d3..366fdf0581 100644
--- a/src/common/types/store-data/unified-data-interface.ts
+++ b/src/common/types/store-data/unified-data-interface.ts
@@ -93,6 +93,7 @@ export type UnifiedIdentifiers = {
export type UnifiedDescriptors = {
snippet?: string;
boundingRectangle?: BoundingRectangle;
+ relatedCssSelectors?: string[];
} & InstancePropertyBag;
export type UnifiedRichResolution = {
diff --git a/src/common/types/store-data/visualization-scan-result-data.ts b/src/common/types/store-data/visualization-scan-result-data.ts
index 3fd1fa92cb..f3bc432d30 100644
--- a/src/common/types/store-data/visualization-scan-result-data.ts
+++ b/src/common/types/store-data/visualization-scan-result-data.ts
@@ -11,6 +11,12 @@ export interface FormattedCheckResult {
id: string;
message: string;
data: any;
+ relatedNodes?: AxeRelatedNode[];
+}
+
+export interface AxeRelatedNode {
+ target: (string | string[])[];
+ html: string;
}
export interface CheckData {
diff --git a/src/injected/adapters/extract-related-selectors.ts b/src/injected/adapters/extract-related-selectors.ts
new file mode 100644
index 0000000000..d1059ebe46
--- /dev/null
+++ b/src/injected/adapters/extract-related-selectors.ts
@@ -0,0 +1,32 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+import { TargetHelper } from 'common/target-helper';
+import { uniq } from 'lodash';
+import { AxeNodeResult } from '../../scanner/iruleresults';
+
+export type RelatedSelectorExtractor = typeof extractRelatedSelectors;
+
+export function extractRelatedSelectors(nodeResult: AxeNodeResult): string[] | undefined {
+ const nodeSelector = TargetHelper.getSelectorFromTarget(nodeResult.target);
+
+ let relatedSelectors: string[] = [];
+ for (const checkType of ['all', 'any', 'none'] as const) {
+ const checks = nodeResult[checkType];
+ for (const check of checks) {
+ const relatedSelectorsForCheck =
+ check.relatedNodes?.map(n => TargetHelper.getSelectorFromTarget(n.target)) ?? [];
+ relatedSelectors.push(...relatedSelectorsForCheck);
+ }
+ }
+
+ // This doesn't usually change anything; it would mainly affect if a rule had multiple
+ // checks that each returned overlapping sets of related nodes. Axe generally returns
+ // nodes in a reasonable order, so we want to use a method that preserves order here.
+ relatedSelectors = uniq(relatedSelectors);
+
+ // This does have a practical impact; some axe checks (eg, color-contrast) will occasionally
+ // return the node itself as a "related node", and we'd rather avoid this.
+ relatedSelectors = relatedSelectors.filter(relatedSelector => relatedSelector !== nodeSelector);
+
+ return relatedSelectors.length === 0 ? undefined : relatedSelectors;
+}
diff --git a/src/injected/adapters/scan-results-to-unified-results.ts b/src/injected/adapters/scan-results-to-unified-results.ts
index 13ccb137e3..4fbeb224a2 100644
--- a/src/injected/adapters/scan-results-to-unified-results.ts
+++ b/src/injected/adapters/scan-results-to-unified-results.ts
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+import { TargetHelper } from 'common/target-helper';
+import { RelatedSelectorExtractor } from 'injected/adapters/extract-related-selectors';
import { ResolutionCreator } from 'injected/adapters/resolution-creator';
import { flatMap } from 'lodash';
@@ -22,9 +24,10 @@ interface RuleResultData {
export class ConvertScanResultsToUnifiedResults {
constructor(
- private uuidGenerator: UUIDGenerator,
- private getFixResolution: ResolutionCreator,
- private getCheckResolution: ResolutionCreator,
+ private readonly uuidGenerator: UUIDGenerator,
+ private readonly getFixResolution: ResolutionCreator,
+ private readonly getCheckResolution: ResolutionCreator,
+ private readonly extractRelatedSelectors: RelatedSelectorExtractor,
) {}
public automatedChecksConversion: ConvertScanResultsToUnifiedResultsDelegate = (
@@ -111,7 +114,7 @@ export class ConvertScanResultsToUnifiedResults {
ruleResultData: RuleResultData,
getResolution: ResolutionCreator,
): UnifiedResult => {
- const cssSelector = nodeResult.target.join(';');
+ const cssSelector = TargetHelper.getSelectorFromTarget(nodeResult.target);
return {
uid: this.uuidGenerator(),
status: ruleResultData.status,
@@ -124,6 +127,7 @@ export class ConvertScanResultsToUnifiedResults {
},
descriptors: {
snippet: nodeResult.snippet || nodeResult.html,
+ relatedCssSelectors: this.extractRelatedSelectors(nodeResult),
},
resolution: {
howToFixSummary: nodeResult.failureSummary!,
diff --git a/src/injected/main-window-initializer.ts b/src/injected/main-window-initializer.ts
index c132c62545..ff5d750396 100644
--- a/src/injected/main-window-initializer.ts
+++ b/src/injected/main-window-initializer.ts
@@ -14,6 +14,7 @@ import { UnifiedScanResultStoreData } from 'common/types/store-data/unified-data
import { toolName } from 'content/strings/application';
import { TabStopRequirementActionMessageCreator } from 'DetailsView/actions/tab-stop-requirement-action-message-creator';
import { GetDetailsSwitcherNavConfiguration } from 'DetailsView/components/details-view-switcher-nav';
+import { extractRelatedSelectors } from 'injected/adapters/extract-related-selectors';
import { getCheckResolution, getFixResolution } from 'injected/adapters/resolution-creator';
import { filterNeedsReviewResults } from 'injected/analyzers/filter-results';
import { NotificationTextCreator } from 'injected/analyzers/notification-text-creator';
@@ -293,6 +294,7 @@ export class MainWindowInitializer extends WindowInitializer {
generateUID,
getFixResolution,
getCheckResolution,
+ extractRelatedSelectors,
);
const notificationTextCreator = new NotificationTextCreator(scanIncompleteWarningDetector);
diff --git a/src/reports/package/accessibilityInsightsReport.d.ts b/src/reports/package/accessibilityInsightsReport.d.ts
index 046cf926d6..41496ae637 100644
--- a/src/reports/package/accessibilityInsightsReport.d.ts
+++ b/src/reports/package/accessibilityInsightsReport.d.ts
@@ -65,6 +65,12 @@ declare namespace AccessibilityInsightsReport {
id: string;
message: string;
data: any;
+ relatedNodes?: AxeRelatedNode[];
+ }
+
+ export interface AxeRelatedNode {
+ target: (string | string[])[];
+ html: string;
}
export type HowToFixData = {
@@ -86,7 +92,7 @@ declare namespace AccessibilityInsightsReport {
elementSelector: string,
snippet: string,
fix: HowToFixData,
- rule: AxeRuleData
+ rule: AxeRuleData,
};
export interface FailuresGroup {
diff --git a/src/reports/package/combined-results-to-cards-model-converter.ts b/src/reports/package/combined-results-to-cards-model-converter.ts
index 3e96dec5e3..3c73b57150 100644
--- a/src/reports/package/combined-results-to-cards-model-converter.ts
+++ b/src/reports/package/combined-results-to-cards-model-converter.ts
@@ -5,6 +5,7 @@ import { CardSelectionViewData } from "common/get-card-selection-view-data";
import { CardResult, CardRuleResult, CardRuleResultsByStatus, CardsViewModel } from "common/types/store-data/card-view-model";
import { GuidanceLink } from "common/types/store-data/guidance-links";
import { UUIDGenerator } from "common/uid-generator";
+import { RelatedSelectorExtractor } from "injected/adapters/extract-related-selectors";
import { ResolutionCreator } from "injected/adapters/resolution-creator";
import { IssueFilingUrlStringUtils } from "issue-filing/common/issue-filing-url-string-utils";
import { isNil } from "lodash";
@@ -18,6 +19,7 @@ export class CombinedResultsToCardsModelConverter {
private readonly uuidGenerator: UUIDGenerator,
private readonly helpUrlGetter: HelpUrlGetter,
private readonly getFixResolution: ResolutionCreator,
+ private readonly extractRelatedSelectors: RelatedSelectorExtractor,
) {}
public convertResults = (
@@ -74,6 +76,11 @@ export class CombinedResultsToCardsModelConverter {
private getFailureCardResult = (failureData: FailureData): CardResult => {
const rule = failureData.rule;
const cssSelector = failureData.elementSelector;
+ const relatedCssSelectors = this.extractRelatedSelectors({
+ target: [cssSelector],
+ html: failureData.snippet,
+ ...failureData.fix,
+ });
const urls: any = {};
if (failureData.urls) {
@@ -100,6 +107,7 @@ export class CombinedResultsToCardsModelConverter {
},
descriptors: {
snippet: failureData.snippet,
+ relatedCssSelectors,
},
resolution: {
howToFixSummary: failureData.fix.failureSummary,
diff --git a/src/reports/package/reporter-factory.ts b/src/reports/package/reporter-factory.ts
index 0bb3ca43ac..b10e082f7c 100644
--- a/src/reports/package/reporter-factory.ts
+++ b/src/reports/package/reporter-factory.ts
@@ -7,6 +7,7 @@ import { getA11yInsightsWebRuleUrl } from 'common/configs/a11y-insights-rule-res
import { CardSelectionViewData } from 'common/get-card-selection-view-data';
import { getCardViewData } from 'common/rule-based-view-model-provider';
import { generateUID } from 'common/uid-generator';
+import { extractRelatedSelectors } from 'injected/adapters/extract-related-selectors';
import { getCheckResolution, getFixResolution } from 'injected/adapters/resolution-creator';
import { ConvertScanResultsToUnifiedResults } from 'injected/adapters/scan-results-to-unified-results';
import { convertScanResultsToUnifiedRules } from 'injected/adapters/scan-results-to-unified-rules';
@@ -94,7 +95,12 @@ const axeResultsReportGenerator = (parameters: AxeReportParameters) => {
mapAxeTagsToGuidanceLinks,
ruleProcessor,
);
- const getUnifiedResults = new ConvertScanResultsToUnifiedResults(generateUID, getFixResolution, getCheckResolution).automatedChecksConversion;
+ const getUnifiedResults = new ConvertScanResultsToUnifiedResults(
+ generateUID,
+ getFixResolution,
+ getCheckResolution,
+ extractRelatedSelectors
+ ).automatedChecksConversion;
const deps: AxeResultsReportDeps = {
reportHtmlGenerator,
@@ -179,6 +185,7 @@ const combinedResultsReportGenerator = (parameters: CombinedReportParameters) =>
generateUID,
helpUrlGetter,
getFixResolution,
+ extractRelatedSelectors,
);
return new CombinedResultsReport(deps, parameters, toolData, resultsToCardsConverter);
diff --git a/src/scanner/iruleresults.d.ts b/src/scanner/iruleresults.d.ts
index 85f7911b33..c8b90b22f8 100644
--- a/src/scanner/iruleresults.d.ts
+++ b/src/scanner/iruleresults.d.ts
@@ -69,6 +69,12 @@ export interface FormattedCheckResult {
message: string;
data: IAxeCheckResultExtraData;
result?: boolean;
+ relatedNodes?: AxeRelatedNode[];
+}
+
+export interface AxeRelatedNode {
+ target: (string | string[])[];
+ html: string;
}
export interface IAxeCheckResultExtraData {
diff --git a/src/tests/unit/tests/common/components/cards/__snapshots__/related-paths-card-row.test.tsx.snap b/src/tests/unit/tests/common/components/cards/__snapshots__/related-paths-card-row.test.tsx.snap
new file mode 100644
index 0000000000..f98415d8b7
--- /dev/null
+++ b/src/tests/unit/tests/common/components/cards/__snapshots__/related-paths-card-row.test.tsx.snap
@@ -0,0 +1,23 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`RelatedPathsCardRow renders matching snapshot with related paths present 1`] = `
+
+
+ #path-1a;.path-1b
+
+
+ #path-2
+
+
+ .path-3
+
+
+ }
+ label="Related paths"
+ rowKey="related-paths-row-123"
+/>
+`;
diff --git a/src/tests/unit/tests/common/components/cards/related-paths-card-row.test.tsx b/src/tests/unit/tests/common/components/cards/related-paths-card-row.test.tsx
new file mode 100644
index 0000000000..5c927d2c78
--- /dev/null
+++ b/src/tests/unit/tests/common/components/cards/related-paths-card-row.test.tsx
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+import {
+ RelatedPathsCardRow,
+ RelatedPathsCardRowProps,
+} from 'common/components/cards/related-paths-card-row';
+import { shallow } from 'enzyme';
+import * as React from 'react';
+
+describe(RelatedPathsCardRow.displayName, () => {
+ it.each([[], null, undefined])(
+ 'renders as null with related paths: %p',
+ (relatedPaths?: any) => {
+ const props: RelatedPathsCardRowProps = {
+ deps: null,
+ index: 123,
+ propertyData: relatedPaths,
+ };
+ const testSubject = shallow();
+
+ expect(testSubject.getElement()).toBeNull();
+ },
+ );
+
+ it('renders matching snapshot with related paths present', () => {
+ const props: RelatedPathsCardRowProps = {
+ deps: null,
+ index: 123,
+ propertyData: ['#path-1a;.path-1b', '#path-2', '.path-3'],
+ };
+ const testSubject = shallow();
+
+ expect(testSubject.getElement()).toMatchSnapshot();
+ });
+});
diff --git a/src/tests/unit/tests/injected/__snapshots__/fix-instruction-processor.test.tsx.snap b/src/tests/unit/tests/injected/__snapshots__/fix-instruction-processor.test.tsx.snap
index bfca5facb8..a6ab4736a0 100644
--- a/src/tests/unit/tests/injected/__snapshots__/fix-instruction-processor.test.tsx.snap
+++ b/src/tests/unit/tests/injected/__snapshots__/fix-instruction-processor.test.tsx.snap
@@ -157,12 +157,6 @@ exports[`FixInstructionProcessor foreground color on the message 1`] = `
`;
-exports[`FixInstructionProcessor no background nor foreground on the message 1`] = `
-
- there is nothing that will trigger the processor to change the fix instruction here
-
-`;
-
exports[`FixInstructionProcessor recommendColor has one recommendation 1`] = `
@@ -377,6 +371,17 @@ exports[`FixInstructionProcessor recommendColor has two recommendations 1`] = `
`;
+exports[`FixInstructionProcessor updates aria-required-children "(see related nodes)" text to "(see Related paths)" 1`] = `
+
+ Element has children which are not allowed
+ (see
+
+ Related paths
+
+ )
+
+`;
+
exports[`FixInstructionProcessor we assume only one instance of fore/background color, second instance will be ignored 1`] = `
diff --git a/src/tests/unit/tests/injected/adapters/__snapshots__/scan-results-to-unified-results.test.ts.snap b/src/tests/unit/tests/injected/adapters/__snapshots__/scan-results-to-unified-results.test.ts.snap
index 2ad80e9b62..c6987361cc 100644
--- a/src/tests/unit/tests/injected/adapters/__snapshots__/scan-results-to-unified-results.test.ts.snap
+++ b/src/tests/unit/tests/injected/adapters/__snapshots__/scan-results-to-unified-results.test.ts.snap
@@ -6,6 +6,9 @@ exports[`ScanResults to Unified Results Test automaticChecksConversion works wit
[
{
"descriptors": {
+ "relatedCssSelectors": [
+ "extractRelatedSelectors output for target1,id1",
+ ],
"snippet": "html1",
},
"identifiers": {
@@ -27,6 +30,9 @@ exports[`ScanResults to Unified Results Test automaticChecksConversion works wit
},
{
"descriptors": {
+ "relatedCssSelectors": [
+ "extractRelatedSelectors output for target2,id2",
+ ],
"snippet": "html2",
},
"identifiers": {
@@ -48,6 +54,9 @@ exports[`ScanResults to Unified Results Test automaticChecksConversion works wit
},
{
"descriptors": {
+ "relatedCssSelectors": [
+ "extractRelatedSelectors output for target3,id3",
+ ],
"snippet": "html3",
},
"identifiers": {
@@ -69,6 +78,9 @@ exports[`ScanResults to Unified Results Test automaticChecksConversion works wit
},
{
"descriptors": {
+ "relatedCssSelectors": [
+ "extractRelatedSelectors output for passTarget1,passTarget2",
+ ],
"snippet": "html-pass",
},
"identifiers": {
@@ -97,6 +109,9 @@ exports[`ScanResults to Unified Results Test needsReviewConversion works with fi
[
{
"descriptors": {
+ "relatedCssSelectors": [
+ "extractRelatedSelectors output for incompleteTarget1",
+ ],
"snippet": "html-incomplete",
},
"identifiers": {
@@ -117,6 +132,9 @@ exports[`ScanResults to Unified Results Test needsReviewConversion works with fi
},
{
"descriptors": {
+ "relatedCssSelectors": [
+ "extractRelatedSelectors output for target1,id1",
+ ],
"snippet": "html1",
},
"identifiers": {
@@ -138,6 +156,9 @@ exports[`ScanResults to Unified Results Test needsReviewConversion works with fi
},
{
"descriptors": {
+ "relatedCssSelectors": [
+ "extractRelatedSelectors output for target2,id2",
+ ],
"snippet": "html2",
},
"identifiers": {
@@ -159,6 +180,9 @@ exports[`ScanResults to Unified Results Test needsReviewConversion works with fi
},
{
"descriptors": {
+ "relatedCssSelectors": [
+ "extractRelatedSelectors output for target3,id3",
+ ],
"snippet": "html3",
},
"identifiers": {
diff --git a/src/tests/unit/tests/injected/adapters/extract-related-selectors.test.ts b/src/tests/unit/tests/injected/adapters/extract-related-selectors.test.ts
new file mode 100644
index 0000000000..d41d352b3c
--- /dev/null
+++ b/src/tests/unit/tests/injected/adapters/extract-related-selectors.test.ts
@@ -0,0 +1,160 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+import { extractRelatedSelectors } from 'injected/adapters/extract-related-selectors';
+import { AxeNodeResult, FormattedCheckResult, Target } from 'scanner/iruleresults';
+
+describe(extractRelatedSelectors, () => {
+ function checkResult(relatedTargets?: Target[]): FormattedCheckResult {
+ const result: FormattedCheckResult = {
+ id: 'check-id',
+ message: 'check-message',
+ data: {},
+ };
+ if (relatedTargets != null) {
+ result.relatedNodes = relatedTargets.map(target => ({ html: 'related-html', target }));
+ }
+ return result;
+ }
+ it('returns undefined if there are no relatedNodes', () => {
+ const input: AxeNodeResult = {
+ target: ['#node'],
+ html: 'node-html',
+ all: [checkResult()],
+ any: [checkResult()],
+ none: [],
+ };
+ const output = extractRelatedSelectors(input);
+ expect(output).toBeUndefined();
+ });
+
+ it('returns undefined if the only relatedNodes are the node itself', () => {
+ const input: AxeNodeResult = {
+ target: ['#node'],
+ html: 'node-html',
+ all: [],
+ any: [checkResult([['#node']])],
+ none: [checkResult([['#node']])],
+ };
+ const output = extractRelatedSelectors(input);
+ expect(output).toBeUndefined();
+ });
+
+ it('extracts as-is simple targets per relatedNode', () => {
+ const input: AxeNodeResult = {
+ target: ['#node'],
+ html: 'node-html',
+ all: [checkResult([['#related-1']])],
+ any: [],
+ none: [checkResult([['.related-2']])],
+ };
+ const output = extractRelatedSelectors(input);
+ expect(output).toStrictEqual(['#related-1', '.related-2']);
+ });
+
+ it('semicolon-joins array-style targets per relatedNode', () => {
+ const input: AxeNodeResult = {
+ target: ['#node'],
+ html: 'node-html',
+ all: [],
+ any: [checkResult([['#related-1-outer', '#related-1-inner']])],
+ none: [],
+ };
+ const output = extractRelatedSelectors(input);
+ expect(output).toStrictEqual(['#related-1-outer;#related-1-inner']);
+ });
+
+ it('comma and semicolon joins array-of-array-style targets per relatedNode', () => {
+ const input: AxeNodeResult = {
+ target: ['#node'],
+ html: 'node-html',
+ all: [],
+ any: [
+ checkResult([
+ [
+ ['#related-1-outer-1', '#related-1-outer-2'],
+ ['#related-1-inner-1', '#related-1-inner-2'],
+ ],
+ ]),
+ ],
+ none: [],
+ };
+ const output = extractRelatedSelectors(input);
+ expect(output).toStrictEqual([
+ '#related-1-outer-1,#related-1-outer-2;#related-1-inner-1,#related-1-inner-2',
+ ]);
+ });
+
+ it('includes multiple relatedNodes from same checks', () => {
+ const input: AxeNodeResult = {
+ target: ['#node'],
+ html: 'node-html',
+ all: [checkResult([['#related-1'], ['#related-2'], ['#related-3']])],
+ any: [],
+ none: [],
+ };
+ const output = extractRelatedSelectors(input);
+ expect(output).toStrictEqual(['#related-1', '#related-2', '#related-3']);
+ });
+
+ it('includes relatedNodes from multiple different checks', () => {
+ const input: AxeNodeResult = {
+ target: ['#node'],
+ html: 'node-html',
+ all: [checkResult([['#related-1']]), checkResult([['#related-2']])],
+ any: [],
+ none: [checkResult([['#related-3']])],
+ };
+ const output = extractRelatedSelectors(input);
+ expect(output).toStrictEqual(['#related-1', '#related-2', '#related-3']);
+ });
+
+ it('omits duplicates', () => {
+ const input: AxeNodeResult = {
+ target: ['#node'],
+ html: 'node-html',
+ all: [checkResult([['#related-1']]), checkResult([['#related-2']])],
+ any: [checkResult([['#related-2']]), checkResult([['#related-3']])],
+ none: [checkResult([['#related-3']]), checkResult([['#related-1']])],
+ };
+ const output = extractRelatedSelectors(input);
+ expect(output).toStrictEqual(['#related-1', '#related-2', '#related-3']);
+ });
+
+ it.each([[[1, 2, 3]], [[3, 2, 1]], [[1, 3, 2]]])(
+ 'maintains original order',
+ (order: number[]) => {
+ const relatedSelectors = order.map(n => `#related-${n}`);
+
+ const input: AxeNodeResult = {
+ target: ['#node'],
+ html: 'node-html',
+ all: [
+ checkResult([[relatedSelectors[0]]]),
+ checkResult([[relatedSelectors[1]]]),
+ checkResult([[relatedSelectors[2]]]),
+ ],
+ any: [],
+ none: [],
+ };
+ const output = extractRelatedSelectors(input);
+ expect(output).toStrictEqual([
+ relatedSelectors[0],
+ relatedSelectors[1],
+ relatedSelectors[2],
+ ]);
+ },
+ );
+
+ it('omits the node itself', () => {
+ const input: AxeNodeResult = {
+ target: ['#node'],
+ html: 'node-html',
+ all: [checkResult([['#related-1']]), checkResult([['#node']])],
+ any: [],
+ none: [checkResult([['#related-2']])],
+ };
+ const output = extractRelatedSelectors(input);
+ expect(output).toStrictEqual(['#related-1', '#related-2']);
+ });
+});
diff --git a/src/tests/unit/tests/injected/adapters/scan-results-to-unified-results.test.ts b/src/tests/unit/tests/injected/adapters/scan-results-to-unified-results.test.ts
index daa5d57593..d3d1844f30 100644
--- a/src/tests/unit/tests/injected/adapters/scan-results-to-unified-results.test.ts
+++ b/src/tests/unit/tests/injected/adapters/scan-results-to-unified-results.test.ts
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+import { RelatedSelectorExtractor } from 'injected/adapters/extract-related-selectors';
import { ResolutionCreator } from 'injected/adapters/resolution-creator';
import { ConvertScanResultsToUnifiedResults } from 'injected/adapters/scan-results-to-unified-results';
import { IMock, Mock, MockBehavior, Times } from 'typemoq';
@@ -12,6 +13,7 @@ describe('ScanResults to Unified Results Test', () => {
let generateGuidMock: IMock<() => string>;
let fixResolutionCreatorMock: IMock;
let checkResolutionCreatorMock: IMock;
+ let extractRelatedSelectorsStub: RelatedSelectorExtractor;
beforeEach(() => {
const guidStub = 'gguid-mock-stub';
@@ -22,6 +24,7 @@ describe('ScanResults to Unified Results Test', () => {
.verifiable(Times.atLeastOnce());
fixResolutionCreatorMock = Mock.ofType();
checkResolutionCreatorMock = Mock.ofType();
+ extractRelatedSelectorsStub = node => [`extractRelatedSelectors output for ${node.target}`];
});
const nullIdentifiers = [null, undefined, {}];
@@ -110,6 +113,7 @@ describe('ScanResults to Unified Results Test', () => {
generateGuidMock.object,
fixResolutionCreatorMock.object,
checkResolutionCreatorMock.object,
+ extractRelatedSelectorsStub,
);
}
diff --git a/src/tests/unit/tests/injected/fix-instruction-processor.test.tsx b/src/tests/unit/tests/injected/fix-instruction-processor.test.tsx
index ea509b501f..a10cc7988b 100644
--- a/src/tests/unit/tests/injected/fix-instruction-processor.test.tsx
+++ b/src/tests/unit/tests/injected/fix-instruction-processor.test.tsx
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
import { FixInstructionProcessor } from 'common/components/fix-instruction-processor';
import { RecommendColor } from 'common/components/recommend-color';
+import * as React from 'react';
import { Mock, Times } from 'typemoq';
describe('FixInstructionProcessor', () => {
@@ -13,13 +14,21 @@ describe('FixInstructionProcessor', () => {
testSubject = new FixInstructionProcessor();
});
- test('no background nor foreground on the message', () => {
+ test('updates aria-required-children "(see related nodes)" text to "(see Related paths)"', () => {
+ const fixInstruction = 'Element has children which are not allowed (see related nodes)';
+
+ const result = testSubject.process(fixInstruction, recommendColorMock.object);
+
+ expect(result).toMatchSnapshot();
+ });
+
+ test('no-ops if neither background nor foreground on the message', () => {
const fixInstruction =
'there is nothing that will trigger the processor to change the fix instruction here';
const result = testSubject.process(fixInstruction, recommendColorMock.object);
- expect(result).toMatchSnapshot();
+ expect(result).toEqual(<>{fixInstruction}>);
});
test('foreground color on the message', () => {
diff --git a/src/tests/unit/tests/reports/package/__snapshots__/combined-results-to-cards-model-converter.test.ts.snap b/src/tests/unit/tests/reports/package/__snapshots__/combined-results-to-cards-model-converter.test.ts.snap
index 585e9ecf57..21dbda3fb4 100644
--- a/src/tests/unit/tests/reports/package/__snapshots__/combined-results-to-cards-model-converter.test.ts.snap
+++ b/src/tests/unit/tests/reports/package/__snapshots__/combined-results-to-cards-model-converter.test.ts.snap
@@ -13,6 +13,9 @@ exports[`CombinedResultsToCardsModelConverter with a mixture of url specificatio
"nodes": [
{
"descriptors": {
+ "relatedCssSelectors": [
+ "extractRelatedSelectors output for .failed-rule-1-selector-1 snippet 1
",
+ ],
"snippet": "snippet 1
",
},
"highlightStatus": "unavailable",
@@ -89,6 +92,9 @@ exports[`CombinedResultsToCardsModelConverter with baseline-aware issues 1`] = `
"nodes": [
{
"descriptors": {
+ "relatedCssSelectors": [
+ "extractRelatedSelectors output for .failed-rule-1-selector-1 snippet 1
",
+ ],
"snippet": "snippet 1
",
},
"highlightStatus": "unavailable",
@@ -116,6 +122,9 @@ exports[`CombinedResultsToCardsModelConverter with baseline-aware issues 1`] = `
},
{
"descriptors": {
+ "relatedCssSelectors": [
+ "extractRelatedSelectors output for .failed-rule-1-selector-2 snippet 2
",
+ ],
"snippet": "snippet 2
",
},
"highlightStatus": "unavailable",
@@ -160,6 +169,9 @@ exports[`CombinedResultsToCardsModelConverter with baseline-aware issues 1`] = `
"nodes": [
{
"descriptors": {
+ "relatedCssSelectors": [
+ "extractRelatedSelectors output for .failed-rule-2-selector-1 snippet 1
",
+ ],
"snippet": "snippet 1
",
},
"highlightStatus": "unavailable",
@@ -187,6 +199,9 @@ exports[`CombinedResultsToCardsModelConverter with baseline-aware issues 1`] = `
},
{
"descriptors": {
+ "relatedCssSelectors": [
+ "extractRelatedSelectors output for .failed-rule-2-selector-2 snippet 2
",
+ ],
"snippet": "snippet 2
",
},
"highlightStatus": "unavailable",
@@ -263,6 +278,9 @@ exports[`CombinedResultsToCardsModelConverter with issues 1`] = `
"nodes": [
{
"descriptors": {
+ "relatedCssSelectors": [
+ "extractRelatedSelectors output for .failed-rule-1-selector-1 snippet 1
",
+ ],
"snippet": "snippet 1
",
},
"highlightStatus": "unavailable",
@@ -290,6 +308,9 @@ exports[`CombinedResultsToCardsModelConverter with issues 1`] = `
},
{
"descriptors": {
+ "relatedCssSelectors": [
+ "extractRelatedSelectors output for .failed-rule-1-selector-2 snippet 2
",
+ ],
"snippet": "snippet 2
",
},
"highlightStatus": "unavailable",
@@ -334,6 +355,9 @@ exports[`CombinedResultsToCardsModelConverter with issues 1`] = `
"nodes": [
{
"descriptors": {
+ "relatedCssSelectors": [
+ "extractRelatedSelectors output for .failed-rule-2-selector-1 snippet 1
",
+ ],
"snippet": "snippet 1
",
},
"highlightStatus": "unavailable",
@@ -361,6 +385,9 @@ exports[`CombinedResultsToCardsModelConverter with issues 1`] = `
},
{
"descriptors": {
+ "relatedCssSelectors": [
+ "extractRelatedSelectors output for .failed-rule-2-selector-2 snippet 2
",
+ ],
"snippet": "snippet 2
",
},
"highlightStatus": "unavailable",
@@ -484,6 +511,9 @@ exports[`CombinedResultsToCardsModelConverter without passed or inapplicable rul
"nodes": [
{
"descriptors": {
+ "relatedCssSelectors": [
+ "extractRelatedSelectors output for .failed-rule-1-selector-1 snippet 1
",
+ ],
"snippet": "snippet 1
",
},
"highlightStatus": "unavailable",
@@ -511,6 +541,9 @@ exports[`CombinedResultsToCardsModelConverter without passed or inapplicable rul
},
{
"descriptors": {
+ "relatedCssSelectors": [
+ "extractRelatedSelectors output for .failed-rule-1-selector-2 snippet 2
",
+ ],
"snippet": "snippet 2
",
},
"highlightStatus": "unavailable",
diff --git a/src/tests/unit/tests/reports/package/combined-results-to-cards-model-converter.test.ts b/src/tests/unit/tests/reports/package/combined-results-to-cards-model-converter.test.ts
index e76439a34d..7fff71e048 100644
--- a/src/tests/unit/tests/reports/package/combined-results-to-cards-model-converter.test.ts
+++ b/src/tests/unit/tests/reports/package/combined-results-to-cards-model-converter.test.ts
@@ -24,6 +24,9 @@ describe(CombinedResultsToCardsModelConverter, () => {
}
} as HelpUrlGetter;
let resolutionCreatorMock: IMock;
+ const extractRelatedSelectorsStub = (nodeResult: AxeNodeResult) => {
+ return [`extractRelatedSelectors output for ${nodeResult.target} ${nodeResult.html}`];
+ }
let testSubject: CombinedResultsToCardsModelConverter;
@@ -42,6 +45,7 @@ describe(CombinedResultsToCardsModelConverter, () => {
uuidGeneratorMock.object,
helpUrlGetterStub,
resolutionCreatorMock.object,
+ extractRelatedSelectorsStub,
);
})
---|