diff --git a/src/helpers/format-default.ts b/src/helpers/format-default.ts index 717bed9ec..9077ca2df 100644 --- a/src/helpers/format-default.ts +++ b/src/helpers/format-default.ts @@ -1,4 +1,5 @@ import { StyleSheet, ViewStyle } from 'react-native'; +import { removeUndefinedKeys } from './object'; const propsToDisplay = [ 'accessible', @@ -64,27 +65,6 @@ export function defaultMapProps( return result; } -function isObject(value: unknown): value is Record { - return typeof value === 'object' && value !== null && !Array.isArray(value); -} - -function removeUndefinedKeys(prop: unknown) { - if (!isObject(prop)) { - return prop; - } - - let hasKeys = false; - const result: Record = {}; - Object.entries(prop).forEach(([key, value]) => { - if (value !== undefined) { - result[key] = value; - hasKeys = true; - } - }); - - return hasKeys ? result : undefined; -} - function extractStyle(style: ViewStyle | undefined) { if (style == null) { return undefined; diff --git a/src/helpers/object.ts b/src/helpers/object.ts index afb17b01e..50106a741 100644 --- a/src/helpers/object.ts +++ b/src/helpers/object.ts @@ -8,3 +8,24 @@ export function pick(object: T, keys: (keyof T)[]): Partial { return result; } + +function isObject(value: unknown): value is Record { + return value !== null && typeof value === 'object' && !Array.isArray(value); +} + +export function removeUndefinedKeys(prop: unknown) { + if (!isObject(prop)) { + return prop; + } + + let hasKeys = false; + const result: Record = {}; + Object.entries(prop).forEach(([key, value]) => { + if (value !== undefined) { + result[key] = value; + hasKeys = true; + } + }); + + return hasKeys ? result : undefined; +} diff --git a/src/matchers/__tests__/to-have-accessibility-value.test.tsx b/src/matchers/__tests__/to-have-accessibility-value.test.tsx new file mode 100644 index 000000000..ca4a156a1 --- /dev/null +++ b/src/matchers/__tests__/to-have-accessibility-value.test.tsx @@ -0,0 +1,166 @@ +import * as React from 'react'; +import { View } from 'react-native'; +import { render, screen } from '../..'; +import '../extend-expect'; + +describe('toHaveAccessibilityValue', () => { + it('supports "accessibilityValue.min"', () => { + render(); + expect(screen.root).toHaveAccessibilityValue({ min: 0 }); + expect(screen.root).not.toHaveAccessibilityValue({ min: 1 }); + }); + + it('supports "accessibilityValue.max"', () => { + render(); + expect(screen.root).toHaveAccessibilityValue({ max: 100 }); + expect(screen.root).not.toHaveAccessibilityValue({ max: 99 }); + }); + + it('supports "accessibilityValue.now"', () => { + render(); + expect(screen.root).toHaveAccessibilityValue({ now: 33 }); + expect(screen.root).not.toHaveAccessibilityValue({ now: 34 }); + }); + + it('supports "accessibilityValue.text"', () => { + render(); + expect(screen.root).toHaveAccessibilityValue({ text: 'Hello' }); + expect(screen.root).toHaveAccessibilityValue({ text: /He/ }); + expect(screen.root).not.toHaveAccessibilityValue({ text: 'Hi' }); + expect(screen.root).not.toHaveAccessibilityValue({ text: /Hi/ }); + }); + + it('supports "aria-valuemin"', () => { + render(); + expect(screen.root).toHaveAccessibilityValue({ min: 0 }); + expect(screen.root).not.toHaveAccessibilityValue({ min: 1 }); + }); + + it('supports "aria-valuemax"', () => { + render(); + expect(screen.root).toHaveAccessibilityValue({ max: 100 }); + expect(screen.root).not.toHaveAccessibilityValue({ max: 99 }); + }); + + it('supports "aria-valuenow"', () => { + render(); + expect(screen.root).toHaveAccessibilityValue({ now: 33 }); + expect(screen.root).not.toHaveAccessibilityValue({ now: 34 }); + }); + + it('supports "aria-valuetext"', () => { + render(); + expect(screen.root).toHaveAccessibilityValue({ text: 'Hello' }); + expect(screen.root).toHaveAccessibilityValue({ text: /He/ }); + expect(screen.root).not.toHaveAccessibilityValue({ text: 'Hi' }); + expect(screen.root).not.toHaveAccessibilityValue({ text: /Hi/ }); + }); + + it('supports multi-argument matching', () => { + render( + + ); + + expect(screen.root).toHaveAccessibilityValue({ now: 5 }); + expect(screen.root).toHaveAccessibilityValue({ now: 5, min: 1 }); + expect(screen.root).toHaveAccessibilityValue({ now: 5, max: 10 }); + expect(screen.root).toHaveAccessibilityValue({ now: 5, min: 1, max: 10 }); + expect(screen.root).toHaveAccessibilityValue({ text: '5/10' }); + expect(screen.root).toHaveAccessibilityValue({ now: 5, text: '5/10' }); + expect(screen.root).toHaveAccessibilityValue({ + now: 5, + min: 1, + max: 10, + text: '5/10', + }); + + expect(screen.root).not.toHaveAccessibilityValue({ now: 6 }); + expect(screen.root).not.toHaveAccessibilityValue({ now: 5, min: 0 }); + expect(screen.root).not.toHaveAccessibilityValue({ now: 5, max: 9 }); + expect(screen.root).not.toHaveAccessibilityValue({ + now: 5, + min: 1, + max: 10, + text: '5 of 10', + }); + }); + + it('gives precedence to ARIA values', () => { + render( + + ); + + expect(screen.root).toHaveAccessibilityValue({ min: 0 }); + expect(screen.root).toHaveAccessibilityValue({ max: 100 }); + expect(screen.root).toHaveAccessibilityValue({ now: 33 }); + expect(screen.root).toHaveAccessibilityValue({ text: 'Hello' }); + + expect(screen.root).not.toHaveAccessibilityValue({ min: 10 }); + expect(screen.root).not.toHaveAccessibilityValue({ max: 90 }); + expect(screen.root).not.toHaveAccessibilityValue({ now: 30 }); + expect(screen.root).not.toHaveAccessibilityValue({ text: 'Hi' }); + }); + + it('shows errors in expected format', () => { + render( + + ); + + expect(() => expect(screen.root).toHaveAccessibilityValue({ min: 10 })) + .toThrowErrorMatchingInlineSnapshot(` + "expect(element).toHaveAccessibilityValue({"min": 10}) + + Expected the element to have accessibility value: + {"min": 10} + Received element with accessibility value: + {"max": 100, "min": 0, "now": 33, "text": "Hello"}" + `); + + expect(() => expect(screen.root).not.toHaveAccessibilityValue({ min: 0 })) + .toThrowErrorMatchingInlineSnapshot(` + "expect(element).not.toHaveAccessibilityValue({"min": 0}) + + Expected the element not to have accessibility value: + {"min": 0} + Received element with accessibility value: + {"max": 100, "min": 0, "now": 33, "text": "Hello"}" + `); + }); + + it('shows errors in expected format with partial value', () => { + render(); + + expect(() => expect(screen.root).toHaveAccessibilityValue({ min: 30 })) + .toThrowErrorMatchingInlineSnapshot(` + "expect(element).toHaveAccessibilityValue({"min": 30}) + + Expected the element to have accessibility value: + {"min": 30} + Received element with accessibility value: + {"now": 33, "text": "Hello"}" + `); + + expect(() => expect(screen.root).not.toHaveAccessibilityValue({ now: 33 })) + .toThrowErrorMatchingInlineSnapshot(` + "expect(element).not.toHaveAccessibilityValue({"now": 33}) + + Expected the element not to have accessibility value: + {"now": 33} + Received element with accessibility value: + {"now": 33, "text": "Hello"}" + `); + }); +}); diff --git a/src/matchers/extend-expect.d.ts b/src/matchers/extend-expect.d.ts index dd8bfa173..1d8fccf23 100644 --- a/src/matchers/extend-expect.d.ts +++ b/src/matchers/extend-expect.d.ts @@ -1,6 +1,7 @@ import type { StyleProp } from 'react-native'; import type { ReactTestInstance } from 'react-test-renderer'; import type { TextMatch, TextMatchOptions } from '../matches'; +import type { AccessibilityValueMatcher } from '../helpers/matchers/accessibilityValue'; import type { Style } from './to-have-style'; export interface JestNativeMatchers { @@ -16,6 +17,7 @@ export interface JestNativeMatchers { toBeSelected(): R; toBeVisible(): R; toContainElement(element: ReactTestInstance | null): R; + toHaveAccessibilityValue(expectedValue: AccessibilityValueMatcher): R; toHaveDisplayValue(expectedValue: TextMatch, options?: TextMatchOptions): R; toHaveProp(name: string, expectedValue?: unknown): R; toHaveStyle(style: StyleProp