Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(axe-core-4.6): Expose axe-core relatedNodes as "Related paths" card row #6388

Merged
merged 11 commits into from
Feb 1, 2023

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -2399,6 +2405,16 @@
class="row-content--07KUh snippet--hSNfv"
>&lt;div&gt;snippet&lt;/div&gt;</td
></tr
><tr class="row---3i35"
><th class="row-label--FlVZl"
>Related paths</th
><td class="row-content--07KUh"
><ul class="path-list--MxxKM"
><li
>#menu &gt; li:nth-child(1)</li
></ul
></td
></tr
><tr class="row---3i35"
><th class="row-label--FlVZl"
>How to fix</th
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -2387,6 +2393,16 @@
class="row-content--07KUh snippet--hSNfv"
>&lt;div&gt;snippet&lt;/div&gt;</td
></tr
><tr class="row---3i35"
><th class="row-label--FlVZl"
>Related paths</th
><td class="row-content--07KUh"
><ul class="path-list--MxxKM"
><li
>#menu &gt; li:nth-child(1)</li
></ul
></td
></tr
><tr class="row---3i35"
><th class="row-label--FlVZl"
>How to fix</th
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions src/common/components/cards/related-paths-card-row.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
.path-list {
padding-left: 16px;
list-style-type: disc;
}
35 changes: 35 additions & 0 deletions src/common/components/cards/related-paths-card-row.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { SimpleCardRow } from 'common/components/cards/simple-card-row';
import { CardRowProps } from 'common/configs/unified-result-property-configurations';
import { NamedFC } from 'common/react/named-fc';
import { isEmpty } from 'lodash';
import * as React from 'react';
import styles from './related-paths-card-row.scss';

export interface RelatedPathsCardRowProps extends CardRowProps {
propertyData: string[];
}

export const RelatedPathsCardRow = NamedFC<RelatedPathsCardRowProps>(
'RelatedPathsCardRow',
({ index, propertyData }) => {
if (isEmpty(propertyData)) {
return null;
}

return (
<SimpleCardRow
label={`Related paths`}
content={
<ul className={styles.pathList}>
{propertyData.map(selector => (
<li key={selector}>{selector}</li>
))}
</ul>
}
rowKey={`related-paths-row-${index}`}
/>
);
},
);
35 changes: 35 additions & 0 deletions src/common/components/fix-instruction-processor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <em>Related paths</em>)
</>
);
}

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) {
Expand Down
20 changes: 10 additions & 10 deletions src/common/configs/unified-result-property-configurations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -13,23 +14,17 @@ 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',
'snippet',
'className',
'contentDescription',
'text',
];
'relatedCssSelectors',
] as const;
export type PropertyType = (typeof AllPropertyTypes)[number];

export interface CardRowDeps {
fixInstructionProcessor: FixInstructionProcessor;
Expand Down Expand Up @@ -59,6 +54,10 @@ export const cssSelectorConfiguration: PropertyConfiguration = {
cardRow: PathCardRow,
};

export const relatedCssSelectorsConfiguration: PropertyConfiguration = {
cardRow: RelatedPathsCardRow,
};

export const snippetConfiguration: PropertyConfiguration = {
cardRow: SnippetCardRow,
};
Expand Down Expand Up @@ -91,6 +90,7 @@ const propertyIdToConfigurationMap: PropertyIdToConfigurationMap = {
contentDescription: contentDescriptionConfiguration,
text: textConfiguration,
urls: urlsConfiguration,
relatedCssSelectors: relatedCssSelectorsConfiguration,
};

export function getPropertyConfiguration(id: string): Readonly<PropertyConfiguration> {
Expand Down
1 change: 1 addition & 0 deletions src/common/types/store-data/unified-data-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export type UnifiedIdentifiers = {
export type UnifiedDescriptors = {
snippet?: string;
boundingRectangle?: BoundingRectangle;
relatedCssSelectors?: string[];
} & InstancePropertyBag;

export type UnifiedRichResolution = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
32 changes: 32 additions & 0 deletions src/injected/adapters/extract-related-selectors.ts
Original file line number Diff line number Diff line change
@@ -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;
}
12 changes: 8 additions & 4 deletions src/injected/adapters/scan-results-to-unified-results.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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 = (
Expand Down Expand Up @@ -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,
Expand All @@ -124,6 +127,7 @@ export class ConvertScanResultsToUnifiedResults {
},
descriptors: {
snippet: nodeResult.snippet || nodeResult.html,
relatedCssSelectors: this.extractRelatedSelectors(nodeResult),
},
resolution: {
howToFixSummary: nodeResult.failureSummary!,
Expand Down
2 changes: 2 additions & 0 deletions src/injected/main-window-initializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -293,6 +294,7 @@ export class MainWindowInitializer extends WindowInitializer {
generateUID,
getFixResolution,
getCheckResolution,
extractRelatedSelectors,
);

const notificationTextCreator = new NotificationTextCreator(scanIncompleteWarningDetector);
Expand Down
8 changes: 7 additions & 1 deletion src/reports/package/accessibilityInsightsReport.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -86,7 +92,7 @@ declare namespace AccessibilityInsightsReport {
elementSelector: string,
snippet: string,
fix: HowToFixData,
rule: AxeRuleData
rule: AxeRuleData,
};

export interface FailuresGroup {
Expand Down
Loading