Commit ff5e8559 authored by David Sveningsson's avatar David Sveningsson
Browse files

fix(rules): `input-missing-label` requires label to be non-hidden

fixes #99
parent 41c39e91
......@@ -2,6 +2,31 @@
exports[`docs/rules/input-missing-label.md inline validation: correct 1`] = `Array []`;
exports[`docs/rules/input-missing-label.md inline validation: hidden 1`] = `
Array [
Object {
"errorCount": 1,
"filePath": "inline",
"messages": Array [
Object {
"column": 2,
"context": undefined,
"line": 2,
"message": "<input> element has label but <label> element is hidden",
"offset": 59,
"ruleId": "input-missing-label",
"selector": "#my-input",
"severity": 2,
"size": 5,
},
],
"source": "<label for=\\"my-input\\" aria-hidden=\\"true\\">My field</label>
<input id=\\"my-input\\" type=\\"text\\">",
"warningCount": 0,
},
]
`;
exports[`docs/rules/input-missing-label.md inline validation: incorrect 1`] = `
Array [
Object {
......
......@@ -26,6 +26,8 @@ markup["correct"] = `<!-- label with descendant -->
<label for="my-field">My field</label>
<input id="my-field" type="text">
</div>`;
markup["hidden"] = `<label for="my-input" aria-hidden="true">My field</label>
<input id="my-input" type="text">`;
describe("docs/rules/input-missing-label.md", () => {
it("inline validation: incorrect", () => {
......@@ -40,4 +42,10 @@ describe("docs/rules/input-missing-label.md", () => {
const report = htmlvalidate.validateString(markup["correct"]);
expect(report.results).toMatchSnapshot();
});
it("inline validation: hidden", () => {
expect.assertions(1);
const htmlvalidate = new HtmlValidate({"rules":{"input-missing-label":"error"}});
const report = htmlvalidate.validateString(markup["hidden"]);
expect(report.results).toMatchSnapshot();
});
});
......@@ -58,3 +58,12 @@ Examples of **correct** code for this rule:
</div>
</validate>
### Hidden labels
This rule requires labels to be accessible, i.e. the label must not be `hidden` or `aria-hidden`.
<validate name="hidden" rules="input-missing-label">
<label for="my-input" aria-hidden="true">My field</label>
<input id="my-input" type="text">
</validate>
......@@ -52,6 +52,26 @@ describe("rule input-missing-label", () => {
}
);
it("should report error when label is hidden", () => {
expect.assertions(2);
const report = htmlvalidate.validateString('<label for="foo" hidden></label><input id="foo">');
expect(report).toBeInvalid();
expect(report).toHaveError(
"input-missing-label",
`<input> element has label but <label> element is hidden`
);
});
it("should report error when label is aria-hidden", () => {
expect.assertions(2);
const report = htmlvalidate.validateString('<label for="foo" hidden></label><input id="foo">');
expect(report).toBeInvalid();
expect(report).toHaveError(
"input-missing-label",
`<input> element has label but <label> element is hidden`
);
});
it("smoketest", () => {
expect.assertions(1);
const report = htmlvalidate.validateFile("test-files/rules/input-missing-label.html");
......
......@@ -15,32 +15,46 @@ export default class InputMissingLabel extends Rule {
this.on("dom:ready", (event: DOMReadyEvent) => {
const root = event.document;
for (const elem of root.querySelectorAll("input, textarea, select")) {
if (isHTMLHidden(elem) || isAriaHidden(elem)) {
continue;
}
/* <input type="hidden"> should not have label */
if (elem.is("input")) {
const type = elem.getAttributeValue("type");
if (type && type.toLowerCase() === "hidden") {
continue;
}
}
/* try to find label by id */
if (findLabelById(root, elem.id)) {
continue;
}
/* try to find parent label (input nested in label) */
if (findLabelByParent(elem)) {
continue;
}
this.report(elem, `<${elem.tagName}> element does not have a <label>`);
this.validateInput(root, elem);
}
});
}
private validateInput(root: DOMTree, elem: HtmlElement): void {
if (isHTMLHidden(elem) || isAriaHidden(elem)) {
return;
}
/* <input type="hidden"> should not have label */
if (elem.is("input")) {
const type = elem.getAttributeValue("type");
if (type && type.toLowerCase() === "hidden") {
return;
}
}
let label: HtmlElement;
/* try to find label by id */
if ((label = findLabelById(root, elem.id))) {
this.validateLabel(elem, label);
return;
}
/* try to find parent label (input nested in label) */
if ((label = findLabelByParent(elem))) {
this.validateLabel(elem, label);
return;
}
this.report(elem, `<${elem.tagName}> element does not have a <label>`);
}
private validateLabel(elem: HtmlElement, label: HtmlElement): void {
if (isHTMLHidden(label) || isAriaHidden(label)) {
this.report(elem, `<${elem.tagName}> element has label but <label> element is hidden`);
}
}
}
function findLabelById(root: DOMTree, id: string): HtmlElement {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment