Bug 1564968 - create Check component for a generic check result in the checks section of the accessibility panel sidebar. r=gl

Differential Revision: https://phabricator.services.mozilla.com/D43444

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Yura Zenevich 2019-08-27 23:34:29 +00:00
Родитель 14f3ce0a9b
Коммит 195b0a1aaf
9 изменённых файлов: 293 добавлений и 151 удалений

Просмотреть файл

@ -708,6 +708,10 @@ body {
}
/* Checks */
.checks .list li:last-of-type {
padding-block-end: 4px;
}
.accessibility-check code {
background-color: var(--accessibility-code-background);
border-radius: 2px;
@ -715,7 +719,7 @@ body {
padding: 0 4px;
}
.accessibility-text-label-check .icon {
.accessibility-check .icon {
display: inline;
-moz-context-properties: fill;
vertical-align: top;
@ -723,11 +727,11 @@ body {
margin-inline-end: 4px;
}
.accessibility-text-label-check .icon.FAIL {
.accessibility-check .icon.FAIL {
fill: var(--theme-icon-error-color);
}
.accessibility-text-label-check .icon.WARNING {
.accessibility-check .icon.WARNING {
fill: var(--theme-icon-warning-color);
}

Просмотреть файл

@ -0,0 +1,155 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// React
const {
Component,
createFactory,
PureComponent,
} = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const ReactDOM = require("devtools/client/shared/vendor/react-dom-factories");
const FluentReact = require("devtools/client/shared/vendor/fluent-react");
const Localized = createFactory(FluentReact.Localized);
const { openDocLink } = require("devtools/client/shared/link");
const {
accessibility: {
SCORES: { BEST_PRACTICES, FAIL, WARNING },
},
} = require("devtools/shared/constants");
/**
* A map of accessibility scores to the text descriptions of check icons.
*/
const SCORE_TO_ICON_MAP = {
[BEST_PRACTICES]: {
l10nId: "accessibility-best-practices",
src: "chrome://devtools/skin/images/info.svg",
},
[FAIL]: {
l10nId: "accessibility-fail",
src: "chrome://devtools/skin/images/error.svg",
},
[WARNING]: {
l10nId: "accessibility-warning",
src: "chrome://devtools/skin/images/alert.svg",
},
};
/**
* Localized "Learn more" link that opens a new tab with relevant documentation.
*/
class LearnMoreClass extends PureComponent {
static get propTypes() {
return {
href: PropTypes.string,
l10nId: PropTypes.string.isRequired,
onClick: PropTypes.func,
};
}
static get defaultProps() {
return {
href: "#",
l10nId: null,
onClick: LearnMoreClass.openDocOnClick,
};
}
static openDocOnClick(event) {
event.preventDefault();
openDocLink(event.target.href);
}
render() {
const { href, l10nId, onClick } = this.props;
const className = "link";
return Localized({ id: l10nId }, ReactDOM.a({ className, href, onClick }));
}
}
const LearnMore = createFactory(LearnMoreClass);
/**
* Renders icon with text description for the accessibility check.
*
* @param {Object}
* Options:
* - score: value from SCORES from "devtools/shared/constants"
*/
function Icon({ score }) {
const { l10nId, src } = SCORE_TO_ICON_MAP[score];
return Localized(
{ id: l10nId, attrs: { alt: true } },
ReactDOM.img({ src, className: `icon ${score}` })
);
}
/**
* Renders text description of the accessibility check.
*
* @param {Object}
* Options:
* - args: arguments for fluent localized string
* - href: url for the learn more link pointing to MDN
* - l10nId: fluent localization id
*/
function Annotation({ args, href, l10nId }) {
return Localized(
{
id: l10nId,
a: LearnMore({ l10nId: "accessibility-learn-more", href }),
...args,
},
ReactDOM.p({ className: "accessibility-check-annotation" })
);
}
/**
* Component for rendering a check for accessibliity checks section,
* warnings and best practices suggestions association with a given
* accessibility object in the accessibility tree.
*/
class Check extends Component {
static get propTypes() {
return {
getAnnotation: PropTypes.func.isRequired,
id: PropTypes.string.isRequired,
issue: PropTypes.string.isRequired,
score: PropTypes.string.isRequired,
};
}
render() {
const { getAnnotation, id, issue, score } = this.props;
return ReactDOM.div(
{
role: "presentation",
className: "accessibility-check",
},
Localized(
{
id,
},
ReactDOM.h3({ className: "accessibility-check-header" })
),
ReactDOM.div(
{
role: "presentation",
},
Icon({ score }),
Annotation({ ...getAnnotation(issue) })
)
);
}
}
module.exports = Check;

Просмотреть файл

@ -5,19 +5,17 @@
// React
const {
Component,
createFactory,
PureComponent,
} = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const ReactDOM = require("devtools/client/shared/vendor/react-dom-factories");
const FluentReact = require("devtools/client/shared/vendor/fluent-react");
const Localized = createFactory(FluentReact.Localized);
const { openDocLink } = require("devtools/client/shared/link");
const Check = createFactory(
require("devtools/client/accessibility/components/Check")
);
const { A11Y_TEXT_LABEL_LINKS } = require("../constants");
const {
accessibility: {
AUDIT_TYPE: { TEXT_LABEL },
@ -43,7 +41,6 @@ const {
TOOLBAR_NO_NAME,
},
},
SCORES: { BEST_PRACTICES, FAIL, WARNING },
},
} = require("devtools/shared/constants");
@ -201,102 +198,12 @@ const ISSUE_TO_ANNOTATION_MAP = {
},
};
/**
* A map of accessibility scores to the text descriptions of check icons.
*/
const SCORE_TO_ICON_MAP = {
[BEST_PRACTICES]: {
l10nId: "accessibility-best-practices",
src: "chrome://devtools/skin/images/info.svg",
},
[FAIL]: {
l10nId: "accessibility-fail",
src: "chrome://devtools/skin/images/error.svg",
},
[WARNING]: {
l10nId: "accessibility-warning",
src: "chrome://devtools/skin/images/alert.svg",
},
};
/**
* Localized "Learn more" link that opens a new tab with relevant documentation.
*/
class LearnMoreClass extends Component {
static get propTypes() {
return {
href: PropTypes.string,
l10nId: PropTypes.string.isRequired,
onClick: PropTypes.func,
};
}
static get defaultProps() {
return {
href: "#",
l10nId: null,
onClick: LearnMoreClass.openDocOnClick,
};
}
static openDocOnClick(event) {
event.preventDefault();
openDocLink(event.target.href);
}
render() {
const { href, l10nId, onClick } = this.props;
const className = "link";
return Localized({ id: l10nId }, ReactDOM.a({ className, href, onClick }));
}
}
const LearnMore = createFactory(LearnMoreClass);
/**
* Renders icon with text description for the text label accessibility check.
*
* @param {Object}
* Options:
* - score: value from SCORES from "devtools/shared/constants"
*/
function Icon({ score }) {
const { l10nId, src } = SCORE_TO_ICON_MAP[score];
return Localized(
{ id: l10nId, attrs: { alt: true } },
ReactDOM.img({ src, className: `icon ${score}` })
);
}
/**
* Renders text description of the text label accessibility check.
*
* @param {Object}
* Options:
* - issue: value from ISSUE_TYPE[AUDIT_TYPE.TEXT_LABEL] from
* "devtools/shared/constants"
*/
function Annotation({ issue }) {
const { args, href, l10nId } = ISSUE_TO_ANNOTATION_MAP[issue];
return Localized(
{
id: l10nId,
a: LearnMore({ l10nId: "accessibility-learn-more", href }),
...args,
},
ReactDOM.p({ className: "accessibility-check-annotation" })
);
}
/**
* Component for rendering a check for text label accessibliity check failures,
* warnings and best practices suggestions association with a given
* accessibility object in the accessibility tree.
*/
class TextLabelCheck extends Component {
class TextLabelCheck extends PureComponent {
static get propTypes() {
return {
issue: PropTypes.string.isRequired,
@ -305,28 +212,11 @@ class TextLabelCheck extends Component {
}
render() {
const { issue, score } = this.props;
return ReactDOM.div(
{
role: "presentation",
className: "accessibility-check",
},
Localized(
{
return Check({
...this.props,
getAnnotation: issue => ISSUE_TO_ANNOTATION_MAP[issue],
id: "accessibility-text-label-header",
},
ReactDOM.h3({ className: "accessibility-check-header" })
),
ReactDOM.div(
{
role: "presentation",
className: "accessibility-text-label-check",
},
Icon({ score }),
Annotation({ issue })
)
);
});
}
}

Просмотреть файл

@ -15,6 +15,7 @@ DevToolsModules(
'Badge.js',
'Badges.js',
'Button.js',
'Check.js',
'Checks.js',
'ColorContrastAccessibility.js',
'ContrastBadge.js',

Просмотреть файл

@ -0,0 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Check component: basic render 1`] = `"<div role=\\"presentation\\" class=\\"accessibility-check\\"><h3 class=\\"accessibility-check-header\\"></h3><div role=\\"presentation\\"><img src=\\"chrome://devtools/skin/images/error.svg\\" class=\\"icon FAIL\\"><p class=\\"accessibility-check-annotation\\"></p></div></div>"`;
exports[`Check component: basic render 2`] = `"<div role=\\"presentation\\" class=\\"accessibility-check\\"><h3 class=\\"accessibility-check-header\\"></h3><div role=\\"presentation\\"><img src=\\"chrome://devtools/skin/images/error.svg\\" class=\\"icon FAIL\\"><p class=\\"accessibility-check-annotation\\"></p></div></div>"`;

Просмотреть файл

@ -1,7 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TextLabelCheck component: BEST_PRACTICES render 1`] = `"<div role=\\"presentation\\" class=\\"accessibility-check\\"><h3 class=\\"accessibility-check-header\\"></h3><div role=\\"presentation\\" class=\\"accessibility-text-label-check\\"><img src=\\"chrome://devtools/skin/images/info.svg\\" class=\\"icon BEST_PRACTICES\\"><p class=\\"accessibility-check-annotation\\"></p></div></div>"`;
exports[`TextLabelCheck component: BEST_PRACTICES render 1`] = `"<div role=\\"presentation\\" class=\\"accessibility-check\\"><h3 class=\\"accessibility-check-header\\"></h3><div role=\\"presentation\\"><img src=\\"chrome://devtools/skin/images/info.svg\\" class=\\"icon BEST_PRACTICES\\"><p class=\\"accessibility-check-annotation\\"></p></div></div>"`;
exports[`TextLabelCheck component: FAIL render 1`] = `"<div role=\\"presentation\\" class=\\"accessibility-check\\"><h3 class=\\"accessibility-check-header\\"></h3><div role=\\"presentation\\" class=\\"accessibility-text-label-check\\"><img src=\\"chrome://devtools/skin/images/error.svg\\" class=\\"icon FAIL\\"><p class=\\"accessibility-check-annotation\\"></p></div></div>"`;
exports[`TextLabelCheck component: BEST_PRACTICES render 2`] = `"<div role=\\"presentation\\" class=\\"accessibility-check\\"><h3 class=\\"accessibility-check-header\\"></h3><div role=\\"presentation\\"><img src=\\"chrome://devtools/skin/images/info.svg\\" class=\\"icon BEST_PRACTICES\\"><p class=\\"accessibility-check-annotation\\"></p></div></div>"`;
exports[`TextLabelCheck component: WARNING render 1`] = `"<div role=\\"presentation\\" class=\\"accessibility-check\\"><h3 class=\\"accessibility-check-header\\"></h3><div role=\\"presentation\\" class=\\"accessibility-text-label-check\\"><img src=\\"chrome://devtools/skin/images/alert.svg\\" class=\\"icon WARNING\\"><p class=\\"accessibility-check-annotation\\"></p></div></div>"`;
exports[`TextLabelCheck component: FAIL render 1`] = `"<div role=\\"presentation\\" class=\\"accessibility-check\\"><h3 class=\\"accessibility-check-header\\"></h3><div role=\\"presentation\\"><img src=\\"chrome://devtools/skin/images/error.svg\\" class=\\"icon FAIL\\"><p class=\\"accessibility-check-annotation\\"></p></div></div>"`;
exports[`TextLabelCheck component: FAIL render 2`] = `"<div role=\\"presentation\\" class=\\"accessibility-check\\"><h3 class=\\"accessibility-check-header\\"></h3><div role=\\"presentation\\"><img src=\\"chrome://devtools/skin/images/error.svg\\" class=\\"icon FAIL\\"><p class=\\"accessibility-check-annotation\\"></p></div></div>"`;
exports[`TextLabelCheck component: WARNING render 1`] = `"<div role=\\"presentation\\" class=\\"accessibility-check\\"><h3 class=\\"accessibility-check-header\\"></h3><div role=\\"presentation\\"><img src=\\"chrome://devtools/skin/images/alert.svg\\" class=\\"icon WARNING\\"><p class=\\"accessibility-check-annotation\\"></p></div></div>"`;
exports[`TextLabelCheck component: WARNING render 2`] = `"<div role=\\"presentation\\" class=\\"accessibility-check\\"><h3 class=\\"accessibility-check-header\\"></h3><div role=\\"presentation\\"><img src=\\"chrome://devtools/skin/images/alert.svg\\" class=\\"icon WARNING\\"><p class=\\"accessibility-check-annotation\\"></p></div></div>"`;

Просмотреть файл

@ -0,0 +1,48 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { mount } = require("enzyme");
const { createFactory } = require("devtools/client/shared/vendor/react");
const CheckClass = require("devtools/client/accessibility/components/Check");
const Check = createFactory(CheckClass);
const FluentReact = require("devtools/client/shared/vendor/fluent-react");
const LocalizationProvider = createFactory(FluentReact.LocalizationProvider);
const {
accessibility: {
AUDIT_TYPE: { TEXT_LABEL },
ISSUE_TYPE: {
[TEXT_LABEL]: { AREA_NO_NAME_FROM_ALT },
},
SCORES: { FAIL },
},
} = require("devtools/shared/constants");
const {
testCheck,
} = require("devtools/client/accessibility/test/jest/helpers");
describe("Check component:", () => {
const props = {
id: "accessibility-text-label-header",
issue: AREA_NO_NAME_FROM_ALT,
score: FAIL,
getAnnotation: jest.fn(),
};
it("basic render", () => {
const wrapper = mount(LocalizationProvider({ bundles: [] }, Check(props)));
expect(wrapper.html()).toMatchSnapshot();
testCheck(wrapper.childAt(0), {
issue: AREA_NO_NAME_FROM_ALT,
score: FAIL,
});
expect(props.getAnnotation.mock.calls.length).toBe(1);
expect(props.getAnnotation.mock.calls[0]).toEqual([AREA_NO_NAME_FROM_ALT]);
});
});

Просмотреть файл

@ -12,6 +12,10 @@ const TextLabelCheck = createFactory(TextLabelCheckClass);
const FluentReact = require("devtools/client/shared/vendor/fluent-react");
const LocalizationProvider = createFactory(FluentReact.LocalizationProvider);
const {
testCustomCheck,
} = require("devtools/client/accessibility/test/jest/helpers");
const {
accessibility: {
AUDIT_TYPE: { TEXT_LABEL },
@ -26,30 +30,6 @@ const {
},
} = require("devtools/shared/constants");
function testTextLabelCheck(wrapper, props) {
expect(wrapper.html()).toMatchSnapshot();
expect(wrapper.children().length).toBe(1);
const container = wrapper.childAt(0);
expect(container.hasClass("accessibility-check")).toBe(true);
expect(container.prop("role")).toBe("presentation");
expect(wrapper.props()).toMatchObject(props);
const localized = wrapper.find(FluentReact.Localized);
expect(localized.length).toBe(3);
const heading = localized.at(0).childAt(0);
expect(heading.type()).toBe("h3");
expect(heading.hasClass("accessibility-check-header")).toBe(true);
const icon = localized.at(1).childAt(0);
expect(icon.type()).toBe("img");
expect(icon.hasClass(props.score)).toBe(true);
const annotation = localized.at(2).childAt(0);
expect(annotation.type()).toBe("p");
expect(annotation.hasClass("accessibility-check-annotation")).toBe(true);
}
describe("TextLabelCheck component:", () => {
const testProps = [
{ issue: AREA_NO_NAME_FROM_ALT, score: FAIL },
@ -64,7 +44,7 @@ describe("TextLabelCheck component:", () => {
);
const textLabelCheck = wrapper.find(TextLabelCheckClass);
testTextLabelCheck(textLabelCheck, props);
testCustomCheck(textLabelCheck, props);
});
}
});

Просмотреть файл

@ -4,6 +4,8 @@
"use strict";
const { reducers } = require("devtools/client/accessibility/reducers/index");
const CheckClass = require("devtools/client/accessibility/components/Check");
const FluentReact = require("devtools/client/shared/vendor/fluent-react");
const {
createStore,
@ -70,8 +72,59 @@ function checkMenuItem(menuItem, expected) {
}
}
/**
*
* @param {ReactWrapper}
* React wrapper for the top level check component.
* @param {Object}
* Expected audit properties:
* - score: audit score
* - issue: audit issue type
*/
function testCustomCheck(wrapper, props) {
expect(wrapper.html()).toMatchSnapshot();
expect(wrapper.children().length).toBe(1);
const check = wrapper.childAt(0);
expect(wrapper.find(CheckClass)).toStrictEqual(check);
testCheck(check, props);
}
/**
*
* @param {ReactWrapper}
* React wrapper for the check component.
* @param {Object}
* Expected audit properties:
* - score: audit score
* - issue: audit issue type
*/
function testCheck(wrapper, props) {
expect(wrapper.html()).toMatchSnapshot();
const container = wrapper.childAt(0);
expect(container.hasClass("accessibility-check")).toBe(true);
expect(container.prop("role")).toBe("presentation");
expect(wrapper.props()).toMatchObject(props);
const localized = wrapper.find(FluentReact.Localized);
expect(localized.length).toBe(3);
const heading = localized.at(0).childAt(0);
expect(heading.type()).toBe("h3");
expect(heading.hasClass("accessibility-check-header")).toBe(true);
const icon = localized.at(1).childAt(0);
expect(icon.type()).toBe("img");
expect(icon.hasClass(props.score)).toBe(true);
const annotation = localized.at(2).childAt(0);
expect(annotation.type()).toBe("p");
expect(annotation.hasClass("accessibility-check-annotation")).toBe(true);
}
module.exports = {
checkMenuItem,
mockAccessible,
setupStore,
testCheck,
testCustomCheck,
};