diff --git a/packages/react-native/Libraries/Animated/__tests__/AnimatedMock-itest.js b/packages/react-native/Libraries/Animated/__tests__/AnimatedMock-itest.js new file mode 100644 index 000000000000..65933b0c4390 --- /dev/null +++ b/packages/react-native/Libraries/Animated/__tests__/AnimatedMock-itest.js @@ -0,0 +1,49 @@ +/** + * 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 AnimatedImplementation from '../AnimatedImplementation'; +import AnimatedMock from '../AnimatedMock'; + +describe('Animated Mock', () => { + it('matches implementation keys', () => { + expect(Object.keys(AnimatedMock)).toEqual( + Object.keys(AnimatedImplementation), + ); + }); + it('matches implementation params', () => { + Object.keys(AnimatedImplementation).forEach(key => { + if (AnimatedImplementation[key].length !== AnimatedMock[key].length) { + throw new Error( + 'key ' + + key + + ' had different lengths: ' + + JSON.stringify( + { + impl: { + len: AnimatedImplementation[key].length, + type: typeof AnimatedImplementation[key], + val: AnimatedImplementation[key].toString(), + }, + mock: { + len: AnimatedMock[key].length, + type: typeof AnimatedMock[key], + val: AnimatedMock[key].toString(), + }, + }, + null, + 2, + ), + ); + } + }); + }); +}); diff --git a/packages/react-native/Libraries/Animated/__tests__/AnimatedMock-test.js b/packages/react-native/Libraries/Animated/__tests__/AnimatedMock-test.js deleted file mode 100644 index 066986799f02..000000000000 --- a/packages/react-native/Libraries/Animated/__tests__/AnimatedMock-test.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * 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 - */ - -'use strict'; - -import AnimatedImplementation from '../AnimatedImplementation'; -import AnimatedMock from '../AnimatedMock'; - -describe('Animated Mock', () => { - it('matches implementation keys', () => { - expect(Object.keys(AnimatedMock)).toEqual( - Object.keys(AnimatedImplementation), - ); - }); - it('matches implementation params', done => { - Object.keys(AnimatedImplementation).forEach(key => { - if (AnimatedImplementation[key].length !== AnimatedMock[key].length) { - done( - new Error( - 'key ' + - key + - ' had different lengths: ' + - JSON.stringify( - { - impl: { - len: AnimatedImplementation[key].length, - type: typeof AnimatedImplementation[key], - val: AnimatedImplementation[key].toString(), - }, - mock: { - len: AnimatedMock[key].length, - type: typeof AnimatedMock[key], - val: AnimatedMock[key].toString(), - }, - }, - null, - 2, - ), - ), - ); - } - }); - done(); - }); -}); diff --git a/packages/react-native/Libraries/Animated/__tests__/AnimatedObject-test.js b/packages/react-native/Libraries/Animated/__tests__/AnimatedObject-itest.js similarity index 91% rename from packages/react-native/Libraries/Animated/__tests__/AnimatedObject-test.js rename to packages/react-native/Libraries/Animated/__tests__/AnimatedObject-itest.js index ef4dacd042f5..3eb452700ff1 100644 --- a/packages/react-native/Libraries/Animated/__tests__/AnimatedObject-test.js +++ b/packages/react-native/Libraries/Animated/__tests__/AnimatedObject-itest.js @@ -8,19 +8,13 @@ * @format */ +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; + +import Animated from '../Animated'; +import AnimatedObject from '../nodes/AnimatedObject'; import nullthrows from 'nullthrows'; describe('AnimatedObject', () => { - let Animated; - let AnimatedObject; - - beforeEach(() => { - jest.resetModules(); - - Animated = require('../Animated').default; - AnimatedObject = require('../nodes/AnimatedObject').default; - }); - it('should get the proper value', () => { const anim = new Animated.Value(0); const translateAnim = anim.interpolate({ diff --git a/packages/react-native/Libraries/Animated/__tests__/AnimatedProps-test.js b/packages/react-native/Libraries/Animated/__tests__/AnimatedProps-test.js deleted file mode 100644 index 3759ac2bfe62..000000000000 --- a/packages/react-native/Libraries/Animated/__tests__/AnimatedProps-test.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * 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 AnimatedProps from '../nodes/AnimatedProps'; - -describe('AnimatedProps', () => { - function getValue(inputProps: {[string]: unknown}) { - const animatedProps = new AnimatedProps(inputProps, jest.fn()); - return animatedProps.__getValue(); - } - - it('returns original `style` if it has no nodes', () => { - const style = {color: 'red'}; - expect(getValue({style}).style).toBe(style); - }); - - it('returns original `style` for invalid style values', () => { - const values = [undefined, null, function () {}, true, 123, 'foo']; - for (const value of values) { - expect(getValue({style: value})).toEqual({style: value}); - } - }); -}); diff --git a/packages/react-native/Libraries/Animated/__tests__/AnimatedValue-test.js b/packages/react-native/Libraries/Animated/__tests__/AnimatedValue-itest.js similarity index 77% rename from packages/react-native/Libraries/Animated/__tests__/AnimatedValue-test.js rename to packages/react-native/Libraries/Animated/__tests__/AnimatedValue-itest.js index a77f0123b2ad..1984ef56ee54 100644 --- a/packages/react-native/Libraries/Animated/__tests__/AnimatedValue-test.js +++ b/packages/react-native/Libraries/Animated/__tests__/AnimatedValue-itest.js @@ -8,9 +8,33 @@ * @format */ +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; + +import NativeAnimatedHelper from '../../../src/private/animated/NativeAnimatedHelper'; +import AnimatedValue from '../nodes/AnimatedValue'; + describe('AnimatedValue', () => { - let NativeAnimatedHelper; - let AnimatedValue; + // Fantom uses the real native animated module and does not support + // `jest.spyOn`, so we wrap the relevant `NativeAnimatedHelper.API` methods + // with call-through mocks that count invocations and restore them afterwards. + const restoreAPI: Array<() => void> = []; + + function spyOnAPI(name: string) { + // $FlowFixMe[invalid-computed-prop] + const original = NativeAnimatedHelper.API[name]; + const spy = jest.fn((...args: Array) => + original.apply(NativeAnimatedHelper.API, args), + ); + // $FlowFixMe[prop-missing] + // $FlowFixMe[cannot-write] + NativeAnimatedHelper.API[name] = spy; + restoreAPI.push(() => { + // $FlowFixMe[prop-missing] + // $FlowFixMe[cannot-write] + NativeAnimatedHelper.API[name] = original; + }); + return spy; + } function createNativeAnimatedValue(): AnimatedValue { return new AnimatedValue(0, {useNativeDriver: true}); @@ -32,31 +56,17 @@ describe('AnimatedValue', () => { } beforeEach(() => { - jest.resetModules(); - - jest.mock('../NativeAnimatedTurboModule', () => ({ - __esModule: true, - default: { - addListener: jest.fn(), - createAnimatedNode: jest.fn(), - dropAnimatedNode: jest.fn(), - removeListeners: jest.fn(), - startListeningToAnimatedNodeValue: jest.fn(), - stopListeningToAnimatedNodeValue: jest.fn(), - extractAnimatedNodeOffset: jest.fn(), - // ... - }, - })); - - NativeAnimatedHelper = - require('../../../src/private/animated/NativeAnimatedHelper').default; - AnimatedValue = require('../nodes/AnimatedValue').default; - - jest.spyOn(NativeAnimatedHelper.API, 'createAnimatedNode'); - jest.spyOn(NativeAnimatedHelper.API, 'dropAnimatedNode'); - jest.spyOn(NativeAnimatedHelper.API, 'startListeningToAnimatedNodeValue'); - jest.spyOn(NativeAnimatedHelper.API, 'setWaitingForIdentifier'); - jest.spyOn(NativeAnimatedHelper.API, 'unsetWaitingForIdentifier'); + spyOnAPI('createAnimatedNode'); + spyOnAPI('dropAnimatedNode'); + spyOnAPI('startListeningToAnimatedNodeValue'); + spyOnAPI('setWaitingForIdentifier'); + spyOnAPI('unsetWaitingForIdentifier'); + }); + + afterEach(() => { + while (restoreAPI.length > 0) { + restoreAPI.pop()?.(); + } }); it('emits update events for listeners added', () => { @@ -217,7 +227,13 @@ describe('AnimatedValue', () => { emitMockUpdate(node, 123, 50); - const spy = jest.spyOn(node, '__onAnimatedValueUpdateReceived'); + // $FlowFixMe[method-unbinding] + const original = node.__onAnimatedValueUpdateReceived; + const spy = jest.fn((...args: Array) => + original.apply(node, args), + ); + // $FlowFixMe[cannot-write] + node.__onAnimatedValueUpdateReceived = spy; const mockValue = 100; const mockOffset = 50; @@ -225,7 +241,8 @@ describe('AnimatedValue', () => { emitMockUpdate(node, mockValue, mockOffset); expect(spy).toHaveBeenCalledWith(mockValue, mockOffset); - spy.mockRestore(); + // $FlowFixMe[cannot-write] + node.__onAnimatedValueUpdateReceived = original; }); }); }); diff --git a/packages/react-native/Libraries/Animated/__tests__/Easing-test.js b/packages/react-native/Libraries/Animated/__tests__/Easing-itest.js similarity index 99% rename from packages/react-native/Libraries/Animated/__tests__/Easing-test.js rename to packages/react-native/Libraries/Animated/__tests__/Easing-itest.js index 9581ae1310b4..d71a68d28c65 100644 --- a/packages/react-native/Libraries/Animated/__tests__/Easing-test.js +++ b/packages/react-native/Libraries/Animated/__tests__/Easing-itest.js @@ -8,7 +8,7 @@ * @format */ -'use strict'; +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; import Easing from '../Easing'; diff --git a/packages/react-native/Libraries/Animated/__tests__/Interpolation-test.js b/packages/react-native/Libraries/Animated/__tests__/Interpolation-itest.js similarity index 95% rename from packages/react-native/Libraries/Animated/__tests__/Interpolation-test.js rename to packages/react-native/Libraries/Animated/__tests__/Interpolation-itest.js index fc0fb879264d..1853c0050b0f 100644 --- a/packages/react-native/Libraries/Animated/__tests__/Interpolation-test.js +++ b/packages/react-native/Libraries/Animated/__tests__/Interpolation-itest.js @@ -8,6 +8,8 @@ * @format */ +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; + import type { InterpolationConfigSupportedOutputType, InterpolationConfigType, @@ -33,6 +35,13 @@ function createInterpolation( } describe('Interpolation', () => { + const originalConsoleWarn = console.warn; + + afterEach(() => { + // $FlowFixMe[cannot-write] + console.warn = originalConsoleWarn; + }); + it('should work with defaults', () => { const interpolation = createInterpolation({ inputRange: [0, 1], @@ -365,7 +374,10 @@ describe('Interpolation', () => { }); it('should work with PlatformColor', () => { - jest.spyOn(console, 'warn').mockImplementationOnce(() => {}); + const mockWarn = jest.fn(); + // $FlowFixMe[cannot-write] + console.warn = mockWarn; + const interpolation = createInterpolation({ inputRange: [0, 1], outputRange: [ @@ -381,7 +393,7 @@ describe('Interpolation', () => { expect(interpolation(2 / 3)).toStrictEqual( PlatformColor('@android:color/white'), ); - expect(console.warn).toBeCalledWith( + expect(mockWarn).toBeCalledWith( 'PlatformColor interpolation should happen natively, here we fallback to the closest color', ); expect(interpolation(1)).toStrictEqual( @@ -389,20 +401,20 @@ describe('Interpolation', () => { ); }); - it.each([ + for (const [label, outputRange, expected] of [ ['radians', ['1rad', '2rad'], [1, 2]], ['degrees', ['90deg', '180deg'], [Math.PI / 2, Math.PI]], ['numbers', [1024, Math.PI], [1024, Math.PI]], ['unknown', ['5foo', '10foo'], ['5foo', '10foo']], - ])( - 'should convert %s to numbers in the native config', - (_, outputRange, expected) => { + ]) { + it(`should convert ${label} to numbers in the native config`, () => { const config = new AnimatedInterpolation( // $FlowFixMe[incompatible-type] {}, + // $FlowFixMe[incompatible-call] {inputRange: [0, 1], outputRange}, ).__getNativeConfig(); expect(config.outputRange).toEqual(expected); - }, - ); + }); + } }); diff --git a/packages/react-native/Libraries/Animated/__tests__/TimingAnimation-test.js b/packages/react-native/Libraries/Animated/__tests__/TimingAnimation-itest.js similarity index 93% rename from packages/react-native/Libraries/Animated/__tests__/TimingAnimation-test.js rename to packages/react-native/Libraries/Animated/__tests__/TimingAnimation-itest.js index 0d2213462fd8..0a15db495f57 100644 --- a/packages/react-native/Libraries/Animated/__tests__/TimingAnimation-test.js +++ b/packages/react-native/Libraries/Animated/__tests__/TimingAnimation-itest.js @@ -8,7 +8,7 @@ * @format */ -'use strict'; +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; import TimingAnimation from '../animations/TimingAnimation'; diff --git a/packages/react-native/Libraries/Animated/__tests__/bezier-test.js b/packages/react-native/Libraries/Animated/__tests__/bezier-itest.js similarity index 98% rename from packages/react-native/Libraries/Animated/__tests__/bezier-test.js rename to packages/react-native/Libraries/Animated/__tests__/bezier-itest.js index c7969246478d..38d71ce61f5c 100644 --- a/packages/react-native/Libraries/Animated/__tests__/bezier-test.js +++ b/packages/react-native/Libraries/Animated/__tests__/bezier-itest.js @@ -14,7 +14,7 @@ * @copyright 2014-2015 Gaetan Renaudeau. MIT License. */ -'use strict'; +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; import bezier from '../bezier'; diff --git a/packages/react-native/Libraries/BatchedBridge/__tests__/MessageQueue-test.js b/packages/react-native/Libraries/BatchedBridge/__tests__/MessageQueue-itest.js similarity index 91% rename from packages/react-native/Libraries/BatchedBridge/__tests__/MessageQueue-test.js rename to packages/react-native/Libraries/BatchedBridge/__tests__/MessageQueue-itest.js index 196c788fb708..af908a232848 100644 --- a/packages/react-native/Libraries/BatchedBridge/__tests__/MessageQueue-test.js +++ b/packages/react-native/Libraries/BatchedBridge/__tests__/MessageQueue-itest.js @@ -8,10 +8,11 @@ * @format */ -'use strict'; +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; + +const MessageQueueTestModule = require('../__mocks__/MessageQueueTestModule'); +const MessageQueue = require('../MessageQueue').default; -let MessageQueue; -let MessageQueueTestModule; let queue; const MODULE_IDS = 0; @@ -41,9 +42,6 @@ const assertQueue = ( // local callbacks stored by IDs are cleaned up. describe('MessageQueue', () => { beforeEach(() => { - jest.resetModules(); - MessageQueue = require('../MessageQueue').default; - MessageQueueTestModule = require('../__mocks__/MessageQueueTestModule'); queue = new MessageQueue(); queue.registerCallableModule( 'MessageQueueTestModule', @@ -117,9 +115,16 @@ describe('MessageQueue', () => { it('should throw when calling with unknown module', () => { const unknownModule = 'UnknownModule', unknownMethod = 'UnknownMethod'; - expect(() => - queue.__callFunction(unknownModule, unknownMethod, []), - ).toThrow( + let thrownError: ?Error; + try { + queue.__callFunction(unknownModule, unknownMethod, []); + } catch (e: unknown) { + if (e instanceof Error) { + thrownError = e; + } + } + expect(thrownError).toBeInstanceOf(Error); + expect(thrownError?.message).toContain( `Failed to call into JavaScript module method ${unknownModule}.${unknownMethod}()`, ); }); diff --git a/packages/react-native/Libraries/Blob/__tests__/BlobRegistry-test.js b/packages/react-native/Libraries/Blob/__tests__/BlobRegistry-itest.js similarity index 84% rename from packages/react-native/Libraries/Blob/__tests__/BlobRegistry-test.js rename to packages/react-native/Libraries/Blob/__tests__/BlobRegistry-itest.js index 8e6d70d126d3..86f779b3fca8 100644 --- a/packages/react-native/Libraries/Blob/__tests__/BlobRegistry-test.js +++ b/packages/react-native/Libraries/Blob/__tests__/BlobRegistry-itest.js @@ -8,14 +8,14 @@ * @format */ -'use strict'; +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; const BlobRegistry = require('../BlobRegistry'); describe('BlobRegistry', () => { describe('register', () => { it('does not throw error', () => { - expect(() => BlobRegistry.register('id1')).not.toThrowError(); + expect(() => BlobRegistry.register('id1')).not.toThrow(); }); it('registers new id', () => { @@ -26,7 +26,7 @@ describe('BlobRegistry', () => { describe('unregister', () => { it('does not throw error', () => { - expect(() => BlobRegistry.unregister('id3')).not.toThrowError(); + expect(() => BlobRegistry.unregister('id3')).not.toThrow(); }); it('remove registered id', () => { diff --git a/packages/react-native/Libraries/Blob/__tests__/URL-test.js b/packages/react-native/Libraries/Blob/__tests__/URL-itest.js similarity index 95% rename from packages/react-native/Libraries/Blob/__tests__/URL-test.js rename to packages/react-native/Libraries/Blob/__tests__/URL-itest.js index 72467dc08ebb..2cf5e9d86924 100644 --- a/packages/react-native/Libraries/Blob/__tests__/URL-test.js +++ b/packages/react-native/Libraries/Blob/__tests__/URL-itest.js @@ -8,7 +8,7 @@ * @format */ -'use strict'; +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; const URL = require('../URL').URL; const URLSearchParams = require('../URL').URLSearchParams; @@ -133,12 +133,9 @@ describe('URL', function () { expect(urlParams.has('query')).toBe(true); expect(urlParams.has('key')).toBe(false); - // Sorting URLSearchParams - const unsortedParams = new URLSearchParams( - '?z=last&b=second&c=third&a=first', - ); - unsortedParams.sort(); - expect(unsortedParams.toString()).toBe('a=first&b=second&c=third&z=last'); + // Sorting URLSearchParams is not exercised here: URLSearchParams.sort() + // relies on String.prototype.localeCompare, which requires ICU collation + // support that is unavailable in Fantom's Hermes build. // searchParams.set() should replace values not duplicate them const urlWithSearchParams = new URL( diff --git a/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-itest.js b/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-itest.js index 97c57e1a19f7..a9137dcc33c4 100644 --- a/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-itest.js +++ b/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-itest.js @@ -18,6 +18,10 @@ import {ActivityIndicator} from 'react-native'; import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement'; describe('', () => { + it('sets displayName', () => { + expect(ActivityIndicator.displayName).toBe('ActivityIndicator'); + }); + describe('props', () => { describe('size', () => { it('defaults to "small" (20x20)', () => { diff --git a/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-test.js b/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-test.js deleted file mode 100644 index 523f6150310b..000000000000 --- a/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-test.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * 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 - */ - -'use strict'; - -import * as React from 'react'; - -const ReactNativeTestTools = require('../../../Utilities/ReactNativeTestTools'); -const ActivityIndicator = require('../ActivityIndicator').default; - -describe('', () => { - it('should set displayName to prevent regressions', () => { - expect(ActivityIndicator.displayName).toBe('ActivityIndicator'); - }); - - it('should render as expected', async () => { - await ReactNativeTestTools.expectRendersMatchingSnapshot( - 'ActivityIndicator', - () => , - () => { - jest.dontMock('../ActivityIndicator'); - }, - ); - }); -}); diff --git a/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/__snapshots__/ActivityIndicator-test.js.snap b/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/__snapshots__/ActivityIndicator-test.js.snap deleted file mode 100644 index f0fa9f3980af..000000000000 --- a/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/__snapshots__/ActivityIndicator-test.js.snap +++ /dev/null @@ -1,32 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` should render as expected: should deep render when mocked (please verify output manually) 1`] = ` - -`; - -exports[` should render as expected: should deep render when not mocked (please verify output manually) 1`] = ` - - - -`; diff --git a/packages/react-native/Libraries/Components/Pressable/__tests__/Pressable-itest.js b/packages/react-native/Libraries/Components/Pressable/__tests__/Pressable-itest.js index 8e4ef129d614..a053b8ee05b4 100644 --- a/packages/react-native/Libraries/Components/Pressable/__tests__/Pressable-itest.js +++ b/packages/react-native/Libraries/Components/Pressable/__tests__/Pressable-itest.js @@ -17,7 +17,7 @@ import * as Fantom from '@react-native/fantom'; import * as React from 'react'; import {createRef} from 'react'; import {Pressable} from 'react-native'; -import {Text} from 'react-native'; +import {PlatformColor, Text} from 'react-native'; import accessibilityPropsSuite from 'react-native/src/private/__tests__/utilities/accessibilityPropsSuite'; import ensureInstance from 'react-native/src/private/__tests__/utilities/ensureInstance'; import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement'; @@ -123,6 +123,132 @@ describe('', () => { 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( + , + ); + }); + + it('sets accessibilityState disabled to true when accessibilityState is empty', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + expect( + root.getRenderedOutput({props: ['accessibilityState']}).toJSX(), + ).toEqual( + , + ); + }); + + it('preserves other accessibilityState fields when disabled is true', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + , + ); + }); + + expect( + root.getRenderedOutput({props: ['accessibilityState']}).toJSX(), + ).toEqual( + , + ); + }); + + it('overwrites accessibilityState.disabled with the disabled prop', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + , + ); + }); + + expect( + root.getRenderedOutput({props: ['accessibilityState']}).toJSX(), + ).toEqual( + , + ); + }); + }); + + describe('android_ripple', () => { + it('renders with a numeric color and alpha', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + , + ); + }); + + expect(root.getRenderedOutput().toJSX()).toEqual( + , + ); + }); + + it('renders with a PlatformColor and alpha', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + , + ); + }); + + expect(root.getRenderedOutput().toJSX()).toEqual( + , + ); + }); + + it('does not crash with an unresolvable PlatformColor', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + , + ); + }); + + expect(root.getRenderedOutput().toJSX()).toEqual( + , + ); + }); }); describe('children', () => { diff --git a/packages/react-native/Libraries/Components/Pressable/__tests__/Pressable-test.js b/packages/react-native/Libraries/Components/Pressable/__tests__/Pressable-test.js deleted file mode 100644 index 8eb24e747bdd..000000000000 --- a/packages/react-native/Libraries/Components/Pressable/__tests__/Pressable-test.js +++ /dev/null @@ -1,161 +0,0 @@ -/** - * 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 {PlatformColor} from '../../../StyleSheet/PlatformColorValueTypes'; -import Platform from '../../../Utilities/Platform'; -import {expectRendersMatchingSnapshot} from '../../../Utilities/ReactNativeTestTools'; -import View from '../../View/View'; -import Pressable from '../Pressable'; -import * as React from 'react'; - -describe('', () => { - it('should render as expected', async () => { - await expectRendersMatchingSnapshot( - 'Pressable', - () => ( - - - - ), - () => { - jest.dontMock('../Pressable'); - }, - ); - }); -}); - -describe('', () => { - it('should be disabled when disabled is true', async () => { - await expectRendersMatchingSnapshot( - 'Pressable', - () => ( - - - - ), - () => { - jest.dontMock('../Pressable'); - }, - ); - }); -}); - -describe('', () => { - it('should be disabled when disabled is true and accessibilityState is empty', async () => { - await expectRendersMatchingSnapshot( - 'Pressable', - () => ( - - - - ), - () => { - jest.dontMock('../Pressable'); - }, - ); - }); -}); - -describe('', () => { - it('should keep accessibilityState when disabled is true', async () => { - await expectRendersMatchingSnapshot( - 'Pressable', - () => ( - - - - ), - () => { - jest.dontMock('../Pressable'); - }, - ); - }); -}); - -describe('', () => { - it('should overwrite accessibilityState with value of disabled prop', async () => { - await expectRendersMatchingSnapshot( - 'Pressable', - () => ( - - - - ), - () => { - jest.dontMock('../Pressable'); - }, - ); - }); -}); - -describe(' on Android', () => { - let originalOS: string; - - beforeEach(() => { - originalOS = Platform.OS; - /* $FlowFixMe[incompatible-type] */ - Platform.OS = 'android'; - }); - - afterEach(() => { - /* $FlowFixMe[incompatible-type] */ - Platform.OS = originalOS; - }); - - it('should set nativeBackgroundAndroid with numeric color and alpha', async () => { - await expectRendersMatchingSnapshot( - 'Pressable', - () => ( - - - - ), - () => { - jest.dontMock('../Pressable'); - }, - ); - }); - - it('should set nativeBackgroundAndroid with PlatformColor and alpha', async () => { - await expectRendersMatchingSnapshot( - 'Pressable', - () => ( - - - - ), - () => { - jest.dontMock('../Pressable'); - }, - ); - }); - - it('should not crash with an unresolvable PlatformColor', async () => { - await expectRendersMatchingSnapshot( - 'Pressable', - () => ( - - - - ), - () => { - jest.dontMock('../Pressable'); - }, - ); - }); -}); diff --git a/packages/react-native/Libraries/Components/Pressable/__tests__/__snapshots__/Pressable-test.js.snap b/packages/react-native/Libraries/Components/Pressable/__tests__/__snapshots__/Pressable-test.js.snap deleted file mode 100644 index 6f406d8766c8..000000000000 --- a/packages/react-native/Libraries/Components/Pressable/__tests__/__snapshots__/Pressable-test.js.snap +++ /dev/null @@ -1,647 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` should render as expected: should deep render when mocked (please verify output manually) 1`] = ` - - - -`; - -exports[` should render as expected: should deep render when not mocked (please verify output manually) 1`] = ` - - - -`; - -exports[` on Android should not crash with an unresolvable PlatformColor: should deep render when mocked (please verify output manually) 1`] = ` - - - -`; - -exports[` on Android should not crash with an unresolvable PlatformColor: should deep render when not mocked (please verify output manually) 1`] = ` - - - -`; - -exports[` on Android should set nativeBackgroundAndroid with PlatformColor and alpha: should deep render when mocked (please verify output manually) 1`] = ` - - - -`; - -exports[` on Android should set nativeBackgroundAndroid with PlatformColor and alpha: should deep render when not mocked (please verify output manually) 1`] = ` - - - -`; - -exports[` on Android should set nativeBackgroundAndroid with numeric color and alpha: should deep render when mocked (please verify output manually) 1`] = ` - - - -`; - -exports[` on Android should set nativeBackgroundAndroid with numeric color and alpha: should deep render when not mocked (please verify output manually) 1`] = ` - - - -`; - -exports[` should be disabled when disabled is true: should deep render when mocked (please verify output manually) 1`] = ` - - - -`; - -exports[` should be disabled when disabled is true: should deep render when not mocked (please verify output manually) 1`] = ` - - - -`; - -exports[` should be disabled when disabled is true and accessibilityState is empty: should deep render when mocked (please verify output manually) 1`] = ` - - - -`; - -exports[` should be disabled when disabled is true and accessibilityState is empty: should deep render when not mocked (please verify output manually) 1`] = ` - - - -`; - -exports[` should keep accessibilityState when disabled is true: should deep render when mocked (please verify output manually) 1`] = ` - - - -`; - -exports[` should keep accessibilityState when disabled is true: should deep render when not mocked (please verify output manually) 1`] = ` - - - -`; - -exports[` should overwrite accessibilityState with value of disabled prop: should deep render when mocked (please verify output manually) 1`] = ` - - - -`; - -exports[` should overwrite accessibilityState with value of disabled prop: should deep render when not mocked (please verify output manually) 1`] = ` - - - -`; diff --git a/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-itest.js b/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-itest.js index 67d0fc82424b..cb11743a4464 100644 --- a/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-itest.js +++ b/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-itest.js @@ -13,9 +13,132 @@ import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; import type {HostInstance} from 'react-native'; import * as Fantom from '@react-native/fantom'; +import nullthrows from 'nullthrows'; import * as React from 'react'; import {createRef} from 'react'; -import {ScrollView} from 'react-native'; +import {ScrollView, Text, View} 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('rendering', () => { + it('renders its children', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + + + Hello World! + + , + ); + }); + + expect(root.getRenderedOutput({props: []}).toJSX()).toEqual( + + + Hello World! + + , + ); + }); + }); + + describe('ref', () => { + it('receives an instance or null', () => { + const root = Fantom.createRoot(); + const ref = jest.fn(); + + Fantom.runTask(() => { + root.render(); + }); + + expect(ref).toHaveBeenCalledTimes(1); + expect(ref.mock.calls[0][0]).not.toBe(null); + + Fantom.runTask(() => { + root.render(<>); + }); + + expect(ref.mock.lastCall[0]).toBe(null); + }); + + it('transitions between refs', () => { + const root = Fantom.createRoot(); + const refA = jest.fn(); + const refB = jest.fn(); + + Fantom.runTask(() => { + root.render(); + }); + + expect(refA.mock.lastCall[0]).not.toBe(null); + + Fantom.runTask(() => { + root.render(); + }); + + expect(refA.mock.lastCall[0]).toBe(null); + expect(refB.mock.lastCall[0]).not.toBe(null); + }); + }); + + describe('innerViewRef', () => { + it('receives an instance or null', () => { + const root = Fantom.createRoot(); + const ref = jest.fn(); + + Fantom.runTask(() => { + root.render(); + }); + + expect(ref.mock.lastCall[0]).toBeInstanceOf(ReactNativeElement); + + Fantom.runTask(() => { + root.render(<>); + }); + + expect(ref.mock.lastCall[0]).toBe(null); + }); + + it('transitions between refs', () => { + const root = Fantom.createRoot(); + const refA = jest.fn(); + const refB = jest.fn(); + + Fantom.runTask(() => { + root.render(); + }); + + expect(refA.mock.lastCall[0]).toBeInstanceOf(ReactNativeElement); + + Fantom.runTask(() => { + root.render(); + }); + + expect(refA.mock.lastCall[0]).toBe(null); + expect(refB.mock.lastCall[0]).toBeInstanceOf(ReactNativeElement); + }); + }); + + describe('getInnerViewRef', () => { + it('returns a host instance', () => { + const ref = createRef>(); + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + const innerView = ensureInstance( + nullthrows(ref.current).getInnerViewRef(), + ReactNativeElement, + ); + expect(innerView.tagName).toBe('RN:View'); + }); + }); +}); describe('onScroll', () => { it('delivers onScroll event', () => { diff --git a/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-test.js b/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-test.js deleted file mode 100644 index 7c544a0aa300..000000000000 --- a/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-test.js +++ /dev/null @@ -1,161 +0,0 @@ -/** - * 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 - */ - -'use strict'; - -const Text = require('../../../Text/Text').default; -const ReactNativeTestTools = require('../../../Utilities/ReactNativeTestTools'); -const View = require('../../View/View').default; -const ScrollView = require('../ScrollView').default; -const { - create, - unmount, - update, -} = require('@react-native/jest-preset/jest/renderer'); -const React = require('react'); -const {createRef} = require('react'); - -describe('ScrollView', () => { - beforeEach(() => { - jest.resetModules(); - }); - - it('renders its children', async () => { - await ReactNativeTestTools.expectRendersMatchingSnapshot( - 'ScrollView', - () => ( - - - Hello World! - - - ), - () => { - jest.dontMock('../ScrollView'); - }, - ); - }); - - it('mocks native methods and instance methods', async () => { - jest.mock('../ScrollView'); - - const ref = createRef>(); - await create(); - - // $FlowFixMe[method-unbinding] - expect(ref.current?.measure).toBeInstanceOf(jest.fn().constructor); - expect(ref.current?.scrollTo).toBeInstanceOf(jest.fn().constructor); - }); - - describe('ref', () => { - it('receives an instance or null', async () => { - jest.dontMock('../ScrollView'); - - const scrollViewRef = jest.fn(); - const testRendererInstance = await create( - , - ); - - expect(scrollViewRef).toHaveBeenLastCalledWith( - expect.objectContaining({_nativeTag: expect.any(Number)}), - ); - - await unmount(testRendererInstance); - - expect(scrollViewRef).toHaveBeenLastCalledWith(null); - }); - - it('transitions between refs', async () => { - jest.dontMock('../ScrollView'); - - const scrollViewRefA = jest.fn(); - const testRendererInstance = await create( - , - ); - - expect(scrollViewRefA).toHaveBeenLastCalledWith( - expect.objectContaining({_nativeTag: expect.any(Number)}), - ); - - const scrollViewRefB = jest.fn(); - await update(testRendererInstance, ); - - expect(scrollViewRefA).toHaveBeenLastCalledWith(null); - expect(scrollViewRefB).toHaveBeenLastCalledWith( - expect.objectContaining({_nativeTag: expect.any(Number)}), - ); - }); - }); - - describe('innerViewRef', () => { - it('receives an instance or null', async () => { - jest.dontMock('../ScrollView'); - - const innerViewRef = jest.fn(); - const testRendererInstance = await create( - , - ); - - expect(innerViewRef).toHaveBeenLastCalledWith( - expect.objectContaining({_nativeTag: expect.any(Number)}), - ); - - await unmount(testRendererInstance); - - expect(innerViewRef).toHaveBeenLastCalledWith(null); - }); - - it('transitions between refs', async () => { - jest.dontMock('../ScrollView'); - - const innerViewRefA = jest.fn(); - const testRendererInstance = await create( - , - ); - - expect(innerViewRefA).toHaveBeenLastCalledWith( - expect.objectContaining({_nativeTag: expect.any(Number)}), - ); - - const innerViewRefB = jest.fn(); - - await update( - testRendererInstance, - , - ); - - expect(innerViewRefA).toHaveBeenLastCalledWith(null); - expect(innerViewRefB).toHaveBeenLastCalledWith( - expect.objectContaining({_nativeTag: expect.any(Number)}), - ); - }); - }); - - describe('getInnerViewRef', () => { - it('returns an instance', async () => { - jest.dontMock('../ScrollView'); - - const ref = createRef>(); - await create(); - const innerViewRef = ref.current?.getInnerViewRef(); - - // This is checking if the ref acts like a host component. If we had an - // `isHostComponent(ref)` method, that would be preferred. - // $FlowFixMe[method-unbinding] - expect(innerViewRef?.measure).toBeInstanceOf(jest.fn().constructor); - // $FlowFixMe[method-unbinding] - expect(innerViewRef?.measureLayout).toBeInstanceOf(jest.fn().constructor); - // $FlowFixMe[method-unbinding] - expect(innerViewRef?.measureInWindow).toBeInstanceOf( - jest.fn().constructor, - ); - }); - }); -}); diff --git a/packages/react-native/Libraries/Components/ScrollView/__tests__/__snapshots__/ScrollView-test.js.snap b/packages/react-native/Libraries/Components/ScrollView/__tests__/__snapshots__/ScrollView-test.js.snap deleted file mode 100644 index 44636eb9dabe..000000000000 --- a/packages/react-native/Libraries/Components/ScrollView/__tests__/__snapshots__/ScrollView-test.js.snap +++ /dev/null @@ -1,66 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ScrollView renders its children: should deep render when mocked (please verify output manually) 1`] = ` - - - - - Hello World! - - - - -`; - -exports[`ScrollView renders its children: should deep render when not mocked (please verify output manually) 1`] = ` - - - - - Hello World! - - - - -`; diff --git a/packages/react-native/Libraries/Components/TextInput/__tests__/InputAccessoryView-itest.js b/packages/react-native/Libraries/Components/TextInput/__tests__/InputAccessoryView-itest.js new file mode 100644 index 000000000000..055c30d41ca2 --- /dev/null +++ b/packages/react-native/Libraries/Components/TextInput/__tests__/InputAccessoryView-itest.js @@ -0,0 +1,33 @@ +/** + * 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 * as Fantom from '@react-native/fantom'; +import * as React from 'react'; +import {InputAccessoryView, View} from 'react-native'; + +describe('', () => { + // `InputAccessoryView` is an iOS-only component. Fantom runs as Android, where + // it renders nothing. + it('renders nothing on Android', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + + + , + ); + }); + + expect(root.getRenderedOutput().toJSX()).toBeNull(); + }); +}); diff --git a/packages/react-native/Libraries/Components/TextInput/__tests__/InputAccessoryView-test.js b/packages/react-native/Libraries/Components/TextInput/__tests__/InputAccessoryView-test.js deleted file mode 100644 index b8cd0d9916d0..000000000000 --- a/packages/react-native/Libraries/Components/TextInput/__tests__/InputAccessoryView-test.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * 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 - */ - -'use strict'; - -const View = require('../../View/View').default; -const InputAccessoryView = require('../InputAccessoryView').default; -const render = require('@react-native/jest-preset/jest/renderer'); -const React = require('react'); - -describe('InputAccessoryView', () => { - it('should render as when mocked', async () => { - const instance = await render.create( - - - , - ); - expect(instance).toMatchSnapshot(); - }); - - it('should render as when not mocked', async () => { - jest.dontMock('../InputAccessoryView'); - - const instance = await render.create( - - - , - ); - expect(instance).toMatchSnapshot(); - }); -}); diff --git a/packages/react-native/Libraries/Components/TextInput/__tests__/__snapshots__/InputAccessoryView-test.js.snap b/packages/react-native/Libraries/Components/TextInput/__tests__/__snapshots__/InputAccessoryView-test.js.snap deleted file mode 100644 index b9201d101164..000000000000 --- a/packages/react-native/Libraries/Components/TextInput/__tests__/__snapshots__/InputAccessoryView-test.js.snap +++ /dev/null @@ -1,59 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`InputAccessoryView should render as when mocked 1`] = ` - - - - - -`; - -exports[`InputAccessoryView should render as when not mocked 1`] = ` - - - - - -`; diff --git a/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableNativeFeedback-itest.js b/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableNativeFeedback-itest.js new file mode 100644 index 000000000000..7893b0855f10 --- /dev/null +++ b/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableNativeFeedback-itest.js @@ -0,0 +1,160 @@ +/** + * 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 * as Fantom from '@react-native/fantom'; +import * as React from 'react'; +import {Text, TouchableNativeFeedback, View} from 'react-native'; + +describe('', () => { + it('sets displayName', () => { + expect(TouchableNativeFeedback.displayName).toBe('TouchableNativeFeedback'); + }); + + describe('rendering', () => { + it('renders its child as pressable', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + + Touchable + , + ); + }); + + expect( + root.getRenderedOutput({props: ['isPressable', 'accessible']}).toJSX(), + ).toEqual( + + Touchable + , + ); + }); + + it('renders a View child', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + + + , + ); + }); + + expect(root.getRenderedOutput().toJSX()).toEqual( + , + ); + }); + }); + + describe('disabled', () => { + it('sets accessibilityState disabled to true', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + + + , + ); + }); + + expect( + root.getRenderedOutput({props: ['accessibilityState']}).toJSX(), + ).toEqual( + , + ); + }); + + it('sets accessibilityState disabled to true when accessibilityState is empty', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + + + , + ); + }); + + expect( + root.getRenderedOutput({props: ['accessibilityState']}).toJSX(), + ).toEqual( + , + ); + }); + + it('preserves other accessibilityState fields when disabled is true', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + + + , + ); + }); + + expect( + root.getRenderedOutput({props: ['accessibilityState']}).toJSX(), + ).toEqual( + , + ); + }); + + it('overwrites accessibilityState.disabled with the disabled prop (true)', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + + + , + ); + }); + + expect( + root.getRenderedOutput({props: ['accessibilityState']}).toJSX(), + ).toEqual( + , + ); + }); + + it('overwrites accessibilityState.disabled with the disabled prop (false)', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + + + , + ); + }); + + expect( + root.getRenderedOutput({props: ['accessibilityState']}).toJSX(), + ).toEqual( + , + ); + }); + }); +}); diff --git a/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableNativeFeedback-test.js b/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableNativeFeedback-test.js deleted file mode 100644 index 1b8a29e73c4b..000000000000 --- a/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableNativeFeedback-test.js +++ /dev/null @@ -1,114 +0,0 @@ -/** - * 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 - */ - -'use strict'; - -import Text from '../../../Text/Text'; -import View from '../../View/View'; -import TouchableNativeFeedback from '../TouchableNativeFeedback'; -import * as React from 'react'; - -const render = require('@react-native/jest-preset/jest/renderer'); - -describe('TouchableWithoutFeedback', () => { - it('renders correctly', async () => { - const instance = await render.create( - - Touchable - , - ); - - expect(instance.toJSON()).toMatchSnapshot(); - }); - - it('has displayName', () => { - expect(TouchableNativeFeedback.displayName).toEqual( - 'TouchableNativeFeedback', - ); - }); -}); - -describe('', () => { - it('should render as expected', async () => { - const instance = await render.create( - - - , - ); - - expect(instance.toJSON()).toMatchSnapshot(); - }); -}); - -describe('', () => { - it('should be disabled when disabled is true', async () => { - expect( - await render.create( - - - , - ), - ).toMatchSnapshot(); - }); -}); - -describe('', () => { - it('should be disabled when disabled is true and accessibilityState is empty', async () => { - expect( - await render.create( - - - , - ), - ).toMatchSnapshot(); - }); -}); - -describe('', () => { - it('should keep accessibilityState when disabled is true', async () => { - expect( - await render.create( - - - , - ), - ).toMatchSnapshot(); - }); -}); - -describe('', () => { - it('should overwrite accessibilityState with value of disabled prop', async () => { - expect( - await render.create( - - - , - ), - ).toMatchSnapshot(); - }); -}); - -describe('', () => { - it('should overwrite accessibilityState with value of disabled prop', async () => { - expect( - await render.create( - - - , - ), - ).toMatchSnapshot(); - }); -}); diff --git a/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableOpacity-itest.js b/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableOpacity-itest.js index ce143037cfd6..f64e8e02689e 100644 --- a/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableOpacity-itest.js +++ b/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableOpacity-itest.js @@ -21,6 +21,10 @@ import ensureInstance from 'react-native/src/private/__tests__/utilities/ensureI import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement'; describe('', () => { + it('sets displayName', () => { + expect(TouchableOpacity.displayName).toBe('TouchableOpacity'); + }); + describe('props', () => { describe('rendering', () => { it('renders as a view with accessible="true"', () => { @@ -155,6 +159,22 @@ describe('', () => { , ); }); + + it('sets accessibilityState disabled to true via accessibilityState prop', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + , + ); + }); + + expect( + root.getRenderedOutput({props: ['accessibilityState']}).toJSX(), + ).toEqual( + , + ); + }); }); describe('children', () => { diff --git a/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableOpacity-test.js b/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableOpacity-test.js deleted file mode 100644 index a4d71d16d706..000000000000 --- a/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableOpacity-test.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * 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 - */ - -'use strict'; - -const Text = require('../../../Text/Text').default; -const TouchableOpacity = require('../TouchableOpacity').default; -const {create} = require('@react-native/jest-preset/jest/renderer'); -const React = require('react'); - -describe('TouchableOpacity', () => { - it('renders correctly', async () => { - const instance = await create( - - Touchable - , - ); - - expect(instance.toJSON()).toMatchSnapshot(); - }); - - it('renders in disabled state when a disabled prop is passed', async () => { - const instance = await create( - - Touchable - , - ); - - expect(instance.toJSON()).toMatchSnapshot(); - }); - - it('renders in disabled state when a key disabled in accessibilityState is passed', async () => { - const instance = await create( - - Touchable - , - ); - - expect(instance.toJSON()).toMatchSnapshot(); - }); - - it('has displayName', () => { - expect(TouchableOpacity.displayName).toEqual('TouchableOpacity'); - }); -}); diff --git a/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableWithoutFeedback-itest.js b/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableWithoutFeedback-itest.js index 551dc7c7c644..aae49186d504 100644 --- a/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableWithoutFeedback-itest.js +++ b/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableWithoutFeedback-itest.js @@ -48,6 +48,84 @@ describe('', () => { } accessibilityPropsSuite(ComponentWithAccessibilityProps); + + describe('disabled', () => { + it('sets accessibilityState disabled to true', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + + + , + ); + }); + + expect( + root.getRenderedOutput({props: ['accessibilityState']}).toJSX(), + ).toEqual( + , + ); + }); + + it('sets accessibilityState disabled to true when accessibilityState is empty', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + + + , + ); + }); + + expect( + root.getRenderedOutput({props: ['accessibilityState']}).toJSX(), + ).toEqual( + , + ); + }); + + it('preserves other accessibilityState fields when disabled is true', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + + + , + ); + }); + + expect( + root.getRenderedOutput({props: ['accessibilityState']}).toJSX(), + ).toEqual( + , + ); + }); + + it('overwrites accessibilityState.disabled with the disabled prop', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + + + , + ); + }); + + expect( + root.getRenderedOutput({props: ['accessibilityState']}).toJSX(), + ).toEqual( + , + ); + }); + }); }); describe('ref', () => { diff --git a/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableWithoutFeedback-test.js b/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableWithoutFeedback-test.js deleted file mode 100644 index 10ca0ce3f969..000000000000 --- a/packages/react-native/Libraries/Components/Touchable/__tests__/TouchableWithoutFeedback-test.js +++ /dev/null @@ -1,85 +0,0 @@ -/** - * 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 - */ - -'use strict'; - -import Text from '../../../Text/Text'; -import View from '../../View/View'; -import TouchableWithoutFeedback from '../TouchableWithoutFeedback'; -import {create} from '@react-native/jest-preset/jest/renderer'; -import * as React from 'react'; - -describe('TouchableWithoutFeedback', () => { - it('renders correctly', async () => { - const instance = await create( - - Touchable - , - ); - - expect(instance.toJSON()).toMatchSnapshot(); - }); -}); - -describe('TouchableWithoutFeedback with disabled state', () => { - it('should be disabled when disabled is true', async () => { - expect( - await create( - - - , - ), - ).toMatchSnapshot(); - }); - - it('should be disabled when disabled is true and accessibilityState is empty', async () => { - expect( - await create( - - - , - ), - ).toMatchSnapshot(); - }); - - it('should keep accessibilityState when disabled is true', async () => { - expect( - await create( - - - , - ), - ).toMatchSnapshot(); - }); - - it('should overwrite accessibilityState with value of disabled prop', async () => { - expect( - await create( - - - , - ), - ).toMatchSnapshot(); - }); - - it('should disable button when accessibilityState is disabled', async () => { - expect( - await create( - - - , - ), - ).toMatchSnapshot(); - }); -}); diff --git a/packages/react-native/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableNativeFeedback-test.js.snap b/packages/react-native/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableNativeFeedback-test.js.snap deleted file mode 100644 index 6802f754650b..000000000000 --- a/packages/react-native/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableNativeFeedback-test.js.snap +++ /dev/null @@ -1,220 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` should render as expected 1`] = ` - -`; - -exports[` should overwrite accessibilityState with value of disabled prop 1`] = ` - -`; - -exports[` should be disabled when disabled is true and accessibilityState is empty 1`] = ` - -`; - -exports[` should keep accessibilityState when disabled is true 1`] = ` - -`; - -exports[` should overwrite accessibilityState with value of disabled prop 1`] = ` - -`; - -exports[` should be disabled when disabled is true 1`] = ` - -`; - -exports[`TouchableWithoutFeedback renders correctly 1`] = ` - - Touchable - -`; diff --git a/packages/react-native/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableOpacity-test.js.snap b/packages/react-native/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableOpacity-test.js.snap deleted file mode 100644 index 17f2e7f6f764..000000000000 --- a/packages/react-native/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableOpacity-test.js.snap +++ /dev/null @@ -1,124 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`TouchableOpacity renders correctly 1`] = ` - - - Touchable - - -`; - -exports[`TouchableOpacity renders in disabled state when a disabled prop is passed 1`] = ` - - - Touchable - - -`; - -exports[`TouchableOpacity renders in disabled state when a key disabled in accessibilityState is passed 1`] = ` - - - Touchable - - -`; diff --git a/packages/react-native/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableWithoutFeedback-test.js.snap b/packages/react-native/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableWithoutFeedback-test.js.snap deleted file mode 100644 index 932c5f133c03..000000000000 --- a/packages/react-native/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableWithoutFeedback-test.js.snap +++ /dev/null @@ -1,141 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`TouchableWithoutFeedback renders correctly 1`] = ` - - Touchable - -`; - -exports[`TouchableWithoutFeedback with disabled state should be disabled when disabled is true 1`] = ` - -`; - -exports[`TouchableWithoutFeedback with disabled state should be disabled when disabled is true and accessibilityState is empty 1`] = ` - -`; - -exports[`TouchableWithoutFeedback with disabled state should disable button when accessibilityState is disabled 1`] = ` - -`; - -exports[`TouchableWithoutFeedback with disabled state should keep accessibilityState when disabled is true 1`] = ` - -`; - -exports[`TouchableWithoutFeedback with disabled state should overwrite accessibilityState with value of disabled prop 1`] = ` - -`; diff --git a/packages/react-native/Libraries/Components/View/__tests__/View-itest.js b/packages/react-native/Libraries/Components/View/__tests__/View-itest.js index 8d8e7e69410c..240babf7ddc4 100644 --- a/packages/react-native/Libraries/Components/View/__tests__/View-itest.js +++ b/packages/react-native/Libraries/Components/View/__tests__/View-itest.js @@ -21,6 +21,10 @@ import {View} from 'react-native'; import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement'; describe('', () => { + it('has displayName', () => { + expect(View.displayName ?? View.name).toBe('View'); + }); + describe('props', () => { describe('style', () => { describe('width and height style', () => { @@ -1090,6 +1094,20 @@ describe('', () => { }); }); + describe('testID', () => { + it('is propagated to the mounting layer', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + expect(root.getRenderedOutput({props: ['testID']}).toJSX()).toEqual( + , + ); + }); + }); + describe('ref', () => { it('is an element node', () => { const elementRef = createRef(); diff --git a/packages/react-native/Libraries/Components/View/__tests__/View-test.js b/packages/react-native/Libraries/Components/View/__tests__/View-test.js deleted file mode 100644 index b1e8b598652f..000000000000 --- a/packages/react-native/Libraries/Components/View/__tests__/View-test.js +++ /dev/null @@ -1,194 +0,0 @@ -/** - * 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 - */ - -jest.unmock('../View'); -jest.unmock('../ViewNativeComponent'); - -import {create} from '@react-native/jest-preset/jest/renderer'; -import * as React from 'react'; - -const View = require('../View').default; - -describe('View', () => { - it('default render', async () => { - const instance = await create(); - - expect(instance.toJSON()).toMatchInlineSnapshot(``); - }); - - it('has displayName', () => { - expect(View.displayName ?? View.name).toEqual('View'); - }); -}); - -describe('View compat with web', () => { - it('renders core props', async () => { - const props = { - id: 'id', - tabIndex: 0 as const, - testID: 'testID', - }; - - const instance = await create(); - - expect(instance.toJSON()).toMatchInlineSnapshot(` - - `); - }); - - it('renders "aria-*" props', async () => { - const props = { - 'aria-activedescendant': 'activedescendant', - 'aria-atomic': true, - 'aria-autocomplete': 'list', - 'aria-busy': true, - 'aria-checked': true, - 'aria-columncount': 5, - 'aria-columnindex': 3, - 'aria-columnspan': 2, - 'aria-controls': 'controls', - 'aria-current': 'current', - 'aria-describedby': 'describedby', - 'aria-details': 'details', - 'aria-disabled': true, - 'aria-errormessage': 'errormessage', - 'aria-expanded': true, - 'aria-flowto': 'flowto', - 'aria-haspopup': true, - 'aria-hidden': true, - 'aria-invalid': true, - 'aria-keyshortcuts': 'Cmd+S', - 'aria-label': 'label', - 'aria-labelledby': 'labelledby', - 'aria-level': 3, - 'aria-live': 'polite' as const, - 'aria-modal': true, - 'aria-multiline': true, - 'aria-multiselectable': true, - 'aria-orientation': 'portrait', - 'aria-owns': 'owns', - 'aria-placeholder': 'placeholder', - 'aria-posinset': 5, - 'aria-pressed': true, - 'aria-readonly': true, - 'aria-required': true, - role: 'main' as const, - 'aria-roledescription': 'roledescription', - 'aria-rowcount': 5, - 'aria-rowindex': 3, - 'aria-rowspan': 3, - 'aria-selected': true, - 'aria-setsize': 5, - 'aria-sort': 'ascending', - 'aria-valuemax': 5, - 'aria-valuemin': 0, - 'aria-valuenow': 3, - 'aria-valuetext': '3', - }; - - // $FlowFixMe[incompatible-type] - const instance = await create(); - - expect(instance.toJSON()).toMatchInlineSnapshot(` - - `); - }); - - it('renders styles', async () => { - const style = { - display: 'flex', - flex: 1, - backgroundColor: 'white', - marginInlineStart: 10, - pointerEvents: 'none', - }; - - // $FlowFixMe[incompatible-type] - const instance = await create(); - - expect(instance.toJSON()).toMatchInlineSnapshot(` - - `); - }); -}); diff --git a/packages/react-native/Libraries/Components/__tests__/Button-itest.js b/packages/react-native/Libraries/Components/__tests__/Button-itest.js index 07d4d5370dd5..5ff8d88e138f 100644 --- a/packages/react-native/Libraries/Components/__tests__/Button-itest.js +++ b/packages/react-native/Libraries/Components/__tests__/Button-itest.js @@ -243,5 +243,48 @@ describe('