diff --git a/src/matchers/__tests__/to-be-empty-element.test.tsx b/src/matchers/__tests__/to-be-empty-element.test.tsx new file mode 100644 index 000000000..c9e3867b5 --- /dev/null +++ b/src/matchers/__tests__/to-be-empty-element.test.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { View } from 'react-native'; +import { render, screen } from '../..'; +import '../extend-expect'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function DoNotRenderChildren({ children }: { children: React.ReactNode }) { + // Intentionally do not render children. + return null; +} + +test('toBeEmptyElement()', () => { + render( + + + + ); + + const empty = screen.getByTestId('empty'); + expect(empty).toBeEmptyElement(); + expect(() => expect(empty).not.toBeEmptyElement()) + .toThrowErrorMatchingInlineSnapshot(` + "expect(element).not.toBeEmptyElement() + + Received: + (no elements)" + `); + + const notEmpty = screen.getByTestId('not-empty'); + expect(notEmpty).not.toBeEmptyElement(); + expect(() => expect(notEmpty).toBeEmptyElement()) + .toThrowErrorMatchingInlineSnapshot(` + "expect(element).toBeEmptyElement() + + Received: + " + `); +}); + +test('toBeEmptyElement() ignores composite-only children', () => { + render( + + + + + + ); + + const view = screen.getByTestId('view'); + expect(view).toBeEmptyElement(); + expect(() => expect(view).not.toBeEmptyElement()) + .toThrowErrorMatchingInlineSnapshot(` + "expect(element).not.toBeEmptyElement() + + Received: + (no elements)" + `); +}); + +test('toBeEmptyElement() on null element', () => { + expect(() => { + expect(null).toBeEmptyElement(); + }).toThrowErrorMatchingInlineSnapshot(` + "expect(received).toBeEmptyElement() + + received value must be a host element. + Received has value: null" + `); +}); diff --git a/src/matchers/__tests__/utils.test.tsx b/src/matchers/__tests__/utils.test.tsx index 0e210459d..f73eb3ce1 100644 --- a/src/matchers/__tests__/utils.test.tsx +++ b/src/matchers/__tests__/utils.test.tsx @@ -8,7 +8,7 @@ function fakeMatcher() { } test('formatElement', () => { - expect(formatElement(null)).toMatchInlineSnapshot(`"null"`); + expect(formatElement(null)).toMatchInlineSnapshot(`" null"`); }); test('checkHostElement allows host element', () => { diff --git a/src/matchers/extend-expect.d.ts b/src/matchers/extend-expect.d.ts index 0fcec96c9..435d4c509 100644 --- a/src/matchers/extend-expect.d.ts +++ b/src/matchers/extend-expect.d.ts @@ -1,8 +1,6 @@ -import { TextMatch, TextMatchOptions } from '../matches'; - export interface JestNativeMatchers { toBeOnTheScreen(): R; - toHaveTextContent(text: TextMatch, options?: TextMatchOptions): R; + toBeEmptyElement(): R; } // Implicit Jest global `expect`. diff --git a/src/matchers/extend-expect.ts b/src/matchers/extend-expect.ts index 1a998d336..dc7744189 100644 --- a/src/matchers/extend-expect.ts +++ b/src/matchers/extend-expect.ts @@ -1,7 +1,9 @@ /// import { toBeOnTheScreen } from './to-be-on-the-screen'; +import { toBeEmptyElement } from './to-be-empty-element'; expect.extend({ toBeOnTheScreen, + toBeEmptyElement, }); diff --git a/src/matchers/index.tsx b/src/matchers/index.tsx index 8d1540b14..34adad661 100644 --- a/src/matchers/index.tsx +++ b/src/matchers/index.tsx @@ -1 +1,2 @@ export { toBeOnTheScreen } from './to-be-on-the-screen'; +export { toBeEmptyElement } from './to-be-empty-element'; diff --git a/src/matchers/to-be-empty-element.tsx b/src/matchers/to-be-empty-element.tsx new file mode 100644 index 000000000..867b71392 --- /dev/null +++ b/src/matchers/to-be-empty-element.tsx @@ -0,0 +1,29 @@ +import { ReactTestInstance } from 'react-test-renderer'; +import { matcherHint, RECEIVED_COLOR } from 'jest-matcher-utils'; +import { getHostChildren } from '../helpers/component-tree'; +import { checkHostElement, formatElementArray } from './utils'; + +export function toBeEmptyElement( + this: jest.MatcherContext, + element: ReactTestInstance +) { + checkHostElement(element, toBeEmptyElement, this); + + const hostChildren = getHostChildren(element); + + return { + pass: hostChildren.length === 0, + message: () => { + return [ + matcherHint( + `${this.isNot ? '.not' : ''}.toBeEmptyElement`, + 'element', + '' + ), + '', + 'Received:', + `${RECEIVED_COLOR(formatElementArray(hostChildren))}`, + ].join('\n'); + }, + }; +} diff --git a/src/matchers/utils.tsx b/src/matchers/utils.tsx index ef5b10f91..f6808b09c 100644 --- a/src/matchers/utils.tsx +++ b/src/matchers/utils.tsx @@ -69,7 +69,7 @@ export function checkHostElement( */ export function formatElement(element: ReactTestInstance | null) { if (element == null) { - return 'null'; + return ' null'; } return redent( @@ -92,6 +92,14 @@ export function formatElement(element: ReactTestInstance | null) { ); } +export function formatElementArray(elements: ReactTestInstance[]) { + if (elements.length === 0) { + return ' (no elements)'; + } + + return redent(elements.map(formatElement).join('\n'), 2); +} + export function formatMessage( matcher: string, expectedLabel: string,