From ca908c068156788c0561159b6c46d030d8a41956 Mon Sep 17 00:00:00 2001 From: Nikita Rubilov Date: Thu, 5 Dec 2024 06:46:15 -0800 Subject: [PATCH] Revert D66702625: Export existing Fantom tests Differential Revision: D66702625 Original commit changeset: e136ea5ea42c Original Phabricator Diff: D66702625 fbshipit-source-id: 43600daaf46e5c1522758d721b0d2f2c9abc7e25 --- .../TextInput/__tests__/TextInput-itest.js | 96 - .../__tests__/ReactFabric-Suspense-itest.js | 245 --- .../src/private/__tests__/expect-itest.js | 515 ------ .../__tests__/ReactNativeElement-itest.js | 1199 ------------- .../dom/nodes/__tests__/ReadOnlyText-itest.js | 412 ----- .../dom/nodes/__tests__/setUpFeatureFlags.js | 16 - .../__tests__/IntersectionObserver-itest.js | 1568 ----------------- .../__tests__/setUpFeatureFlags.js | 16 - .../__tests__/MutationObserver-itest.js | 1074 ----------- .../__tests__/setUpFeatureFlags.js | 16 - 10 files changed, 5157 deletions(-) delete mode 100644 packages/react-native/Libraries/Components/TextInput/__tests__/TextInput-itest.js delete mode 100644 packages/react-native/Libraries/ReactNative/__tests__/ReactFabric-Suspense-itest.js delete mode 100644 packages/react-native/src/private/__tests__/expect-itest.js delete mode 100644 packages/react-native/src/private/webapis/dom/nodes/__tests__/ReactNativeElement-itest.js delete mode 100644 packages/react-native/src/private/webapis/dom/nodes/__tests__/ReadOnlyText-itest.js delete mode 100644 packages/react-native/src/private/webapis/dom/nodes/__tests__/setUpFeatureFlags.js delete mode 100644 packages/react-native/src/private/webapis/intersectionobserver/__tests__/IntersectionObserver-itest.js delete mode 100644 packages/react-native/src/private/webapis/intersectionobserver/__tests__/setUpFeatureFlags.js delete mode 100644 packages/react-native/src/private/webapis/mutationobserver/__tests__/MutationObserver-itest.js delete mode 100644 packages/react-native/src/private/webapis/mutationobserver/__tests__/setUpFeatureFlags.js diff --git a/packages/react-native/Libraries/Components/TextInput/__tests__/TextInput-itest.js b/packages/react-native/Libraries/Components/TextInput/__tests__/TextInput-itest.js deleted file mode 100644 index 504d69fd766..00000000000 --- a/packages/react-native/Libraries/Components/TextInput/__tests__/TextInput-itest.js +++ /dev/null @@ -1,96 +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 - * @oncall react_native - */ - -import '../../../Core/InitializeCore.js'; -import * as ReactNativeTester from '../../../../src/private/__tests__/ReactNativeTester'; -import TextInput from '../TextInput'; -import * as React from 'react'; -import {useEffect, useLayoutEffect, useRef} from 'react'; - -describe('TextInput', () => { - it('creates view before dispatching view command from ref function', () => { - const root = ReactNativeTester.createRoot(); - - ReactNativeTester.runTask(() => { - root.render( - { - if (node) { - node.focus(); - } - }} - />, - ); - }); - - const mountingLogs = root.getMountingLogs(); - - expect(mountingLogs.length).toBe(2); - expect(mountingLogs[0]).toBe('create view type: `AndroidTextInput`'); - expect(mountingLogs[1]).toBe( - 'dispatch command `focus` on component `AndroidTextInput`', - ); - }); - - it('creates view before dispatching view command from useLayoutEffect', () => { - const root = ReactNativeTester.createRoot(); - - function Component() { - const textInputRef = useRef>( - null, - ); - - useLayoutEffect(() => { - textInputRef.current?.focus(); - }); - - return ; - } - ReactNativeTester.runTask(() => { - root.render(); - }); - - const mountingLogs = root.getMountingLogs(); - - expect(mountingLogs.length).toBe(2); - expect(mountingLogs[0]).toBe('create view type: `AndroidTextInput`'); - expect(mountingLogs[1]).toBe( - 'dispatch command `focus` on component `AndroidTextInput`', - ); - }); - - it('creates view before dispatching view command from useEffect', () => { - const root = ReactNativeTester.createRoot(); - - function Component() { - const textInputRef = useRef>( - null, - ); - - useEffect(() => { - textInputRef.current?.focus(); - }); - - return ; - } - ReactNativeTester.runTask(() => { - root.render(); - }); - - const mountingLogs = root.getMountingLogs(); - - expect(mountingLogs.length).toBe(2); - expect(mountingLogs[0]).toBe('create view type: `AndroidTextInput`'); - expect(mountingLogs[1]).toBe( - 'dispatch command `focus` on component `AndroidTextInput`', - ); - }); -}); diff --git a/packages/react-native/Libraries/ReactNative/__tests__/ReactFabric-Suspense-itest.js b/packages/react-native/Libraries/ReactNative/__tests__/ReactFabric-Suspense-itest.js deleted file mode 100644 index e4c7ceee69a..00000000000 --- a/packages/react-native/Libraries/ReactNative/__tests__/ReactFabric-Suspense-itest.js +++ /dev/null @@ -1,245 +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 - * @oncall react_native - */ - -import '../../Core/InitializeCore.js'; -import * as ReactNativeTester from '../../../src/private/__tests__/ReactNativeTester'; -import View from '../../Components/View/View'; -import * as React from 'react'; -import {Suspense, startTransition} from 'react'; - -let resolveFunction: (() => void) | null = null; - -// This is a workaround for a bug to get the demo running. -// TODO: replace with real implementation when the bug is fixed. -// $FlowFixMe: [missing-local-annot] -function use(promise) { - if (promise.status === 'fulfilled') { - return promise.value; - } else if (promise.status === 'rejected') { - throw promise.reason; - } else if (promise.status === 'pending') { - throw promise; - } else { - promise.status = 'pending'; - promise.then( - result => { - promise.status = 'fulfilled'; - promise.value = result; - }, - reason => { - promise.status = 'rejected'; - promise.reason = reason; - }, - ); - throw promise; - } -} - -type SquareData = { - color: 'red' | 'green', -}; - -enum SquareId { - Green = 'green-square', - Red = 'red-square', -} - -async function getGreenSquareData(): Promise { - await new Promise(resolve => { - resolveFunction = resolve; - }); - return { - color: 'green', - }; -} - -async function getRedSquareData(): Promise { - await new Promise(resolve => { - resolveFunction = resolve; - }); - return { - color: 'red', - }; -} - -const cache = new Map(); - -async function getData(squareId: SquareId): Promise { - switch (squareId) { - case SquareId.Green: - return await getGreenSquareData(); - case SquareId.Red: - return await getRedSquareData(); - } -} - -async function fetchData(squareId: SquareId): Promise { - const data = await getData(squareId); - cache.set(squareId, data); - return data; -} - -function Square(props: {squareId: SquareId}) { - let data = cache.get(props.squareId); - if (data == null) { - data = use(fetchData(props.squareId)); - } - return ; -} - -function GreenSquare() { - return ; -} - -function RedSquare() { - return ; -} - -function Fallback() { - return ; -} - -describe('Suspense', () => { - it('shows fallback if data is not available', () => { - cache.clear(); - const root = ReactNativeTester.createRoot(); - - ReactNativeTester.runTask(() => { - root.render( - }> - - , - ); - }); - - let mountingLogs = root.getMountingLogs(); - - expect(mountingLogs.length).toBe(1); - expect(mountingLogs[0]).toBe( - 'create view type: `View` nativeId: `suspense fallback`', - ); - - expect(resolveFunction).not.toBeNull(); - ReactNativeTester.runTask(() => { - resolveFunction?.(); - resolveFunction = null; - }); - - mountingLogs = root.getMountingLogs(); - - expect(mountingLogs.length).toBe(1); - expect(mountingLogs[0]).toBe( - 'create view type: `View` nativeId: `square with data: green`', - ); - - ReactNativeTester.runTask(() => { - root.render( - }> - - , - ); - }); - - mountingLogs = root.getMountingLogs(); - - expect(mountingLogs.length).toBe(1); - expect(mountingLogs[0]).toBe( - 'create view type: `View` nativeId: `suspense fallback`', - ); - - expect(resolveFunction).not.toBeNull(); - ReactNativeTester.runTask(() => { - resolveFunction?.(); - resolveFunction = null; - }); - - mountingLogs = root.getMountingLogs(); - - expect(mountingLogs.length).toBe(1); - expect(mountingLogs[0]).toBe( - 'create view type: `View` nativeId: `square with data: red`', - ); - - ReactNativeTester.runTask(() => { - root.render( - }> - - , - ); - }); - - mountingLogs = root.getMountingLogs(); - - expect(mountingLogs.length).toBe(1); - expect(mountingLogs[0]).toBe( - 'create view type: `View` nativeId: `square with data: green`', - ); - - expect(resolveFunction).toBeNull(); - - root.destroy(); - }); - - // TODO(T207868872): this test only succeeds with enableFabricCompleteRootInCommitPhase enabled. - // enableFabricCompleteRootInCommitPhase is hardcoded to true in the testing environment. - it('shows stale data while transition is happening', () => { - cache.clear(); - cache.set(SquareId.Green, {color: 'green'}); - - const root = ReactNativeTester.createRoot(); - - function App(props: {color: 'red' | 'green'}) { - return ( - }> - {props.color === 'green' ? : } - - ); - } - - ReactNativeTester.runTask(() => { - root.render(); - }); - - let mountingLogs = root.getMountingLogs(); - - expect(mountingLogs.length).toBe(1); - expect(mountingLogs[0]).toBe( - 'create view type: `View` nativeId: `square with data: green`', - ); - - expect(resolveFunction).toBeNull(); - ReactNativeTester.runTask(() => { - startTransition(() => { - root.render(); - }); - }); - - mountingLogs = root.getMountingLogs(); - - // Green square is still mounted. Fallback is not shown to the user. - expect(mountingLogs.length).toBe(0); - - expect(resolveFunction).not.toBeNull(); - ReactNativeTester.runTask(() => { - resolveFunction?.(); - resolveFunction = null; - }); - - mountingLogs = root.getMountingLogs(); - - expect(mountingLogs.length).toBe(1); - expect(mountingLogs[0]).toBe( - 'create view type: `View` nativeId: `square with data: red`', - ); - - root.destroy(); - }); -}); diff --git a/packages/react-native/src/private/__tests__/expect-itest.js b/packages/react-native/src/private/__tests__/expect-itest.js deleted file mode 100644 index 9db94a70e3d..00000000000 --- a/packages/react-native/src/private/__tests__/expect-itest.js +++ /dev/null @@ -1,515 +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 - * @oncall react_native - */ - -function ensureError(fn: () => void): void { - try { - fn(); - } catch (e) { - return; - } - - throw new Error(`Expected function to throw, but it didn't`); -} - -describe('expect', () => { - test('toThrow', () => { - expect(() => { - throw new Error(); - }).toThrow(); - - expect(() => { - throw new Error('error message'); - }).toThrow('error message'); - - expect(() => { - throw new Error('error message'); - }).not.toThrow('error message 2'); - - expect(() => {}).not.toThrow(); - - ensureError(() => { - expect(() => {}).toThrow(); - }); - - ensureError(() => { - expect(() => { - throw new Error(); - }).not.toThrow(); - }); - }); - - test('toBe', () => { - expect(1).toBe(1); - expect(1).not.toBe(2); - - const obj = {a: 1}; - const obj2 = {a: 1}; - - expect(obj).toBe(obj); - expect(obj).not.toBe(obj2); - - expect(() => { - expect(obj).not.toBe(obj); - }).toThrow(); - - expect(() => { - expect(1).not.toBe(1); - }).toThrow(); - }); - - test('toEqual', () => { - expect(1).toEqual(1); - expect(1).not.toEqual(2); - - const obj = {a: 1}; - const obj2 = {a: 1}; - const obj3 = {a: 2}; - - expect(obj).toEqual(obj); - expect(obj).toEqual(obj2); - expect(obj).not.toEqual(obj3); - - expect(null).toEqual(null); - expect(undefined).toEqual(undefined); - expect(null).not.toEqual(undefined); - - expect({a: null}).not.toEqual({a: undefined}); - expect({a: undefined}).not.toEqual({}); - - expect(() => { - expect(obj).not.toEqual(obj2); - }).toThrow(); - - expect(() => { - expect(obj).toEqual(obj3); - }).toThrow(); - - expect(() => { - expect(1).not.toEqual(1); - }).toThrow(); - - expect(() => { - expect(null).not.toEqual(null); - }).toThrow(); - - expect(() => { - expect(undefined).not.toEqual(undefined); - }).toThrow(); - - expect(() => { - expect({a: undefined}).toEqual({}); - }).toThrow(); - }); - - test('toBeInstanceOf', () => { - class Class {} - - expect(1).not.toBeInstanceOf(Number); - expect(1).not.toBeInstanceOf(Class); - - expect(new Class()).toBeInstanceOf(Class); - expect(new Class()).toBeInstanceOf(Object); - expect(new Class()).not.toBeInstanceOf(Number); - - expect(() => { - expect(1).toBeInstanceOf(Number); - }).toThrow(); - - expect(() => { - expect(new Class()).not.toBeInstanceOf(Class); - }).toThrow(); - }); - - test('toBeCloseTo', () => { - expect(1).toBeCloseTo(1.001); - expect(1).toBeCloseTo(1.01, 1); - expect(1).toBeCloseTo(1.1, 0); - - expect(() => { - expect(1).toBeCloseTo(1.01); - }).toThrow(); - - expect(() => { - expect(1).toBeCloseTo(1.1, 1); - }).toThrow(); - - expect(() => { - expect(1).toBeCloseTo(2, 0); - }).toThrow(); - }); - - test('toHaveBeenCalled', () => { - const fn = jest.fn(); - - expect(fn).not.toHaveBeenCalled(); - - expect(() => { - expect(fn).toHaveBeenCalled(); - }).toThrow(); - - fn(); - - expect(fn).toHaveBeenCalled(); - - expect(() => { - expect(fn).not.toHaveBeenCalled(); - }).toThrow(); - - // Passing functions that aren't mocks should always fail - expect(() => { - expect(() => {}).toHaveBeenCalled(); - }).toThrow(); - - expect(() => { - expect(() => {}).not.toHaveBeenCalled(); - }).toThrow(); - }); - - test('toHaveBeenCalledTimes', () => { - const fn = jest.fn(); - - expect(fn).toHaveBeenCalledTimes(0); - expect(fn).not.toHaveBeenCalledTimes(1); - - expect(() => { - expect(fn).not.toHaveBeenCalledTimes(0); - }).toThrow(); - - expect(() => { - expect(fn).toHaveBeenCalledTimes(1); - }).toThrow(); - - fn(); - - expect(fn).not.toHaveBeenCalledTimes(0); - expect(fn).toHaveBeenCalledTimes(1); - - expect(() => { - expect(fn).toHaveBeenCalledTimes(0); - }).toThrow(); - - expect(() => { - expect(fn).not.toHaveBeenCalledTimes(1); - }).toThrow(); - - // Passing functions that aren't mocks should always fail - expect(() => { - expect(() => {}).toHaveBeenCalledTimes(0); - }).toThrow(); - - expect(() => { - expect(() => {}).not.toHaveBeenCalledTimes(1); - }).toThrow(); - }); - - describe('jest.fn()', () => { - it('tracks execution of functions without implementations', () => { - const fn = jest.fn(); - - expect(fn).toBeInstanceOf(Function); - - expect(fn.mock.calls).toEqual([]); - expect(fn.mock.lastCall).toBe(undefined); - expect(fn.mock.instances).toEqual([]); - expect(fn.mock.contexts).toEqual([]); - expect(fn.mock.results).toEqual([]); - - expect(fn()).toBe(undefined); - - expect(fn.mock.calls).toEqual([[]]); - expect(fn.mock.lastCall).toEqual([]); - expect(fn.mock.instances).toEqual([undefined]); - expect(fn.mock.contexts).toEqual([global]); - expect(fn.mock.contexts[0]).toBe(global); - expect(fn.mock.results).toEqual([{value: undefined, isThrow: false}]); - }); - - it('tracks execution of methods without implementations', () => { - const fn = jest.fn(); - - expect(fn).toBeInstanceOf(Function); - - expect(fn.mock.calls).toEqual([]); - expect(fn.mock.lastCall).toBe(undefined); - expect(fn.mock.instances).toEqual([]); - expect(fn.mock.contexts).toEqual([]); - expect(fn.mock.results).toEqual([]); - - const obj = {fn}; - expect(obj.fn()).toBe(undefined); - - expect(fn.mock.calls).toEqual([[]]); - expect(fn.mock.lastCall).toEqual([]); - expect(fn.mock.instances).toEqual([undefined]); - expect(fn.mock.contexts).toEqual([obj]); - expect(fn.mock.contexts[0]).toBe(obj); - expect(fn.mock.results).toEqual([{value: undefined, isThrow: false}]); - }); - - it('tracks constructors without implementations', () => { - const fn = jest.fn(); - - expect(fn).toBeInstanceOf(Function); - - expect(fn.mock.calls).toEqual([]); - expect(fn.mock.lastCall).toBe(undefined); - expect(fn.mock.instances).toEqual([]); - expect(fn.mock.contexts).toEqual([]); - expect(fn.mock.results).toEqual([]); - - // $FlowExpectedError[invalid-constructor] - const instance = new fn(); - expect(instance).toBeInstanceOf(Object); - - expect(fn.mock.calls).toEqual([[]]); - expect(fn.mock.lastCall).toEqual([]); - expect(fn.mock.instances).toEqual([instance]); - expect(fn.mock.instances[0]).toBe(instance); - expect(fn.mock.contexts).toEqual([instance]); - expect(fn.mock.contexts[0]).toBe(instance); - expect(fn.mock.results).toEqual([{value: undefined, isThrow: false}]); - }); - - it('tracks execution of functions with an implementation', () => { - const fn = jest.fn((a, b) => { - return a + b; - }); - - expect(fn).toBeInstanceOf(Function); - - expect(fn.mock.calls).toEqual([]); - expect(fn.mock.lastCall).toBe(undefined); - expect(fn.mock.instances).toEqual([]); - expect(fn.mock.contexts).toEqual([]); - expect(fn.mock.results).toEqual([]); - - expect(fn(1, 2)).toBe(3); - - expect(fn.mock.calls).toEqual([[1, 2]]); - expect(fn.mock.lastCall).toEqual([1, 2]); - expect(fn.mock.instances).toEqual([undefined]); - expect(fn.mock.contexts).toEqual([global]); - expect(fn.mock.contexts[0]).toBe(global); - expect(fn.mock.results).toEqual([{value: 3, isThrow: false}]); - }); - - it('tracks execution of methods with an implementation', () => { - const fn = jest.fn(function (this: {prop: number}): number { - return this.prop; - }); - - expect(fn).toBeInstanceOf(Function); - - expect(fn.mock.calls).toEqual([]); - expect(fn.mock.lastCall).toBe(undefined); - expect(fn.mock.instances).toEqual([]); - expect(fn.mock.contexts).toEqual([]); - expect(fn.mock.results).toEqual([]); - - const obj = {fn, prop: 2}; - expect(obj.fn()).toBe(2); - - expect(fn.mock.calls).toEqual([[]]); - expect(fn.mock.lastCall).toEqual([]); - expect(fn.mock.instances).toEqual([undefined]); - expect(fn.mock.contexts).toEqual([obj]); - expect(fn.mock.contexts[0]).toBe(obj); - expect(fn.mock.results).toEqual([{value: 2, isThrow: false}]); - }); - - it('tracks constructors with an implementation', () => { - const fn = jest.fn(function (this: {prop: number}) { - this.prop = 3; - }); - - expect(fn).toBeInstanceOf(Function); - - expect(fn.mock.calls).toEqual([]); - expect(fn.mock.lastCall).toBe(undefined); - expect(fn.mock.instances).toEqual([]); - expect(fn.mock.contexts).toEqual([]); - expect(fn.mock.results).toEqual([]); - - // $FlowExpectedError[invalid-constructor] - const instance = new fn(); - expect(instance).toBeInstanceOf(Object); - - expect(instance.prop).toBe(3); - - expect(fn.mock.calls).toEqual([[]]); - expect(fn.mock.lastCall).toEqual([]); - expect(fn.mock.instances).toEqual([instance]); - expect(fn.mock.instances[0]).toBe(instance); - expect(fn.mock.contexts).toEqual([instance]); - expect(fn.mock.contexts[0]).toBe(instance); - expect(fn.mock.results).toEqual([{value: undefined, isThrow: false}]); - }); - }); - - test('toBeNull()', () => { - expect(null).toBeNull(); - expect('string value').not.toBeNull(); - - expect(() => { - expect(null).not.toBeNull(); - }).toThrow(); - - expect(() => { - expect('string value').toBeNull(); - }).toThrow(); - }); - - test('toBeLessThan', () => { - expect(1).toBeLessThan(2); - - expect(1).not.toBeLessThan(1); - - expect(1).not.toBeLessThan(0); - - expect(() => { - expect(1).toBeLessThan(0); - }).toThrow(); - - expect(() => { - expect(1).toBeLessThan(1); - }).toThrow(); - - expect(() => { - expect(1).not.toBeLessThan(2); - }).toThrow(); - - // Should always throw if the received value isn't a number - expect(() => { - expect('string value').toBeLessThan(1); - }).toThrow(); - - expect(() => { - expect('string value').not.toBeLessThan(1); - }).toThrow(); - - // Should always throw if the expected value isn't a number - expect(() => { - // $FlowExpectedError[incompatible-call] - expect(1).toBeLessThan('string value'); - }).toThrow(); - - expect(() => { - // $FlowExpectedError[incompatible-call] - expect(1).not.toBeLessThan('string value'); - }).toThrow(); - }); - - test('toBeLessThanOrEqual', () => { - expect(1).toBeLessThanOrEqual(1); - expect(1).toBeLessThanOrEqual(2); - - expect(1).not.toBeLessThanOrEqual(0.8); - - expect(() => { - expect(1).not.toBeLessThanOrEqual(1); - }).toThrow(); - - expect(() => { - expect(1).not.toBeLessThanOrEqual(2); - }).toThrow(); - - // Should always throw if the received value isn't a number - expect(() => { - expect('string value').toBeLessThanOrEqual(1); - }).toThrow(); - - expect(() => { - expect('string value').not.toBeLessThanOrEqual(1); - }).toThrow(); - - // Should always throw if the expected value isn't a number - expect(() => { - // $FlowExpectedError[incompatible-call] - expect(1).toBeLessThanOrEqual('string value'); - }).toThrow(); - - expect(() => { - // $FlowExpectedError[incompatible-call] - expect(1).not.toBeLessThanOrEqual('string value'); - }).toThrow(); - }); - - test('toBeGreaterThan', () => { - expect(1).toBeGreaterThan(0); - - expect(1).not.toBeGreaterThan(1); - - expect(1).not.toBeGreaterThan(2); - - expect(() => { - expect(1).toBeGreaterThan(2); - }).toThrow(); - - expect(() => { - expect(1).not.toBeGreaterThan(0); - }).toThrow(); - - // Should always throw if the received value isn't a number - expect(() => { - expect('string value').toBeGreaterThan(1); - }).toThrow(); - - expect(() => { - expect('string value').not.toBeGreaterThan(1); - }).toThrow(); - - // Should always throw if the expected value isn't a number - expect(() => { - // $FlowExpectedError[incompatible-call] - expect(1).toBeGreaterThan('string value'); - }).toThrow(); - - expect(() => { - // $FlowExpectedError[incompatible-call] - expect(1).not.toBeGreaterThan('string value'); - }).toThrow(); - }); - - test('toBeGreaterThanOrEqual', () => { - expect(1).toBeGreaterThanOrEqual(0); - expect(1).toBeGreaterThanOrEqual(1); - - expect(1).not.toBeGreaterThanOrEqual(2); - - expect(() => { - expect(1).not.toBeGreaterThanOrEqual(0); - }).toThrow(); - - expect(() => { - expect(1).not.toBeGreaterThanOrEqual(1); - }).toThrow(); - - // Should always throw if the received value isn't a number - expect(() => { - expect('string value').toBeGreaterThanOrEqual(1); - }).toThrow(); - - expect(() => { - expect('string value').not.toBeGreaterThanOrEqual(1); - }).toThrow(); - - // Should always throw if the expected value isn't a number - expect(() => { - // $FlowExpectedError[incompatible-call] - expect(1).toBeGreaterThanOrEqual('string value'); - }).toThrow(); - - expect(() => { - // $FlowExpectedError[incompatible-call] - expect(1).not.toBeGreaterThanOrEqual('string value'); - }).toThrow(); - }); -}); diff --git a/packages/react-native/src/private/webapis/dom/nodes/__tests__/ReactNativeElement-itest.js b/packages/react-native/src/private/webapis/dom/nodes/__tests__/ReactNativeElement-itest.js deleted file mode 100644 index ad8d0a1bccf..00000000000 --- a/packages/react-native/src/private/webapis/dom/nodes/__tests__/ReactNativeElement-itest.js +++ /dev/null @@ -1,1199 +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 - * @oncall react_native - */ - -import './setUpFeatureFlags'; -import '../../../../../../Libraries/Core/InitializeCore.js'; - -import * as ReactNativeTester from '../../../../__tests__/ReactNativeTester'; -import ScrollView from '../../../../../../Libraries/Components/ScrollView/ScrollView'; -import View from '../../../../../../Libraries/Components/View/View'; -import { - NativeText, - NativeVirtualText, -} from '../../../../../../Libraries/Text/TextNativeComponent'; -import HTMLCollection from '../../oldstylecollections/HTMLCollection'; -import NodeList from '../../oldstylecollections/NodeList'; -import ReactNativeElement from '../ReactNativeElement'; -import ReadOnlyNode from '../ReadOnlyNode'; -import * as React from 'react'; - -function ensureReactNativeElement(value: mixed): ReactNativeElement { - if (!(value instanceof ReactNativeElement)) { - throw new Error( - `Expected instance of ReactNativeElement but got ${String(value)}`, - ); - } - - return value; -} - -/* eslint-disable no-bitwise */ - -describe('ReactNativeElement', () => { - it('should be used to create public instances when the `enableAccessToHostTreeInFabric` feature flag is enabled', () => { - let node; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - node = receivedNode; - }} - />, - ); - }); - - expect(node).toBeInstanceOf(ReactNativeElement); - }); - - describe('extends `ReadOnlyNode`', () => { - describe('nodeType', () => { - it('returns ReadOnlyNode.ELEMENT_NODE', () => { - let lastParentNode; - let lastChildNodeA; - let lastChildNodeB; - let lastChildNodeC; - - // Initial render with 3 children - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - lastParentNode = node; - }}> - { - lastChildNodeA = node; - }} - /> - { - lastChildNodeB = node; - }} - /> - { - lastChildNodeC = node; - }} - /> - , - ); - }); - - const parentNode = ensureReactNativeElement(lastParentNode); - const childNodeA = ensureReactNativeElement(lastChildNodeA); - const childNodeB = ensureReactNativeElement(lastChildNodeB); - const childNodeC = ensureReactNativeElement(lastChildNodeC); - - expect(parentNode.nodeType).toBe(ReadOnlyNode.ELEMENT_NODE); - expect(childNodeA.nodeType).toBe(ReadOnlyNode.ELEMENT_NODE); - expect(childNodeB.nodeType).toBe(ReadOnlyNode.ELEMENT_NODE); - expect(childNodeC.nodeType).toBe(ReadOnlyNode.ELEMENT_NODE); - }); - }); - - describe('nodeValue', () => { - it('returns null', () => { - let lastParentNode; - let lastChildNodeA; - let lastChildNodeB; - let lastChildNodeC; - - // Initial render with 3 children - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - lastParentNode = node; - }}> - { - lastChildNodeA = node; - }} - /> - { - lastChildNodeB = node; - }} - /> - { - lastChildNodeC = node; - }} - /> - , - ); - }); - - const parentNode = ensureReactNativeElement(lastParentNode); - const childNodeA = ensureReactNativeElement(lastChildNodeA); - const childNodeB = ensureReactNativeElement(lastChildNodeB); - const childNodeC = ensureReactNativeElement(lastChildNodeC); - - expect(parentNode.nodeValue).toBe(null); - expect(childNodeA.nodeValue).toBe(null); - expect(childNodeB.nodeValue).toBe(null); - expect(childNodeC.nodeValue).toBe(null); - }); - }); - - describe('childNodes / hasChildNodes()', () => { - it('returns updated child nodes information', () => { - let lastParentNode; - let lastChildNodeA; - let lastChildNodeB; - let lastChildNodeC; - - // Initial render with 3 children - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - lastParentNode = node; - }}> - { - lastChildNodeA = node; - }} - /> - { - lastChildNodeB = node; - }} - /> - { - lastChildNodeC = node; - }} - /> - , - ); - }); - - const parentNode = ensureReactNativeElement(lastParentNode); - const childNodeA = ensureReactNativeElement(lastChildNodeA); - const childNodeB = ensureReactNativeElement(lastChildNodeB); - const childNodeC = ensureReactNativeElement(lastChildNodeC); - - const childNodes = parentNode.childNodes; - expect(childNodes).toBeInstanceOf(NodeList); - expect(childNodes.length).toBe(3); - expect(childNodes[0]).toBe(childNodeA); - expect(childNodes[1]).toBe(childNodeB); - expect(childNodes[2]).toBe(childNodeC); - - expect(parentNode.hasChildNodes()).toBe(true); - - // Remove one of the children - ReactNativeTester.runTask(() => { - root.render( - - - - , - ); - }); - - const childNodesAfterUpdate = parentNode.childNodes; - expect(childNodesAfterUpdate).toBeInstanceOf(NodeList); - expect(childNodesAfterUpdate.length).toBe(2); - expect(childNodesAfterUpdate[0]).toBe(childNodeA); - expect(childNodesAfterUpdate[1]).toBe(childNodeB); - - expect(parentNode.hasChildNodes()).toBe(true); - - // Unmount node - ReactNativeTester.runTask(() => { - root.render(<>); - }); - - const childNodesAfterUnmount = parentNode.childNodes; - expect(childNodesAfterUnmount).toBeInstanceOf(NodeList); - expect(childNodesAfterUnmount.length).toBe(0); - - expect(parentNode.hasChildNodes()).toBe(false); - }); - }); - - describe('firstChild / lastChild / previousSibling / nextSibling / parentNode / parentElement / getRootNode()', () => { - it('return updated relative nodes', () => { - let lastParentNode; - let lastChildNodeA; - let lastChildNodeB; - let lastChildNodeC; - - // Initial render with 3 children - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - lastParentNode = node; - }}> - { - lastChildNodeA = node; - }} - /> - { - lastChildNodeB = node; - }} - /> - { - lastChildNodeC = node; - }} - /> - , - ); - }); - - const parentNode = ensureReactNativeElement(lastParentNode); - const childNodeA = ensureReactNativeElement(lastChildNodeA); - const childNodeB = ensureReactNativeElement(lastChildNodeB); - const childNodeC = ensureReactNativeElement(lastChildNodeC); - - expect(parentNode.isConnected).toBe(true); - expect(parentNode.firstChild).toBe(childNodeA); - expect(parentNode.lastChild).toBe(childNodeC); - expect(parentNode.previousSibling).toBe(null); - expect(parentNode.nextSibling).toBe(null); - // Document-level root nodes are not supported yet - expect(parentNode.parentNode).toBe(null); - expect(parentNode.parentElement).toBe(null); - expect(parentNode.getRootNode()).toBe(parentNode); - - expect(childNodeA.isConnected).toBe(true); - expect(childNodeA.firstChild).toBe(null); - expect(childNodeA.lastChild).toBe(null); - expect(childNodeA.previousSibling).toBe(null); - expect(childNodeA.nextSibling).toBe(childNodeB); - expect(childNodeA.parentNode).toBe(parentNode); - expect(childNodeA.parentElement).toBe(parentNode); - expect(childNodeA.getRootNode()).toBe(parentNode); - - expect(childNodeB.isConnected).toBe(true); - expect(childNodeB.firstChild).toBe(null); - expect(childNodeB.lastChild).toBe(null); - expect(childNodeB.previousSibling).toBe(childNodeA); - expect(childNodeB.nextSibling).toBe(childNodeC); - expect(childNodeB.parentNode).toBe(parentNode); - expect(childNodeB.parentElement).toBe(parentNode); - expect(childNodeB.getRootNode()).toBe(parentNode); - - expect(childNodeC.isConnected).toBe(true); - expect(childNodeC.firstChild).toBe(null); - expect(childNodeC.lastChild).toBe(null); - expect(childNodeC.previousSibling).toBe(childNodeB); - expect(childNodeC.nextSibling).toBe(null); - expect(childNodeC.parentNode).toBe(parentNode); - expect(childNodeC.parentElement).toBe(parentNode); - expect(childNodeC.getRootNode()).toBe(parentNode); - - // Remove one of the children - ReactNativeTester.runTask(() => { - root.render( - - - - , - ); - }); - - expect(parentNode.isConnected).toBe(true); - expect(parentNode.firstChild).toBe(childNodeA); - expect(parentNode.lastChild).toBe(childNodeB); - expect(parentNode.previousSibling).toBe(null); - expect(parentNode.nextSibling).toBe(null); - // Document-level root nodes are not supported yet - expect(parentNode.parentNode).toBe(null); - expect(parentNode.parentElement).toBe(null); - expect(parentNode.getRootNode()).toBe(parentNode); - - expect(childNodeA.isConnected).toBe(true); - expect(childNodeA.firstChild).toBe(null); - expect(childNodeA.lastChild).toBe(null); - expect(childNodeA.previousSibling).toBe(null); - expect(childNodeA.nextSibling).toBe(childNodeB); - expect(childNodeA.parentNode).toBe(parentNode); - expect(childNodeA.parentElement).toBe(parentNode); - expect(childNodeA.getRootNode()).toBe(parentNode); - - expect(childNodeB.isConnected).toBe(true); - expect(childNodeB.firstChild).toBe(null); - expect(childNodeB.lastChild).toBe(null); - expect(childNodeB.previousSibling).toBe(childNodeA); - expect(childNodeB.nextSibling).toBe(null); - expect(childNodeB.parentNode).toBe(parentNode); - expect(childNodeB.parentElement).toBe(parentNode); - expect(childNodeB.getRootNode()).toBe(parentNode); - - // Disconnected - expect(childNodeC.isConnected).toBe(false); - expect(childNodeC.firstChild).toBe(null); - expect(childNodeC.lastChild).toBe(null); - expect(childNodeC.previousSibling).toBe(null); - expect(childNodeC.nextSibling).toBe(null); - expect(childNodeC.parentNode).toBe(null); - expect(childNodeC.parentElement).toBe(null); - expect(childNodeC.getRootNode()).toBe(childNodeC); - - // Unmount node - ReactNativeTester.runTask(() => { - root.render(<>); - }); - - // Disconnected - expect(parentNode.isConnected).toBe(false); - expect(parentNode.firstChild).toBe(null); - expect(parentNode.lastChild).toBe(null); - expect(parentNode.previousSibling).toBe(null); - expect(parentNode.nextSibling).toBe(null); - expect(parentNode.parentNode).toBe(null); - expect(parentNode.parentElement).toBe(null); - expect(parentNode.getRootNode()).toBe(parentNode); - - // Disconnected - expect(childNodeA.isConnected).toBe(false); - expect(childNodeA.firstChild).toBe(null); - expect(childNodeA.lastChild).toBe(null); - expect(childNodeA.previousSibling).toBe(null); - expect(childNodeA.nextSibling).toBe(null); - expect(childNodeA.parentNode).toBe(null); - expect(childNodeA.parentElement).toBe(null); - expect(childNodeA.getRootNode()).toBe(childNodeA); - - // Disconnected - expect(childNodeB.isConnected).toBe(false); - expect(childNodeB.firstChild).toBe(null); - expect(childNodeB.lastChild).toBe(null); - expect(childNodeB.previousSibling).toBe(null); - expect(childNodeB.nextSibling).toBe(null); - expect(childNodeB.parentNode).toBe(null); - expect(childNodeB.parentElement).toBe(null); - expect(childNodeB.getRootNode()).toBe(childNodeB); - - // Disconnected - expect(childNodeC.isConnected).toBe(false); - expect(childNodeC.firstChild).toBe(null); - expect(childNodeC.lastChild).toBe(null); - expect(childNodeC.previousSibling).toBe(null); - expect(childNodeC.nextSibling).toBe(null); - expect(childNodeC.parentNode).toBe(null); - expect(childNodeC.parentElement).toBe(null); - expect(childNodeC.getRootNode()).toBe(childNodeC); - }); - }); - - describe('compareDocumentPosition / contains', () => { - it('handles containment, order and connection', () => { - let lastParentNode; - let lastChildNodeA; - let lastChildNodeAA; - let lastChildNodeB; - let lastChildNodeBB; - - // Initial render with 2 children - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - lastParentNode = node; - }}> - { - lastChildNodeA = node; - }}> - { - lastChildNodeAA = node; - }} - /> - - { - lastChildNodeB = node; - }}> - { - lastChildNodeBB = node; - }} - /> - - , - ); - }); - - const parentNode = ensureReactNativeElement(lastParentNode); - const childNodeA = ensureReactNativeElement(lastChildNodeA); - const childNodeAA = ensureReactNativeElement(lastChildNodeAA); - const childNodeB = ensureReactNativeElement(lastChildNodeB); - const childNodeBB = ensureReactNativeElement(lastChildNodeBB); - - // Node/self - expect(parentNode.compareDocumentPosition(parentNode)).toBe(0); - expect(parentNode.contains(parentNode)).toBe(true); - // Parent/child - expect(parentNode.compareDocumentPosition(childNodeA)).toBe( - ReadOnlyNode.DOCUMENT_POSITION_CONTAINED_BY | - ReadOnlyNode.DOCUMENT_POSITION_FOLLOWING, - ); - expect(parentNode.contains(childNodeA)).toBe(true); - // Child/parent - expect(childNodeA.compareDocumentPosition(parentNode)).toBe( - ReadOnlyNode.DOCUMENT_POSITION_CONTAINS | - ReadOnlyNode.DOCUMENT_POSITION_PRECEDING, - ); - expect(childNodeA.contains(parentNode)).toBe(false); - // Grandparent/grandchild - expect(parentNode.compareDocumentPosition(childNodeAA)).toBe( - ReadOnlyNode.DOCUMENT_POSITION_CONTAINED_BY | - ReadOnlyNode.DOCUMENT_POSITION_FOLLOWING, - ); - expect(parentNode.contains(childNodeAA)).toBe(true); - // Grandchild/grandparent - expect(childNodeAA.compareDocumentPosition(parentNode)).toBe( - ReadOnlyNode.DOCUMENT_POSITION_CONTAINS | - ReadOnlyNode.DOCUMENT_POSITION_PRECEDING, - ); - expect(childNodeAA.contains(parentNode)).toBe(false); - // Sibling/sibling - expect(childNodeA.compareDocumentPosition(childNodeB)).toBe( - ReadOnlyNode.DOCUMENT_POSITION_FOLLOWING, - ); - expect(childNodeA.contains(childNodeB)).toBe(false); - // Sibling/sibling - expect(childNodeB.compareDocumentPosition(childNodeA)).toBe( - ReadOnlyNode.DOCUMENT_POSITION_PRECEDING, - ); - expect(childNodeB.contains(childNodeA)).toBe(false); - // Cousing/cousing - expect(childNodeAA.compareDocumentPosition(childNodeBB)).toBe( - ReadOnlyNode.DOCUMENT_POSITION_FOLLOWING, - ); - expect(childNodeAA.contains(childNodeBB)).toBe(false); - // Cousing/cousing - expect(childNodeBB.compareDocumentPosition(childNodeAA)).toBe( - ReadOnlyNode.DOCUMENT_POSITION_PRECEDING, - ); - expect(childNodeBB.contains(childNodeAA)).toBe(false); - - // Remove one of the children - ReactNativeTester.runTask(() => { - root.render( - - - - , - ); - }); - - // Node/disconnected - expect(parentNode.compareDocumentPosition(childNodeAA)).toBe( - ReadOnlyNode.DOCUMENT_POSITION_DISCONNECTED, - ); - expect(parentNode.contains(childNodeAA)).toBe(false); - // Disconnected/node - expect(childNodeAA.compareDocumentPosition(parentNode)).toBe( - ReadOnlyNode.DOCUMENT_POSITION_DISCONNECTED, - ); - expect(childNodeAA.contains(parentNode)).toBe(false); - // Disconnected/disconnected - expect(childNodeAA.compareDocumentPosition(childNodeBB)).toBe( - ReadOnlyNode.DOCUMENT_POSITION_DISCONNECTED, - ); - expect(childNodeAA.contains(childNodeBB)).toBe(false); - // Disconnected/disconnected - expect(childNodeBB.compareDocumentPosition(childNodeAA)).toBe( - ReadOnlyNode.DOCUMENT_POSITION_DISCONNECTED, - ); - expect(childNodeBB.contains(childNodeAA)).toBe(false); - // Disconnected/self - expect(childNodeBB.compareDocumentPosition(childNodeBB)).toBe(0); - expect(childNodeBB.contains(childNodeBB)).toBe(true); - - let lastAltParentNode; - - // Similar structure in a different tree - const root2 = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root2.render( - { - lastAltParentNode = node; - }}> - - - , - ); - }); - - const altParentNode = ensureReactNativeElement(lastAltParentNode); - - // Node/same position in different tree - expect(altParentNode.compareDocumentPosition(parentNode)).toBe( - ReadOnlyNode.DOCUMENT_POSITION_DISCONNECTED, - ); - expect(parentNode.compareDocumentPosition(altParentNode)).toBe( - ReadOnlyNode.DOCUMENT_POSITION_DISCONNECTED, - ); - expect(parentNode.contains(altParentNode)).toBe(false); - expect(altParentNode.contains(parentNode)).toBe(false); - - // Node/child position in different tree - expect(altParentNode.compareDocumentPosition(childNodeA)).toBe( - ReadOnlyNode.DOCUMENT_POSITION_DISCONNECTED, - ); - expect(childNodeA.compareDocumentPosition(altParentNode)).toBe( - ReadOnlyNode.DOCUMENT_POSITION_DISCONNECTED, - ); - expect(altParentNode.contains(childNodeA)).toBe(false); - expect(childNodeA.contains(altParentNode)).toBe(false); - }); - }); - }); - - describe('extends `ReadOnlyElement`', () => { - describe('children / childElementCount', () => { - it('return updated element children information', () => { - let lastParentElement; - let lastChildElementA; - let lastChildElementB; - let lastChildElementC; - - // Initial render with 3 children - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - lastParentElement = element; - }}> - { - lastChildElementA = element; - }} - /> - { - lastChildElementB = element; - }} - /> - { - lastChildElementC = element; - }} - /> - , - ); - }); - - const parentElement = ensureReactNativeElement(lastParentElement); - const childElementA = ensureReactNativeElement(lastChildElementA); - const childElementB = ensureReactNativeElement(lastChildElementB); - const childElementC = ensureReactNativeElement(lastChildElementC); - - const children = parentElement.children; - expect(children).toBeInstanceOf(HTMLCollection); - expect(children.length).toBe(3); - expect(children[0]).toBe(childElementA); - expect(children[1]).toBe(childElementB); - expect(children[2]).toBe(childElementC); - - expect(parentElement.childElementCount).toBe(3); - - // Remove one of the children - ReactNativeTester.runTask(() => { - root.render( - - - - , - ); - }); - - const childrenAfterUpdate = parentElement.children; - expect(childrenAfterUpdate).toBeInstanceOf(HTMLCollection); - expect(childrenAfterUpdate.length).toBe(2); - expect(childrenAfterUpdate[0]).toBe(childElementA); - expect(childrenAfterUpdate[1]).toBe(childElementB); - - expect(parentElement.childElementCount).toBe(2); - - // Unmount node - ReactNativeTester.runTask(() => { - root.render(<>); - }); - - const childrenAfterUnmount = parentElement.children; - expect(childrenAfterUnmount).toBeInstanceOf(HTMLCollection); - expect(childrenAfterUnmount.length).toBe(0); - - expect(parentElement.childElementCount).toBe(0); - }); - }); - - describe('firstElementChild / lastElementChild / previousElementSibling / nextElementSibling', () => { - it('return updated relative elements', () => { - let lastParentElement; - let lastChildElementA; - let lastChildElementB; - let lastChildElementC; - - // Initial render with 3 children - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - lastParentElement = element; - }}> - { - lastChildElementA = element; - }} - /> - { - lastChildElementB = element; - }} - /> - { - lastChildElementC = element; - }} - /> - , - ); - }); - - const parentElement = ensureReactNativeElement(lastParentElement); - const childElementA = ensureReactNativeElement(lastChildElementA); - const childElementB = ensureReactNativeElement(lastChildElementB); - const childElementC = ensureReactNativeElement(lastChildElementC); - - expect(parentElement.firstElementChild).toBe(childElementA); - expect(parentElement.lastElementChild).toBe(childElementC); - expect(parentElement.previousElementSibling).toBe(null); - expect(parentElement.nextElementSibling).toBe(null); - - expect(childElementA.firstElementChild).toBe(null); - expect(childElementA.lastElementChild).toBe(null); - expect(childElementA.previousElementSibling).toBe(null); - expect(childElementA.nextElementSibling).toBe(childElementB); - - expect(childElementB.firstElementChild).toBe(null); - expect(childElementB.lastElementChild).toBe(null); - expect(childElementB.previousElementSibling).toBe(childElementA); - expect(childElementB.nextElementSibling).toBe(childElementC); - - expect(childElementC.firstElementChild).toBe(null); - expect(childElementC.lastElementChild).toBe(null); - expect(childElementC.previousElementSibling).toBe(childElementB); - expect(childElementC.nextElementSibling).toBe(null); - - // Remove one of the children - ReactNativeTester.runTask(() => { - root.render( - - - - , - ); - }); - - expect(parentElement.firstElementChild).toBe(childElementA); - expect(parentElement.lastElementChild).toBe(childElementB); - expect(parentElement.previousElementSibling).toBe(null); - expect(parentElement.nextElementSibling).toBe(null); - - expect(childElementA.firstElementChild).toBe(null); - expect(childElementA.lastElementChild).toBe(null); - expect(childElementA.previousElementSibling).toBe(null); - expect(childElementA.nextElementSibling).toBe(childElementB); - - expect(childElementB.firstElementChild).toBe(null); - expect(childElementB.lastElementChild).toBe(null); - expect(childElementB.previousElementSibling).toBe(childElementA); - expect(childElementB.nextElementSibling).toBe(null); - - // Disconnected - expect(childElementC.firstElementChild).toBe(null); - expect(childElementC.lastElementChild).toBe(null); - expect(childElementC.previousElementSibling).toBe(null); - expect(childElementC.nextElementSibling).toBe(null); - - // Unmount node - ReactNativeTester.runTask(() => { - root.render(<>); - }); - - // Disconnected - expect(parentElement.firstElementChild).toBe(null); - expect(parentElement.lastElementChild).toBe(null); - expect(parentElement.previousElementSibling).toBe(null); - expect(parentElement.nextElementSibling).toBe(null); - - // Disconnected - expect(childElementA.firstElementChild).toBe(null); - expect(childElementA.lastElementChild).toBe(null); - expect(childElementA.previousElementSibling).toBe(null); - expect(childElementA.nextElementSibling).toBe(null); - - // Disconnected - expect(childElementB.firstElementChild).toBe(null); - expect(childElementB.lastElementChild).toBe(null); - expect(childElementB.previousElementSibling).toBe(null); - expect(childElementB.nextElementSibling).toBe(null); - - // Disconnected - expect(childElementC.firstElementChild).toBe(null); - expect(childElementC.lastElementChild).toBe(null); - expect(childElementC.previousElementSibling).toBe(null); - expect(childElementC.nextElementSibling).toBe(null); - }); - }); - - describe('textContent', () => { - it('should return the concatenated values of all its text node descendants (using DFS)', () => { - let lastParentNode; - let lastChildNodeA; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - lastParentNode = node; - }}> - Hello - { - lastChildNodeA = node; - }}> - world! - - , - ); - }); - - const parentNode = ensureReactNativeElement(lastParentNode); - const childNodeA = ensureReactNativeElement(lastChildNodeA); - - expect(parentNode.textContent).toBe('Hello world!'); - expect(childNodeA.textContent).toBe('world!'); - - let lastChildNodeB; - ReactNativeTester.runTask(() => { - root.render( - - Hello - - world - - { - lastChildNodeB = node; - }}> - - - again - and again! - - - - , - ); - }); - - const childNodeB = ensureReactNativeElement(lastChildNodeB); - - expect(parentNode.textContent).toBe('Hello world again and again!'); - expect(childNodeA.textContent).toBe('world '); - expect(childNodeB.textContent).toBe('again and again!'); - }); - }); - - describe('getBoundingClientRect', () => { - it('returns a DOMRect with its size and position, or an empty DOMRect when disconnected', () => { - let lastElement; - - const root = ReactNativeTester.createRoot(); - - ReactNativeTester.runTask(() => { - root.render( - { - lastElement = element; - }} - />, - ); - }); - - const element = ensureReactNativeElement(lastElement); - - const boundingClientRect = element.getBoundingClientRect(); - expect(boundingClientRect).toBeInstanceOf(DOMRect); - expect(boundingClientRect.x).toBe(5); - expect(boundingClientRect.y).toBe(10); - expect(boundingClientRect.width).toBe(50); - expect(boundingClientRect.height).toBe(101); - - ReactNativeTester.runTask(() => { - root.render(); - }); - - const boundingClientRectAfterUnmount = element.getBoundingClientRect(); - expect(boundingClientRectAfterUnmount).toBeInstanceOf(DOMRect); - expect(boundingClientRectAfterUnmount.x).toBe(0); - expect(boundingClientRectAfterUnmount.y).toBe(0); - expect(boundingClientRectAfterUnmount.width).toBe(0); - expect(boundingClientRectAfterUnmount.height).toBe(0); - }); - }); - - describe('scrollLeft / scrollTop', () => { - it('return the scroll position on each axis', () => { - let lastElement; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - lastElement = element; - }} - />, - ); - }); - - const element = ensureReactNativeElement(lastElement); - - expect(element.scrollLeft).toBeCloseTo(5.1); - expect(element.scrollTop).toBeCloseTo(10.2); - - ReactNativeTester.runTask(() => { - root.render(); - }); - - expect(element.scrollLeft).toBe(0); - expect(element.scrollTop).toBe(0); - }); - }); - - describe('scrollWidth / scrollHeight', () => { - it('return the scroll size on each axis', () => { - let lastElement; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - lastElement = element; - }}> - - , - ); - }); - - const element = ensureReactNativeElement(lastElement); - - expect(element.scrollWidth).toBe(200); - expect(element.scrollHeight).toBe(1500); - - ReactNativeTester.runTask(() => { - root.render(); - }); - - expect(element.scrollWidth).toBe(0); - expect(element.scrollHeight).toBe(0); - }); - }); - - describe('clientWidth / clientHeight', () => { - it('return the inner size of the node', () => { - let lastElement; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - lastElement = element; - }} - />, - ); - }); - - const element = ensureReactNativeElement(lastElement); - - expect(element.clientWidth).toBe(200); - expect(element.clientHeight).toBe(250); - - ReactNativeTester.runTask(() => { - root.render(); - }); - - expect(element.clientWidth).toBe(0); - expect(element.clientHeight).toBe(0); - }); - }); - - describe('clientLeft / clientTop', () => { - it('return the border size of the node', () => { - let lastElement; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - lastElement = element; - }} - />, - ); - }); - - const element = ensureReactNativeElement(lastElement); - - expect(element.clientLeft).toBe(200); - expect(element.clientTop).toBe(250); - - ReactNativeTester.runTask(() => { - root.render(); - }); - - expect(element.clientLeft).toBe(0); - expect(element.clientTop).toBe(0); - }); - }); - - describe('id', () => { - it('returns the current `id` prop from the node', () => { - let lastElement; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - lastElement = element; - }} - />, - ); - }); - - const element = ensureReactNativeElement(lastElement); - - expect(element.id).toBe(''); - }); - - it('returns the current `nativeID` prop from the node', () => { - let lastElement; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - lastElement = element; - }} - />, - ); - }); - - const element = ensureReactNativeElement(lastElement); - - expect(element.id).toBe(''); - }); - }); - - describe('tagName', () => { - it('returns the normalized tag name for the node', () => { - let lastElement; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - lastElement = element; - }} - />, - ); - }); - - const element = ensureReactNativeElement(lastElement); - - expect(element.tagName).toBe('RN:View'); - }); - }); - }); - - describe('implements specific `ReactNativeElement` methods', () => { - describe('offsetWidth / offsetHeight', () => { - it('return the rounded width and height, or 0 when disconnected', () => { - let lastElement; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - lastElement = element; - }} - />, - ); - }); - - const element = ensureReactNativeElement(lastElement); - - expect(element.offsetWidth).toBe(50); - expect(element.offsetHeight).toBe(101); - - ReactNativeTester.runTask(() => { - root.render(); - }); - - expect(element.offsetWidth).toBe(0); - expect(element.offsetHeight).toBe(0); - }); - }); - - describe('offsetParent / offsetTop / offsetLeft', () => { - it('retun the rounded offset values and the parent, or null and zeros when disconnected or hidden', () => { - let lastParentElement; - let lastElement; - - const root = ReactNativeTester.createRoot(); - - ReactNativeTester.runTask(() => { - root.render( - { - lastParentElement = element; - }}> - { - lastElement = element; - }} - /> - , - ); - }); - - const parentElement = ensureReactNativeElement(lastParentElement); - const element = ensureReactNativeElement(lastElement); - - expect(element.offsetTop).toBe(11); - expect(element.offsetLeft).toBe(5); - expect(element.offsetParent).toBe(parentElement); - - ReactNativeTester.runTask(() => { - root.render( - - - , - ); - }); - - expect(element.offsetTop).toBe(0); - expect(element.offsetLeft).toBe(0); - expect(element.offsetParent).toBe(null); - - ReactNativeTester.runTask(() => { - root.render(); - }); - - expect(element.offsetTop).toBe(0); - expect(element.offsetLeft).toBe(0); - expect(element.offsetParent).toBe(null); - }); - }); - }); -}); diff --git a/packages/react-native/src/private/webapis/dom/nodes/__tests__/ReadOnlyText-itest.js b/packages/react-native/src/private/webapis/dom/nodes/__tests__/ReadOnlyText-itest.js deleted file mode 100644 index 1d74bfb76c3..00000000000 --- a/packages/react-native/src/private/webapis/dom/nodes/__tests__/ReadOnlyText-itest.js +++ /dev/null @@ -1,412 +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 - * @oncall react_native - */ - -import './setUpFeatureFlags'; -import '../../../../../../Libraries/Core/InitializeCore.js'; - -import {NativeText} from '../../../../../../Libraries/Text/TextNativeComponent'; -import * as ReactNativeTester from '../../../../../../src/private/__tests__/ReactNativeTester'; -import ReactNativeElement from '../ReactNativeElement'; -import ReadOnlyNode from '../ReadOnlyNode'; -import ReadOnlyText from '../ReadOnlyText'; -import invariant from 'invariant'; -import * as React from 'react'; - -function ensureReadOnlyText(value: mixed): ReadOnlyText { - if (!(value instanceof ReadOnlyText)) { - throw new Error( - `Expected instance of ReactOnlyNode but got ${String(value)}`, - ); - } - - return value; -} - -function ensureReadOnlyNode(value: mixed): ReadOnlyNode { - if (!(value instanceof ReadOnlyNode)) { - throw new Error( - `Expected instance of ReactOnlyNode but got ${String(value)}`, - ); - } - - return value; -} - -function ensureReactNativeElement(value: mixed): ReactNativeElement { - if (!(value instanceof ReactNativeElement)) { - throw new Error( - `Expected instance of ReactNativeElement but got ${String(value)}`, - ); - } - - return value; -} - -describe('ReadOnlyText', () => { - it('should be used to create public text instances when the `enableAccessToHostTreeInFabric` feature flag is enabled', () => { - let lastParentNode; - - const root = ReactNativeTester.createRoot(); - - ReactNativeTester.runTask(() => { - root.render( - { - lastParentNode = node; - }}> - Some text - , - ); - }); - - const parentNode = ensureReadOnlyNode(lastParentNode); - const textNode = parentNode.childNodes[0]; - - expect(textNode).toBeInstanceOf(ReadOnlyText); - }); - - describe('extends `ReadOnlyNode`', () => { - describe('nodeName', () => { - it('returns "#text"', () => { - let lastParentNode; - - const root = ReactNativeTester.createRoot(); - - ReactNativeTester.runTask(() => { - root.render( - { - lastParentNode = node; - }}> - Some text - , - ); - }); - - const parentNode = ensureReadOnlyNode(lastParentNode); - const textNode = parentNode.childNodes[0]; - - expect(textNode.nodeName).toBe('#text'); - }); - }); - - describe('nodeType', () => { - it('returns ReadOnlyNode.TEXT_NODE', () => { - let lastParentNode; - - const root = ReactNativeTester.createRoot(); - - ReactNativeTester.runTask(() => { - root.render( - { - lastParentNode = node; - }}> - Some text - , - ); - }); - - const parentNode = ensureReadOnlyNode(lastParentNode); - const textNode = parentNode.childNodes[0]; - - expect(textNode.nodeType).toBe(ReadOnlyNode.TEXT_NODE); - }); - }); - - describe('nodeValue / textContent', () => { - it('returns the string data contained in the node', () => { - let lastParentNode; - - const root = ReactNativeTester.createRoot(); - - ReactNativeTester.runTask(() => { - root.render( - { - lastParentNode = node; - }}> - Some text - , - ); - }); - - const parentNode = ensureReadOnlyNode(lastParentNode); - const textNode = parentNode.childNodes[0]; - - expect(textNode.nodeValue).toBe('Some text'); - expect(textNode.textContent).toBe('Some text'); - }); - }); - - describe('traversal', () => { - it('only preserves text nodes when their contents do not change', () => { - let lastParentElement; - let lastChildElementA; - - const root = ReactNativeTester.createRoot(); - - ReactNativeTester.runTask(() => { - root.render( - { - lastParentElement = element; - }}> - Text A - { - lastChildElementA = element; - }} - /> - Text B - , - ); - }); - - const parentElement: ReactNativeElement = - ensureReactNativeElement(lastParentElement); - const childElementA: ReactNativeElement = - ensureReactNativeElement(lastChildElementA); - - // Get text nodes and refine them as text nodes for Flow - const childTextA = parentElement.childNodes[0]; - invariant( - childTextA instanceof ReadOnlyText, - 'expected instance of ReadOnlyText', - ); - expect(childTextA.textContent).toBe('Text A'); - const childTextB = parentElement.childNodes[2]; - invariant( - childTextB instanceof ReadOnlyText, - 'expected instance of ReadOnlyText', - ); - expect(childTextB.textContent).toBe('Text B'); - - // Validate structure - expect(parentElement.childNodes.length).toBe(3); - expect(parentElement.childNodes[0]).toBe(childTextA); - expect(parentElement.childNodes[1]).toBe(childElementA); - expect(parentElement.childNodes[2]).toBe(childTextB); - - // Change contents of the second text only - ReactNativeTester.runTask(() => { - root.render( - { - lastParentElement = element; - }}> - Text A - { - lastChildElementA = element; - }} - /> - Text B modified - , - ); - }); - - expect(parentElement.childNodes.length).toBe(3); - expect(parentElement.childNodes[0]).toBe(childTextA); - expect(parentElement.childNodes[1]).toBe(childElementA); - expect(parentElement.childNodes[2]).not.toBe(childTextB); - expect(parentElement.childNodes[2]).toBeInstanceOf(ReadOnlyText); - expect(ensureReadOnlyText(parentElement.childNodes[2]).data).toBe( - 'Text B modified', - ); - expect(childTextB.isConnected).toBe(false); - }); - }); - }); - - describe('extends `ReadOnlyCharacterData`', () => { - describe('data / length', () => { - it('returns the string data and its length, respectively', () => { - let lastParentNode; - - const root = ReactNativeTester.createRoot(); - - ReactNativeTester.runTask(() => { - root.render( - { - lastParentNode = node; - }}> - Some text - , - ); - }); - - const parentNode: ReadOnlyNode = ensureReadOnlyNode(lastParentNode); - const textNode = ensureReadOnlyText(parentNode.childNodes[0]); - - expect(textNode.data).toBe('Some text'); - expect(textNode.length).toBe('Some text'.length); - }); - }); - - describe('previousElementSibling / nextElementSibling', () => { - it('return updated relative elements', () => { - let lastParentElement; - let lastChildElementA; - let lastChildElementB; - let lastChildElementC; - - const root = ReactNativeTester.createRoot(); - - ReactNativeTester.runTask(() => { - root.render( - { - lastParentElement = element; - }}> - Text A - { - lastChildElementA = element; - }} - /> - Text B - { - lastChildElementB = element; - }} - /> - Text C - { - lastChildElementC = element; - }} - /> - Text D - , - ); - }); - - const parentElement = ensureReactNativeElement(lastParentElement); - const childElementA = ensureReactNativeElement(lastChildElementA); - const childElementB = ensureReactNativeElement(lastChildElementB); - const childElementC = ensureReactNativeElement(lastChildElementC); - - // Get text nodes and refine them as text nodes for Flow - const childTextA = parentElement.childNodes[0]; - invariant( - childTextA instanceof ReadOnlyText, - 'expected instance of ReadOnlyText', - ); - const childTextB = parentElement.childNodes[2]; - invariant( - childTextB instanceof ReadOnlyText, - 'expected instance of ReadOnlyText', - ); - const childTextC = parentElement.childNodes[4]; - invariant( - childTextC instanceof ReadOnlyText, - 'expected instance of ReadOnlyText', - ); - const childTextD = parentElement.childNodes[6]; - invariant( - childTextD instanceof ReadOnlyText, - 'expected instance of ReadOnlyText', - ); - - // Validate structure - expect(parentElement.childNodes.length).toBe(7); - expect(parentElement.childNodes[0]).toBe(childTextA); - expect(parentElement.childNodes[1]).toBe(childElementA); - expect(parentElement.childNodes[2]).toBe(childTextB); - expect(parentElement.childNodes[3]).toBe(childElementB); - expect(parentElement.childNodes[4]).toBe(childTextC); - expect(parentElement.childNodes[5]).toBe(childElementC); - expect(parentElement.childNodes[6]).toBe(childTextD); - - expect(childTextA.previousElementSibling).toBe(null); - expect(childTextA.nextElementSibling).toBe(childElementA); - - expect(childTextB.previousElementSibling).toBe(childElementA); - expect(childTextB.nextElementSibling).toBe(childElementB); - - expect(childTextC.previousElementSibling).toBe(childElementB); - expect(childTextC.nextElementSibling).toBe(childElementC); - - expect(childTextD.previousElementSibling).toBe(childElementC); - expect(childTextD.nextElementSibling).toBe(null); - }); - }); - - describe('substringData', () => { - it('returns a slice of the text content', () => { - let lastParentElement; - - const root = ReactNativeTester.createRoot(); - - ReactNativeTester.runTask(() => { - root.render( - { - lastParentElement = element; - }}> - Text A - , - ); - }); - - const parentElement = ensureReactNativeElement(lastParentElement); - - // Get text nodes and refine them as text nodes for Flow - const childTextA = parentElement.childNodes[0]; - invariant( - childTextA instanceof ReadOnlyText, - 'expected instance of ReadOnlyText', - ); - - expect(childTextA.substringData(0, 1)).toBe('T'); - expect(childTextA.substringData(0, 5)).toBe('Text '); - // Count > length - expect(childTextA.substringData(0, 10)).toBe('Text A'); - // Count = length - expect(childTextA.substringData(0, 6)).toBe('Text A'); - expect(childTextA.substringData(0, 0)).toBe(''); - // Negative count - expect(childTextA.substringData(0, -1)).toBe('Text A'); - expect(childTextA.substringData(0, -10)).toBe('Text A'); - - expect(childTextA.substringData(1, 3)).toBe('ext'); - expect(childTextA.substringData(5, 1)).toBe('A'); - // Offset + count > length - expect(childTextA.substringData(5, 2)).toBe('A'); - // Offset = length - expect(childTextA.substringData(6, 1)).toBe(''); - // Offset = length & negative count - expect(childTextA.substringData(6, -1)).toBe(''); - // Negative count - expect(childTextA.substringData(5, -1)).toBe('A'); - - // Out of bounds offset - expect(() => { - childTextA.substringData(-1, 0); - }).toThrow(); - expect(() => { - childTextA.substringData(7, 0); - }).toThrow(); - }); - }); - }); -}); diff --git a/packages/react-native/src/private/webapis/dom/nodes/__tests__/setUpFeatureFlags.js b/packages/react-native/src/private/webapis/dom/nodes/__tests__/setUpFeatureFlags.js deleted file mode 100644 index 6b2553b76b6..00000000000 --- a/packages/react-native/src/private/webapis/dom/nodes/__tests__/setUpFeatureFlags.js +++ /dev/null @@ -1,16 +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 - * @oncall react_native - */ - -import * as ReactNativeFeatureFlags from '../../../../featureflags/ReactNativeFeatureFlags'; - -ReactNativeFeatureFlags.override({ - enableAccessToHostTreeInFabric: () => true, -}); diff --git a/packages/react-native/src/private/webapis/intersectionobserver/__tests__/IntersectionObserver-itest.js b/packages/react-native/src/private/webapis/intersectionobserver/__tests__/IntersectionObserver-itest.js deleted file mode 100644 index f034867e8fa..00000000000 --- a/packages/react-native/src/private/webapis/intersectionobserver/__tests__/IntersectionObserver-itest.js +++ /dev/null @@ -1,1568 +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 - * @oncall react_native - */ - -/* eslint-disable lint/sort-imports */ -import type IntersectionObserverType from '../IntersectionObserver'; - -import DOMRectReadOnly from '../../dom/geometry/DOMRectReadOnly'; -import * as ReactNativeTester from '../../../__tests__/ReactNativeTester'; -import setUpIntersectionObserver from '../../../setup/setUpIntersectionObserver'; -import ReactNativeElement from '../../dom/nodes/ReactNativeElement'; -import IntersectionObserverEntry from '../IntersectionObserverEntry'; -import * as React from 'react'; - -import './setUpFeatureFlags'; -import '../../../../../Libraries/Core/InitializeCore.js'; - -import ScrollView from '../../../../../Libraries/Components/ScrollView/ScrollView'; -import View from '../../../../../Libraries/Components/View/View'; - -declare const IntersectionObserver: Class; - -setUpIntersectionObserver(); - -function ensureInstance(value: mixed, Class: Class): T { - if (!(value instanceof Class)) { - // $FlowExpectedError[incompatible-use] - const className = Class.name; - throw new Error( - `Expected instance of ${className} but got ${String(value)}`, - ); - } - - return value; -} - -function ensureReactNativeElement(value: mixed): ReactNativeElement { - return ensureInstance(value, ReactNativeElement); -} - -export function expectRectEquals( - rect: DOMRectReadOnly, - expected: {x: number, y: number, width: number, height: number}, -): boolean { - const {x, y, width, height} = expected; - if ( - !( - rect.x === x && - rect.y === y && - rect.width === width && - rect.height === height - ) - ) { - const received = { - x: rect.x, - y: rect.y, - width: rect.width, - height: rect.height, - }; - throw new Error( - `Expected ${JSON.stringify(expected)} but received ${JSON.stringify(received)}`, - ); - } - return true; -} - -describe('IntersectionObserver', () => { - describe('constructor(callback, {root, rootMargin, threshold, rnRootThreshold})', () => { - it('should throw if `callback` is not provided', () => { - expect(() => { - // $FlowExpectedError[incompatible-call] - return new IntersectionObserver(); - }).toThrow( - "Failed to construct 'IntersectionObserver': 1 argument required, but only 0 present.", - ); - }); - - it('should throw if `callback` is not a function', () => { - expect(() => { - // $FlowExpectedError[incompatible-call] - return new IntersectionObserver('not a function!'); - }).toThrow( - "Failed to construct 'IntersectionObserver': parameter 1 is not of type 'Function'.", - ); - }); - - it('should throw if `root` is provided', () => { - let maybeNode; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }} - />, - ); - }); - - const node = ensureReactNativeElement(maybeNode); - - expect(() => { - // $FlowExpectedError[prop-missing] root is not even defined in Flow. - return new IntersectionObserver(() => {}, {root: node}); - }).toThrow( - "Failed to construct 'IntersectionObserver': root is not supported", - ); - }); - - it('should throw if `rootMargin` is provided', () => { - expect(() => { - // $FlowExpectedError[prop-missing] rootMargin is not even defined in Flow. - return new IntersectionObserver(() => {}, {rootMargin: '10px'}); - }).toThrow( - "Failed to construct 'IntersectionObserver': rootMargin is not supported", - ); - }); - - it('should throw if `threshold` contains a value lower than 0 or greater than 1', () => { - expect(() => { - return new IntersectionObserver(() => {}, {threshold: 1.01}); - }).toThrow( - "Failed to construct 'IntersectionObserver': Threshold values must be numbers between 0 and 1", - ); - - expect(() => { - return new IntersectionObserver(() => {}, {threshold: -0.01}); - }).toThrow( - "Failed to construct 'IntersectionObserver': Threshold values must be numbers between 0 and 1", - ); - }); - - it('should throw if `threshold` contains a value that cannot be casted to a finite number', () => { - expect(() => { - // $FlowExpectedError[incompatible-call] - return new IntersectionObserver(() => {}, {threshold: ['test']}); - }).toThrow( - "Failed to read the 'threshold' property from 'IntersectionObserverInit': The provided double value is non-finite.", - ); - }); - - it('should provide access to `root`, `rootMargin` and `thresholds`', () => { - const observer = new IntersectionObserver(() => {}); - - expect(observer.root).toBe(null); - expect(observer.rootMargin).toBe('0px 0px 0px 0px'); - expect(observer.thresholds).toEqual([0]); - }); - - it('should normalize `threshold` values', () => { - // Sets default value - expect(new IntersectionObserver(() => {}).thresholds).toEqual([0]); - // Sets default value - expect( - new IntersectionObserver(() => {}, {threshold: []}).thresholds, - ).toEqual([0]); - - // Converts to array - expect( - new IntersectionObserver(() => {}, {threshold: 0.5}).thresholds, - ).toEqual([0.5]); - - // Sorts - expect( - new IntersectionObserver(() => {}, {threshold: [0.5, 0, 1]}).thresholds, - ).toEqual([0, 0.5, 1]); - - // Does NOT deduplicate (browsers don't do it - shrug) - expect( - new IntersectionObserver(() => {}, {threshold: [0.5, 0.5, 0.5]}) - .thresholds, - ).toEqual([0.5, 0.5, 0.5]); - - // Casts to number - expect( - // $FlowExpectedError[incompatible-call] - new IntersectionObserver(() => {}, {threshold: [true]}).thresholds, - ).toEqual([1]); - expect( - // $FlowExpectedError[incompatible-call] - new IntersectionObserver(() => {}, {threshold: [false]}).thresholds, - ).toEqual([0]); - expect( - // $FlowExpectedError[incompatible-call] - new IntersectionObserver(() => {}, {threshold: ['']}).thresholds, - ).toEqual([0]); - }); - - it('should not throw if rnRootThreshold is null or undefined', () => { - expect(() => { - // $FlowExpectedError[incompatible-call] - return new IntersectionObserver(() => {}, {rnRootThreshold: null}); - }).not.toThrow(); - - expect(() => { - return new IntersectionObserver(() => {}, { - rnRootThreshold: undefined, - }); - }).not.toThrow(); - }); - - it('should throw if rnRootThreshold is set to an invalid value', () => { - expect(() => { - return new IntersectionObserver(() => {}, { - // $FlowExpectedError[incompatible-call] - rnRootThreshold: 2, - }); - }).toThrow( - "Failed to construct 'IntersectionObserver': Threshold values must be numbers between 0 and 1", - ); - - expect(() => { - return new IntersectionObserver(() => {}, { - // $FlowExpectedError[incompatible-call] - rnRootThreshold: 'invalid', - }); - }).toThrow( - "Failed to read the 'rnRootThreshold' property from 'IntersectionObserverInit': The provided double value is non-finite.", - ); - }); - - it('should default to null if rnRootThreshold is not set or invalid', () => { - expect( - new IntersectionObserver(() => {}, {}).rnRootThresholds, - ).toBeNull(); - - expect( - new IntersectionObserver(() => {}, {threshold: 1}).rnRootThresholds, - ).toBeNull(); - expect( - // $FlowExpectedError[incompatible-call] - new IntersectionObserver(() => {}, {rnRootThreshold: null}) - .rnRootThresholds, - ).toBeNull(); - - expect( - new IntersectionObserver(() => {}, {rnRootThreshold: undefined}) - .rnRootThresholds, - ).toBeNull(); - - expect( - new IntersectionObserver(() => {}, {rnRootThreshold: []}) - .rnRootThresholds, - ).toBeNull(); - - expect( - // $FlowExpectedError[incompatible-call] - new IntersectionObserver(() => {}, {rnRootThreshold: [null]}) - .rnRootThresholds, - ).toBeNull(); - }); - - it('should normalize `rnRootThreshold` values', () => { - // Sets default value - expect(new IntersectionObserver(() => {}).rnRootThresholds).toBeNull(); - // Sets default value - expect( - new IntersectionObserver(() => {}, {rnRootThreshold: []}) - .rnRootThresholds, - ).toBeNull(); - - // Converts to array - expect( - new IntersectionObserver(() => {}, {rnRootThreshold: 0.5}) - .rnRootThresholds, - ).toEqual([0.5]); - - // Sorts - expect( - new IntersectionObserver(() => {}, {rnRootThreshold: [0.5, 0, 1]}) - .rnRootThresholds, - ).toEqual([0, 0.5, 1]); - - // Does NOT deduplicate (browsers don't do it - shrug) - expect( - new IntersectionObserver(() => {}, {rnRootThreshold: [0.5, 0.5, 0.5]}) - .rnRootThresholds, - ).toEqual([0.5, 0.5, 0.5]); - - // Casts to number - expect( - // $FlowExpectedError[incompatible-call] - new IntersectionObserver(() => {}, {rnRootThreshold: [true]}) - .rnRootThresholds, - ).toEqual([1]); - expect( - // $FlowExpectedError[incompatible-call] - new IntersectionObserver(() => {}, {rnRootThreshold: [false]}) - .rnRootThresholds, - ).toEqual([0]); - expect( - // $FlowExpectedError[incompatible-call] - new IntersectionObserver(() => {}, {rnRootThreshold: ['']}) - .rnRootThresholds, - ).toEqual([0]); - }); - - it('should default thresholds to empty if rnRootThreshold is validly', () => { - expect( - new IntersectionObserver(() => {}, {rnRootThreshold: 0.5}).thresholds, - ).toEqual([]); - - expect( - new IntersectionObserver(() => {}, {rnRootThreshold: [1]}).thresholds, - ).toEqual([]); - }); - - it('should default thresholds to [0] if rnRootThreshold is invalidly set', () => { - expect( - // $FlowExpectedError[incompatible-call] - new IntersectionObserver(() => {}, {rnRootThreshold: null}).thresholds, - ).toEqual([0]); - - expect( - new IntersectionObserver(() => {}, {rnRootThreshold: []}).thresholds, - ).toEqual([0]); - }); - }); - - describe('observe(target)', () => { - it('should throw if `target` is not a `ReactNativeElement`', () => { - const observer = new IntersectionObserver(() => {}); - expect(() => { - // $FlowExpectedError[incompatible-call] - observer.observe('something'); - }).toThrow( - "Failed to execute 'observe' on 'IntersectionObserver': parameter 1 is not of type 'ReactNativeElement'.", - ); - }); - - it('should start observing the target when called for the first time (using normalized thresholds)', () => { - let maybeNode; - let observer: IntersectionObserver; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }} - />, - ); - }); - - const node = ensureReactNativeElement(maybeNode); - - const intersectionObserverCallback = jest.fn(); - - ReactNativeTester.runTask(() => { - observer = new IntersectionObserver(intersectionObserverCallback, { - threshold: [1, 0.5, 0], - }); - observer.observe(node); - }); - - expect(observer?.thresholds).toEqual([0, 0.5, 1]); - expect(intersectionObserverCallback).toHaveBeenCalledTimes(1); - const [entries, reportedObserver] = - intersectionObserverCallback.mock.lastCall; - expect(entries.length).toBe(1); - expect(entries[0].isIntersecting).toBe(true); - - expectRectEquals(entries[0].intersectionRect, { - x: 0, - y: 0, - width: 100, - height: 100, - }); - expect(entries[0].target).toBe(node); - expect(entries[0].intersectionRatio).toBe(1); - expectRectEquals(entries[0].boundingClientRect, { - x: 0, - y: 0, - width: 100, - height: 100, - }); - expectRectEquals(entries[0].rootBounds, { - x: 0, - y: 0, - width: 1000, - height: 1000, - }); - - expect(reportedObserver).toBe(observer); - }); - - it('should ignore subsequent calls to observe a target already being observed', () => { - let maybeNode; - let observer: IntersectionObserver; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }} - />, - ); - }); - const node = ensureReactNativeElement(maybeNode); - - const intersectionObserverCallback = jest.fn(); - - // Let observer to run - ReactNativeTester.runTask(() => { - observer = new IntersectionObserver(intersectionObserverCallback, { - threshold: [0, 0.5, 1], - }); - - observer.observe(node); - }); - - expect(intersectionObserverCallback).toHaveBeenCalledTimes(1); - const [entries, reportedObserver] = - intersectionObserverCallback.mock.lastCall; - - expect(entries.length).toBe(1); - expect(entries[0]).toBeInstanceOf(IntersectionObserverEntry); - expect(entries[0].target).toBe(node); - expect(reportedObserver).toBe(observer); - - // Observe the same node and let observer run again - ReactNativeTester.runTask(() => { - observer.observe(node); - }); - - // Expect no additional calls - expect(intersectionObserverCallback).toHaveBeenCalledTimes(1); - }); - - it('should ignore disconnected targets', () => { - let maybeNode; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }} - />, - ); - }); - - const node = ensureReactNativeElement(maybeNode); - - ReactNativeTester.runTask(() => { - root.render(<>); - }); - - expect(node.isConnected).toBe(false); - - const intersectionObserverCallback = jest.fn(); - - ReactNativeTester.runTask(() => { - const observer = new IntersectionObserver(intersectionObserverCallback); - observer.observe(node); - }); - - expect(intersectionObserverCallback).not.toHaveBeenCalled(); - }); - - it('should report completely non-intersecting initial state correctly', () => { - let maybeNode; - let observer: IntersectionObserver; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - - { - maybeNode = receivedNode; - }} - /> - , - , - ); - }); - const node = ensureReactNativeElement(maybeNode); - - const intersectionObserverCallback = jest.fn(); - - ReactNativeTester.runTask(() => { - observer = new IntersectionObserver(intersectionObserverCallback, { - threshold: [0], - rnRootThreshold: [0.5], - }); - - observer.observe(node); - }); - - expect(intersectionObserverCallback).toHaveBeenCalledTimes(1); - const [entries, reportedObserver] = - intersectionObserverCallback.mock.lastCall; - - expect(reportedObserver).toBe(observer); - expect(entries.length).toBe(1); - expect(entries[0]).toBeInstanceOf(IntersectionObserverEntry); - expect(entries[0].intersectionRatio).toBe(0); - expect(entries[0].rnRootIntersectionRatio).toBe(0); - expect(entries[0].isIntersecting).toBe(false); - expect(entries[0].target).toBe(node); - expectRectEquals(entries[0].intersectionRect, { - x: 0, - y: 0, - width: 0, - height: 0, - }); - expectRectEquals(entries[0].boundingClientRect, { - x: 0, - y: -200, - width: 50, - height: 50, - }); - expectRectEquals(entries[0].rootBounds, { - x: 0, - y: 0, - width: 1000, - height: 1000, - }); - }); - - it('should report partial non-intersecting initial state correctly', () => { - let maybeNode; - let observer: IntersectionObserver; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - - { - maybeNode = receivedNode; - }} - /> - , - , - ); - }); - const node = ensureReactNativeElement(maybeNode); - - const intersectionObserverCallback = jest.fn(); - - ReactNativeTester.runTask(() => { - observer = new IntersectionObserver(intersectionObserverCallback, { - threshold: [1], - }); - - observer.observe(node); - }); - - expect(intersectionObserverCallback).toHaveBeenCalledTimes(1); - const [entries, reportedObserver] = - intersectionObserverCallback.mock.lastCall; - - expect(reportedObserver).toBe(observer); - expect(entries.length).toBe(1); - expect(entries[0]).toBeInstanceOf(IntersectionObserverEntry); - expect(entries[0].intersectionRatio).toBe(0.5); - expect(entries[0].rnRootIntersectionRatio).toBe(0.00125); - expect(entries[0].isIntersecting).toBe(false); - expect(entries[0].target).toBe(node); - expectRectEquals(entries[0].intersectionRect, { - x: 0, - y: 0, - width: 50, - height: 25, - }); - expectRectEquals(entries[0].boundingClientRect, { - x: 0, - y: -25, - width: 50, - height: 50, - }); - expectRectEquals(entries[0].rootBounds, { - x: 0, - y: 0, - width: 1000, - height: 1000, - }); - }); - - it('should report partial intersecting initial state correctly', () => { - let maybeNode; - let observer: IntersectionObserver; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - - { - maybeNode = receivedNode; - }} - /> - , - , - ); - }); - const node = ensureReactNativeElement(maybeNode); - - const intersectionObserverCallback = jest.fn(); - - ReactNativeTester.runTask(() => { - observer = new IntersectionObserver(intersectionObserverCallback, { - threshold: [], - }); - - observer.observe(node); - }); - - expect(intersectionObserverCallback).toHaveBeenCalledTimes(1); - const [entries, reportedObserver] = - intersectionObserverCallback.mock.lastCall; - - expect(reportedObserver).toBe(observer); - expect(entries.length).toBe(1); - expect(entries[0]).toBeInstanceOf(IntersectionObserverEntry); - expect(entries[0].intersectionRatio).toBe(0.5); - expect(entries[0].rnRootIntersectionRatio).toBe(0.00125); - expect(entries[0].isIntersecting).toBe(true); - expect(entries[0].target).toBe(node); - expectRectEquals(entries[0].intersectionRect, { - x: 0, - y: 0, - width: 50, - height: 25, - }); - expectRectEquals(entries[0].boundingClientRect, { - x: 0, - y: -25, - width: 50, - height: 50, - }); - expectRectEquals(entries[0].rootBounds, { - x: 0, - y: 0, - width: 1000, - height: 1000, - }); - }); - - it('should report subsequent updates correctly', () => { - let maybeNode; - let observer: IntersectionObserver; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }} - />, - ); - }); - - const node = ensureReactNativeElement(maybeNode); - - expect(node.isConnected).toBe(true); - - const intersectionObserverCallback = jest.fn(); - - ReactNativeTester.runTask(() => { - observer = new IntersectionObserver(intersectionObserverCallback); - observer.observe(node); - }); - - expect(intersectionObserverCallback).toHaveBeenCalledTimes(1); - const [entries, reportedObserver] = - intersectionObserverCallback.mock.lastCall; - expect(entries.length).toBe(1); - expect(entries[0]).toBeInstanceOf(IntersectionObserverEntry); - expect(entries[0].intersectionRatio).toBe(1); - expect(entries[0].rnRootIntersectionRatio).toBe(0.01); - expect(entries[0].isIntersecting).toBe(true); - - expectRectEquals(entries[0].intersectionRect, { - x: 0, - y: 0, - width: 100, - height: 100, - }); - expectRectEquals(entries[0].boundingClientRect, { - x: 0, - y: 0, - width: 100, - height: 100, - }); - expectRectEquals(entries[0].rootBounds, { - x: 0, - y: 0, - width: 1000, - height: 1000, - }); - - expect(reportedObserver).toBe(observer); - - // Move the view out of the viewport - // Using position absolute as there isn't support ScrollView scrolls - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }} - />, - ); - }); - - expect(node.isConnected).toBe(true); - expect(intersectionObserverCallback).toHaveBeenCalledTimes(2); - const [entries2, reportedObserver2] = - intersectionObserverCallback.mock.lastCall; - expect(entries2.length).toBe(1); - expect(entries2[0].isIntersecting).toBe(false); - expect(entries2[0].target).toBe(node); - - expectRectEquals(entries2[0].intersectionRect, { - x: 0, - y: 0, - width: 0, - height: 0, - }); - expectRectEquals(entries2[0].boundingClientRect, { - x: 0, - y: 2000, - width: 100, - height: 100, - }); - expectRectEquals(entries2[0].rootBounds, { - x: 0, - y: 0, - width: 1000, - height: 1000, - }); - expect(reportedObserver2).toBe(observer); - }); - - it('should report updates to the right observers', () => { - let maybeNode1; - let maybeNode2; - let observer1: IntersectionObserver; - let observer2: IntersectionObserver; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - - { - maybeNode1 = receivedNode; - }} - /> - { - maybeNode2 = receivedNode; - }} - /> - , - ); - }); - const node1 = ensureReactNativeElement(maybeNode1); - const node2 = ensureReactNativeElement(maybeNode2); - - const intersectionObserverCallback1 = jest.fn(); - const intersectionObserverCallback2 = jest.fn(); - - ReactNativeTester.runTask(() => { - observer1 = new IntersectionObserver(intersectionObserverCallback1, { - threshold: [0], - }); - - observer1.observe(node1); - observer1.observe(node2); - - observer2 = new IntersectionObserver(intersectionObserverCallback2, { - threshold: [1], - }); - observer2.observe(node2); - }); - - // Verify observer1 is reporting right thing - expect(intersectionObserverCallback1).toHaveBeenCalledTimes(1); - const [entries1, reportedObserver1] = - intersectionObserverCallback1.mock.lastCall; - - expect(reportedObserver1).toBe(observer1); - expect(entries1.length).toBe(2); - - expect(entries1[0].isIntersecting).toBe(false); - expect(entries1[0].intersectionRatio).toBe(0); - expect(entries1[0].target).toBe(node1); - - expectRectEquals(entries1[0].intersectionRect, { - x: 0, - y: 0, - width: 0, - height: 0, - }); - expectRectEquals(entries1[0].boundingClientRect, { - x: 0, - y: -100, - width: 50, - height: 50, - }); - expectRectEquals(entries1[0].rootBounds, { - x: 0, - y: 0, - width: 1000, - height: 1000, - }); - - expect(entries1[1].isIntersecting).toBe(true); - expect(entries1[1].intersectionRatio).toBe(0.75); - expect(entries1[1].target).toBe(node2); - - // Verify observer2 is reporting no intersection because the threshold is 1 - expect(intersectionObserverCallback2).toHaveBeenCalledTimes(1); - const [entries2, reportedObserver2] = - intersectionObserverCallback2.mock.lastCall; - - expect(reportedObserver2).toBe(observer2); - expect(entries2.length).toBe(1); - - expect(entries2[0].isIntersecting).toBe(false); - expect(entries2[0].intersectionRatio).toBe(0.75); - expect(entries2[0].target).toBe(node2); - - expectRectEquals(entries2[0].intersectionRect, { - x: 0, - y: 0, - width: 200, - height: 150, - }); - expectRectEquals(entries2[0].boundingClientRect, { - x: 0, - y: -50, - width: 200, - height: 200, - }); - expectRectEquals(entries2[0].rootBounds, { - x: 0, - y: 0, - width: 1000, - height: 1000, - }); - }); - describe('rootThreshold', () => { - it('should report partial intersecting initial state correctly', () => { - let maybeNode; - let observer: IntersectionObserver; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }} - />, - ); - }); - - const node = ensureReactNativeElement(maybeNode); - - const intersectionObserverCallback = jest.fn(); - - ReactNativeTester.runTask(() => { - observer = new IntersectionObserver(intersectionObserverCallback, { - rnRootThreshold: [1, 0.5, 0], - }); - observer.observe(node); - }); - - expect(observer?.rnRootThresholds).toEqual([0, 0.5, 1]); - expect(intersectionObserverCallback).toHaveBeenCalledTimes(1); - - const [entries, reportedObserver] = - intersectionObserverCallback.mock.lastCall; - expect(entries.length).toBe(1); - expect(entries[0].isIntersecting).toBe(true); - expect(entries[0].target).toBe(node); - expect(entries[0].intersectionRatio).toBe(1); - expect(entries[0].rnRootIntersectionRatio).toBe(0.01); - - expectRectEquals(entries[0].intersectionRect, { - x: 0, - y: 0, - width: 100, - height: 100, - }); - expectRectEquals(entries[0].boundingClientRect, { - x: 0, - y: 0, - width: 100, - height: 100, - }); - expectRectEquals(entries[0].rootBounds, { - x: 0, - y: 0, - width: 1000, - height: 1000, - }); - - expect(reportedObserver).toBe(observer); - }); - - it('should report partial non-intersecting initial state correctly', () => { - let maybeNode; - let observer: IntersectionObserver; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - - { - maybeNode = receivedNode; - }} - /> - , - , - ); - }); - const node = ensureReactNativeElement(maybeNode); - - const intersectionObserverCallback = jest.fn(); - - ReactNativeTester.runTask(() => { - observer = new IntersectionObserver(intersectionObserverCallback, { - rnRootThreshold: [0.5], - }); - - observer.observe(node); - }); - - expect(intersectionObserverCallback).toHaveBeenCalledTimes(1); - const [entries, reportedObserver] = - intersectionObserverCallback.mock.lastCall; - - expect(reportedObserver).toBe(observer); - expect(entries.length).toBe(1); - expect(entries[0]).toBeInstanceOf(IntersectionObserverEntry); - expect(entries[0].intersectionRatio).toBe(0.5); - expect(entries[0].rnRootIntersectionRatio).toBe(0.00125); - expect(entries[0].isIntersecting).toBe(false); - expect(entries[0].target).toBe(node); - expectRectEquals(entries[0].intersectionRect, { - x: 0, - y: 0, - width: 50, - height: 25, - }); - expectRectEquals(entries[0].boundingClientRect, { - x: 0, - y: -25, - width: 50, - height: 50, - }); - expectRectEquals(entries[0].rootBounds, { - x: 0, - y: 0, - width: 1000, - height: 1000, - }); - }); - - it('should report completely non-intersecting initial state correctly', () => { - let maybeNode; - let observer: IntersectionObserver; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - - { - maybeNode = receivedNode; - }} - /> - , - , - ); - }); - const node = ensureReactNativeElement(maybeNode); - - const intersectionObserverCallback = jest.fn(); - - ReactNativeTester.runTask(() => { - observer = new IntersectionObserver(intersectionObserverCallback, { - rnRootThreshold: [0.5], - }); - - observer.observe(node); - }); - - expect(intersectionObserverCallback).toHaveBeenCalledTimes(1); - const [entries, reportedObserver] = - intersectionObserverCallback.mock.lastCall; - - expect(reportedObserver).toBe(observer); - expect(entries.length).toBe(1); - expect(entries[0]).toBeInstanceOf(IntersectionObserverEntry); - expect(entries[0].intersectionRatio).toBe(0); - expect(entries[0].rnRootIntersectionRatio).toBe(0); - expect(entries[0].isIntersecting).toBe(false); - expect(entries[0].target).toBe(node); - expectRectEquals(entries[0].intersectionRect, { - x: 0, - y: 0, - width: 0, - height: 0, - }); - expectRectEquals(entries[0].boundingClientRect, { - x: 0, - y: -200, - width: 50, - height: 50, - }); - expectRectEquals(entries[0].rootBounds, { - x: 0, - y: 0, - width: 1000, - height: 1000, - }); - }); - - it('should report subsequent updates correctly', () => { - let maybeNode; - let observer: IntersectionObserver; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }} - />, - ); - }); - - const node = ensureReactNativeElement(maybeNode); - - expect(node.isConnected).toBe(true); - - const intersectionObserverCallback = jest.fn(); - - ReactNativeTester.runTask(() => { - observer = new IntersectionObserver(intersectionObserverCallback, { - rnRootThreshold: [1], - }); - observer.observe(node); - }); - - expect(intersectionObserverCallback).toHaveBeenCalledTimes(1); - const [entries, reportedObserver] = - intersectionObserverCallback.mock.lastCall; - expect(entries.length).toBe(1); - expect(entries[0]).toBeInstanceOf(IntersectionObserverEntry); - expect(entries[0].intersectionRatio).toBe(0); - expect(entries[0].rnRootIntersectionRatio).toBe(0); - expect(entries[0].isIntersecting).toBe(false); - expectRectEquals(entries[0].intersectionRect, { - x: 0, - y: 0, - width: 0, - height: 0, - }); - expectRectEquals(entries[0].boundingClientRect, { - x: 0, - y: 2000, - width: 1000, - height: 1000, - }); - expectRectEquals(entries[0].rootBounds, { - x: 0, - y: 0, - width: 1000, - height: 1000, - }); - - expect(reportedObserver).toBe(observer); - - // Move the view out of the viewport - // Using position absolute as we don't support changes in scroll position yet - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }} - />, - ); - }); - - expect(node.isConnected).toBe(true); - expect(intersectionObserverCallback).toHaveBeenCalledTimes(2); - const [entries2, reportedObserver2] = - intersectionObserverCallback.mock.lastCall; - expect(entries2.length).toBe(1); - expect(entries2[0].isIntersecting).toBe(true); - expect(entries2[0].intersectionRatio).toBe(1); - expect(entries2[0].rnRootIntersectionRatio).toBe(1); - expect(entries2[0].target).toBe(node); - - expectRectEquals(entries2[0].intersectionRect, { - x: 0, - y: 0, - width: 1000, - height: 1000, - }); - expectRectEquals(entries2[0].boundingClientRect, { - x: 0, - y: 0, - width: 1000, - height: 1000, - }); - expectRectEquals(entries2[0].rootBounds, { - x: 0, - y: 0, - width: 1000, - height: 1000, - }); - - expect(reportedObserver2).toBe(observer); - }); - }); - }); - - describe('unobserve(target)', () => { - it('should throw if `target` is not a `ReactNativeElement`', () => { - const observer = new IntersectionObserver(() => {}); - expect(() => { - // $FlowExpectedError[incompatible-call] - observer.unobserve('something'); - }).toThrow( - "Failed to execute 'unobserve' on 'IntersectionObserver': parameter 1 is not of type 'ReactNativeElement'.", - ); - }); - - it('should ignore the call if `target` was not observed (not fail)', () => { - let maybeNode; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }} - />, - ); - }); - - const node = ensureReactNativeElement(maybeNode); - const callback = jest.fn(); - - ReactNativeTester.runTask(() => { - const observer = new IntersectionObserver(callback); - observer.unobserve(node); - }); - - expect(callback).not.toHaveBeenCalled(); - }); - - it('should stop observing the target if it was observed', () => { - let maybeNode; - let observer: IntersectionObserver; - - const root = ReactNativeTester.createRoot(); - // View is not intersecting with ScrollView - ReactNativeTester.runTask(() => { - root.render( - - { - maybeNode = receivedNode; - }} - /> - , - ); - }); - - const node = ensureReactNativeElement(maybeNode); - - const callback = jest.fn(); - - ReactNativeTester.runTask(() => { - observer = new IntersectionObserver(callback, {threshold: 1}); - observer.observe(node); - }); - - // Should get initial non-intersecting entry on observation - expect(callback).toHaveBeenCalledTimes(1); - const [entries] = callback.mock.lastCall; - expect(entries.length).toBe(1); - expect(entries[0].isIntersecting).toBe(false); - - ReactNativeTester.runTask(() => { - observer.unobserve(node); - }); - - // Update View such that it is intersecting - ReactNativeTester.runTask(() => { - root.render( - - { - maybeNode = receivedNode; - }} - /> - , - ); - }); - - // Expect no additional callback calls - expect(callback).toHaveBeenCalledTimes(1); - }); - - it('should stop observing the target if it was observed (detached target after observing)', () => { - let maybeNode; - let observer: IntersectionObserver; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }} - />, - ); - }); - - const node = ensureReactNativeElement(maybeNode); - - const callback = jest.fn(); - - ReactNativeTester.runTask(() => { - observer = new IntersectionObserver(callback); - observer.observe(node); - }); - - expect(callback).toHaveBeenCalledTimes(1); - - ReactNativeTester.runTask(() => { - root.render(<>); - }); - - expect(node.isConnected).toBe(false); - - ReactNativeTester.runTask(() => { - observer.unobserve(node); - }); - - // expect no change in callback - expect(callback).toHaveBeenCalledTimes(1); - }); - - it('should stop observing the target if it was observed (detached target before observing)', () => { - let maybeNode; - let observer: IntersectionObserver; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }} - />, - ); - }); - - const node = ensureReactNativeElement(maybeNode); - - ReactNativeTester.runTask(() => { - root.render(<>); - }); - expect(node.isConnected).toBe(false); - - ReactNativeTester.runTask(() => { - observer = new IntersectionObserver(() => {}); - observer.observe(node); - // TODO what happens if this throws an exception? - observer.unobserve(node); - throw new Error('unobserve should not throw'); - }); - }); - - it('should not report the initial state if the target is unobserved before it is delivered', () => { - let maybeNode; - let observer: IntersectionObserver; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }} - />, - ); - }); - - const node = ensureReactNativeElement(maybeNode); - - const intersectionObserverCallback = jest.fn(); - - ReactNativeTester.runTask(() => { - observer = new IntersectionObserver(intersectionObserverCallback); - observer.observe(node); - observer.unobserve(node); - }); - - expect(intersectionObserverCallback).not.toHaveBeenCalled(); - }); - - it('should not report updates if the target is unobserved before they are delivered', () => { - let maybeNode; - let observer: IntersectionObserver; - - const root = ReactNativeTester.createRoot(); - // View is not intersecting with ScrollView - ReactNativeTester.runTask(() => { - root.render( - - { - maybeNode = receivedNode; - }} - /> - , - ); - }); - - const node = ensureReactNativeElement(maybeNode); - - const callback = jest.fn(); - - ReactNativeTester.runTask(() => { - observer = new IntersectionObserver(callback, {threshold: 1}); - observer.observe(node); - }); - - // Should get initial non-intersecting entry on observation - expect(callback).toHaveBeenCalledTimes(1); - const [entries] = callback.mock.lastCall; - expect(entries.length).toBe(1); - expect(entries[0].isIntersecting).toBe(false); - - ReactNativeTester.runTask(() => { - observer.unobserve(node); - }); - - // Update View such that it is intersecting - ReactNativeTester.runTask(() => { - root.render( - - { - maybeNode = receivedNode; - }} - /> - , - ); - }); - - // The callback should not be called again because the target was - // unobserved before the callback was called with the update. - expect(callback).toHaveBeenCalledTimes(1); - }); - - it('should not report updates if the target is unobserved before they are delivered (with other active targets)', () => { - let maybeNode1; - let maybeNode2; - let observer: IntersectionObserver; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - <> - { - maybeNode1 = receivedNode; - }} - /> - { - maybeNode2 = receivedNode; - }} - /> - , - ); - }); - const node1 = ensureReactNativeElement(maybeNode1); - const node2 = ensureReactNativeElement(maybeNode2); - - const intersectionObserverCallback = jest.fn(); - - ReactNativeTester.runTask(() => { - observer = new IntersectionObserver(intersectionObserverCallback, { - threshold: 0, - }); - - observer.observe(node1); - observer.observe(node2); - }); - - expect(intersectionObserverCallback).toHaveBeenCalledTimes(1); - - ReactNativeTester.runTask(() => { - observer.unobserve(node1); - }); - - ReactNativeTester.runTask(() => { - root.render( - <> - { - maybeNode1 = receivedNode; - }} - /> - { - maybeNode2 = receivedNode; - }} - /> - , - ); - }); - - // There should be no additional callback becuase node1 was unobserved - // and node2 has no change in intersecting state (threshold 0) - expect(intersectionObserverCallback).toHaveBeenCalledTimes(1); - }); - - // This is a regression test for a bug where the registration data for the - // target was incorrectly cleaned up when a single observer stopped - // observing it. - it('should work with multiple intersection observer instances', () => { - let maybeNode; - let observer1: IntersectionObserver; - let observer2: IntersectionObserver; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }} - />, - ); - }); - - const node = ensureReactNativeElement(maybeNode); - - ReactNativeTester.runTask(() => { - observer1 = new IntersectionObserver(() => {}); - observer2 = new IntersectionObserver(() => {}); - - observer1.observe(node); - observer2.observe(node); - - observer1.unobserve(node); - - // The second call shouldn't log errors (that would make the test fail). - observer2.unobserve(node); - throw new Error('unobserve should not throw'); - }); - }); - }); - - describe('disconnect', () => { - it('should do nothing if no targets are observed (not fail)', () => { - const callback = jest.fn(); - - ReactNativeTester.runTask(() => { - const observer = new IntersectionObserver(callback); - observer.disconnect(); - }); - - expect(callback).not.toHaveBeenCalled(); - }); - - it('should stop observing all observed targets', () => { - let maybeNode1; - let maybeNode2; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - <> - { - maybeNode1 = receivedNode; - }} - /> - { - maybeNode2 = receivedNode; - }} - /> - , - ); - }); - const node1 = ensureReactNativeElement(maybeNode1); - const node2 = ensureReactNativeElement(maybeNode2); - - const callback = jest.fn(); - - ReactNativeTester.runTask(() => { - const observer = new IntersectionObserver(callback); - - observer.observe(node1); - observer.observe(node2); - - observer.disconnect(); - }); - - expect(callback).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/packages/react-native/src/private/webapis/intersectionobserver/__tests__/setUpFeatureFlags.js b/packages/react-native/src/private/webapis/intersectionobserver/__tests__/setUpFeatureFlags.js deleted file mode 100644 index 5af0f6f71dd..00000000000 --- a/packages/react-native/src/private/webapis/intersectionobserver/__tests__/setUpFeatureFlags.js +++ /dev/null @@ -1,16 +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 - * @oncall react_native - */ - -import * as ReactNativeFeatureFlags from '../../../featureflags/ReactNativeFeatureFlags'; - -ReactNativeFeatureFlags.override({ - enableAccessToHostTreeInFabric: () => true, -}); diff --git a/packages/react-native/src/private/webapis/mutationobserver/__tests__/MutationObserver-itest.js b/packages/react-native/src/private/webapis/mutationobserver/__tests__/MutationObserver-itest.js deleted file mode 100644 index d14ec74a2dc..00000000000 --- a/packages/react-native/src/private/webapis/mutationobserver/__tests__/MutationObserver-itest.js +++ /dev/null @@ -1,1074 +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 - * @oncall react_native - */ - -import type MutationObserverType from '../MutationObserver'; - -import * as ReactNativeTester from '../../../__tests__/ReactNativeTester'; -import View from '../../../../../Libraries/Components/View/View'; -import setUpMutationObserver from '../../../setup/setUpMutationObserver'; -import ReactNativeElement from '../../dom/nodes/ReactNativeElement'; -import nullthrows from 'nullthrows'; -import * as React from 'react'; - -import './setUpFeatureFlags'; -import '../../../../../Libraries/Core/InitializeCore.js'; - -declare const MutationObserver: Class; - -setUpMutationObserver(); - -function ensureInstance(value: mixed, Class: Class): T { - if (!(value instanceof Class)) { - // $FlowExpectedError[incompatible-use] - const className = Class.name; - throw new Error( - `Expected instance of ${className} but got ${String(value)}`, - ); - } - - return value; -} - -function ensureReactNativeElement(value: mixed): ReactNativeElement { - return ensureInstance(value, ReactNativeElement); -} - -function ensureMutationRecordArray( - value: mixed, -): $ReadOnlyArray { - return ensureInstance(value, Array).map((element: mixed) => - ensureInstance(element, MutationRecord), - ); -} - -describe('MutationObserver', () => { - describe('constructor(callback)', () => { - it('should throw if `callback` is not provided', () => { - expect(() => { - // $FlowExpectedError[incompatible-call] - return new MutationObserver(); - }).toThrow( - "Failed to construct 'MutationObserver': 1 argument required, but only 0 present.", - ); - }); - - it('should throw if `callback` is not a function', () => { - expect(() => { - // $FlowExpectedError[incompatible-call] - return new MutationObserver('not a function!'); - }).toThrow( - "Failed to construct 'MutationObserver': parameter 1 is not of type 'Function'.", - ); - }); - }); - - describe('observe(target, {childList: boolean, subtree: boolean})', () => { - it('should throw if `target` is not a `ReactNativeElement`', () => { - const observer = new MutationObserver(() => {}); - expect(() => { - // $FlowExpectedError[incompatible-call] - observer.observe('something'); - }).toThrow( - "Failed to execute 'observe' on 'MutationObserver': parameter 1 is not of type 'ReactNativeElement'.", - ); - }); - - it('should throw if the `childList` option is not provided', () => { - let maybeNode; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }} - />, - ); - }); - - const node = ensureReactNativeElement(maybeNode); - - expect(() => { - const observer = new MutationObserver(() => {}); - observer.observe(node); - }).toThrow( - "Failed to execute 'observe' on 'MutationObserver': The options object must set 'childList' to true.", - ); - - expect(() => { - const observer = new MutationObserver(() => {}); - // $FlowExpectedError[incompatible-call] - observer.observe(node, {childList: false}); - }).toThrow( - "Failed to execute 'observe' on 'MutationObserver': The options object must set 'childList' to true.", - ); - - expect(() => { - const observer = new MutationObserver(() => {}); - observer.observe(node, {childList: true}); - }).not.toThrow(); - - expect(() => { - const observer = new MutationObserver(() => {}); - // $FlowExpectedError[incompatible-call] - observer.observe(node, {childList: 1}); - }).not.toThrow(); - }); - - it('should throw if the `attributes` option is provided', () => { - let maybeNode; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }} - />, - ); - }); - - const node = ensureReactNativeElement(maybeNode); - - expect(() => { - const observer = new MutationObserver(() => {}); - observer.observe(node, {childList: true, attributes: true}); - }).toThrow( - "Failed to execute 'observe' on 'MutationObserver': attributes is not supported", - ); - }); - - it('should throw if the `attributeFilter` option is provided', () => { - let maybeNode; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }} - />, - ); - }); - - const node = ensureReactNativeElement(maybeNode); - - expect(() => { - const observer = new MutationObserver(() => {}); - observer.observe(node, {childList: true, attributeFilter: []}); - }).toThrow( - "Failed to execute 'observe' on 'MutationObserver': attributeFilter is not supported", - ); - }); - - it('should throw if the `attributeOldValue` option is provided', () => { - let maybeNode; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }} - />, - ); - }); - - const node = ensureReactNativeElement(maybeNode); - - expect(() => { - const observer = new MutationObserver(() => {}); - // $FlowExpected - observer.observe(node, {childList: true, attributeOldValue: true}); - }).toThrow( - "Failed to execute 'observe' on 'MutationObserver': attributeOldValue is not supported", - ); - }); - - it('should throw if the `characterData` option is provided', () => { - let maybeNode; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }} - />, - ); - }); - - const node = ensureReactNativeElement(maybeNode); - - expect(() => { - const observer = new MutationObserver(() => {}); - observer.observe(node, {childList: true, characterData: true}); - }).toThrow( - "Failed to execute 'observe' on 'MutationObserver': characterData is not supported", - ); - }); - - it('should throw if the `characterDataOldValue` option is provided', () => { - let maybeNode; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }} - />, - ); - }); - - const node = ensureReactNativeElement(maybeNode); - - expect(() => { - const observer = new MutationObserver(() => {}); - observer.observe(node, {childList: true, characterDataOldValue: true}); - }).toThrow( - "Failed to execute 'observe' on 'MutationObserver': characterDataOldValue is not supported", - ); - }); - - it('should ignore calls to observe disconnected targets', () => { - let maybeNode; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }} - />, - ); - }); - - const node = ensureReactNativeElement(maybeNode); - - ReactNativeTester.runTask(() => { - root.render(<>); - }); - - expect(node.isConnected).toBe(false); - - const observerCallback = () => {}; - const observer = new MutationObserver(observerCallback); - - expect(() => { - observer.observe(node, {childList: true}); - }).not.toThrow(); - }); - - it('should report direct children added to and removed from an observed node (childList: true, subtree: false) ', () => { - let maybeNode; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }} - />, - ); - }); - - const node = ensureReactNativeElement(maybeNode); - - const observerCallbackCallArgs = []; - const observerCallback = (...args: $ReadOnlyArray) => { - observerCallbackCallArgs.push(args); - }; - const observer = new MutationObserver(observerCallback); - observer.observe(node, {childList: true}); - - // Does not report anything initially - expect(observerCallbackCallArgs.length).toBe(0); - - let maybeChildNode1, maybeChildNode2; - - ReactNativeTester.runTask(() => { - root.render( - - { - maybeChildNode1 = receivedChildNode; - }} - /> - { - maybeChildNode2 = receivedChildNode; - }} - /> - , - ); - }); - - const childNode1 = ensureReactNativeElement(maybeChildNode1); - const childNode2 = ensureReactNativeElement(maybeChildNode2); - - expect(observerCallbackCallArgs.length).toBe(1); - const firstCall = nullthrows(observerCallbackCallArgs.at(-1)); - expect(firstCall.length).toBe(2); - - const firstRecords = ensureMutationRecordArray(firstCall[0]); - expect(firstRecords).toBeInstanceOf(Array); - expect(firstRecords.length).toBe(1); - expect(firstRecords[0]).toBeInstanceOf(MutationRecord); - - const firstRecord = firstRecords[0]; - expect(firstRecord.type).toBe('childList'); - expect(firstRecord.target).toBe(node); - expect(firstRecord.addedNodes).toBeInstanceOf(NodeList); - expect(firstRecord.addedNodes[0]).toBe(childNode1); - expect(firstRecord.addedNodes[1]).toBe(childNode2); - expect(firstRecord.removedNodes).toBeInstanceOf(NodeList); - expect(firstRecord.removedNodes.length).toBe(0); - - expect(firstCall[1]).toBe(observer); - - ReactNativeTester.runTask(() => { - root.render( - - - , - ); - }); - - expect(observerCallbackCallArgs.length).toBe(2); - const secondCall = nullthrows(observerCallbackCallArgs.at(-1)); - expect(secondCall.length).toBe(2); - - const secondRecords = ensureMutationRecordArray(secondCall[0]); - expect(secondRecords.length).toBe(1); - expect(secondRecords[0]).toBeInstanceOf(MutationRecord); - - const secondRecord = secondRecords[0]; - expect(secondRecord.type).toBe('childList'); - expect(secondRecord.target).toBe(node); - expect(secondRecord.addedNodes).toBeInstanceOf(NodeList); - expect([...secondRecord.addedNodes]).toEqual([]); - expect(secondRecord.removedNodes).toBeInstanceOf(NodeList); - expect([...secondRecord.removedNodes]).toEqual([childNode1]); - - expect(secondCall[1]).toBe(observer); - }); - - it('should NOT report changes in transitive children when `subtree` is not set to true', () => { - let maybeObservedNode; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeObservedNode = ensureReactNativeElement(receivedNode); - }}> - - , - ); - }); - - const observedNode = nullthrows(maybeObservedNode); - - const observerCallback = jest.fn(); - const observer = new MutationObserver(observerCallback); - observer.observe(observedNode, {childList: true}); - - // Does not report anything initially - expect(observerCallback).not.toHaveBeenCalled(); - - ReactNativeTester.runTask(() => { - root.render( - - - - - , - ); - }); - - expect(observerCallback).not.toHaveBeenCalled(); - - ReactNativeTester.runTask(() => { - root.render( - - - , - ); - }); - - expect(observerCallback).not.toHaveBeenCalled(); - }); - - it('should report changes in transitive children when `subtree` is set to true', () => { - let maybeNode; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }}> - - , - ); - }); - - const node = ensureReactNativeElement(maybeNode); - - const observerCallback = jest.fn(); - const observer = new MutationObserver(observerCallback); - observer.observe(node, {childList: true, subtree: true}); - - // Does not report anything initially - expect(observerCallback).not.toHaveBeenCalled(); - - let maybeNode111; - - ReactNativeTester.runTask(() => { - root.render( - - - { - maybeNode111 = receivedGrandchildNode; - }} - /> - - , - ); - }); - - const node111 = ensureReactNativeElement(maybeNode111); - - expect(observerCallback).toHaveBeenCalledTimes(1); - const firstCall = observerCallback.mock.lastCall; - const firstRecords = ensureMutationRecordArray(firstCall[0]); - expect(firstRecords.length).toBe(1); - expect([...firstRecords[0].addedNodes]).toEqual([node111]); - expect([...firstRecords[0].removedNodes]).toEqual([]); - - ReactNativeTester.runTask(() => { - root.render( - - - , - ); - }); - - expect(observerCallback).toHaveBeenCalledTimes(2); - const secondRecords = ensureMutationRecordArray( - observerCallback.mock.lastCall[0], - ); - expect(secondRecords.length).toBe(1); - expect([...secondRecords[0].addedNodes]).toEqual([]); - expect([...secondRecords[0].removedNodes]).toEqual([node111]); - }); - - it('should report changes in different parts of the subtree as separate entries (subtree = true)', () => { - let maybeNode; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode = receivedNode; - }}> - - - , - ); - }); - - const node = ensureReactNativeElement(maybeNode); - - const observerCallback = jest.fn(); - const observer = new MutationObserver(observerCallback); - observer.observe(node, {childList: true, subtree: true}); - - // Does not report anything initially - expect(observerCallback).not.toHaveBeenCalled(); - - let maybeNode111, maybeNode121; - - ReactNativeTester.runTask(() => { - root.render( - - - { - maybeNode111 = receivedGrandchildNode; - }} - /> - - - { - maybeNode121 = receivedGrandchildNode; - }} - /> - - , - ); - }); - - const node111 = ensureReactNativeElement(maybeNode111); - const node121 = ensureReactNativeElement(maybeNode121); - - expect(observerCallback).toHaveBeenCalledTimes(1); - const firstCall = observerCallback.mock.lastCall; - const firstRecords = ensureMutationRecordArray(firstCall[0]); - expect(firstRecords.length).toBe(2); - expect([...firstRecords[0].addedNodes]).toEqual([node111]); - expect([...firstRecords[0].removedNodes]).toEqual([]); - expect([...firstRecords[1].addedNodes]).toEqual([node121]); - expect([...firstRecords[1].removedNodes]).toEqual([]); - - ReactNativeTester.runTask(() => { - root.render( - - - - , - ); - }); - - expect(observerCallback).toHaveBeenCalledTimes(2); - const secondCall = observerCallback.mock.lastCall; - const secondRecords = ensureMutationRecordArray(secondCall[0]); - expect(secondRecords.length).toBe(2); - expect([...secondRecords[0].addedNodes]).toEqual([]); - expect([...secondRecords[0].removedNodes]).toEqual([node111]); - expect([...secondRecords[1].addedNodes]).toEqual([]); - expect([...secondRecords[1].removedNodes]).toEqual([node121]); - - expect(secondCall[1]).toBe(observer); - }); - - describe('multiple observers', () => { - it('should report changes to multiple observers observing different subtrees', () => { - let maybeNode1; - let maybeNode2; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - <> - { - maybeNode1 = receivedNode; - }} - /> - { - maybeNode2 = receivedNode; - }} - /> - , - ); - }); - - const node1 = ensureReactNativeElement(maybeNode1); - const node2 = ensureReactNativeElement(maybeNode2); - - const observerCallback1 = jest.fn(); - const observer1 = new MutationObserver(observerCallback1); - observer1.observe(node1, {childList: true, subtree: true}); - - const observerCallback2 = jest.fn(); - const observer2 = new MutationObserver(observerCallback2); - observer2.observe(node2, {childList: true, subtree: true}); - - // Does not report anything initially - expect(observerCallback1).not.toHaveBeenCalled(); - expect(observerCallback2).not.toHaveBeenCalled(); - - let maybeChildNode11; - let maybeChildNode21; - - ReactNativeTester.runTask(() => { - root.render( - <> - - { - maybeChildNode11 = receivedNode; - }} - /> - - - { - maybeChildNode21 = receivedNode; - }} - /> - - , - ); - }); - - const childNode11 = ensureReactNativeElement(maybeChildNode11); - const childNode21 = ensureReactNativeElement(maybeChildNode21); - - expect(observerCallback1).toHaveBeenCalledTimes(1); - const observer1Records1 = ensureMutationRecordArray( - observerCallback1.mock.lastCall[0], - ); - expect(observer1Records1.length).toBe(1); - expect([...observer1Records1[0].addedNodes]).toEqual([childNode11]); - expect([...observer1Records1[0].removedNodes]).toEqual([]); - - expect(observerCallback2).toHaveBeenCalledTimes(1); - const observer2Records1 = ensureMutationRecordArray( - observerCallback2.mock.lastCall[0], - ); - expect(observer2Records1.length).toBe(1); - expect([...observer2Records1[0].addedNodes]).toEqual([childNode21]); - expect([...observer2Records1[0].removedNodes]).toEqual([]); - - ReactNativeTester.runTask(() => { - root.render( - <> - - - , - ); - }); - - expect(observerCallback1).toHaveBeenCalledTimes(2); - const observer1Records2 = ensureMutationRecordArray( - observerCallback1.mock.lastCall[0], - ); - expect(observer1Records2.length).toBe(1); - expect([...observer1Records2[0].addedNodes]).toEqual([]); - expect([...observer1Records2[0].removedNodes]).toEqual([childNode11]); - - expect(observerCallback2).toHaveBeenCalledTimes(2); - const observer2Records2 = ensureMutationRecordArray( - observerCallback2.mock.lastCall[0], - ); - expect(observer2Records2.length).toBe(1); - expect([...observer2Records2[0].addedNodes]).toEqual([]); - expect([...observer2Records2[0].removedNodes]).toEqual([childNode21]); - }); - - it('should report changes to multiple observers observing the same subtree', () => { - let maybeNode1; - let maybeNode2; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode1 = receivedNode; - }}> - { - maybeNode2 = receivedNode; - }} - /> - , - ); - }); - - const node1 = ensureReactNativeElement(maybeNode1); - const node2 = ensureReactNativeElement(maybeNode2); - - const observerCallback1 = jest.fn(); - const observer1 = new MutationObserver(observerCallback1); - observer1.observe(node1, {childList: true, subtree: true}); - - const observerCallback2 = jest.fn(); - const observer2 = new MutationObserver(observerCallback2); - observer2.observe(node2, {childList: true, subtree: true}); - - // Does not report anything initially - expect(observerCallback1).not.toHaveBeenCalled(); - expect(observerCallback2).not.toHaveBeenCalled(); - - let maybeChildNode111; - - ReactNativeTester.runTask(() => { - root.render( - - - { - maybeChildNode111 = receivedNode; - }} - /> - - , - ); - }); - - const childNode111 = ensureReactNativeElement(maybeChildNode111); - - expect(observerCallback1).toHaveBeenCalledTimes(1); - const observer1Records1 = ensureMutationRecordArray( - observerCallback1.mock.lastCall[0], - ); - expect(observer1Records1.length).toBe(1); - expect([...observer1Records1[0].addedNodes]).toEqual([childNode111]); - expect([...observer1Records1[0].removedNodes]).toEqual([]); - expect(observerCallback2).toHaveBeenCalledTimes(1); - const observer2Records1 = ensureMutationRecordArray( - observerCallback2.mock.lastCall[0], - ); - expect(observer2Records1.length).toBe(1); - expect([...observer2Records1[0].addedNodes]).toEqual([childNode111]); - expect([...observer2Records1[0].removedNodes]).toEqual([]); - - ReactNativeTester.runTask(() => { - root.render( - <> - - - - , - ); - }); - - expect(observerCallback1).toHaveBeenCalledTimes(2); - const observer1Records2 = ensureMutationRecordArray( - observerCallback1.mock.lastCall[0], - ); - expect(observer1Records2.length).toBe(1); - expect([...observer1Records2[0].addedNodes]).toEqual([]); - expect([...observer1Records2[0].removedNodes]).toEqual([childNode111]); - - expect(observerCallback2).toHaveBeenCalledTimes(2); - const observer2Records2 = ensureMutationRecordArray( - observerCallback2.mock.lastCall[0], - ); - expect(observer2Records2.length).toBe(1); - expect([...observer2Records2[0].addedNodes]).toEqual([]); - expect([...observer2Records2[0].removedNodes]).toEqual([childNode111]); - }); - }); - - describe('multiple observed nodes in the same observer', () => { - it('should report changes in disjoint observations', () => { - let maybeNode1; - let maybeNode2; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - <> - { - maybeNode1 = receivedNode; - }} - /> - { - maybeNode2 = receivedNode; - }} - /> - , - ); - }); - - const node1 = ensureReactNativeElement(maybeNode1); - const node2 = ensureReactNativeElement(maybeNode2); - - const observerCallback = jest.fn(); - const observer = new MutationObserver(observerCallback); - observer.observe(node1, {childList: true, subtree: true}); - observer.observe(node2, {childList: true, subtree: true}); - - // Does not report anything initially - expect(observerCallback).not.toHaveBeenCalled(); - - let maybeChildNode11; - let maybeChildNode21; - - ReactNativeTester.runTask(() => { - root.render( - <> - - { - maybeChildNode11 = receivedNode; - }} - /> - - - { - maybeChildNode21 = receivedNode; - }} - /> - - , - ); - }); - - const childNode11 = ensureReactNativeElement(maybeChildNode11); - const childNode21 = ensureReactNativeElement(maybeChildNode21); - - expect(observerCallback).toHaveBeenCalledTimes(1); - const records = ensureMutationRecordArray( - observerCallback.mock.lastCall[0], - ); - expect(records.length).toBe(2); - expect([...records[0].addedNodes]).toEqual([childNode11]); - expect([...records[0].removedNodes]).toEqual([]); - expect([...records[1].addedNodes]).toEqual([childNode21]); - expect([...records[1].removedNodes]).toEqual([]); - - ReactNativeTester.runTask(() => { - root.render( - <> - - - , - ); - }); - - expect(observerCallback).toHaveBeenCalledTimes(2); - const records2 = ensureMutationRecordArray( - observerCallback.mock.lastCall[0], - ); - expect(records2.length).toBe(2); - expect([...records2[0].addedNodes]).toEqual([]); - expect([...records2[0].removedNodes]).toEqual([childNode11]); - expect([...records2[1].addedNodes]).toEqual([]); - expect([...records2[1].removedNodes]).toEqual([childNode21]); - }); - - it('should report changes in joint observations', () => { - let maybeNode1; - let maybeNode11; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeNode1 = receivedNode; - }}> - { - maybeNode11 = receivedNode; - }} - /> - , - ); - }); - - const node1 = ensureReactNativeElement(maybeNode1); - const node11 = ensureReactNativeElement(maybeNode11); - - const observerCallback = jest.fn(); - const observer = new MutationObserver(observerCallback); - observer.observe(node1, {childList: true, subtree: true}); - observer.observe(node11, {childList: true, subtree: true}); - - // Does not report anything initially - expect(observerCallback).not.toHaveBeenCalled(); - - let maybeChildNode111; - - ReactNativeTester.runTask(() => { - root.render( - - - { - maybeChildNode111 = receivedNode; - }} - /> - - , - ); - }); - - const childNode111 = ensureReactNativeElement(maybeChildNode111); - - expect(observerCallback).toHaveBeenCalledTimes(1); - const records = ensureMutationRecordArray( - observerCallback.mock.lastCall[0], - ); - expect(records.length).toBe(1); - expect([...records[0].addedNodes]).toEqual([childNode111]); - expect([...records[0].removedNodes]).toEqual([]); - - ReactNativeTester.runTask(() => { - root.render( - - - , - ); - }); - - expect(observerCallback).toHaveBeenCalledTimes(2); - const records2 = ensureMutationRecordArray( - observerCallback.mock.lastCall[0], - ); - expect(records2.length).toBe(1); - expect([...records2[0].addedNodes]).toEqual([]); - expect([...records2[0].removedNodes]).toEqual([childNode111]); - }); - }); - }); - - describe('disconnect()', () => { - it('should stop observing targets', () => { - let maybeObservedNode; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeObservedNode = ensureReactNativeElement(receivedNode); - }} - />, - ); - }); - - const observedNode = nullthrows(maybeObservedNode); - - const observerCallback = jest.fn(); - const observer = new MutationObserver(observerCallback); - observer.observe(observedNode, {childList: true}); - - // Does not report anything initially - expect(observerCallback).not.toHaveBeenCalled(); - - ReactNativeTester.runTask(() => { - root.render( - - - - , - ); - }); - - expect(observerCallback).toHaveBeenCalledTimes(1); - - observer.disconnect(); - - ReactNativeTester.runTask(() => { - root.render( - - - , - ); - }); - - expect(observerCallback).toHaveBeenCalledTimes(1); - }); - - it('should correctly unobserve targets that are disconnected after observing', () => { - let maybeObservedNode; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeObservedNode = ensureReactNativeElement(receivedNode); - }} - />, - ); - }); - - const observedNode = nullthrows(maybeObservedNode); - - const observerCallback = jest.fn(); - const observer = new MutationObserver(observerCallback); - observer.observe(observedNode, {childList: true}); - - ReactNativeTester.runTask(() => { - root.render(<>); - }); - - expect(observedNode.isConnected).toBe(false); - - expect(() => { - observer.disconnect(); - }).not.toThrow(); - }); - - it('should correctly unobserve targets that are disconnected before observing', () => { - let maybeObservedNode; - - const root = ReactNativeTester.createRoot(); - ReactNativeTester.runTask(() => { - root.render( - { - maybeObservedNode = ensureReactNativeElement(receivedNode); - }} - />, - ); - }); - - const observedNode = nullthrows(maybeObservedNode); - - ReactNativeTester.runTask(() => { - root.render(<>); - }); - - expect(observedNode.isConnected).toBe(false); - - const observerCallback = jest.fn(); - const observer = new MutationObserver(observerCallback); - observer.observe(observedNode, {childList: true}); - - expect(() => { - observer.disconnect(); - }).not.toThrow(); - }); - }); -}); diff --git a/packages/react-native/src/private/webapis/mutationobserver/__tests__/setUpFeatureFlags.js b/packages/react-native/src/private/webapis/mutationobserver/__tests__/setUpFeatureFlags.js deleted file mode 100644 index 5af0f6f71dd..00000000000 --- a/packages/react-native/src/private/webapis/mutationobserver/__tests__/setUpFeatureFlags.js +++ /dev/null @@ -1,16 +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 - * @oncall react_native - */ - -import * as ReactNativeFeatureFlags from '../../../featureflags/ReactNativeFeatureFlags'; - -ReactNativeFeatureFlags.override({ - enableAccessToHostTreeInFabric: () => true, -});