Skip to content

Commit

Permalink
feat: toBeEmptyElement matcher (#1462)
Browse files Browse the repository at this point in the history
* feat: implement toBeEmptyElement

* refactor: tweak formatting and tests

---------

Co-authored-by: Maciej Jastrzebski <[email protected]>
  • Loading branch information
kyawthura-gg and mdjastrzebski authored Aug 22, 2023
1 parent 4148038 commit cf08c06
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 5 deletions.
71 changes: 71 additions & 0 deletions src/matchers/__tests__/to-be-empty-element.test.tsx
Original file line number Diff line number Diff line change
@@ -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(
<View testID="not-empty">
<View testID="empty" />
</View>
);

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:
<View
testID="empty"
/>"
`);
});

test('toBeEmptyElement() ignores composite-only children', () => {
render(
<View testID="view">
<DoNotRenderChildren>
<View testID="not-rendered" />
</DoNotRenderChildren>
</View>
);

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"
`);
});
2 changes: 1 addition & 1 deletion src/matchers/__tests__/utils.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function fakeMatcher() {
}

test('formatElement', () => {
expect(formatElement(null)).toMatchInlineSnapshot(`"null"`);
expect(formatElement(null)).toMatchInlineSnapshot(`" null"`);
});

test('checkHostElement allows host element', () => {
Expand Down
4 changes: 1 addition & 3 deletions src/matchers/extend-expect.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { TextMatch, TextMatchOptions } from '../matches';

export interface JestNativeMatchers<R> {
toBeOnTheScreen(): R;
toHaveTextContent(text: TextMatch, options?: TextMatchOptions): R;
toBeEmptyElement(): R;
}

// Implicit Jest global `expect`.
Expand Down
2 changes: 2 additions & 0 deletions src/matchers/extend-expect.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/// <reference path="./extend-expect.d.ts" />

import { toBeOnTheScreen } from './to-be-on-the-screen';
import { toBeEmptyElement } from './to-be-empty-element';

expect.extend({
toBeOnTheScreen,
toBeEmptyElement,
});
1 change: 1 addition & 0 deletions src/matchers/index.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { toBeOnTheScreen } from './to-be-on-the-screen';
export { toBeEmptyElement } from './to-be-empty-element';
29 changes: 29 additions & 0 deletions src/matchers/to-be-empty-element.tsx
Original file line number Diff line number Diff line change
@@ -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');
},
};
}
10 changes: 9 additions & 1 deletion src/matchers/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export function checkHostElement(
*/
export function formatElement(element: ReactTestInstance | null) {
if (element == null) {
return 'null';
return ' null';
}

return redent(
Expand All @@ -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,
Expand Down

0 comments on commit cf08c06

Please sign in to comment.