Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement: console.warn when no label is provided #12 #122

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions docs/index.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<!doctype html>
<html>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>react-toggle</title>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" />
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>react-toggle</title>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" />
</head>
<body>
<div id="application" class="container"></div>
<script src="bundle.js"></script><!-- Don't change `src`-attribute unless you know what you're doing -->
Expand Down
179 changes: 179 additions & 0 deletions spec/util.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@

import { expect } from 'chai'
import { hasParentLabel, hasIdThatMatchesLabelFor, hasAriaLabelOrAriaLabelledbyAttr, checkForLabel } from '../src/component/util'

describe('utils', () => {

describe('hasParentLabel util', () => {

it('will return false if there isn\'t a node.parentNode', () => {
// Arrange
const mockElement = {
parentNode: null,
}

// Act
const result = hasParentLabel(mockElement)

// Assert
expect(result).to.be.false
})

it('will return true if there is a node.parentNode and', () => {
// Arrange
const mockTagName = 'David Bowie'
const mockElement = {
parentNode: {
nodeName: 'David Bowie',
},
}

// Act
const result = hasParentLabel(mockElement, mockTagName)

// Assert
expect(result).to.be.true
})

it('will recursively call itself until there is no parentNode', () => {
// Arrange
const mockTagName = 'Prince'
const mockElement = {
parentNode: {
nodeName: 'David Bowie',
parentNode: {
nodeName: 'Billy Corgan',
parentNode: {
nodeName: 'Jack White',
parentNode: null,
},
},
},
}

// Act
const result = hasParentLabel(mockElement, mockTagName)

// Assert
expect(result).to.be.false
})

it('will recursively call itself until the parentNode.nodeName equals the given tag name', () => {
// Arrange
const mockTagName = 'Prince'
const mockElement = {
parentNode: {
nodeName: 'David Bowie',
parentNode: {
nodeName: 'Billy Corgan',
parentNode: {
nodeName: 'Jack White',
parentNode: {
nodeName: 'Prince',
},
},
},
},
}

// Act
const result = hasParentLabel(mockElement, mockTagName)

// Assert
expect(result).to.be.true
})
}) // End describe hasParentlabel util

describe('hasIdThatMatchesLabelFor util', () => {

it('will return false if the node does not have an id', () => {
// Arrange
const mockElement = {
id: '',
}

// Act
const result = hasIdThatMatchesLabelFor(mockElement)

// Assert
expect(result).to.be.false
})

it('will return false if node.id is not in any nodes returned by document.querySelectorAll', () => {
// Arrange
const mockElement = {
id: 'space-oddity',
}
const mockDocumentObj = {
querySelectorAll: () => [],
}

// Act
const result = hasIdThatMatchesLabelFor(mockElement, mockDocumentObj)

// Assert
expect(result).to.be.false
})

it('will return true if node.id is in any nodes returned by document.querySelectorAll', () => {
// Arrange
const mockElement = {
id: 'raspberry-beret',
}
const mockLabelNode = {
htmlFor: 'raspberry-beret',
}
const mockDocumentObj = {
querySelectorAll: () => [mockLabelNode],
}

// Act
const result = hasIdThatMatchesLabelFor(mockElement, mockDocumentObj)

// Assert
expect(result).to.be.true
})
})

describe('hasAriaLabelOrAriaLabelledbyAttr util', () => {

it('will return false if the element does not have the aria-label or aria-labelledby attribute', () => {
// Arrange
const mockElement = {
getAttribute: () => null,
}

// Act
const result = hasAriaLabelOrAriaLabelledbyAttr(mockElement)

// Assert
expect(result).to.be.false
})

it('will return true if the element has the aria-label attribute', () => {
// Arrange
const mockElement = {
getAttribute: (attr) => attr === 'aria-label',
}

// Act
const result = hasAriaLabelOrAriaLabelledbyAttr(mockElement)

// Assert
expect(result).to.be.true
})

it('will return true if the element has the aria-labelledby attribute', () => {
// Arrange
const mockElement = {
getAttribute: (attr) => attr === 'aria-labelledby',
}

// Act
const result = hasAriaLabelOrAriaLabelledbyAttr(mockElement)

// Assert
expect(result).to.be.true
})
})
})
9 changes: 7 additions & 2 deletions src/component/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import classNames from 'classnames'
import PropTypes from 'prop-types'
import Check from './check'
import X from './x'
import { pointerCoord } from './util'
import { checkForLabel, pointerCoord } from './util'

export default class Toggle extends PureComponent {
constructor (props) {
Expand All @@ -27,6 +27,10 @@ export default class Toggle extends PureComponent {
}
}

componentDidMount () {
checkForLabel(this.toggleContainer, this.input)
}

handleClick (event) {
const checkbox = this.input
if (event.target !== checkbox && !this.moved) {
Expand Down Expand Up @@ -135,7 +139,8 @@ export default class Toggle extends PureComponent {
onClick={this.handleClick}
onTouchStart={this.handleTouchStart}
onTouchMove={this.handleTouchMove}
onTouchEnd={this.handleTouchEnd}>
onTouchEnd={this.handleTouchEnd}
ref={ref => { this.toggleContainer = ref }}>
<div className='react-toggle-track'>
<div className='react-toggle-track-check'>
{this.getIcon('checked')}
Expand Down
23 changes: 23 additions & 0 deletions src/component/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,26 @@ export function pointerCoord (event) {
}
return { x: 0, y: 0 }
}

export function checkForLabel (toggleContainer, input) {
return (hasParentLabel(toggleContainer, 'LABEL') || hasIdThatMatchesLabelFor(input) || hasAriaLabelOrAriaLabelledbyAttr(input)) ||
console.warn('There seems to not be any associated label for the react-toggle element. https://www.w3.org/standards/webdesign/accessibility')
}

export function hasParentLabel (element, tagName) {
return (element.parentNode && element.parentNode.nodeName === tagName)
? true
: (!element.parentNode)
? false
: hasParentLabel(element.parentNode, tagName)
}

export function hasIdThatMatchesLabelFor (element, document = window.document) {
return element.id
? Array.from(document.querySelectorAll('label')).some(label => label.htmlFor === element.id)
: false
}

export function hasAriaLabelOrAriaLabelledbyAttr (element) {
return !!element.getAttribute('aria-label') || !!element.getAttribute('aria-labelledby')
}