diff --git a/src/__tests__/to-be-visible.tsx b/src/__tests__/to-be-visible.tsx index 281c02a..cffe95e 100644 --- a/src/__tests__/to-be-visible.tsx +++ b/src/__tests__/to-be-visible.tsx @@ -124,7 +124,7 @@ describe('.toBeVisible', () => { expect(() => expect(null).toBeVisible()).toThrowErrorMatchingInlineSnapshot(` "expect(received).toBeVisible() - received value must be a React Element. + received value must be a host element or composite Text/TextInput element Received has value: null" `); }); diff --git a/src/__tests__/to-have-text-content.tsx b/src/__tests__/to-have-text-content.tsx index 3770d56..2893abf 100644 --- a/src/__tests__/to-have-text-content.tsx +++ b/src/__tests__/to-have-text-content.tsx @@ -74,8 +74,8 @@ describe('.toHaveTextContent', () => { test('can handle multiple levels with no explicit children prop', () => { const NoChildren = ({ text }: { text: string }) => {text}; const answer = 'Answer'; - const { container } = render( - + const { getByTestId } = render( + {answer} {': '} @@ -86,7 +86,7 @@ describe('.toHaveTextContent', () => { , ); - expect(container).toHaveTextContent(/^Answer: 42$/); + expect(getByTestId('subject')).toHaveTextContent(/^Answer: 42$/); }); test('throws when no match is found', () => { diff --git a/src/__tests__/utils.ts b/src/__tests__/utils.ts index 298b492..29c622e 100644 --- a/src/__tests__/utils.ts +++ b/src/__tests__/utils.ts @@ -1,25 +1,67 @@ +import { View, Text, TextInput, Pressable, TouchableOpacity } from 'react-native'; import { checkReactElement, isEmpty } from '../utils'; describe('checkReactElement', () => { - test('it does not throw an error for valid native primitives', () => { - expect(() => { - // @ts-expect-error Argument of type '{ type: "text"; }' is not assignable to parameter of type 'ReactTestInstance'. Type '{ type: "text"; }' is missing the following properties from type 'ReactTestInstance': instance, props, parent, children, and 6 more.ts(2345) - checkReactElement({ type: 'Text' }, () => {}, null); - }).not.toThrow(); + test('ReactTestInstance does not throw for host elements', () => { + expect(() => + // @ts-expect-error Passing incorrect Jest Matcher data + checkReactElement({ type: 'View' }, () => {}, {}), + ).not.toThrow(); + expect(() => + // @ts-expect-error Passing incorrect Jest Matcher data + checkReactElement({ type: 'TextInput' }, () => {}, {}), + ).not.toThrow(); + expect(() => + // @ts-expect-error Passing incorrect Jest Matcher data + checkReactElement({ type: 'View' }, () => {}, {}), + ).not.toThrow(); }); - test('ReactTestInstance does not throw', () => { - expect(() => { - // @ts-expect-error Argument of type '{ _fiber: {}; }' is not assignable to parameter of type 'ReactTestInstance'. Object literal may only specify known properties, and '_fiber' does not exist in type 'ReactTestInstance'.ts(2345) - checkReactElement({ _fiber: {} }, () => {}, null); - }).not.toThrow(); + test('ReactTestInstance does not throw for composite Text elements', () => { + expect(() => + // @ts-expect-error Passing incorrect Jest Matcher data + checkReactElement({ type: Text }, () => {}, {}), + ).not.toThrow(); }); - test('it does throw an error for invalid native primitives', () => { - expect(() => { - // @ts-expect-error Argument of type '{ type: "button"; }' is not assignable to parameter of type 'ReactTestInstance'. Type '{ type: "button"; }' is missing the following properties from type 'ReactTestInstance': instance, props, parent, children, and 6 more.ts(2345) - checkReactElement({ type: 'Button' }, () => {}, null); - }).toThrow(); + test('ReactTestInstance does not throw for composite TextInput elements', () => { + expect(() => + // @ts-expect-error Passing incorrect Jest Matcher data + checkReactElement({ type: TextInput }, () => {}, {}), + ).not.toThrow(); + }); + + test('it does throw for composite elements', () => { + expect(() => + // @ts-expect-error Incorrect Test Renderer typings + checkReactElement({ type: View }, () => {}, {}), + ).toThrowErrorMatchingInlineSnapshot(` + "expect(received).() + + received value must be a host element or composite Text/TextInput element + Received has type: object + Received has value: {"type": [Function Component]}" + `); + expect(() => + // @ts-expect-error Incorrect Test Renderer typings + checkReactElement({ type: Pressable }, () => {}, {}), + ).toThrowErrorMatchingInlineSnapshot(` + "expect(received).() + + received value must be a host element or composite Text/TextInput element + Received has type: object + Received has value: {"type": {"$$typeof": Symbol(react.memo), "compare": null, "type": {"$$typeof": Symbol(react.forward_ref), "render": [Function Pressable]}}}" + `); + expect(() => + // @ts-expect-error Incorrect Test Renderer typings + checkReactElement({ type: TouchableOpacity }, () => {}, {}), + ).toThrowErrorMatchingInlineSnapshot(` + "expect(received).() + + received value must be a host element or composite Text/TextInput element + Received has type: object + Received has value: {"type": {"$$typeof": Symbol(react.forward_ref), "render": [Function anonymous]}}" + `); }); }); diff --git a/src/utils.ts b/src/utils.ts index f591dba..b83b07d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,3 +1,5 @@ +import { Text, TextInput } from 'react-native'; +import type { ReactTestInstance } from 'react-test-renderer'; import redent from 'redent'; import { RECEIVED_COLOR as receivedColor, @@ -8,23 +10,10 @@ import { stringify, } from 'jest-matcher-utils'; import prettyFormat, { plugins } from 'pretty-format'; -import type { ReactTestInstance } from 'react-test-renderer'; +import { isHostElement } from './component-tree'; const { ReactTestComponent, ReactElement } = plugins; -const VALID_ELEMENTS = [ - 'Image', - 'Text', - 'TextInput', - 'Modal', - 'View', - 'RefreshControl', - 'ScrollView', - 'ActivityIndicator', - 'ListView', - 'ListViewDataSource', -]; - class ReactElementTypeError extends Error { constructor(received: unknown, matcherFn: jest.CustomMatcher, context: jest.MatcherContext) { super(); @@ -44,7 +33,9 @@ class ReactElementTypeError extends Error { this.message = [ matcherHint(`${context.isNot ? '.not' : ''}.${matcherFn.name}`, 'received', ''), '', - `${receivedColor('received')} value must be a React Element.`, + `${receivedColor( + 'received', + )} value must be a host element or composite Text/TextInput element`, withType, ].join('\n'); } @@ -59,8 +50,7 @@ function checkReactElement( throw new ReactElementTypeError(element, matcherFn, context); } - // @ts-expect-error internal _fiber property of ReactTestInstance - if (!element._fiber && !VALID_ELEMENTS.includes(element.type.toString())) { + if (!isHostElement(element) && element.type !== Text && element.type !== TextInput) { throw new ReactElementTypeError(element, matcherFn, context); } }