diff --git a/src/DetailsView/details-view-initializer.ts b/src/DetailsView/details-view-initializer.ts index e8942574713..c4f91bf03d7 100644 --- a/src/DetailsView/details-view-initializer.ts +++ b/src/DetailsView/details-view-initializer.ts @@ -112,6 +112,10 @@ import { allCardInteractionsSupported } from '../common/components/cards/card-in import { CardsCollapsibleControl } from '../common/components/cards/collapsible-component-cards'; import { FixInstructionProcessor } from '../common/components/fix-instruction-processor'; import { NewTabLink } from '../common/components/new-tab-link'; +import { + getNeedsReviewRuleResourcesUrl, + isOutcomeNeedsReview, +} from '../common/configs/needs-review-rule-resources'; import { getPropertyConfiguration } from '../common/configs/unified-result-property-configurations'; import { DateProvider } from '../common/date-provider'; import { DocumentManipulator } from '../common/document-manipulator'; @@ -739,6 +743,8 @@ if (tabId != null) { defaultRulesMap: getDefaultRulesMap(), convertAssessmentStoreDataToScanNodeResults, convertUnifiedStoreDataToScanNodeResults, + GetNeedsReviewRuleResourcesUrl: getNeedsReviewRuleResourcesUrl, + IsOutcomeNeedsReview: isOutcomeNeedsReview, }; const renderer = new DetailsViewRenderer( diff --git a/src/common/components/cards/rule-content.tsx b/src/common/components/cards/rule-content.tsx index 178be1ec068..57f32b6c3d7 100644 --- a/src/common/components/cards/rule-content.tsx +++ b/src/common/components/cards/rule-content.tsx @@ -6,6 +6,11 @@ import { NarrowModeStatus } from 'DetailsView/components/narrow-mode-detector'; import * as React from 'react'; import { TargetAppData } from '../../../common/types/store-data/unified-data-interface'; +import { InstanceOutcomeType } from '../../../reports/components/instance-outcome-type'; +import { + getNeedsReviewRuleResourcesUrl, + isOutcomeNeedsReview, +} from '../../configs/needs-review-rule-resources'; import { CardRuleResult } from '../../types/store-data/card-view-model'; import { UserConfigurationStoreData } from '../../types/store-data/user-configuration-store'; import { InstanceDetailsGroup, InstanceDetailsGroupDeps } from './instance-details-group'; @@ -20,9 +25,12 @@ export type RuleContentProps = { targetAppInfo: TargetAppData; cardSelectionMessageCreator?: CardSelectionMessageCreator; narrowModeStatus?: NarrowModeStatus; + outcomeType: InstanceOutcomeType; }; export const RuleContent = NamedFC('RuleContent', props => { + props.deps.GetNeedsReviewRuleResourcesUrl = getNeedsReviewRuleResourcesUrl; + props.deps.IsOutcomeNeedsReview = isOutcomeNeedsReview; return ( <> diff --git a/src/common/components/cards/rule-resources.tsx b/src/common/components/cards/rule-resources.tsx index b9e47fa688f..fe585bddf2b 100644 --- a/src/common/components/cards/rule-resources.tsx +++ b/src/common/components/cards/rule-resources.tsx @@ -8,55 +8,69 @@ import { UnifiedRule } from 'common/types/store-data/unified-data-interface'; import { isEmpty } from 'lodash'; import * as React from 'react'; +import { InstanceOutcomeType } from '../../../reports/components/instance-outcome-type'; +import { + getNeedsReviewRuleResourcesUrl, + isOutcomeNeedsReview, +} from '../../configs/needs-review-rule-resources'; import styles from './rule-resources.scss'; export type RuleResourcesDeps = GuidanceTagsDeps & { LinkComponent: LinkComponentType; + IsOutcomeNeedsReview: typeof isOutcomeNeedsReview; + GetNeedsReviewRuleResourcesUrl: typeof getNeedsReviewRuleResourcesUrl; }; export type RuleResourcesProps = { deps: RuleResourcesDeps; rule: UnifiedRule; + outcomeType: InstanceOutcomeType; }; -export const RuleResources = NamedFC('RuleResources', ({ deps, rule }) => { - if (rule.url == null && isEmpty(rule.guidance)) { - return null; - } - - const renderTitle = () => ( -
Resources for this rule
- ); - - const renderRuleLink = () => { - if (rule.url == null) { +export const RuleResources = NamedFC( + 'RuleResources', + ({ deps, rule, outcomeType }) => { + if (rule.url == null && isEmpty(rule.guidance)) { return null; } - const ruleId = rule.id; - const ruleUrl = rule.url; + const renderTitle = () => ( +
Resources for this rule
+ ); + + const renderRuleLink = () => { + if (rule.url == null) { + return null; + } + + const ruleId = rule.id; + const ruleUrl = deps.IsOutcomeNeedsReview(outcomeType) + ? deps.GetNeedsReviewRuleResourcesUrl(ruleId) + : rule.url; + + return ( + + + More information about {ruleId} + + + ); + }; + + const renderGuidanceLinks = () => { + return ; + }; + const renderGuidanceTags = () => ; + return ( - - - More information about {ruleId} - - +
+ {renderTitle()} + {renderRuleLink()} + + {renderGuidanceLinks()} + {renderGuidanceTags()} + +
); - }; - - const renderGuidanceLinks = () => { - return ; - }; - const renderGuidanceTags = () => ; - - return ( -
- {renderTitle()} - {renderRuleLink()} - - {renderGuidanceLinks()} - {renderGuidanceTags()} - -
- ); -}); + }, +); diff --git a/src/common/components/cards/rules-with-instances.tsx b/src/common/components/cards/rules-with-instances.tsx index 87cb3fb6f6b..20d70033233 100644 --- a/src/common/components/cards/rules-with-instances.tsx +++ b/src/common/components/cards/rules-with-instances.tsx @@ -73,6 +73,7 @@ export const RulesWithInstances = NamedFC( key={`${rule.id}-rule-group`} deps={deps} rule={rule} + outcomeType={outcomeType} userConfigurationStoreData={userConfigurationStoreData} targetAppInfo={targetAppInfo} cardSelectionMessageCreator={cardSelectionMessageCreator} diff --git a/src/common/configs/needs-review-rule-resources.ts b/src/common/configs/needs-review-rule-resources.ts new file mode 100644 index 00000000000..65602418a46 --- /dev/null +++ b/src/common/configs/needs-review-rule-resources.ts @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const needsReviewRuleResourcesPath = + 'https://accessibilityinsights.io/info-examples/web/needs-review'; + +export const isOutcomeNeedsReview = (outcomeType: string): boolean => { + return outcomeType === 'review'; +}; + +export const getNeedsReviewRuleResourcesUrl = (ruleId: string): string => { + return `${needsReviewRuleResourcesPath}/${ruleId}`; +}; diff --git a/src/tests/unit/tests/common/components/cards/__snapshots__/rule-content.test.tsx.snap b/src/tests/unit/tests/common/components/cards/__snapshots__/rule-content.test.tsx.snap index 169ba177275..434310d4554 100644 --- a/src/tests/unit/tests/common/components/cards/__snapshots__/rule-content.test.tsx.snap +++ b/src/tests/unit/tests/common/components/cards/__snapshots__/rule-content.test.tsx.snap @@ -3,9 +3,11 @@ exports[`RuleContent renders 1`] = ` @@ -13,6 +15,22 @@ exports[`RuleContent renders 1`] = ` exports[`RuleContent renders: InstanceDetailsGroup props 1`] = ` { + "deps": { + "GetNeedsReviewRuleResourcesUrl": [MockFunction], + "IsOutcomeNeedsReview": [MockFunction], + }, + "rule": { + "id": "test-id", + }, +} +`; + +exports[`RuleContent renders: RuleResources props 1`] = ` +{ + "deps": { + "GetNeedsReviewRuleResourcesUrl": [MockFunction], + "IsOutcomeNeedsReview": [MockFunction], + }, "rule": { "id": "test-id", }, diff --git a/src/tests/unit/tests/common/components/cards/__snapshots__/rule-resources.test.tsx.snap b/src/tests/unit/tests/common/components/cards/__snapshots__/rule-resources.test.tsx.snap index 4bfedbb0739..9f25888a932 100644 --- a/src/tests/unit/tests/common/components/cards/__snapshots__/rule-resources.test.tsx.snap +++ b/src/tests/unit/tests/common/components/cards/__snapshots__/rule-resources.test.tsx.snap @@ -3,7 +3,8 @@ exports[`RuleResources renders with { url: 'test-url', guidanceLinks: [ [length]: 0 ], - linkComponent: 'ExternalLink' + linkComponent: 'ExternalLink', + outcomeType: 'pass' } 1`] = `
`; - -exports[`RuleResources renders with { - url: null, - guidanceLinks: [ [length]: 0 ], - linkComponent: 'ExternalLink' -}: GuidanceLinks props 1`] = `undefined`; - -exports[`RuleResources renders with { - url: null, - guidanceLinks: [ [length]: 0 ], - linkComponent: 'ExternalLink' -}: GuidanceTags props 1`] = `undefined`; - -exports[`RuleResources renders with { - url: null, - guidanceLinks: [ { href: 'test-href' }, [length]: 1 ], - linkComponent: 'NewTabLink' + url: 'test-url', + guidanceLinks: null, + linkComponent: 'NewTabLink', + outcomeType: 'pass' } 1`] = `
Resources for this rule
+ + + More information about some-rule + + - +
@@ -276,38 +301,51 @@ exports[`RuleResources renders with { `; exports[`RuleResources renders with { - url: null, - guidanceLinks: [ { href: 'test-href' }, [length]: 1 ], - linkComponent: 'NewTabLink' + url: 'test-url', + guidanceLinks: null, + linkComponent: 'NewTabLink', + outcomeType: 'pass' }: GuidanceLinks props 1`] = ` { "LinkComponent": [Function], - "links": [ - { - "href": "test-href", - }, - ], + "links": null, } `; exports[`RuleResources renders with { - url: null, - guidanceLinks: [ { href: 'test-href' }, [length]: 1 ], - linkComponent: 'NewTabLink' + url: 'test-url', + guidanceLinks: null, + linkComponent: 'NewTabLink', + outcomeType: 'pass' }: GuidanceTags props 1`] = ` { "deps": { + "GetNeedsReviewRuleResourcesUrl": [MockFunction], + "IsOutcomeNeedsReview": [MockFunction] { + "calls": [ + [ + "pass", + ], + ], + "results": [ + { + "type": "return", + "value": undefined, + }, + ], + }, "LinkComponent": [Function], }, - "links": [ - { - "href": "test-href", - }, - ], + "links": null, } `; -exports[`RuleResources renders with { url: 'test-url', guidanceLinks: null, linkComponent: 'NewTabLink' } 1`] = ` +exports[`RuleResources renders with { + url: 'test-url', + guidanceLinks: null, + linkComponent: 'NewTabLink', + outcomeType: 'review' +} 1`] = `
`; -exports[`RuleResources renders with { url: 'test-url', guidanceLinks: null, linkComponent: 'NewTabLink' }: GuidanceLinks props 1`] = ` +exports[`RuleResources renders with { + url: 'test-url', + guidanceLinks: null, + linkComponent: 'NewTabLink', + outcomeType: 'review' +}: GuidanceLinks props 1`] = ` { "LinkComponent": [Function], "links": null, } `; -exports[`RuleResources renders with { url: 'test-url', guidanceLinks: null, linkComponent: 'NewTabLink' }: GuidanceTags props 1`] = ` +exports[`RuleResources renders with { + url: 'test-url', + guidanceLinks: null, + linkComponent: 'NewTabLink', + outcomeType: 'review' +}: GuidanceTags props 1`] = ` { "deps": { + "GetNeedsReviewRuleResourcesUrl": [MockFunction], + "IsOutcomeNeedsReview": [MockFunction] { + "calls": [ + [ + "review", + ], + ], + "results": [ + { + "type": "return", + "value": undefined, + }, + ], + }, "LinkComponent": [Function], }, "links": null, } `; -exports[`RuleResources renders with { url: null, guidanceLinks: null, linkComponent: 'NewTabLink' } 1`] = ``; +exports[`RuleResources renders with { + url: null, + guidanceLinks: [ [length]: 0 ], + linkComponent: 'ExternalLink', + outcomeType: 'pass' +} 1`] = ``; + +exports[`RuleResources renders with { + url: null, + guidanceLinks: [ [length]: 0 ], + linkComponent: 'ExternalLink', + outcomeType: 'pass' +}: GuidanceLinks props 1`] = `undefined`; + +exports[`RuleResources renders with { + url: null, + guidanceLinks: [ [length]: 0 ], + linkComponent: 'ExternalLink', + outcomeType: 'pass' +}: GuidanceTags props 1`] = `undefined`; + +exports[`RuleResources renders with { + url: null, + guidanceLinks: [ [length]: 0 ], + linkComponent: 'ExternalLink', + outcomeType: 'review' +} 1`] = ``; + +exports[`RuleResources renders with { + url: null, + guidanceLinks: [ [length]: 0 ], + linkComponent: 'ExternalLink', + outcomeType: 'review' +}: GuidanceLinks props 1`] = `undefined`; -exports[`RuleResources renders with { url: null, guidanceLinks: null, linkComponent: 'NewTabLink' }: GuidanceLinks props 1`] = `undefined`; +exports[`RuleResources renders with { + url: null, + guidanceLinks: [ [length]: 0 ], + linkComponent: 'ExternalLink', + outcomeType: 'review' +}: GuidanceTags props 1`] = `undefined`; -exports[`RuleResources renders with { url: null, guidanceLinks: null, linkComponent: 'NewTabLink' }: GuidanceTags props 1`] = `undefined`; +exports[`RuleResources renders with { + url: null, + guidanceLinks: [ { href: 'test-href' }, [length]: 1 ], + linkComponent: 'NewTabLink', + outcomeType: 'pass' +} 1`] = ` + +
+
+ Resources for this rule +
+ + + + +
+
+`; + +exports[`RuleResources renders with { + url: null, + guidanceLinks: [ { href: 'test-href' }, [length]: 1 ], + linkComponent: 'NewTabLink', + outcomeType: 'pass' +}: GuidanceLinks props 1`] = ` +{ + "LinkComponent": [Function], + "links": [ + { + "href": "test-href", + }, + ], +} +`; + +exports[`RuleResources renders with { + url: null, + guidanceLinks: [ { href: 'test-href' }, [length]: 1 ], + linkComponent: 'NewTabLink', + outcomeType: 'pass' +}: GuidanceTags props 1`] = ` +{ + "deps": { + "GetNeedsReviewRuleResourcesUrl": [MockFunction], + "IsOutcomeNeedsReview": [MockFunction], + "LinkComponent": [Function], + }, + "links": [ + { + "href": "test-href", + }, + ], +} +`; + +exports[`RuleResources renders with { + url: null, + guidanceLinks: null, + linkComponent: 'NewTabLink', + outcomeType: 'pass' +} 1`] = ``; + +exports[`RuleResources renders with { + url: null, + guidanceLinks: null, + linkComponent: 'NewTabLink', + outcomeType: 'pass' +}: GuidanceLinks props 1`] = `undefined`; + +exports[`RuleResources renders with { + url: null, + guidanceLinks: null, + linkComponent: 'NewTabLink', + outcomeType: 'pass' +}: GuidanceTags props 1`] = `undefined`; diff --git a/src/tests/unit/tests/common/components/cards/__snapshots__/rules-with-instances.test.tsx.snap b/src/tests/unit/tests/common/components/cards/__snapshots__/rules-with-instances.test.tsx.snap index e0c9874685e..2d4a05efdd2 100644 --- a/src/tests/unit/tests/common/components/cards/__snapshots__/rules-with-instances.test.tsx.snap +++ b/src/tests/unit/tests/common/components/cards/__snapshots__/rules-with-instances.test.tsx.snap @@ -46,6 +46,7 @@ exports[`RulesWithInstances renders: CardsCollapsibleControl props 1`] = ` { mockReactComponents([InstanceDetailsGroup, RuleResources]); @@ -19,11 +24,15 @@ describe('RuleContent', () => { rule: { id: 'test-id', }, + deps: { + GetNeedsReviewRuleResourcesUrl: getNeedsReviewRuleResourcesUrl, + IsOutcomeNeedsReview: isOutcomeNeedsReview, + }, } as RuleContentProps; const renderResult = render(); expect(renderResult.asFragment()).toMatchSnapshot(); - expectMockedComponentPropsToMatchSnapshots([InstanceDetailsGroup]); + expectMockedComponentPropsToMatchSnapshots([InstanceDetailsGroup, RuleResources]); }); }); diff --git a/src/tests/unit/tests/common/components/cards/rule-resources.test.tsx b/src/tests/unit/tests/common/components/cards/rule-resources.test.tsx index eb7af95f823..dee6c00ed8c 100644 --- a/src/tests/unit/tests/common/components/cards/rule-resources.test.tsx +++ b/src/tests/unit/tests/common/components/cards/rule-resources.test.tsx @@ -17,12 +17,17 @@ import { expectMockedComponentPropsToMatchSnapshots, mockReactComponents, } from 'tests/unit/mock-helpers/mock-module-helpers'; - +import { + getNeedsReviewRuleResourcesUrl, + isOutcomeNeedsReview, +} from '../../../../../../common/configs/needs-review-rule-resources'; +import { InstanceOutcomeType } from '../../../../../../reports/components/instance-outcome-type'; import { exampleUnifiedRuleResult } from './sample-view-model-data'; jest.mock('common/components/guidance-tags'); jest.mock('common/components/guidance-links'); jest.mock('common/components/external-link'); +jest.mock('common/configs/needs-review-rule-resources'); describe('RuleResources', () => { mockReactComponents([GuidanceTags, GuidanceLinks, ExternalLink]); @@ -36,6 +41,7 @@ describe('RuleResources', () => { url: string; guidanceLinks: GuidanceLink[]; linkComponent: keyof typeof linkComponents; + outcomeType: InstanceOutcomeType; }; const testCases: TestCases[] = [ @@ -43,16 +49,35 @@ describe('RuleResources', () => { url: 'test-url', guidanceLinks: [{ href: 'test-href' } as GuidanceLink], linkComponent: 'ExternalLink', + outcomeType: 'pass', }, { url: null, guidanceLinks: [{ href: 'test-href' } as GuidanceLink], linkComponent: 'NewTabLink', + outcomeType: 'pass', + }, + { + url: 'test-url', + guidanceLinks: [], + linkComponent: 'ExternalLink', + outcomeType: 'pass', + }, + { + url: 'test-url', + guidanceLinks: null, + linkComponent: 'NewTabLink', + outcomeType: 'pass', + }, + { url: null, guidanceLinks: [], linkComponent: 'ExternalLink', outcomeType: 'pass' }, + { url: null, guidanceLinks: null, linkComponent: 'NewTabLink', outcomeType: 'pass' }, + { + url: 'test-url', + guidanceLinks: null, + linkComponent: 'NewTabLink', + outcomeType: 'review', }, - { url: 'test-url', guidanceLinks: [], linkComponent: 'ExternalLink' }, - { url: 'test-url', guidanceLinks: null, linkComponent: 'NewTabLink' }, - { url: null, guidanceLinks: [], linkComponent: 'ExternalLink' }, - { url: null, guidanceLinks: null, linkComponent: 'NewTabLink' }, + { url: null, guidanceLinks: [], linkComponent: 'ExternalLink', outcomeType: 'review' }, ]; it.each(testCases)('with %o', testCase => { @@ -64,7 +89,10 @@ describe('RuleResources', () => { rule, deps: { LinkComponent: linkComponents[testCase.linkComponent], + IsOutcomeNeedsReview: isOutcomeNeedsReview, + GetNeedsReviewRuleResourcesUrl: getNeedsReviewRuleResourcesUrl, } as RuleResourcesDeps, + outcomeType: testCase.outcomeType, }; const renderResult = render(); diff --git a/src/tests/unit/tests/common/configs/needs-review-rule-resources.test.ts b/src/tests/unit/tests/common/configs/needs-review-rule-resources.test.ts new file mode 100644 index 00000000000..0934a7fde80 --- /dev/null +++ b/src/tests/unit/tests/common/configs/needs-review-rule-resources.test.ts @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { + getNeedsReviewRuleResourcesUrl, + isOutcomeNeedsReview, +} from 'common/configs/needs-review-rule-resources'; + +describe(getNeedsReviewRuleResourcesUrl, () => { + const ruleId = 'rule-id'; + const needsReviewRuleResourcesPath = + 'https://accessibilityinsights.io/info-examples/web/needs-review'; + + it('for ruleId passed to get NeedsReviewRuleResourcesUrl', () => { + const expectedUrl = `${needsReviewRuleResourcesPath}/${ruleId}`; + expect(getNeedsReviewRuleResourcesUrl(ruleId)).toBe(expectedUrl); + }); +}); + +describe(isOutcomeNeedsReview, () => { + it('for outcome that is review', () => { + expect(isOutcomeNeedsReview('review')).toBe(true); + }); + + it('for outcome that is not review', () => { + expect(isOutcomeNeedsReview('issue')).toBe(false); + }); +});