-
Notifications
You must be signed in to change notification settings - Fork 24.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Summary: Changelog: [internal] Differential Revision: D66702625
- Loading branch information
1 parent
6bbca83
commit ab7db35
Showing
10 changed files
with
4,502 additions
and
0 deletions.
There are no files selected for viewing
96 changes: 96 additions & 0 deletions
96
packages/react-native/Libraries/Components/TextInput/__tests__/TextInput-itest.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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`', | ||
); | ||
}); | ||
}); |
219 changes: 219 additions & 0 deletions
219
packages/react-native/Libraries/ReactNative/__tests__/ReactFabric-Suspense-itest.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
/** | ||
* 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, use} from 'react'; | ||
|
||
let resolveFunction: (() => void) | null = null; | ||
|
||
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(); | ||
}); | ||
}); |
Oops, something went wrong.