Skip to content

Commit cf08c06

Browse files
feat: toBeEmptyElement matcher (#1462)
* feat: implement toBeEmptyElement * refactor: tweak formatting and tests --------- Co-authored-by: Maciej Jastrzebski <[email protected]>
1 parent 4148038 commit cf08c06

File tree

7 files changed

+114
-5
lines changed

7 files changed

+114
-5
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import React from 'react';
2+
import { View } from 'react-native';
3+
import { render, screen } from '../..';
4+
import '../extend-expect';
5+
6+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
7+
function DoNotRenderChildren({ children }: { children: React.ReactNode }) {
8+
// Intentionally do not render children.
9+
return null;
10+
}
11+
12+
test('toBeEmptyElement()', () => {
13+
render(
14+
<View testID="not-empty">
15+
<View testID="empty" />
16+
</View>
17+
);
18+
19+
const empty = screen.getByTestId('empty');
20+
expect(empty).toBeEmptyElement();
21+
expect(() => expect(empty).not.toBeEmptyElement())
22+
.toThrowErrorMatchingInlineSnapshot(`
23+
"expect(element).not.toBeEmptyElement()
24+
25+
Received:
26+
(no elements)"
27+
`);
28+
29+
const notEmpty = screen.getByTestId('not-empty');
30+
expect(notEmpty).not.toBeEmptyElement();
31+
expect(() => expect(notEmpty).toBeEmptyElement())
32+
.toThrowErrorMatchingInlineSnapshot(`
33+
"expect(element).toBeEmptyElement()
34+
35+
Received:
36+
<View
37+
testID="empty"
38+
/>"
39+
`);
40+
});
41+
42+
test('toBeEmptyElement() ignores composite-only children', () => {
43+
render(
44+
<View testID="view">
45+
<DoNotRenderChildren>
46+
<View testID="not-rendered" />
47+
</DoNotRenderChildren>
48+
</View>
49+
);
50+
51+
const view = screen.getByTestId('view');
52+
expect(view).toBeEmptyElement();
53+
expect(() => expect(view).not.toBeEmptyElement())
54+
.toThrowErrorMatchingInlineSnapshot(`
55+
"expect(element).not.toBeEmptyElement()
56+
57+
Received:
58+
(no elements)"
59+
`);
60+
});
61+
62+
test('toBeEmptyElement() on null element', () => {
63+
expect(() => {
64+
expect(null).toBeEmptyElement();
65+
}).toThrowErrorMatchingInlineSnapshot(`
66+
"expect(received).toBeEmptyElement()
67+
68+
received value must be a host element.
69+
Received has value: null"
70+
`);
71+
});

src/matchers/__tests__/utils.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ function fakeMatcher() {
88
}
99

1010
test('formatElement', () => {
11-
expect(formatElement(null)).toMatchInlineSnapshot(`"null"`);
11+
expect(formatElement(null)).toMatchInlineSnapshot(`" null"`);
1212
});
1313

1414
test('checkHostElement allows host element', () => {

src/matchers/extend-expect.d.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import { TextMatch, TextMatchOptions } from '../matches';
2-
31
export interface JestNativeMatchers<R> {
42
toBeOnTheScreen(): R;
5-
toHaveTextContent(text: TextMatch, options?: TextMatchOptions): R;
3+
toBeEmptyElement(): R;
64
}
75

86
// Implicit Jest global `expect`.

src/matchers/extend-expect.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
/// <reference path="./extend-expect.d.ts" />
22

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

56
expect.extend({
67
toBeOnTheScreen,
8+
toBeEmptyElement,
79
});

src/matchers/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { toBeOnTheScreen } from './to-be-on-the-screen';
2+
export { toBeEmptyElement } from './to-be-empty-element';

src/matchers/to-be-empty-element.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { ReactTestInstance } from 'react-test-renderer';
2+
import { matcherHint, RECEIVED_COLOR } from 'jest-matcher-utils';
3+
import { getHostChildren } from '../helpers/component-tree';
4+
import { checkHostElement, formatElementArray } from './utils';
5+
6+
export function toBeEmptyElement(
7+
this: jest.MatcherContext,
8+
element: ReactTestInstance
9+
) {
10+
checkHostElement(element, toBeEmptyElement, this);
11+
12+
const hostChildren = getHostChildren(element);
13+
14+
return {
15+
pass: hostChildren.length === 0,
16+
message: () => {
17+
return [
18+
matcherHint(
19+
`${this.isNot ? '.not' : ''}.toBeEmptyElement`,
20+
'element',
21+
''
22+
),
23+
'',
24+
'Received:',
25+
`${RECEIVED_COLOR(formatElementArray(hostChildren))}`,
26+
].join('\n');
27+
},
28+
};
29+
}

src/matchers/utils.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export function checkHostElement(
6969
*/
7070
export function formatElement(element: ReactTestInstance | null) {
7171
if (element == null) {
72-
return 'null';
72+
return ' null';
7373
}
7474

7575
return redent(
@@ -92,6 +92,14 @@ export function formatElement(element: ReactTestInstance | null) {
9292
);
9393
}
9494

95+
export function formatElementArray(elements: ReactTestInstance[]) {
96+
if (elements.length === 0) {
97+
return ' (no elements)';
98+
}
99+
100+
return redent(elements.map(formatElement).join('\n'), 2);
101+
}
102+
95103
export function formatMessage(
96104
matcher: string,
97105
expectedLabel: string,

0 commit comments

Comments
 (0)