mirror of
https://github.com/callstack/react-native-testing-library.git
synced 2026-09-18 23:09:04 +08:00
feat: add within operator for returned instance-bound queries (#306)
* Basic within implementation and tests * Added typescript types & tests * Removed duplicated import in index.d.ts * Reversed prettier run on typings * Fixed package level exports for `within` operator * Replaced getByTestId queries with getByA11yHint queries in unit tests for `within` * Added `within` section to API.md * Updated README.md regarding `within` operator * Update website/docs/API.md Co-authored-by: Michał Pierzchała <thymikee@gmail.com>
This commit is contained in:
committed by
GitHub
parent
51bd418ba7
commit
1809825313
@@ -85,6 +85,7 @@ The [public API](https://callstack.github.io/react-native-testing-library/docs/a
|
||||
- [`render`](https://callstack.github.io/react-native-testing-library/docs/api#render) – deeply renders given React element and returns helpers to query the output components.
|
||||
- [`fireEvent`](https://callstack.github.io/react-native-testing-library/docs/api#fireevent) - invokes named event handler on the element.
|
||||
- [`waitForElement`](https://callstack.github.io/react-native-testing-library/docs/api#waitforelement) - waits for non-deterministic periods of time until your element appears or times out.
|
||||
- [`within`](https://callstack.github.io/react-native-testing-library/docs/api#within) - creates a queries object scoped for given element.
|
||||
- [`flushMicrotasksQueue`](https://callstack.github.io/react-native-testing-library/docs/api#flushmicrotasksqueue) - waits for microtasks queue to flush.
|
||||
|
||||
**Note to users who are more familiar with `react-testing-library`:** This API does not expose `cleanup` because it doesn't interact with the DOM. There's nothing to clean up.
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
import { View, Text, TextInput } from 'react-native';
|
||||
import { render, within } from '..';
|
||||
|
||||
test('within() exposes basic queries', () => {
|
||||
const rootQueries = render(
|
||||
<View>
|
||||
<View accessibilityHint="first">
|
||||
<Text>Same Text</Text>
|
||||
<TextInput value="Same Value" placeholder="Same Placeholder" />
|
||||
</View>
|
||||
<View accessibilityHint="second">
|
||||
<Text>Same Text</Text>
|
||||
<TextInput value="Same Value" placeholder="Same Placeholder" />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
expect(rootQueries.getAllByText('Same Text')).toHaveLength(2);
|
||||
expect(rootQueries.getAllByDisplayValue('Same Value')).toHaveLength(2);
|
||||
expect(rootQueries.getAllByPlaceholder('Same Placeholder')).toHaveLength(2);
|
||||
|
||||
const firstQueries = within(rootQueries.getByA11yHint('first'));
|
||||
expect(firstQueries.getAllByText('Same Text')).toHaveLength(1);
|
||||
expect(firstQueries.getByText('Same Text')).toBeTruthy();
|
||||
expect(firstQueries.queryAllByText('Same Text')).toHaveLength(1);
|
||||
expect(firstQueries.queryByText('Same Text')).toBeTruthy();
|
||||
expect(firstQueries.getByDisplayValue('Same Value')).toBeTruthy();
|
||||
expect(firstQueries.getByPlaceholder('Same Placeholder')).toBeTruthy();
|
||||
|
||||
const secondQueries = within(rootQueries.getByA11yHint('second'));
|
||||
expect(secondQueries.getAllByText('Same Text')).toHaveLength(1);
|
||||
expect(secondQueries.getByText('Same Text')).toBeTruthy();
|
||||
expect(secondQueries.queryAllByText('Same Text')).toHaveLength(1);
|
||||
expect(secondQueries.queryByText('Same Text')).toBeTruthy();
|
||||
expect(secondQueries.getByDisplayValue('Same Value')).toBeTruthy();
|
||||
expect(secondQueries.getByPlaceholder('Same Placeholder')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('within() exposes a11y queries', () => {
|
||||
const rootQueries = render(
|
||||
<View>
|
||||
<View accessibilityHint="first">
|
||||
<TextInput
|
||||
value="Same Value"
|
||||
accessibilityLabel="Same Label"
|
||||
accessibilityHint="Same Hint"
|
||||
/>
|
||||
</View>
|
||||
<View accessibilityHint="second">
|
||||
<TextInput
|
||||
value="Same Value"
|
||||
accessibilityLabel="Same Label"
|
||||
accessibilityHint="Same Hint"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
expect(rootQueries.getAllByA11yLabel('Same Label')).toHaveLength(2);
|
||||
expect(rootQueries.getAllByA11yHint('Same Hint')).toHaveLength(2);
|
||||
|
||||
const firstQueries = within(rootQueries.getByA11yHint('first'));
|
||||
expect(firstQueries.getByA11yLabel('Same Label')).toBeTruthy();
|
||||
expect(firstQueries.getByA11yHint('Same Hint')).toBeTruthy();
|
||||
|
||||
const secondQueries = within(rootQueries.getByA11yHint('second'));
|
||||
expect(secondQueries.getAllByA11yLabel('Same Label')).toHaveLength(1);
|
||||
expect(secondQueries.getAllByA11yHint('Same Hint')).toHaveLength(1);
|
||||
});
|
||||
@@ -7,6 +7,7 @@ import flushMicrotasksQueue from './flushMicrotasksQueue';
|
||||
import render from './render';
|
||||
import shallow from './shallow';
|
||||
import waitForElement from './waitForElement';
|
||||
import within from './within';
|
||||
|
||||
export { act };
|
||||
export { cleanup };
|
||||
@@ -16,3 +17,4 @@ export { flushMicrotasksQueue };
|
||||
export { render };
|
||||
export { shallow };
|
||||
export { waitForElement };
|
||||
export { within };
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// @flow
|
||||
import { getByAPI } from './helpers/getByAPI';
|
||||
import { queryByAPI } from './helpers/queryByAPI';
|
||||
import a11yAPI from './helpers/a11yAPI';
|
||||
|
||||
export default function within(instance: ReactTestInstance) {
|
||||
return {
|
||||
...getByAPI(instance),
|
||||
...queryByAPI(instance),
|
||||
...a11yAPI(instance),
|
||||
};
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
debug,
|
||||
waitForElement,
|
||||
act,
|
||||
within,
|
||||
} from '../..';
|
||||
|
||||
interface HasRequiredProp {
|
||||
@@ -217,3 +218,115 @@ const waitByAll: Promise<Array<ReactTestInstance>> = waitForElement<
|
||||
act(() => {
|
||||
render(<TestComponent />);
|
||||
});
|
||||
|
||||
// within API
|
||||
const instance: ReactTestInstance = tree.getByText('<View />');
|
||||
const withinGetByText: ReactTestInstance = within(instance).getByText('Test');
|
||||
const withinGetAllByText: ReactTestInstance[] = within(instance).getAllByText(
|
||||
'Test'
|
||||
);
|
||||
const withinGetByDisplayValue: ReactTestInstance = within(
|
||||
instance
|
||||
).getByDisplayValue('Test');
|
||||
const withinGetAllByDisplayValue: ReactTestInstance[] = within(
|
||||
instance
|
||||
).getAllByDisplayValue('Test');
|
||||
const withinGetByPlaceholder: ReactTestInstance = within(
|
||||
instance
|
||||
).getByPlaceholder('Test');
|
||||
const withinGetAllByPlaceholder: ReactTestInstance[] = within(
|
||||
instance
|
||||
).getAllByPlaceholder('Test');
|
||||
const withinGetByTestId: ReactTestInstance = within(instance).getByTestId(
|
||||
'Test'
|
||||
);
|
||||
const withinGetAllByTestId: ReactTestInstance[] = within(
|
||||
instance
|
||||
).getAllByTestId('Test');
|
||||
|
||||
const withinQueryByText: ReactTestInstance | null = within(
|
||||
instance
|
||||
).queryByText('Test');
|
||||
const withinQueryAllByText: ReactTestInstance[] = within(
|
||||
instance
|
||||
).queryAllByText('Test');
|
||||
const withinQueryByDisplayValue: ReactTestInstance | null = within(
|
||||
instance
|
||||
).queryByDisplayValue('Test');
|
||||
const withinQueryAllByDisplayValue: ReactTestInstance[] = within(
|
||||
instance
|
||||
).queryAllByDisplayValue('Test');
|
||||
const withinQueryByPlaceholder: ReactTestInstance | null = within(
|
||||
instance
|
||||
).queryByPlaceholder('Test');
|
||||
const withinQueryAllByPlaceholder: ReactTestInstance[] = within(
|
||||
instance
|
||||
).queryAllByPlaceholder('Test');
|
||||
const withinQueryByTestId: ReactTestInstance | null = within(
|
||||
instance
|
||||
).queryByTestId('Test');
|
||||
const withinQueryAllByTestId: ReactTestInstance[] = within(
|
||||
instance
|
||||
).queryAllByTestId('Test');
|
||||
|
||||
const withinGetByA11yLabel: ReactTestInstance = within(instance).getByA11yLabel(
|
||||
'Test'
|
||||
);
|
||||
const withinGetAllByA11yLabel: ReactTestInstance[] = within(
|
||||
instance
|
||||
).getAllByA11yLabel('Test');
|
||||
const withinGetByA11yHint: ReactTestInstance = within(instance).getByA11yHint(
|
||||
'Test'
|
||||
);
|
||||
const withinGetAllByA11yHint: ReactTestInstance[] = within(
|
||||
instance
|
||||
).getAllByA11yHint('Test');
|
||||
const withinGetByA11yRole: ReactTestInstance = within(instance).getByA11yRole(
|
||||
'button'
|
||||
);
|
||||
const withinGetAllByA11yRole: ReactTestInstance[] = within(
|
||||
instance
|
||||
).getAllByA11yRole('button');
|
||||
const withinGetByA11yState: ReactTestInstance = within(
|
||||
instance
|
||||
).getByA11yState({ busy: true });
|
||||
const withinGetAllByA11yState: ReactTestInstance[] = within(
|
||||
instance
|
||||
).getAllByA11yState({ busy: true });
|
||||
const withinGetByA11yValue: ReactTestInstance = within(
|
||||
instance
|
||||
).getByA11yValue({ min: 10 });
|
||||
const withinGetAllByA11yValue: ReactTestInstance[] = within(
|
||||
instance
|
||||
).getAllByA11yValue({ min: 10 });
|
||||
|
||||
const withinQueryByA11yLabel: ReactTestInstance | null = within(
|
||||
instance
|
||||
).queryByA11yLabel('Test');
|
||||
const withinQueryAllByA11yLabel: ReactTestInstance[] = within(
|
||||
instance
|
||||
).queryAllByA11yLabel('Test');
|
||||
const withinQueryByA11yHint: ReactTestInstance | null = within(
|
||||
instance
|
||||
).queryByA11yHint('Test');
|
||||
const withinQueryAllByA11yHint: ReactTestInstance[] = within(
|
||||
instance
|
||||
).queryAllByA11yHint('Test');
|
||||
const withinQueryByA11yRole: ReactTestInstance | null = within(
|
||||
instance
|
||||
).queryByA11yRole('button');
|
||||
const withinQueryAllByA11yRole: ReactTestInstance[] = within(
|
||||
instance
|
||||
).queryAllByA11yRole('button');
|
||||
const withinQueryByA11yState: ReactTestInstance | null = within(
|
||||
instance
|
||||
).queryByA11yState({ busy: true });
|
||||
const withinQueryAllByA11yState: ReactTestInstance[] = within(
|
||||
instance
|
||||
).queryAllByA11yState({ busy: true });
|
||||
const withinQueryByA11yValue: ReactTestInstance | null = within(
|
||||
instance
|
||||
).queryByA11yValue({ min: 10 });
|
||||
const withinQueryAllByA11yValue: ReactTestInstance[] = within(
|
||||
instance
|
||||
).queryAllByA11yValue({ min: 10 });
|
||||
|
||||
Vendored
+4
-1
@@ -123,7 +123,9 @@ export interface RenderOptions {
|
||||
createNodeMock?: (element: React.ReactElement<any>) => any;
|
||||
}
|
||||
|
||||
export interface RenderAPI extends GetByAPI, QueryByAPI, A11yAPI {
|
||||
type Queries = GetByAPI & QueryByAPI & A11yAPI;
|
||||
|
||||
export interface RenderAPI extends Queries {
|
||||
update(nextElement: React.ReactElement<any>): void;
|
||||
rerender(nextElement: React.ReactElement<any>): void;
|
||||
unmount(nextElement?: React.ReactElement<any>): void;
|
||||
@@ -175,3 +177,4 @@ export declare const debug: DebugAPI;
|
||||
export declare const fireEvent: FireEventAPI;
|
||||
export declare const waitForElement: WaitForElementFunction;
|
||||
export declare const act: (callback: () => void) => Thenable;
|
||||
export declare const within: (instance: ReactTestInstance) => Queries;
|
||||
|
||||
@@ -341,6 +341,34 @@ test('waiting for an Banana to be ready', async () => {
|
||||
|
||||
If you're using Jest's [Timer Mocks](https://jestjs.io/docs/en/timer-mocks#docsNav), remember not to use `async/await` syntax as it will stall your tests.
|
||||
|
||||
## `within`
|
||||
|
||||
- [`Example code`](https://github.com/callstack/react-native-testing-library/blob/master/src/__tests__/within.test.js)
|
||||
|
||||
Defined as:
|
||||
|
||||
```jsx
|
||||
function within(instance: ReactTestInstance): Queries
|
||||
```
|
||||
|
||||
Perform [queries](./Queries.md) scoped to given element.
|
||||
|
||||
:::note
|
||||
Please note that additional `render` specific operations like `update`, `unmount`, `debug`, `toJSON` are _not_ included.
|
||||
:::
|
||||
|
||||
```jsx
|
||||
const detailsScreen = within(getByA11yHint('Details Screen'));
|
||||
expect(detailsScreen.getByText('Some Text')).toBeTruthy();
|
||||
expect(detailsScreen.getByDisplayValue('Some Value')).toBeTruthy();
|
||||
expect(detailsScreen.getByA11yLabel('Some Label')).toBeTruthy();
|
||||
expect(detailsScreen.getByA11yHint('Some Label')).toBeTruthy();
|
||||
```
|
||||
|
||||
Use cases for scoped queries include:
|
||||
* queries scoped to a single item inside a FlatList containing many items
|
||||
* queries scoped to a single screen in tests involving screen transitions (e.g. with react-navigation)
|
||||
|
||||
## `debug`
|
||||
|
||||
- [`Example code`](https://github.com/callstack/react-native-testing-library/blob/master/src/__tests__/debug.test.js)
|
||||
|
||||
Reference in New Issue
Block a user