From d2e1bff5f0e76961b80929f65869e1e2a2ca6fae Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Thu, 26 Feb 2026 03:32:58 -0800 Subject: [PATCH 1/2] Add Fantom integration tests (#55760) Summary: Add new Fantom integration tests for TouchableOpacity, achieving 40.54% line coverage for TouchableOpacity.js (from 0%). Changelog: [Internal] Differential Revision: D94360684 --- .../__tests__/TouchableOpacity-itest.js | 215 ++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 packages/react-native/Libraries/Components/Touchable/__tests__/TouchableOpacity-itest.js diff --git a/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableOpacity-itest.js b/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableOpacity-itest.js new file mode 100644 index 000000000000..cdb2cd4211cf --- /dev/null +++ b/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableOpacity-itest.js @@ -0,0 +1,215 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + * @oncall react_native + */ + +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; + +import type {HostInstance} from 'react-native'; + +import * as Fantom from '@react-native/fantom'; +import * as React from 'react'; +import {createRef} from 'react'; +import {Text, TouchableOpacity} from 'react-native'; +import ensureInstance from 'react-native/src/private/__tests__/utilities/ensureInstance'; +import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement'; + +describe('', () => { + describe('props', () => { + describe('rendering', () => { + it('renders as a view with accessible="true"', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + expect(root.getRenderedOutput({props: ['accessible']}).toJSX()).toEqual( + , + ); + }); + }); + + describe('style', () => { + it('applies style props', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + , + ); + }); + + expect( + root + .getRenderedOutput({ + props: ['width', 'height', 'backgroundColor'], + }) + .toJSX(), + ).toEqual( + , + ); + }); + }); + + describe('activeOpacity', () => { + it('does not render explicit opacity when using default', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + // Default opacity of 1 is not rendered as a prop + expect(root.getRenderedOutput({props: ['opacity']}).toJSX()).toEqual( + , + ); + }); + + it('renders with custom style opacity', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + expect(root.getRenderedOutput({props: ['opacity']}).toJSX()).toEqual( + , + ); + }); + }); + + describe('onPress', () => { + it('triggers callback when the element is pressed', () => { + const elementRef = createRef(); + const onPressCallback = jest.fn(); + + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + , + ); + }); + + const element = ensureInstance(elementRef.current, ReactNativeElement); + Fantom.dispatchNativeEvent(element, 'click'); + + expect(onPressCallback).toHaveBeenCalledTimes(1); + }); + }); + + describe('disabled', () => { + it('cannot be pressed when disabled', () => { + const elementRef = createRef(); + const onPressCallback = jest.fn(); + + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + , + ); + }); + + const element = ensureInstance(elementRef.current, ReactNativeElement); + Fantom.dispatchNativeEvent(element, 'click'); + + expect(onPressCallback).toHaveBeenCalledTimes(0); + }); + + it('sets accessibilityState disabled to true', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + expect( + root.getRenderedOutput({props: ['accessibilityState']}).toJSX(), + ).toEqual( + , + ); + }); + }); + + describe('children', () => { + it('renders children inside the touchable', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + + Press me + , + ); + }); + + const element = ensureInstance( + root.document.documentElement.firstElementChild, + ReactNativeElement, + ); + expect(element.childNodes.length).toBe(1); + + expect(root.getRenderedOutput({props: ['accessible']}).toJSX()).toEqual( + + Press me + , + ); + }); + }); + }); + + describe('ref', () => { + describe('instance', () => { + it('is an element node', () => { + const elementRef = createRef(); + + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + expect(elementRef.current).toBeInstanceOf(ReactNativeElement); + }); + + it('uses the "RN:View" tag name', () => { + const elementRef = createRef(); + + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + const element = ensureInstance(elementRef.current, ReactNativeElement); + expect(element.tagName).toBe('RN:View'); + }); + }); + }); +}); From 6ff720a96a5e9002c6f9a43db49602958985f89e Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Thu, 26 Feb 2026 03:32:58 -0800 Subject: [PATCH 2/2] Add Fantom integration tests for ActivityIndicator Summary: Add new Fantom integration tests for ActivityIndicator, achieving 100% line coverage for ActivityIndicator.js (from 0%). Changelog: [Internal] Differential Revision: D94360957 --- .../__tests__/ActivityIndicator-itest.js | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 packages/react-native/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-itest.js diff --git a/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-itest.js b/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-itest.js new file mode 100644 index 000000000000..df3e4cbe57d8 --- /dev/null +++ b/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-itest.js @@ -0,0 +1,191 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; + +import ensureInstance from '../../../../src/private/__tests__/utilities/ensureInstance'; +import * as Fantom from '@react-native/fantom'; +import * as React from 'react'; +import {createRef} from 'react'; +import {ActivityIndicator} from 'react-native'; +import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement'; + +describe('', () => { + describe('props', () => { + describe('size', () => { + it('defaults to "small" (20x20)', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + expect(root.getRenderedOutput().toJSX()).toEqual( + , + ); + }); + + it('renders with size "small"', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + expect(root.getRenderedOutput().toJSX()).toEqual( + , + ); + }); + + it('renders with size "large" (36x36)', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + expect(root.getRenderedOutput().toJSX()).toEqual( + , + ); + }); + + it('renders with numeric size on Android', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + expect(root.getRenderedOutput().toJSX()).toEqual( + , + ); + }); + }); + + describe('color', () => { + it('renders an AndroidProgressBar when color is set', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + // Color is a native prop not serialized as a view attribute, + // but the component still renders correctly + expect(root.getRenderedOutput().toJSX()).toEqual( + , + ); + }); + }); + + describe('animating', () => { + it('defaults to true', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + // Component renders normally when animating (default) + expect(root.getRenderedOutput().toJSX()).toEqual( + , + ); + }); + + it('renders when animating is false', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + // Component still renders when not animating + expect(root.getRenderedOutput().toJSX()).toEqual( + , + ); + }); + }); + + describe('style', () => { + it('applies wrapper View style', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + expect(root.getRenderedOutput({props: ['opacity']}).toJSX()).toEqual( + + + , + ); + }); + }); + + describe('accessibilityLabel', () => { + it('is propagated to the native component', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + , + ); + }); + + expect( + root.getRenderedOutput({props: ['accessibilityLabel']}).toJSX(), + ).toEqual( + , + ); + }); + }); + + describe('testID', () => { + it('is propagated to the native component', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + expect(root.getRenderedOutput({props: ['testID']}).toJSX()).toEqual( + , + ); + }); + }); + }); + + describe('ref', () => { + it('provides a valid ReactNativeElement instance', () => { + const elementRef = + createRef>(); + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + expect(elementRef.current).toBeInstanceOf(ReactNativeElement); + }); + + it('has the correct tag name', () => { + const elementRef = + createRef>(); + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + const element = ensureInstance(elementRef.current, ReactNativeElement); + expect(element.tagName).toBe('RN:AndroidProgressBar'); + }); + }); +});