Repository: kentcdodds/dom-testing-library
Pull request #28 (2018-04-22 18:18:56)

Add queryAll/getAll methods
<!--
Thanks for your interest in the project. Bugs filed and PRs submitted are appreciated!

Please make sure that you are familiar with and follow the Code of Conduct for
this project (found in the CODE_OF_CONDUCT.md file).

Also, please make sure you're familiar with and follow the instructions in the
contributing guidelines (found in the CONTRIBUTING.md file).

If you're new to contributing to open source projects, you might find this free
video course helpful: http://kcd.im/pull-request

Please fill out the information below to expedite the review and (hopefully)
merge of your pull request!
-->

<!-- What changes are being made? (What feature/bug is being fixed here?) -->

**What**:

Adds "all" methods for:
- byAltText
- byTestId

<!-- Why are these changes necessary? -->

**Why**:

#27 

<!-- How were these changes implemented? -->

**How**:

<!-- Have you done all of these things?  -->

**Checklist**:

<!-- add "N/A" to the end of each line that's irrelevant to your changes -->

<!-- to check an item, place an "x" in the box like so: "- [x] Documentation" -->

* [x] Documentation
* [x] Tests
* [x] Ready to be merged <!-- In your opinion, is this ready to be merged as soon as it's reviewed? -->
* [x] Added myself to contributors table <!-- this is optional, see the contributing guidelines for instructions -->

<!-- feel free to add additional comments -->

---

**Sidenote**

If these `getAll` methods are added, the `queryBy` methods seem kind of redundant - instead of checking if `queryBy` returns `null`, you can check if `getAll` is `[]`.


alexkrolick: > I'm thinking that it would be good to actually have an *All* version for every query/get method for consistency.

Yep, just wanted to start with a few cases for review.

> And I'm thinking that we could probably have the *All* methods be the base and the existing methods just use those and return the first element to simplify the implementation.

Yeah. Would the `queryBy` methods continue to be supported (ie `if (result.length === 0) return null`) or phased out?

> Also, we'll want to make sure to document the existence of the *All* methods.

Yep.

kentcdodds: Yes, let's do this with no breaking changes.

Yes, please maintain 100% code coverage 👍
alexkrolick: OK, 🍏 

- Added `queryAll` methods for everything
- Reimplemented `query` APIs using `queryAll`
- Aliased `getAll` to `queryAll` (they are the same)
- Added tests for the array methods; most of the code is actually executed in tests for the `get` methods.
alexkrolick: ✅ Changed `getAll` to throw the same errors as the `get` methods

Patch:
diff --git a/.all-contributorsrc b/.all-contributorsrc
index f382cca..5d5a66b 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -191,6 +191,15 @@
         "code",
         "test"
       ]
+    },
+    {
+      "login": "alexkrolick",
+      "name": "Alex Krolick",
+      "avatar_url": "https://avatars3.githubusercontent.com/u/1571667?v=4",
+      "profile": "https://alexkrolick.com",
+      "contributions": [
+        "code"
+      ]
     }
   ]
 }
diff --git a/README.md b/README.md
index 8672f01..eb67260 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@
 [![downloads][downloads-badge]][npmtrends]
 [![MIT License][license-badge]][license]
 
