Skip to content

Commit

Permalink
feat: toHaveAccessibilityValue() matcher (#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
mdjastrzebski authored Nov 16, 2022
1 parent f050c41 commit eeb35d1
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 6 deletions.
13 changes: 7 additions & 6 deletions extend-expect.d.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import type { AccessibilityState, ImageStyle, StyleProp, TextStyle, ViewStyle } from 'react-native';
import type { ReactTestInstance } from 'react-test-renderer';
import type { AccessibilityValueMatcher } from './src/to-have-accessibility-value';

declare global {
namespace jest {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Matchers<R, T> {
toBeDisabled(): R;
toContainElement(element: ReactTestInstance | null): R;
toBeEmptyElement(): R;
toHaveProp(attr: string, value?: unknown): R;
toHaveTextContent(text: string | RegExp, options?: { normalizeWhitespace: boolean }): R;
toBeEnabled(): R;
toBeVisible(): R;
toContainElement(element: ReactTestInstance | null): R;
toHaveTextContent(text: string | RegExp, options?: { normalizeWhitespace: boolean }): R;
toHaveProp(attr: string, value?: unknown): R;
toHaveStyle(style: StyleProp<ViewStyle | TextStyle | ImageStyle>): R;
toHaveAccessibilityState(state: AccessibilityState): R;
toHaveAccessibilityValue(state: AccessibilityValueMatcher): R;

/** @deprecated This function has been renamed to `toBeEmptyElement`. */
toBeEmpty(): R;
toBeVisible(): R;

toHaveAccessibilityState(state: AccessibilityState): R;
}
}
}
137 changes: 137 additions & 0 deletions src/__tests__/to-have-accessibility-value.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import * as React from 'react';
import { View } from 'react-native';
import { render } from '@testing-library/react-native';

test('.toHaveAccessibilityValue to handle min, max, now', () => {
const { getByTestId } = render(
<View>
<View testID="min" accessibilityValue={{ min: 1 }} />
<View testID="max" accessibilityValue={{ max: 10 }} />
<View testID="now" accessibilityValue={{ now: 5 }} />
<View testID="min-max" accessibilityValue={{ min: 2, max: 5 }} />
<View testID="min-now" accessibilityValue={{ min: 2, now: 3 }} />
<View testID="max-now" accessibilityValue={{ max: 5, now: 4 }} />
<View testID="min-max-now" accessibilityValue={{ min: 2, max: 5, now: 3 }} />
</View>,
);

expect(getByTestId('min')).toHaveAccessibilityValue({ min: 1 });
expect(getByTestId('min')).not.toHaveAccessibilityValue({ min: 2 });
expect(() => expect(getByTestId('min')).toHaveAccessibilityValue({ min: 2 }))
.toThrowErrorMatchingInlineSnapshot(`
"expect(element).toHaveAccessibilityValue({"min": 2})
Expected the element to have accessibility value:
{"min": 2}
Received element with accessibility value:
{"min": 1}"
`);

expect(getByTestId('max')).toHaveAccessibilityValue({ max: 10 });
expect(getByTestId('max')).not.toHaveAccessibilityValue({ max: 5 });
expect(() => expect(getByTestId('max')).toHaveAccessibilityValue({ max: 5 }))
.toThrowErrorMatchingInlineSnapshot(`
"expect(element).toHaveAccessibilityValue({"max": 5})
Expected the element to have accessibility value:
{"max": 5}
Received element with accessibility value:
{"max": 10}"
`);

expect(getByTestId('now')).toHaveAccessibilityValue({ now: 5 });
expect(getByTestId('now')).not.toHaveAccessibilityValue({ now: 3 });
expect(() => expect(getByTestId('now')).toHaveAccessibilityValue({ now: 3 }))
.toThrowErrorMatchingInlineSnapshot(`
"expect(element).toHaveAccessibilityValue({"now": 3})
Expected the element to have accessibility value:
{"now": 3}
Received element with accessibility value:
{"now": 5}"
`);

expect(getByTestId('min-max')).toHaveAccessibilityValue({ min: 2, max: 5 });
expect(getByTestId('min-max')).not.toHaveAccessibilityValue({ min: 3, max: 5 });
expect(getByTestId('min-max')).not.toHaveAccessibilityValue({ min: 2, max: 4 });
expect(getByTestId('min-max')).not.toHaveAccessibilityValue({ min: 3, max: 4 });

expect(getByTestId('min-now')).toHaveAccessibilityValue({ min: 2, now: 3 });
expect(getByTestId('min-now')).not.toHaveAccessibilityValue({ min: 1, now: 3 });
expect(getByTestId('min-now')).not.toHaveAccessibilityValue({ min: 2, now: 4 });
expect(getByTestId('min-now')).not.toHaveAccessibilityValue({ min: 0, now: 4 });

expect(getByTestId('max-now')).toHaveAccessibilityValue({ max: 5, now: 4 });
expect(getByTestId('max-now')).not.toHaveAccessibilityValue({ max: 6, now: 4 });
expect(getByTestId('max-now')).not.toHaveAccessibilityValue({ max: 5, now: 3 });
expect(getByTestId('max-now')).not.toHaveAccessibilityValue({ max: 6, now: 3 });

expect(getByTestId('min-max-now')).toHaveAccessibilityValue({ min: 2, max: 5, now: 3 });
expect(getByTestId('min-max-now')).not.toHaveAccessibilityValue({ min: 1, max: 5, now: 3 });
expect(getByTestId('min-max-now')).not.toHaveAccessibilityValue({ min: 2, max: 6, now: 3 });
expect(getByTestId('min-max-now')).not.toHaveAccessibilityValue({ min: 2, max: 5, now: 4 });
});

test('.toHaveAccessibilityValue to handle string text', () => {
const { getByTestId } = render(
<View>
<View testID="text" accessibilityValue={{ text: 'Hello world!' }} />
<View testID="text-now" accessibilityValue={{ text: 'Hello world!', now: 5 }} />
</View>,
);

expect(getByTestId('text')).toHaveAccessibilityValue({ text: 'Hello world!' });
expect(getByTestId('text')).not.toHaveAccessibilityValue({ text: 'Hello other!' });
expect(() => expect(getByTestId('text')).toHaveAccessibilityValue({ text: 'Hello other!' }))
.toThrowErrorMatchingInlineSnapshot(`
"expect(element).toHaveAccessibilityValue({"text": "Hello other!"})
Expected the element to have accessibility value:
{"text": "Hello other!"}
Received element with accessibility value:
{"text": "Hello world!"}"
`);
});

test('.toHaveAccessibilityValue to handle regex text', () => {
const { getByTestId } = render(
<View>
<View testID="text" accessibilityValue={{ text: 'Hello world!' }} />
<View testID="text-now" accessibilityValue={{ text: 'Hello world!', now: 5 }} />
</View>,
);

expect(getByTestId('text')).toHaveAccessibilityValue({ text: /hello/i });
expect(getByTestId('text')).not.toHaveAccessibilityValue({ text: /other/i });
expect(() => expect(getByTestId('text')).toHaveAccessibilityValue({ text: /other/i }))
.toThrowErrorMatchingInlineSnapshot(`
"expect(element).toHaveAccessibilityValue({"text": /other/i})
Expected the element to have accessibility value:
{"text": /other/i}
Received element with accessibility value:
{"text": "Hello world!"}"
`);

expect(getByTestId('text-now')).toHaveAccessibilityValue({ text: /hello/i, now: 5 });
expect(getByTestId('text-now')).not.toHaveAccessibilityValue({ text: /hello/i, now: 3 });
expect(getByTestId('text-now')).not.toHaveAccessibilityValue({ text: /other/i, now: 5 });
expect(() => expect(getByTestId('text-now')).toHaveAccessibilityValue({ text: /hello/i, now: 3 }))
.toThrowErrorMatchingInlineSnapshot(`
"expect(element).toHaveAccessibilityValue({"now": 3, "text": /hello/i})
Expected the element to have accessibility value:
{"now": 3, "text": /hello/i}
Received element with accessibility value:
{"now": 5, "text": "Hello world!"}"
`);
expect(() => expect(getByTestId('text-now')).toHaveAccessibilityValue({ text: /other/i, now: 5 }))
.toThrowErrorMatchingInlineSnapshot(`
"expect(element).toHaveAccessibilityValue({"now": 5, "text": /other/i})
Expected the element to have accessibility value:
{"now": 5, "text": /other/i}
Received element with accessibility value:
{"now": 5, "text": "Hello world!"}"
`);
});
2 changes: 2 additions & 0 deletions src/extend-expect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { toHaveStyle } from './to-have-style';
import { toHaveTextContent } from './to-have-text-content';
import { toBeVisible } from './to-be-visible';
import { toHaveAccessibilityState } from './to-have-accessibility-state';
import { toHaveAccessibilityValue } from './to-have-accessibility-value';

expect.extend({
toBeDisabled,
Expand All @@ -18,4 +19,5 @@ expect.extend({
toHaveTextContent,
toBeVisible,
toHaveAccessibilityState,
toHaveAccessibilityValue,
});
51 changes: 51 additions & 0 deletions src/to-have-accessibility-value.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { AccessibilityValue } from 'react-native';
import type { ReactTestInstance } from 'react-test-renderer';
import { matcherHint, stringify } from 'jest-matcher-utils';
import { checkReactElement, getMessage, matches } from './utils';

export interface AccessibilityValueMatcher {
min?: number;
max?: number;
now?: number;
text?: string | RegExp;
}

export function toHaveAccessibilityValue(
this: jest.MatcherContext,
element: ReactTestInstance,
expectedValue: AccessibilityValueMatcher,
) {
checkReactElement(element, toHaveAccessibilityValue, this);

const value = element.props.accessibilityValue;

return {
pass: matchAccessibilityValue(value, expectedValue),
message: () => {
const matcher = matcherHint(
`${this.isNot ? '.not' : ''}.toHaveAccessibilityValue`,
'element',
stringify(expectedValue),
);
return getMessage(
matcher,
`Expected the element ${this.isNot ? 'not to' : 'to'} have accessibility value`,
stringify(expectedValue),
'Received element with accessibility value',
stringify(value),
);
},
};
}

function matchAccessibilityValue(
value: AccessibilityValue,
matcher: AccessibilityValueMatcher,
): boolean {
return (
(matcher.min === undefined || matcher.min === value.min) &&
(matcher.max === undefined || matcher.max === value.max) &&
(matcher.now === undefined || matcher.now === value.now) &&
(matcher.text === undefined || matches(value.text ?? '', matcher.text))
);
}

0 comments on commit eeb35d1

Please sign in to comment.