diff --git a/README.md b/README.md
index 6373816d4..b95b05d41 100644
--- a/README.md
+++ b/README.md
@@ -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.
diff --git a/src/__tests__/within.test.js b/src/__tests__/within.test.js
new file mode 100644
index 000000000..97bf5252b
--- /dev/null
+++ b/src/__tests__/within.test.js
@@ -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(
+
+
+ Same Text
+
+
+
+ Same Text
+
+
+
+ );
+
+ 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(
+
+
+
+
+
+
+
+
+ );
+
+ 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);
+});
diff --git a/src/index.js b/src/index.js
index f4263745c..2e7ac9f20 100644
--- a/src/index.js
+++ b/src/index.js
@@ -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 };
diff --git a/src/within.js b/src/within.js
new file mode 100644
index 000000000..a7dd00b40
--- /dev/null
+++ b/src/within.js
@@ -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),
+ };
+}
diff --git a/typings/__tests__/index.test.tsx b/typings/__tests__/index.test.tsx
index c4a5ad020..5e0db16f4 100644
--- a/typings/__tests__/index.test.tsx
+++ b/typings/__tests__/index.test.tsx
@@ -9,6 +9,7 @@ import {
debug,
waitForElement,
act,
+ within,
} from '../..';
interface HasRequiredProp {
@@ -217,3 +218,115 @@ const waitByAll: Promise> = waitForElement<
act(() => {
render();
});
+
+// within API
+const instance: ReactTestInstance = tree.getByText('');
+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 });
diff --git a/typings/index.d.ts b/typings/index.d.ts
index 1c03ed410..4afc01665 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -123,7 +123,9 @@ export interface RenderOptions {
createNodeMock?: (element: React.ReactElement) => any;
}
-export interface RenderAPI extends GetByAPI, QueryByAPI, A11yAPI {
+type Queries = GetByAPI & QueryByAPI & A11yAPI;
+
+export interface RenderAPI extends Queries {
update(nextElement: React.ReactElement): void;
rerender(nextElement: React.ReactElement): void;
unmount(nextElement?: React.ReactElement): 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;
diff --git a/website/docs/API.md b/website/docs/API.md
index 8cf47a27c..f3037d779 100644
--- a/website/docs/API.md
+++ b/website/docs/API.md
@@ -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)