Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/__tests__/jest-native.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ test('jest-native matchers work correctly', () => {
expect(getByText('Disabled Button')).toBeDisabled();
expect(getByText('Enabled Button')).not.toBeDisabled();

expect(getByA11yHint('Empty Text')).toBeEmpty();
expect(getByA11yHint('Empty View')).toBeEmpty();
expect(getByA11yHint('Not Empty Text')).not.toBeEmpty();
expect(getByA11yHint('Not Empty View')).not.toBeEmpty();
expect(getByA11yHint('Empty Text')).toBeEmptyElement();
expect(getByA11yHint('Empty View')).toBeEmptyElement();
expect(getByA11yHint('Not Empty Text')).not.toBeEmptyElement();
expect(getByA11yHint('Not Empty View')).not.toBeEmptyElement();

expect(getByA11yHint('Container View')).toContainElement(
// $FlowFixMe - TODO: fix @testing-library/jest-native flow typings
Expand Down
95 changes: 95 additions & 0 deletions src/__tests__/tripwire.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import * as React from 'react';
import { View, Text, TextInput } from 'react-native';
import { render } from '..';
import { getHostSelf } from '../helpers/component-tree';

test('React Native tripwire: <View> renders single host element', () => {
const view = render(<View testID="test" />);
const hostView = view.getByTestId('test');
expect(getHostSelf(hostView)).toBe(hostView);

expect(view.toJSON()).toMatchInlineSnapshot(`
<View
testID="test"
/>
`);
});

test('React Native tripwire: <Text> renders single host element', () => {
const view = render(<Text testID="test">Hello</Text>);
const compositeView = view.getByText('Hello');
const hostView = view.getByTestId('test');
expect(getHostSelf(compositeView)).toBe(hostView);

expect(view.toJSON()).toMatchInlineSnapshot(`
<Text
testID="test"
>
Hello
</Text>
`);
});

test('React Native tripwire: nested <Text> renders single host element', () => {
const view = render(
<Text testID="test">
<Text testID="before">Before</Text>
Hello
<Text testID="after">
<Text testID="deeplyNested">Deeply nested</Text>
</Text>
</Text>
);
expect(getHostSelf(view.getByText('Hello'))).toBe(view.getByTestId('test'));
expect(getHostSelf(view.getByText('Before'))).toBe(
view.getByTestId('before')
);
expect(getHostSelf(view.getByText('Deeply nested'))).toBe(
view.getByTestId('deeplyNested')
);

expect(view.toJSON()).toMatchInlineSnapshot(`
<Text
testID="test"
>
<Text
testID="before"
>
Before
</Text>
Hello
<Text
testID="after"
>
<Text
testID="deeplyNested"
>
Deeply nested
</Text>
</Text>
</Text>
`);
});

test('React Native tripwire: <TextInput> renders single host element', () => {
const view = render(
<TextInput
testID="test"
defaultValue="default"
value="currentValue"
placeholder="Placeholder"
/>
);
const compositeView = view.getByPlaceholderText('Placeholder');
const hostView = view.getByTestId('test');
expect(getHostSelf(compositeView)).toBe(hostView);

expect(view.toJSON()).toMatchInlineSnapshot(`
<TextInput
defaultValue="default"
placeholder="Placeholder"
testID="test"
value="currentValue"
/>
`);
});
82 changes: 80 additions & 2 deletions src/helpers/__tests__/component-tree.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@ import { render } from '../..';
import {
getHostChildren,
getHostParent,
getHostSelf,
getHostSelves,
getHostSiblings,
} from '../component-tree';

function ZeroHostChildren() {
return <></>;
}

function MultipleHostChildren() {
return (
<>
Expand Down Expand Up @@ -89,6 +94,29 @@ test('returns host children for composite component', () => {
]);
});

test('returns host self for host components', () => {
const view = render(
<View testID="grandparent">
<View testID="parent">
<View testID="subject" />
<View testID="sibling" />
</View>
</View>
);

const hostSubject = view.getByTestId('subject');
expect(getHostSelf(hostSubject)).toEqual(hostSubject);

const hostSibling = view.getByTestId('sibling');
expect(getHostSelf(hostSibling)).toEqual(hostSibling);

const hostParent = view.getByTestId('parent');
expect(getHostSelf(hostParent)).toEqual(hostParent);

const hostGrandparent = view.getByTestId('grandparent');
expect(getHostSelf(hostGrandparent)).toEqual(hostGrandparent);
});

test('returns host selves for host components', () => {
const view = render(
<View testID="grandparent">
Expand All @@ -112,6 +140,51 @@ test('returns host selves for host components', () => {
expect(getHostSelves(hostGrandparent)).toEqual([hostGrandparent]);
});

test('returns host self for React Native composite components', () => {
const view = render(
<View testID="parent">
<Text testID="text">Text</Text>
<TextInput
testID="textInput"
defaultValue="TextInputValue"
placeholder="TextInputPlaceholder"
/>
</View>
);

const compositeText = view.getByText('Text');
const hostText = view.getByTestId('text');
expect(getHostSelf(compositeText)).toEqual(hostText);

const compositeTextInputByValue = view.getByDisplayValue('TextInputValue');
const compositeTextInputByPlaceholder = view.getByPlaceholderText(
'TextInputPlaceholder'
);
const hostTextInput = view.getByTestId('textInput');
expect(getHostSelf(compositeTextInputByValue)).toEqual(hostTextInput);
expect(getHostSelf(compositeTextInputByPlaceholder)).toEqual(hostTextInput);
});

test('throws on non-single host self element for custom composite components', () => {
const view = render(
<View testID="parent">
<ZeroHostChildren />
<MultipleHostChildren />
</View>
);

const zeroCompositeComponent = view.UNSAFE_getByType(ZeroHostChildren);
expect(() => getHostSelf(zeroCompositeComponent)).toThrow(
'Expected exactly one host element, but found none.'
);

const multipleCompositeComponent =
view.UNSAFE_getByType(MultipleHostChildren);
expect(() => getHostSelf(multipleCompositeComponent)).toThrow(
'Expected exactly one host element, but found 3.'
);
});

test('returns host selves for React Native composite components', () => {
const view = render(
<View testID="parent">
Expand Down Expand Up @@ -142,16 +215,21 @@ test('returns host selves for React Native composite components', () => {
test('returns host selves for custom composite components', () => {
const view = render(
<View testID="parent">
<ZeroHostChildren />
<MultipleHostChildren />
<View testID="sibling" />
</View>
);

const compositeComponent = view.UNSAFE_getByType(MultipleHostChildren);
const zeroCompositeComponent = view.UNSAFE_getByType(ZeroHostChildren);
expect(getHostSelves(zeroCompositeComponent)).toEqual([]);

const multipleCompositeComponent =
view.UNSAFE_getByType(MultipleHostChildren);
const hostChild1 = view.getByTestId('child1');
const hostChild2 = view.getByTestId('child2');
const hostChild3 = view.getByTestId('child3');
expect(getHostSelves(compositeComponent)).toEqual([
expect(getHostSelves(multipleCompositeComponent)).toEqual([
hostChild1,
hostChild2,
hostChild3,
Expand Down
26 changes: 26 additions & 0 deletions src/helpers/component-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,32 @@ export function getHostChildren(
return hostChildren;
}

/**
* Return a single host element that represent the passed host or composite element.
*
* @param element The element start traversing from.
* @throws Error if the passed element is a composite element and has no host children or has more than one host child.
* @returns If the passed element is a host element, it will return itself, if the passed element is a composite
* element, it will return a single host descendant.
*/
export function getHostSelf(
element: ReactTestInstance | null
): ReactTestInstance {
const hostSelves = getHostSelves(element);

if (hostSelves.length === 0) {
throw new Error(`Expected exactly one host element, but found none.`);
}

if (hostSelves.length > 1) {
throw new Error(
`Expected exactly one host element, but found ${hostSelves.length}.`
);
}

return hostSelves[0];
}

/**
* Return the array of host elements that represent the passed element.
*
Expand Down