,
+ `)
+ expect(queryAllByTestId('nope')).toHaveLength(0)
+ expect(queryAllByAltText('nope')).toHaveLength(0)
+ expect(queryAllByLabelText('nope')).toHaveLength(0)
+ expect(queryAllByPlaceholderText('nope')).toHaveLength(0)
+ expect(queryAllByText('nope')).toHaveLength(0)
+})
+
test('using jest helpers to assert element states', () => {
const {queryByTestId} = render(`2`)
diff --git a/src/queries.js b/src/queries.js
index a846754e..49d14223 100644
--- a/src/queries.js
+++ b/src/queries.js
@@ -10,97 +10,135 @@ function debugDOM(htmlElement) {
// The queries here should only be things that are accessible to both users who are using a screen reader
// and those who are not using a screen reader (with the exception of the data-testid attribute query).
-function queryLabelByText(container, text) {
- return (
- Array.from(container.querySelectorAll('label')).find(label =>
- matches(label.textContent, label, text),
- ) || null
+function firstResultOrNull(queryFunction, ...args) {
+ const result = queryFunction(...args)
+ if (result.length === 0) return null
+ return result[0]
+}
+
+function queryAllLabelsByText(container, text) {
+ return Array.from(container.querySelectorAll('label')).filter(label =>
+ matches(label.textContent, label, text),
)
}
-function queryByLabelText(container, text, {selector = '*'} = {}) {
- const label = queryLabelByText(container, text)
- if (!label) {
- return queryByAttribute('aria-label', container, text)
- }
- /* istanbul ignore if */
- if (label.control) {
- // appears to be unsupported in jsdom: https://github.com/jsdom/jsdom/issues/2175
- // but this would be the proper way to do things
- return label.control
- } else if (label.getAttribute('for')) {
- // 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
- //
- return container.querySelector(`[id="${label.getAttribute('for')}"]`)
- } else if (label.getAttribute('id')) {
- //
- return container.querySelector(
- `[aria-labelledby="${label.getAttribute('id')}"]`,
- )
- } else if (label.childNodes.length) {
- //
- return label.querySelector(selector)
- } else {
- return null
- }
+function queryAllByLabelText(container, text, {selector = '*'} = {}) {
+ const labels = queryAllLabelsByText(container, text)
+ const labelledElements = labels
+ .map(label => {
+ /* istanbul ignore if */
+ if (label.control) {
+ // appears to be unsupported in jsdom: https://github.com/jsdom/jsdom/issues/2175
+ // but this would be the proper way to do things
+ return label.control
+ } else if (label.getAttribute('for')) {
+ // 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
+ //
+ return container.querySelector(`[id="${label.getAttribute('for')}"]`)
+ } else if (label.getAttribute('id')) {
+ //
+ return container.querySelector(
+ `[aria-labelledby="${label.getAttribute('id')}"]`,
+ )
+ } else if (label.childNodes.length) {
+ //
+ return label.querySelector(selector)
+ } else {
+ return null
+ }
+ })
+ .filter(label => label !== null)
+ .concat(queryAllByAttribute('aria-label', container, text))
+
+ return labelledElements
+}
+
+function queryByLabelText(container, text, opts) {
+ return firstResultOrNull(queryAllByLabelText, container, text, opts)
+}
+
+function queryAllByText(container, text, {selector = '*'} = {}) {
+ return Array.from(container.querySelectorAll(selector)).filter(node =>
+ matches(getNodeText(node), node, text),
+ )
+}
+
+function queryByText(container, text, opts) {
+ return firstResultOrNull(queryAllByText, container, text, opts)
}
-function queryByText(container, text, {selector = '*'} = {}) {
- return (
- Array.from(container.querySelectorAll(selector)).find(node =>
- matches(getNodeText(node), node, text),
- ) || null
+// this is just a utility and not an exposed query.
+// There are no plans to expose this.
+function queryAllByAttribute(attribute, container, text) {
+ return Array.from(container.querySelectorAll(`[${attribute}]`)).filter(node =>
+ matches(node.getAttribute(attribute), node, text),
)
}
// this is just a utility and not an exposed query.
// There are no plans to expose this.
function queryByAttribute(attribute, container, text) {
- return (
- Array.from(container.querySelectorAll(`[${attribute}]`)).find(node =>
- matches(node.getAttribute(attribute), node, text),
- ) || null
- )
+ return firstResultOrNull(queryAllByAttribute, attribute, container, text)
}
const queryByPlaceholderText = queryByAttribute.bind(null, 'placeholder')
+const queryAllByPlaceholderText = queryAllByAttribute.bind(null, 'placeholder')
const queryByTestId = queryByAttribute.bind(null, 'data-testid')
+const queryAllByTestId = queryAllByAttribute.bind(null, 'data-testid')
+
+function queryAllByAltText(container, alt) {
+ return Array.from(container.querySelectorAll('img,input,area')).filter(node =>
+ matches(node.getAttribute('alt'), node, alt),
+ )
+}
+
+function queryByAltText(container, alt) {
+ return firstResultOrNull(queryAllByAltText, container, alt)
+}
// getters
// the reason we're not dynamically generating these functions that look so similar:
// 1. The error messages are specific to each one and depend on arguments
// 2. The stack trace will look better because it'll have a helpful method name.
-function getByTestId(container, id, ...rest) {
- const el = queryByTestId(container, id, ...rest)
- if (!el) {
+function getAllByTestId(container, id, ...rest) {
+ const els = queryAllByTestId(container, id, ...rest)
+ if (!els.length) {
throw new Error(
`Unable to find an element by: [data-testid="${id}"] \n\n${debugDOM(
container,
)}`,
)
}
- return el
+ return els
+}
+
+function getByTestId(...args) {
+ return firstResultOrNull(getAllByTestId, ...args)
}
-function getByPlaceholderText(container, text, ...rest) {
- const el = queryByPlaceholderText(container, text, ...rest)
- if (!el) {
+function getAllByPlaceholderText(container, text, ...rest) {
+ const els = queryAllByPlaceholderText(container, text, ...rest)
+ if (!els.length) {
throw new Error(
`Unable to find an element with the placeholder text of: ${text} \n\n${debugDOM(
container,
)}`,
)
}
- return el
+ return els
}
-function getByLabelText(container, text, ...rest) {
- const el = queryByLabelText(container, text, ...rest)
- if (!el) {
- const label = queryLabelByText(container, text)
- if (label) {
+function getByPlaceholderText(...args) {
+ return firstResultOrNull(getAllByPlaceholderText, ...args)
+}
+
+function getAllByLabelText(container, text, ...rest) {
+ const els = queryAllByLabelText(container, text, ...rest)
+ if (!els.length) {
+ const labels = queryAllLabelsByText(container, text)
+ if (labels.length) {
throw new Error(
`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. \n\n${debugDOM(
container,
@@ -114,52 +152,66 @@ function getByLabelText(container, text, ...rest) {
)
}
}
- return el
+ return els
+}
+
+function getByLabelText(...args) {
+ return firstResultOrNull(getAllByLabelText, ...args)
}
-function getByText(container, text, ...rest) {
- const el = queryByText(container, text, ...rest)
- if (!el) {
+function getAllByText(container, text, ...rest) {
+ const els = queryAllByText(container, text, ...rest)
+ if (!els.length) {
throw new Error(
`Unable to find an element with the text: ${text}. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. \n\n${debugDOM(
container,
)}`,
)
}
- return el
+ return els
}
-function queryByAltText(container, alt) {
- return (
- Array.from(container.querySelectorAll('img,input,area')).find(node =>
- matches(node.getAttribute('alt'), node, alt),
- ) || null
- )
+function getByText(...args) {
+ return firstResultOrNull(getAllByText, ...args)
}
-function getByAltText(container, alt) {
- const el = queryByAltText(container, alt)
- if (!el) {
+function getAllByAltText(container, alt) {
+ const els = queryAllByAltText(container, alt)
+ if (!els.length) {
throw new Error(
`Unable to find an element with the alt text: ${alt} \n\n${debugDOM(
container,
)}`,
)
}
- return el
+ return els
+}
+
+function getByAltText(...args) {
+ return firstResultOrNull(getAllByAltText, ...args)
}
export {
queryByPlaceholderText,
+ queryAllByPlaceholderText,
getByPlaceholderText,
+ getAllByPlaceholderText,
queryByText,
+ queryAllByText,
getByText,
+ getAllByText,
queryByLabelText,
+ queryAllByLabelText,
getByLabelText,
+ getAllByLabelText,
queryByAltText,
+ queryAllByAltText,
getByAltText,
+ getAllByAltText,
queryByTestId,
+ queryAllByTestId,
getByTestId,
+ getAllByTestId,
}
/* eslint complexity:["error", 14] */