From 4c63680ac8638ab1a502b2e86a34d30529afbe9c Mon Sep 17 00:00:00 2001 From: Kyaw Thura Date: Sat, 19 Aug 2023 22:10:28 +0700 Subject: [PATCH 1/2] feat: implement toBeEmptyElement --- .../__tests__/to-be-empty-element.test.tsx | 30 +++++++++++++++++++ src/matchers/extend-expect.ts | 2 ++ src/matchers/index.tsx | 1 + src/matchers/to-be-empty-element.tsx | 29 ++++++++++++++++++ 4 files changed, 62 insertions(+) create mode 100644 src/matchers/__tests__/to-be-empty-element.test.tsx create mode 100644 src/matchers/to-be-empty-element.tsx 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..46f5663f7 --- /dev/null +++ b/src/matchers/__tests__/to-be-empty-element.test.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { View } from 'react-native'; +import { render, screen } from '../..'; +import '../extend-expect'; + +test('.toBeEmptyElement', () => { + render( + + + + ); + + const empty = screen.getByTestId('empty'); + const notEmpty = screen.queryByTestId('not-empty'); + const nonExistentElement = screen.queryByTestId('not-exists'); + const fakeElement = { thisIsNot: 'an html element' }; + + expect(empty).toBeEmptyElement(); + expect(notEmpty).not.toBeEmptyElement(); + + expect(() => expect(empty).not.toBeEmptyElement()).toThrow(); + + expect(() => expect(notEmpty).toBeEmptyElement()).toThrow(); + + expect(() => expect(fakeElement).toBeEmptyElement()).toThrow(); + + expect(() => { + expect(nonExistentElement).toBeEmptyElement(); + }).toThrow(); +}); 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..bce2630d2 --- /dev/null +++ b/src/matchers/to-be-empty-element.tsx @@ -0,0 +1,29 @@ +import { ReactTestInstance } from 'react-test-renderer'; +import { matcherHint, printReceived } from 'jest-matcher-utils'; +import { getHostChildren } from '../helpers/component-tree'; +import { checkHostElement } from './utils'; + +export function toBeEmptyElement( + this: jest.MatcherContext, + element: ReactTestInstance +) { + checkHostElement(element, toBeEmptyElement, this); + + const pass = getHostChildren(element).length === 0; + + return { + pass, + message: () => { + return [ + matcherHint( + `${this.isNot ? '.not' : ''}.toBeEmptyElement`, + 'element', + '' + ), + '', + 'Received:', + ` ${printReceived(element.children)}`, + ].join('\n'); + }, + }; +} From df27859e6539cb50957203a6134a954392ddf7be Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 22 Aug 2023 12:03:25 +0200 Subject: [PATCH 2/2] refactor: tweak formatting and tests --- .../__tests__/to-be-empty-element.test.tsx | 61 ++++++++++++++++--- src/matchers/__tests__/utils.test.tsx | 2 +- src/matchers/extend-expect.d.ts | 4 +- src/matchers/to-be-empty-element.tsx | 10 +-- src/matchers/utils.tsx | 10 ++- 5 files changed, 67 insertions(+), 20 deletions(-) diff --git a/src/matchers/__tests__/to-be-empty-element.test.tsx b/src/matchers/__tests__/to-be-empty-element.test.tsx index 46f5663f7..c9e3867b5 100644 --- a/src/matchers/__tests__/to-be-empty-element.test.tsx +++ b/src/matchers/__tests__/to-be-empty-element.test.tsx @@ -3,7 +3,13 @@ import { View } from 'react-native'; import { render, screen } from '../..'; import '../extend-expect'; -test('.toBeEmptyElement', () => { +// 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( @@ -11,20 +17,55 @@ test('.toBeEmptyElement', () => { ); const empty = screen.getByTestId('empty'); - const notEmpty = screen.queryByTestId('not-empty'); - const nonExistentElement = screen.queryByTestId('not-exists'); - const fakeElement = { thisIsNot: 'an html element' }; - 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() - expect(() => expect(empty).not.toBeEmptyElement()).toThrow(); + Received: + " + `); +}); - expect(() => expect(notEmpty).toBeEmptyElement()).toThrow(); +test('toBeEmptyElement() ignores composite-only children', () => { + render( + + + + + + ); - expect(() => expect(fakeElement).toBeEmptyElement()).toThrow(); + 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(nonExistentElement).toBeEmptyElement(); - }).toThrow(); + 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/to-be-empty-element.tsx b/src/matchers/to-be-empty-element.tsx index bce2630d2..867b71392 100644 --- a/src/matchers/to-be-empty-element.tsx +++ b/src/matchers/to-be-empty-element.tsx @@ -1,7 +1,7 @@ import { ReactTestInstance } from 'react-test-renderer'; -import { matcherHint, printReceived } from 'jest-matcher-utils'; +import { matcherHint, RECEIVED_COLOR } from 'jest-matcher-utils'; import { getHostChildren } from '../helpers/component-tree'; -import { checkHostElement } from './utils'; +import { checkHostElement, formatElementArray } from './utils'; export function toBeEmptyElement( this: jest.MatcherContext, @@ -9,10 +9,10 @@ export function toBeEmptyElement( ) { checkHostElement(element, toBeEmptyElement, this); - const pass = getHostChildren(element).length === 0; + const hostChildren = getHostChildren(element); return { - pass, + pass: hostChildren.length === 0, message: () => { return [ matcherHint( @@ -22,7 +22,7 @@ export function toBeEmptyElement( ), '', 'Received:', - ` ${printReceived(element.children)}`, + `${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,