diff --git a/src/helpers/accessiblity.ts b/src/helpers/accessiblity.ts index 7a4bb0e1d..f3ac4de34 100644 --- a/src/helpers/accessiblity.ts +++ b/src/helpers/accessiblity.ts @@ -176,6 +176,13 @@ export function getAccessibilityCheckedState( return ariaChecked ?? accessibilityState?.checked; } +export function getAccessibilitySelectedState( + element: ReactTestInstance +): NonNullable { + const { accessibilityState, 'aria-selected': ariaSelected } = element.props; + return ariaSelected ?? accessibilityState?.selected ?? false; +} + export function getAccessibilityValue( element: ReactTestInstance ): AccessibilityValue | undefined { diff --git a/src/matchers/__tests__/to-be-selected.test.tsx b/src/matchers/__tests__/to-be-selected.test.tsx new file mode 100644 index 000000000..cbf1f46cc --- /dev/null +++ b/src/matchers/__tests__/to-be-selected.test.tsx @@ -0,0 +1,96 @@ +import * as React from 'react'; +import { View } from 'react-native'; +import { render, screen } from '../..'; +import '../extend-expect'; + +test('.toBeSelected() basic case', () => { + render( + <> + + + + + + + ); + + expect(screen.getByTestId('selected')).toBeSelected(); + expect(screen.getByTestId('selected-aria')).toBeSelected(); + expect(screen.getByTestId('not-selected')).not.toBeSelected(); + expect(screen.getByTestId('not-selected-aria')).not.toBeSelected(); + expect(screen.getByTestId('default')).not.toBeSelected(); +}); + +test('.toBeSelected() error messages', () => { + render( + <> + + + + + + + ); + + expect(() => expect(screen.getByTestId('selected')).not.toBeSelected()) + .toThrowErrorMatchingInlineSnapshot(` + "expect(element).not.toBeSelected() + + Received element is selected + " + `); + + expect(() => expect(screen.getByTestId('selected-aria')).not.toBeSelected()) + .toThrowErrorMatchingInlineSnapshot(` + "expect(element).not.toBeSelected() + + Received element is selected + " + `); + + expect(() => expect(screen.getByTestId('not-selected')).toBeSelected()) + .toThrowErrorMatchingInlineSnapshot(` + "expect(element).toBeSelected() + + Received element is not selected + " + `); + + expect(() => expect(screen.getByTestId('not-selected-aria')).toBeSelected()) + .toThrowErrorMatchingInlineSnapshot(` + "expect(element).toBeSelected() + + Received element is not selected + " + `); + + expect(() => expect(screen.getByTestId('default')).toBeSelected()) + .toThrowErrorMatchingInlineSnapshot(` + "expect(element).toBeSelected() + + Received element is not selected + " + `); +}); diff --git a/src/matchers/extend-expect.d.ts b/src/matchers/extend-expect.d.ts index 781db2d38..6bc63beaf 100644 --- a/src/matchers/extend-expect.d.ts +++ b/src/matchers/extend-expect.d.ts @@ -7,6 +7,7 @@ export interface JestNativeMatchers { toBeEmptyElement(): R; toBeEnabled(): R; toBePartiallyChecked(): R; + toBeSelected(): R; toBeVisible(): R; toHaveDisplayValue(expectedValue: TextMatch, options?: TextMatchOptions): R; toHaveProp(name: string, expectedValue?: unknown): R; diff --git a/src/matchers/extend-expect.ts b/src/matchers/extend-expect.ts index 25b5d3c16..87988a4a6 100644 --- a/src/matchers/extend-expect.ts +++ b/src/matchers/extend-expect.ts @@ -5,6 +5,7 @@ import { toBeChecked } from './to-be-checked'; import { toBeDisabled, toBeEnabled } from './to-be-disabled'; import { toBeEmptyElement } from './to-be-empty-element'; import { toBePartiallyChecked } from './to-be-partially-checked'; +import { toBeSelected } from './to-be-selected'; import { toBeVisible } from './to-be-visible'; import { toHaveDisplayValue } from './to-have-display-value'; import { toHaveProp } from './to-have-prop'; @@ -17,6 +18,7 @@ expect.extend({ toBeEmptyElement, toBeEnabled, toBePartiallyChecked, + toBeSelected, toBeVisible, toHaveDisplayValue, toHaveProp, diff --git a/src/matchers/index.tsx b/src/matchers/index.tsx index 72718a184..16f5a6d9a 100644 --- a/src/matchers/index.tsx +++ b/src/matchers/index.tsx @@ -7,3 +7,4 @@ export { toBeVisible } from './to-be-visible'; export { toHaveDisplayValue } from './to-have-display-value'; export { toHaveProp } from './to-have-prop'; export { toHaveTextContent } from './to-have-text-content'; +export { toBeSelected } from './to-be-selected'; diff --git a/src/matchers/to-be-selected.ts b/src/matchers/to-be-selected.ts new file mode 100644 index 000000000..3e35c4dd0 --- /dev/null +++ b/src/matchers/to-be-selected.ts @@ -0,0 +1,24 @@ +import { ReactTestInstance } from 'react-test-renderer'; +import { matcherHint } from 'jest-matcher-utils'; +import { getAccessibilitySelectedState } from '../helpers/accessiblity'; +import { checkHostElement, formatElement } from './utils'; + +export function toBeSelected( + this: jest.MatcherContext, + element: ReactTestInstance +) { + checkHostElement(element, toBeSelected, this); + + return { + pass: getAccessibilitySelectedState(element), + message: () => { + const is = this.isNot ? 'is' : 'is not'; + return [ + matcherHint(`${this.isNot ? '.not' : ''}.toBeSelected`, 'element', ''), + '', + `Received element ${is} selected`, + formatElement(element), + ].join('\n'); + }, + }; +}