From 0408e91b2c5db6a29e3d73cb4015cc5cb04a6eba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 1 Jan 2026 15:25:55 +0100 Subject: [PATCH 01/28] wip: replace basic types --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .yarnrc.yml | 4 +- CLAUDE.md | 6 +- README.md | 4 +- package.json | 7 +- src/__tests__/render-hook.test.tsx | 35 +-- .../render-string-validation.test.tsx | 200 ------------------ src/act.ts | 7 +- src/event-handler.ts | 4 +- src/fire-event.ts | 34 ++- src/helpers/__tests__/component-tree.test.tsx | 4 +- .../__tests__/ensure-peer-deps.test.ts | 16 +- src/helpers/accessibility.ts | 36 ++-- src/helpers/component-tree.ts | 26 +-- src/helpers/debug.ts | 4 +- src/helpers/ensure-peer-deps.ts | 2 +- src/helpers/find-all.ts | 14 +- src/helpers/format-element.ts | 10 +- src/helpers/host-component-names.ts | 14 +- .../matchers/match-accessibility-state.ts | 7 +- .../matchers/match-accessibility-value.ts | 4 +- src/helpers/matchers/match-label-text.ts | 4 +- src/helpers/matchers/match-text-content.ts | 4 +- src/helpers/pointer-events.ts | 4 +- src/helpers/string-validation.ts | 2 +- src/helpers/text-content.ts | 4 +- src/helpers/text-input.ts | 6 +- .../to-have-accessible-name.test.tsx | 6 +- src/matchers/to-be-busy.ts | 4 +- src/matchers/to-be-checked.ts | 6 +- src/matchers/to-be-disabled.ts | 8 +- src/matchers/to-be-empty-element.ts | 4 +- src/matchers/to-be-expanded.ts | 6 +- src/matchers/to-be-on-the-screen.ts | 8 +- src/matchers/to-be-partially-checked.ts | 6 +- src/matchers/to-be-selected.ts | 4 +- src/matchers/to-be-visible.ts | 12 +- src/matchers/to-contain-element.ts | 8 +- src/matchers/to-have-accessibility-value.ts | 4 +- src/matchers/to-have-accessible-name.ts | 4 +- src/matchers/to-have-display-value.ts | 4 +- src/matchers/to-have-prop.ts | 4 +- src/matchers/to-have-style.ts | 4 +- src/matchers/to-have-text-content.ts | 4 +- src/matchers/types.ts | 4 +- src/matchers/utils.ts | 8 +- src/native-state.ts | 6 +- src/queries/display-value.ts | 8 +- src/queries/hint-text.ts | 14 +- src/queries/label-text.ts | 6 +- src/queries/make-queries.ts | 26 +-- src/queries/placeholder-text.ts | 10 +- src/queries/role.ts | 17 +- src/queries/test-id.ts | 14 +- src/queries/text.ts | 6 +- src/queries/unsafe-props.ts | 24 +-- src/queries/unsafe-type.ts | 26 ++- src/render-act.ts | 31 ++- src/render-async.tsx | 12 +- src/render.tsx | 64 +----- src/screen.ts | 12 +- src/test-utils/json.ts | 4 +- src/user-event/clear.ts | 4 +- src/user-event/index.ts | 16 +- src/user-event/paste.ts | 4 +- .../press/__tests__/longPress.test.tsx | 4 +- src/user-event/press/__tests__/press.test.tsx | 4 +- src/user-event/press/press.ts | 8 +- src/user-event/scroll/scroll-to.ts | 10 +- src/user-event/setup/setup.ts | 14 +- src/user-event/type/type.ts | 8 +- src/user-event/utils/dispatch-event.ts | 8 +- src/within.ts | 4 +- yarn.lock | 66 +++--- 74 files changed, 352 insertions(+), 650 deletions(-) delete mode 100644 src/__tests__/render-string-validation.test.tsx diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 366ce7186..46c97b328 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -34,5 +34,5 @@ labels: 'bug report' diff --git a/.yarnrc.yml b/.yarnrc.yml index b6bff7067..96756dd3e 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -5,10 +5,10 @@ npmMinimalAgeGate: '3d' npmPreapprovedPackages: - react - react-native - - react-test-renderer + - universal-test-renderer - '@react-native/*' - '@types/react' - - '@types/react-test-renderer' + - '@types/universal-test-renderer' - hermes-compiler yarnPath: .yarn/releases/yarn-4.11.0.cjs diff --git a/CLAUDE.md b/CLAUDE.md index 9b2bdfde3..cfd0c1020 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -This is the **React Native Testing Library (RNTL)** - a comprehensive testing solution for React Native applications that provides React Native runtime simulation on top of `react-test-renderer`. The library encourages better testing practices by focusing on testing behavior rather than implementation details. +This is the **React Native Testing Library (RNTL)** - a comprehensive testing solution for React Native applications that provides React Native runtime simulation on top of `universal-test-renderer`. The library encourages better testing practices by focusing on testing behavior rather than implementation details. ## Key Development Commands @@ -37,7 +37,7 @@ To test a specific file: `yarn test path/to/test.test.tsx` 1. **`src/index.ts`** - Main entry point that sets up auto-cleanup and extends Jest matchers 2. **`src/pure.ts`** - Pure exports without auto-cleanup for advanced use cases -3. **`src/render.tsx`** - Core rendering functionality using `react-test-renderer` +3. **`src/render.tsx`** - Core rendering functionality using `universal-test-renderer` 4. **`src/screen.ts`** - Global screen object providing access to rendered components ### Key Modules @@ -131,7 +131,7 @@ The build creates: ## Testing Environment -- Uses `react-test-renderer` for component rendering +- Uses `universal-test-renderer` for component rendering - Fake timers recommended for user events - String validation available for text rendering checks - Supports both concurrent and legacy React rendering modes diff --git a/README.md b/README.md index 9a2920e24..8cbe0e8fc 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ You want to write maintainable tests for your React Native components. As a part ## This solution -The React Native Testing Library (RNTL) is a comprehensive solution for testing React Native components. It provides React Native runtime simulation on top of `react-test-renderer`, in a way that encourages better testing practices. Its primary guiding principle is: +The React Native Testing Library (RNTL) is a comprehensive solution for testing React Native components. It provides React Native runtime simulation on top of `universal-test-renderer`, in a way that encourages better testing practices. Its primary guiding principle is: > The more your tests resemble the way your software is used, the more confidence they can give you. @@ -36,7 +36,7 @@ yarn add --dev @testing-library/react-native npm install --save-dev @testing-library/react-native ``` -This library has a `peerDependencies` listing for `react-test-renderer`. Make sure that your `react-test-renderer` version matches exactly the `react` version, avoid using `^` in version number. +This library has a `peerDependencies` listing for `universal-test-renderer`. Make sure that your `universal-test-renderer` version matches exactly the `react` version, avoid using `^` in version number. ### Additional Jest matchers diff --git a/package.json b/package.json index 796f9739a..37d34b81f 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "jest": ">=29.0.0", "react": ">=18.2.0", "react-native": ">=0.71", - "react-test-renderer": ">=18.2.0" + "universal-test-renderer": ">=18.2.0" }, "peerDependenciesMeta": { "jest": { @@ -80,7 +80,6 @@ "@types/jest": "^30.0.0", "@types/node": "^24.10.1", "@types/react": "^19.2.6", - "@types/react-test-renderer": "^19.1.0", "babel-jest": "^30.2.0", "babel-plugin-module-resolver": "^5.0.2", "del-cli": "^7.0.0", @@ -92,10 +91,10 @@ "react": "19.1.1", "react-native": "0.82.1", "react-native-gesture-handler": "^2.29.1", - "react-test-renderer": "19.1.1", "release-it": "^19.0.6", "typescript": "^5.9.3", - "typescript-eslint": "^8.47.0" + "typescript-eslint": "^8.47.0", + "universal-test-renderer": "^0.6.0" }, "publishConfig": { "registry": "https://registry.npmjs.org" diff --git a/src/__tests__/render-hook.test.tsx b/src/__tests__/render-hook.test.tsx index 9cdc6618b..9dcc09ba6 100644 --- a/src/__tests__/render-hook.test.tsx +++ b/src/__tests__/render-hook.test.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from 'react'; import React from 'react'; -import TestRenderer from 'react-test-renderer'; +import TestRenderer from 'universal-test-renderer'; import { renderHook } from '../pure'; @@ -85,19 +85,20 @@ test('props type is inferred correctly when initial props is explicitly undefine expect(result.current.param).toBe(6); }); -/** - * This test makes sure that calling renderHook does - * not try to detect host component names in any form. - * But since there are numerous methods that could trigger that - * we check the count of renders using React Test Renderers. - */ -test('does render only once', () => { - jest.spyOn(TestRenderer, 'create'); - - renderHook(() => { - const [state, setState] = React.useState(1); - return [state, setState]; - }); - - expect(TestRenderer.create).toHaveBeenCalledTimes(1); -}); +// TEMP +// /** +// * This test makes sure that calling renderHook does +// * not try to detect host component names in any form. +// * But since there are numerous methods that could trigger that +// * we check the count of renders using React Test Renderers. +// */ +// test('does render only once', () => { +// jest.spyOn(TestRenderer, 'create'); + +// renderHook(() => { +// const [state, setState] = React.useState(1); +// return [state, setState]; +// }); + +// expect(TestRenderer.create).toHaveBeenCalledTimes(1); +// }); diff --git a/src/__tests__/render-string-validation.test.tsx b/src/__tests__/render-string-validation.test.tsx deleted file mode 100644 index 9ac25a01f..000000000 --- a/src/__tests__/render-string-validation.test.tsx +++ /dev/null @@ -1,200 +0,0 @@ -import * as React from 'react'; -import { Pressable, Text, View } from 'react-native'; - -import { fireEvent, render, screen } from '..'; -import { excludeConsoleMessage } from '../test-utils/console'; - -// eslint-disable-next-line no-console -const originalConsoleError = console.error; - -const VALIDATION_ERROR = - 'Invariant Violation: Text strings must be rendered within a component'; -const PROFILER_ERROR = 'The above error occurred in the component'; - -beforeEach(() => { - // eslint-disable-next-line no-console - console.error = excludeConsoleMessage(console.error, PROFILER_ERROR); -}); -afterEach(() => { - // eslint-disable-next-line no-console - console.error = originalConsoleError; -}); - -test('should throw when rendering a string outside a text component', () => { - expect(() => - render(hello, { - unstable_validateStringsRenderedWithinText: true, - }), - ).toThrow( - `${VALIDATION_ERROR}. Detected attempt to render "hello" string within a component.`, - ); -}); - -test('should throw an error when rerendering with text outside of Text component', () => { - render(, { - unstable_validateStringsRenderedWithinText: true, - }); - - expect(() => screen.rerender(hello)).toThrow( - `${VALIDATION_ERROR}. Detected attempt to render "hello" string within a component.`, - ); -}); - -const InvalidTextAfterPress = () => { - const [showText, setShowText] = React.useState(false); - - if (!showText) { - return ( - setShowText(true)}> - Show text - - ); - } - - return text rendered outside text component; -}; - -test('should throw an error when strings are rendered outside Text', () => { - render(, { - unstable_validateStringsRenderedWithinText: true, - }); - - expect(() => fireEvent.press(screen.getByText('Show text'))).toThrow( - `${VALIDATION_ERROR}. Detected attempt to render "text rendered outside text component" string within a component.`, - ); -}); - -test('should not throw for texts nested in fragments', () => { - expect(() => - render( - - <>hello - , - { unstable_validateStringsRenderedWithinText: true }, - ), - ).not.toThrow(); -}); - -test('should not throw if option validateRenderedString is false', () => { - expect(() => render(hello)).not.toThrow(); -}); - -test(`should throw when one of the children is a text and the parent is not a Text component`, () => { - expect(() => - render( - - hello - hello - , - { unstable_validateStringsRenderedWithinText: true }, - ), - ).toThrow( - `${VALIDATION_ERROR}. Detected attempt to render "hello" string within a component.`, - ); -}); - -test(`should throw when a string is rendered within a fragment rendered outside a Text`, () => { - expect(() => - render( - - <>hello - , - { unstable_validateStringsRenderedWithinText: true }, - ), - ).toThrow( - `${VALIDATION_ERROR}. Detected attempt to render "hello" string within a component.`, - ); -}); - -test('should throw if a number is rendered outside a text', () => { - expect(() => - render(0, { unstable_validateStringsRenderedWithinText: true }), - ).toThrow( - `${VALIDATION_ERROR}. Detected attempt to render "0" string within a component.`, - ); -}); - -const Trans = ({ i18nKey }: { i18nKey: string }) => <>{i18nKey}; - -test('should throw with components returning string value not rendered in Text', () => { - expect(() => - render( - - - , - { unstable_validateStringsRenderedWithinText: true }, - ), - ).toThrow( - `${VALIDATION_ERROR}. Detected attempt to render "hello" string within a component.`, - ); -}); - -test('should not throw with components returning string value rendered in Text', () => { - expect(() => - render( - - - , - { unstable_validateStringsRenderedWithinText: true }, - ), - ).not.toThrow(); -}); - -test('should throw when rendering string in a View in a Text', () => { - expect(() => - render( - - hello - , - { unstable_validateStringsRenderedWithinText: true }, - ), - ).toThrow( - `${VALIDATION_ERROR}. Detected attempt to render "hello" string within a component.`, - ); -}); - -const UseEffectComponent = () => { - const [showText, setShowText] = React.useState(false); - - React.useEffect(() => { - setShowText(true); - }, []); - - if (!showText) { - return Text is hidden; - } - - return ( - - Text is visible - - ); -}; - -test('should render immediate setState in useEffect properly', async () => { - render(, { unstable_validateStringsRenderedWithinText: true }); - - expect(await screen.findByText('Text is visible')).toBeTruthy(); -}); - -const InvalidUseEffectComponent = () => { - const [showText, setShowText] = React.useState(false); - - React.useEffect(() => { - setShowText(true); - }, []); - - if (!showText) { - return Text is hidden; - } - - return Text is visible; -}; - -test('should throw properly for immediate setState in useEffect', () => { - expect(() => - render(, { unstable_validateStringsRenderedWithinText: true }), - ).toThrow( - `${VALIDATION_ERROR}. Detected attempt to render "Text is visible" string within a component.`, - ); -}); diff --git a/src/act.ts b/src/act.ts index 8d503b5cf..fffa58ea8 100644 --- a/src/act.ts +++ b/src/act.ts @@ -1,13 +1,13 @@ // This file and the act() implementation is sourced from react-testing-library // https://github.com/testing-library/react-testing-library/blob/3dcd8a9649e25054c0e650d95fca2317b7008576/types/index.d.ts import * as React from 'react'; -import { act as reactTestRendererAct } from 'react-test-renderer'; -const reactAct = typeof React.act === 'function' ? React.act : reactTestRendererAct; -type ReactAct = 0 extends 1 & typeof React.act ? typeof reactTestRendererAct : typeof React.act; +const reactAct = React.act; +type ReactAct = typeof React.act; // See https://github.com/reactwg/react-18/discussions/102 for more context on global.IS_REACT_ACT_ENVIRONMENT declare global { + // eslint-disable-next-line no-var var IS_REACT_ACT_ENVIRONMENT: boolean | undefined; } @@ -67,7 +67,6 @@ function withGlobalActEnvironment(actImplementation: ReactAct) { }; } -// @ts-expect-error: typings get too complex const act = withGlobalActEnvironment(reactAct) as ReactAct; export default act; diff --git a/src/event-handler.ts b/src/event-handler.ts index 8f275c6b4..c8c6d12c3 100644 --- a/src/event-handler.ts +++ b/src/event-handler.ts @@ -1,4 +1,4 @@ -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; export type EventHandlerOptions = { /** Include check for event handler named without adding `on*` prefix. */ @@ -6,7 +6,7 @@ export type EventHandlerOptions = { }; export function getEventHandler( - element: ReactTestInstance, + element: HostElement, eventName: string, options?: EventHandlerOptions, ) { diff --git a/src/fire-event.ts b/src/fire-event.ts index 981e6e649..f64d4166e 100644 --- a/src/fire-event.ts +++ b/src/fire-event.ts @@ -5,7 +5,7 @@ import type { TextProps, ViewProps, } from 'react-native'; -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import act from './act'; import { getEventHandler } from './event-handler'; @@ -18,7 +18,7 @@ import type { Point, StringWithAutocomplete } from './types'; type EventHandler = (...args: unknown[]) => unknown; -export function isTouchResponder(element: ReactTestInstance) { +export function isTouchResponder(element: HostElement) { if (!isHostElement(element)) { return false; } @@ -50,9 +50,9 @@ const textInputEventsIgnoringEditableProp = new Set([ ]); export function isEventEnabled( - element: ReactTestInstance, + element: HostElement, eventName: string, - nearestTouchResponder?: ReactTestInstance, + nearestTouchResponder?: HostElement, ) { if (nearestTouchResponder != null && isHostTextInput(nearestTouchResponder)) { return ( @@ -75,9 +75,9 @@ export function isEventEnabled( } function findEventHandler( - element: ReactTestInstance, + element: HostElement, eventName: string, - nearestTouchResponder?: ReactTestInstance, + nearestTouchResponder?: HostElement, ): EventHandler | null { const touchResponder = isTouchResponder(element) ? element : nearestTouchResponder; @@ -106,7 +106,7 @@ type EventName = StringWithAutocomplete< | EventNameExtractor >; -function fireEvent(element: ReactTestInstance, eventName: EventName, ...data: unknown[]) { +function fireEvent(element: HostElement, eventName: EventName, ...data: unknown[]) { if (!isElementMounted(element)) { return; } @@ -126,20 +126,16 @@ function fireEvent(element: ReactTestInstance, eventName: EventName, ...data: un return returnValue; } -fireEvent.press = (element: ReactTestInstance, ...data: unknown[]) => +fireEvent.press = (element: HostElement, ...data: unknown[]) => fireEvent(element, 'press', ...data); -fireEvent.changeText = (element: ReactTestInstance, ...data: unknown[]) => +fireEvent.changeText = (element: HostElement, ...data: unknown[]) => fireEvent(element, 'changeText', ...data); -fireEvent.scroll = (element: ReactTestInstance, ...data: unknown[]) => +fireEvent.scroll = (element: HostElement, ...data: unknown[]) => fireEvent(element, 'scroll', ...data); -async function fireEventAsync( - element: ReactTestInstance, - eventName: EventName, - ...data: unknown[] -) { +async function fireEventAsync(element: HostElement, eventName: EventName, ...data: unknown[]) { if (!isElementMounted(element)) { return; } @@ -160,13 +156,13 @@ async function fireEventAsync( return returnValue; } -fireEventAsync.press = async (element: ReactTestInstance, ...data: unknown[]) => +fireEventAsync.press = async (element: HostElement, ...data: unknown[]) => await fireEventAsync(element, 'press', ...data); -fireEventAsync.changeText = async (element: ReactTestInstance, ...data: unknown[]) => +fireEventAsync.changeText = async (element: HostElement, ...data: unknown[]) => await fireEventAsync(element, 'changeText', ...data); -fireEventAsync.scroll = async (element: ReactTestInstance, ...data: unknown[]) => +fireEventAsync.scroll = async (element: HostElement, ...data: unknown[]) => await fireEventAsync(element, 'scroll', ...data); export { fireEventAsync }; @@ -180,7 +176,7 @@ const scrollEventNames = new Set([ 'momentumScrollEnd', ]); -function setNativeStateIfNeeded(element: ReactTestInstance, eventName: string, value: unknown) { +function setNativeStateIfNeeded(element: HostElement, eventName: string, value: unknown) { if (eventName === 'changeText' && typeof value === 'string' && isEditableTextInput(element)) { nativeState.valueForElement.set(element, value); } diff --git a/src/helpers/__tests__/component-tree.test.tsx b/src/helpers/__tests__/component-tree.test.tsx index c8a33036b..e9eddac34 100644 --- a/src/helpers/__tests__/component-tree.test.tsx +++ b/src/helpers/__tests__/component-tree.test.tsx @@ -7,7 +7,7 @@ import { getHostParent, getHostSelves, getHostSiblings, - getUnsafeRootElement, + getContainerElement, } from '../component-tree'; function ZeroHostChildren() { @@ -227,6 +227,6 @@ describe('getUnsafeRootElement()', () => { ); const view = screen.getByTestId('view'); - expect(getUnsafeRootElement(view)).toEqual(screen.UNSAFE_root); + expect(getContainerElement(view)).toEqual(screen.UNSAFE_root); }); }); diff --git a/src/helpers/__tests__/ensure-peer-deps.test.ts b/src/helpers/__tests__/ensure-peer-deps.test.ts index 354eab004..c54df5e0f 100644 --- a/src/helpers/__tests__/ensure-peer-deps.test.ts +++ b/src/helpers/__tests__/ensure-peer-deps.test.ts @@ -2,7 +2,7 @@ // Mock the require calls jest.mock('react/package.json', () => ({ version: '19.0.0' })); -jest.mock('react-test-renderer/package.json', () => ({ version: '19.0.0' })); +jest.mock('universal-test-renderer/package.json', () => ({ version: '19.0.0' })); describe('ensurePeerDeps', () => { const originalEnv = process.env; @@ -21,27 +21,27 @@ describe('ensurePeerDeps', () => { expect(() => require('../ensure-peer-deps')).not.toThrow(); }); - it('should throw when react-test-renderer is missing', () => { - jest.mock('react-test-renderer/package.json', () => { + it('should throw when universal-test-renderer is missing', () => { + jest.mock('universal-test-renderer/package.json', () => { throw new Error('Module not found'); }); expect(() => require('../ensure-peer-deps')).toThrow( - 'Missing dev dependency "react-test-renderer@19.0.0"', + 'Missing dev dependency "universal-test-renderer@19.0.0"', ); }); - it('should throw when react-test-renderer version mismatches', () => { - jest.mock('react-test-renderer/package.json', () => ({ version: '18.2.0' })); + it('should throw when universal-test-renderer version mismatches', () => { + jest.mock('universal-test-renderer/package.json', () => ({ version: '18.2.0' })); expect(() => require('../ensure-peer-deps')).toThrow( - 'Incorrect version of "react-test-renderer" detected. Expected "19.0.0", but found "18.2.0"', + 'Incorrect version of "universal-test-renderer" detected. Expected "19.0.0", but found "18.2.0"', ); }); it('should skip dependency check when RNTL_SKIP_DEPS_CHECK is set', () => { process.env.RNTL_SKIP_DEPS_CHECK = '1'; - jest.mock('react-test-renderer/package.json', () => { + jest.mock('universal-test-renderer/package.json', () => { throw new Error('Module not found'); }); diff --git a/src/helpers/accessibility.ts b/src/helpers/accessibility.ts index 95ab9166f..dbf75251f 100644 --- a/src/helpers/accessibility.ts +++ b/src/helpers/accessibility.ts @@ -1,15 +1,15 @@ import type { AccessibilityRole, AccessibilityState, AccessibilityValue, Role } from 'react-native'; import { StyleSheet } from 'react-native'; -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; -import { getHostSiblings, getUnsafeRootElement, isHostElement } from './component-tree'; +import { getHostSiblings, getContainerElement, isHostElement } from './component-tree'; import { findAll } from './find-all'; import { isHostImage, isHostSwitch, isHostText, isHostTextInput } from './host-component-names'; import { getTextContent } from './text-content'; import { isEditableTextInput } from './text-input'; type IsInaccessibleOptions = { - cache?: WeakMap; + cache?: WeakMap; }; export const accessibilityStateKeys: (keyof AccessibilityState)[] = [ @@ -23,14 +23,14 @@ export const accessibilityStateKeys: (keyof AccessibilityState)[] = [ export const accessibilityValueKeys: (keyof AccessibilityValue)[] = ['min', 'max', 'now', 'text']; export function isHiddenFromAccessibility( - element: ReactTestInstance | null, + element: HostElement | null, { cache }: IsInaccessibleOptions = {}, ): boolean { if (element == null) { return true; } - let current: ReactTestInstance | null = element; + let current: HostElement | null = element; while (current) { let isCurrentSubtreeInaccessible = cache?.get(current); @@ -52,7 +52,7 @@ export function isHiddenFromAccessibility( /** RTL-compatibility alias for `isHiddenFromAccessibility` */ export const isInaccessible = isHiddenFromAccessibility; -function isSubtreeInaccessible(element: ReactTestInstance): boolean { +function isSubtreeInaccessible(element: HostElement): boolean { // Null props can happen for React.Fragments if (element.props == null) { return false; @@ -89,7 +89,7 @@ function isSubtreeInaccessible(element: ReactTestInstance): boolean { return false; } -export function isAccessibilityElement(element: ReactTestInstance | null): boolean { +export function isAccessibilityElement(element: HostElement | null): boolean { if (element == null) { return false; } @@ -119,7 +119,7 @@ export function isAccessibilityElement(element: ReactTestInstance | null): boole * @param element * @returns */ -export function getRole(element: ReactTestInstance): Role | AccessibilityRole { +export function getRole(element: HostElement): Role | AccessibilityRole { const explicitRole = element.props.role ?? element.props.accessibilityRole; if (explicitRole) { return normalizeRole(explicitRole); @@ -150,14 +150,14 @@ export function normalizeRole(role: string): Role | AccessibilityRole { return role as Role | AccessibilityRole; } -export function computeAriaModal(element: ReactTestInstance): boolean | undefined { +export function computeAriaModal(element: HostElement): boolean | undefined { return element.props['aria-modal'] ?? element.props.accessibilityViewIsModal; } -export function computeAriaLabel(element: ReactTestInstance): string | undefined { +export function computeAriaLabel(element: HostElement): string | undefined { const labelElementId = element.props['aria-labelledby'] ?? element.props.accessibilityLabelledBy; if (labelElementId) { - const rootElement = getUnsafeRootElement(element); + const rootElement = getContainerElement(element); const labelElement = findAll( rootElement, (node) => isHostElement(node) && node.props.nativeID === labelElementId, @@ -182,12 +182,12 @@ export function computeAriaLabel(element: ReactTestInstance): string | undefined } // See: https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State#busy-state -export function computeAriaBusy({ props }: ReactTestInstance): boolean { +export function computeAriaBusy({ props }: HostElement): boolean { return props['aria-busy'] ?? props.accessibilityState?.busy ?? false; } // See: https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State#checked-state -export function computeAriaChecked(element: ReactTestInstance): AccessibilityState['checked'] { +export function computeAriaChecked(element: HostElement): AccessibilityState['checked'] { const { props } = element; if (isHostSwitch(element)) { @@ -203,7 +203,7 @@ export function computeAriaChecked(element: ReactTestInstance): AccessibilitySta } // See: https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State#disabled-state -export function computeAriaDisabled(element: ReactTestInstance): boolean { +export function computeAriaDisabled(element: HostElement): boolean { if (isHostTextInput(element) && !isEditableTextInput(element)) { return true; } @@ -218,16 +218,16 @@ export function computeAriaDisabled(element: ReactTestInstance): boolean { } // See: https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State#expanded-state -export function computeAriaExpanded({ props }: ReactTestInstance): boolean | undefined { +export function computeAriaExpanded({ props }: HostElement): boolean | undefined { return props['aria-expanded'] ?? props.accessibilityState?.expanded; } // See: https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State#selected-state -export function computeAriaSelected({ props }: ReactTestInstance): boolean { +export function computeAriaSelected({ props }: HostElement): boolean { return props['aria-selected'] ?? props.accessibilityState?.selected ?? false; } -export function computeAriaValue(element: ReactTestInstance): AccessibilityValue { +export function computeAriaValue(element: HostElement): AccessibilityValue { const { accessibilityValue, 'aria-valuemax': ariaValueMax, @@ -244,7 +244,7 @@ export function computeAriaValue(element: ReactTestInstance): AccessibilityValue }; } -export function computeAccessibleName(element: ReactTestInstance): string | undefined { +export function computeAccessibleName(element: HostElement): string | undefined { return computeAriaLabel(element) ?? getTextContent(element); } diff --git a/src/helpers/component-tree.ts b/src/helpers/component-tree.ts index 9b2c99afd..d7d46ef1b 100644 --- a/src/helpers/component-tree.ts +++ b/src/helpers/component-tree.ts @@ -1,28 +1,28 @@ -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import { screen } from '../screen'; /** - * ReactTestInstance referring to host element. + * HostElement referring to host element. */ -export type HostTestInstance = ReactTestInstance & { type: string }; +export type HostTestInstance = HostElement & { type: string }; /** * Checks if the given element is a host element. * @param element The element to check. */ -export function isHostElement(element?: ReactTestInstance | null): element is HostTestInstance { +export function isHostElement(element?: HostElement | null): element is HostTestInstance { return typeof element?.type === 'string'; } -export function isElementMounted(element: ReactTestInstance) { - return getUnsafeRootElement(element) === screen.UNSAFE_root; +export function isElementMounted(element: HostElement) { + return getContainerElement(element) === screen.UNSAFE_root; } /** * Returns first host ancestor for given element. * @param element The element start traversing from. */ -export function getHostParent(element: ReactTestInstance | null): HostTestInstance | null { +export function getHostParent(element: HostElement | null): HostTestInstance | null { if (element == null) { return null; } @@ -43,7 +43,7 @@ export function getHostParent(element: ReactTestInstance | null): HostTestInstan * Returns host children for given element. * @param element The element start traversing from. */ -export function getHostChildren(element: ReactTestInstance | null): HostTestInstance[] { +export function getHostChildren(element: HostElement | null): HostTestInstance[] { if (element == null) { return []; } @@ -72,7 +72,7 @@ export function getHostChildren(element: ReactTestInstance | null): HostTestInst * @returns If the passed element is a host element, it will return an array containing only that element, * if the passed element is a composite element, it will return an array containing its host children (zero, one or many). */ -export function getHostSelves(element: ReactTestInstance | null): HostTestInstance[] { +export function getHostSelves(element: HostElement | null): HostTestInstance[] { return isHostElement(element) ? [element] : getHostChildren(element); } @@ -80,19 +80,19 @@ export function getHostSelves(element: ReactTestInstance | null): HostTestInstan * Returns host siblings for given element. * @param element The element start traversing from. */ -export function getHostSiblings(element: ReactTestInstance | null): HostTestInstance[] { +export function getHostSiblings(element: HostElement | null): HostTestInstance[] { const hostParent = getHostParent(element); const hostSelves = getHostSelves(element); return getHostChildren(hostParent).filter((sibling) => !hostSelves.includes(sibling)); } /** - * Returns the unsafe root element of the tree (probably composite). + * Returns the containerelement of the tree. * * @param element The element start traversing from. - * @returns The root element of the tree (host or composite). + * @returns The container element of the tree. */ -export function getUnsafeRootElement(element: ReactTestInstance) { +export function getContainerElement(element: HostElement) { let current = element; while (current.parent) { current = current.parent; diff --git a/src/helpers/debug.ts b/src/helpers/debug.ts index 4ec242f61..eaeacbdd0 100644 --- a/src/helpers/debug.ts +++ b/src/helpers/debug.ts @@ -1,4 +1,4 @@ -import type { ReactTestRendererJSON } from 'react-test-renderer'; +import type { JsonNode } from 'universal-test-renderer'; import type { FormatElementOptions } from './format-element'; import { formatJson } from './format-element'; @@ -12,7 +12,7 @@ export type DebugOptions = { * Log pretty-printed deep test component instance */ export function debug( - instance: ReactTestRendererJSON | ReactTestRendererJSON[], + instance: JsonNode | JsonNode[], { message, ...formatOptions }: DebugOptions = {}, ) { if (message) { diff --git a/src/helpers/ensure-peer-deps.ts b/src/helpers/ensure-peer-deps.ts index b06507bfc..21150bb35 100644 --- a/src/helpers/ensure-peer-deps.ts +++ b/src/helpers/ensure-peer-deps.ts @@ -1,6 +1,6 @@ function ensurePeerDeps() { const reactVersion = getPackageVersion('react'); - ensurePackage('react-test-renderer', reactVersion); + ensurePackage('universal-test-renderer', reactVersion); } function ensurePackage(name: string, expectedVersion: string) { diff --git a/src/helpers/find-all.ts b/src/helpers/find-all.ts index 4b476dfdb..836059bec 100644 --- a/src/helpers/find-all.ts +++ b/src/helpers/find-all.ts @@ -1,4 +1,4 @@ -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import { getConfig } from '../config'; import { isHiddenFromAccessibility } from './accessibility'; @@ -17,8 +17,8 @@ interface FindAllOptions { } export function findAll( - root: ReactTestInstance, - predicate: (element: ReactTestInstance) => boolean, + root: HostElement, + predicate: (element: HostElement) => boolean, options?: FindAllOptions, ): HostTestInstance[] { const results = findAllInternal(root, predicate, options); @@ -30,15 +30,15 @@ export function findAll( return results; } - const cache = new WeakMap(); + const cache = new WeakMap(); return results.filter((element) => !isHiddenFromAccessibility(element, { cache })); } // Extracted from React Test Renderer -// src: https://github.com/facebook/react/blob/8e2bde6f2751aa6335f3cef488c05c3ea08e074a/packages/react-test-renderer/src/ReactTestRenderer.js#L402 +// src: https://github.com/facebook/react/blob/8e2bde6f2751aa6335f3cef488c05c3ea08e074a/packages/universal-test-renderer/src/Root.js#L402 function findAllInternal( - root: ReactTestInstance, - predicate: (element: ReactTestInstance) => boolean, + root: HostElement, + predicate: (element: HostElement) => boolean, options?: FindAllOptions, ): HostTestInstance[] { const results: HostTestInstance[] = []; diff --git a/src/helpers/format-element.ts b/src/helpers/format-element.ts index 295636db2..345cc8b82 100644 --- a/src/helpers/format-element.ts +++ b/src/helpers/format-element.ts @@ -1,4 +1,4 @@ -import type { ReactTestInstance, ReactTestRendererJSON } from 'react-test-renderer'; +import type { HostElement, JsonNode } from 'universal-test-renderer'; import type { NewPlugin } from 'pretty-format'; import prettyFormat, { plugins } from 'pretty-format'; @@ -22,7 +22,7 @@ export type FormatElementOptions = { * @param element Element to format. */ export function formatElement( - element: ReactTestInstance | null, + element: HostElement | null, { compact, highlight = true, mapProps = defaultMapProps }: FormatElementOptions = {}, ) { if (element == null) { @@ -35,7 +35,7 @@ export function formatElement( return prettyFormat( { // This prop is needed persuade the prettyFormat that the element is - // a ReactTestRendererJSON instance, so it is formatted as JSX. + // a JsonNode instance, so it is formatted as JSX. $$typeof: Symbol.for('react.test.json'), type: `${element.type}`, props: mapProps ? mapProps(props) : props, @@ -52,7 +52,7 @@ export function formatElement( ); } -export function formatElementList(elements: ReactTestInstance[], options?: FormatElementOptions) { +export function formatElementList(elements: HostElement[], options?: FormatElementOptions) { if (elements.length === 0) { return '(no elements)'; } @@ -61,7 +61,7 @@ export function formatElementList(elements: ReactTestInstance[], options?: Forma } export function formatJson( - json: ReactTestRendererJSON | ReactTestRendererJSON[], + json: JsonNode | JsonNode[], { compact, highlight = true, mapProps = defaultMapProps }: FormatElementOptions = {}, ) { return prettyFormat(json, { diff --git a/src/helpers/host-component-names.ts b/src/helpers/host-component-names.ts index 45e019bc8..2345ef354 100644 --- a/src/helpers/host-component-names.ts +++ b/src/helpers/host-component-names.ts @@ -1,4 +1,4 @@ -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import type { HostTestInstance } from './component-tree'; @@ -13,7 +13,7 @@ const HOST_MODAL_NAMES = ['Modal']; * Checks if the given element is a host Text element. * @param element The element to check. */ -export function isHostText(element: ReactTestInstance): element is HostTestInstance { +export function isHostText(element: HostElement): element is HostTestInstance { return typeof element?.type === 'string' && HOST_TEXT_NAMES.includes(element.type); } @@ -21,7 +21,7 @@ export function isHostText(element: ReactTestInstance): element is HostTestInsta * Checks if the given element is a host TextInput element. * @param element The element to check. */ -export function isHostTextInput(element: ReactTestInstance): element is HostTestInstance { +export function isHostTextInput(element: HostElement): element is HostTestInstance { return typeof element?.type === 'string' && HOST_TEXT_INPUT_NAMES.includes(element.type); } @@ -29,7 +29,7 @@ export function isHostTextInput(element: ReactTestInstance): element is HostTest * Checks if the given element is a host Image element. * @param element The element to check. */ -export function isHostImage(element: ReactTestInstance): element is HostTestInstance { +export function isHostImage(element: HostElement): element is HostTestInstance { return typeof element?.type === 'string' && HOST_IMAGE_NAMES.includes(element.type); } @@ -37,7 +37,7 @@ export function isHostImage(element: ReactTestInstance): element is HostTestInst * Checks if the given element is a host Switch element. * @param element The element to check. */ -export function isHostSwitch(element: ReactTestInstance): element is HostTestInstance { +export function isHostSwitch(element: HostElement): element is HostTestInstance { return typeof element?.type === 'string' && HOST_SWITCH_NAMES.includes(element.type); } @@ -45,7 +45,7 @@ export function isHostSwitch(element: ReactTestInstance): element is HostTestIns * Checks if the given element is a host ScrollView element. * @param element The element to check. */ -export function isHostScrollView(element: ReactTestInstance): element is HostTestInstance { +export function isHostScrollView(element: HostElement): element is HostTestInstance { return typeof element?.type === 'string' && HOST_SCROLL_VIEW_NAMES.includes(element.type); } @@ -53,6 +53,6 @@ export function isHostScrollView(element: ReactTestInstance): element is HostTes * Checks if the given element is a host Modal element. * @param element The element to check. */ -export function isHostModal(element: ReactTestInstance): element is HostTestInstance { +export function isHostModal(element: HostElement): element is HostTestInstance { return typeof element?.type === 'string' && HOST_MODAL_NAMES.includes(element.type); } diff --git a/src/helpers/matchers/match-accessibility-state.ts b/src/helpers/matchers/match-accessibility-state.ts index 0aabf216b..9cf1be21d 100644 --- a/src/helpers/matchers/match-accessibility-state.ts +++ b/src/helpers/matchers/match-accessibility-state.ts @@ -1,4 +1,4 @@ -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import { computeAriaBusy, @@ -20,10 +20,7 @@ export interface AccessibilityStateMatcher { expanded?: boolean; } -export function matchAccessibilityState( - node: ReactTestInstance, - matcher: AccessibilityStateMatcher, -) { +export function matchAccessibilityState(node: HostElement, matcher: AccessibilityStateMatcher) { if (matcher.busy !== undefined && matcher.busy !== computeAriaBusy(node)) { return false; } diff --git a/src/helpers/matchers/match-accessibility-value.ts b/src/helpers/matchers/match-accessibility-value.ts index 6fe281d32..9e332f49f 100644 --- a/src/helpers/matchers/match-accessibility-value.ts +++ b/src/helpers/matchers/match-accessibility-value.ts @@ -1,4 +1,4 @@ -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import type { TextMatch } from '../../matches'; import { computeAriaValue } from '../accessibility'; @@ -12,7 +12,7 @@ export interface AccessibilityValueMatcher { } export function matchAccessibilityValue( - node: ReactTestInstance, + node: HostElement, matcher: AccessibilityValueMatcher, ): boolean { const value = computeAriaValue(node); diff --git a/src/helpers/matchers/match-label-text.ts b/src/helpers/matchers/match-label-text.ts index ce1fef4c0..b30197892 100644 --- a/src/helpers/matchers/match-label-text.ts +++ b/src/helpers/matchers/match-label-text.ts @@ -1,11 +1,11 @@ -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import type { TextMatch, TextMatchOptions } from '../../matches'; import { matches } from '../../matches'; import { computeAriaLabel } from '../accessibility'; export function matchAccessibilityLabel( - element: ReactTestInstance, + element: HostElement, expectedLabel: TextMatch, options?: TextMatchOptions, ) { diff --git a/src/helpers/matchers/match-text-content.ts b/src/helpers/matchers/match-text-content.ts index dd5e7d90e..b193f6d25 100644 --- a/src/helpers/matchers/match-text-content.ts +++ b/src/helpers/matchers/match-text-content.ts @@ -1,4 +1,4 @@ -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import type { TextMatch, TextMatchOptions } from '../../matches'; import { matches } from '../../matches'; @@ -12,7 +12,7 @@ import { getTextContent } from '../text-content'; * @returns - Whether the node's text content matches the given string or regex. */ export function matchTextContent( - node: ReactTestInstance, + node: HostElement, text: TextMatch, options: TextMatchOptions = {}, ) { diff --git a/src/helpers/pointer-events.ts b/src/helpers/pointer-events.ts index 5992669c7..d0ff86a77 100644 --- a/src/helpers/pointer-events.ts +++ b/src/helpers/pointer-events.ts @@ -1,5 +1,5 @@ import { StyleSheet } from 'react-native'; -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import { getHostParent } from './component-tree'; @@ -10,7 +10,7 @@ import { getHostParent } from './component-tree'; * 'box-none': The View is never the target of touch events but its subviews can be * 'box-only': The view can be the target of touch events but its subviews cannot be * see the official react native doc https://reactnative.dev/docs/view#pointerevents */ -export const isPointerEventEnabled = (element: ReactTestInstance, isParent?: boolean): boolean => { +export const isPointerEventEnabled = (element: HostElement, isParent?: boolean): boolean => { // Check both props.pointerEvents and props.style.pointerEvents const pointerEvents = element?.props.pointerEvents ?? StyleSheet.flatten(element?.props.style)?.pointerEvents; diff --git a/src/helpers/string-validation.ts b/src/helpers/string-validation.ts index 17864c8e1..07f31b6e0 100644 --- a/src/helpers/string-validation.ts +++ b/src/helpers/string-validation.ts @@ -1,4 +1,4 @@ -import type { ReactTestRendererNode } from 'react-test-renderer'; +import type { ReactTestRendererNode } from 'universal-test-renderer'; export const validateStringsRenderedWithinText = ( rendererJSON: ReactTestRendererNode | Array | null, diff --git a/src/helpers/text-content.ts b/src/helpers/text-content.ts index 126dca44f..208160d35 100644 --- a/src/helpers/text-content.ts +++ b/src/helpers/text-content.ts @@ -1,6 +1,6 @@ -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; -export function getTextContent(element: ReactTestInstance | string | null): string { +export function getTextContent(element: HostElement | string | null): string { if (!element) { return ''; } diff --git a/src/helpers/text-input.ts b/src/helpers/text-input.ts index 682043992..29fa000b3 100644 --- a/src/helpers/text-input.ts +++ b/src/helpers/text-input.ts @@ -1,13 +1,13 @@ -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import { nativeState } from '../native-state'; import { isHostTextInput } from './host-component-names'; -export function isEditableTextInput(element: ReactTestInstance) { +export function isEditableTextInput(element: HostElement) { return isHostTextInput(element) && element.props.editable !== false; } -export function getTextInputValue(element: ReactTestInstance) { +export function getTextInputValue(element: HostElement) { if (!isHostTextInput(element)) { throw new Error(`Element is not a "TextInput", but it has type "${element.type}".`); } diff --git a/src/matchers/__tests__/to-have-accessible-name.test.tsx b/src/matchers/__tests__/to-have-accessible-name.test.tsx index 0337f2ba3..1f433d857 100644 --- a/src/matchers/__tests__/to-have-accessible-name.test.tsx +++ b/src/matchers/__tests__/to-have-accessible-name.test.tsx @@ -119,14 +119,14 @@ test('toHaveAccessibleName() handles a view without name when called without exp }); it('toHaveAccessibleName() rejects non-host element', () => { - const nonElement = 'This is not a ReactTestInstance'; + const nonElement = 'This is not a HostElement'; expect(() => expect(nonElement).toHaveAccessibleName()).toThrowErrorMatchingInlineSnapshot(` "expect(received).toHaveAccessibleName() received value must be a host element. Received has type: string - Received has value: "This is not a ReactTestInstance"" + Received has value: "This is not a HostElement"" `); expect(() => expect(nonElement).not.toHaveAccessibleName()).toThrowErrorMatchingInlineSnapshot(` @@ -134,6 +134,6 @@ it('toHaveAccessibleName() rejects non-host element', () => { received value must be a host element. Received has type: string - Received has value: "This is not a ReactTestInstance"" + Received has value: "This is not a HostElement"" `); }); diff --git a/src/matchers/to-be-busy.ts b/src/matchers/to-be-busy.ts index 6af30d9bc..4bb73d041 100644 --- a/src/matchers/to-be-busy.ts +++ b/src/matchers/to-be-busy.ts @@ -1,4 +1,4 @@ -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import { matcherHint } from 'jest-matcher-utils'; import redent from 'redent'; @@ -6,7 +6,7 @@ import { computeAriaBusy } from '../helpers/accessibility'; import { formatElement } from '../helpers/format-element'; import { checkHostElement } from './utils'; -export function toBeBusy(this: jest.MatcherContext, element: ReactTestInstance) { +export function toBeBusy(this: jest.MatcherContext, element: HostElement) { checkHostElement(element, toBeBusy, this); return { diff --git a/src/matchers/to-be-checked.ts b/src/matchers/to-be-checked.ts index 2a5dbacd1..4976b786c 100644 --- a/src/matchers/to-be-checked.ts +++ b/src/matchers/to-be-checked.ts @@ -1,4 +1,4 @@ -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import { matcherHint } from 'jest-matcher-utils'; import redent from 'redent'; @@ -13,7 +13,7 @@ import { formatElement } from '../helpers/format-element'; import { isHostSwitch } from '../helpers/host-component-names'; import { checkHostElement } from './utils'; -export function toBeChecked(this: jest.MatcherContext, element: ReactTestInstance) { +export function toBeChecked(this: jest.MatcherContext, element: HostElement) { checkHostElement(element, toBeChecked, this); if (!isHostSwitch(element) && !isSupportedAccessibilityElement(element)) { @@ -37,7 +37,7 @@ export function toBeChecked(this: jest.MatcherContext, element: ReactTestInstanc }; } -function isSupportedAccessibilityElement(element: ReactTestInstance) { +function isSupportedAccessibilityElement(element: HostElement) { if (!isAccessibilityElement(element)) { return false; } diff --git a/src/matchers/to-be-disabled.ts b/src/matchers/to-be-disabled.ts index 96b5dadab..0c2147c0c 100644 --- a/src/matchers/to-be-disabled.ts +++ b/src/matchers/to-be-disabled.ts @@ -1,4 +1,4 @@ -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import { matcherHint } from 'jest-matcher-utils'; import redent from 'redent'; @@ -7,7 +7,7 @@ import { getHostParent } from '../helpers/component-tree'; import { formatElement } from '../helpers/format-element'; import { checkHostElement } from './utils'; -export function toBeDisabled(this: jest.MatcherContext, element: ReactTestInstance) { +export function toBeDisabled(this: jest.MatcherContext, element: HostElement) { checkHostElement(element, toBeDisabled, this); const isDisabled = computeAriaDisabled(element) || isAncestorDisabled(element); @@ -26,7 +26,7 @@ export function toBeDisabled(this: jest.MatcherContext, element: ReactTestInstan }; } -export function toBeEnabled(this: jest.MatcherContext, element: ReactTestInstance) { +export function toBeEnabled(this: jest.MatcherContext, element: HostElement) { checkHostElement(element, toBeEnabled, this); const isEnabled = !computeAriaDisabled(element) && !isAncestorDisabled(element); @@ -45,7 +45,7 @@ export function toBeEnabled(this: jest.MatcherContext, element: ReactTestInstanc }; } -function isAncestorDisabled(element: ReactTestInstance): boolean { +function isAncestorDisabled(element: HostElement): boolean { const parent = getHostParent(element); if (parent == null) { return false; diff --git a/src/matchers/to-be-empty-element.ts b/src/matchers/to-be-empty-element.ts index 31c1d9e08..7435de79f 100644 --- a/src/matchers/to-be-empty-element.ts +++ b/src/matchers/to-be-empty-element.ts @@ -1,4 +1,4 @@ -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import { matcherHint, RECEIVED_COLOR } from 'jest-matcher-utils'; import redent from 'redent'; @@ -6,7 +6,7 @@ import { getHostChildren } from '../helpers/component-tree'; import { formatElementList } from '../helpers/format-element'; import { checkHostElement } from './utils'; -export function toBeEmptyElement(this: jest.MatcherContext, element: ReactTestInstance) { +export function toBeEmptyElement(this: jest.MatcherContext, element: HostElement) { checkHostElement(element, toBeEmptyElement, this); const hostChildren = getHostChildren(element); diff --git a/src/matchers/to-be-expanded.ts b/src/matchers/to-be-expanded.ts index 4fd6a656e..dd4c71879 100644 --- a/src/matchers/to-be-expanded.ts +++ b/src/matchers/to-be-expanded.ts @@ -1,4 +1,4 @@ -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import { matcherHint } from 'jest-matcher-utils'; import redent from 'redent'; @@ -6,7 +6,7 @@ import { computeAriaExpanded } from '../helpers/accessibility'; import { formatElement } from '../helpers/format-element'; import { checkHostElement } from './utils'; -export function toBeExpanded(this: jest.MatcherContext, element: ReactTestInstance) { +export function toBeExpanded(this: jest.MatcherContext, element: HostElement) { checkHostElement(element, toBeExpanded, this); return { @@ -23,7 +23,7 @@ export function toBeExpanded(this: jest.MatcherContext, element: ReactTestInstan }; } -export function toBeCollapsed(this: jest.MatcherContext, element: ReactTestInstance) { +export function toBeCollapsed(this: jest.MatcherContext, element: HostElement) { checkHostElement(element, toBeCollapsed, this); return { diff --git a/src/matchers/to-be-on-the-screen.ts b/src/matchers/to-be-on-the-screen.ts index cbdbdf378..520dd16c0 100644 --- a/src/matchers/to-be-on-the-screen.ts +++ b/src/matchers/to-be-on-the-screen.ts @@ -1,18 +1,18 @@ -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import { matcherHint, RECEIVED_COLOR } from 'jest-matcher-utils'; import redent from 'redent'; -import { getUnsafeRootElement } from '../helpers/component-tree'; +import { getContainerElement } from '../helpers/component-tree'; import { formatElement } from '../helpers/format-element'; import { screen } from '../screen'; import { checkHostElement } from './utils'; -export function toBeOnTheScreen(this: jest.MatcherContext, element: ReactTestInstance) { +export function toBeOnTheScreen(this: jest.MatcherContext, element: HostElement) { if (element !== null || !this.isNot) { checkHostElement(element, toBeOnTheScreen, this); } - const pass = element === null ? false : screen.UNSAFE_root === getUnsafeRootElement(element); + const pass = element === null ? false : screen.container === getContainerElement(element); const errorFound = () => { return `expected element tree not to contain element, but found\n${redent( diff --git a/src/matchers/to-be-partially-checked.ts b/src/matchers/to-be-partially-checked.ts index 1224de1aa..c1b3e2caf 100644 --- a/src/matchers/to-be-partially-checked.ts +++ b/src/matchers/to-be-partially-checked.ts @@ -1,4 +1,4 @@ -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import { matcherHint } from 'jest-matcher-utils'; import redent from 'redent'; @@ -7,7 +7,7 @@ import { ErrorWithStack } from '../helpers/errors'; import { formatElement } from '../helpers/format-element'; import { checkHostElement } from './utils'; -export function toBePartiallyChecked(this: jest.MatcherContext, element: ReactTestInstance) { +export function toBePartiallyChecked(this: jest.MatcherContext, element: HostElement) { checkHostElement(element, toBePartiallyChecked, this); if (!hasValidAccessibilityRole(element)) { @@ -31,7 +31,7 @@ export function toBePartiallyChecked(this: jest.MatcherContext, element: ReactTe }; } -function hasValidAccessibilityRole(element: ReactTestInstance) { +function hasValidAccessibilityRole(element: HostElement) { const role = getRole(element); return isAccessibilityElement(element) && role === 'checkbox'; } diff --git a/src/matchers/to-be-selected.ts b/src/matchers/to-be-selected.ts index f33fe8449..b059d4746 100644 --- a/src/matchers/to-be-selected.ts +++ b/src/matchers/to-be-selected.ts @@ -1,4 +1,4 @@ -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import { matcherHint } from 'jest-matcher-utils'; import redent from 'redent'; @@ -6,7 +6,7 @@ import { computeAriaSelected } from '../helpers/accessibility'; import { formatElement } from '../helpers/format-element'; import { checkHostElement } from './utils'; -export function toBeSelected(this: jest.MatcherContext, element: ReactTestInstance) { +export function toBeSelected(this: jest.MatcherContext, element: HostElement) { checkHostElement(element, toBeSelected, this); return { diff --git a/src/matchers/to-be-visible.ts b/src/matchers/to-be-visible.ts index d21b112e9..84eff29b6 100644 --- a/src/matchers/to-be-visible.ts +++ b/src/matchers/to-be-visible.ts @@ -1,5 +1,5 @@ import { StyleSheet } from 'react-native'; -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import { matcherHint } from 'jest-matcher-utils'; import redent from 'redent'; @@ -9,7 +9,7 @@ import { formatElement } from '../helpers/format-element'; import { isHostModal } from '../helpers/host-component-names'; import { checkHostElement } from './utils'; -export function toBeVisible(this: jest.MatcherContext, element: ReactTestInstance) { +export function toBeVisible(this: jest.MatcherContext, element: HostElement) { if (element !== null || !this.isNot) { checkHostElement(element, toBeVisible, this); } @@ -29,11 +29,11 @@ export function toBeVisible(this: jest.MatcherContext, element: ReactTestInstanc } function isElementVisible( - element: ReactTestInstance, - accessibilityCache?: WeakMap, + element: HostElement, + accessibilityCache?: WeakMap, ): boolean { // Use cache to speed up repeated searches by `isHiddenFromAccessibility`. - const cache = accessibilityCache ?? new WeakMap(); + const cache = accessibilityCache ?? new WeakMap(); if (isHiddenFromAccessibility(element, { cache })) { return false; } @@ -56,7 +56,7 @@ function isElementVisible( return isElementVisible(hostParent, cache); } -function isHiddenForStyles(element: ReactTestInstance) { +function isHiddenForStyles(element: HostElement) { const flatStyle = StyleSheet.flatten(element.props.style); return flatStyle?.display === 'none' || flatStyle?.opacity === 0; } diff --git a/src/matchers/to-contain-element.ts b/src/matchers/to-contain-element.ts index c891cf7a3..c9ee9629c 100644 --- a/src/matchers/to-contain-element.ts +++ b/src/matchers/to-contain-element.ts @@ -1,4 +1,4 @@ -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import { matcherHint, RECEIVED_COLOR } from 'jest-matcher-utils'; import redent from 'redent'; @@ -7,8 +7,8 @@ import { checkHostElement } from './utils'; export function toContainElement( this: jest.MatcherContext, - container: ReactTestInstance, - element: ReactTestInstance | null, + container: HostElement, + element: HostElement | null, ) { checkHostElement(container, toContainElement, this); @@ -16,7 +16,7 @@ export function toContainElement( checkHostElement(element, toContainElement, this); } - let matches: ReactTestInstance[] = []; + let matches: HostElement[] = []; if (element) { matches = container.findAll((node) => node === element); } diff --git a/src/matchers/to-have-accessibility-value.ts b/src/matchers/to-have-accessibility-value.ts index 6c5ec423b..a7259b49d 100644 --- a/src/matchers/to-have-accessibility-value.ts +++ b/src/matchers/to-have-accessibility-value.ts @@ -1,4 +1,4 @@ -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import { matcherHint, stringify } from 'jest-matcher-utils'; import { computeAriaValue } from '../helpers/accessibility'; @@ -9,7 +9,7 @@ import { checkHostElement, formatMessage } from './utils'; export function toHaveAccessibilityValue( this: jest.MatcherContext, - element: ReactTestInstance, + element: HostElement, expectedValue: AccessibilityValueMatcher, ) { checkHostElement(element, toHaveAccessibilityValue, this); diff --git a/src/matchers/to-have-accessible-name.ts b/src/matchers/to-have-accessible-name.ts index 6cdf9b07a..bbaf3e919 100644 --- a/src/matchers/to-have-accessible-name.ts +++ b/src/matchers/to-have-accessible-name.ts @@ -1,4 +1,4 @@ -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import { matcherHint } from 'jest-matcher-utils'; import { computeAccessibleName } from '../helpers/accessibility'; @@ -8,7 +8,7 @@ import { checkHostElement, formatMessage } from './utils'; export function toHaveAccessibleName( this: jest.MatcherContext, - element: ReactTestInstance, + element: HostElement, expectedName?: TextMatch, options?: TextMatchOptions, ) { diff --git a/src/matchers/to-have-display-value.ts b/src/matchers/to-have-display-value.ts index d7284b3e5..15bf37ce8 100644 --- a/src/matchers/to-have-display-value.ts +++ b/src/matchers/to-have-display-value.ts @@ -1,4 +1,4 @@ -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import { matcherHint } from 'jest-matcher-utils'; import { ErrorWithStack } from '../helpers/errors'; @@ -10,7 +10,7 @@ import { checkHostElement, formatMessage } from './utils'; export function toHaveDisplayValue( this: jest.MatcherContext, - element: ReactTestInstance, + element: HostElement, expectedValue: TextMatch, options?: TextMatchOptions, ) { diff --git a/src/matchers/to-have-prop.ts b/src/matchers/to-have-prop.ts index ce0b6204b..608456e95 100644 --- a/src/matchers/to-have-prop.ts +++ b/src/matchers/to-have-prop.ts @@ -1,11 +1,11 @@ -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import { matcherHint, printExpected, stringify } from 'jest-matcher-utils'; import { checkHostElement, formatMessage } from './utils'; export function toHaveProp( this: jest.MatcherContext, - element: ReactTestInstance, + element: HostElement, name: string, expectedValue: unknown, ) { diff --git a/src/matchers/to-have-style.ts b/src/matchers/to-have-style.ts index cdf486b39..7f3c2f0ed 100644 --- a/src/matchers/to-have-style.ts +++ b/src/matchers/to-have-style.ts @@ -1,6 +1,6 @@ import type { ImageStyle, StyleProp, TextStyle, ViewStyle } from 'react-native'; import { StyleSheet } from 'react-native'; -import type { ReactTestInstance } from 'react-test-renderer'; +import type { HostElement } from 'universal-test-renderer'; import { diff, matcherHint } from 'jest-matcher-utils'; import { checkHostElement, formatMessage } from './utils'; @@ -11,7 +11,7 @@ type StyleLike = Record; export function toHaveStyle( this: jest.MatcherContext, - element: ReactTestInstance, + element: HostElement, style: StyleProp