Skip to content

Commit eeb35d1

Browse files
feat: toHaveAccessibilityValue() matcher (#127)
1 parent f050c41 commit eeb35d1

File tree

4 files changed

+197
-6
lines changed

4 files changed

+197
-6
lines changed

extend-expect.d.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
import type { AccessibilityState, ImageStyle, StyleProp, TextStyle, ViewStyle } from 'react-native';
22
import type { ReactTestInstance } from 'react-test-renderer';
3+
import type { AccessibilityValueMatcher } from './src/to-have-accessibility-value';
34

45
declare global {
56
namespace jest {
67
// eslint-disable-next-line @typescript-eslint/no-unused-vars
78
interface Matchers<R, T> {
89
toBeDisabled(): R;
9-
toContainElement(element: ReactTestInstance | null): R;
1010
toBeEmptyElement(): R;
11-
toHaveProp(attr: string, value?: unknown): R;
12-
toHaveTextContent(text: string | RegExp, options?: { normalizeWhitespace: boolean }): R;
1311
toBeEnabled(): R;
12+
toBeVisible(): R;
13+
toContainElement(element: ReactTestInstance | null): R;
14+
toHaveTextContent(text: string | RegExp, options?: { normalizeWhitespace: boolean }): R;
15+
toHaveProp(attr: string, value?: unknown): R;
1416
toHaveStyle(style: StyleProp<ViewStyle | TextStyle | ImageStyle>): R;
17+
toHaveAccessibilityState(state: AccessibilityState): R;
18+
toHaveAccessibilityValue(state: AccessibilityValueMatcher): R;
1519

1620
/** @deprecated This function has been renamed to `toBeEmptyElement`. */
1721
toBeEmpty(): R;
18-
toBeVisible(): R;
19-
20-
toHaveAccessibilityState(state: AccessibilityState): R;
2122
}
2223
}
2324
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import * as React from 'react';
2+
import { View } from 'react-native';
3+
import { render } from '@testing-library/react-native';
4+
5+
test('.toHaveAccessibilityValue to handle min, max, now', () => {
6+
const { getByTestId } = render(
7+
<View>
8+
<View testID="min" accessibilityValue={{ min: 1 }} />
9+
<View testID="max" accessibilityValue={{ max: 10 }} />
10+
<View testID="now" accessibilityValue={{ now: 5 }} />
11+
<View testID="min-max" accessibilityValue={{ min: 2, max: 5 }} />
12+
<View testID="min-now" accessibilityValue={{ min: 2, now: 3 }} />
13+
<View testID="max-now" accessibilityValue={{ max: 5, now: 4 }} />
14+
<View testID="min-max-now" accessibilityValue={{ min: 2, max: 5, now: 3 }} />
15+
</View>,
16+
);
17+
18+
expect(getByTestId('min')).toHaveAccessibilityValue({ min: 1 });
19+
expect(getByTestId('min')).not.toHaveAccessibilityValue({ min: 2 });
20+
expect(() => expect(getByTestId('min')).toHaveAccessibilityValue({ min: 2 }))
21+
.toThrowErrorMatchingInlineSnapshot(`
22+
"expect(element).toHaveAccessibilityValue({"min": 2})
23+
24+
Expected the element to have accessibility value:
25+
{"min": 2}
26+
Received element with accessibility value:
27+
{"min": 1}"
28+
`);
29+
30+
expect(getByTestId('max')).toHaveAccessibilityValue({ max: 10 });
31+
expect(getByTestId('max')).not.toHaveAccessibilityValue({ max: 5 });
32+
expect(() => expect(getByTestId('max')).toHaveAccessibilityValue({ max: 5 }))
33+
.toThrowErrorMatchingInlineSnapshot(`
34+
"expect(element).toHaveAccessibilityValue({"max": 5})
35+
36+
Expected the element to have accessibility value:
37+
{"max": 5}
38+
Received element with accessibility value:
39+
{"max": 10}"
40+
`);
41+
42+
expect(getByTestId('now')).toHaveAccessibilityValue({ now: 5 });
43+
expect(getByTestId('now')).not.toHaveAccessibilityValue({ now: 3 });
44+
expect(() => expect(getByTestId('now')).toHaveAccessibilityValue({ now: 3 }))
45+
.toThrowErrorMatchingInlineSnapshot(`
46+
"expect(element).toHaveAccessibilityValue({"now": 3})
47+
48+
Expected the element to have accessibility value:
49+
{"now": 3}
50+
Received element with accessibility value:
51+
{"now": 5}"
52+
`);
53+
54+
expect(getByTestId('min-max')).toHaveAccessibilityValue({ min: 2, max: 5 });
55+
expect(getByTestId('min-max')).not.toHaveAccessibilityValue({ min: 3, max: 5 });
56+
expect(getByTestId('min-max')).not.toHaveAccessibilityValue({ min: 2, max: 4 });
57+
expect(getByTestId('min-max')).not.toHaveAccessibilityValue({ min: 3, max: 4 });
58+
59+
expect(getByTestId('min-now')).toHaveAccessibilityValue({ min: 2, now: 3 });
60+
expect(getByTestId('min-now')).not.toHaveAccessibilityValue({ min: 1, now: 3 });
61+
expect(getByTestId('min-now')).not.toHaveAccessibilityValue({ min: 2, now: 4 });
62+
expect(getByTestId('min-now')).not.toHaveAccessibilityValue({ min: 0, now: 4 });
63+
64+
expect(getByTestId('max-now')).toHaveAccessibilityValue({ max: 5, now: 4 });
65+
expect(getByTestId('max-now')).not.toHaveAccessibilityValue({ max: 6, now: 4 });
66+
expect(getByTestId('max-now')).not.toHaveAccessibilityValue({ max: 5, now: 3 });
67+
expect(getByTestId('max-now')).not.toHaveAccessibilityValue({ max: 6, now: 3 });
68+
69+
expect(getByTestId('min-max-now')).toHaveAccessibilityValue({ min: 2, max: 5, now: 3 });
70+
expect(getByTestId('min-max-now')).not.toHaveAccessibilityValue({ min: 1, max: 5, now: 3 });
71+
expect(getByTestId('min-max-now')).not.toHaveAccessibilityValue({ min: 2, max: 6, now: 3 });
72+
expect(getByTestId('min-max-now')).not.toHaveAccessibilityValue({ min: 2, max: 5, now: 4 });
73+
});
74+
75+
test('.toHaveAccessibilityValue to handle string text', () => {
76+
const { getByTestId } = render(
77+
<View>
78+
<View testID="text" accessibilityValue={{ text: 'Hello world!' }} />
79+
<View testID="text-now" accessibilityValue={{ text: 'Hello world!', now: 5 }} />
80+
</View>,
81+
);
82+
83+
expect(getByTestId('text')).toHaveAccessibilityValue({ text: 'Hello world!' });
84+
expect(getByTestId('text')).not.toHaveAccessibilityValue({ text: 'Hello other!' });
85+
expect(() => expect(getByTestId('text')).toHaveAccessibilityValue({ text: 'Hello other!' }))
86+
.toThrowErrorMatchingInlineSnapshot(`
87+
"expect(element).toHaveAccessibilityValue({"text": "Hello other!"})
88+
89+
Expected the element to have accessibility value:
90+
{"text": "Hello other!"}
91+
Received element with accessibility value:
92+
{"text": "Hello world!"}"
93+
`);
94+
});
95+
96+
test('.toHaveAccessibilityValue to handle regex text', () => {
97+
const { getByTestId } = render(
98+
<View>
99+
<View testID="text" accessibilityValue={{ text: 'Hello world!' }} />
100+
<View testID="text-now" accessibilityValue={{ text: 'Hello world!', now: 5 }} />
101+
</View>,
102+
);
103+
104+
expect(getByTestId('text')).toHaveAccessibilityValue({ text: /hello/i });
105+
expect(getByTestId('text')).not.toHaveAccessibilityValue({ text: /other/i });
106+
expect(() => expect(getByTestId('text')).toHaveAccessibilityValue({ text: /other/i }))
107+
.toThrowErrorMatchingInlineSnapshot(`
108+
"expect(element).toHaveAccessibilityValue({"text": /other/i})
109+
110+
Expected the element to have accessibility value:
111+
{"text": /other/i}
112+
Received element with accessibility value:
113+
{"text": "Hello world!"}"
114+
`);
115+
116+
expect(getByTestId('text-now')).toHaveAccessibilityValue({ text: /hello/i, now: 5 });
117+
expect(getByTestId('text-now')).not.toHaveAccessibilityValue({ text: /hello/i, now: 3 });
118+
expect(getByTestId('text-now')).not.toHaveAccessibilityValue({ text: /other/i, now: 5 });
119+
expect(() => expect(getByTestId('text-now')).toHaveAccessibilityValue({ text: /hello/i, now: 3 }))
120+
.toThrowErrorMatchingInlineSnapshot(`
121+
"expect(element).toHaveAccessibilityValue({"now": 3, "text": /hello/i})
122+
123+
Expected the element to have accessibility value:
124+
{"now": 3, "text": /hello/i}
125+
Received element with accessibility value:
126+
{"now": 5, "text": "Hello world!"}"
127+
`);
128+
expect(() => expect(getByTestId('text-now')).toHaveAccessibilityValue({ text: /other/i, now: 5 }))
129+
.toThrowErrorMatchingInlineSnapshot(`
130+
"expect(element).toHaveAccessibilityValue({"now": 5, "text": /other/i})
131+
132+
Expected the element to have accessibility value:
133+
{"now": 5, "text": /other/i}
134+
Received element with accessibility value:
135+
{"now": 5, "text": "Hello world!"}"
136+
`);
137+
});

src/extend-expect.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { toHaveStyle } from './to-have-style';
66
import { toHaveTextContent } from './to-have-text-content';
77
import { toBeVisible } from './to-be-visible';
88
import { toHaveAccessibilityState } from './to-have-accessibility-state';
9+
import { toHaveAccessibilityValue } from './to-have-accessibility-value';
910

1011
expect.extend({
1112
toBeDisabled,
@@ -18,4 +19,5 @@ expect.extend({
1819
toHaveTextContent,
1920
toBeVisible,
2021
toHaveAccessibilityState,
22+
toHaveAccessibilityValue,
2123
});

src/to-have-accessibility-value.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type { AccessibilityValue } from 'react-native';
2+
import type { ReactTestInstance } from 'react-test-renderer';
3+
import { matcherHint, stringify } from 'jest-matcher-utils';
4+
import { checkReactElement, getMessage, matches } from './utils';
5+
6+
export interface AccessibilityValueMatcher {
7+
min?: number;
8+
max?: number;
9+
now?: number;
10+
text?: string | RegExp;
11+
}
12+
13+
export function toHaveAccessibilityValue(
14+
this: jest.MatcherContext,
15+
element: ReactTestInstance,
16+
expectedValue: AccessibilityValueMatcher,
17+
) {
18+
checkReactElement(element, toHaveAccessibilityValue, this);
19+
20+
const value = element.props.accessibilityValue;
21+
22+
return {
23+
pass: matchAccessibilityValue(value, expectedValue),
24+
message: () => {
25+
const matcher = matcherHint(
26+
`${this.isNot ? '.not' : ''}.toHaveAccessibilityValue`,
27+
'element',
28+
stringify(expectedValue),
29+
);
30+
return getMessage(
31+
matcher,
32+
`Expected the element ${this.isNot ? 'not to' : 'to'} have accessibility value`,
33+
stringify(expectedValue),
34+
'Received element with accessibility value',
35+
stringify(value),
36+
);
37+
},
38+
};
39+
}
40+
41+
function matchAccessibilityValue(
42+
value: AccessibilityValue,
43+
matcher: AccessibilityValueMatcher,
44+
): boolean {
45+
return (
46+
(matcher.min === undefined || matcher.min === value.min) &&
47+
(matcher.max === undefined || matcher.max === value.max) &&
48+
(matcher.now === undefined || matcher.now === value.now) &&
49+
(matcher.text === undefined || matches(value.text ?? '', matcher.text))
50+
);
51+
}

0 commit comments

Comments
 (0)