diff --git a/src/__tests__/suggestions.js b/src/__tests__/suggestions.js
index f0c7fb66..00b40dfb 100644
--- a/src/__tests__/suggestions.js
+++ b/src/__tests__/suggestions.js
@@ -578,3 +578,44 @@ test('should suggest hidden option if element is not in the accessibilty tree',
]
`)
})
+
+test('should find label text using the aria-labelledby', () => {
+ const {container} = renderIntoDocument(`
+
+ `)
+
+ expect(
+ getSuggestedQuery(
+ container.querySelector('[id="sixth-id"]'),
+ 'get',
+ 'labelText',
+ ),
+ ).toMatchInlineSnapshot(
+ {
+ queryArgs: [/6th one 6th two 6th three/i],
+ queryMethod: 'getByLabelText',
+ queryName: 'LabelText',
+ variant: 'get',
+ warning: '',
+ },
+ `
+ Object {
+ "queryArgs": Array [
+ Object {},
+ ],
+ "queryMethod": "getByLabelText",
+ "queryName": "LabelText",
+ "toString": [Function],
+ "variant": "get",
+ "warning": "",
+ }
+ `,
+ )
+})
diff --git a/src/label-helpers.js b/src/label-helpers.js
new file mode 100644
index 00000000..52c5dc99
--- /dev/null
+++ b/src/label-helpers.js
@@ -0,0 +1,75 @@
+import {TEXT_NODE} from './helpers'
+
+const labelledNodeNames = [
+ 'button',
+ 'meter',
+ 'output',
+ 'progress',
+ 'select',
+ 'textarea',
+ 'input',
+]
+
+function getTextContent(node) {
+ if (labelledNodeNames.includes(node.nodeName.toLowerCase())) {
+ return ''
+ }
+
+ if (node.nodeType === TEXT_NODE) return node.textContent
+
+ return Array.from(node.childNodes)
+ .map(childNode => getTextContent(childNode))
+ .join('')
+}
+
+function getLabelContent(node) {
+ let textContent
+ if (node.tagName.toLowerCase() === 'label') {
+ textContent = getTextContent(node)
+ } else {
+ textContent = node.value || node.textContent
+ }
+ return textContent
+}
+
+// Based on https://github.com/eps1lon/dom-accessibility-api/pull/352
+function getRealLabels(element) {
+ if (element.labels !== undefined) return element.labels
+
+ if (!isLabelable(element)) return []
+
+ const labels = element.ownerDocument.querySelectorAll('label')
+ return Array.from(labels).filter(label => label.control === element)
+}
+
+function isLabelable(element) {
+ return (
+ element.tagName.match(/BUTTON|METER|OUTPUT|PROGRESS|SELECT|TEXTAREA/) ||
+ (element.tagName === 'INPUT' && element.getAttribute('type') !== 'hidden')
+ )
+}
+
+function getLabels(container, element, {selector = '*'} = {}) {
+ const labelsId = element.getAttribute('aria-labelledby')
+ ? element.getAttribute('aria-labelledby').split(' ')
+ : []
+ return labelsId.length
+ ? labelsId.map(labelId => {
+ const labellingElement = container.querySelector(`[id="${labelId}"]`)
+ return labellingElement
+ ? {content: getLabelContent(labellingElement), formControl: null}
+ : {content: '', formControl: null}
+ })
+ : Array.from(getRealLabels(element)).map(label => {
+ const textToMatch = getLabelContent(label)
+ const formControlSelector =
+ 'button, input, meter, output, progress, select, textarea'
+ const labelledFormControl = Array.from(
+ label.querySelectorAll(formControlSelector),
+ ).filter(formControlElement => formControlElement.matches(selector))[0]
+
+ return {content: textToMatch, formControl: labelledFormControl}
+ })
+}
+
+export {getLabels, getRealLabels, getLabelContent}
diff --git a/src/queries/label-text.js b/src/queries/label-text.js
index fc86d583..b4a45cc6 100644
--- a/src/queries/label-text.js
+++ b/src/queries/label-text.js
@@ -1,5 +1,6 @@
import {getConfig} from '../config'
-import {checkContainerType, TEXT_NODE} from '../helpers'
+import {checkContainerType} from '../helpers'
+import {getLabels, getRealLabels, getLabelContent} from '../label-helpers'
import {
fuzzyMatches,
matches,
@@ -11,16 +12,6 @@ import {
wrapSingleQueryWithSuggestion,
} from './all-utils'
-const labelledNodeNames = [
- 'button',
- 'meter',
- 'output',
- 'progress',
- 'select',
- 'textarea',
- 'input',
-]
-
function queryAllLabels(container) {
return Array.from(container.querySelectorAll('label,input'))
.map(node => {
@@ -46,28 +37,6 @@ function queryAllLabelsByText(
.map(({node}) => node)
}
-function getTextContent(node) {
- if (labelledNodeNames.includes(node.nodeName.toLowerCase())) {
- return ''
- }
-
- if (node.nodeType === TEXT_NODE) return node.textContent
-
- return Array.from(node.childNodes)
- .map(childNode => getTextContent(childNode))
- .join('')
-}
-
-function getLabelContent(node) {
- let textContent
- if (node.tagName.toLowerCase() === 'label') {
- textContent = getTextContent(node)
- } else {
- textContent = node.value || node.textContent
- }
- return textContent
-}
-
function queryAllByLabelText(
container,
text,
@@ -79,34 +48,21 @@ function queryAllByLabelText(
const matchNormalizer = makeNormalizer({collapseWhitespace, trim, normalizer})
const matchingLabelledElements = Array.from(container.querySelectorAll('*'))
.filter(element => {
- return getLabels(element) || element.hasAttribute('aria-labelledby')
+ return (
+ getRealLabels(element).length || element.hasAttribute('aria-labelledby')
+ )
})
.reduce((labelledElements, labelledElement) => {
- const labelsId = labelledElement.getAttribute('aria-labelledby')
- ? labelledElement.getAttribute('aria-labelledby').split(' ')
- : []
- let labelsValue = labelsId.length
- ? labelsId.map(labelId => {
- const labellingElement = container.querySelector(
- `[id="${labelId}"]`,
- )
- return labellingElement ? getLabelContent(labellingElement) : ''
- })
- : Array.from(getLabels(labelledElement)).map(label => {
- const textToMatch = getLabelContent(label)
- const formControlSelector = labelledNodeNames.join(',')
- const labelledFormControl = Array.from(
- label.querySelectorAll(formControlSelector),
- ).filter(element => element.matches(selector))[0]
- if (labelledFormControl) {
- if (
- matcher(textToMatch, labelledFormControl, text, matchNormalizer)
- )
- labelledElements.push(labelledFormControl)
- }
- return textToMatch
- })
- labelsValue = labelsValue.filter(Boolean)
+ const labelList = getLabels(container, labelledElement, {selector})
+ labelList
+ .filter(label => Boolean(label.formControl))
+ .forEach(label => {
+ if (matcher(label.content, label.formControl, text, matchNormalizer))
+ labelledElements.push(label.formControl)
+ })
+ const labelsValue = labelList
+ .filter(label => Boolean(label.content))
+ .map(label => label.content)
if (
matcher(labelsValue.join(' '), labelledElement, text, matchNormalizer)
)
@@ -232,6 +188,7 @@ const queryAllByLabelTextWithSuggestions = wrapAllByQueryWithSuggestion(
queryAllByLabelText.name,
'queryAll',
)
+
export {
queryAllByLabelTextWithSuggestions as queryAllByLabelText,
queryByLabelText,
@@ -240,20 +197,3 @@ export {
findAllByLabelText,
findByLabelText,
}
-
-// Based on https://github.com/eps1lon/dom-accessibility-api/pull/352
-function getLabels(element) {
- if (element.labels !== undefined) return element.labels
-
- if (!isLabelable(element)) return null
-
- const labels = element.ownerDocument.querySelectorAll('label')
- return Array.from(labels).filter(label => label.control === element)
-}
-
-function isLabelable(element) {
- return (
- element.tagName.match(/BUTTON|METER|OUTPUT|PROGRESS|SELECT|TEXTAREA/) ||
- (element.tagName === 'INPUT' && element.getAttribute('type') !== 'hidden')
- )
-}
diff --git a/src/suggestions.js b/src/suggestions.js
index e3fef0e7..0f1bef6a 100644
--- a/src/suggestions.js
+++ b/src/suggestions.js
@@ -3,33 +3,10 @@ import {getDefaultNormalizer} from './matches'
import {getNodeText} from './get-node-text'
import {DEFAULT_IGNORE_TAGS, getConfig} from './config'
import {getImplicitAriaRoles, isInaccessible} from './role-helpers'
+import {getLabels} from './label-helpers'
const normalize = getDefaultNormalizer()
-function getLabelTextFor(element) {
- let label =
- element.labels &&
- Array.from(element.labels).find(el => Boolean(normalize(el.textContent)))
-
- // non form elements that are using aria-labelledby won't be included in `element.labels`
- if (!label) {
- const ariaLabelledBy = element.getAttribute('aria-labelledby')
- if (ariaLabelledBy) {
- // this is only a temporary fix. The problem is that at the moment @testing-library/dom
- // not support label concatenation
- // see https://github.com/testing-library/dom-testing-library/issues/545
- const firstId = ariaLabelledBy.split(' ')[0]
- // we're using this notation because with the # selector we would have to escape special characters e.g. user.name
- // see https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector#Escaping_special_characters
- label = document.querySelector(`[id="${firstId}"]`)
- }
- }
-
- if (label) {
- return label.textContent
- }
- return undefined
-}
function escapeRegExp(string) {
return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
}
@@ -113,7 +90,9 @@ export function getSuggestedQuery(element, variant = 'get', method) {
})
}
- const labelText = getLabelTextFor(element)
+ const labelText = getLabels(document, element)
+ .map(label => label.content)
+ .join(' ')
if (canSuggest('LabelText', method, labelText)) {
return makeSuggestion('LabelText', element, labelText, {variant})
}