Skip to content

Commit e879745

Browse files
airjp73kentcdodds
authored andcommitted
feat(ByLabelText): change selector to match target (not label) (#373)
Closes #372 BREAKING CHANGE: If you used the `selector` option in `ByLabelText` queries, then you will probably need to update that code to be able to find the label you're looking for.
1 parent 6084f53 commit e879745

File tree

2 files changed

+88
-12
lines changed

2 files changed

+88
-12
lines changed

src/__tests__/element-queries.js

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ test('can get elements labelled with aria-labelledby attribute', () => {
199199
expect(getByLabelText('Section One').id).toBe('section-one')
200200
})
201201

202-
test('can get sibling elements with aria-labelledby attrib ute', () => {
202+
test('can get sibling elements with aria-labelledby attribute', () => {
203203
const {getAllByLabelText} = render(`
204204
<div>
205205
<svg id="icon" aria-labelledby="icon-desc"></svg>
@@ -212,6 +212,73 @@ test('can get sibling elements with aria-labelledby attrib ute', () => {
212212
expect(result[0].id).toBe('icon')
213213
})
214214

215+
test('can filter results of label query based on selector', () => {
216+
const {getAllByLabelText} = render(`
217+
<div>
218+
<label id="label1" for="input1">
219+
Test Label
220+
<input id="input2" />
221+
</label>
222+
<input id="input1" class="fancy-input" />
223+
<span aria-labelledby="label1">Some hint text</span>
224+
</div>
225+
`)
226+
227+
const result = getAllByLabelText('Test Label', {selector: '.fancy-input'})
228+
expect(result).toHaveLength(1)
229+
expect(result[0].id).toBe('input1')
230+
})
231+
232+
test('can find any form control when label text is inside other elements', () => {
233+
const {getAllByLabelText} = render(`
234+
<label>
235+
<span>Test</span>
236+
<span>Label</span>
237+
<button />
238+
<input />
239+
<meter />
240+
<output />
241+
<progress />
242+
<select />
243+
<textarea />
244+
</label>
245+
`)
246+
247+
const result = getAllByLabelText('Test Label')
248+
expect(result).toHaveLength(7)
249+
})
250+
251+
test('can find non-input elements when aria-labelledby a label', () => {
252+
const {getAllByLabelText} = render(`
253+
<div>
254+
<label id="label1">Test Label</label>
255+
<ul aria-labelledby="label1">
256+
<li>Hello</li>
257+
</ul
258+
</div>
259+
`)
260+
261+
const result = getAllByLabelText('Test Label')
262+
expect(result).toHaveLength(1)
263+
expect(result[0].nodeName).toBe('UL')
264+
})
265+
266+
test('can find the correct element when there are multiple matching labels', () => {
267+
const {getByLabelText} = render(`
268+
<label>
269+
Test Label
270+
<input />
271+
</label>
272+
<label>
273+
Test Label
274+
<textarea></textarea>
275+
</label>
276+
`)
277+
278+
const result = getByLabelText('Test Label', {selector: 'input'})
279+
expect(result.nodeName).toBe('INPUT')
280+
})
281+
215282
test('get can get form controls by placeholder', () => {
216283
const {getByPlaceholderText} = render(`
217284
<input id="username-id" placeholder="username" />,

src/queries/label-text.js

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,10 @@ function queryAllByLabelText(
4646
normalizer: matchNormalizer,
4747
})
4848
const labelledElements = labels
49-
.map(label => {
49+
.reduce((matchedElements, label) => {
50+
const elementsForLabel = []
5051
if (label.control) {
51-
return label.control
52+
elementsForLabel.push(label.control)
5253
}
5354
/* istanbul ignore if */
5455
if (label.getAttribute('for')) {
@@ -57,21 +58,27 @@ function queryAllByLabelText(
5758
// <label for="someId">text</label><input id="someId" />
5859

5960
// .control support has landed in jsdom (https://github.com/jsdom/jsdom/issues/2175)
60-
return container.querySelector(`[id="${label.getAttribute('for')}"]`)
61+
elementsForLabel.push(
62+
container.querySelector(`[id="${label.getAttribute('for')}"]`),
63+
)
6164
}
6265
if (label.getAttribute('id')) {
6366
// <label id="someId">text</label><input aria-labelledby="someId" />
64-
return container.querySelector(
65-
`[aria-labelledby~="${label.getAttribute('id')}"]`,
66-
)
67+
container
68+
.querySelectorAll(`[aria-labelledby~="${label.getAttribute('id')}"]`)
69+
.forEach(element => elementsForLabel.push(element))
6770
}
6871
if (label.childNodes.length) {
6972
// <label>text: <input /></label>
70-
return label.querySelector(selector)
73+
const formControlSelector =
74+
'button, input, meter, output, progress, select, textarea'
75+
label
76+
.querySelectorAll(formControlSelector)
77+
.forEach(element => elementsForLabel.push(element))
7178
}
72-
return null
73-
})
74-
.filter(label => label !== null)
79+
return matchedElements.concat(elementsForLabel)
80+
}, [])
81+
.filter(element => element !== null)
7582
.concat(queryAllByAttribute('aria-label', container, text, {exact}))
7683

7784
const possibleAriaLabelElements = queryAllByText(container, text, {
@@ -95,7 +102,9 @@ function queryAllByLabelText(
95102
[],
96103
)
97104

98-
return Array.from(new Set([...labelledElements, ...ariaLabelledElements]))
105+
return Array.from(
106+
new Set([...labelledElements, ...ariaLabelledElements]),
107+
).filter(element => element.matches(selector))
99108
}
100109

101110
// the getAll* query would normally look like this:

0 commit comments

Comments
 (0)