Skip to content

Commit

Permalink
feat: new rule: input-requires-label - All inputs require a label (#159)
Browse files Browse the repository at this point in the history
  • Loading branch information
olore authored and thedaviddias committed May 18, 2020
1 parent 8ad7cff commit 5bd40fb
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/rules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export { default as idClassValue } from './id-class-value';
export { default as idUnique } from './id-unique';
export { default as inlineScriptDisabled } from './inline-script-disabled';
export { default as inlineStyleDisabled } from './inline-style-disabled';
export { default as inputRequiresLabel } from './input-requires-label';
export { default as scriptDisabled } from './script-disabled';
export { default as spaceTabMixedDisabled } from './space-tab-mixed-disabled';
export { default as specCharEscape } from './spec-char-escape';
Expand Down
46 changes: 46 additions & 0 deletions src/rules/input-requires-label.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
export default {
id: 'input-requires-label',
description: 'All [ input ] tags must have a corresponding [ label ] tag. ',
init: function(parser, reporter){
var self = this,
labelTags = [],
inputTags = [];

parser.addListener('tagstart', function(event) {
var tagName = event.tagName.toLowerCase(),
mapAttrs = parser.getMapAttrs(event.attrs),
col = event.col + tagName.length + 1;

if (tagName === 'input') {
inputTags.push({event: event, col: col, id: mapAttrs['id']});
}

if (tagName === 'label') {
if (('for' in mapAttrs) && mapAttrs['for'] !== '') {
labelTags.push({event: event, col: col, forValue: mapAttrs['for']});
}
}

});

parser.addListener('end', function() {
inputTags.forEach(function(inputTag) {
if (!hasMatchingLabelTag(inputTag)) {
reporter.warn('No matching [ label ] tag found.', inputTag.event.line, inputTag.col, self, inputTag.event.raw);
}
});
});


function hasMatchingLabelTag(inputTag) {
var found = false;
labelTags.forEach(function(labelTag){
if (inputTag.id && (inputTag.id === labelTag.forValue)) {
found = true;
}
});
return found;

}
}
};
71 changes: 71 additions & 0 deletions test/rules/input-requires-label.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
const expect = require("expect.js");

const HTMLHint = require('../../dist/htmlhint.js').HTMLHint;

const ruleId = 'input-requires-label';
const ruleOptions = {};

ruleOptions[ruleId] = true;

describe(`Rules: ${ruleId}`, function(){

describe('Successful cases', function() {

it('Input tag with a matching label before should result in no error', function () {
var code = '<label for="some-id"/><input id="some-id" type="password" />';
var messages = HTMLHint.verify(code, ruleOptions);
expect(messages.length).to.be(0);
});

it('Input tag with a matching label after should result in no error', function () {
var code = '<input id="some-id" type="password" /> <label for="some-id"/>';
var messages = HTMLHint.verify(code, ruleOptions);
expect(messages.length).to.be(0);
});
});


describe('Error cases', function() {

it('Input tag with no matching label should result in an error', function () {
var code = '<input type="password">';
var messages = HTMLHint.verify(code, ruleOptions);
expect(messages.length).to.be(1);
expect(messages[0].rule.id).to.be(ruleId);
expect(messages[0].line).to.be(1);
expect(messages[0].col).to.be(7);
expect(messages[0].type).to.be('warning');
});

it('Input tag with label that doesn\'t match id should result in error', function () {
var code = '<input id="some-id" type="password" /> <label for="some-other-id"/>';
var messages = HTMLHint.verify(code, ruleOptions);
expect(messages.length).to.be(1);
expect(messages[0].rule.id).to.be(ruleId);
expect(messages[0].line).to.be(1);
expect(messages[0].col).to.be(7);
expect(messages[0].type).to.be('warning');
});

it('Input tag with blank label:for should result in error', function () {
var code = '<input id="some-id" type="password" /> <label for=""/>';
var messages = HTMLHint.verify(code, ruleOptions);
expect(messages.length).to.be(1);
expect(messages[0].rule.id).to.be(ruleId);
expect(messages[0].line).to.be(1);
expect(messages[0].col).to.be(7);
expect(messages[0].type).to.be('warning');
});

it('Input tag with no id should result in error', function () {
var code = '<input type="password" /> <label for="something"/>';
var messages = HTMLHint.verify(code, ruleOptions);
expect(messages.length).to.be(1);
expect(messages[0].rule.id).to.be(ruleId);
expect(messages[0].line).to.be(1);
expect(messages[0].col).to.be(7);
expect(messages[0].type).to.be('warning');
});
});

});

0 comments on commit 5bd40fb

Please sign in to comment.