From 1bec8c353e71281dfdad9bd576d171dc9dbd1580 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 21 Nov 2023 12:09:13 +0100 Subject: [PATCH 01/12] docs: describe query variants --- website/docs/HowShouldIQuery.md | 39 ++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/website/docs/HowShouldIQuery.md b/website/docs/HowShouldIQuery.md index 55af03e7f..4df485096 100644 --- a/website/docs/HowShouldIQuery.md +++ b/website/docs/HowShouldIQuery.md @@ -3,7 +3,44 @@ id: how-should-i-query title: How Should I Query? --- -## Priority +React Native Testing Library provides various query types, allowing flexibility in finding views appropriate for your tests. At the same time, the number of queries might be confusing. This guide aims to help you pick the correct queries for your test scenarios. + +## Query parts + +Each query is composed of two parts: variant and predicate, which are separated by the `By` word in the middle of the query. + +Consider the following query: + +```ts +getByRole(); +``` + +For this query, `get` is the query variant, and `ByRole` is the predicate. + +### Query variant + +The query variant describes the return type of the query and is also an implicit assumption on the number of matching elements. + +| Variant | Return type | Assertion | Is Async? | +| `get` | `ReactTestInstance` | Exactly one matching element | No | +| `getAll` | `Array` | At least one matching element | No | +| `query` | `ReactTestInstance \| null` | Zero or one matching element | No | +| `query` | `Array` | No assertion | No | +| `find` | `Promise` | Exactly one matching element | Yes | +| `findAll` | `Promise>` | At least one matching element | Yes | + +Here are general guidelines for picking idiomatic query variants: + +1. Use `get` in the most common case when you expect a _single matching element_. Use other queries only in more specific cases. +2. Use `find` for an element not yet in the element tree, but you expect it to be there as a _result of some asynchronous action_. +3. Use `getAll` (and `findAll` for async) if you expect _more than one matching element_. +4. Use `query` variant only when element _should not exist_, in order to pass it to e.g. `not.toBeOnTheScreen()` matcher. + +Do not use `queryAll` as it does not provide any assertions on the number of matched elements. + +Using idiomatic query variants helps better express your test's intent and expectations about the number of matching elements. Using other query variants might work but could make it harder to reason about the test. + +### Query Predicate Based on the [Guiding Principles](https://testing-library.com/docs/guiding-principles), your test should resemble how users interact with your code (component, page, etc.) as much as possible. With this in mind, we recommend this order of priority: From 1ebfadc479ad486f161b42901f5a6c62a98c8ef8 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 21 Nov 2023 12:18:17 +0100 Subject: [PATCH 02/12] docs: tweaks --- website/docs/HowShouldIQuery.md | 43 +++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/website/docs/HowShouldIQuery.md b/website/docs/HowShouldIQuery.md index 4df485096..5e0c5631e 100644 --- a/website/docs/HowShouldIQuery.md +++ b/website/docs/HowShouldIQuery.md @@ -7,38 +7,41 @@ React Native Testing Library provides various query types, allowing flexibility ## Query parts -Each query is composed of two parts: variant and predicate, which are separated by the `By` word in the middle of the query. +Each query is composed of two parts: variant and predicate, which are separated by the `by` word in the middle of the query. Consider the following query: -```ts -getByRole(); +``` +getByRole() ``` For this query, `get` is the query variant, and `ByRole` is the predicate. ### Query variant -The query variant describes the return type of the query and is also an implicit assumption on the number of matching elements. +The query variant describes the return type of the query and is also an implicit assertion on the number of matching elements. -| Variant | Return type | Assertion | Is Async? | -| `get` | `ReactTestInstance` | Exactly one matching element | No | -| `getAll` | `Array` | At least one matching element | No | -| `query` | `ReactTestInstance \| null` | Zero or one matching element | No | -| `query` | `Array` | No assertion | No | -| `find` | `Promise` | Exactly one matching element | Yes | -| `findAll` | `Promise>` | At least one matching element | Yes | +| Variant | Assertion | Return type | Is Async? | +| ---------- | ----------------------------- | ------------------------------------------ | --------- | +| `get` | Exactly one matching element | `ReactTestInstance` | No | +| `getAll` | At least one matching element | `Array` | No | +| `query` | Zero or one matching element | ReactTestInstance | null | No | +| `queryAll` | No assertion | `Array` | No | +| `find` | Exactly one matching element | `Promise` | Yes | +| `findAll` | At least one matching element | `Promise>` | Yes | -Here are general guidelines for picking idiomatic query variants: +#### Idiomatic query variants -1. Use `get` in the most common case when you expect a _single matching element_. Use other queries only in more specific cases. -2. Use `find` for an element not yet in the element tree, but you expect it to be there as a _result of some asynchronous action_. -3. Use `getAll` (and `findAll` for async) if you expect _more than one matching element_. -4. Use `query` variant only when element _should not exist_, in order to pass it to e.g. `not.toBeOnTheScreen()` matcher. +Using idiomatic query variants helps better express your test's intent and expectations about the number of matching elements. Using other query variants might work but could make it harder to reason about the test. -Do not use `queryAll` as it does not provide any assertions on the number of matched elements. +Here are general guidelines for picking idiomatic query variants: -Using idiomatic query variants helps better express your test's intent and expectations about the number of matching elements. Using other query variants might work but could make it harder to reason about the test. +1. Use `get` in the most common case when you expect a **single matching element**. Use other queries only in more specific cases. +2. Use `find` when an element is not yet in the element tree, but you expect it to be there as a **result of some asynchronous action**. +3. Use `getAll` (and `findAll` for async) if you expect **more than one matching element**, e.g. in a list. +4. Use `query` variant only when element **should not exist**, in order to pass it to e.g. `not.toBeOnTheScreen()` matcher. + +Do not use `queryAll` as it does not provide any assertions on the number of matching elements. ### Query Predicate @@ -56,3 +59,7 @@ Based on the [Guiding Principles](https://testing-library.com/docs/guiding-princ - [`getByRole`](https://callstack.github.io/react-native-testing-library/docs/api-queries#byrole): This can be used to query every element that is exposed in the accessibility tree as a role, like buttons or images. 3. **Test IDs** - [`getByTestId`](https://callstack.github.io/react-native-testing-library/docs/api-queries#bytestid): The user cannot see (or hear) these, so this is only recommended for cases where you can't match by text or it doesn't make sense + +``` + +``` From e4f6df74fdd6abd80f67f4f6d45ccc03ba918188 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 21 Nov 2023 13:39:10 +0100 Subject: [PATCH 03/12] docs: tweaks --- website/docs/HowShouldIQuery.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/docs/HowShouldIQuery.md b/website/docs/HowShouldIQuery.md index 5e0c5631e..0d94b5fc5 100644 --- a/website/docs/HowShouldIQuery.md +++ b/website/docs/HowShouldIQuery.md @@ -19,7 +19,7 @@ For this query, `get` is the query variant, and `ByRole` is the predicate. ### Query variant -The query variant describes the return type of the query and is also an implicit assertion on the number of matching elements. +The query variants describe the expected number (and timing) of matching elements, so differ in their return type. | Variant | Assertion | Return type | Is Async? | | ---------- | ----------------------------- | ------------------------------------------ | --------- | @@ -30,6 +30,8 @@ The query variant describes the return type of the query and is also an implicit | `find` | Exactly one matching element | `Promise` | Yes | | `findAll` | At least one matching element | `Promise>` | Yes | +Queries work as implicit assertions on the number of matching elements and will throw an error when the assertion fails. + #### Idiomatic query variants Using idiomatic query variants helps better express your test's intent and expectations about the number of matching elements. Using other query variants might work but could make it harder to reason about the test. From 9d1b61e33b8bae46454f181a93571e0e4dbffd29 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 21 Nov 2023 14:55:51 +0100 Subject: [PATCH 04/12] docs: more content --- website/docs/HowShouldIQuery.md | 65 +++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/website/docs/HowShouldIQuery.md b/website/docs/HowShouldIQuery.md index 0d94b5fc5..99ac12f45 100644 --- a/website/docs/HowShouldIQuery.md +++ b/website/docs/HowShouldIQuery.md @@ -47,7 +47,66 @@ Do not use `queryAll` as it does not provide any assertions on the number of mat ### Query Predicate -Based on the [Guiding Principles](https://testing-library.com/docs/guiding-principles), your test should resemble how users interact with your code (component, page, etc.) as much as possible. With this in mind, we recommend this order of priority: +The query predicate describes how you decide whether to match the given element. + +| Predicate | Components | Inspected props | +| ------------------- | ----------------- | ------------------------------------------------------------------------------------------- | +| `ByRole` | all host elements | `role`, `accessibilityRole`,
optional: accessible name, accessibility state and value | +| `ByText` | `Text` | `children` (text content) | +| `ByDisplayText` | `TextInput` | `value`, `defaultValue` | +| `ByPlaceholderText` | `TextInput` | `placeholder` | +| `ByLabelText` | all host elements | `aria-label`, `aria-labelledby`,
`accessibilityLabel`, `accessibilityLabelledBy` | +| `ByHintText` | all host elements | `accessibilityHint` | +| `ByTestId` | all host elements | `testID` | + +In the list above, the `ByRole` query is by far the most powerful one, as it can be further optional requirements. Other queries are much simpler and target a +single property type. + +#### Idiomatic query predicates + +Choosing proper query predicate helps better express the test's intent and make the tests resemble how users interact with your code (components, screens, etc) as much as possible in accordance to our [Guiding Principles](https://testing-library.com/docs/guiding-principles). + +Additionally, most of the predicates promote usage of proper accessibility props, which add a semantic layer on top of otherwise homogenous element tree. + +1. The first and most versatile predicate is `ByRole` which starts with semantic role of element and can be further narrowed down with additional options. React Native has two role systems, the web/ARIA-compatible one based on [`role` prop](https://reactnative.dev/docs/accessibility#role), and the traditional one based on [`accessibilityRole` prop](https://reactnative.dev/docs/accessibility#accessibilityrole), you can use either of these. + +In most cases you (or the component library you'll using), should be responsible for setting proper role props, so that both test code and assistive technologies like screen reader can better understand your view hierarchy. Some useful roles include: + +- `alert` - important text to be presented to the user, e.g. error message +- `button` +- `checkbox` & `switch` - on/off controls +- `heading` (`header`) - header for content section, e.g. title of navigation bar +- `img` (`image`) +- `link` +- `menu` & `menuitem` +- `progressbar` +- `radiogroup` & `radio` +- `searchbox` (`search`) +- `slider` (`adjustable`) +- `summary` +- `tablist` & `tab` +- `text` - static text that cannot change +- `toolbar` - container for action buttons + +Frequently you will want to add `name` option, which will match both element's role as well as his accessible name (= element's accessibility label or text content). + +Here are a couple of examples: + +- button Start : `getByRole("button", { name: "Start" })` +- silent mode switch: `getByRole("switch", { name: "Silent Mode" })` +- whole menu: `getByRole("menu", { name: "Editing Menu" })` +- particular menu item: `getByRole("menuitem", { name: "Undo" })` +- error messages: `getByRole("alert", { name: /Not logged in/ })` +- screen header: `getByRole("header", { name: "Settings" })` + +Other useful options include: + +- accessibility state like: `checked`, `selected`, `disabled`, `busy`, `expanded` +- accessibility value: `value: { now, min, max, text }` + +However, these two types of check can frequently be better handled by corresponding Jest matchers like `toBeChecked()`, `toBeSelected()`, `toBeEnabled()`/`toBeDisabled()`, `toBeBusy()`, `toBeExpanded()`/`toBeCollapsed()`, and `toHaveAccessibilityValue()`. + +### --- Old --- 1. **Queries Accessible to Everyone** queries that reflect the experience of visual users as well as those that use assistive technology - [`getByText`](https://callstack.github.io/react-native-testing-library/docs/api-queries#bytext): This is the number 1 method a user finds any visible text on interactive and non-interactive elements. @@ -57,9 +116,9 @@ Based on the [Guiding Principles](https://testing-library.com/docs/guiding-princ - [`getByHintText`](https://callstack.github.io/react-native-testing-library/docs/api-queries#bya11yhint-byaccessibilityhint-byhinttext): This can be used to query every element that is exposed in the accessibility tree as a hint. Make sure it also has a label set. - [`getByAccessibilityState`](https://callstack.github.io/react-native-testing-library/docs/api-queries#bya11ystate-byaccessibilitystate): This can be used to query every element that is exposed in the accessibility tree as a state of an interactive element, like a checkbox. - [`getByAccessibilityValue`](https://callstack.github.io/react-native-testing-library/docs/api-queries#bya11value-byaccessibilityvalue): This can be used to query every element that is exposed in the accessibility tree as a value on a range, like a slider. -2. **Queries Users Can Infer** +1. **Queries Users Can Infer** - [`getByRole`](https://callstack.github.io/react-native-testing-library/docs/api-queries#byrole): This can be used to query every element that is exposed in the accessibility tree as a role, like buttons or images. -3. **Test IDs** +1. **Test IDs** - [`getByTestId`](https://callstack.github.io/react-native-testing-library/docs/api-queries#bytestid): The user cannot see (or hear) these, so this is only recommended for cases where you can't match by text or it doesn't make sense ``` From 424b03726ef1db56d14c77858c8df72274383718 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 21 Nov 2023 18:25:19 +0100 Subject: [PATCH 05/12] docs: tweaks --- .vscode/settings.json | 3 + website/docs/HowShouldIQuery.md | 129 ++++++++++++++++++-------------- 2 files changed, 77 insertions(+), 55 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..efc4e3a65 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cSpell.words": ["RNTL"] +} diff --git a/website/docs/HowShouldIQuery.md b/website/docs/HowShouldIQuery.md index 99ac12f45..7dd34a08c 100644 --- a/website/docs/HowShouldIQuery.md +++ b/website/docs/HowShouldIQuery.md @@ -5,7 +5,7 @@ title: How Should I Query? React Native Testing Library provides various query types, allowing flexibility in finding views appropriate for your tests. At the same time, the number of queries might be confusing. This guide aims to help you pick the correct queries for your test scenarios. -## Query parts +# Query parts Each query is composed of two parts: variant and predicate, which are separated by the `by` word in the middle of the query. @@ -15,67 +15,68 @@ Consider the following query: getByRole() ``` -For this query, `get` is the query variant, and `ByRole` is the predicate. +For this query, `getBy*` is the query variant, and `*ByRole` is the predicate. -### Query variant +## Query variant The query variants describe the expected number (and timing) of matching elements, so differ in their return type. -| Variant | Assertion | Return type | Is Async? | -| ---------- | ----------------------------- | ------------------------------------------ | --------- | -| `get` | Exactly one matching element | `ReactTestInstance` | No | -| `getAll` | At least one matching element | `Array` | No | -| `query` | Zero or one matching element | ReactTestInstance | null | No | -| `queryAll` | No assertion | `Array` | No | -| `find` | Exactly one matching element | `Promise` | Yes | -| `findAll` | At least one matching element | `Promise>` | Yes | +| Variant | Assertion | Return type | Is Async? | +| ------------- | ----------------------------- | ------------------------------------------ | --------- | +| `getBy*` | Exactly one matching element | `ReactTestInstance` | No | +| `getAllBy*` | At least one matching element | `Array` | No | +| `queryBy*` | Zero or one matching element | ReactTestInstance | null | No | +| `queryAllBy*` | No assertion | `Array` | No | +| `findBy*` | Exactly one matching element | `Promise` | Yes | +| `findAllBy*` | At least one matching element | `Promise>` | Yes | Queries work as implicit assertions on the number of matching elements and will throw an error when the assertion fails. -#### Idiomatic query variants +### Idiomatic query variants Using idiomatic query variants helps better express your test's intent and expectations about the number of matching elements. Using other query variants might work but could make it harder to reason about the test. Here are general guidelines for picking idiomatic query variants: -1. Use `get` in the most common case when you expect a **single matching element**. Use other queries only in more specific cases. -2. Use `find` when an element is not yet in the element tree, but you expect it to be there as a **result of some asynchronous action**. -3. Use `getAll` (and `findAll` for async) if you expect **more than one matching element**, e.g. in a list. -4. Use `query` variant only when element **should not exist**, in order to pass it to e.g. `not.toBeOnTheScreen()` matcher. +1. Use `getBy*` in the most common case when you expect a **single matching element**. Use other queries only in more specific cases. +2. Use `findBy*` when an element is not yet in the element tree, but you expect it to be there as a **result of some asynchronous action**. +3. Use `getAllBy*` (and `findAllBy*` for async) if you expect **more than one matching element**, e.g. in a list. +4. Use `queryBy*` variant only when element **should not exist**, in order to pass it to e.g. [`not.toBeOnTheScreen()`](jest-matchers#tobeonthescreen) matcher. -Do not use `queryAll` as it does not provide any assertions on the number of matching elements. +Do not use `queryAllBy*` as it does not provide any assertions on the number of matching elements. -### Query Predicate +## Query predicate The query predicate describes how you decide whether to match the given element. -| Predicate | Components | Inspected props | -| ------------------- | ----------------- | ------------------------------------------------------------------------------------------- | -| `ByRole` | all host elements | `role`, `accessibilityRole`,
optional: accessible name, accessibility state and value | -| `ByText` | `Text` | `children` (text content) | -| `ByDisplayText` | `TextInput` | `value`, `defaultValue` | -| `ByPlaceholderText` | `TextInput` | `placeholder` | -| `ByLabelText` | all host elements | `aria-label`, `aria-labelledby`,
`accessibilityLabel`, `accessibilityLabelledBy` | -| `ByHintText` | all host elements | `accessibilityHint` | -| `ByTestId` | all host elements | `testID` | +| Predicate | Supported elements | Inspected props | +| ----------------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------- | +| [`*ByRole`](api-queries#byrole) | all host elements | `role`, `accessibilityRole`,
optional: accessible name, accessibility state and value | +| [`*ByLabelText`](api-queries#bylabeltext) | all host elements | `aria-label`, `aria-labelledby`,
`accessibilityLabel`, `accessibilityLabelledBy` | +| [`*ByDisplayValue`](api-queries#bydisplayvalue) | [`TextInput`](https://reactnative.dev/docs/textinput) | `value`, `defaultValue` | +| [`*ByPlaceholderText`](api-queries#byplaceholdertext) | [`TextInput`](https://reactnative.dev/docs/textinput) | `placeholder` | +| [`*ByText`](api-queries#bytext) | [`Text`](https://reactnative.dev/docs/text) | `children` (text content) | +| [`*ByHintText`](api-queries#byhinttext) | all host elements | `accessibilityHint` | +| [`*ByTestId`](api-queries#bytestid) | all host elements | `testID` | -In the list above, the `ByRole` query is by far the most powerful one, as it can be further optional requirements. Other queries are much simpler and target a -single property type. +### Idiomatic query predicates -#### Idiomatic query predicates +Choosing the proper query predicate helps better express the test's intent and make the tests resemble how users interact with your code (components, screens, etc.) as much as possible following our [Guiding Principles](https://testing-library.com/docs/guiding-principles). -Choosing proper query predicate helps better express the test's intent and make the tests resemble how users interact with your code (components, screens, etc) as much as possible in accordance to our [Guiding Principles](https://testing-library.com/docs/guiding-principles). +Additionally, most predicates promote the usage of proper accessibility props, which add a semantic layer on top of an element tree composed primarily of [`View`](https://reactnative.dev/docs/view) elements. -Additionally, most of the predicates promote usage of proper accessibility props, which add a semantic layer on top of otherwise homogenous element tree. +### 1. By Role query -1. The first and most versatile predicate is `ByRole` which starts with semantic role of element and can be further narrowed down with additional options. React Native has two role systems, the web/ARIA-compatible one based on [`role` prop](https://reactnative.dev/docs/accessibility#role), and the traditional one based on [`accessibilityRole` prop](https://reactnative.dev/docs/accessibility#accessibilityrole), you can use either of these. +The first and most versatile predicate is [`*ByRole`](api-queries#byrole), which starts with the semantic role of the element and can be further narrowed down with additional options. React Native has two role systems, the web/ARIA-compatible one based on [`role`](https://reactnative.dev/docs/accessibility#role) prop and the traditional one based on [`accessibilityRole`](https://reactnative.dev/docs/accessibility#accessibilityrole) prop, you can use either of these. -In most cases you (or the component library you'll using), should be responsible for setting proper role props, so that both test code and assistive technologies like screen reader can better understand your view hierarchy. Some useful roles include: +In most cases, you need to set accessibility roles explicitly (or your component library can set some of them for you). These roles allow both assistive technologies like screen reader and testing code to understand your view hierarchy better. -- `alert` - important text to be presented to the user, e.g. error message +Some frequently used roles include: + +- `alert` - important text to be presented to the user, e.g., error message - `button` - `checkbox` & `switch` - on/off controls -- `heading` (`header`) - header for content section, e.g. title of navigation bar +- `heading` (`header`) - header for content section, e.g., the title of navigation bar - `img` (`image`) - `link` - `menu` & `menuitem` @@ -88,7 +89,9 @@ In most cases you (or the component library you'll using), should be responsible - `text` - static text that cannot change - `toolbar` - container for action buttons -Frequently you will want to add `name` option, which will match both element's role as well as his accessible name (= element's accessibility label or text content). +#### Name option + +Frequently, you will want to add the [`name`](api-queries#byrole-options) option, which will match both the element's role and its accessible name (= element's accessibility label or text content). Here are a couple of examples: @@ -99,28 +102,44 @@ Here are a couple of examples: - error messages: `getByRole("alert", { name: /Not logged in/ })` - screen header: `getByRole("header", { name: "Settings" })` -Other useful options include: +#### Other options -- accessibility state like: `checked`, `selected`, `disabled`, `busy`, `expanded` +Other useful [options](api-queries#byrole-options) include: + +- accessibility states like: `disabled`, `selected`, `checked`, `busy`, `expanded` - accessibility value: `value: { now, min, max, text }` -However, these two types of check can frequently be better handled by corresponding Jest matchers like `toBeChecked()`, `toBeSelected()`, `toBeEnabled()`/`toBeDisabled()`, `toBeBusy()`, `toBeExpanded()`/`toBeCollapsed()`, and `toHaveAccessibilityValue()`. +These option types can frequently be better expressed by using the corresponding Jest matchers: -### --- Old --- +- [`toBeEnabled()` / `toBeDisabled()`](jest-matchers#tobeenabled) +- [`toBeSelected()`](jest-matchers#tobeselected) +- [`toBeChecked()` / `toBePartiallyChecked()`](jest-matchers#tobechecked) +- [`toBeExpanded()` / `toBeCollapsed()`](jest-matchers#tobeexpanded) +- [`toBeBusy()`](jest-matchers#tobebusy) +- [`toHaveAccessibilityValue()`](jest-matchers#tohaveaccessibilityvalue) -1. **Queries Accessible to Everyone** queries that reflect the experience of visual users as well as those that use assistive technology - - [`getByText`](https://callstack.github.io/react-native-testing-library/docs/api-queries#bytext): This is the number 1 method a user finds any visible text on interactive and non-interactive elements. - - [`getByDisplayValue`](https://callstack.github.io/react-native-testing-library/docs/api-queries#bydisplayvalue): Useful for the current value of a `TextInput`. - - [`getByPlaceholderText`](https://callstack.github.io/react-native-testing-library/docs/api-queries#byplaceholdertext): Only useful for targeting a placeholder of a `TextInput`. - - [`getByLabelText`](https://callstack.github.io/react-native-testing-library/docs/api-queries#bylabeltext): This can be used to query every element that is exposed in the accessibility tree as a label, usually when there's no visible text. - - [`getByHintText`](https://callstack.github.io/react-native-testing-library/docs/api-queries#bya11yhint-byaccessibilityhint-byhinttext): This can be used to query every element that is exposed in the accessibility tree as a hint. Make sure it also has a label set. - - [`getByAccessibilityState`](https://callstack.github.io/react-native-testing-library/docs/api-queries#bya11ystate-byaccessibilitystate): This can be used to query every element that is exposed in the accessibility tree as a state of an interactive element, like a checkbox. - - [`getByAccessibilityValue`](https://callstack.github.io/react-native-testing-library/docs/api-queries#bya11value-byaccessibilityvalue): This can be used to query every element that is exposed in the accessibility tree as a value on a range, like a slider. -1. **Queries Users Can Infer** - - [`getByRole`](https://callstack.github.io/react-native-testing-library/docs/api-queries#byrole): This can be used to query every element that is exposed in the accessibility tree as a role, like buttons or images. -1. **Test IDs** - - [`getByTestId`](https://callstack.github.io/react-native-testing-library/docs/api-queries#bytestid): The user cannot see (or hear) these, so this is only recommended for cases where you can't match by text or it doesn't make sense +### 2. Text input queries -``` +Querying `TextInput` elements presents a unique challenge as there is no role for `TextInput` elements. There is a `searchbox`/`search` role, which can be assigned to `TextInput`, but it should be only used in the context of search inputs, leaving other text inputs without a role to query with. -``` +Therefore, you can use the following queries to find relevant text inputs: + +1. [`*ByLabelText`](api-queries#bylabeltext) - will match the accessibility label of the element. This query will match any host elements, including `TextInput` elements. +2. [`*ByDisplayValue`](api-queries#bydisplayvalue) - will the value of `TextInput` element. This query will match only `TextInput` elements. +3. [`*ByPlaceholderText`](api-queries#byplaceholdertext) - will match the placeholder of `TextInput` element. This query will match only `TextInput` elements. + +### 3. Other accessible predicates + +These queries reflect the apps' user experience, both visual and through assistive technologies (e.g. screen reader). + +These queries include: + +- [`*ByText`](api-queries#bytext) - will match the text content of the element +- [`*ByLabelText`](api-queries#bylabeltext) - will match the accessibility label of the element +- [`*ByHintText`](api-queries#byhinttext) - will match the accessibility hint of the element + +### 4. Test ID + +As a final predicate, you can use the `testID` prop to find relevant views. Using the [`*ByTestId`](api-queries#bytestid) predicate offers the most flexibility, but at the same time, it does not represent the user experience, as users are not aware of test IDs. + +Note that using test IDs is a widespread technique in end-to-end testing due to various issues with querying views through other means **in its specific context**. Nevertheless, we still encourage you to use recommended RNTL queries as it will make your integration and component test more reliable and resilient. From 38951c614d71fc51cdcfbc0a0236a10ebca43773 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 21 Nov 2023 18:49:32 +0100 Subject: [PATCH 06/12] docs: tweaks --- .vscode/settings.json | 2 +- website/docs/HowShouldIQuery.md | 76 ++++++++++++++++----------------- website/docs/Queries.md | 68 ++++++++++++++++------------- 3 files changed, 77 insertions(+), 69 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index efc4e3a65..d2fc91bb1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "cSpell.words": ["RNTL"] + "cSpell.words": ["Pressable", "RNTL"] } diff --git a/website/docs/HowShouldIQuery.md b/website/docs/HowShouldIQuery.md index 7dd34a08c..6b7ee2f3f 100644 --- a/website/docs/HowShouldIQuery.md +++ b/website/docs/HowShouldIQuery.md @@ -3,7 +3,7 @@ id: how-should-i-query title: How Should I Query? --- -React Native Testing Library provides various query types, allowing flexibility in finding views appropriate for your tests. At the same time, the number of queries might be confusing. This guide aims to help you pick the correct queries for your test scenarios. +React Native Testing Library provides various query types, allowing great flexibility in finding views appropriate for your tests. At the same time, the number of queries might be confusing. This guide aims to help you pick the correct queries for your test scenarios. # Query parts @@ -19,16 +19,16 @@ For this query, `getBy*` is the query variant, and `*ByRole` is the predicate. ## Query variant -The query variants describe the expected number (and timing) of matching elements, so differ in their return type. +The query variants describe the expected number (and timing) of matching elements, so they differ in their return type. -| Variant | Assertion | Return type | Is Async? | -| ------------- | ----------------------------- | ------------------------------------------ | --------- | -| `getBy*` | Exactly one matching element | `ReactTestInstance` | No | -| `getAllBy*` | At least one matching element | `Array` | No | -| `queryBy*` | Zero or one matching element | ReactTestInstance | null | No | -| `queryAllBy*` | No assertion | `Array` | No | -| `findBy*` | Exactly one matching element | `Promise` | Yes | -| `findAllBy*` | At least one matching element | `Promise>` | Yes | +| Variant | Assertion | Return type | Is Async? | +| ----------------------------------------- | ----------------------------- | ------------------------------------------ | --------- | +| [`getBy*`](api-queries#get-by) | Exactly one matching element | `ReactTestInstance` | No | +| [`getAllBy*`](api-queries#get-all-by) | At least one matching element | `Array` | No | +| [`queryBy*`](api-queries#query-by) | Zero or one matching element | ReactTestInstance | null | No | +| [`queryAllBy*`](api-queries#query-all-by) | No assertion | `Array` | No | +| [`findBy*`](api-queries#find-by) | Exactly one matching element | `Promise` | Yes | +| [`findAllBy*`](api-queries#find-all-by) | At least one matching element | `Promise>` | Yes | Queries work as implicit assertions on the number of matching elements and will throw an error when the assertion fails. @@ -41,23 +41,23 @@ Here are general guidelines for picking idiomatic query variants: 1. Use `getBy*` in the most common case when you expect a **single matching element**. Use other queries only in more specific cases. 2. Use `findBy*` when an element is not yet in the element tree, but you expect it to be there as a **result of some asynchronous action**. 3. Use `getAllBy*` (and `findAllBy*` for async) if you expect **more than one matching element**, e.g. in a list. -4. Use `queryBy*` variant only when element **should not exist**, in order to pass it to e.g. [`not.toBeOnTheScreen()`](jest-matchers#tobeonthescreen) matcher. +4. Use `queryBy*` variant only when element **should not exist** to use it together with e.g. [`not.toBeOnTheScreen()`](jest-matchers#tobeonthescreen) matcher. -Do not use `queryAllBy*` as it does not provide any assertions on the number of matching elements. +Do not use `queryAllBy*` as it provides no assertions on the number of matching elements. ## Query predicate The query predicate describes how you decide whether to match the given element. -| Predicate | Supported elements | Inspected props | -| ----------------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------- | -| [`*ByRole`](api-queries#byrole) | all host elements | `role`, `accessibilityRole`,
optional: accessible name, accessibility state and value | -| [`*ByLabelText`](api-queries#bylabeltext) | all host elements | `aria-label`, `aria-labelledby`,
`accessibilityLabel`, `accessibilityLabelledBy` | -| [`*ByDisplayValue`](api-queries#bydisplayvalue) | [`TextInput`](https://reactnative.dev/docs/textinput) | `value`, `defaultValue` | -| [`*ByPlaceholderText`](api-queries#byplaceholdertext) | [`TextInput`](https://reactnative.dev/docs/textinput) | `placeholder` | -| [`*ByText`](api-queries#bytext) | [`Text`](https://reactnative.dev/docs/text) | `children` (text content) | -| [`*ByHintText`](api-queries#byhinttext) | all host elements | `accessibilityHint` | -| [`*ByTestId`](api-queries#bytestid) | all host elements | `testID` | +| Predicate | Supported elements | Inspected props | +| ------------------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------- | +| [`*ByRole`](api-queries#by-role) | all host elements | `role`, `accessibilityRole`,
optional: accessible name, accessibility state and value | +| [`*ByLabelText`](api-queries#by-label-text) | all host elements | `aria-label`, `aria-labelledby`,
`accessibilityLabel`, `accessibilityLabelledBy` | +| [`*ByDisplayValue`](api-queries#by-display-value) | [`TextInput`](https://reactnative.dev/docs/textinput) | `value`, `defaultValue` | +| [`*ByPlaceholderText`](api-queries#by-placeholder-text) | [`TextInput`](https://reactnative.dev/docs/textinput) | `placeholder` | +| [`*ByText`](api-queries#by-text) | [`Text`](https://reactnative.dev/docs/text) | `children` (text content) | +| [`*ByHintText`](api-queries#by-hint-text) | all host elements | `accessibilityHint` | +| [`*ByTestId`](api-queries#by-test-id) | all host elements | `testID` | ### Idiomatic query predicates @@ -65,11 +65,11 @@ Choosing the proper query predicate helps better express the test's intent and m Additionally, most predicates promote the usage of proper accessibility props, which add a semantic layer on top of an element tree composed primarily of [`View`](https://reactnative.dev/docs/view) elements. -### 1. By Role query +### 1. By Role query {#by-role-query} -The first and most versatile predicate is [`*ByRole`](api-queries#byrole), which starts with the semantic role of the element and can be further narrowed down with additional options. React Native has two role systems, the web/ARIA-compatible one based on [`role`](https://reactnative.dev/docs/accessibility#role) prop and the traditional one based on [`accessibilityRole`](https://reactnative.dev/docs/accessibility#accessibilityrole) prop, you can use either of these. +The first and most versatile predicate is [`*ByRole`](api-queries#by-role), which starts with the semantic role of the element and can be further narrowed down with additional options. React Native has two role systems, the web/ARIA-compatible one based on [`role`](https://reactnative.dev/docs/accessibility#role) prop and the traditional one based on [`accessibilityRole`](https://reactnative.dev/docs/accessibility#accessibilityrole) prop, you can use either of these. -In most cases, you need to set accessibility roles explicitly (or your component library can set some of them for you). These roles allow both assistive technologies like screen reader and testing code to understand your view hierarchy better. +In most cases, you need to set accessibility roles explicitly (or your component library can set some of them for you). These roles allow assistive technologies (like screen readers) and testing code to understand your view hierarchy better. Some frequently used roles include: @@ -89,9 +89,9 @@ Some frequently used roles include: - `text` - static text that cannot change - `toolbar` - container for action buttons -#### Name option +#### Name option {#by-role-query-name-option} -Frequently, you will want to add the [`name`](api-queries#byrole-options) option, which will match both the element's role and its accessible name (= element's accessibility label or text content). +Frequently, you will want to add the [`name`](api-queries#by-role-options) option, which will match both the element's role and its accessible name (= element's accessibility label or text content). Here are a couple of examples: @@ -102,9 +102,9 @@ Here are a couple of examples: - error messages: `getByRole("alert", { name: /Not logged in/ })` - screen header: `getByRole("header", { name: "Settings" })` -#### Other options +#### Other options {#by-role-query-other-options} -Other useful [options](api-queries#byrole-options) include: +Other useful [options](api-queries#by-role-options) include: - accessibility states like: `disabled`, `selected`, `checked`, `busy`, `expanded` - accessibility value: `value: { now, min, max, text }` @@ -118,28 +118,28 @@ These option types can frequently be better expressed by using the corresponding - [`toBeBusy()`](jest-matchers#tobebusy) - [`toHaveAccessibilityValue()`](jest-matchers#tohaveaccessibilityvalue) -### 2. Text input queries +### 2. Text input queries {#text-input-queries} Querying `TextInput` elements presents a unique challenge as there is no role for `TextInput` elements. There is a `searchbox`/`search` role, which can be assigned to `TextInput`, but it should be only used in the context of search inputs, leaving other text inputs without a role to query with. Therefore, you can use the following queries to find relevant text inputs: -1. [`*ByLabelText`](api-queries#bylabeltext) - will match the accessibility label of the element. This query will match any host elements, including `TextInput` elements. -2. [`*ByDisplayValue`](api-queries#bydisplayvalue) - will the value of `TextInput` element. This query will match only `TextInput` elements. -3. [`*ByPlaceholderText`](api-queries#byplaceholdertext) - will match the placeholder of `TextInput` element. This query will match only `TextInput` elements. +1. [`*ByLabelText`](api-queries#by-label-text) - will match the accessibility label of the element. This query will match any host elements, including `TextInput` elements. +2. [`*ByDisplayValue`](api-queries#by-display-value) - will the value of `TextInput` element. This query will match only `TextInput` elements. +3. [`*ByPlaceholderText`](api-queries#by-placeholder-text) - will match the placeholder of `TextInput` element. This query will match only `TextInput` elements. -### 3. Other accessible predicates +### 3. Other accessible queries {#other-accessible-queries} These queries reflect the apps' user experience, both visual and through assistive technologies (e.g. screen reader). These queries include: -- [`*ByText`](api-queries#bytext) - will match the text content of the element -- [`*ByLabelText`](api-queries#bylabeltext) - will match the accessibility label of the element -- [`*ByHintText`](api-queries#byhinttext) - will match the accessibility hint of the element +- [`*ByText`](api-queries#by-text) - will match the text content of the element +- [`*ByLabelText`](api-queries#by-label-text) - will match the accessibility label of the element +- [`*ByHintText`](api-queries#by-hint-text) - will match the accessibility hint of the element -### 4. Test ID +### 4. Test ID query {#test-id-query} -As a final predicate, you can use the `testID` prop to find relevant views. Using the [`*ByTestId`](api-queries#bytestid) predicate offers the most flexibility, but at the same time, it does not represent the user experience, as users are not aware of test IDs. +As a final predicate, you can use the `testID` prop to find relevant views. Using the [`*ByTestId`](api-queries#by-test-id) predicate offers the most flexibility, but at the same time, it does not represent the user experience, as users are not aware of test IDs. Note that using test IDs is a widespread technique in end-to-end testing due to various issues with querying views through other means **in its specific context**. Nevertheless, we still encourage you to use recommended RNTL queries as it will make your integration and component test more reliable and resilient. diff --git a/website/docs/Queries.md b/website/docs/Queries.md index f99a765cd..05da7a290 100644 --- a/website/docs/Queries.md +++ b/website/docs/Queries.md @@ -2,6 +2,7 @@ id: api-queries title: Queries --- + import TOCInline from '@theme/TOCInline'; @@ -11,27 +12,27 @@ import TOCInline from '@theme/TOCInline'; > `getBy*` queries are shown by default in the [query documentation](#queries) > below. -### `getBy*` queries +### `getBy*` queries {#get-by} `getBy*` queries return the first matching node for a query, and throw an error if no elements match or if more than one match is found. If you need to find more than one element, then use `getAllBy`. -### `getAllBy*` queries +### `getAllBy*` queries {#get-all-by} `getAllBy*` queries return an array of all matching nodes for a query, and throw an error if no elements match. -### `queryBy*` queries +### `queryBy*` queries {#query-by} `queryBy*` queries return the first matching node for a query, and return `null` if no elements match. This is useful for asserting an element that is not present. This throws if more than one match is found (use `queryAllBy` instead). -### `queryAllBy*` queries +### `queryAllBy*` queries {#query-all-by} `queryAllBy*` queries return an array of all matching nodes for a query, and return an empty array (`[]`) when no elements match. -### `findBy*` queries +### `findBy*` queries {#find-by} `findBy*` queries return a promise which resolves when a matching element is found. The promise is rejected if no elements match or if more than one match is found after a default timeout of 1000 ms. If you need to find more than one element, then use `findAllBy*`. -### `findAllBy*` queries +### `findAllBy*` queries {#find-all-by} `findAllBy*` queries return a promise which resolves to an array of matching elements. The promise is rejected if no elements match after a default timeout of 1000 ms. @@ -60,7 +61,7 @@ type ReactTestInstance = { }; ``` -### `ByRole` +### `ByRole` {#by-role} > getByRole, getAllByRole, queryByRole, queryAllByRole, findByRole, findAllByRole @@ -89,9 +90,11 @@ Returns a `ReactTestInstance` with matching `role` or `accessibilityRole` prop. :::info In order for `*ByRole` queries to match an element it needs to be considered an accessibility element: + 1. `Text`, `TextInput` and `Switch` host elements are these by default. 2. `View` host elements need an explicit [`accessible`](https://reactnative.dev/docs/accessibility#accessible) prop set to `true` 3. Some React Native composite components like `Pressable` & `TouchableOpacity` render host `View` element with `accessible` prop already set. + ::: ```jsx @@ -107,7 +110,7 @@ const element2 = screen.getByRole('button', { name: 'Hello' }); const element3 = screen.getByRole('button', { name: 'Hello', disabled: true }); ``` -#### Options {#byrole-options} +#### Options {#by-role-options} `name`: Finds an element with given `role`/`accessibilityRole` and an accessible name (equivalent to `byText` or `byLabelText` query). @@ -123,7 +126,7 @@ const element3 = screen.getByRole('button', { name: 'Hello', disabled: true }); `value`: Filter elements by their accessibility value, based on either `aria-valuemin`, `aria-valuemax`, `aria-valuenow`, `aria-valuetext` or `accessibilityValue` props. Accessiblity value conceptually consists of numeric `min`, `max` and `now` entries, as well as string `text` entry. See React Native [accessibilityValue](https://reactnative.dev/docs/accessibility#accessibilityvalue) docs to learn more about the accessibility value concept. -### `ByText` +### `ByText` {#by-text} > getByText, getAllByText, queryByText, queryAllByText, findByText, findAllByText @@ -149,7 +152,7 @@ render(); const element = screen.getByText('banana'); ``` -### `ByPlaceholderText` +### `ByPlaceholderText` {#by-placeholder-text} > getByPlaceholderText, getAllByPlaceholderText, queryByPlaceholderText, queryAllByPlaceholderText, findByPlaceholderText, findAllByPlaceholderText @@ -173,7 +176,7 @@ render(); const element = screen.getByPlaceholderText('username'); ``` -### `ByDisplayValue` +### `ByDisplayValue` {#by-display-value} > getByDisplayValue, getAllByDisplayValue, queryByDisplayValue, queryAllByDisplayValue, findByDisplayValue, findAllByDisplayValue @@ -197,7 +200,7 @@ render(); const element = screen.getByDisplayValue('username'); ``` -### `ByTestId` +### `ByTestId` {#by-test-id} > getByTestId, getAllByTestId, queryByTestId, queryAllByTestId, findByTestId, findAllByTestId @@ -225,7 +228,7 @@ const element = screen.getByTestId('unique-id'); In the spirit of [the guiding principles](https://testing-library.com/docs/guiding-principles), it is recommended to use this only after the other queries don't work for your use case. Using `testID` attributes do not resemble how your software is used and should be avoided if possible. However, they are particularly useful for end-to-end testing on real devices, e.g. using Detox and it's an encouraged technique to use there. Learn more from the blog post ["Making your UI tests resilient to change"](https://kentcdodds.com/blog/making-your-ui-tests-resilient-to-change). ::: -### `ByLabelText` +### `ByLabelText` {#by-label-text} > getByLabelText, getAllByLabelText, queryByLabelText, queryAllByLabelText, findByLabelText, findAllByLabelText @@ -241,6 +244,7 @@ getByLabelText( ``` Returns a `ReactTestInstance` with matching label: + - either by matching [`aria-label`](https://reactnative.dev/docs/accessibility#aria-label)/[`accessibilityLabel`](https://reactnative.dev/docs/accessibility#accessibilitylabel) prop - or by matching text content of view referenced by [`aria-labelledby`](https://reactnative.dev/docs/accessibility#aria-labelledby-android)/[`accessibilityLabelledBy`](https://reactnative.dev/docs/accessibility#accessibilitylabelledby-android) prop @@ -251,7 +255,7 @@ render(); const element = screen.getByLabelText('my-label'); ``` -### `ByHintText`, `ByA11yHint`, `ByAccessibilityHint` +### `ByHintText`, `ByA11yHint`, `ByAccessibilityHint` {#by-hint-text} > getByA11yHint, getAllByA11yHint, queryByA11yHint, queryAllByA11yHint, findByA11yHint, findAllByA11yHint > getByAccessibilityHint, getAllByAccessibilityHint, queryByAccessibilityHint, queryAllByAccessibilityHint, findByAccessibilityHint, findAllByAccessibilityHint @@ -281,19 +285,19 @@ const element = screen.getByHintText('Plays a song'); Please consult [Apple guidelines on how `accessibilityHint` should be used](https://developer.apple.com/documentation/objectivec/nsobject/1615093-accessibilityhint). ::: - - -### `ByA11yState`, `ByAccessibilityState` (deprecated) +### `ByA11yState`, `ByAccessibilityState` (deprecated) {#by-accessibility-state} :::caution This query has been marked deprecated, as is typically too general to give meaningful results. Therefore, it's better to use one of following options: -* [`*ByRole`](#byrole) query with relevant state options: `disabled`, `selected`, `checked`, `expanded` and `busy` -* use built-in Jest matchers to check the state of element found using some other query: - * enabled state: [`toBeEnabled()` / `toBeDisabled()`](jest-matchers#tobeenabled) - * checked state: [`toBeChecked()` / `toBePartiallyChecked()`](jest-matchers#tobechecked) - * selected state: [`toBeSelected()`](jest-matchers#tobeselected) - * expanded state: [`toBeExpanded()` / `toBeCollapsed()`](jest-matchers#tobeexpanded) - * busy state: [`toBeBusy()`](jest-matchers#tobebusy) + +- [`*ByRole`](#by-role) query with relevant state options: `disabled`, `selected`, `checked`, `expanded` and `busy` +- use built-in Jest matchers to check the state of element found using some other query: + - enabled state: [`toBeEnabled()` / `toBeDisabled()`](jest-matchers#tobeenabled) + - checked state: [`toBeChecked()` / `toBePartiallyChecked()`](jest-matchers#tobechecked) + - selected state: [`toBeSelected()`](jest-matchers#tobeselected) + - expanded state: [`toBeExpanded()` / `toBeCollapsed()`](jest-matchers#tobeexpanded) + - busy state: [`toBeBusy()`](jest-matchers#tobebusy) + ::: > getByA11yState, getAllByA11yState, queryByA11yState, queryAllByA11yState, findByA11yState, findAllByA11yState @@ -314,7 +318,7 @@ getByA11yState( ): ReactTestInstance; ``` -Returns a `ReactTestInstance` with matching `accessibilityState` prop or ARIA state props: `aria-disabled`, `aria-selected`, `aria-checked`, `aria-busy`, and `aria-expanded`. +Returns a `ReactTestInstance` with matching `accessibilityState` prop or ARIA state props: `aria-disabled`, `aria-selected`, `aria-checked`, `aria-busy`, and `aria-expanded`. ```jsx import { render, screen } from '@testing-library/react-native'; @@ -351,13 +355,14 @@ but will not match elements with following props: The difference in handling default values is made to reflect observed accessibility behaviour on iOS and Android platforms. ::: -### `ByA11yValue`, `ByAccessibilityValue` (deprecated) +### `ByA11yValue`, `ByAccessibilityValue` (deprecated) {#by-accessibility-value} :::caution This query has been marked deprecated, as is typically too general to give meaningful results. Therefore, it's better to use one of following options: -* [`toHaveAccessibilityValue()`](jest-matchers#tohaveaccessibilityvalue) Jest matcher to check the state of element found using some other query -* [`*ByRole`](#byrole) query with `value` option -::: + +- [`toHaveAccessibilityValue()`](jest-matchers#tohaveaccessibilityvalue) Jest matcher to check the state of element found using some other query +- [`*ByRole`](#by-role) query with `value` option + ::: > getByA11yValue, getAllByA11yValue, queryByA11yValue, queryAllByA11yValue, findByA11yValue, findAllByA11yValue > getByAccessibilityValue, getAllByAccessibilityValue, queryByAccessibilityValue, queryAllByAccessibilityValue, findByAccessibilityValue, findAllByAccessibilityValue @@ -409,7 +414,9 @@ render(Hidden from accessibility); // Exclude hidden elements expect( - screen.queryByText('Hidden from accessibility', { includeHiddenElements: false }) + screen.queryByText('Hidden from accessibility', { + includeHiddenElements: false, + }) ).not.toBeOnTheScreen(); // Include hidden elements @@ -463,6 +470,7 @@ screen.getByText(/hello world/); ``` ### Options {#text-match-options} + #### Precision ```typescript From ddc0089e2d73af1aae134784653b7c6808f4413e Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 21 Nov 2023 18:58:06 +0100 Subject: [PATCH 07/12] docs: tweaks --- website/docs/HowShouldIQuery.md | 26 +++++++++++++------------- website/sidebars.js | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/website/docs/HowShouldIQuery.md b/website/docs/HowShouldIQuery.md index 6b7ee2f3f..87d24a6e3 100644 --- a/website/docs/HowShouldIQuery.md +++ b/website/docs/HowShouldIQuery.md @@ -49,15 +49,15 @@ Do not use `queryAllBy*` as it provides no assertions on the number of matching The query predicate describes how you decide whether to match the given element. -| Predicate | Supported elements | Inspected props | -| ------------------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------- | -| [`*ByRole`](api-queries#by-role) | all host elements | `role`, `accessibilityRole`,
optional: accessible name, accessibility state and value | -| [`*ByLabelText`](api-queries#by-label-text) | all host elements | `aria-label`, `aria-labelledby`,
`accessibilityLabel`, `accessibilityLabelledBy` | -| [`*ByDisplayValue`](api-queries#by-display-value) | [`TextInput`](https://reactnative.dev/docs/textinput) | `value`, `defaultValue` | -| [`*ByPlaceholderText`](api-queries#by-placeholder-text) | [`TextInput`](https://reactnative.dev/docs/textinput) | `placeholder` | -| [`*ByText`](api-queries#by-text) | [`Text`](https://reactnative.dev/docs/text) | `children` (text content) | -| [`*ByHintText`](api-queries#by-hint-text) | all host elements | `accessibilityHint` | -| [`*ByTestId`](api-queries#by-test-id) | all host elements | `testID` | +| Predicate | Supported elements | Inspected props | +| ------------------------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------- | +| [`*ByRole`](api-queries#by-role) | all host elements | `role`, `accessibilityRole`,
optional: accessible name, accessibility state and value | +| [`*ByLabelText`](api-queries#by-label-text) | all host elements | `aria-label`, `aria-labelledby`,
`accessibilityLabel`, `accessibilityLabelledBy` | +| [`*ByDisplayValue`](api-queries#by-display-value) | `TextInput` | `value`, `defaultValue` | +| [`*ByPlaceholderText`](api-queries#by-placeholder-text) | `TextInput` | `placeholder` | +| [`*ByText`](api-queries#by-text) | `Text` | `children` (text content) | +| [`*ByHintText`](api-queries#by-hint-text) | all host elements | `accessibilityHint` | +| [`*ByTestId`](api-queries#by-test-id) | all host elements | `testID` | ### Idiomatic query predicates @@ -120,7 +120,7 @@ These option types can frequently be better expressed by using the corresponding ### 2. Text input queries {#text-input-queries} -Querying `TextInput` elements presents a unique challenge as there is no role for `TextInput` elements. There is a `searchbox`/`search` role, which can be assigned to `TextInput`, but it should be only used in the context of search inputs, leaving other text inputs without a role to query with. +Querying [`TextInput`](https://reactnative.dev/docs/textinput) elements presents a unique challenge as there is no separate role for `TextInput` elements. There is a `searchbox`/`search` role, which can be assigned to `TextInput`, but it should be only used in the context of search inputs, leaving other text inputs without a role to query with. Therefore, you can use the following queries to find relevant text inputs: @@ -134,9 +134,9 @@ These queries reflect the apps' user experience, both visual and through assisti These queries include: -- [`*ByText`](api-queries#by-text) - will match the text content of the element -- [`*ByLabelText`](api-queries#by-label-text) - will match the accessibility label of the element -- [`*ByHintText`](api-queries#by-hint-text) - will match the accessibility hint of the element +- [`*ByText`](api-queries#by-text) - will match the text content of the element. This query will match only `Text` elements. +- [`*ByLabelText`](api-queries#by-label-text) - will match the accessibility label of the element. This query will match any host elements. +- [`*ByHintText`](api-queries#by-hint-text) - will match the accessibility hint of the element. This query will match any host elements. ### 4. Test ID query {#test-id-query} diff --git a/website/sidebars.js b/website/sidebars.js index f8b1f51eb..f27a0740c 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -3,9 +3,9 @@ module.exports = { Introduction: ['getting-started', 'faq'], 'API Reference': ['api', 'api-queries', 'jest-matchers', 'user-event'], Guides: [ - 'troubleshooting', 'how-should-i-query', 'eslint-plugin-testing-library', + 'troubleshooting', ], Advanced: ['testing-env', 'understanding-act'], Migrations: [ From 0e3773494f0223542d840aa9222a05e746d47b56 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 21 Nov 2023 18:59:47 +0100 Subject: [PATCH 08/12] docs: update caniuse --- website/yarn.lock | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/website/yarn.lock b/website/yarn.lock index 212129084..d84975bca 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -3952,17 +3952,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001426": - version: 1.0.30001456 - resolution: "caniuse-lite@npm:1.0.30001456" - checksum: a40dbc996af9683eac5e4e74c1b713e6b4744a9a8780b0ee9d47a866fb8628565e5faaf3497064438f1e92a661a8f9570cc2ec722d7693339def7ea9f31dc84d - languageName: node - linkType: hard - -"caniuse-lite@npm:^1.0.30001449": - version: 1.0.30001466 - resolution: "caniuse-lite@npm:1.0.30001466" - checksum: ac92474409942988c882ad2ff819a0c76288e6984067246cc8b08d047c3dc31c31847927eba0174be115c0520da7b67ab68ce0092d8100610e541c51cd99fd58 +"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001426, caniuse-lite@npm:^1.0.30001449": + version: 1.0.30001563 + resolution: "caniuse-lite@npm:1.0.30001563" + checksum: a8b367d43e0307ec243d8df8515d563fa3de895e9698eec4539037aed400da81e0df737164da2a6b7104ab6e75b4ea63db0adebcaabe326f2792841980259256 languageName: node linkType: hard From 8c9aff563cd788d009c5d30ef7adb1d35c1e6deb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 23 Nov 2023 00:06:28 +0100 Subject: [PATCH 09/12] docs: tweaks --- website/docs/HowShouldIQuery.md | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/website/docs/HowShouldIQuery.md b/website/docs/HowShouldIQuery.md index 87d24a6e3..e364cc445 100644 --- a/website/docs/HowShouldIQuery.md +++ b/website/docs/HowShouldIQuery.md @@ -5,7 +5,7 @@ title: How Should I Query? React Native Testing Library provides various query types, allowing great flexibility in finding views appropriate for your tests. At the same time, the number of queries might be confusing. This guide aims to help you pick the correct queries for your test scenarios. -# Query parts +## Query parts Each query is composed of two parts: variant and predicate, which are separated by the `by` word in the middle of the query. @@ -41,9 +41,9 @@ Here are general guidelines for picking idiomatic query variants: 1. Use `getBy*` in the most common case when you expect a **single matching element**. Use other queries only in more specific cases. 2. Use `findBy*` when an element is not yet in the element tree, but you expect it to be there as a **result of some asynchronous action**. 3. Use `getAllBy*` (and `findAllBy*` for async) if you expect **more than one matching element**, e.g. in a list. -4. Use `queryBy*` variant only when element **should not exist** to use it together with e.g. [`not.toBeOnTheScreen()`](jest-matchers#tobeonthescreen) matcher. +4. Use `queryBy*` only when element **should not exist** to use it together with e.g. [`not.toBeOnTheScreen()`](jest-matchers#tobeonthescreen) matcher. -Do not use `queryAllBy*` as it provides no assertions on the number of matching elements. +Avoid using `queryAllBy*` in regular tests, as it provides no assertions on the number of matching elements. You may still find it useful when building reusable custom testing tools. ## Query predicate @@ -95,12 +95,11 @@ Frequently, you will want to add the [`name`](api-queries#by-role-options) optio Here are a couple of examples: -- button Start : `getByRole("button", { name: "Start" })` +- start button: `getByRole("button", { name: "Start" })` - silent mode switch: `getByRole("switch", { name: "Silent Mode" })` -- whole menu: `getByRole("menu", { name: "Editing Menu" })` -- particular menu item: `getByRole("menuitem", { name: "Undo" })` -- error messages: `getByRole("alert", { name: /Not logged in/ })` - screen header: `getByRole("header", { name: "Settings" })` +- undo menu item: `getByRole("menuitem", { name: "Undo" })` +- error messages: `getByRole("alert", { name: /Not logged in/ })` #### Other options {#by-role-query-other-options} @@ -125,8 +124,8 @@ Querying [`TextInput`](https://reactnative.dev/docs/textinput) elements presents Therefore, you can use the following queries to find relevant text inputs: 1. [`*ByLabelText`](api-queries#by-label-text) - will match the accessibility label of the element. This query will match any host elements, including `TextInput` elements. -2. [`*ByDisplayValue`](api-queries#by-display-value) - will the value of `TextInput` element. This query will match only `TextInput` elements. -3. [`*ByPlaceholderText`](api-queries#by-placeholder-text) - will match the placeholder of `TextInput` element. This query will match only `TextInput` elements. +2. [`*ByPlaceholderText`](api-queries#by-placeholder-text) - will match the placeholder of `TextInput` element. This query will match only `TextInput` elements. +3. [`*ByDisplayValue`](api-queries#by-display-value) - will the current (or default) value of `TextInput` element. This query will match only `TextInput` elements. ### 3. Other accessible queries {#other-accessible-queries} @@ -135,8 +134,8 @@ These queries reflect the apps' user experience, both visual and through assisti These queries include: - [`*ByText`](api-queries#by-text) - will match the text content of the element. This query will match only `Text` elements. -- [`*ByLabelText`](api-queries#by-label-text) - will match the accessibility label of the element. This query will match any host elements. -- [`*ByHintText`](api-queries#by-hint-text) - will match the accessibility hint of the element. This query will match any host elements. +- [`*ByLabelText`](api-queries#by-label-text) - will match the accessibility label of the element. +- [`*ByHintText`](api-queries#by-hint-text) - will match the accessibility hint of the element. ### 4. Test ID query {#test-id-query} From 058c4e29ff2ff7ea11646d184ff50e795f707701 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Mon, 27 Nov 2023 18:51:15 +0100 Subject: [PATCH 10/12] docs: tweaks --- website/docs/HowShouldIQuery.md | 22 +++------------------- website/docs/Queries.md | 32 +++++++++++++++++++++++++------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/website/docs/HowShouldIQuery.md b/website/docs/HowShouldIQuery.md index e364cc445..3a8ff8ca6 100644 --- a/website/docs/HowShouldIQuery.md +++ b/website/docs/HowShouldIQuery.md @@ -34,7 +34,7 @@ Queries work as implicit assertions on the number of matching elements and will ### Idiomatic query variants -Using idiomatic query variants helps better express your test's intent and expectations about the number of matching elements. Using other query variants might work but could make it harder to reason about the test. +Idiomatic query variants clarify test intent and the expected number of matching elements. They will also throw helpful errors if assertions fail to help diagnose the issues. Here are general guidelines for picking idiomatic query variants: @@ -61,9 +61,9 @@ The query predicate describes how you decide whether to match the given element. ### Idiomatic query predicates -Choosing the proper query predicate helps better express the test's intent and make the tests resemble how users interact with your code (components, screens, etc.) as much as possible following our [Guiding Principles](https://testing-library.com/docs/guiding-principles). +Choosing the proper query predicate helps better express the test's intent and make the tests resemble how users interact with your code (components, screens, etc.) as much as possible following our [Guiding Principles](https://testing-library.com/docs/guiding-principles). Additionally, most predicates promote the usage of proper accessibility props, which add a semantic layer on top of an element tree composed primarily of [`View`](https://reactnative.dev/docs/view) elements. -Additionally, most predicates promote the usage of proper accessibility props, which add a semantic layer on top of an element tree composed primarily of [`View`](https://reactnative.dev/docs/view) elements. +It is recommended to use queries in the following order of priority: ### 1. By Role query {#by-role-query} @@ -101,22 +101,6 @@ Here are a couple of examples: - undo menu item: `getByRole("menuitem", { name: "Undo" })` - error messages: `getByRole("alert", { name: /Not logged in/ })` -#### Other options {#by-role-query-other-options} - -Other useful [options](api-queries#by-role-options) include: - -- accessibility states like: `disabled`, `selected`, `checked`, `busy`, `expanded` -- accessibility value: `value: { now, min, max, text }` - -These option types can frequently be better expressed by using the corresponding Jest matchers: - -- [`toBeEnabled()` / `toBeDisabled()`](jest-matchers#tobeenabled) -- [`toBeSelected()`](jest-matchers#tobeselected) -- [`toBeChecked()` / `toBePartiallyChecked()`](jest-matchers#tobechecked) -- [`toBeExpanded()` / `toBeCollapsed()`](jest-matchers#tobeexpanded) -- [`toBeBusy()`](jest-matchers#tobebusy) -- [`toHaveAccessibilityValue()`](jest-matchers#tohaveaccessibilityvalue) - ### 2. Text input queries {#text-input-queries} Querying [`TextInput`](https://reactnative.dev/docs/textinput) elements presents a unique challenge as there is no separate role for `TextInput` elements. There is a `searchbox`/`search` role, which can be assigned to `TextInput`, but it should be only used in the context of search inputs, leaving other text inputs without a role to query with. diff --git a/website/docs/Queries.md b/website/docs/Queries.md index 05da7a290..018570556 100644 --- a/website/docs/Queries.md +++ b/website/docs/Queries.md @@ -112,19 +112,37 @@ const element3 = screen.getByRole('button', { name: 'Hello', disabled: true }); #### Options {#by-role-options} -`name`: Finds an element with given `role`/`accessibilityRole` and an accessible name (equivalent to `byText` or `byLabelText` query). +- `name`: Finds an element with given `role`/`accessibilityRole` and an accessible name (equivalent to `byText` or `byLabelText` query). -`disabled`: You can filter elements by their disabled state (coming either from `aria-disabled` prop or `accessbilityState.disabled` prop). The possible values are `true` or `false`. Querying `disabled: false` will also match elements with `disabled: undefined` (see the [wiki](https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State) for more details). See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `disabled` state. +- `disabled`: You can filter elements by their disabled state (coming either from `aria-disabled` prop or `accessbilityState.disabled` prop). The possible values are `true` or `false`. Querying `disabled: false` will also match elements with `disabled: undefined` (see the [wiki](https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State) for more details). -`selected`: You can filter elements by their selected state (coming either from `aria-selected` prop or `accessbilityState.selected` prop). The possible values are `true` or `false`. Querying `selected: false` will also match elements with `selected: undefined` (see the [wiki](https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State) for more details). See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `selected` state. + - See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `disabled` state. + - This option can alternatively be expressed using the [`toBeEnabled()` / `toBeDisabled()`](jest-matchers#tobeenabled) Jest matchers. -`checked`: You can filter elements by their checked state (coming either from `aria-checked` prop or `accessbilityState.checked` prop). The possible values are `true`, `false`, or `"mixed"`. See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `checked` state. +- `selected`: You can filter elements by their selected state (coming either from `aria-selected` prop or `accessbilityState.selected` prop). The possible values are `true` or `false`. Querying `selected: false` will also match elements with `selected: undefined` (see the [wiki](https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State) for more details). -`busy`: You can filter elements by their busy state (coming either from `aria-busy` prop or `accessbilityState.busy` prop). The possible values are `true` or `false`. Querying `busy: false` will also match elements with `busy: undefined` (see the [wiki](https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State) for more details). See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `busy` state. + - See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `selected` state. + - This option can alternatively be expressed using the [`toBeSelected()`](jest-matchers#tobeselected) Jest matcher. -`expanded`: You can filter elements by their expanded state (coming either from `aria-expanded` prop or `accessbilityState.expanded` prop). The possible values are `true` or `false`. See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `expanded` state. +* `checked`: You can filter elements by their checked state (coming either from `aria-checked` prop or `accessbilityState.checked` prop). The possible values are `true`, `false`, or `"mixed"`. -`value`: Filter elements by their accessibility value, based on either `aria-valuemin`, `aria-valuemax`, `aria-valuenow`, `aria-valuetext` or `accessibilityValue` props. Accessiblity value conceptually consists of numeric `min`, `max` and `now` entries, as well as string `text` entry. See React Native [accessibilityValue](https://reactnative.dev/docs/accessibility#accessibilityvalue) docs to learn more about the accessibility value concept. + - See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `checked` state. + - This option can alternatively be expressed using the [`toBeChecked()` / `toBePartiallyChecked()`](jest-matchers#tobechecked) Jest matchers. + +* `busy`: You can filter elements by their busy state (coming either from `aria-busy` prop or `accessbilityState.busy` prop). The possible values are `true` or `false`. Querying `busy: false` will also match elements with `busy: undefined` (see the [wiki](https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State) for more details). + + - See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `busy` state. + - This option can alternatively be expressed using the [`toBeBusy()`](jest-matchers#tobebusy) Jest matcher. + +* `expanded`: You can filter elements by their expanded state (coming either from `aria-expanded` prop or `accessbilityState.expanded` prop). The possible values are `true` or `false`. + + - See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `expanded` state. + - This option can alternatively be expressed using the [`toBeExpanded()` / `toBeCollapsed()`](jest-matchers#tobeexpanded) Jest matchers. + +* `value`: Filter elements by their accessibility value, based on either `aria-valuemin`, `aria-valuemax`, `aria-valuenow`, `aria-valuetext` or `accessibilityValue` props. Accessiblity value conceptually consists of numeric `min`, `max` and `now` entries, as well as string `text` entry. + + - See React Native [accessibilityValue](https://reactnative.dev/docs/accessibility#accessibilityvalue) docs to learn more about the accessibility value concept. + - This option can alternatively be expressed using the [`toHaveAccessibilityValue()`](jest-matchers#tohaveaccessibilityvalue) Jest matcher. ### `ByText` {#by-text} From 66e5a91b07f2597023e7c46f4f86cc5b12afea84 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Mon, 27 Nov 2023 18:52:21 +0100 Subject: [PATCH 11/12] docs: tweaks --- website/docs/HowShouldIQuery.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/HowShouldIQuery.md b/website/docs/HowShouldIQuery.md index 3a8ff8ca6..24c137be5 100644 --- a/website/docs/HowShouldIQuery.md +++ b/website/docs/HowShouldIQuery.md @@ -63,7 +63,7 @@ The query predicate describes how you decide whether to match the given element. Choosing the proper query predicate helps better express the test's intent and make the tests resemble how users interact with your code (components, screens, etc.) as much as possible following our [Guiding Principles](https://testing-library.com/docs/guiding-principles). Additionally, most predicates promote the usage of proper accessibility props, which add a semantic layer on top of an element tree composed primarily of [`View`](https://reactnative.dev/docs/view) elements. -It is recommended to use queries in the following order of priority: +It is recommended to use query predicates in the following order of priority: ### 1. By Role query {#by-role-query} From be622231172e7074230b8109350b984fd88e2e13 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Mon, 27 Nov 2023 18:56:56 +0100 Subject: [PATCH 12/12] docs: tweaks --- website/docs/Queries.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/Queries.md b/website/docs/Queries.md index 018570556..9128f5137 100644 --- a/website/docs/Queries.md +++ b/website/docs/Queries.md @@ -112,7 +112,7 @@ const element3 = screen.getByRole('button', { name: 'Hello', disabled: true }); #### Options {#by-role-options} -- `name`: Finds an element with given `role`/`accessibilityRole` and an accessible name (equivalent to `byText` or `byLabelText` query). +- `name`: Finds an element with given `role`/`accessibilityRole` and an accessible name (= accessability label or text content). - `disabled`: You can filter elements by their disabled state (coming either from `aria-disabled` prop or `accessbilityState.disabled` prop). The possible values are `true` or `false`. Querying `disabled: false` will also match elements with `disabled: undefined` (see the [wiki](https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State) for more details).