Skip to content

Commit a9f32a6

Browse files
feat: toBeExpanded matcher (#1497)
* feat: toBeExpanded matcher * feat: toBeCollapsed matcher * chore: shorten syntax for isElementCollapsed check * refactor: clean up --------- Co-authored-by: Maciej Jastrzebski <mdjastrzebski@gmail.com>
1 parent d898a9d commit a9f32a6

File tree

8 files changed

+271
-0
lines changed

8 files changed

+271
-0
lines changed

src/helpers/accessiblity.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,20 @@ export function isElementBusy(
213213
return ariaBusy ?? accessibilityState?.busy ?? false;
214214
}
215215

216+
export function isElementCollapsed(
217+
element: ReactTestInstance
218+
): NonNullable<AccessibilityState['expanded']> {
219+
const { accessibilityState, 'aria-expanded': ariaExpanded } = element.props;
220+
return (ariaExpanded ?? accessibilityState?.expanded) === false;
221+
}
222+
223+
export function isElementExpanded(
224+
element: ReactTestInstance
225+
): NonNullable<AccessibilityState['expanded']> {
226+
const { accessibilityState, 'aria-expanded': ariaExpanded } = element.props;
227+
return ariaExpanded ?? accessibilityState?.expanded ?? false;
228+
}
229+
216230
export function isElementSelected(
217231
element: ReactTestInstance
218232
): NonNullable<AccessibilityState['selected']> {
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import * as React from 'react';
2+
import { View } from 'react-native';
3+
import { render, screen } from '../..';
4+
import '../extend-expect';
5+
6+
test('toBeCollapsed() basic case', () => {
7+
render(
8+
<>
9+
<View testID="expanded" accessibilityState={{ expanded: true }} />
10+
<View testID="expanded-aria" aria-expanded />
11+
<View testID="not-expanded" accessibilityState={{ expanded: false }} />
12+
<View testID="not-expanded-aria" aria-expanded={false} />
13+
<View testID="default" />
14+
</>
15+
);
16+
17+
expect(screen.getByTestId('expanded')).not.toBeCollapsed();
18+
expect(screen.getByTestId('expanded-aria')).not.toBeCollapsed();
19+
expect(screen.getByTestId('not-expanded')).toBeCollapsed();
20+
expect(screen.getByTestId('not-expanded-aria')).toBeCollapsed();
21+
expect(screen.getByTestId('default')).not.toBeCollapsed();
22+
});
23+
24+
test('toBeCollapsed() error messages', () => {
25+
render(
26+
<>
27+
<View testID="expanded" accessibilityState={{ expanded: true }} />
28+
<View testID="expanded-aria" aria-expanded />
29+
<View testID="not-expanded" accessibilityState={{ expanded: false }} />
30+
<View testID="not-expanded-aria" aria-expanded={false} />
31+
<View testID="default" />
32+
</>
33+
);
34+
35+
expect(() => expect(screen.getByTestId('expanded')).toBeCollapsed())
36+
.toThrowErrorMatchingInlineSnapshot(`
37+
"expect(element).toBeCollapsed()
38+
39+
Received element is not collapsed:
40+
<View
41+
accessibilityState={
42+
{
43+
"expanded": true,
44+
}
45+
}
46+
testID="expanded"
47+
/>"
48+
`);
49+
50+
expect(() => expect(screen.getByTestId('expanded-aria')).toBeCollapsed())
51+
.toThrowErrorMatchingInlineSnapshot(`
52+
"expect(element).toBeCollapsed()
53+
54+
Received element is not collapsed:
55+
<View
56+
aria-expanded={true}
57+
testID="expanded-aria"
58+
/>"
59+
`);
60+
61+
expect(() => expect(screen.getByTestId('not-expanded')).not.toBeCollapsed())
62+
.toThrowErrorMatchingInlineSnapshot(`
63+
"expect(element).not.toBeCollapsed()
64+
65+
Received element is collapsed:
66+
<View
67+
accessibilityState={
68+
{
69+
"expanded": false,
70+
}
71+
}
72+
testID="not-expanded"
73+
/>"
74+
`);
75+
76+
expect(() =>
77+
expect(screen.getByTestId('not-expanded-aria')).not.toBeCollapsed()
78+
).toThrowErrorMatchingInlineSnapshot(`
79+
"expect(element).not.toBeCollapsed()
80+
81+
Received element is collapsed:
82+
<View
83+
aria-expanded={false}
84+
testID="not-expanded-aria"
85+
/>"
86+
`);
87+
88+
expect(() => expect(screen.getByTestId('default')).toBeCollapsed())
89+
.toThrowErrorMatchingInlineSnapshot(`
90+
"expect(element).toBeCollapsed()
91+
92+
Received element is not collapsed:
93+
<View
94+
testID="default"
95+
/>"
96+
`);
97+
});
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import * as React from 'react';
2+
import { View } from 'react-native';
3+
import { render, screen } from '../..';
4+
import '../extend-expect';
5+
6+
test('toBeExpanded() basic case', () => {
7+
render(
8+
<>
9+
<View testID="expanded" accessibilityState={{ expanded: true }} />
10+
<View testID="expanded-aria" aria-expanded />
11+
<View testID="not-expanded" accessibilityState={{ expanded: false }} />
12+
<View testID="not-expanded-aria" aria-expanded={false} />
13+
<View testID="default" />
14+
</>
15+
);
16+
17+
expect(screen.getByTestId('expanded')).toBeExpanded();
18+
expect(screen.getByTestId('expanded-aria')).toBeExpanded();
19+
expect(screen.getByTestId('not-expanded')).not.toBeExpanded();
20+
expect(screen.getByTestId('not-expanded-aria')).not.toBeExpanded();
21+
expect(screen.getByTestId('default')).not.toBeExpanded();
22+
});
23+
24+
test('toBeExpanded() error messages', () => {
25+
render(
26+
<>
27+
<View testID="expanded" accessibilityState={{ expanded: true }} />
28+
<View testID="expanded-aria" aria-expanded />
29+
<View testID="not-expanded" accessibilityState={{ expanded: false }} />
30+
<View testID="not-expanded-aria" aria-expanded={false} />
31+
<View testID="default" />
32+
</>
33+
);
34+
35+
expect(() => expect(screen.getByTestId('expanded')).not.toBeExpanded())
36+
.toThrowErrorMatchingInlineSnapshot(`
37+
"expect(element).not.toBeExpanded()
38+
39+
Received element is expanded:
40+
<View
41+
accessibilityState={
42+
{
43+
"expanded": true,
44+
}
45+
}
46+
testID="expanded"
47+
/>"
48+
`);
49+
50+
expect(() => expect(screen.getByTestId('expanded-aria')).not.toBeExpanded())
51+
.toThrowErrorMatchingInlineSnapshot(`
52+
"expect(element).not.toBeExpanded()
53+
54+
Received element is expanded:
55+
<View
56+
aria-expanded={true}
57+
testID="expanded-aria"
58+
/>"
59+
`);
60+
61+
expect(() => expect(screen.getByTestId('not-expanded')).toBeExpanded())
62+
.toThrowErrorMatchingInlineSnapshot(`
63+
"expect(element).toBeExpanded()
64+
65+
Received element is not expanded:
66+
<View
67+
accessibilityState={
68+
{
69+
"expanded": false,
70+
}
71+
}
72+
testID="not-expanded"
73+
/>"
74+
`);
75+
76+
expect(() => expect(screen.getByTestId('not-expanded-aria')).toBeExpanded())
77+
.toThrowErrorMatchingInlineSnapshot(`
78+
"expect(element).toBeExpanded()
79+
80+
Received element is not expanded:
81+
<View
82+
aria-expanded={false}
83+
testID="not-expanded-aria"
84+
/>"
85+
`);
86+
87+
expect(() => expect(screen.getByTestId('default')).toBeExpanded())
88+
.toThrowErrorMatchingInlineSnapshot(`
89+
"expect(element).toBeExpanded()
90+
91+
Received element is not expanded:
92+
<View
93+
testID="default"
94+
/>"
95+
`);
96+
});

src/matchers/extend-expect.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ import type { Style } from './to-have-style';
66
export interface JestNativeMatchers<R> {
77
toBeOnTheScreen(): R;
88
toBeChecked(): R;
9+
toBeCollapsed(): R;
910
toBeDisabled(): R;
1011
toBeBusy(): R;
1112
toBeEmptyElement(): R;
1213
toBeEnabled(): R;
14+
toBeExpanded(): R;
1315
toBePartiallyChecked(): R;
1416
toBeSelected(): R;
1517
toBeVisible(): R;

src/matchers/extend-expect.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
import { toBeOnTheScreen } from './to-be-on-the-screen';
44
import { toBeChecked } from './to-be-checked';
5+
import { toBeCollapsed } from './to-be-collapsed';
56
import { toBeDisabled, toBeEnabled } from './to-be-disabled';
67
import { toBeBusy } from './to-be-busy';
78
import { toBeEmptyElement } from './to-be-empty-element';
9+
import { toBeExpanded } from './to-be-expanded';
810
import { toBePartiallyChecked } from './to-be-partially-checked';
911
import { toBeSelected } from './to-be-selected';
1012
import { toBeVisible } from './to-be-visible';
@@ -17,10 +19,12 @@ import { toHaveTextContent } from './to-have-text-content';
1719
expect.extend({
1820
toBeOnTheScreen,
1921
toBeChecked,
22+
toBeCollapsed,
2023
toBeDisabled,
2124
toBeBusy,
2225
toBeEmptyElement,
2326
toBeEnabled,
27+
toBeExpanded,
2428
toBePartiallyChecked,
2529
toBeSelected,
2630
toBeVisible,

src/matchers/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
export { toBeBusy } from './to-be-busy';
22
export { toBeChecked } from './to-be-checked';
3+
export { toBeCollapsed } from './to-be-collapsed';
34
export { toBeDisabled, toBeEnabled } from './to-be-disabled';
45
export { toBeEmptyElement } from './to-be-empty-element';
6+
export { toBeExpanded } from './to-be-expanded';
57
export { toBeOnTheScreen } from './to-be-on-the-screen';
68
export { toBePartiallyChecked } from './to-be-partially-checked';
79
export { toBeSelected } from './to-be-selected';

src/matchers/to-be-collapsed.tsx

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

src/matchers/to-be-expanded.tsx

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

0 commit comments

Comments
 (0)