diff --git a/src/__tests__/byDisplayValue.test.js b/src/__tests__/byDisplayValue.test.js
index 4bdc743d9..953a3157e 100644
--- a/src/__tests__/byDisplayValue.test.js
+++ b/src/__tests__/byDisplayValue.test.js
@@ -100,3 +100,65 @@ test('findBy queries work asynchronously', async () => {
await expect(findByDisplayValue('Display Value')).resolves.toBeTruthy();
await expect(findAllByDisplayValue('Display Value')).resolves.toHaveLength(1);
}, 20000);
+
+test('all queries should respect accessibility', async () => {
+ const OtherComp = () => (
+
+
+
+
+
+
+
+
+ );
+ const Comp = () => (
+
+
+
+
+
+
+
+
+
+
+
+ );
+
+ const {
+ findAllByDisplayValue,
+ findByDisplayValue,
+ getAllByDisplayValue,
+ queryAllByDisplayValue,
+ getByDisplayValue,
+ queryByDisplayValue,
+ } = render(, { respectAccessibility: true });
+
+ await expect(getAllByDisplayValue('test_02')).toHaveLength(2);
+ await expect(() => getAllByDisplayValue('test_01')).toThrow(
+ 'Unable to find an element with displayValue: test_01'
+ );
+ await expect(() => getByDisplayValue('test_02')).toThrow(
+ 'Found multiple elements with display value: test_02 '
+ );
+ await expect(() => getByDisplayValue('test_01')).toThrow(
+ 'Unable to find an element with displayValue: test_01'
+ );
+ await expect(queryAllByDisplayValue('test_01')).toHaveLength(0);
+ await expect(queryAllByDisplayValue('test_02')).toHaveLength(2);
+ await expect(queryByDisplayValue('test_01')).toBeNull();
+ await expect(() => queryByDisplayValue('test_02')).toThrow(
+ 'Found multiple elements with display value: test_02 '
+ );
+ await expect(findAllByDisplayValue('test_01')).rejects.toEqual(
+ new Error('Unable to find an element with displayValue: test_01')
+ );
+ await expect(findAllByDisplayValue('test_02')).resolves.toHaveLength(2);
+ await expect(findByDisplayValue('test_01')).rejects.toEqual(
+ new Error('Unable to find an element with displayValue: test_01')
+ );
+ await expect(findByDisplayValue('test_02')).rejects.toEqual(
+ new Error('Found multiple elements with display value: test_02 ')
+ );
+});
diff --git a/src/__tests__/byPlaceholderText.test.js b/src/__tests__/byPlaceholderText.test.js
index 6e4ee1c00..120320bad 100644
--- a/src/__tests__/byPlaceholderText.test.js
+++ b/src/__tests__/byPlaceholderText.test.js
@@ -59,3 +59,67 @@ test('getAllByPlaceholderText, queryAllByPlaceholderText', () => {
expect(queryAllByPlaceholderText(/fresh/i)).toEqual(inputs);
expect(queryAllByPlaceholderText('no placeholder')).toHaveLength(0);
});
+
+test('queries should respect accessibility', async () => {
+ const OtherComp = () => (
+
+
+
+
+
+
+
+
+ );
+ const Comp = () => (
+
+
+
+
+
+
+
+
+
+
+
+ );
+
+ const {
+ findAllByPlaceholderText,
+ findByPlaceholderText,
+ getAllByPlaceholderText,
+ getByPlaceholderText,
+ queryAllByPlaceholderText,
+ queryByPlaceholderText,
+ } = render(, {
+ respectAccessibility: true,
+ });
+
+ await expect(() => getAllByPlaceholderText('test_01')).toThrow(
+ 'Unable to find an element with placeholder: test_01'
+ );
+ await expect(getAllByPlaceholderText('test_02')).toHaveLength(2);
+ await expect(() => getByPlaceholderText('test_01')).toThrow(
+ 'Unable to find an element with placeholder: test_01'
+ );
+ await expect(() => getByPlaceholderText('test_02')).toThrow(
+ 'Found multiple elements with placeholder: test_02 '
+ );
+ await expect(queryAllByPlaceholderText('test_01')).toHaveLength(0);
+ await expect(queryAllByPlaceholderText('test_02')).toHaveLength(2);
+ await expect(queryByPlaceholderText('test_01')).toBeNull();
+ await expect(() => queryByPlaceholderText('test_02')).toThrow(
+ 'Found multiple elements with placeholder: test_02 '
+ );
+ await expect(findAllByPlaceholderText('test_01')).rejects.toEqual(
+ new Error('Unable to find an element with placeholder: test_01')
+ );
+ await expect(findAllByPlaceholderText('test_02')).resolves.toHaveLength(2);
+ await expect(findByPlaceholderText('test_01')).rejects.toEqual(
+ new Error('Unable to find an element with placeholder: test_01')
+ );
+ await expect(findByPlaceholderText('test_02')).rejects.toEqual(
+ new Error('Found multiple elements with placeholder: test_02 ')
+ );
+});
diff --git a/src/__tests__/byTestId.test.js b/src/__tests__/byTestId.test.js
index 3f8363e5a..c5d913af5 100644
--- a/src/__tests__/byTestId.test.js
+++ b/src/__tests__/byTestId.test.js
@@ -133,3 +133,67 @@ test('findByTestId and findAllByTestId work asynchronously', async () => {
await expect(findByTestId('aTestId')).resolves.toBeTruthy();
await expect(findAllByTestId('aTestId')).resolves.toHaveLength(1);
}, 20000);
+
+test('queries should respect accessibility', async () => {
+ const OtherComp = () => (
+
+
+
+
+
+
+
+
+ );
+ const Comp = () => (
+
+
+
+
+
+
+
+
+
+
+
+ );
+
+ const {
+ findAllByTestId,
+ findByTestId,
+ getAllByTestId,
+ getByTestId,
+ queryAllByTestId,
+ queryByTestId,
+ } = render(, {
+ respectAccessibility: true,
+ });
+
+ await expect(() => getAllByTestId('test_01')).toThrow(
+ 'Unable to find an element with testID: test'
+ );
+ await expect(getAllByTestId('test_02')).toHaveLength(2);
+ await expect(() => getByTestId('test_01')).toThrow(
+ 'Unable to find an element with testID: test_01'
+ );
+ await expect(() => getByTestId('test_02')).toThrow(
+ 'Found multiple elements with testID: test_02'
+ );
+ await expect(queryAllByTestId('test_01')).toHaveLength(0);
+ await expect(queryAllByTestId('test_02')).toHaveLength(2);
+ await expect(queryByTestId('test_01')).toBeNull();
+ await expect(() => queryByTestId('test_02')).toThrow(
+ 'Found multiple elements with testID: test_02'
+ );
+ await expect(findAllByTestId('test_01')).rejects.toEqual(
+ new Error('Unable to find an element with testID: test_01')
+ );
+ await expect(findAllByTestId('test_02')).resolves.toHaveLength(2);
+ await expect(findByTestId('test_01')).rejects.toEqual(
+ new Error('Unable to find an element with testID: test_01')
+ );
+ await expect(findByTestId('test_02')).rejects.toEqual(
+ new Error('Found multiple elements with testID: test_02')
+ );
+});
diff --git a/src/__tests__/byText.test.js b/src/__tests__/byText.test.js
index 5b4ff36dd..1de4ae702 100644
--- a/src/__tests__/byText.test.js
+++ b/src/__tests__/byText.test.js
@@ -85,6 +85,68 @@ test('getAllByText, queryAllByText', () => {
expect(queryAllByText('InExistent')).toHaveLength(0);
});
+test('queries should respect accessibility', async () => {
+ const OtherComp = () => (
+
+
+ test_01
+
+
+ test_02
+
+
+ );
+ const Comp = () => (
+
+
+
+
+ test_02
+
+ test_01
+
+
+ );
+
+ const {
+ findAllByText,
+ findByText,
+ getAllByText,
+ getByText,
+ queryAllByText,
+ queryByText,
+ } = render(, {
+ respectAccessibility: true,
+ });
+
+ await expect(() => getAllByText('test_01')).toThrow(
+ 'Unable to find an element with text: test_01'
+ );
+ await expect(getAllByText('test_02')).toHaveLength(2);
+ await expect(() => getByText('test_01')).toThrow(
+ 'Unable to find an element with text: test_01'
+ );
+ await expect(() => getByText('test_02')).toThrow(
+ 'Found multiple elements with text: test_02'
+ );
+ await expect(queryAllByText('test_01')).toHaveLength(0);
+ await expect(queryAllByText('test_02')).toHaveLength(2);
+ await expect(queryByText('test_01')).toBeNull();
+ await expect(() => queryByText('test_02')).toThrow(
+ 'Found multiple elements with text: test_02'
+ );
+ await expect(findAllByText('test_01')).rejects.toEqual(
+ new Error('Unable to find an element with text: test_01')
+ );
+ await expect(findAllByText('test_02')).resolves.toHaveLength(2);
+ await expect(findByText('test_01')).rejects.toEqual(
+ new Error('Unable to find an element with text: test_01')
+ );
+ await expect(findByText('test_02')).rejects.toEqual(
+ new Error('Found multiple elements with text: test_02')
+ );
+});
+
test('findByText queries work asynchronously', async () => {
const options = { timeout: 10 }; // Short timeout so that this test runs quickly
const { rerender, findByText, findAllByText } = render();
diff --git a/src/render.js b/src/render.js
index e6323d58d..b8e4a4e8f 100644
--- a/src/render.js
+++ b/src/render.js
@@ -13,6 +13,7 @@ import debugDeep from './helpers/debugDeep';
type Options = {
wrapper?: React.ComponentType,
createNodeMock?: (element: React.Element) => any,
+ respectAccessibility?: boolean,
};
type TestRendererOptions = {
createNodeMock: (element: React.Element) => any,
@@ -24,7 +25,7 @@ type TestRendererOptions = {
*/
export default function render(
component: React.Element,
- { wrapper: Wrapper, createNodeMock }: Options = {}
+ { wrapper: Wrapper, createNodeMock, respectAccessibility }: Options = {}
): {
...FindByAPI,
...QueryByAPI,
@@ -45,7 +46,9 @@ export default function render(
createNodeMock ? { createNodeMock } : undefined
);
const update = updateWithAct(renderer, wrap);
- const instance = renderer.root;
+ const instance = respectAccessibility
+ ? appendFindAllTrap(renderer)
+ : renderer.root;
const unmount = () => {
act(() => {
renderer.unmount();
@@ -107,3 +110,64 @@ function debug(
debugImpl.shallow = (message) => debugShallow(instance, message);
return debugImpl;
}
+
+function appendFindAllTrap(renderer: ReactTestRenderer) {
+ return new Proxy(renderer.root, {
+ get(target, prop) {
+ const isFindAllProp = prop === 'findAll';
+
+ return isFindAllProp ? newFindAll(target) : target[prop];
+ },
+ });
+}
+
+function newFindAll(instance: ReactTestInstance) {
+ return (
+ predicate: (instance: ReactTestInstance) => boolean
+ ): ReactTestInstance[] => {
+ const elements = instance.findAll(predicate);
+
+ return elements.filter(isReactTestElementVisibleToAccessibility);
+ };
+}
+
+function isReactTestElementVisibleToAccessibility(
+ instance: ReactTestInstance
+): boolean {
+ const isElementVisible =
+ !accessibilityHiddenIOS(instance) &&
+ !accessibilityHiddenAndroid(instance) &&
+ !hiddenByStyles(instance);
+
+ if (!instance.parent) {
+ return isElementVisible;
+ }
+
+ const isParentVisible = isReactTestElementVisibleToAccessibility(
+ instance.parent
+ );
+
+ return isParentVisible && isElementVisible;
+}
+
+function accessibilityHiddenIOS(instance: ReactTestInstance): boolean {
+ const siblingHasAccessibilityViewIsModal =
+ instance.parent &&
+ instance.parent.children.some(
+ (c) =>
+ c.props && c.props.accessibilityViewIsModal && !Object.is(c, instance)
+ );
+
+ return (
+ instance.props.accessibilityElementsHidden ||
+ siblingHasAccessibilityViewIsModal
+ );
+}
+
+function accessibilityHiddenAndroid(instance: ReactTestInstance) {
+ return instance.props.importantForAccessibility === 'no-hide-descendants';
+}
+
+function hiddenByStyles(instance: ReactTestInstance) {
+ return instance.props.style && instance.props.style.display === 'none';
+}
diff --git a/typings/index.d.ts b/typings/index.d.ts
index c0188727d..6683e347f 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -296,6 +296,7 @@ export interface Thenable {
export interface RenderOptions {
wrapper?: React.ComponentType;
createNodeMock?: (element: React.ReactElement) => any;
+ respectAccessibility?: boolean;
}
type Debug = {