Skip to content

Get element by label input value and by labels concat values #607

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

Closed
Closed
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
12 changes: 12 additions & 0 deletions src/__tests__/element-queries.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ test('can get form controls by label text', () => {
<label id="fifth-label-two">5th two</label>
<input aria-labelledby="fifth-label-one fifth-label-two" id="fifth-id" />
</div>
<div>
<label id="sixth-label-one">6th one</label>
<label id="sixth-label-two">6th two</label>
<input id="sixth-label-three" type="text" value="6th three"/>
<input aria-labelledby="sixth-label-one sixth-label-two sixth-label-three" id="sixth-id" />
</div>
</div>
`)
expect(getByLabelText('1st').id).toBe('first-id')
Expand All @@ -176,6 +182,12 @@ test('can get form controls by label text', () => {
expect(getByLabelText('4th').id).toBe('fourth.id')
expect(getByLabelText('5th one').id).toBe('fifth-id')
expect(getByLabelText('5th two').id).toBe('fifth-id')
expect(getByLabelText('6th one').id).toBe('sixth-id')
expect(getByLabelText('6th two').id).toBe('sixth-id')
expect(getByLabelText('6th three').id).toBe('sixth-id')
expect(getByLabelText('6th one 6th two 6th three', {concat: true}).id).toBe(
'sixth-id',
)
})

test('can get elements labelled with aria-labelledby attribute', () => {
Expand Down
6 changes: 4 additions & 2 deletions src/matches.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ function fuzzyMatches(textToMatch, node, matcher, normalizer) {
}
}

function matches(textToMatch, node, matcher, normalizer) {
function matches(textToMatch, node, matcher, normalizer, concat = false) {
if (typeof textToMatch !== 'string') {
return false
}

const normalizedText = normalizer(textToMatch)
if (typeof matcher === 'string') {
return normalizedText === matcher
return concat
? matcher.toLowerCase().includes(normalizedText.toLowerCase())
: normalizedText === matcher
} else if (typeof matcher === 'function') {
return matcher(normalizedText, node)
} else {
Expand Down
36 changes: 29 additions & 7 deletions src/queries/label-text.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,41 +15,63 @@ import {queryAllByText} from './text'
function queryAllLabelsByText(
container,
text,
{exact = true, trim, collapseWhitespace, normalizer} = {},
{exact = true, trim, collapseWhitespace, normalizer, concat = false} = {},
) {
const matcher = exact ? matches : fuzzyMatches
const matchNormalizer = makeNormalizer({collapseWhitespace, trim, normalizer})
return Array.from(container.querySelectorAll('label')).filter(label => {
let textToMatch = label.textContent
return Array.from(container.querySelectorAll('label,input')).filter(node => {
let textToMatch =
node.tagName.toLowerCase() === 'label'
? node.textContent
: node.value || null

// The children of a textarea are part of `textContent` as well. We
// need to remove them from the string so we can match it afterwards.
Array.from(label.querySelectorAll('textarea')).forEach(textarea => {
Array.from(node.querySelectorAll('textarea')).forEach(textarea => {
textToMatch = textToMatch.replace(textarea.value, '')
})

// The children of a select are also part of `textContent`, so we
// need also to remove their text.
Array.from(label.querySelectorAll('select')).forEach(select => {
Array.from(node.querySelectorAll('select')).forEach(select => {
textToMatch = textToMatch.replace(select.textContent, '')
})

return matcher(textToMatch, label, text, matchNormalizer)
return matcher(textToMatch, node, text, matchNormalizer, concat)
})
}

function queryAllByLabelText(
container,
text,
{selector = '*', exact = true, collapseWhitespace, trim, normalizer} = {},
{
selector = '*',
exact = true,
collapseWhitespace,
trim,
normalizer,
concat = false,
} = {},
) {
checkContainerType(container)

const matchNormalizer = makeNormalizer({collapseWhitespace, trim, normalizer})
const labels = queryAllLabelsByText(container, text, {
exact,
normalizer: matchNormalizer,
concat,
})
if (concat) {
const labelsValues = labels.map(label => label.textContent || label.value)
if (
labelsValues.reduce(
(agg, label) => agg.replace(`${label} `, ''),
`${text} `,
) !== ''
) {
return []
}
}
const labelledElements = labels
.reduce((matchedElements, label) => {
const elementsForLabel = []
Expand Down
1 change: 1 addition & 0 deletions types/matches.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface MatcherOptions {
normalizer?: NormalizerFn
/** suppress suggestions for a specific query */
suggest?: boolean
concat?: boolean
}

export type Match = (
Expand Down