From 4f9da13973dd11fded9fec4b3641b34d7a935636 Mon Sep 17 00:00:00 2001 From: Kyaw Thura Date: Wed, 30 Aug 2023 23:34:44 +0700 Subject: [PATCH 01/10] feat: added toBeChecked & toBePartiallyChecked --- src/matchers/extend-expect.d.ts | 2 + src/matchers/extend-expect.ts | 3 ++ src/matchers/index.tsx | 1 + src/matchers/to-be-checked.tsx | 71 +++++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+) create mode 100644 src/matchers/to-be-checked.tsx diff --git a/src/matchers/extend-expect.d.ts b/src/matchers/extend-expect.d.ts index 7123d9d0b..9b3bfafb1 100644 --- a/src/matchers/extend-expect.d.ts +++ b/src/matchers/extend-expect.d.ts @@ -9,6 +9,8 @@ export interface JestNativeMatchers { toHaveDisplayValue(expectedValue: TextMatch, options?: TextMatchOptions): R; toHaveProp(name: string, expectedValue?: unknown): R; toHaveTextContent(expectedText: TextMatch, options?: TextMatchOptions): R; + toBeChecked(): R; + toBePartiallyChecked(): R; } // Implicit Jest global `expect`. diff --git a/src/matchers/extend-expect.ts b/src/matchers/extend-expect.ts index 601d158f9..1945d0959 100644 --- a/src/matchers/extend-expect.ts +++ b/src/matchers/extend-expect.ts @@ -7,6 +7,7 @@ import { toBeVisible } from './to-be-visible'; import { toHaveDisplayValue } from './to-have-display-value'; import { toHaveProp } from './to-have-prop'; import { toHaveTextContent } from './to-have-text-content'; +import { toBeChecked, toBePartiallyChecked } from './to-be-checked'; expect.extend({ toBeOnTheScreen, @@ -17,4 +18,6 @@ expect.extend({ toHaveDisplayValue, toHaveProp, toHaveTextContent, + toBeChecked, + toBePartiallyChecked, }); diff --git a/src/matchers/index.tsx b/src/matchers/index.tsx index ffe850f05..c67629369 100644 --- a/src/matchers/index.tsx +++ b/src/matchers/index.tsx @@ -1,3 +1,4 @@ export { toBeOnTheScreen } from './to-be-on-the-screen'; export { toBeEmptyElement } from './to-be-empty-element'; export { toBeVisible } from './to-be-visible'; +export { toBeChecked, toBePartiallyChecked } from './to-be-checked'; diff --git a/src/matchers/to-be-checked.tsx b/src/matchers/to-be-checked.tsx new file mode 100644 index 000000000..26f160690 --- /dev/null +++ b/src/matchers/to-be-checked.tsx @@ -0,0 +1,71 @@ +import type { ReactTestInstance } from 'react-test-renderer'; +import { matcherHint } from 'jest-matcher-utils'; +import { isAccessibilityElement } from '../helpers/accessiblity'; +import { formatElement } from './utils'; + +export function toBeChecked( + this: jest.MatcherContext, + element: ReactTestInstance +) { + if (!isValidAccessibilityRole(element)) { + return { + pass: false, + message: () => + `only accessibility element with accessibilityRole="checkbox" or accessibilityRole="radio" can be used with .toBeChecked()`, + }; + } + const checkedState = element.props?.accessibilityState?.checked; + const pass = checkedState === true; + + return { + pass, + message: () => { + const is = pass ? 'is' : 'is not'; + return [ + matcherHint(`${this.isNot ? '.not' : ''}.toBeChecked`, 'element', ''), + '', + `Received element ${is} checked:`, + formatElement(element), + ].join('\n'); + }, + }; +} + +export function toBePartiallyChecked( + this: jest.MatcherContext, + element: ReactTestInstance +) { + if (!isValidAccessibilityRole(element)) { + return { + pass: false, + message: () => + `only accessibility element with accessibilityRole="checkbox" or accessibilityRole="radio" can be used with .toBePartiallyChecked()`, + }; + } + const checkedState = element.props?.accessiblityState?.checked; + const pass = checkedState === 'mixed'; + + return { + pass, + message: () => { + const is = pass ? 'is' : 'is not'; + return [ + matcherHint( + `${this.isNot ? '.not' : ''}.toBePartiallyChecked`, + 'element', + '' + ), + '', + `Received element ${is} partially checked:`, + formatElement(element), + ].join('\n'); + }, + }; +} + +const isValidAccessibilityRole = (element: ReactTestInstance) => { + const role = element.props?.accessibilityRole; + return ( + isAccessibilityElement(element) && (role === 'checkbox' || role === 'radio') + ); +}; From 7e8cc31e619a245e05a5816e0410122db4935011 Mon Sep 17 00:00:00 2001 From: Kyaw Thura Date: Wed, 30 Aug 2023 23:34:49 +0700 Subject: [PATCH 02/10] test: wip test --- src/matchers/__tests__/to-be-checked.test.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/matchers/__tests__/to-be-checked.test.tsx diff --git a/src/matchers/__tests__/to-be-checked.test.tsx b/src/matchers/__tests__/to-be-checked.test.tsx new file mode 100644 index 000000000..405a68fa5 --- /dev/null +++ b/src/matchers/__tests__/to-be-checked.test.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { View } from 'react-native'; +import render from '../../render'; +import { screen } from '../../screen'; +import '../extend-expect'; + +test('toBeCheck() with checkbox role', () => { + render( + + ); + + const view = screen.getByTestId('view'); + expect(view).toBeChecked(); +}); From 151831c7f87c06859cdaf4d59e9525cf34e7fc2f Mon Sep 17 00:00:00 2001 From: Kyaw Thura Date: Thu, 31 Aug 2023 23:03:07 +0700 Subject: [PATCH 03/10] fix: fix typo --- src/matchers/to-be-checked.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/matchers/to-be-checked.tsx b/src/matchers/to-be-checked.tsx index 26f160690..6c042b0e9 100644 --- a/src/matchers/to-be-checked.tsx +++ b/src/matchers/to-be-checked.tsx @@ -42,7 +42,8 @@ export function toBePartiallyChecked( `only accessibility element with accessibilityRole="checkbox" or accessibilityRole="radio" can be used with .toBePartiallyChecked()`, }; } - const checkedState = element.props?.accessiblityState?.checked; + + const checkedState = element.props?.accessibilityState?.checked; const pass = checkedState === 'mixed'; return { From bfa646b24b684e099db7f367dc504b9490c7f764 Mon Sep 17 00:00:00 2001 From: Kyaw Thura Date: Thu, 31 Aug 2023 23:03:17 +0700 Subject: [PATCH 04/10] test: added more test --- src/matchers/__tests__/to-be-checked.test.tsx | 67 ++++++++++++++++--- 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/src/matchers/__tests__/to-be-checked.test.tsx b/src/matchers/__tests__/to-be-checked.test.tsx index 405a68fa5..b7d86aba6 100644 --- a/src/matchers/__tests__/to-be-checked.test.tsx +++ b/src/matchers/__tests__/to-be-checked.test.tsx @@ -1,19 +1,64 @@ import React from 'react'; -import { View } from 'react-native'; +import { type AccessibilityRole, View } from 'react-native'; import render from '../../render'; import { screen } from '../../screen'; import '../extend-expect'; -test('toBeCheck() with checkbox role', () => { - render( - +const ViewWithRole = ({ role }: { role: AccessibilityRole }) => { + return ( + <> + + + + ); +}; + +test('toBeCheck() with checkbox role', () => { + render(); + + const checkedView = screen.getByTestId('checkbox-checked'); + const unCheckedView = screen.getByTestId('checkbox-unchecked'); + const partiallyCheckedView = screen.getByTestId('checkbox-mixChecked'); + expect(checkedView).toBeChecked(); + expect(unCheckedView).not.toBeChecked(); + expect(partiallyCheckedView).toBePartiallyChecked(); + + expect(() => expect(checkedView).not.toBeChecked()).toThrow(); + expect(() => expect(unCheckedView).toBeChecked()).toThrow(); + expect(() => + expect(partiallyCheckedView).not.toBePartiallyChecked() + ).toThrow(); +}); + +test('toBeCheck() with radio role', () => { + render(); + + const checkedView = screen.getByTestId('radio-checked'); + const unCheckedView = screen.getByTestId('radio-unchecked'); + const partiallyCheckedView = screen.getByTestId('radio-mixChecked'); + expect(checkedView).toBeChecked(); + expect(unCheckedView).not.toBeChecked(); + expect(partiallyCheckedView).toBePartiallyChecked(); - const view = screen.getByTestId('view'); - expect(view).toBeChecked(); + expect(() => expect(checkedView).not.toBeChecked()).toThrow(); + expect(() => expect(unCheckedView).toBeChecked()).toThrow(); + expect(() => + expect(partiallyCheckedView).not.toBePartiallyChecked() + ).toThrow(); }); From b78e4cf2af149098fa25b0a9aae7e4c8934e952e Mon Sep 17 00:00:00 2001 From: Kyaw Thura Date: Fri, 1 Sep 2023 19:31:53 +0700 Subject: [PATCH 05/10] fix: fixed throw error --- src/matchers/to-be-checked.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/matchers/to-be-checked.tsx b/src/matchers/to-be-checked.tsx index 6c042b0e9..c5ce19ede 100644 --- a/src/matchers/to-be-checked.tsx +++ b/src/matchers/to-be-checked.tsx @@ -1,6 +1,7 @@ import type { ReactTestInstance } from 'react-test-renderer'; import { matcherHint } from 'jest-matcher-utils'; import { isAccessibilityElement } from '../helpers/accessiblity'; +import { ErrorWithStack } from '../helpers/errors'; import { formatElement } from './utils'; export function toBeChecked( @@ -8,12 +9,12 @@ export function toBeChecked( element: ReactTestInstance ) { if (!isValidAccessibilityRole(element)) { - return { - pass: false, - message: () => - `only accessibility element with accessibilityRole="checkbox" or accessibilityRole="radio" can be used with .toBeChecked()`, - }; + throw new ErrorWithStack( + `toBeChecked() works only on accessibility element with accessibilityRole="checkbox" or accessibilityRole="radio".`, + toBeChecked + ); } + const checkedState = element.props?.accessibilityState?.checked; const pass = checkedState === true; @@ -36,11 +37,10 @@ export function toBePartiallyChecked( element: ReactTestInstance ) { if (!isValidAccessibilityRole(element)) { - return { - pass: false, - message: () => - `only accessibility element with accessibilityRole="checkbox" or accessibilityRole="radio" can be used with .toBePartiallyChecked()`, - }; + throw new ErrorWithStack( + `toBePartiallyChecked() works only on accessibility element with accessibilityRole="checkbox" or accessibilityRole="radio".`, + toBePartiallyChecked + ); } const checkedState = element.props?.accessibilityState?.checked; From 05cea9b62a1c46d0a94a454c0a7dba53daeeae45 Mon Sep 17 00:00:00 2001 From: Kyaw Thura Date: Fri, 1 Sep 2023 19:32:17 +0700 Subject: [PATCH 06/10] test: added throw error test --- src/matchers/__tests__/to-be-checked.test.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/matchers/__tests__/to-be-checked.test.tsx b/src/matchers/__tests__/to-be-checked.test.tsx index b7d86aba6..5669cb668 100644 --- a/src/matchers/__tests__/to-be-checked.test.tsx +++ b/src/matchers/__tests__/to-be-checked.test.tsx @@ -62,3 +62,15 @@ test('toBeCheck() with radio role', () => { expect(partiallyCheckedView).not.toBePartiallyChecked() ).toThrow(); }); + +test('throws error with invalid accessibility', () => { + render(); + + const checkedView = screen.getByTestId('adjustable-checked'); + const unCheckedView = screen.getByTestId('adjustable-unchecked'); + const partiallyCheckedView = screen.getByTestId('adjustable-mixChecked'); + + expect(() => expect(checkedView).toBeChecked()).toThrow(); + expect(() => expect(unCheckedView).not.toBeChecked()).toThrow(); + expect(() => expect(partiallyCheckedView).toBePartiallyChecked()).toThrow(); +}); From de8af0317ad17e521e50caae5acd2d54d7b9177a Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Fri, 1 Sep 2023 23:28:38 +0200 Subject: [PATCH 07/10] refactor: refactor matchers --- src/matchers/extend-expect.d.ts | 4 +-- src/matchers/extend-expect.ts | 6 ++-- src/matchers/to-be-checked.tsx | 53 +++++++++++++++++++-------------- src/matchers/to-be-disabled.tsx | 4 +-- 4 files changed, 38 insertions(+), 29 deletions(-) diff --git a/src/matchers/extend-expect.d.ts b/src/matchers/extend-expect.d.ts index 9b3bfafb1..781db2d38 100644 --- a/src/matchers/extend-expect.d.ts +++ b/src/matchers/extend-expect.d.ts @@ -2,15 +2,15 @@ import type { TextMatch, TextMatchOptions } from '../matches'; export interface JestNativeMatchers { toBeOnTheScreen(): R; + toBeChecked(): R; toBeDisabled(): R; toBeEmptyElement(): R; toBeEnabled(): R; + toBePartiallyChecked(): R; toBeVisible(): R; toHaveDisplayValue(expectedValue: TextMatch, options?: TextMatchOptions): R; toHaveProp(name: string, expectedValue?: unknown): R; toHaveTextContent(expectedText: TextMatch, options?: TextMatchOptions): R; - toBeChecked(): R; - toBePartiallyChecked(): R; } // Implicit Jest global `expect`. diff --git a/src/matchers/extend-expect.ts b/src/matchers/extend-expect.ts index 1945d0959..bdd3249ef 100644 --- a/src/matchers/extend-expect.ts +++ b/src/matchers/extend-expect.ts @@ -1,23 +1,23 @@ /// import { toBeOnTheScreen } from './to-be-on-the-screen'; +import { toBeChecked, toBePartiallyChecked } from './to-be-checked'; import { toBeDisabled, toBeEnabled } from './to-be-disabled'; import { toBeEmptyElement } from './to-be-empty-element'; import { toBeVisible } from './to-be-visible'; import { toHaveDisplayValue } from './to-have-display-value'; import { toHaveProp } from './to-have-prop'; import { toHaveTextContent } from './to-have-text-content'; -import { toBeChecked, toBePartiallyChecked } from './to-be-checked'; expect.extend({ toBeOnTheScreen, + toBeChecked, toBeDisabled, toBeEmptyElement, toBeEnabled, + toBePartiallyChecked, toBeVisible, toHaveDisplayValue, toHaveProp, toHaveTextContent, - toBeChecked, - toBePartiallyChecked, }); diff --git a/src/matchers/to-be-checked.tsx b/src/matchers/to-be-checked.tsx index c5ce19ede..0eb53daeb 100644 --- a/src/matchers/to-be-checked.tsx +++ b/src/matchers/to-be-checked.tsx @@ -1,27 +1,30 @@ +import { AccessibilityState } from 'react-native'; import type { ReactTestInstance } from 'react-test-renderer'; import { matcherHint } from 'jest-matcher-utils'; -import { isAccessibilityElement } from '../helpers/accessiblity'; +import { + getAccessibilityRole, + isAccessibilityElement, +} from '../helpers/accessiblity'; import { ErrorWithStack } from '../helpers/errors'; -import { formatElement } from './utils'; +import { checkHostElement, formatElement } from './utils'; export function toBeChecked( this: jest.MatcherContext, element: ReactTestInstance ) { - if (!isValidAccessibilityRole(element)) { + checkHostElement(element, toBeChecked, this); + + if (!hasValidAccessibilityRole(element)) { throw new ErrorWithStack( - `toBeChecked() works only on accessibility element with accessibilityRole="checkbox" or accessibilityRole="radio".`, + `toBeChecked() works only on accessibility element with accessibility role of "checkbox" or "radio".`, toBeChecked ); } - const checkedState = element.props?.accessibilityState?.checked; - const pass = checkedState === true; - return { - pass, + pass: getCheckedState(element) === true, message: () => { - const is = pass ? 'is' : 'is not'; + const is = this.isNot ? 'is' : 'is not'; return [ matcherHint(`${this.isNot ? '.not' : ''}.toBeChecked`, 'element', ''), '', @@ -36,20 +39,19 @@ export function toBePartiallyChecked( this: jest.MatcherContext, element: ReactTestInstance ) { - if (!isValidAccessibilityRole(element)) { + checkHostElement(element, toBePartiallyChecked, this); + + if (!hasValidAccessibilityRole(element)) { throw new ErrorWithStack( - `toBePartiallyChecked() works only on accessibility element with accessibilityRole="checkbox" or accessibilityRole="radio".`, + `toBePartiallyChecked() works only on accessibility element with accessibility role of "checkbox" or "radio".`, toBePartiallyChecked ); } - const checkedState = element.props?.accessibilityState?.checked; - const pass = checkedState === 'mixed'; - return { - pass, + pass: getCheckedState(element) === 'mixed', message: () => { - const is = pass ? 'is' : 'is not'; + const is = this.isNot ? 'is' : 'is not'; return [ matcherHint( `${this.isNot ? '.not' : ''}.toBePartiallyChecked`, @@ -64,9 +66,16 @@ export function toBePartiallyChecked( }; } -const isValidAccessibilityRole = (element: ReactTestInstance) => { - const role = element.props?.accessibilityRole; - return ( - isAccessibilityElement(element) && (role === 'checkbox' || role === 'radio') - ); -}; +const VALID_ROLES = new Set(['checkbox', 'radio']); + +function hasValidAccessibilityRole(element: ReactTestInstance) { + const role = getAccessibilityRole(element); + return isAccessibilityElement(element) && VALID_ROLES.has(role); +} + +function getCheckedState( + element: ReactTestInstance +): AccessibilityState['checked'] { + const { accessibilityState, 'aria-checked': ariaChecked } = element.props; + return ariaChecked ?? accessibilityState.checked; +} diff --git a/src/matchers/to-be-disabled.tsx b/src/matchers/to-be-disabled.tsx index 18be1bcc2..16bb96aa3 100644 --- a/src/matchers/to-be-disabled.tsx +++ b/src/matchers/to-be-disabled.tsx @@ -16,7 +16,7 @@ export function toBeDisabled( return { pass: isDisabled, message: () => { - const is = isDisabled ? 'is' : 'is not'; + const is = this.isNot ? 'is' : 'is not'; return [ matcherHint(`${this.isNot ? '.not' : ''}.toBeDisabled`, 'element', ''), '', @@ -38,7 +38,7 @@ export function toBeEnabled( return { pass: isEnabled, message: () => { - const is = isEnabled ? 'is' : 'is not'; + const is = this.isNot ? 'is' : 'is not'; return [ matcherHint(`${this.isNot ? '.not' : ''}.toBeEnabled`, 'element', ''), '', From 20e2da2de572158d1cd2a5e230471938b6202d9e Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Fri, 1 Sep 2023 23:34:29 +0200 Subject: [PATCH 08/10] refactor: tweaks --- src/matchers/to-be-checked.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/matchers/to-be-checked.tsx b/src/matchers/to-be-checked.tsx index 0eb53daeb..1bc40624a 100644 --- a/src/matchers/to-be-checked.tsx +++ b/src/matchers/to-be-checked.tsx @@ -16,7 +16,7 @@ export function toBeChecked( if (!hasValidAccessibilityRole(element)) { throw new ErrorWithStack( - `toBeChecked() works only on accessibility element with accessibility role of "checkbox" or "radio".`, + `toBeChecked() works only on accessibility element with role of "checkbox" or "radio".`, toBeChecked ); } @@ -43,7 +43,7 @@ export function toBePartiallyChecked( if (!hasValidAccessibilityRole(element)) { throw new ErrorWithStack( - `toBePartiallyChecked() works only on accessibility element with accessibility role of "checkbox" or "radio".`, + `toBePartiallyChecked() works only on accessibility element with role of "checkbox" or "radio".`, toBePartiallyChecked ); } From ca9dc20029592ce7584e91d033b4b1a179a36868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Mon, 4 Sep 2023 22:50:42 +0200 Subject: [PATCH 09/10] refactor: final tweaks --- src/helpers/accessiblity.ts | 7 + src/matchers/__tests__/to-be-checked.test.tsx | 174 ++++++++++++++---- .../to-be-partially-checked.test.tsx | 109 +++++++++++ src/matchers/extend-expect.ts | 3 +- src/matchers/to-be-checked.tsx | 44 +---- src/matchers/to-be-partially-checked.tsx | 45 +++++ 6 files changed, 304 insertions(+), 78 deletions(-) create mode 100644 src/matchers/__tests__/to-be-partially-checked.test.tsx create mode 100644 src/matchers/to-be-partially-checked.tsx diff --git a/src/helpers/accessiblity.ts b/src/helpers/accessiblity.ts index b29088ad9..a2b1cca88 100644 --- a/src/helpers/accessiblity.ts +++ b/src/helpers/accessiblity.ts @@ -160,3 +160,10 @@ export function getAccessibilityState(element: ReactTestInstance) { selected: ariaSelected ?? accessibilityState?.selected, }; } + +export function getAccessibilityCheckedState( + element: ReactTestInstance +): AccessibilityState['checked'] { + const { accessibilityState, 'aria-checked': ariaChecked } = element.props; + return ariaChecked ?? accessibilityState?.checked; +} diff --git a/src/matchers/__tests__/to-be-checked.test.tsx b/src/matchers/__tests__/to-be-checked.test.tsx index 5669cb668..83f75c3b2 100644 --- a/src/matchers/__tests__/to-be-checked.test.tsx +++ b/src/matchers/__tests__/to-be-checked.test.tsx @@ -4,8 +4,8 @@ import render from '../../render'; import { screen } from '../../screen'; import '../extend-expect'; -const ViewWithRole = ({ role }: { role: AccessibilityRole }) => { - return ( +function renderViewsWithRole(role: AccessibilityRole) { + return render( <> { accessibilityState={{ checked: false }} /> + ); -}; +} test('toBeCheck() with checkbox role', () => { - render(); + renderViewsWithRole('checkbox'); - const checkedView = screen.getByTestId('checkbox-checked'); - const unCheckedView = screen.getByTestId('checkbox-unchecked'); - const partiallyCheckedView = screen.getByTestId('checkbox-mixChecked'); - expect(checkedView).toBeChecked(); - expect(unCheckedView).not.toBeChecked(); - expect(partiallyCheckedView).toBePartiallyChecked(); + const checked = screen.getByTestId('checkbox-checked'); + const unchecked = screen.getByTestId('checkbox-unchecked'); + const mixed = screen.getByTestId('checkbox-mixed'); + const defaultView = screen.getByTestId('checkbox-default'); - expect(() => expect(checkedView).not.toBeChecked()).toThrow(); - expect(() => expect(unCheckedView).toBeChecked()).toThrow(); - expect(() => - expect(partiallyCheckedView).not.toBePartiallyChecked() - ).toThrow(); + expect(checked).toBeChecked(); + expect(unchecked).not.toBeChecked(); + expect(mixed).not.toBeChecked(); + expect(defaultView).not.toBeChecked(); + + expect(() => expect(checked).not.toBeChecked()) + .toThrowErrorMatchingInlineSnapshot(` + "expect(element).not.toBeChecked() + + Received element is checked: + " + `); + expect(() => expect(unchecked).toBeChecked()) + .toThrowErrorMatchingInlineSnapshot(` + "expect(element).toBeChecked() + + Received element is not checked: + " + `); + expect(() => expect(mixed).toBeChecked()).toThrowErrorMatchingInlineSnapshot(` + "expect(element).toBeChecked() + + Received element is not checked: + " + `); + expect(() => expect(defaultView).toBeChecked()) + .toThrowErrorMatchingInlineSnapshot(` + "expect(element).toBeChecked() + + Received element is not checked: + " + `); }); test('toBeCheck() with radio role', () => { - render(); + renderViewsWithRole('radio'); - const checkedView = screen.getByTestId('radio-checked'); - const unCheckedView = screen.getByTestId('radio-unchecked'); - const partiallyCheckedView = screen.getByTestId('radio-mixChecked'); - expect(checkedView).toBeChecked(); - expect(unCheckedView).not.toBeChecked(); - expect(partiallyCheckedView).toBePartiallyChecked(); + const checked = screen.getByTestId('radio-checked'); + const unchecked = screen.getByTestId('radio-unchecked'); + const defaultView = screen.getByTestId('radio-default'); - expect(() => expect(checkedView).not.toBeChecked()).toThrow(); - expect(() => expect(unCheckedView).toBeChecked()).toThrow(); - expect(() => - expect(partiallyCheckedView).not.toBePartiallyChecked() - ).toThrow(); + expect(checked).toBeChecked(); + expect(unchecked).not.toBeChecked(); + expect(defaultView).not.toBeChecked(); + + expect(() => expect(checked).not.toBeChecked()) + .toThrowErrorMatchingInlineSnapshot(` + "expect(element).not.toBeChecked() + + Received element is checked: + " + `); + expect(() => expect(unchecked).toBeChecked()) + .toThrowErrorMatchingInlineSnapshot(` + "expect(element).toBeChecked() + + Received element is not checked: + " + `); + expect(() => expect(defaultView).toBeChecked()) + .toThrowErrorMatchingInlineSnapshot(` + "expect(element).toBeChecked() + + Received element is not checked: + " + `); }); -test('throws error with invalid accessibility', () => { - render(); +test('throws error for invalid role', () => { + renderViewsWithRole('adjustable'); - const checkedView = screen.getByTestId('adjustable-checked'); - const unCheckedView = screen.getByTestId('adjustable-unchecked'); - const partiallyCheckedView = screen.getByTestId('adjustable-mixChecked'); + const checked = screen.getByTestId('adjustable-checked'); + const unchecked = screen.getByTestId('adjustable-unchecked'); - expect(() => expect(checkedView).toBeChecked()).toThrow(); - expect(() => expect(unCheckedView).not.toBeChecked()).toThrow(); - expect(() => expect(partiallyCheckedView).toBePartiallyChecked()).toThrow(); + expect(() => + expect(checked).toBeChecked() + ).toThrowErrorMatchingInlineSnapshot( + `"toBeChecked() works only on accessibility elements with "checkbox" or "radio" role."` + ); + expect(() => + expect(unchecked).not.toBeChecked() + ).toThrowErrorMatchingInlineSnapshot( + `"toBeChecked() works only on accessibility elements with "checkbox" or "radio" role."` + ); }); diff --git a/src/matchers/__tests__/to-be-partially-checked.test.tsx b/src/matchers/__tests__/to-be-partially-checked.test.tsx new file mode 100644 index 000000000..b4f781801 --- /dev/null +++ b/src/matchers/__tests__/to-be-partially-checked.test.tsx @@ -0,0 +1,109 @@ +import React from 'react'; +import { type AccessibilityRole, View } from 'react-native'; +import render from '../../render'; +import { screen } from '../../screen'; +import '../extend-expect'; + +function renderViewsWithRole(role: AccessibilityRole) { + return render( + <> + + + + + + ); +} + +test('toBePartiallyCheck() with checkbox role', () => { + renderViewsWithRole('checkbox'); + + const checked = screen.getByTestId('checkbox-checked'); + const unchecked = screen.getByTestId('checkbox-unchecked'); + const mixed = screen.getByTestId('checkbox-mixed'); + const defaultView = screen.getByTestId('checkbox-default'); + + expect(mixed).toBePartiallyChecked(); + + expect(checked).not.toBePartiallyChecked(); + expect(unchecked).not.toBePartiallyChecked(); + expect(defaultView).not.toBePartiallyChecked(); + + expect(() => expect(mixed).not.toBePartiallyChecked()) + .toThrowErrorMatchingInlineSnapshot(` + "expect(element).not.toBePartiallyChecked() + + Received element is partially checked: + " + `); + + expect(() => expect(checked).toBePartiallyChecked()) + .toThrowErrorMatchingInlineSnapshot(` + "expect(element).toBePartiallyChecked() + + Received element is not partially checked: + " + `); + expect(() => expect(defaultView).toBePartiallyChecked()) + .toThrowErrorMatchingInlineSnapshot(` + "expect(element).toBePartiallyChecked() + + Received element is not partially checked: + " + `); +}); + +test('toBeCheck() with radio role', () => { + renderViewsWithRole('radio'); + + const checked = screen.getByTestId('radio-checked'); + const mixed = screen.getByTestId('radio-mixed'); + + expect(() => + expect(checked).toBePartiallyChecked() + ).toThrowErrorMatchingInlineSnapshot( + `"toBePartiallyChecked() works only on accessibility elements with "checkbox" role."` + ); + expect(() => + expect(mixed).toBePartiallyChecked() + ).toThrowErrorMatchingInlineSnapshot( + `"toBePartiallyChecked() works only on accessibility elements with "checkbox" role."` + ); +}); diff --git a/src/matchers/extend-expect.ts b/src/matchers/extend-expect.ts index bdd3249ef..25b5d3c16 100644 --- a/src/matchers/extend-expect.ts +++ b/src/matchers/extend-expect.ts @@ -1,9 +1,10 @@ /// import { toBeOnTheScreen } from './to-be-on-the-screen'; -import { toBeChecked, toBePartiallyChecked } from './to-be-checked'; +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 { toBeVisible } from './to-be-visible'; import { toHaveDisplayValue } from './to-have-display-value'; import { toHaveProp } from './to-have-prop'; diff --git a/src/matchers/to-be-checked.tsx b/src/matchers/to-be-checked.tsx index 1bc40624a..7a9a67e0d 100644 --- a/src/matchers/to-be-checked.tsx +++ b/src/matchers/to-be-checked.tsx @@ -1,7 +1,7 @@ -import { AccessibilityState } from 'react-native'; import type { ReactTestInstance } from 'react-test-renderer'; import { matcherHint } from 'jest-matcher-utils'; import { + getAccessibilityCheckedState, getAccessibilityRole, isAccessibilityElement, } from '../helpers/accessiblity'; @@ -16,13 +16,13 @@ export function toBeChecked( if (!hasValidAccessibilityRole(element)) { throw new ErrorWithStack( - `toBeChecked() works only on accessibility element with role of "checkbox" or "radio".`, + `toBeChecked() works only on accessibility elements with "checkbox" or "radio" role.`, toBeChecked ); } return { - pass: getCheckedState(element) === true, + pass: getAccessibilityCheckedState(element) === true, message: () => { const is = this.isNot ? 'is' : 'is not'; return [ @@ -35,47 +35,9 @@ export function toBeChecked( }; } -export function toBePartiallyChecked( - this: jest.MatcherContext, - element: ReactTestInstance -) { - checkHostElement(element, toBePartiallyChecked, this); - - if (!hasValidAccessibilityRole(element)) { - throw new ErrorWithStack( - `toBePartiallyChecked() works only on accessibility element with role of "checkbox" or "radio".`, - toBePartiallyChecked - ); - } - - return { - pass: getCheckedState(element) === 'mixed', - message: () => { - const is = this.isNot ? 'is' : 'is not'; - return [ - matcherHint( - `${this.isNot ? '.not' : ''}.toBePartiallyChecked`, - 'element', - '' - ), - '', - `Received element ${is} partially checked:`, - formatElement(element), - ].join('\n'); - }, - }; -} - const VALID_ROLES = new Set(['checkbox', 'radio']); function hasValidAccessibilityRole(element: ReactTestInstance) { const role = getAccessibilityRole(element); return isAccessibilityElement(element) && VALID_ROLES.has(role); } - -function getCheckedState( - element: ReactTestInstance -): AccessibilityState['checked'] { - const { accessibilityState, 'aria-checked': ariaChecked } = element.props; - return ariaChecked ?? accessibilityState.checked; -} diff --git a/src/matchers/to-be-partially-checked.tsx b/src/matchers/to-be-partially-checked.tsx new file mode 100644 index 000000000..079d80269 --- /dev/null +++ b/src/matchers/to-be-partially-checked.tsx @@ -0,0 +1,45 @@ +import type { ReactTestInstance } from 'react-test-renderer'; +import { matcherHint } from 'jest-matcher-utils'; +import { + getAccessibilityCheckedState, + getAccessibilityRole, + isAccessibilityElement, +} from '../helpers/accessiblity'; +import { ErrorWithStack } from '../helpers/errors'; +import { checkHostElement, formatElement } from './utils'; + +export function toBePartiallyChecked( + this: jest.MatcherContext, + element: ReactTestInstance +) { + checkHostElement(element, toBePartiallyChecked, this); + + if (!hasValidAccessibilityRole(element)) { + throw new ErrorWithStack( + 'toBePartiallyChecked() works only on accessibility elements with "checkbox" role.', + toBePartiallyChecked + ); + } + + return { + pass: getAccessibilityCheckedState(element) === 'mixed', + message: () => { + const is = this.isNot ? 'is' : 'is not'; + return [ + matcherHint( + `${this.isNot ? '.not' : ''}.toBePartiallyChecked`, + 'element', + '' + ), + '', + `Received element ${is} partially checked:`, + formatElement(element), + ].join('\n'); + }, + }; +} + +function hasValidAccessibilityRole(element: ReactTestInstance) { + const role = getAccessibilityRole(element); + return isAccessibilityElement(element) && role === 'checkbox'; +} From 0bd5f15727f7be311170a67bf2d7fbbe93b66144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Mon, 4 Sep 2023 22:53:57 +0200 Subject: [PATCH 10/10] chore: fix ts --- src/matchers/index.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/matchers/index.tsx b/src/matchers/index.tsx index c67629369..72718a184 100644 --- a/src/matchers/index.tsx +++ b/src/matchers/index.tsx @@ -1,4 +1,9 @@ export { toBeOnTheScreen } from './to-be-on-the-screen'; +export { toBeChecked } from './to-be-checked'; +export { toBeDisabled, toBeEnabled } from './to-be-disabled'; export { toBeEmptyElement } from './to-be-empty-element'; +export { toBePartiallyChecked } from './to-be-partially-checked'; export { toBeVisible } from './to-be-visible'; -export { toBeChecked, toBePartiallyChecked } from './to-be-checked'; +export { toHaveDisplayValue } from './to-have-display-value'; +export { toHaveProp } from './to-have-prop'; +export { toHaveTextContent } from './to-have-text-content';