a11y-checker
Advanced tools
Comparing version 2.0.1 to 2.1.1
{ | ||
"name": "a11y-checker", | ||
"version": "2.0.1", | ||
"version": "2.1.1", | ||
"description": "Identifies accessibility issues in your code.", | ||
"main": "a11y.js", | ||
"main": "./src/a11y.js", | ||
"scripts": { | ||
@@ -7,0 +7,0 @@ "start": "npm-run-all --parallel dev:server lint:watch", |
@@ -0,8 +1,26 @@ | ||
# How to use | ||
## Install | ||
```bash | ||
npm install --save a11y-checker | ||
``` | ||
## Usage | ||
- Import a11yChecker | ||
```js | ||
import a11yChecker from 'a11y-checker'; | ||
``` | ||
- Call it after page loads: | ||
```js | ||
a11yChecker(); | ||
``` | ||
## Contribute | ||
* clone project | ||
```bash | ||
git clone git@github.com:Muhnad/a11y-checker.git | ||
git clone git@github.com:Muhnad/a11y-checker.git | ||
``` | ||
@@ -18,5 +36,4 @@ * `cd a11y-checker/` | ||
- body: for check everything happens inside `<body>` | ||
- dist: for production usage. | ||
## Rules | ||
[Docs](https://github.com/Muhnad/a11y-checker/tree/master/docs) |
import * as head from './head'; | ||
import * as body from './body'; | ||
const a11yChecker = (() => { | ||
head.hasDocumentType(); | ||
head.hasDocumentTitle(); | ||
head.hasDocumentLanguage(); | ||
head.hasDocumentMetaCharset(); | ||
head.hasDocumentScalable(); | ||
const a11yChecker = () => { | ||
head.hasDocumentType(); | ||
head.hasDocumentTitle(); | ||
head.hasDocumentLanguage(); | ||
head.hasDocumentMetaCharset(); | ||
head.hasDocumentScalable(); | ||
body.hasHeadingOnce(); | ||
body.hasImagesAlt(); | ||
body.hasLinksText(); | ||
body.hasLinksHref(); | ||
body.hasLinksTarget(); | ||
body.hasButtonsText(); | ||
body.hasSVGRole(); | ||
body.hasIframeTitle(); | ||
body.hasFormsLabel(); | ||
body.hasForLabel(); | ||
body.hasVideoTrack(); | ||
body.hasAudioTrack(); | ||
body.hasPositiveTabIndex(); | ||
body.hasDuplicateIds(); | ||
})(); | ||
body.hasHeadingOnce(); | ||
body.hasImagesAlt(); | ||
body.hasLinksText(); | ||
body.hasLinksHref(); | ||
body.hasLinksTarget(); | ||
body.hasButtonsText(); | ||
body.hasSVGRole(); | ||
body.hasIframeTitle(); | ||
body.hasFormsLabel(); | ||
body.hasForLabel(); | ||
body.hasVideoTrack(); | ||
body.hasAudioTrack(); | ||
body.hasPositiveTabIndex(); | ||
body.hasDuplicateIds(); | ||
}; | ||
export default a11yChecker; |
@@ -10,6 +10,6 @@ import {Warning} from '../utils/warn'; | ||
const hasHeadingOnce = () => { | ||
const H1 = getElements('h1'); | ||
const hasMultiHeading = H1.length > 1; | ||
const H1 = getElements('h1'); | ||
const hasMultiHeading = H1.length > 1; | ||
if (hasMultiHeading) Warning('Page has Multi <h1> tag. Fix: use only one <h1> in the page.'); | ||
if (hasMultiHeading) Warning('Page has Multi <h1> tag. Fix: use only one <h1> in the page.'); | ||
}; | ||
@@ -19,18 +19,18 @@ | ||
const hasImagesAlt = () => { | ||
const IMGS = [...getElements('img')]; | ||
const imagesWithoutAlt = IMGS.filter(img => !hasAttribute(img, 'alt')); | ||
const hasMissingAlt = imagesWithoutAlt.length > 0; | ||
const withoutAltWarning = imagesWithoutAlt.forEach(image => Warning(`Image Alt is missing. Fix: Add alt="IMAGE WELL DESCRIBED" to ${image.outerHTML}`)); | ||
const IMGS = [...getElements('img')]; | ||
const imagesWithoutAlt = IMGS.filter(img => !hasAttribute(img, 'alt')); | ||
const hasMissingAlt = imagesWithoutAlt.length > 0; | ||
const withoutAltWarning = imagesWithoutAlt.forEach(image => Warning(`Image Alt is missing. Fix: Add alt="IMAGE WELL DESCRIBED" to ${image.outerHTML}`)); | ||
if (hasMissingAlt) withoutAltWarning; | ||
if (hasMissingAlt) withoutAltWarning; | ||
}; | ||
const hasLinksText = () => { | ||
const LINKS = [...getElements('a')]; | ||
const warningMessage = 'Link text is missing. Fix: DESCRIBE PURPOSE OF LINK'; | ||
const linksWithoutText = LINKS.filter(link => isEmpty(link.textContent) && !hasAccessibileText(link)); | ||
const hasMissingText = linksWithoutText.length > 0; | ||
const withoutTextWarning = linksWithoutText.forEach(link => Warning(`${warningMessage} to ${link.outerHTML}`)); | ||
const LINKS = [...getElements('a')]; | ||
const warningMessage = 'Link text is missing. Fix: DESCRIBE PURPOSE OF LINK'; | ||
const linksWithoutText = LINKS.filter(link => isEmpty(link.textContent) && !hasAccessibileText(link)); | ||
const hasMissingText = linksWithoutText.length > 0; | ||
const withoutTextWarning = linksWithoutText.forEach(link => Warning(`${warningMessage} to ${link.outerHTML}`)); | ||
if (hasMissingText) withoutTextWarning; | ||
if (hasMissingText) withoutTextWarning; | ||
}; | ||
@@ -40,8 +40,8 @@ | ||
const hasLinksHref = () => { | ||
const LINKS = [...getElements('a')]; | ||
const linksWithoutHref = LINKS.filter(link => (isEmpty(getAttribute(link, 'href')) || !hasAttribute(link, 'href')) && !hasAttribute(link, 'role')); | ||
const hasMissingHref = linksWithoutHref.length > 0; | ||
const withoutHrefWarning = linksWithoutHref.forEach(link => Warning(`Link Href is missing. Fix: Add href="LINK URL" to ${link.outerHTML}`)); | ||
const LINKS = [...getElements('a')]; | ||
const linksWithoutHref = LINKS.filter(link => (isEmpty(getAttribute(link, 'href')) || !hasAttribute(link, 'href')) && !hasAttribute(link, 'role')); | ||
const hasMissingHref = linksWithoutHref.length > 0; | ||
const withoutHrefWarning = linksWithoutHref.forEach(link => Warning(`Link Href is missing. Fix: Add href="LINK URL" to ${link.outerHTML}`)); | ||
if (hasMissingHref) withoutHrefWarning; | ||
if (hasMissingHref) withoutHrefWarning; | ||
}; | ||
@@ -51,9 +51,9 @@ | ||
const hasLinksTarget = () => { | ||
const LINKS = [...getElements('a')]; | ||
const warningMessage = 'Hint message is missing. Should add hint message to recognize this link will open in new tab. Fix: Add aria-describedby="ELEMENT ID"'; | ||
const linksWithTarget = LINKS.filter(link => getAttribute(link, 'target') === '_blank' && !hasAttribute(link, 'aria-describedby')); | ||
const hasTarget = linksWithTarget.length > 0; | ||
const missingTargetHint = linksWithTarget.forEach(link => Warning(`${warningMessage} to ${link.outerHTML}`)); | ||
const LINKS = [...getElements('a')]; | ||
const warningMessage = 'Hint message is missing. Should add hint message to recognize this link will open in new tab. Fix: Add aria-describedby="ELEMENT ID"'; | ||
const linksWithTarget = LINKS.filter(link => getAttribute(link, 'target') === '_blank' && !hasAttribute(link, 'aria-describedby')); | ||
const hasTarget = linksWithTarget.length > 0; | ||
const missingTargetHint = linksWithTarget.forEach(link => Warning(`${warningMessage} to ${link.outerHTML}`)); | ||
if (hasTarget) missingTargetHint; | ||
if (hasTarget) missingTargetHint; | ||
}; | ||
@@ -63,9 +63,9 @@ | ||
const hasButtonsText = () => { | ||
const BUTTONS = [...getElements('button')]; | ||
const warningMessage = 'Button text or aria-label is missing. Fix: Add aria-label="VALUE" or <button>VALUE</button>'; | ||
const buttonsWithoutText = BUTTONS.filter(button => isEmpty(button.textContent) && !hasAccessibileText(button)); | ||
const hasMissingText = buttonsWithoutText.length > 0; | ||
const withoutTextWarning = buttonsWithoutText.forEach(button => Warning(`${warningMessage} to ${button.outerHTML}`)); | ||
const BUTTONS = [...getElements('button')]; | ||
const warningMessage = 'Button text or aria-label is missing. Fix: Add aria-label="VALUE" or <button>VALUE</button>'; | ||
const buttonsWithoutText = BUTTONS.filter(button => isEmpty(button.textContent) && !hasAccessibileText(button)); | ||
const hasMissingText = buttonsWithoutText.length > 0; | ||
const withoutTextWarning = buttonsWithoutText.forEach(button => Warning(`${warningMessage} to ${button.outerHTML}`)); | ||
if (hasMissingText) withoutTextWarning; | ||
if (hasMissingText) withoutTextWarning; | ||
}; | ||
@@ -75,10 +75,10 @@ | ||
const hasForLabel = () => { | ||
const LABELS = [...getElements('label')]; | ||
const isLabeld = label => { | ||
if (!hasAttribute(label, 'for') || isEmpty(getAttribute(label, 'for'))) | ||
Warning(`For is missing in label. Fix: Add for="INPUT ID" to ${label.outerHTML}`); | ||
}; | ||
const missingForLabel = LABELS.forEach(isLabeld); | ||
const LABELS = [...getElements('label')]; | ||
const isLabeld = label => { | ||
if (!hasAttribute(label, 'for') || isEmpty(getAttribute(label, 'for'))) | ||
Warning(`For is missing in label. Fix: Add for="INPUT ID" to ${label.outerHTML}`); | ||
}; | ||
const missingForLabel = LABELS.forEach(isLabeld); | ||
return missingForLabel; | ||
return missingForLabel; | ||
}; | ||
@@ -88,6 +88,6 @@ | ||
const hasSVGRole = () => { | ||
const SVGS = [...getElements('SVG')]; | ||
const hasMissingRole = SVGS.some(svg => getAttribute(svg, 'aria-hidden') !== 'true' && !hasAttribute(svg, 'role')); | ||
const SVGS = [...getElements('SVG')]; | ||
const hasMissingRole = SVGS.some(svg => getAttribute(svg, 'aria-hidden') !== 'true' && !hasAttribute(svg, 'role') && !getAttribute(svg, 'id')); | ||
if (hasMissingRole) Warning('SVG Role is missing. Fix: Add role="img" or (aria-hidden="true" if you need to hide element from SR).'); | ||
if (hasMissingRole) Warning('SVG Role is missing. Fix: Add role="img" or (aria-hidden="true" if you need to hide element from SR).'); | ||
}; | ||
@@ -97,6 +97,6 @@ | ||
const hasIframeTitle = () => { | ||
const IFRAMES = [...getElements('iframe')]; | ||
const iframeWithoutTitle = IFRAMES.some(ifrmae => !hasAttribute(ifrmae, 'title')); | ||
const IFRAMES = [...getElements('iframe')]; | ||
const iframeWithoutTitle = IFRAMES.some(ifrmae => !hasAttribute(ifrmae, 'title')); | ||
if (iframeWithoutTitle) Warning('Title is missing in iframe. Fix: Add title="DESCRIBE CONTENT OF FRAME" to <iframe>'); | ||
if (iframeWithoutTitle) Warning('Title is missing in iframe. Fix: Add title="DESCRIBE CONTENT OF FRAME" to <iframe>'); | ||
}; | ||
@@ -106,6 +106,6 @@ | ||
const hasVideoTrack = () => { | ||
const VIDEOS = [...getElements('video')]; | ||
const videoWithoutTrack = VIDEOS.some(hasTrack); | ||
const VIDEOS = [...getElements('video')]; | ||
const videoWithoutTrack = VIDEOS.some(hasTrack); | ||
if (videoWithoutTrack) Warning('Video track is missing. Fix: Add <track> element with subtitles, captions to >video>'); | ||
if (videoWithoutTrack) Warning('Video track is missing. Fix: Add <track> element with subtitles, captions to >video>'); | ||
}; | ||
@@ -115,6 +115,6 @@ | ||
const hasAudioTrack = () => { | ||
const AUDIOS = [...getElements('audio')]; | ||
const audioWithoutTrack = AUDIOS.some(hasTrack); | ||
const AUDIOS = [...getElements('audio')]; | ||
const audioWithoutTrack = AUDIOS.some(hasTrack); | ||
if (audioWithoutTrack) Warning('Audio track is missing. Fix: Add <track> element with subtitles, captions to <audio>'); | ||
if (audioWithoutTrack) Warning('Audio track is missing. Fix: Add <track> element with subtitles, captions to <audio>'); | ||
}; | ||
@@ -124,6 +124,6 @@ | ||
const hasFormsLabel = () => { | ||
const FORMS = [...getElements('form')]; | ||
const formsWithoutLabels = FORMS.some(video => !hasAccessibileText(video)); | ||
const FORMS = [...getElements('form')]; | ||
const formsWithoutLabels = FORMS.some(video => !hasAccessibileText(video)); | ||
if (formsWithoutLabels) Warning('Forms Label is missing. Fix: Add aria-label, aria-labelledby to <form>'); | ||
if (formsWithoutLabels) Warning('Forms Label is missing. Fix: Add aria-label, aria-labelledby to <form>'); | ||
}; | ||
@@ -133,7 +133,7 @@ | ||
const hasPositiveTabIndex = () => { | ||
const ALLELEMENTS = [...getElements('*')]; | ||
const elementsWithTabindex = ALLELEMENTS.filter(element => getAttribute(element, 'tabindex') > 0); | ||
const hasPositiveindex = elementsWithTabindex.length > 0; | ||
const ALLELEMENTS = [...getElements('*')]; | ||
const elementsWithTabindex = ALLELEMENTS.filter(element => getAttribute(element, 'tabindex') > 0); | ||
const hasPositiveindex = elementsWithTabindex.length > 0; | ||
if (hasPositiveindex) Warning('Avoid using positive integer values for tabindex. Fix: Remove/Replace tabindex=">0" '); | ||
if (hasPositiveindex) Warning('Avoid using positive integer values for tabindex. Fix: Remove/Replace tabindex=">0" '); | ||
}; | ||
@@ -143,10 +143,10 @@ | ||
const hasDuplicateIds = () => { | ||
const ALLELEMENTS = [...getElements('*')]; | ||
const elementsWithId = ALLELEMENTS | ||
.map(element => getAttribute(element, 'id')) | ||
.filter(el => !isNull(el)); | ||
const uniqueIds = [...new Set(elementsWithId)]; | ||
const hasDuplicate = elementsWithId.length > uniqueIds.length; | ||
const ALLELEMENTS = [...getElements('*')]; | ||
const elementsWithId = ALLELEMENTS | ||
.map(element => getAttribute(element, 'id')) | ||
.filter(el => !isNull(el)); | ||
const uniqueIds = [...new Set(elementsWithId)]; | ||
const hasDuplicate = elementsWithId.length > uniqueIds.length; | ||
if (hasDuplicate) Warning('Avoid duplicate ids, ID must be unique. Fix: Remove/Replace duplicate id'); | ||
if (hasDuplicate) Warning('Avoid duplicate ids, ID must be unique. Fix: Remove/Replace duplicate id'); | ||
}; | ||
@@ -153,0 +153,0 @@ |
@@ -11,3 +11,3 @@ import {Warning} from '../utils/warn'; | ||
const hasDocumentType = () => { | ||
if (!doctype) Warning('Doctype is missing. Fix: Add <!DOCTYPE html>'); | ||
if (!doctype) Warning('Doctype is missing. Fix: Add <!DOCTYPE html>'); | ||
}; | ||
@@ -17,3 +17,3 @@ | ||
const hasDocumentTitle = () => { | ||
if (isEmpty(title)) Warning('Title is missing. Fix: <title>WELL DESCRIBED TITLE</title>'); | ||
if (isEmpty(title)) Warning('Title is missing. Fix: <title>WELL DESCRIBED TITLE</title>'); | ||
}; | ||
@@ -23,13 +23,13 @@ | ||
const hasDocumentLanguage = () => { | ||
const HTML = getElement('html'); | ||
const hasLanguageAttr = hasAttribute(HTML, 'lang'); | ||
const HTML = getElement('html'); | ||
const hasLanguageAttr = hasAttribute(HTML, 'lang'); | ||
if (hasLanguageAttr) { | ||
const getLanguageValue = getAttribute(HTML, 'lang'); | ||
const isLanguageValueNotExist = isEmpty(getLanguageValue); | ||
if (hasLanguageAttr) { | ||
const getLanguageValue = getAttribute(HTML, 'lang'); | ||
const isLanguageValueNotExist = isEmpty(getLanguageValue); | ||
if (isLanguageValueNotExist) Warning('Language value is missing in HTML element. Fix: Add lang="LANGUAGE VALUE" to <html>'); | ||
} else { | ||
Warning('Language is missing in HTML element. Fix: Add lang="LANGUAGE VALUE" to <html>'); | ||
} | ||
if (isLanguageValueNotExist) Warning('Language value is missing in HTML element. Fix: Add lang="LANGUAGE VALUE" to <html>'); | ||
} else { | ||
Warning('Language is missing in HTML element. Fix: Add lang="LANGUAGE VALUE" to <html>'); | ||
} | ||
}; | ||
@@ -39,6 +39,6 @@ | ||
const hasDocumentMetaCharset = () => { | ||
const META = [...getElements('meta')]; | ||
const hasMetaCharset = META.some(tag => hasAttribute(tag, 'charset')); | ||
const META = [...getElements('meta')]; | ||
const hasMetaCharset = META.some(tag => hasAttribute(tag, 'charset')); | ||
if (!hasMetaCharset) Warning('Document encoding is missing. Fix: Add <meta charset="utf-8"/>'); | ||
if (!hasMetaCharset) Warning('Document encoding is missing. Fix: Add <meta charset="utf-8"/>'); | ||
}; | ||
@@ -48,6 +48,6 @@ | ||
const hasDocumentScalable = () => { | ||
const META = [...getElements('meta')]; | ||
const hasMetaScalable = META.some(el => getAttribute(el, 'user-scalable') === 'no'); | ||
const META = [...getElements('meta')]; | ||
const hasMetaScalable = META.some(el => getAttribute(el, 'user-scalable') === 'no'); | ||
if (hasMetaScalable) Warning('Document must not use the user-scalable=no. Fix: Remove user-scalable=no from <meta name=viewport>'); | ||
if (hasMetaScalable) Warning('Document must not use the user-scalable=no. Fix: Remove user-scalable=no from <meta name=viewport>'); | ||
}; | ||
@@ -54,0 +54,0 @@ |
module.exports = { | ||
entry: __dirname + '/src/a11y.js', | ||
output: { | ||
path: __dirname + '/dist', | ||
publicPath: '/dist/', | ||
filename: 'a11y.js', | ||
library: 'a11yChecker', | ||
libraryTarget: 'umd', | ||
umdNamedDefine: true | ||
}, | ||
module: { | ||
rules: [{ | ||
test: /\.js$/, | ||
exclude: /node_modules/, | ||
use: 'babel-loader' | ||
}] | ||
} | ||
entry: __dirname + '/src/a11y.js', | ||
output: { | ||
path: __dirname + '/dist', | ||
publicPath: '/dist/', | ||
filename: 'a11y.js', | ||
library: 'a11yChecker', | ||
libraryTarget: 'umd', | ||
umdNamedDefine: true | ||
}, | ||
module: { | ||
rules: [{ | ||
test: /\.js$/, | ||
exclude: /node_modules/, | ||
use: 'babel-loader' | ||
}] | ||
} | ||
}; |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
39
14763
16