Export existing Fantom tests (2nd attempt) (#48118)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/48118

Changelog: [internal]

This is a re-land of https://github.com/facebook/react-native/pull/48085

Reviewed By: rshest

Differential Revision: D66820308

fbshipit-source-id: b0ccd4b52965988015422ebdb8cd1172d1f5e9db
This commit is contained in:
Rubén Norte
2024-12-05 17:06:11 -08:00
committed by Facebook GitHub Bot
parent 18ebea533d
commit 1243679fe2
10 changed files with 5157 additions and 0 deletions
@@ -0,0 +1,96 @@
/**
* 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(
<TextInput
ref={node => {
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 | React.ElementRef<typeof TextInput>>(
null,
);
useLayoutEffect(() => {
textInputRef.current?.focus();
});
return <TextInput ref={textInputRef} />;
}
ReactNativeTester.runTask(() => {
root.render(<Component />);
});
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 | React.ElementRef<typeof TextInput>>(
null,
);
useEffect(() => {
textInputRef.current?.focus();
});
return <TextInput ref={textInputRef} />;
}
ReactNativeTester.runTask(() => {
root.render(<Component />);
});
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`',
);
});
});
@@ -0,0 +1,245 @@
/**
* 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<SquareData> {
await new Promise(resolve => {
resolveFunction = resolve;
});
return {
color: 'green',
};
}
async function getRedSquareData(): Promise<SquareData> {
await new Promise(resolve => {
resolveFunction = resolve;
});
return {
color: 'red',
};
}
const cache = new Map<SquareId, SquareData>();
async function getData(squareId: SquareId): Promise<SquareData> {
switch (squareId) {
case SquareId.Green:
return await getGreenSquareData();
case SquareId.Red:
return await getRedSquareData();
}
}
async function fetchData(squareId: SquareId): Promise<SquareData> {
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 <View key={data.color} nativeID={'square with data: ' + data.color} />;
}
function GreenSquare() {
return <Square squareId={SquareId.Green} />;
}
function RedSquare() {
return <Square squareId={SquareId.Red} />;
}
function Fallback() {
return <View nativeID="suspense fallback" />;
}
describe('Suspense', () => {
it('shows fallback if data is not available', () => {
cache.clear();
const root = ReactNativeTester.createRoot();
ReactNativeTester.runTask(() => {
root.render(
<Suspense fallback={<Fallback />}>
<GreenSquare />
</Suspense>,
);
});
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(
<Suspense fallback={<Fallback />}>
<RedSquare />
</Suspense>,
);
});
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(
<Suspense fallback={<Fallback />}>
<GreenSquare />
</Suspense>,
);
});
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 (
<Suspense fallback={<Fallback />}>
{props.color === 'green' ? <GreenSquare /> : <RedSquare />}
</Suspense>
);
}
ReactNativeTester.runTask(() => {
root.render(<App color="green" />);
});
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(<App color="red" />);
});
});
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();
});
});
@@ -0,0 +1,515 @@
/**
* 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();
});
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,412 @@
/**
* 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(
<NativeText
ref={node => {
lastParentNode = node;
}}>
Some text
</NativeText>,
);
});
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(
<NativeText
ref={node => {
lastParentNode = node;
}}>
Some text
</NativeText>,
);
});
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(
<NativeText
ref={node => {
lastParentNode = node;
}}>
Some text
</NativeText>,
);
});
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(
<NativeText
ref={node => {
lastParentNode = node;
}}>
Some text
</NativeText>,
);
});
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(
<NativeText
key="parent"
ref={element => {
lastParentElement = element;
}}>
Text A
<NativeText
key="childA"
ref={element => {
lastChildElementA = element;
}}
/>
Text B
</NativeText>,
);
});
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(
<NativeText
key="parent"
ref={element => {
lastParentElement = element;
}}>
Text A
<NativeText
key="childA"
ref={element => {
lastChildElementA = element;
}}
/>
Text B modified
</NativeText>,
);
});
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(
<NativeText
ref={node => {
lastParentNode = node;
}}>
Some text
</NativeText>,
);
});
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(
<NativeText
key="parent"
ref={element => {
lastParentElement = element;
}}>
Text A
<NativeText
key="childA"
ref={element => {
lastChildElementA = element;
}}
/>
Text B
<NativeText
key="childB"
ref={element => {
lastChildElementB = element;
}}
/>
Text C
<NativeText
key="childC"
ref={element => {
lastChildElementC = element;
}}
/>
Text D
</NativeText>,
);
});
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(
<NativeText
key="parent"
ref={element => {
lastParentElement = element;
}}>
Text A
</NativeText>,
);
});
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();
});
});
});
});
@@ -0,0 +1,16 @@
/**
* 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,
});
@@ -0,0 +1,16 @@
/**
* 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,
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,16 @@
/**
* 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,
});