mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
8f0713fd4b
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53551 See title. Follow up on D81044841 Changelog: [Internal] Reviewed By: rshest Differential Revision: D81447133 fbshipit-source-id: 7e6ca4523401c30c4861606c795f306193d97a15
2642 lines
89 KiB
JavaScript
2642 lines
89 KiB
JavaScript
/**
|
|
* 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.
|
|
*
|
|
* @fantom_flags enableViewCulling:true
|
|
* @flow strict-local
|
|
* @format
|
|
*/
|
|
|
|
import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
|
|
|
|
import type {HostInstance} from 'react-native';
|
|
|
|
import ensureInstance from '../../../../src/private/__tests__/utilities/ensureInstance';
|
|
import * as Fantom from '@react-native/fantom';
|
|
import nullthrows from 'nullthrows';
|
|
import * as React from 'react';
|
|
import {createRef, useState} from 'react';
|
|
import {FlatList, Modal, ScrollView, View} from 'react-native';
|
|
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
|
|
|
|
test('basic culling', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
const nodeRef = createRef<HostInstance>();
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView style={{height: 100, width: 100}} ref={nodeRef}>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{height: 10, width: 10, marginTop: 45}}
|
|
/>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
const element = ensureInstance(nodeRef.current, ReactNativeElement);
|
|
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 60,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Delete {type: "View", nativeID: "child"}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Delete {type: "View", nativeID: (N/A)}',
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
]);
|
|
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 0,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
});
|
|
|
|
test('recursive culling', () => {
|
|
const root = Fantom.createRoot({viewportHeight: 100, viewportWidth: 100});
|
|
const nodeRef = createRef<HostInstance>();
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView style={{height: 100, width: 100}} ref={nodeRef}>
|
|
<View
|
|
nativeID={'element A'}
|
|
style={{height: 30, width: 30, marginTop: 25}}>
|
|
<View nativeID={'child AA'} style={{height: 10, width: 10}} />
|
|
<View
|
|
nativeID={'child AB'}
|
|
style={{height: 10, width: 10, marginTop: 5}}
|
|
/>
|
|
</View>
|
|
<View
|
|
nativeID={'element B'}
|
|
style={{height: 30, width: 30, marginTop: 195}}>
|
|
<View nativeID={'child BA'} style={{height: 10, width: 10}} />
|
|
<View
|
|
nativeID={'child BB'}
|
|
style={{height: 10, width: 10, marginTop: 5}}
|
|
/>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "element A"}',
|
|
'Create {type: "View", nativeID: "child AA"}',
|
|
'Create {type: "View", nativeID: "child AB"}',
|
|
'Insert {type: "View", parentNativeID: "element A", index: 0, nativeID: "child AA"}',
|
|
'Insert {type: "View", parentNativeID: "element A", index: 1, nativeID: "child AB"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element A"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
const element = ensureInstance(nodeRef.current, ReactNativeElement);
|
|
|
|
// === Scroll down to the edge of child AA ===
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 30,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
]);
|
|
|
|
// === Scroll down past child AA ===
|
|
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 36,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Remove {type: "View", parentNativeID: "element A", index: 0, nativeID: "child AA"}',
|
|
'Delete {type: "View", nativeID: "child AA"}',
|
|
]);
|
|
|
|
// === Scroll down past child AB ===
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 51,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Remove {type: "View", parentNativeID: "element A", index: 0, nativeID: "child AB"}',
|
|
'Delete {type: "View", nativeID: "child AB"}',
|
|
]);
|
|
|
|
// === Scroll down past element A ===
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 56,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element A"}',
|
|
'Delete {type: "View", nativeID: "element A"}',
|
|
]);
|
|
|
|
// Scroll element B into viewport. Just child BA should be created.
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 155,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "element B"}',
|
|
'Create {type: "View", nativeID: "child BA"}',
|
|
'Insert {type: "View", parentNativeID: "element B", index: 0, nativeID: "child BA"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element B"}',
|
|
]);
|
|
|
|
// Scroll child BA into viewport.
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 165,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "child BB"}',
|
|
'Insert {type: "View", parentNativeID: "element B", index: 1, nativeID: "child BB"}',
|
|
]);
|
|
|
|
// Scroll back to start
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 0,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Remove {type: "View", parentNativeID: "element B", index: 1, nativeID: "child BB"}',
|
|
'Remove {type: "View", parentNativeID: "element B", index: 0, nativeID: "child BA"}',
|
|
'Delete {type: "View", nativeID: "child BA"}',
|
|
'Delete {type: "View", nativeID: "child BB"}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element B"}',
|
|
'Delete {type: "View", nativeID: "element B"}',
|
|
'Create {type: "View", nativeID: "element A"}',
|
|
'Create {type: "View", nativeID: "child AA"}',
|
|
'Create {type: "View", nativeID: "child AB"}',
|
|
'Insert {type: "View", parentNativeID: "element A", index: 0, nativeID: "child AA"}',
|
|
'Insert {type: "View", parentNativeID: "element A", index: 1, nativeID: "child AB"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element A"}',
|
|
]);
|
|
|
|
// Scroll past element A
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 85,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Remove {type: "View", parentNativeID: "element A", index: 1, nativeID: "child AB"}',
|
|
'Remove {type: "View", parentNativeID: "element A", index: 0, nativeID: "child AA"}',
|
|
'Delete {type: "View", nativeID: "child AA"}',
|
|
'Delete {type: "View", nativeID: "child AB"}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element A"}',
|
|
'Delete {type: "View", nativeID: "element A"}',
|
|
]);
|
|
});
|
|
|
|
test('recursive culling when initial offset is negative', () => {
|
|
const root = Fantom.createRoot({viewportHeight: 874, viewportWidth: 402});
|
|
const nodeRef = createRef<HostInstance>();
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 874, width: 402}}
|
|
contentOffset={{x: 0, y: -10000}}
|
|
ref={nodeRef}>
|
|
<View
|
|
nativeID={'child A'}
|
|
style={{height: 100, width: 100, marginTop: 235}}
|
|
/>
|
|
<View
|
|
nativeID={'child B'}
|
|
style={{height: 100, width: 100, marginTop: 235}}>
|
|
<View nativeID={'child BA'} style={{height: 17, width: 100}} />
|
|
<View
|
|
nativeID={'child BB'}
|
|
style={{height: 17, width: 100, marginTop: 60}}
|
|
/>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
const element = ensureInstance(nodeRef.current, ReactNativeElement);
|
|
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 0,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "child A"}',
|
|
'Create {type: "View", nativeID: "child B"}',
|
|
'Create {type: "View", nativeID: "child BA"}',
|
|
'Create {type: "View", nativeID: "child BB"}',
|
|
'Insert {type: "View", parentNativeID: "child B", index: 0, nativeID: "child BA"}',
|
|
'Insert {type: "View", parentNativeID: "child B", index: 1, nativeID: "child BB"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child A"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 1, nativeID: "child B"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
});
|
|
|
|
test('deep nesting', () => {
|
|
const root = Fantom.createRoot({viewportHeight: 100, viewportWidth: 100});
|
|
const nodeRef = createRef<HostInstance>();
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView style={{height: 100, width: 100}} ref={nodeRef}>
|
|
<View
|
|
nativeID={'element A'}
|
|
style={{height: 10, width: 100, marginTop: 30}}
|
|
/>
|
|
<View
|
|
nativeID={'element B'}
|
|
style={{height: 50, width: 100, marginTop: 85}}>
|
|
<View
|
|
nativeID={'child BA'}
|
|
style={{height: 30, width: 80, marginTop: 10, marginLeft: 10}}>
|
|
<View
|
|
nativeID={'child BAA'}
|
|
style={{height: 10, width: 75, marginTop: 5, marginLeft: 5}}
|
|
/>
|
|
<View
|
|
nativeID={'child BAB'}
|
|
style={{height: 10, width: 75, marginTop: 15, marginLeft: 5}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "element A"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element A"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
const element = ensureInstance(nodeRef.current, ReactNativeElement);
|
|
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 40,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "element B"}',
|
|
'Create {type: "View", nativeID: "child BA"}',
|
|
'Create {type: "View", nativeID: "child BAA"}',
|
|
'Insert {type: "View", parentNativeID: "child BA", index: 0, nativeID: "child BAA"}',
|
|
'Insert {type: "View", parentNativeID: "element B", index: 0, nativeID: "child BA"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 1, nativeID: "element B"}',
|
|
]);
|
|
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 150,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element A"}',
|
|
'Delete {type: "View", nativeID: "element A"}',
|
|
'Create {type: "View", nativeID: "child BAB"}',
|
|
'Insert {type: "View", parentNativeID: "child BA", index: 1, nativeID: "child BAB"}',
|
|
]);
|
|
});
|
|
|
|
test('adding new item into area that is not culled', () => {
|
|
const root = Fantom.createRoot({viewportHeight: 100, viewportWidth: 100});
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView style={{height: 100, width: 100}}>
|
|
<View
|
|
nativeID={'element A'}
|
|
style={{height: 20, width: 20, marginTop: 30}}
|
|
/>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "element A"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element A"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView style={{height: 100, width: 100}}>
|
|
<View
|
|
nativeID={'element A'}
|
|
style={{height: 20, width: 20, marginTop: 30}}>
|
|
<View nativeID={'child AA'} style={{height: 20, width: 20}} />
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Create {type: "View", nativeID: "child AA"}',
|
|
'Insert {type: "View", parentNativeID: "element A", index: 0, nativeID: "child AA"}',
|
|
]);
|
|
});
|
|
|
|
test('adding new item into area that is culled', () => {
|
|
const root = Fantom.createRoot({viewportHeight: 100, viewportWidth: 100});
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
contentOffset={{x: 0, y: 45}}
|
|
style={{height: 100, width: 100}}>
|
|
<View
|
|
key="element B"
|
|
nativeID={'element B'}
|
|
style={{height: 20, width: 20, marginTop: 30}}
|
|
/>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "element B"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element B"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
contentOffset={{x: 0, y: 45}}
|
|
style={{height: 100, width: 100}}>
|
|
<View
|
|
key="element A"
|
|
nativeID={'element A'}
|
|
style={{height: 20, width: 20}}
|
|
/>
|
|
<View
|
|
key="element B"
|
|
nativeID={'element B'}
|
|
style={{height: 20, width: 20, marginTop: 10}}
|
|
/>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
// element B is updated but it should be inconsequential.
|
|
// Differentiator generates an update for it because Yoga cloned
|
|
// shadow node backing element B.
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "View", nativeID: "element B"}',
|
|
]);
|
|
});
|
|
|
|
test('initial render', () => {
|
|
const nodeRef = createRef<HostInstance>();
|
|
const root = Fantom.createRoot({viewportHeight: 100, viewportWidth: 100});
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
contentOffset={{x: 0, y: 45}}
|
|
ref={nodeRef}
|
|
style={{height: 100, width: 100}}>
|
|
<View nativeID={'element A'} style={{height: 50, width: 100}} />
|
|
<View
|
|
nativeID={'element B'}
|
|
style={{height: 50, width: 100, marginTop: 100}}>
|
|
<View nativeID={'child BA'} style={{height: 20, width: 100}} />
|
|
<View
|
|
nativeID={'child BB'}
|
|
style={{height: 20, width: 100, marginTop: 10}}
|
|
/>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "element A"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element A"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
const element = ensureInstance(nodeRef.current, ReactNativeElement);
|
|
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 100,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element A"}',
|
|
'Delete {type: "View", nativeID: "element A"}',
|
|
'Create {type: "View", nativeID: "element B"}',
|
|
'Create {type: "View", nativeID: "child BA"}',
|
|
'Create {type: "View", nativeID: "child BB"}',
|
|
'Insert {type: "View", parentNativeID: "element B", index: 0, nativeID: "child BA"}',
|
|
'Insert {type: "View", parentNativeID: "element B", index: 1, nativeID: "child BB"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element B"}',
|
|
]);
|
|
});
|
|
|
|
test('unmounting culled elements', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 100, width: 100}}
|
|
contentOffset={{x: 0, y: 20}}>
|
|
<View nativeID={'element 1'} style={{height: 10, width: 10}} />
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(<></>);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Remove {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
'Delete {type: "ScrollView", nativeID: (N/A)}',
|
|
]);
|
|
});
|
|
|
|
// TODO: only elements in ScrollView are culled.
|
|
test('basic culling smaller ScrollView', () => {
|
|
const nodeRef = createRef<HostInstance>();
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView ref={nodeRef} style={{height: 50, width: 50, marginTop: 25}}>
|
|
<View nativeID={'element 1'} style={{height: 10, width: 10}} />
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "element 1"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element 1"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
const element = ensureInstance(nodeRef.current, ReactNativeElement);
|
|
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 11,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element 1"}',
|
|
'Delete {type: "View", nativeID: "element 1"}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Delete {type: "View", nativeID: (N/A)}',
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
]);
|
|
});
|
|
|
|
test('views are not culled when outside of viewport', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<View
|
|
nativeID={'child'}
|
|
style={{height: 10, width: 10, marginTop: 101}}
|
|
/>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "View", nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (root), index: 0, nativeID: "child"}',
|
|
]);
|
|
});
|
|
|
|
test('culling with transform move', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
const nodeRef = createRef<HostInstance>();
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView style={{height: 100, width: 100}} ref={nodeRef}>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{
|
|
height: 10,
|
|
width: 10,
|
|
marginTop: 90,
|
|
transform: [{translateY: 11}],
|
|
}}
|
|
/>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
const element = ensureInstance(nodeRef.current, ReactNativeElement);
|
|
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 1,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
]);
|
|
});
|
|
|
|
test('culling with recursive transform move', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
const nodeRef = createRef<HostInstance>();
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView style={{height: 100, width: 100}} ref={nodeRef}>
|
|
<View style={{transform: [{translateY: 11}]}}>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{
|
|
height: 10,
|
|
width: 10,
|
|
marginTop: 90,
|
|
}}
|
|
/>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
const element = ensureInstance(nodeRef.current, ReactNativeElement);
|
|
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 1,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
]);
|
|
});
|
|
|
|
test('culling with transform scale', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
const nodeRef = createRef<HostInstance>();
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView style={{height: 100, width: 100}} ref={nodeRef}>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{
|
|
height: 10,
|
|
width: 10,
|
|
marginTop: 105,
|
|
transform: [{scale: 2}],
|
|
}}
|
|
/>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
const element = ensureInstance(nodeRef.current, ReactNativeElement);
|
|
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 121,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Delete {type: "View", nativeID: "child"}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Delete {type: "View", nativeID: (N/A)}',
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
]);
|
|
});
|
|
|
|
test('culling when ScrollView parent has transform', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<View style={{transform: [{translateY: 100}]}}>
|
|
<ScrollView style={{height: 100, width: 100}}>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{height: 10, width: 10, marginTop: 45}}
|
|
/>
|
|
</ScrollView>
|
|
</View>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
});
|
|
|
|
test('culling inside of Modal', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
const nodeRef = createRef<HostInstance>();
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
// <ScrollView /> is scrolled down and if it wasn't for the Modal,
|
|
// the content would be culled.
|
|
<ScrollView
|
|
contentOffset={{x: 0, y: 100}}
|
|
style={{height: 100, width: 100}}>
|
|
<Modal ref={nodeRef} />
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
const element = ensureInstance(nodeRef.current, ReactNativeElement);
|
|
|
|
Fantom.runOnUIThread(() => {
|
|
Fantom.enqueueModalSizeUpdate(element, {
|
|
width: 100,
|
|
height: 100,
|
|
});
|
|
});
|
|
|
|
Fantom.runWorkLoop();
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "ModalHostView", nativeID: (root)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ModalHostView", parentNativeID: (N/A), index: 0, nativeID: (root)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
'Update {type: "View", nativeID: (N/A)}',
|
|
'Update {type: "ModalHostView", nativeID: (root)}',
|
|
'Update {type: "View", nativeID: (N/A)}',
|
|
]);
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
contentOffset={{x: 0, y: 100}}
|
|
style={{height: 100, width: 100}}>
|
|
<Modal ref={nodeRef}>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{height: 10, width: 10, marginTop: 45}}
|
|
/>
|
|
</Modal>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Create {type: "View", nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
]);
|
|
});
|
|
|
|
test('nesting inside FlatList with item resizing', () => {
|
|
const root = Fantom.createRoot({viewportHeight: 100, viewportWidth: 100});
|
|
let _setIsExpanded = null;
|
|
function ExpandableComponent() {
|
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
_setIsExpanded = setIsExpanded;
|
|
return <View>{isExpanded && <View style={{height: 80.5}} />}</View>;
|
|
}
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<FlatList
|
|
style={{height: 100, width: 100}}
|
|
data={[{key: 'one'}, {key: 'two'}]}
|
|
renderItem={({item}) => {
|
|
if (item.key === 'one') {
|
|
return <ExpandableComponent />;
|
|
} else if (item.key === 'two') {
|
|
return (
|
|
// position: 'absolute' is the important part that prevents Yoga from overcloning.
|
|
// When Yoga overclones, differentiator visits all cloned nodes and culling is correctly
|
|
// applied.
|
|
<View style={{position: 'absolute'}}>
|
|
<View nativeID={'parent'} style={{marginTop: 10}}>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{height: 10, width: 75, marginTop: 10}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
}}
|
|
/>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toContain(
|
|
'Create {type: "View", nativeID: "child"}',
|
|
);
|
|
|
|
Fantom.runTask(() => {
|
|
nullthrows(_setIsExpanded)(true);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toContain(
|
|
'Delete {type: "View", nativeID: "child"}',
|
|
);
|
|
});
|
|
|
|
describe('reparenting', () => {
|
|
test('view flattening with culling', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
const nodeRef = createRef<HostInstance>();
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView style={{height: 100, width: 100}} ref={nodeRef}>
|
|
<View
|
|
style={{
|
|
marginTop: 150,
|
|
}}>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{height: 10, width: 10, backgroundColor: 'red'}}
|
|
/>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
const element = ensureInstance(nodeRef.current, ReactNativeElement);
|
|
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 60,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
]);
|
|
|
|
// force view to be unflattened.
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView style={{height: 100, width: 100}} ref={nodeRef}>
|
|
<View
|
|
style={{
|
|
marginTop: 150,
|
|
opacity: 0, // force view to be unflattened
|
|
}}>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{height: 10, width: 10, backgroundColor: 'red'}}
|
|
/>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "View", nativeID: "child"}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
]);
|
|
|
|
// force view to be flattened.
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView style={{height: 100, width: 100}} ref={nodeRef}>
|
|
<View
|
|
style={{
|
|
marginTop: 150,
|
|
}}>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{height: 10, width: 10, backgroundColor: 'red'}}
|
|
/>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "View", nativeID: "child"}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Delete {type: "View", nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
]);
|
|
});
|
|
|
|
test('scroll view parent is unflattened and culled view becomes visible', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<View style={{width: 100, height: 100}}>
|
|
<ScrollView>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{height: 10, width: 10, marginTop: 150}}
|
|
/>
|
|
</ScrollView>
|
|
</View>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<View nativeID="unflattened" style={{width: 100, height: 100}}>
|
|
<ScrollView>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{height: 10, width: 10, marginTop: 50}}
|
|
/>
|
|
</ScrollView>
|
|
</View>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Remove {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "unflattened"}',
|
|
'Update {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (root), index: 0, nativeID: "unflattened"}',
|
|
'Insert {type: "ScrollView", parentNativeID: "unflattened", index: 0, nativeID: (N/A)}',
|
|
]);
|
|
});
|
|
|
|
test('scroll view parent is flattened and culled view becomes visible', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<View nativeID="unflattened" style={{width: 100, height: 100}}>
|
|
<ScrollView>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{height: 10, width: 10, marginTop: 150}}
|
|
/>
|
|
</ScrollView>
|
|
</View>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "View", nativeID: "unflattened"}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: "unflattened", index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (root), index: 0, nativeID: "unflattened"}',
|
|
]);
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<View style={{width: 100, height: 100}}>
|
|
<ScrollView>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{height: 10, width: 10, marginTop: 50}}
|
|
/>
|
|
</ScrollView>
|
|
</View>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Remove {type: "ScrollView", parentNativeID: "unflattened", index: 0, nativeID: (N/A)}',
|
|
'Remove {type: "View", parentNativeID: (root), index: 0, nativeID: "unflattened"}',
|
|
'Delete {type: "View", nativeID: "unflattened"}',
|
|
'Update {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
});
|
|
|
|
test('scroll view parent is flattened and view becomes culled', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<View nativeID="unflattened" style={{width: 100, height: 100}}>
|
|
<ScrollView>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{height: 10, width: 10, marginTop: 50}}
|
|
/>
|
|
</ScrollView>
|
|
</View>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "View", nativeID: "unflattened"}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: "unflattened", index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (root), index: 0, nativeID: "unflattened"}',
|
|
]);
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<View style={{width: 100, height: 100}}>
|
|
<ScrollView>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{height: 10, width: 10, marginTop: 150}}
|
|
/>
|
|
</ScrollView>
|
|
</View>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Remove {type: "ScrollView", parentNativeID: "unflattened", index: 0, nativeID: (N/A)}',
|
|
'Remove {type: "View", parentNativeID: (root), index: 0, nativeID: "unflattened"}',
|
|
'Delete {type: "View", nativeID: "unflattened"}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Delete {type: "View", nativeID: "child"}',
|
|
'Update {type: "View", nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
});
|
|
|
|
test('scroll view parent is unflattened and view becomes culled', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<View style={{width: 100, height: 100}}>
|
|
<ScrollView>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{height: 10, width: 10, marginTop: 50}}
|
|
/>
|
|
</ScrollView>
|
|
</View>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<View nativeID="unflattened" style={{width: 100, height: 100}}>
|
|
<ScrollView>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{height: 10, width: 10, marginTop: 150}}
|
|
/>
|
|
</ScrollView>
|
|
</View>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Remove {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "unflattened"}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Delete {type: "View", nativeID: "child"}',
|
|
'Update {type: "View", nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (root), index: 0, nativeID: "unflattened"}',
|
|
'Insert {type: "ScrollView", parentNativeID: "unflattened", index: 0, nativeID: (N/A)}',
|
|
]);
|
|
});
|
|
|
|
test('parent-child flattening with culling', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 100, width: 100}}
|
|
contentOffset={{x: 0, y: 60}}>
|
|
<View
|
|
style={{
|
|
marginTop: 100,
|
|
opacity: 0,
|
|
}}>
|
|
<View
|
|
style={{
|
|
marginTop: 50,
|
|
opacity: 0,
|
|
}}>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{height: 10, width: 10, backgroundColor: 'red'}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
// force parent-child to be flattened.
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 100, width: 100}}
|
|
contentOffset={{x: 0, y: 60}}>
|
|
<View
|
|
style={{
|
|
marginTop: 100,
|
|
}}>
|
|
<View
|
|
style={{
|
|
marginTop: 50,
|
|
}}>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{height: 10, width: 10, backgroundColor: 'red'}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "View", nativeID: "child"}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Delete {type: "View", nativeID: (N/A)}',
|
|
'Delete {type: "View", nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
]);
|
|
});
|
|
|
|
test('flattening grandparent ', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView style={{height: 100, width: 100}}>
|
|
<View // grandparent
|
|
style={{
|
|
marginTop: 70,
|
|
opacity: 0, // opacity 0 - can't be flattened
|
|
}}>
|
|
<View // parent
|
|
nativeID="parent"
|
|
style={{height: 10, width: 10, marginTop: 10}}>
|
|
<View // child
|
|
nativeID="child"
|
|
style={{height: 5, width: 5, marginTop: 5}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toContain(
|
|
'Insert {type: "View", parentNativeID: "parent", index: 0, nativeID: "child"}',
|
|
);
|
|
|
|
// Flatten grandparent by changing opacity to default value.
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView style={{height: 100, width: 100}}>
|
|
<View // grandparent
|
|
style={{
|
|
marginTop: 70,
|
|
}}>
|
|
<View // parent
|
|
nativeID="parent"
|
|
style={{height: 10, width: 11, marginTop: 10}}>
|
|
<View // child
|
|
nativeID="child"
|
|
style={{height: 5, width: 5, marginTop: 5}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "View", nativeID: "parent"}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "parent"}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Delete {type: "View", nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "parent"}',
|
|
]);
|
|
});
|
|
|
|
test('unflattening grandparent', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView style={{height: 100, width: 100}}>
|
|
<View // grandparent
|
|
style={{
|
|
marginTop: 70,
|
|
}}>
|
|
<View // parent
|
|
nativeID={'parent'}
|
|
style={{height: 10, width: 10, marginTop: 10}}>
|
|
<View // child
|
|
nativeID="child"
|
|
style={{height: 5, width: 5, marginTop: 5}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toContain(
|
|
'Insert {type: "View", parentNativeID: "parent", index: 0, nativeID: "child"}',
|
|
);
|
|
|
|
// Unflatten grandparent by setting opacity to 0.
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView style={{height: 100, width: 100}}>
|
|
<View // grandparent
|
|
style={{
|
|
marginTop: 70,
|
|
opacity: 0, // opacity 0 - can't be flattened
|
|
}}>
|
|
<View // parent
|
|
nativeID={'parent'}
|
|
style={{height: 10, width: 11, marginTop: 10}}>
|
|
<View // child
|
|
nativeID="child"
|
|
style={{height: 5, width: 5, marginTop: 5}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "View", nativeID: "parent"}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "parent"}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "parent"}',
|
|
]);
|
|
});
|
|
|
|
test('parent-child flattening with child culled', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
const nodeRef = createRef<HostInstance>();
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 100, width: 100}}
|
|
ref={nodeRef}
|
|
contentOffset={{x: 0, y: 50}}>
|
|
<View
|
|
style={{
|
|
marginTop: 100,
|
|
opacity: 0.5,
|
|
}}>
|
|
<View
|
|
style={{
|
|
marginTop: 50,
|
|
opacity: 0.1,
|
|
}}>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{height: 10, width: 10, marginTop: 5}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
// force parent-child to be flattened.
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 100, width: 100}}
|
|
ref={nodeRef}
|
|
contentOffset={{x: 0, y: 50}}>
|
|
<View
|
|
style={{
|
|
marginTop: 100,
|
|
}}>
|
|
<View
|
|
style={{
|
|
marginTop: 50,
|
|
}}>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{height: 10, width: 10, marginTop: 5}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Delete {type: "View", nativeID: (N/A)}',
|
|
'Delete {type: "View", nativeID: (N/A)}',
|
|
]);
|
|
});
|
|
|
|
test('parent-child switching from unflattened-flattened to flattened-unflattened', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 100, width: 100}}
|
|
contentOffset={{x: 0, y: 60}}>
|
|
<View
|
|
style={{
|
|
marginTop: 100,
|
|
opacity: 0,
|
|
}}>
|
|
<View
|
|
style={{
|
|
marginTop: 50,
|
|
}}>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{height: 10, width: 10, backgroundColor: 'red'}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
// force view to be flattened.
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 100, width: 100}}
|
|
contentOffset={{x: 0, y: 60}}>
|
|
<View
|
|
style={{
|
|
marginTop: 100,
|
|
}}>
|
|
<View
|
|
style={{
|
|
marginTop: 50,
|
|
opacity: 0,
|
|
}}>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{height: 10, width: 10, backgroundColor: 'red'}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "View", nativeID: "child"}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Delete {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
});
|
|
|
|
test('unflattening and creating a subtree that is partially culled', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
// First render with a flattened view container that is visible.
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 100, width: 100}}
|
|
contentOffset={{x: 0, y: 111}}>
|
|
<View style={{marginTop: 200}} />
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
const nodeRef = createRef<HostInstance>();
|
|
|
|
// Now update opacity to unflattned the container and add a child that has a culled descendant.
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 100, width: 100}}
|
|
ref={nodeRef}
|
|
contentOffset={{x: 0, y: 111}}>
|
|
<View
|
|
style={{
|
|
marginTop: 200,
|
|
opacity: 0.5, // Force unflattening
|
|
}}>
|
|
<View
|
|
nativeID="child"
|
|
style={{
|
|
marginTop: 10,
|
|
height: 10,
|
|
width: 10,
|
|
}}>
|
|
<View
|
|
nativeID="grandchild"
|
|
style={{
|
|
marginTop: 5,
|
|
height: 5,
|
|
width: 5,
|
|
}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Update {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
]);
|
|
|
|
const element = ensureInstance(nodeRef.current, ReactNativeElement);
|
|
|
|
// Scroll down to see the grandchild.
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 115,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "grandchild"}',
|
|
'Insert {type: "View", parentNativeID: "child", index: 0, nativeID: "grandchild"}',
|
|
]);
|
|
});
|
|
|
|
test('unflattening and creating a deeper subtree that is partially culled', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
// First render with a flattened view container that is visible.
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 100, width: 100}}
|
|
contentOffset={{x: 0, y: 115}}>
|
|
<View style={{marginTop: 200}} />
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
const nodeRef = createRef<HostInstance>();
|
|
|
|
// Now update opacity to unflattned the container and add a child that has a culled descendant.
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 100, width: 100}}
|
|
ref={nodeRef}
|
|
contentOffset={{x: 0, y: 115}}>
|
|
<View
|
|
style={{
|
|
marginTop: 200,
|
|
opacity: 0.5, // Force unflattening
|
|
}}>
|
|
<View
|
|
nativeID="child"
|
|
style={{
|
|
marginTop: 10,
|
|
height: 10,
|
|
width: 10,
|
|
}}>
|
|
<View
|
|
nativeID="grandchild"
|
|
style={{
|
|
marginTop: 5, // 215
|
|
height: 5,
|
|
width: 5,
|
|
}}>
|
|
<View
|
|
nativeID="grandgrandchild"
|
|
style={{
|
|
marginTop: 2.5, // 217.5
|
|
height: 2.5,
|
|
width: 2.5,
|
|
}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Update {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "child"}',
|
|
'Create {type: "View", nativeID: "grandchild"}',
|
|
'Insert {type: "View", parentNativeID: "child", index: 0, nativeID: "grandchild"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
]);
|
|
|
|
const element = ensureInstance(nodeRef.current, ReactNativeElement);
|
|
|
|
// Scroll down to see the grandchild.
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 118,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "grandgrandchild"}',
|
|
'Insert {type: "View", parentNativeID: "grandchild", index: 0, nativeID: "grandgrandchild"}',
|
|
]);
|
|
});
|
|
|
|
test('flattening and deleting a deeper subtree that is partially culled', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
// First render with a unflattened view container that is visible and a subtree that is partially culled.
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 100, width: 100}}
|
|
contentOffset={{x: 0, y: 115}}>
|
|
<View style={{marginTop: 200, opacity: 0.5}}>
|
|
<View
|
|
nativeID="child"
|
|
style={{
|
|
marginTop: 10,
|
|
height: 10,
|
|
width: 10,
|
|
}}>
|
|
<View
|
|
nativeID="grandchild"
|
|
style={{
|
|
marginTop: 5,
|
|
height: 5,
|
|
width: 5,
|
|
}}>
|
|
<View
|
|
nativeID="grandgrandchild"
|
|
style={{
|
|
marginTop: 2.5,
|
|
height: 2.5,
|
|
width: 2.5,
|
|
}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
// All views are mounted, except for the grandchild.
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "child"}',
|
|
'Create {type: "View", nativeID: "grandchild"}',
|
|
'Insert {type: "View", parentNativeID: "child", index: 0, nativeID: "grandchild"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
// Now change opacity to the default to flatten the container and delete container's subtree.
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 100, width: 100}}
|
|
contentOffset={{x: 0, y: 115}}>
|
|
<View
|
|
style={{
|
|
marginTop: 200,
|
|
}}
|
|
/>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
// Note that the grandchild is not deleted because it was not previously mounted.
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Update {type: "View", nativeID: (N/A)}',
|
|
'Remove {type: "View", parentNativeID: "child", index: 0, nativeID: "grandchild"}',
|
|
'Delete {type: "View", nativeID: "grandchild"}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Delete {type: "View", nativeID: (N/A)}',
|
|
'Delete {type: "View", nativeID: "child"}',
|
|
]);
|
|
});
|
|
|
|
test('flattening and deleting a subtree that is partially culled', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
// First render with a unflattened view container that is visible and a subtree that is partially culled.
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 100, width: 100}}
|
|
contentOffset={{x: 0, y: 111}}>
|
|
<View style={{marginTop: 200, opacity: 0.5}}>
|
|
<View
|
|
nativeID="child"
|
|
style={{
|
|
marginTop: 10,
|
|
height: 10,
|
|
width: 10,
|
|
}}>
|
|
<View
|
|
nativeID="grandchild"
|
|
style={{
|
|
marginTop: 5,
|
|
height: 5,
|
|
width: 5,
|
|
}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
// All views are mounted, except for the grandchild.
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
// Now change opacity to the default to flatten the container and delete container's subtree.
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 100, width: 100}}
|
|
contentOffset={{x: 0, y: 111}}>
|
|
<View
|
|
style={{
|
|
marginTop: 200,
|
|
}}
|
|
/>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
// Note that the grandchild is not deleted because it was not previously mounted.
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Update {type: "View", nativeID: (N/A)}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Delete {type: "View", nativeID: (N/A)}',
|
|
'Delete {type: "View", nativeID: "child"}',
|
|
]);
|
|
});
|
|
|
|
test('parent-child switching from unflattened-flattened to flattened-unflattened and grandchild is culled', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
// First render unflattened view container with flattened child that has a culled grandchild.
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 100, width: 100}}
|
|
contentOffset={{x: 0, y: 60}}>
|
|
<View
|
|
style={{
|
|
marginTop: 100,
|
|
opacity: 0,
|
|
}}>
|
|
<View
|
|
style={{
|
|
marginTop: 50,
|
|
}}>
|
|
<View
|
|
nativeID={'grandchild'}
|
|
style={{height: 10, width: 10, marginTop: 11}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
// Note that `grandchild` is not mounted.
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
const nodeRef = createRef<HostInstance>();
|
|
|
|
// Now change unflattened view container to flattened and change its child to be unflattened.
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 100, width: 100}}
|
|
ref={nodeRef}
|
|
contentOffset={{x: 0, y: 60}}>
|
|
<View
|
|
style={{
|
|
marginTop: 100,
|
|
}}>
|
|
<View
|
|
style={{
|
|
marginTop: 50,
|
|
opacity: 0,
|
|
}}>
|
|
<View
|
|
nativeID={'grandchild'}
|
|
style={{height: 10, width: 10, marginTop: 11}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
// Note that `grandchild` is not mounted.
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Delete {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
const element = ensureInstance(nodeRef.current, ReactNativeElement);
|
|
|
|
// Scroll to reveal grandchild.
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 70,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "grandchild"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "grandchild"}',
|
|
]);
|
|
});
|
|
|
|
test('parent-child switching from flattened-unflattened to unflattened-flattened and grandchild is culled', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
// First render unflattened view container with flattened child that has a culled grandchild.
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 100, width: 100}}
|
|
contentOffset={{x: 0, y: 60}}>
|
|
<View
|
|
style={{
|
|
marginTop: 100,
|
|
}}>
|
|
<View
|
|
style={{
|
|
marginTop: 50,
|
|
opacity: 0,
|
|
}}>
|
|
<View
|
|
nativeID={'grandchild'}
|
|
style={{height: 10, width: 10, marginTop: 11}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
// Note that `grandchild` is not mounted.
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
const nodeRef = createRef<HostInstance>();
|
|
|
|
// Now change unflattened view container to flattened and change its child to be unflattened.
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 100, width: 100}}
|
|
ref={nodeRef}
|
|
contentOffset={{x: 0, y: 60}}>
|
|
<View
|
|
style={{
|
|
marginTop: 100,
|
|
opacity: 0,
|
|
}}>
|
|
<View
|
|
style={{
|
|
marginTop: 50,
|
|
}}>
|
|
<View
|
|
nativeID={'grandchild'}
|
|
style={{height: 10, width: 10, marginTop: 11}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
// Note that `grandchild` is not mounted.
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Delete {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
const element = ensureInstance(nodeRef.current, ReactNativeElement);
|
|
|
|
// Scroll to reveal grandchild.
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 70,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "grandchild"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "grandchild"}',
|
|
]);
|
|
});
|
|
|
|
test('parent-child switching from flattened-unflattened to unflattened-flattened', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
// First create a view hierarchy where parent is flattened but child is not.
|
|
// `grandchild` is not culled.
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 100, width: 100}}
|
|
contentOffset={{x: 0, y: 60}}>
|
|
<View
|
|
style={{
|
|
marginTop: 100,
|
|
}}>
|
|
<View
|
|
style={{
|
|
marginTop: 50,
|
|
opacity: 0,
|
|
}}>
|
|
<View nativeID={'grandchild'} style={{height: 10, width: 10}} />
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "grandchild"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "grandchild"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
// Now switch parent to be unflattened and child to be flattened.
|
|
// `grandchild` remains visible.
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 100, width: 100}}
|
|
contentOffset={{x: 0, y: 60}}>
|
|
<View
|
|
style={{
|
|
marginTop: 100,
|
|
opacity: 0,
|
|
}}>
|
|
<View
|
|
style={{
|
|
marginTop: 50,
|
|
}}>
|
|
<View nativeID={'grandchild'} style={{height: 10, width: 10}} />
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "View", nativeID: "grandchild"}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "grandchild"}',
|
|
'Delete {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "grandchild"}',
|
|
]);
|
|
});
|
|
|
|
test('nested scroll view with unflattened wrapper', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView style={{width: 100, height: 100}}>
|
|
<View>
|
|
<ScrollView
|
|
contentOffset={{x: 15, y: 0}}
|
|
style={{height: 100, width: 100}}
|
|
horizontal={true}>
|
|
<View nativeID="child" style={{width: 10, height: 10}} />
|
|
</ScrollView>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView style={{height: 100, width: 100, padding: 1}}>
|
|
<View nativeID="unflattened">
|
|
<ScrollView
|
|
contentOffset={{x: 15, y: 0}}
|
|
style={{height: 100, width: 100}}
|
|
horizontal={true}>
|
|
<View nativeID="child" style={{width: 10, height: 10}} />
|
|
</ScrollView>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Update {type: "View", nativeID: (N/A)}',
|
|
'Remove {type: "ScrollView", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "unflattened"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "unflattened"}',
|
|
'Insert {type: "ScrollView", parentNativeID: "unflattened", index: 0, nativeID: (N/A)}',
|
|
]);
|
|
});
|
|
|
|
test('reparenting with reparented subtree changing its marginTop', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
const nodeRef = createRef<HostInstance>();
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView ref={nodeRef} style={{width: 100, height: 100}}>
|
|
<View>
|
|
<View
|
|
style={{
|
|
height: 100,
|
|
width: 100,
|
|
}}
|
|
collapsableChildren={false}>
|
|
<View nativeID="child" style={{width: 10, height: 10}}>
|
|
<View
|
|
nativeID="grandchild"
|
|
style={{width: 5, height: 5, marginTop: 5}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "child"}',
|
|
'Create {type: "View", nativeID: "grandchild"}',
|
|
'Insert {type: "View", parentNativeID: "child", index: 0, nativeID: "grandchild"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView ref={nodeRef} style={{height: 100, width: 100}}>
|
|
<View nativeID="unflattened">
|
|
<View
|
|
style={{
|
|
height: 100,
|
|
width: 100,
|
|
marginTop: 97,
|
|
}}
|
|
collapsableChildren={false}>
|
|
<View nativeID="child" style={{width: 10, height: 10}}>
|
|
<View
|
|
nativeID="grandchild"
|
|
style={{width: 5, height: 5, marginTop: 5}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Update {type: "View", nativeID: (N/A)}',
|
|
'Update {type: "View", nativeID: "child"}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Create {type: "View", nativeID: "unflattened"}',
|
|
'Remove {type: "View", parentNativeID: "child", index: 0, nativeID: "grandchild"}',
|
|
'Delete {type: "View", nativeID: "grandchild"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "unflattened"}',
|
|
'Insert {type: "View", parentNativeID: "unflattened", index: 0, nativeID: "child"}',
|
|
]);
|
|
|
|
const element = ensureInstance(nodeRef.current, ReactNativeElement);
|
|
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 50,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "grandchild"}',
|
|
'Insert {type: "View", parentNativeID: "child", index: 0, nativeID: "grandchild"}',
|
|
]);
|
|
});
|
|
|
|
test('reparenting deep tree with reparented subtree changing its marginTop', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
const nodeRef = createRef<HostInstance>();
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView ref={nodeRef} style={{width: 100, height: 100}}>
|
|
<View>
|
|
<View
|
|
style={{
|
|
height: 100,
|
|
width: 100,
|
|
}}
|
|
collapsableChildren={false}>
|
|
<View nativeID="child" style={{width: 10, height: 10}}>
|
|
<View
|
|
nativeID="grandchild"
|
|
style={{width: 5, height: 5, marginTop: 5}}>
|
|
<View
|
|
nativeID="grandgrandchild"
|
|
style={{width: 5, height: 5, marginTop: 5}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "child"}',
|
|
'Create {type: "View", nativeID: "grandchild"}',
|
|
'Create {type: "View", nativeID: "grandgrandchild"}',
|
|
'Insert {type: "View", parentNativeID: "grandchild", index: 0, nativeID: "grandgrandchild"}',
|
|
'Insert {type: "View", parentNativeID: "child", index: 0, nativeID: "grandchild"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView ref={nodeRef} style={{height: 100, width: 100}}>
|
|
<View nativeID="unflattened">
|
|
<View
|
|
style={{
|
|
height: 100,
|
|
width: 100,
|
|
marginTop: 94,
|
|
}}
|
|
collapsableChildren={false}>
|
|
<View nativeID="child" style={{width: 10, height: 10}}>
|
|
<View
|
|
nativeID="grandchild"
|
|
style={{width: 5, height: 5, marginTop: 5}}>
|
|
<View
|
|
nativeID="grandgrandchild"
|
|
style={{width: 2.5, height: 2.5, marginTop: 2.5}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Update {type: "View", nativeID: (N/A)}',
|
|
'Update {type: "View", nativeID: "child"}',
|
|
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
|
|
'Create {type: "View", nativeID: "unflattened"}',
|
|
'Remove {type: "View", parentNativeID: "grandchild", index: 0, nativeID: "grandgrandchild"}',
|
|
'Delete {type: "View", nativeID: "grandgrandchild"}',
|
|
'Update {type: "View", nativeID: "grandchild"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "unflattened"}',
|
|
'Insert {type: "View", parentNativeID: "unflattened", index: 0, nativeID: "child"}',
|
|
]);
|
|
|
|
const element = ensureInstance(nodeRef.current, ReactNativeElement);
|
|
|
|
Fantom.scrollTo(element, {
|
|
x: 0,
|
|
y: 50,
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "grandgrandchild"}',
|
|
'Insert {type: "View", parentNativeID: "grandchild", index: 0, nativeID: "grandgrandchild"}',
|
|
]);
|
|
});
|
|
|
|
test('parent-child flattening with deep hierarchy', () => {
|
|
function renderTree(root: Fantom.Root, isFinal: boolean) {
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 100, width: 100}}
|
|
contentOffset={{x: 0, y: 52}}>
|
|
<View
|
|
style={{
|
|
marginTop: isFinal ? 92 : 100,
|
|
opacity: isFinal ? 0 : undefined,
|
|
}}>
|
|
<View
|
|
style={{
|
|
marginTop: 50,
|
|
opacity: isFinal ? 0 : undefined,
|
|
}}>
|
|
<View collapsable={false} style={{height: 10, width: 10}}>
|
|
<View
|
|
collapsable={false}
|
|
style={{height: 5, width: 5, marginTop: 5}}>
|
|
<View
|
|
nativeID="child"
|
|
style={{height: 2.5, width: 2.5, marginTop: 2.5}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
}
|
|
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
renderTree(root, false);
|
|
|
|
expect(root.takeMountingManagerLogs()).not.toContain(
|
|
'Create {type: "View", nativeID: "child"}',
|
|
);
|
|
|
|
renderTree(root, true);
|
|
|
|
expect(root.takeMountingManagerLogs()).toContain(
|
|
'Create {type: "View", nativeID: "child"}',
|
|
);
|
|
|
|
const finalRoot = Fantom.createRoot({
|
|
viewportWidth: 100,
|
|
viewportHeight: 100,
|
|
});
|
|
|
|
renderTree(finalRoot, true);
|
|
|
|
expect(root.getRenderedOutput().toJSON).toEqual(
|
|
finalRoot.getRenderedOutput().toJSON,
|
|
);
|
|
});
|
|
|
|
test('parent-child unflattening with deep hierarchy', () => {
|
|
function renderTree(root: Fantom.Root, isFinal: boolean) {
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{height: 100, width: 100}}
|
|
contentOffset={{x: 0, y: 52}}>
|
|
<View
|
|
style={{
|
|
marginTop: isFinal ? 92 : 100,
|
|
opacity: isFinal ? undefined : 0,
|
|
}}>
|
|
<View
|
|
style={{
|
|
marginTop: 50,
|
|
opacity: isFinal ? undefined : 0,
|
|
}}>
|
|
<View collapsable={false} style={{height: 10, width: 10}}>
|
|
<View
|
|
collapsable={false}
|
|
style={{height: 5, width: 5, marginTop: 5}}>
|
|
<View
|
|
nativeID="child"
|
|
style={{height: 2.5, width: 2.5, marginTop: 2.5}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
}
|
|
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
renderTree(root, false);
|
|
|
|
expect(root.takeMountingManagerLogs()).not.toContain(
|
|
'Create {type: "View", nativeID: "child"}',
|
|
);
|
|
|
|
renderTree(root, true);
|
|
|
|
expect(root.takeMountingManagerLogs()).toContain(
|
|
'Create {type: "View", nativeID: "child"}',
|
|
);
|
|
|
|
const finalRoot = Fantom.createRoot({
|
|
viewportWidth: 100,
|
|
viewportHeight: 100,
|
|
});
|
|
|
|
renderTree(finalRoot, true);
|
|
|
|
expect(root.getRenderedOutput().toJSON).toEqual(
|
|
finalRoot.getRenderedOutput().toJSON,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('opt out mechanism - Unstable_uncullableView & Unstable_uncullableTrace', () => {
|
|
test('modal is still rendered even though it is in culling region', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
const nodeRef = createRef<HostInstance>();
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView style={{height: 100, width: 100}}>
|
|
<View nativeID="modal parent" style={{marginTop: 101}}>
|
|
<Modal ref={nodeRef}>
|
|
<View nativeID="child" style={{height: 10, width: 10}} />
|
|
</Modal>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
const element = ensureInstance(nodeRef.current, ReactNativeElement);
|
|
|
|
Fantom.runOnUIThread(() => {
|
|
Fantom.enqueueModalSizeUpdate(element, {
|
|
width: 100,
|
|
height: 100,
|
|
});
|
|
});
|
|
Fantom.runWorkLoop();
|
|
|
|
const logs = root.takeMountingManagerLogs();
|
|
expect(logs).toContain('Create {type: "View", nativeID: "child"}');
|
|
expect(logs).toContain('Create {type: "View", nativeID: "modal parent"}');
|
|
|
|
// Modal is unmounted. Views that were only mounted because of its existence must be unmounted.
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView style={{height: 100, width: 100}}>
|
|
<View nativeID="modal parent" style={{marginTop: 101}} />
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toContain(
|
|
'Delete {type: "View", nativeID: "modal parent"}',
|
|
);
|
|
});
|
|
|
|
test('modal is mounted in second update', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView style={{height: 100, width: 100}}>
|
|
<View style={{marginTop: 101}} />
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
const nodeRef = createRef<HostInstance>();
|
|
|
|
// Adding modal to view hierarchy.
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView style={{height: 100, width: 100}}>
|
|
<View style={{marginTop: 101}}>
|
|
<Modal ref={nodeRef}>
|
|
<View nativeID="child" style={{height: 10, width: 10}} />
|
|
</Modal>
|
|
</View>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
const element = ensureInstance(nodeRef.current, ReactNativeElement);
|
|
|
|
Fantom.runOnUIThread(() => {
|
|
Fantom.enqueueModalSizeUpdate(element, {
|
|
width: 100,
|
|
height: 100,
|
|
});
|
|
});
|
|
Fantom.runWorkLoop();
|
|
|
|
expect(root.takeMountingManagerLogs()).toContain(
|
|
'Create {type: "View", nativeID: "child"}',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('culling inside ScrollView with overflow visible', () => {
|
|
it('shows view outside of bounds', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView style={{height: 100, width: 100, overflow: 'visible'}}>
|
|
<View
|
|
nativeID={'child'}
|
|
style={{height: 10, width: 10, marginTop: 145}} // 145 is below the viewport
|
|
/>
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
// Child is not culled because overflow:visible.
|
|
expect(root.takeMountingManagerLogs()).toContain(
|
|
'Create {type: "View", nativeID: "child"}',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('horizontal ScrollView in RTL script', () => {
|
|
it('renders item 1', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{direction: 'rtl', height: 100, width: 100}}
|
|
horizontal={true}>
|
|
<View nativeID={'item1'} style={{height: 90, width: 90, margin: 5}} />
|
|
<View nativeID={'item2'} style={{height: 90, width: 90, margin: 5}} />
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "AndroidHorizontalScrollContentView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "item1"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "item1"}',
|
|
'Insert {type: "AndroidHorizontalScrollContentView", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
});
|
|
|
|
it('takes contentOffset into account', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView
|
|
style={{direction: 'rtl', height: 100, width: 100}}
|
|
horizontal={true}
|
|
contentOffset={{x: 100, y: 0}}>
|
|
<View nativeID={'item1'} style={{height: 90, width: 90, margin: 5}} />
|
|
<View nativeID={'item2'} style={{height: 90, width: 90, margin: 5}} />
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "AndroidHorizontalScrollContentView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "item2"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "item2"}',
|
|
'Insert {type: "AndroidHorizontalScrollContentView", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('Views with no layout', () => {
|
|
it('are not culled', () => {
|
|
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
|
|
|
|
Fantom.runTask(() => {
|
|
root.render(
|
|
<ScrollView style={{height: 100, width: 100}}>
|
|
<View nativeID={'viewWithLayout'} style={{height: 100, width: 100}} />
|
|
<View style={{height: 1000, width: 100}} />
|
|
<View
|
|
nativeID={'culledViewWithLayout'}
|
|
style={{height: 100, width: 100}}
|
|
/>
|
|
<View nativeID={'viewWithoutLayout'} style={{height: 0, width: 0}} />
|
|
</ScrollView>,
|
|
);
|
|
});
|
|
|
|
expect(root.takeMountingManagerLogs()).toEqual([
|
|
'Update {type: "RootView", nativeID: (root)}',
|
|
'Create {type: "ScrollView", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: (N/A)}',
|
|
'Create {type: "View", nativeID: "viewWithLayout"}',
|
|
'Create {type: "View", nativeID: "viewWithoutLayout"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "viewWithLayout"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 1, nativeID: "viewWithoutLayout"}',
|
|
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
|
|
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
|
|
]);
|
|
});
|
|
});
|