-[![All Contributors](https://img.shields.io/badge/all_contributors-18-orange.svg?style=flat-square)](#contributors)
+[![All Contributors](https://img.shields.io/badge/all_contributors-19-orange.svg?style=flat-square)](#contributors)
 [![PRs Welcome][prs-badge]][prs]
 [![Code of Conduct][coc-badge]][coc]
 
@@ -83,6 +83,7 @@ when a real user uses it.
   * [Using other assertion libraries](#using-other-assertion-libraries)
 * [`TextMatch`](#textmatch)
 * [`query` APIs](#query-apis)
+* [`queryAll` and `getAll` APIs](#queryall-and-getall-apis)
 * [`bindElementToQueries`](#bindelementtoqueries)
 * [Debugging](#debugging)
   * [`prettyDOM`](#prettydom)
@@ -106,6 +107,8 @@ npm install --save-dev dom-testing-library
 
 ## Usage
 
+Note: each of the `get` APIs below have a matching [`getAll`](#queryall-and-getall-apis) API that returns all elements instead of just the first one, and [`query`](#query-apis)/[`getAll`](#queryall-and-getall-apis) that return `null`/`[]` instead of throwing an error.
+
 ```javascript
 // src/__tests__/example.js
 // query utilities:
@@ -489,6 +492,16 @@ expect(submitButton).toBeNull() // it doesn't exist
 expect(submitButton).not.toBeInTheDOM()
 ```
 
+## `queryAll` and `getAll` APIs
+
+Each of the `query` APIs have a corresponsing `queryAll` version that always returns an Array of matching nodes. `getAll` is the same but throws when the array has a length of 0.
+
+```javascript
+const submitButtons = queryAllByText(container, 'submit')
+expect(submitButtons).toHaveLength(3) // expect 3 elements
+expect(submitButtons[0]).toBeInTheDOM()
+```
+
 ## `bindElementToQueries`
 
 `bindElementToQueries` takes a DOM element and binds it to the raw query functions, allowing them
@@ -711,7 +724,7 @@ Thanks goes to these people ([emoji key][emojis]):
 | [<img src="https://avatars.githubusercontent.com/u/1500684?v=3" width="100px;"/><br /><sub><b>Kent C. Dodds</b></sub>](https://kentcdodds.com)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=kentcdodds "Code") [📖](https://github.com/kentcdodds/dom-testing-library/commits?author=kentcdodds "Documentation") [🚇](#infra-kentcdodds "Infrastructure (Hosting, Build-Tools, etc)") [⚠️](https://github.com/kentcdodds/dom-testing-library/commits?author=kentcdodds "Tests") | [<img src="https://avatars1.githubusercontent.com/u/2430381?v=4" width="100px;"/><br /><sub><b>Ryan Castner</b></sub>](http://audiolion.github.io)<br />[📖](https://github.com/kentcdodds/dom-testing-library/commits?author=audiolion "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/8008023?v=4" width="100px;"/><br /><sub><b>Daniel Sandiego</b></sub>](https://www.dnlsandiego.com)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=dnlsandiego "Code") | [<img src="https://avatars2.githubusercontent.com/u/12592677?v=4" width="100px;"/><br /><sub><b>Paweł Mikołajczyk</b></sub>](https://github.com/Miklet)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=Miklet "Code") | [<img src="https://avatars3.githubusercontent.com/u/464978?v=4" width="100px;"/><br /><sub><b>Alejandro Ñáñez Ortiz</b></sub>](http://co.linkedin.com/in/alejandronanez/)<br />[📖](https://github.com/kentcdodds/dom-testing-library/commits?author=alejandronanez "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/1402095?v=4" width="100px;"/><br /><sub><b>Matt Parrish</b></sub>](https://github.com/pbomb)<br />[🐛](https://github.com/kentcdodds/dom-testing-library/issues?q=author%3Apbomb "Bug reports") [💻](https://github.com/kentcdodds/dom-testing-library/commits?author=pbomb "Code") [📖](https://github.com/kentcdodds/dom-testing-library/commits?author=pbomb "Documentation") [⚠️](https://github.com/kentcdodds/dom-testing-library/commits?author=pbomb "Tests") | [<img src="https://avatars1.githubusercontent.com/u/1288694?v=4" width="100px;"/><br /><sub><b>Justin Hall</b></sub>](https://github.com/wKovacs64)<br />[📦](#platform-wKovacs64 "Packaging/porting to new platform") |
 | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
 | [<img src="https://avatars1.githubusercontent.com/u/1241511?s=460&v=4" width="100px;"/><br /><sub><b>Anto Aravinth</b></sub>](https://github.com/antoaravinth)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=antoaravinth "Code") [⚠️](https://github.com/kentcdodds/dom-testing-library/commits?author=antoaravinth "Tests") [📖](https://github.com/kentcdodds/dom-testing-library/commits?author=antoaravinth "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/3462296?v=4" width="100px;"/><br /><sub><b>Jonah Moses</b></sub>](https://github.com/JonahMoses)<br />[📖](https://github.com/kentcdodds/dom-testing-library/commits?author=JonahMoses "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/4002543?v=4" width="100px;"/><br /><sub><b>Łukasz Gandecki</b></sub>](http://team.thebrain.pro)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=lgandecki "Code") [⚠️](https://github.com/kentcdodds/dom-testing-library/commits?author=lgandecki "Tests") [📖](https://github.com/kentcdodds/dom-testing-library/commits?author=lgandecki "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/498274?v=4" width="100px;"/><br /><sub><b>Ivan Babak</b></sub>](https://sompylasar.github.io)<br />[🐛](https://github.com/kentcdodds/dom-testing-library/issues?q=author%3Asompylasar "Bug reports") [🤔](#ideas-sompylasar "Ideas, Planning, & Feedback") [💻](https://github.com/kentcdodds/dom-testing-library/commits?author=sompylasar "Code") [📖](https://github.com/kentcdodds/dom-testing-library/commits?author=sompylasar "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/4439618?v=4" width="100px;"/><br /><sub><b>Jesse Day</b></sub>](https://github.com/jday3)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=jday3 "Code") | [<img src="https://avatars0.githubusercontent.com/u/15199?v=4" width="100px;"/><br /><sub><b>Ernesto García</b></sub>](http://gnapse.github.io)<br />[💬](#question-gnapse "Answering Questions") [💻](https://github.com/kentcdodds/dom-testing-library/commits?author=gnapse "Code") [📖](https://github.com/kentcdodds/dom-testing-library/commits?author=gnapse "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/2747424?v=4" width="100px;"/><br /><sub><b>Josef Maxx Blake</b></sub>](http://jomaxx.com)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=jomaxx "Code") [📖](https://github.com/kentcdodds/dom-testing-library/commits?author=jomaxx "Documentation") [⚠️](https://github.com/kentcdodds/dom-testing-library/commits?author=jomaxx "Tests") |
-| [<img src="https://avatars3.githubusercontent.com/u/725236?v=4" width="100px;"/><br /><sub><b>Alex Cook</b></sub>](https://github.com/alecook)<br />[📖](https://github.com/kentcdodds/dom-testing-library/commits?author=alecook "Documentation") [💡](#example-alecook "Examples") | [<img src="https://avatars3.githubusercontent.com/u/10348212?v=4" width="100px;"/><br /><sub><b>Daniel Cook</b></sub>](https://github.com/dfcook)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=dfcook "Code") [📖](https://github.com/kentcdodds/dom-testing-library/commits?author=dfcook "Documentation") [⚠️](https://github.com/kentcdodds/dom-testing-library/commits?author=dfcook "Tests") | [<img src="https://avatars2.githubusercontent.com/u/21194045?s=400&v=4" width="100px;"/><br /><sub><b>Thomas Chia</b></sub>](https://github.com/thchia)<br />[🐛](https://github.com/kentcdodds/dom-testing-library/issues?q=author%3Athchia "Bug reports") [💻](https://github.com/kentcdodds/dom-testing-library/commits?author=thchia "Code") | [<img src="https://avatars1.githubusercontent.com/u/28659384?v=4" width="100px;"/><br /><sub><b>Tim Deschryver</b></sub>](https://github.com/tdeschryver)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=tdeschryver "Code") [⚠️](https://github.com/kentcdodds/dom-testing-library/commits?author=tdeschryver "Tests") |
+| [<img src="https://avatars3.githubusercontent.com/u/725236?v=4" width="100px;"/><br /><sub><b>Alex Cook</b></sub>](https://github.com/alecook)<br />[📖](https://github.com/kentcdodds/dom-testing-library/commits?author=alecook "Documentation") [💡](#example-alecook "Examples") | [<img src="https://avatars3.githubusercontent.com/u/10348212?v=4" width="100px;"/><br /><sub><b>Daniel Cook</b></sub>](https://github.com/dfcook)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=dfcook "Code") [📖](https://github.com/kentcdodds/dom-testing-library/commits?author=dfcook "Documentation") [⚠️](https://github.com/kentcdodds/dom-testing-library/commits?author=dfcook "Tests") | [<img src="https://avatars2.githubusercontent.com/u/21194045?s=400&v=4" width="100px;"/><br /><sub><b>Thomas Chia</b></sub>](https://github.com/thchia)<br />[🐛](https://github.com/kentcdodds/dom-testing-library/issues?q=author%3Athchia "Bug reports") [💻](https://github.com/kentcdodds/dom-testing-library/commits?author=thchia "Code") | [<img src="https://avatars1.githubusercontent.com/u/28659384?v=4" width="100px;"/><br /><sub><b>Tim Deschryver</b></sub>](https://github.com/tdeschryver)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=tdeschryver "Code") [⚠️](https://github.com/kentcdodds/dom-testing-library/commits?author=tdeschryver "Tests") | [<img src="https://avatars3.githubusercontent.com/u/1571667?v=4" width="100px;"/><br /><sub><b>Alex Krolick</b></sub>](https://alexkrolick.com)<br />[💻](https://github.com/kentcdodds/dom-testing-library/commits?author=alexkrolick "Code") |
 
 <!-- ALL-CONTRIBUTORS-LIST:END -->
 
diff --git a/src/queries.js b/src/queries.js
index a846754..49d1422 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
-    // <label for="someId">text</label><input id="someId" />
-    return container.querySelector(`[id="${label.getAttribute('for')}"]`)
-  } else if (label.getAttribute('id')) {
-    // <label id="someId">text</label><input aria-labelledby="someId" />
-    return container.querySelector(
-      `[aria-labelledby="${label.getAttribute('id')}"]`,
-    )
-  } else if (label.childNodes.length) {
-    // <label>text: <input /></label>
-    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
+        // <label for="someId">text</label><input id="someId" />
+        return container.querySelector(`[id="${label.getAttribute('for')}"]`)
+      } else if (label.getAttribute('id')) {
+        // <label id="someId">text</label><input aria-labelledby="someId" />
+        return container.querySelector(
+          `[aria-labelledby="${label.getAttribute('id')}"]`,
+        )
+      } else if (label.childNodes.length) {
+        // <label>text: <input /></label>
+        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] */


Test patch:
diff --git a/src/__tests__/element-queries.js b/src/__tests__/element-queries.js
index 3060175..1cf4e91 100644
--- a/src/__tests__/element-queries.js
+++ b/src/__tests__/element-queries.js
@@ -117,6 +117,78 @@ test('get element by its alt text', () => {
   expect(getByAltText(/fin.*nem.*poster$/i).src).toBe('/finding-nemo.png')
 })
 
+test('getAll* matchers return an array', () => {
+  const {
+    getAllByAltText,
+    getAllByTestId,
+    getAllByLabelText,
+    getAllByPlaceholderText,
+    getAllByText,
+  } = render(`
+    <div>
+      <img
+        data-testid="poster"
+        alt="finding nemo poster" 
+        src="/finding-nemo.png" />
+      <img
+        data-testid="poster"
+        alt="finding dory poster" 
+        src="/finding-dory.png" />
+      <img
+        data-testid="poster"
+        alt="jumanji poster" 
+        src="/jumanji.png" />
+      <p>Where to next?</p>
+      <label for="username">User Name</label>
+      <input id="username" placeholder="Dwayne 'The Rock' Johnson" />
+    </div>,
+  `)
+  expect(getAllByAltText(/finding.*poster$/i)).toHaveLength(2)
+  expect(getAllByAltText('jumanji')).toHaveLength(1)
+  expect(getAllByTestId('poster')).toHaveLength(3)
+  expect(getAllByPlaceholderText(/The Rock/)).toHaveLength(1)
+  expect(getAllByLabelText('User Name')).toHaveLength(1)
+  expect(getAllByText('where')).toHaveLength(1)
+})
+
+test('getAll* matchers throw for 0 matches', () => {
+  const {
+    getAllByAltText,
+    getAllByTestId,
+    getAllByLabelText,
+    getAllByPlaceholderText,
+    getAllByText,
+  } = render(`
+    <div>
+      <label>No Matches Please</label>
+    </div>,
+  `)
+  expect(() => getAllByTestId('nope')).toThrow()
+  expect(() => getAllByAltText('nope')).toThrow()
+  expect(() => getAllByLabelText('nope')).toThrow()
+  expect(() => getAllByLabelText('no matches please')).toThrow()
+  expect(() => getAllByPlaceholderText('nope')).toThrow()
+  expect(() => getAllByText('nope')).toThrow()
+})
+
+test('queryAll* matchers return an array for 0 matches', () => {
+  const {
+    queryAllByAltText,
+    queryAllByTestId,
+    queryAllByLabelText,
+    queryAllByPlaceholderText,
+    queryAllByText,
+  } = render(`
+    <div>
+    </div>,
+  `)
+  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(`<span data-testid="count-value">2</span>`)