Skip to content

Commit 92f03c5

Browse files
fix(ByLabelText): improve error message for invalid labels (#720)
* fix(ByLabelText): improve error message when label is associated with non-labellable elements (#716) * fix(ByLabelText) test coverage for #716 Co-authored-by: Ioannis Papadopoulos <[email protected]>
1 parent 26bf006 commit 92f03c5

File tree

2 files changed

+144
-7
lines changed

2 files changed

+144
-7
lines changed

src/__tests__/element-queries.js

Lines changed: 113 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -352,17 +352,19 @@ test('label with no form control', () => {
352352
`)
353353
})
354354

355-
test('label with no form control and fuzzy matcher', () => {
355+
test('label with "for" attribute but no form control and fuzzy matcher', () => {
356356
const {getByLabelText, queryByLabelText} = render(
357-
`<label>All alone label</label>`,
357+
`<label for="foo">All alone label</label>`,
358358
)
359359
expect(queryByLabelText('alone', {exact: false})).toBeNull()
360360
expect(() => getByLabelText('alone', {exact: false}))
361361
.toThrowErrorMatchingInlineSnapshot(`
362362
"Found a label with the text of: alone, however no form control was found associated to that label. Make sure you're using the "for" attribute or "aria-labelledby" attribute correctly.
363363
364364
<div>
365-
<label>
365+
<label
366+
for="foo"
367+
>
366368
All alone label
367369
</label>
368370
</div>"
@@ -407,6 +409,114 @@ test('label with children with no form control', () => {
407409
`)
408410
})
409411

412+
test('label with non-labellable element', () => {
413+
const {getByLabelText, queryByLabelText} = render(`
414+
<div>
415+
<label for="div1">Label 1</label>
416+
<div id="div1">
417+
Hello
418+
</div>
419+
</div>
420+
`)
421+
422+
expect(queryByLabelText(/Label/)).toBeNull()
423+
expect(() => getByLabelText(/Label/)).toThrowErrorMatchingInlineSnapshot(`
424+
"Found a label with the text of: /Label/, however the element associated with this label (<div />) is non-labellable [https://html.spec.whatwg.org/multipage/forms.html#category-label]. If you really need to label a <div />, you can use aria-label or aria-labelledby instead.
425+
426+
<div>
427+
428+
429+
<div>
430+
431+
432+
<label
433+
for="div1"
434+
>
435+
Label 1
436+
</label>
437+
438+
439+
<div
440+
id="div1"
441+
>
442+
443+
Hello
444+
445+
</div>
446+
447+
448+
</div>
449+
450+
451+
</div>"
452+
`)
453+
})
454+
455+
test('multiple labels with non-labellable elements', () => {
456+
const {getAllByLabelText, queryAllByLabelText} = render(`
457+
<div>
458+
<label for="span1">Label 1</label>
459+
<span id="span1">
460+
Hello
461+
</span>
462+
<label for="p1">Label 2</label>
463+
<p id="p1">
464+
World
465+
</p>
466+
</div>
467+
`)
468+
469+
expect(queryAllByLabelText(/Label/)).toEqual([])
470+
expect(() => getAllByLabelText(/Label/)).toThrowErrorMatchingInlineSnapshot(`
471+
"Found a label with the text of: /Label/, however the element associated with this label (<span />) is non-labellable [https://html.spec.whatwg.org/multipage/forms.html#category-label]. If you really need to label a <span />, you can use aria-label or aria-labelledby instead.
472+
473+
Found a label with the text of: /Label/, however the element associated with this label (<p />) is non-labellable [https://html.spec.whatwg.org/multipage/forms.html#category-label]. If you really need to label a <p />, you can use aria-label or aria-labelledby instead.
474+
475+
<div>
476+
477+
478+
<div>
479+
480+
481+
<label
482+
for="span1"
483+
>
484+
Label 1
485+
</label>
486+
487+
488+
<span
489+
id="span1"
490+
>
491+
492+
Hello
493+
494+
</span>
495+
496+
497+
<label
498+
for="p1"
499+
>
500+
Label 2
501+
</label>
502+
503+
504+
<p
505+
id="p1"
506+
>
507+
508+
World
509+
510+
</p>
511+
512+
513+
</div>
514+
515+
516+
</div>"
517+
`)
518+
})
519+
410520
test('totally empty label', () => {
411521
const {getByLabelText, queryByLabelText} = render(`<label />`)
412522
expect(queryByLabelText('')).toBeNull()

src/queries/label-text.js

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,27 @@ const getAllByLabelText = (container, text, ...rest) => {
149149
if (!els.length) {
150150
const labels = queryAllLabelsByText(container, text, ...rest)
151151
if (labels.length) {
152-
throw getConfig().getElementError(
153-
`Found a label with the text of: ${text}, however no form control was found associated to that label. Make sure you're using the "for" attribute or "aria-labelledby" attribute correctly.`,
154-
container,
155-
)
152+
const tagNames = labels
153+
.map(label =>
154+
getTagNameOfElementAssociatedWithLabelViaFor(container, label),
155+
)
156+
.filter(tagName => !!tagName)
157+
if (tagNames.length) {
158+
throw getConfig().getElementError(
159+
tagNames
160+
.map(
161+
tagName =>
162+
`Found a label with the text of: ${text}, however the element associated with this label (<${tagName} />) is non-labellable [https://html.spec.whatwg.org/multipage/forms.html#category-label]. If you really need to label a <${tagName} />, you can use aria-label or aria-labelledby instead.`,
163+
)
164+
.join('\n\n'),
165+
container,
166+
)
167+
} else {
168+
throw getConfig().getElementError(
169+
`Found a label with the text of: ${text}, however no form control was found associated to that label. Make sure you're using the "for" attribute or "aria-labelledby" attribute correctly.`,
170+
container,
171+
)
172+
}
156173
} else {
157174
throw getConfig().getElementError(
158175
`Unable to find a label with the text of: ${text}`,
@@ -163,6 +180,16 @@ const getAllByLabelText = (container, text, ...rest) => {
163180
return els
164181
}
165182

183+
function getTagNameOfElementAssociatedWithLabelViaFor(container, label) {
184+
const htmlFor = label.getAttribute('for')
185+
if (!htmlFor) {
186+
return null
187+
}
188+
189+
const element = container.querySelector(`[id="${htmlFor}"]`)
190+
return element ? element.tagName.toLowerCase() : null
191+
}
192+
166193
// the reason mentioned above is the same reason we're not using buildQueries
167194
const getMultipleError = (c, text) =>
168195
`Found multiple elements with the text of: ${text}`

0 commit comments

Comments
 (0)