diff --git a/extend-expect.d.ts b/extend-expect.d.ts index 17d3c0b..35e7722 100644 --- a/extend-expect.d.ts +++ b/extend-expect.d.ts @@ -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 { 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): 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; } } } diff --git a/src/__tests__/to-have-accessibility-value.tsx b/src/__tests__/to-have-accessibility-value.tsx new file mode 100644 index 0000000..047f56f --- /dev/null +++ b/src/__tests__/to-have-accessibility-value.tsx @@ -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( + + + + + + + + + , + ); + + 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( + + + + , + ); + + 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( + + + + , + ); + + 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!"}" + `); +}); diff --git a/src/extend-expect.ts b/src/extend-expect.ts index 33f1c36..3f4b7ee 100644 --- a/src/extend-expect.ts +++ b/src/extend-expect.ts @@ -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, @@ -18,4 +19,5 @@ expect.extend({ toHaveTextContent, toBeVisible, toHaveAccessibilityState, + toHaveAccessibilityValue, }); diff --git a/src/to-have-accessibility-value.ts b/src/to-have-accessibility-value.ts new file mode 100644 index 0000000..e8d43da --- /dev/null +++ b/src/to-have-accessibility-value.ts @@ -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)) + ); +}