,
// Unsafe aliases
UNSAFE_getByType: (type: React.ComponentType
) => ReactTestInstance,
diff --git a/src/helpers/makeQueries.js b/src/helpers/makeQueries.js
index 35aa7d4b7..5d0a4e1ce 100644
--- a/src/helpers/makeQueries.js
+++ b/src/helpers/makeQueries.js
@@ -2,14 +2,19 @@
import waitFor from '../waitFor';
import type { WaitForOptions } from '../waitFor';
import { ErrorWithStack } from './errors';
+import type { TextMatchOptions } from './byText';
type QueryFunction = (
instance: ReactTestInstance
-) => (args: ArgType) => ReturnType;
+) => (args: ArgType, queryOptions?: TextMatchOptions) => ReturnType;
type FindQueryFunction = (
instance: ReactTestInstance
-) => (args: ArgType, waitForOptions?: WaitForOptions) => Promise;
+) => (
+ args: ArgType,
+ queryOptions?: TextMatchOptions & WaitForOptions,
+ waitForOptions?: WaitForOptions
+) => Promise;
type QueryAllByQuery = QueryFunction<
QueryArg,
@@ -37,14 +42,43 @@ export type Queries = {
findAllBy: FindAllByQuery,
};
+// The WaitForOptions has been moved to the second option param of findBy* methods with the adding of TextMatchOptions
+// To make the migration easier and avoid a breaking change, keep reading this options from the first param but warn
+const deprecatedKeys: $Keys[] = [
+ 'timeout',
+ 'interval',
+ 'stackTraceError',
+];
+const extractDeprecatedWaitForOptionUsage = (queryOptions?: WaitForOptions) => {
+ if (queryOptions) {
+ const waitForOptions: WaitForOptions = {
+ timeout: queryOptions.timeout,
+ interval: queryOptions.interval,
+ stackTraceError: queryOptions.stackTraceError,
+ };
+ deprecatedKeys.forEach((key) => {
+ if (queryOptions[key]) {
+ // eslint-disable-next-line no-console
+ console.warn(
+ `Use of option "${key}" in a findBy* query's second parameter, TextMatchOptions, is deprecated. Please pass this option in the third, WaitForOptions, parameter.
+Example:
+
+ findByText(text, {}, { ${key}: ${queryOptions[key].toString()} })`
+ );
+ }
+ });
+ return waitForOptions;
+ }
+};
+
export function makeQueries(
queryAllByQuery: QueryAllByQuery,
getMissingError: (args: QueryArg) => string,
getMultipleError: (args: QueryArg) => string
): Queries {
function getAllByQuery(instance: ReactTestInstance) {
- return function getAllFn(args: QueryArg) {
- const results = queryAllByQuery(instance)(args);
+ return function getAllFn(args: QueryArg, queryOptions?: TextMatchOptions) {
+ const results = queryAllByQuery(instance)(args, queryOptions);
if (results.length === 0) {
throw new ErrorWithStack(getMissingError(args), getAllFn);
@@ -55,8 +89,11 @@ export function makeQueries(
}
function queryByQuery(instance: ReactTestInstance) {
- return function singleQueryFn(args: QueryArg) {
- const results = queryAllByQuery(instance)(args);
+ return function singleQueryFn(
+ args: QueryArg,
+ queryOptions?: TextMatchOptions
+ ) {
+ const results = queryAllByQuery(instance)(args, queryOptions);
if (results.length > 1) {
throw new ErrorWithStack(getMultipleError(args), singleQueryFn);
@@ -71,8 +108,8 @@ export function makeQueries(
}
function getByQuery(instance: ReactTestInstance) {
- return function getFn(args: QueryArg) {
- const results = queryAllByQuery(instance)(args);
+ return function getFn(args: QueryArg, queryOptions?: TextMatchOptions) {
+ const results = queryAllByQuery(instance)(args, queryOptions);
if (results.length > 1) {
throw new ErrorWithStack(getMultipleError(args), getFn);
@@ -89,18 +126,32 @@ export function makeQueries(
function findAllByQuery(instance: ReactTestInstance) {
return function findAllFn(
args: QueryArg,
+ queryOptions?: TextMatchOptions & WaitForOptions,
waitForOptions?: WaitForOptions = {}
) {
- return waitFor(() => getAllByQuery(instance)(args), waitForOptions);
+ const deprecatedWaitForOptions = extractDeprecatedWaitForOptionUsage(
+ queryOptions
+ );
+ return waitFor(() => getAllByQuery(instance)(args, queryOptions), {
+ ...deprecatedWaitForOptions,
+ ...waitForOptions,
+ });
};
}
function findByQuery(instance: ReactTestInstance) {
return function findFn(
args: QueryArg,
+ queryOptions?: TextMatchOptions & WaitForOptions,
waitForOptions?: WaitForOptions = {}
) {
- return waitFor(() => getByQuery(instance)(args), waitForOptions);
+ const deprecatedWaitForOptions = extractDeprecatedWaitForOptionUsage(
+ queryOptions
+ );
+ return waitFor(() => getByQuery(instance)(args, queryOptions), {
+ ...deprecatedWaitForOptions,
+ ...waitForOptions,
+ });
};
}
diff --git a/src/helpers/queryByAPI.js b/src/helpers/queryByAPI.js
index 80a8558df..064bb3cc8 100644
--- a/src/helpers/queryByAPI.js
+++ b/src/helpers/queryByAPI.js
@@ -1,5 +1,6 @@
// @flow
import * as React from 'react';
+import type { TextMatchOptions } from './byText';
import {
UNSAFE_getByType,
UNSAFE_getByProps,
@@ -20,18 +21,32 @@ import {
} from './errors';
export type QueryByAPI = {|
- queryByText: (name: string | RegExp) => ReactTestInstance | null,
+ queryByText: (
+ name: string | RegExp,
+ queryOptions?: TextMatchOptions
+ ) => ReactTestInstance | null,
+ queryAllByText: (
+ text: string | RegExp,
+ queryOptions?: TextMatchOptions
+ ) => Array,
queryByPlaceholderText: (
- placeholder: string | RegExp
+ placeholder: string | RegExp,
+ queryOptions?: TextMatchOptions
) => ReactTestInstance | null,
- queryByDisplayValue: (value: string | RegExp) => ReactTestInstance | null,
- queryByTestId: (testID: string | RegExp) => ReactTestInstance | null,
- queryAllByTestId: (testID: string | RegExp) => Array,
- queryAllByText: (text: string | RegExp) => Array,
queryAllByPlaceholderText: (
- placeholder: string | RegExp
+ placeholder: string | RegExp,
+ queryOptions?: TextMatchOptions
) => Array,
- queryAllByDisplayValue: (value: string | RegExp) => Array,
+ queryByDisplayValue: (
+ value: string | RegExp,
+ queryOptions?: TextMatchOptions
+ ) => ReactTestInstance | null,
+ queryAllByDisplayValue: (
+ value: string | RegExp,
+ queryOptions?: TextMatchOptions
+ ) => Array,
+ queryByTestId: (testID: string | RegExp) => ReactTestInstance | null,
+ queryAllByTestId: (testID: string | RegExp) => Array,
// Unsafe aliases
UNSAFE_queryByType: (
diff --git a/src/matches.js b/src/matches.js
new file mode 100644
index 000000000..b5cfcd74c
--- /dev/null
+++ b/src/matches.js
@@ -0,0 +1,41 @@
+// @flow
+export type NormalizerFn = (textToNormalize: string) => string;
+
+export function matches(
+ matcher: string | RegExp,
+ text: string,
+ normalizer?: NormalizerFn = getDefaultNormalizer(),
+ exact?: boolean = true
+): boolean {
+ if (typeof text !== 'string') {
+ return false;
+ }
+
+ const normalizedText = normalizer(text);
+ if (typeof matcher === 'string') {
+ return exact
+ ? normalizedText === matcher
+ : normalizedText.toLowerCase().includes(matcher.toLowerCase());
+ } else {
+ return matcher.test(normalizedText);
+ }
+}
+
+type NormalizerConfig = {
+ trim?: boolean,
+ collapseWhitespace?: boolean,
+};
+
+export function getDefaultNormalizer({
+ trim = true,
+ collapseWhitespace = true,
+}: NormalizerConfig = {}): NormalizerFn {
+ return (text: string) => {
+ let normalizedText = text;
+ normalizedText = trim ? normalizedText.trim() : normalizedText;
+ normalizedText = collapseWhitespace
+ ? normalizedText.replace(/\s+/g, ' ')
+ : normalizedText;
+ return normalizedText;
+ };
+}
diff --git a/src/pure.js b/src/pure.js
index b36edc6aa..61d9c5414 100644
--- a/src/pure.js
+++ b/src/pure.js
@@ -8,6 +8,7 @@ import shallow from './shallow';
import waitFor, { waitForElement } from './waitFor';
import waitForElementToBeRemoved from './waitForElementToBeRemoved';
import { within, getQueriesForElement } from './within';
+import { getDefaultNormalizer } from './matches';
export { act };
export { cleanup };
@@ -18,3 +19,4 @@ export { shallow };
export { waitFor, waitForElement };
export { waitForElementToBeRemoved };
export { within, getQueriesForElement };
+export { getDefaultNormalizer };
diff --git a/typings/__tests__/index.test.tsx b/typings/__tests__/index.test.tsx
index 40341f84a..6cd6c558c 100644
--- a/typings/__tests__/index.test.tsx
+++ b/typings/__tests__/index.test.tsx
@@ -10,6 +10,7 @@ import {
act,
within,
getQueriesForElement,
+ getDefaultNormalizer,
} from '../..';
interface HasRequiredProp {
@@ -133,19 +134,23 @@ const queryAllBy: ReactTestInstance[][] = [
// findBy API
const findBy: Promise[] = [
tree.findByText('View'),
- tree.findByText('View', { timeout: 10, interval: 10 }),
+ tree.findByText('View', {}, { timeout: 10, interval: 10 }),
tree.findByText(/View/g),
- tree.findByText(/View/g, { timeout: 10, interval: 5 }),
+ tree.findByText(/View/g, {}, { timeout: 10, interval: 5 }),
tree.findByPlaceholderText('my placeholder'),
- tree.findByPlaceholderText('my placeholder', { timeout: 10, interval: 5 }),
+ tree.findByPlaceholderText(
+ 'my placeholder',
+ {},
+ { timeout: 10, interval: 5 }
+ ),
tree.findByPlaceholderText(/placeholder/g),
- tree.findByPlaceholderText(/placeholder/g, { timeout: 10, interval: 5 }),
+ tree.findByPlaceholderText(/placeholder/g, {}, { timeout: 10, interval: 5 }),
tree.findByDisplayValue('my value'),
- tree.findByDisplayValue('my value', { timeout: 10, interval: 10 }),
+ tree.findByDisplayValue('my value', {}, { timeout: 10, interval: 10 }),
tree.findByDisplayValue(/value/g),
- tree.findByDisplayValue(/value/g, { timeout: 10, interval: 10 }),
+ tree.findByDisplayValue(/value/g, {}, { timeout: 10, interval: 10 }),
tree.findByTestId('test-id'),
- tree.findByTestId(/test-id/, { timeout: 10, interval: 10 }),
+ tree.findByTestId(/test-id/, {}, { timeout: 10, interval: 10 }),
tree.findByA11yLabel('label'),
tree.findByA11yLabel('label', { timeout: 10, interval: 10 }),
tree.findByLabelText('label'),
@@ -166,22 +171,30 @@ const findBy: Promise[] = [
const findAllBy: Promise[] = [
tree.findAllByText('View'),
- tree.findAllByText('View', { timeout: 10, interval: 10 }),
+ tree.findAllByText('View', {}, { timeout: 10, interval: 10 }),
tree.findAllByText(/View/g),
- tree.findAllByText(/View/g, { timeout: 10, interval: 5 }),
+ tree.findAllByText(/View/g, { exact: false }, { timeout: 10, interval: 5 }),
tree.findAllByPlaceholderText('my placeholder'),
- tree.findAllByPlaceholderText('my placeholder', {
- timeout: 10,
- interval: 10,
- }),
+ tree.findAllByPlaceholderText(
+ 'my placeholder',
+ { exact: false },
+ {
+ timeout: 10,
+ interval: 10,
+ }
+ ),
tree.findAllByPlaceholderText(/placeholder/g),
- tree.findAllByPlaceholderText(/placeholder/g, { timeout: 10, interval: 10 }),
+ tree.findAllByPlaceholderText(
+ /placeholder/g,
+ {},
+ { timeout: 10, interval: 10 }
+ ),
tree.findAllByDisplayValue('View'),
- tree.findAllByDisplayValue('View', { timeout: 10, interval: 10 }),
+ tree.findAllByDisplayValue('View', {}, { timeout: 10, interval: 10 }),
tree.findAllByDisplayValue(/View/g),
- tree.findAllByDisplayValue(/View/g, { timeout: 10, interval: 10 }),
+ tree.findAllByDisplayValue(/View/g, {}, { timeout: 10, interval: 10 }),
tree.findAllByTestId('test-id'),
- tree.findAllByTestId(/test-id/, { timeout: 10, interval: 10 }),
+ tree.findAllByTestId(/test-id/, {}, { timeout: 10, interval: 10 }),
tree.findAllByA11yLabel('label'),
tree.findAllByA11yLabel('label', { timeout: 10, interval: 10 }),
tree.findAllByLabelText('label'),
@@ -461,3 +474,13 @@ const withinFindAll: Promise[] = [
getQueriesForElement(instance).findAllByA11yState({ busy: true }),
getQueriesForElement(instance).findAllByA11yValue({ min: 10 }),
];
+
+// TextMatch
+const normalizer = getDefaultNormalizer({
+ trim: false,
+ collapseWhitespace: true,
+});
+tree.getByText('text', { exact: false, normalizer });
+tree.getByPlaceholderText('text', { exact: false, normalizer });
+tree.getByDisplayValue('text', { exact: true, normalizer });
+tree.getByTestId('text', { exact: false, normalizer });
diff --git a/typings/index.d.ts b/typings/index.d.ts
index 2003c2272..c0b6ea4bf 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -14,17 +14,38 @@ type QueryAllReturn = Array | [];
type FindReturn = Promise;
type FindAllReturn = Promise;
+type TextMatch = string | RegExp;
+
interface GetByAPI {
- getByText: (text: string | RegExp) => ReactTestInstance;
- getByPlaceholderText: (placeholder: string | RegExp) => ReactTestInstance;
- getByDisplayValue: (value: string | RegExp) => ReactTestInstance;
- getByTestId: (testID: string | RegExp) => ReactTestInstance;
- getAllByTestId: (testID: string | RegExp) => Array;
- getAllByText: (text: string | RegExp) => Array;
+ getByText: (text: TextMatch, options?: TextMatchOptions) => ReactTestInstance;
+ getByPlaceholderText: (
+ placeholder: TextMatch,
+ options?: TextMatchOptions
+ ) => ReactTestInstance;
+ getByDisplayValue: (
+ value: TextMatch,
+ options?: TextMatchOptions
+ ) => ReactTestInstance;
+ getByTestId: (
+ testID: TextMatch,
+ options?: TextMatchOptions
+ ) => ReactTestInstance;
+ getAllByTestId: (
+ testID: TextMatch,
+ options?: TextMatchOptions
+ ) => Array;
+ getAllByText: (
+ text: TextMatch,
+ options?: TextMatchOptions
+ ) => Array;
getAllByPlaceholderText: (
- placeholder: string | RegExp
+ placeholder: TextMatch,
+ options?: TextMatchOptions
+ ) => Array;
+ getAllByDisplayValue: (
+ value: TextMatch,
+ options?: TextMatchOptions
) => Array;
- getAllByDisplayValue: (value: string | RegExp) => Array;
// Unsafe aliases
UNSAFE_getByType: (type: React.ComponentType
) => ReactTestInstance;
@@ -64,19 +85,31 @@ interface GetByAPI {
}
interface QueryByAPI {
- queryByText: (name: string | RegExp) => ReactTestInstance | null;
+ queryByText: (
+ name: TextMatch,
+ options?: TextMatchOptions
+ ) => ReactTestInstance | null;
queryByPlaceholderText: (
- placeholder: string | RegExp
+ placeholder: TextMatch,
+ options?: TextMatchOptions
+ ) => ReactTestInstance | null;
+ queryByDisplayValue: (
+ value: TextMatch,
+ options?: TextMatchOptions
) => ReactTestInstance | null;
- queryByDisplayValue: (value: string | RegExp) => ReactTestInstance | null;
- queryByTestId: (testID: string | RegExp) => ReactTestInstance | null;
- queryAllByTestId: (testID: string | RegExp) => Array | [];
- queryAllByText: (text: string | RegExp) => Array | [];
+ queryByTestId: (testID: TextMatch) => ReactTestInstance | null;
+ queryAllByTestId: (testID: TextMatch) => Array | [];
+ queryAllByText: (
+ text: TextMatch,
+ options?: TextMatchOptions
+ ) => Array | [];
queryAllByPlaceholderText: (
- placeholder: string | RegExp
+ placeholder: TextMatch,
+ options?: TextMatchOptions
) => Array | [];
queryAllByDisplayValue: (
- value: string | RegExp
+ value: TextMatch,
+ options?: TextMatchOptions
) => Array | [];
// Unsafe aliases
@@ -126,35 +159,43 @@ interface QueryByAPI {
interface FindByAPI {
findByText: (
- text: string | RegExp,
+ text: TextMatch,
+ queryOptions?: TextMatchOptions,
waitForOptions?: WaitForOptions
) => FindReturn;
findByPlaceholderText: (
- placeholder: string | RegExp,
+ placeholder: TextMatch,
+ queryOptions?: TextMatchOptions,
waitForOptions?: WaitForOptions
) => FindReturn;
findByDisplayValue: (
- value: string | RegExp,
+ value: TextMatch,
+ queryOptions?: TextMatchOptions,
waitForOptions?: WaitForOptions
) => FindReturn;
findByTestId: (
- testID: string | RegExp,
+ testID: TextMatch,
+ queryOptions?: TextMatchOptions,
waitForOptions?: WaitForOptions
) => FindReturn;
findAllByText: (
- text: string | RegExp,
+ text: TextMatch,
+ queryOptions?: TextMatchOptions,
waitForOptions?: WaitForOptions
) => FindAllReturn;
findAllByPlaceholderText: (
- placeholder: string | RegExp,
+ placeholder: TextMatch,
+ queryOptions?: TextMatchOptions,
waitForOptions?: WaitForOptions
) => FindAllReturn;
findAllByDisplayValue: (
- value: string | RegExp,
+ value: TextMatch,
+ queryOptions?: TextMatchOptions,
waitForOptions?: WaitForOptions
) => FindAllReturn;
findAllByTestId: (
- testID: string | RegExp,
+ testID: TextMatch,
+ queryOptions?: TextMatchOptions,
waitForOptions?: WaitForOptions
) => FindAllReturn;
}
@@ -169,54 +210,54 @@ export type A11yValue = {
type A11yAPI = {
// Label
- getByA11yLabel: (matcher: string | RegExp) => GetReturn;
- getByLabelText: (matcher: string | RegExp) => GetReturn;
- getAllByA11yLabel: (matcher: string | RegExp) => GetAllReturn;
- getAllByLabelText: (matcher: string | RegExp) => GetAllReturn;
- queryByA11yLabel: (matcher: string | RegExp) => QueryReturn;
- queryByLabelText: (matcher: string | RegExp) => QueryReturn;
- queryAllByA11yLabel: (matcher: string | RegExp) => QueryAllReturn;
- queryAllByLabelText: (matcher: string | RegExp) => QueryAllReturn;
+ getByA11yLabel: (matcher: TextMatch) => GetReturn;
+ getByLabelText: (matcher: TextMatch) => GetReturn;
+ getAllByA11yLabel: (matcher: TextMatch) => GetAllReturn;
+ getAllByLabelText: (matcher: TextMatch) => GetAllReturn;
+ queryByA11yLabel: (matcher: TextMatch) => QueryReturn;
+ queryByLabelText: (matcher: TextMatch) => QueryReturn;
+ queryAllByA11yLabel: (matcher: TextMatch) => QueryAllReturn;
+ queryAllByLabelText: (matcher: TextMatch) => QueryAllReturn;
findByA11yLabel: (
- matcher: string | RegExp,
+ matcher: TextMatch,
waitForOptions?: WaitForOptions
) => FindReturn;
findByLabelText: (
- matcher: string | RegExp,
+ matcher: TextMatch,
waitForOptions?: WaitForOptions
) => FindReturn;
findAllByA11yLabel: (
- matcher: string | RegExp,
+ matcher: TextMatch,
waitForOptions?: WaitForOptions
) => FindAllReturn;
findAllByLabelText: (
- matcher: string | RegExp,
+ matcher: TextMatch,
waitForOptions?: WaitForOptions
) => FindAllReturn;
// Hint
- getByA11yHint: (matcher: string | RegExp) => GetReturn;
- getByHintText: (matcher: string | RegExp) => GetReturn;
- getAllByA11yHint: (matcher: string | RegExp) => GetAllReturn;
- getAllByHintText: (matcher: string | RegExp) => GetAllReturn;
- queryByA11yHint: (matcher: string | RegExp) => QueryReturn;
- queryByHintText: (matcher: string | RegExp) => QueryReturn;
- queryAllByA11yHint: (matcher: string | RegExp) => QueryAllReturn;
- queryAllByHintText: (matcher: string | RegExp) => QueryAllReturn;
+ getByA11yHint: (matcher: TextMatch) => GetReturn;
+ getByHintText: (matcher: TextMatch) => GetReturn;
+ getAllByA11yHint: (matcher: TextMatch) => GetAllReturn;
+ getAllByHintText: (matcher: TextMatch) => GetAllReturn;
+ queryByA11yHint: (matcher: TextMatch) => QueryReturn;
+ queryByHintText: (matcher: TextMatch) => QueryReturn;
+ queryAllByA11yHint: (matcher: TextMatch) => QueryAllReturn;
+ queryAllByHintText: (matcher: TextMatch) => QueryAllReturn;
findByA11yHint: (
- matcher: string | RegExp,
+ matcher: TextMatch,
waitForOptions?: WaitForOptions
) => FindReturn;
findByHintText: (
- matcher: string | RegExp,
+ matcher: TextMatch,
waitForOptions?: WaitForOptions
) => FindReturn;
findAllByA11yHint: (
- matcher: string | RegExp,
+ matcher: TextMatch,
waitForOptions?: WaitForOptions
) => FindAllReturn;
findAllByHintText: (
- matcher: string | RegExp,
+ matcher: TextMatch,
waitForOptions?: WaitForOptions
) => FindAllReturn;
@@ -334,6 +375,16 @@ export declare const render: (
export declare const cleanup: () => void;
export declare const fireEvent: FireEventAPI;
+type NormalizerFn = (textToNormalize: string) => string;
+type NormalizerConfig = {
+ trim?: boolean;
+ collapseWhitespace?: boolean;
+};
+type TextMatchOptions = {
+ exact?: boolean;
+ normalizer?: NormalizerFn;
+};
+
type WaitForOptions = {
timeout?: number;
interval?: number;
@@ -359,6 +410,10 @@ export declare const getQueriesForElement: (
instance: ReactTestInstance
) => Queries;
+export declare const getDefaultNormalizer: (
+ normalizerConfig?: NormalizerConfig
+) => NormalizerFn;
+
/**
* @deprecated This function has been removed. Please use `waitFor` function.
*/
diff --git a/website/docs/Queries.md b/website/docs/Queries.md
index 848de3cf9..1a0e29d8a 100644
--- a/website/docs/Queries.md
+++ b/website/docs/Queries.md
@@ -44,15 +44,19 @@ In order to properly use `findBy` and `findAllBy` queries you need at least Reac
_Note: most methods like this one return a [`ReactTestInstance`](https://reactjs.org/docs/test-renderer.html#testinstance) with following properties that you may be interested in:_
-```jsx
+```typescript
type ReactTestInstance = {
- type: string | Function,
- props: { [propName: string]: any },
- parent: null | ReactTestInstance,
- children: Array,
+ type: string | Function;
+ props: { [propName: string]: any };
+ parent: null | ReactTestInstance;
+ children: Array;
};
```
+### Options
+
+Query first argument can be a **string** or a **regex**. Some queries accept optional argument which change string matching behaviour. See [TextMatch](#TextMatch) for more info.
+
### `ByText`
> getByText, getAllByText, queryByText, queryAllByText, findByText, findAllByText
@@ -203,6 +207,99 @@ const { getByA11yValue } = render();
const element = getByA11yValue({ min: 40 });
```
+## TextMatch
+
+Most of the query APIs take a `TextMatch` as an argument, which means the argument can be either a _string_ or _regex_.
+
+```typescript
+type TextMatchOptions = {
+ exact?: boolean;
+ normalizer?: (textToNormalize: string) => string;
+ trim?: boolean;
+ collapseWhitespace?: boolean;
+};
+```
+
+### Examples
+
+Given the following render:
+
+```jsx
+const { getByText } = render(Hello World);
+```
+
+Will **find a match**:
+
+```js
+// Matching a string:
+getByText('Hello World'); // full string match
+getByText('llo Worl', { exact: false }); // substring match
+getByText('hello world', { exact: false }); // ignore case-sensitivity
+
+// Matching a regex:
+getByText(/World/); // substring match
+getByText(/world/i); // substring match, ignore case
+getByText(/^hello world$/i); // full string match, ignore case-sensitivity
+getByText(/Hello W?oRlD/i); // advanced regex
+```
+
+Will **NOT find a match**
+
+```js
+// substring does not match
+getByText('llo Worl');
+// full string does not match
+getByText('Goodbye World');
+
+// case-sensitive regex with different case
+getByText(/hello world/);
+```
+
+### Precision
+
+Queries that take a `TextMatch` also accept an object as the final argument that can contain options that affect the precision of string matching:
+
+- `exact`: Defaults to `true`; matches full strings, case-sensitive. When false, matches substrings and is not case-sensitive.
+ - `exact` has no effect on regex argument.
+ - In most cases using a `regex` instead of a string gives you more control over fuzzy matching and should be preferred over `{ exact: false }`.
+- `normalizer`: An optional function which overrides normalization behavior. See [Normalization](#Normalization).
+
+`exact` option defaults to `true` but if you want to search for a text slice or make text matching case-insensitive you can override it. That being said we advise you to use regex in more complex scenarios.
+
+### Normalization
+
+Before running any matching logic against text, it is automatically normalized. By default, normalization consists of trimming whitespace from the start and end of text, and collapsing multiple adjacent whitespace characters into a single space.
+
+If you want to prevent that normalization, or provide alternative normalization (e.g. to remove Unicode control characters), you can provide a `normalizer` function in the options object. This function will be given a string and is expected to return a normalized version of that string.
+
+:::info
+Specifying a value for `normalizer` replaces the built-in normalization, but you can call `getDefaultNormalizer` to obtain a built-in normalizer, either to adjust that normalization or to call it from your own normalizer.
+:::
+
+`getDefaultNormalizer` take options object which allows the selection of behaviour:
+
+- `trim`: Defaults to `true`. Trims leading and trailing whitespace.
+- `collapseWhitespace`: Defaults to `true`. Collapses inner whitespace (newlines, tabs repeated spaces) into a single space.
+
+#### Normalization Examples
+
+To perform a match against text without trimming:
+
+```typescript
+getByText(node, 'text', {
+ normalizer: getDefaultNormalizer({ trim: false }),
+});
+```
+
+To override normalization to remove some Unicode characters whilst keeping some (but not all) of the built-in normalization behavior:
+
+```typescript
+getByText(node, 'text', {
+ normalizer: (str) =>
+ getDefaultNormalizer({ trim: false })(str).replace(/[\u200E-\u200F]*/g, ''),
+});
+```
+
## Unit testing helpers
> Use sparingly and responsibly, escape hatches here