@symbiote-native/angular 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/symbiote-angular-dev.cjs +20 -19
- package/build/angular/anchor-host-registry.js +2 -2
- package/build/angular/anchor-host-registry.js.map +1 -1
- package/build/angular/components/activity-indicator/index.android.d.ts +2 -1
- package/build/angular/components/activity-indicator/index.android.js +6 -3
- package/build/angular/components/activity-indicator/index.android.js.map +1 -1
- package/build/angular/components/activity-indicator/index.ios.d.ts +2 -1
- package/build/angular/components/activity-indicator/index.ios.js +6 -3
- package/build/angular/components/activity-indicator/index.ios.js.map +1 -1
- package/build/angular/components/activity-indicator/shared.js +3 -3
- package/build/angular/components/button.d.ts +4 -2
- package/build/angular/components/button.js +28 -17
- package/build/angular/components/button.js.map +1 -1
- package/build/angular/components/flat-list/index.d.ts +5 -3
- package/build/angular/components/flat-list/index.js +35 -38
- package/build/angular/components/flat-list/index.js.map +1 -1
- package/build/angular/components/image/index.android.d.ts +8 -3
- package/build/angular/components/image/index.android.js +39 -9
- package/build/angular/components/image/index.android.js.map +1 -1
- package/build/angular/components/image/index.ios.d.ts +8 -3
- package/build/angular/components/image/index.ios.js +39 -9
- package/build/angular/components/image/index.ios.js.map +1 -1
- package/build/angular/components/image/shared.d.ts +4 -1
- package/build/angular/components/image/shared.js +33 -2
- package/build/angular/components/image/shared.js.map +1 -1
- package/build/angular/components/image-background.d.ts +2 -1
- package/build/angular/components/image-background.js +6 -4
- package/build/angular/components/image-background.js.map +1 -1
- package/build/angular/components/input-accessory-view/index.d.ts +9 -4
- package/build/angular/components/input-accessory-view/index.js +43 -9
- package/build/angular/components/input-accessory-view/index.js.map +1 -1
- package/build/angular/components/keyboard-avoiding-view/index.d.ts +2 -1
- package/build/angular/components/keyboard-avoiding-view/index.js +6 -4
- package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -1
- package/build/angular/components/modal/index.d.ts +11 -8
- package/build/angular/components/modal/index.js +45 -28
- package/build/angular/components/modal/index.js.map +1 -1
- package/build/angular/components/pressable/index.d.ts +11 -5
- package/build/angular/components/pressable/index.js +61 -12
- package/build/angular/components/pressable/index.js.map +1 -1
- package/build/angular/components/refresh-control/index.d.ts +8 -4
- package/build/angular/components/refresh-control/index.js +52 -16
- package/build/angular/components/refresh-control/index.js.map +1 -1
- package/build/angular/components/safe-area-view/index.d.ts +9 -4
- package/build/angular/components/safe-area-view/index.js +46 -9
- package/build/angular/components/safe-area-view/index.js.map +1 -1
- package/build/angular/components/scroll-view/index.android.d.ts +2 -3
- package/build/angular/components/scroll-view/index.android.js +49 -52
- package/build/angular/components/scroll-view/index.android.js.map +1 -1
- package/build/angular/components/scroll-view/index.ios.d.ts +2 -3
- package/build/angular/components/scroll-view/index.ios.js +15 -17
- package/build/angular/components/scroll-view/index.ios.js.map +1 -1
- package/build/angular/components/scroll-view/projection.d.ts +3 -0
- package/build/angular/components/scroll-view/projection.js +68 -7
- package/build/angular/components/scroll-view/projection.js.map +1 -1
- package/build/angular/components/scroll-view/shared.d.ts +14 -5
- package/build/angular/components/scroll-view/shared.js +198 -69
- package/build/angular/components/scroll-view/shared.js.map +1 -1
- package/build/angular/components/scroll-view/sticky-header.js +14 -19
- package/build/angular/components/scroll-view/sticky-header.js.map +1 -1
- package/build/angular/components/section-list/index.d.ts +2 -1
- package/build/angular/components/section-list/index.js +31 -39
- package/build/angular/components/section-list/index.js.map +1 -1
- package/build/angular/components/switch/index.android.d.ts +7 -3
- package/build/angular/components/switch/index.android.js +38 -10
- package/build/angular/components/switch/index.android.js.map +1 -1
- package/build/angular/components/switch/index.ios.d.ts +7 -3
- package/build/angular/components/switch/index.ios.js +38 -10
- package/build/angular/components/switch/index.ios.js.map +1 -1
- package/build/angular/components/switch/shared.d.ts +12 -3
- package/build/angular/components/switch/shared.js +66 -5
- package/build/angular/components/switch/shared.js.map +1 -1
- package/build/angular/components/text-input.d.ts +10 -2
- package/build/angular/components/text-input.js +46 -6
- package/build/angular/components/text-input.js.map +1 -1
- package/build/angular/components/touchable/index.d.ts +17 -8
- package/build/angular/components/touchable/index.js +78 -19
- package/build/angular/components/touchable/index.js.map +1 -1
- package/build/angular/components/touchable-native-feedback/index.d.ts +6 -3
- package/build/angular/components/touchable-native-feedback/index.js +61 -25
- package/build/angular/components/touchable-native-feedback/index.js.map +1 -1
- package/build/angular/components/virtualized-list/directives.js +18 -18
- package/build/angular/components/virtualized-list/index.d.ts +12 -4
- package/build/angular/components/virtualized-list/index.js +163 -68
- package/build/angular/components/virtualized-list/index.js.map +1 -1
- package/build/angular/components/virtualized-section-list/directives.js +12 -12
- package/build/angular/components/virtualized-section-list/index.d.ts +2 -1
- package/build/angular/components/virtualized-section-list/index.js +6 -4
- package/build/angular/components/virtualized-section-list/index.js.map +1 -1
- package/build/angular/components.d.ts +1 -1
- package/build/angular/components.js +5 -0
- package/build/angular/components.js.map +1 -1
- package/build/angular/create-portal/index.js +29 -35
- package/build/angular/create-portal/index.js.map +1 -1
- package/build/angular/create-tunnel/index.js +19 -22
- package/build/angular/create-tunnel/index.js.map +1 -1
- package/build/angular/descriptor-to-angular/index.js +3 -3
- package/build/angular/index.d.ts +4 -2
- package/build/angular/index.js +2 -2
- package/build/angular/index.js.map +1 -1
- package/build/angular/modules/animated/animated-leaf-binder.d.ts +1 -7
- package/build/angular/modules/animated/animated-leaf-binder.js +22 -72
- package/build/angular/modules/animated/animated-leaf-binder.js.map +1 -1
- package/build/angular/modules/animated/create-animated-component.d.ts +11 -6
- package/build/angular/modules/animated/create-animated-component.js +83 -50
- package/build/angular/modules/animated/create-animated-component.js.map +1 -1
- package/build/angular/modules/app-registry/index.js.map +1 -1
- package/build/angular/modules/status-bar/index.js +3 -3
- package/build/angular/primitives/index.d.ts +1 -1
- package/build/angular/primitives/index.js +46 -46
- package/build/angular/primitives/index.js.map +1 -1
- package/build/angular/primitives/shared.d.ts +39 -1
- package/build/angular/primitives/shared.js +110 -9
- package/build/angular/primitives/shared.js.map +1 -1
- package/build/angular/render/index.js +23 -35
- package/build/angular/render/index.js.map +1 -1
- package/build/angular/services/color-scheme.service.js +3 -3
- package/build/angular/services/window-dimensions.service.js +3 -3
- package/package.json +13 -8
- package/src/anchor-host-registry.ts +2 -2
- package/src/components/activity-indicator/index.android.ts +2 -0
- package/src/components/activity-indicator/index.ios.ts +2 -0
- package/src/components/button.ts +28 -14
- package/src/components/flat-list/index.ts +34 -36
- package/src/components/image/index.android.ts +50 -6
- package/src/components/image/index.ios.ts +50 -6
- package/src/components/image/shared.ts +34 -2
- package/src/components/image-background.ts +2 -1
- package/src/components/input-accessory-view/index.ts +42 -4
- package/src/components/keyboard-avoiding-view/index.ts +7 -1
- package/src/components/modal/index.ts +51 -26
- package/src/components/pressable/index.ts +67 -8
- package/src/components/refresh-control/index.ts +54 -12
- package/src/components/safe-area-view/index.ts +50 -5
- package/src/components/scroll-view/index.android.ts +34 -41
- package/src/components/scroll-view/index.ios.ts +10 -21
- package/src/components/scroll-view/projection.ts +79 -7
- package/src/components/scroll-view/shared.ts +223 -68
- package/src/components/scroll-view/sticky-header.ts +11 -16
- package/src/components/section-list/index.ts +27 -36
- package/src/components/switch/index.android.ts +48 -7
- package/src/components/switch/index.ios.ts +48 -7
- package/src/components/switch/shared.ts +79 -3
- package/src/components/text-input.ts +52 -2
- package/src/components/touchable/index.ts +76 -16
- package/src/components/touchable-native-feedback/index.ts +62 -21
- package/src/components/virtualized-list/index.ts +137 -44
- package/src/components/virtualized-section-list/index.ts +2 -1
- package/src/components.ts +5 -0
- package/src/create-portal/index.ts +23 -29
- package/src/create-tunnel/index.ts +13 -16
- package/src/index.ts +30 -1
- package/src/modules/animated/animated-leaf-binder.ts +28 -77
- package/src/modules/animated/create-animated-component.ts +75 -36
- package/src/modules/app-registry/index.ts +6 -8
- package/src/primitives/index.ts +1 -0
- package/src/primitives/shared.ts +102 -3
- package/src/render/index.ts +23 -35
- package/build/angular/modules/animated/animated-leaf-binder/index.d.ts +0 -14
- package/build/angular/modules/animated/animated-leaf-binder/index.js +0 -91
- package/build/angular/modules/animated/animated-leaf-binder/index.js.map +0 -1
- package/src/__tests__/responder-change-detection.test.ts +0 -102
- package/src/__tests__/responder-nested-cd.test.ts +0 -113
- package/src/angular-gaps.test.ts +0 -142
- package/src/angular-imports.test.ts +0 -100
- package/src/components/activity-indicator/activity-indicator-class-style.test.ts +0 -44
- package/src/components/activity-indicator/activity-indicator.test.ts +0 -131
- package/src/components/flat-list/column-wrapper-style-class.test.ts +0 -116
- package/src/components/flat-list/flat-list-array-style.test.ts +0 -131
- package/src/components/flat-list/flat-list-class-style.test.ts +0 -74
- package/src/components/flat-list/flat-list-scroll-containment.test.ts +0 -107
- package/src/components/flat-list/flat-list.test.ts +0 -99
- package/src/components/image/image-class-style.test.ts +0 -46
- package/src/components/image-background-class-style.test.ts +0 -48
- package/src/components/image-background-style-class.test.ts +0 -79
- package/src/components/input-accessory-view/input-accessory-view.test.ts +0 -43
- package/src/components/keyboard-avoiding-view/keyboard-avoiding-view.test.ts +0 -54
- package/src/components/modal/modal.test.ts +0 -43
- package/src/components/pressable/pressable.test.ts +0 -85
- package/src/components/refresh-control/refresh-control.test.ts +0 -41
- package/src/components/safe-area-view/safe-area-view.test.ts +0 -43
- package/src/components/scroll-view/android-scroll-view-axis-projection.test.ts +0 -123
- package/src/components/scroll-view/content-container-style-class.test.ts +0 -75
- package/src/components/scroll-view/scroll-view-class-style.test.ts +0 -134
- package/src/components/scroll-view/scroll-view-projection.test.ts +0 -194
- package/src/components/switch/android-switch-class.test.ts +0 -42
- package/src/components/switch/switch.test.ts +0 -92
- package/src/components/text-input-class-style.test.ts +0 -54
- package/src/components/touchable/touchable.test.ts +0 -102
- package/src/components/touchable-native-feedback/touchable-native-feedback.test.ts +0 -49
- package/src/components/virtualized-list/virtualized-list-class-style.test.ts +0 -76
- package/src/components/virtualized-section-list/virtualized-section-list-class-style.test.ts +0 -60
- package/src/create-portal/create-portal.test.ts +0 -90
- package/src/create-tunnel/create-tunnel.test.ts +0 -120
- package/src/descriptor-to-angular/descriptor-outlet.test.ts +0 -94
- package/src/host-instance/host-instance.test.ts +0 -49
- package/src/modules/animated/animated-leaf-binder/animated-leaf-binder.test.ts +0 -116
- package/src/modules/animated/animated-leaf-binder/index.ts +0 -101
- package/src/modules/animated/animated-leaf-binder.test.ts +0 -116
- package/src/modules/animated/animated-scroll-view.test.ts +0 -102
- package/src/modules/app-registry/app-registry.test.ts +0 -106
- package/src/modules/status-bar/status-bar.test.ts +0 -50
- package/src/render/render.test.ts +0 -147
- package/src/renderer/renderer.test.ts +0 -203
- package/src/services/services.test.ts +0 -163
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
// Pure Fabrication (GRASP): the leaf-lifecycle orchestration shared by every Animated*
|
|
2
|
-
// wrapper, extracted out of AnimatedComponentBase so AnimatedImage — which cannot extend
|
|
3
|
-
// AnimatedComponentBase, since it must extend ImageBase for TS's-native image handling —
|
|
4
|
-
// gets the SAME logic via composition instead of copy-pasting it. Framework-thin on
|
|
5
|
-
// purpose: it knows nothing about Angular (@ViewChild, DI, change detection), only the
|
|
6
|
-
// engine's Animated leaf + commit primitives, so it stays testable without any Angular
|
|
7
|
-
// bootstrap. NOTE: react/vue drive this same dance through their own runtime HOC
|
|
8
|
-
// (createAnimatedComponent) rather than a class, so there is no cross-adapter dedup
|
|
9
|
-
// opportunity here yet — this Pure Fabrication is local to the angular adapter.
|
|
10
|
-
import {
|
|
11
|
-
AnimatedProps,
|
|
12
|
-
attachNativeEventHandler,
|
|
13
|
-
dlog,
|
|
14
|
-
whenCommitted,
|
|
15
|
-
type ISymbioteNode,
|
|
16
|
-
} from '@symbiote-native/engine';
|
|
17
|
-
|
|
18
|
-
export class AnimatedLeafBinder {
|
|
19
|
-
// The leaf currently wired into the value graph. Null until the first reconcile.
|
|
20
|
-
private attached: AnimatedProps | null = null;
|
|
21
|
-
// The whenCommitted waiter for the current leaf's node binding, cancelled before the next.
|
|
22
|
-
private cancelBind: (() => void) | undefined;
|
|
23
|
-
// Detachers for any Animated.event prop bound natively to the committed node (JS path: none).
|
|
24
|
-
private eventDetachers: Array<() => void> = [];
|
|
25
|
-
|
|
26
|
-
// `resolveNode` stays a caller-supplied resolver (not a constructor-captured node) so this
|
|
27
|
-
// class never needs to know HOW the host node is found — AnimatedComponentBase and
|
|
28
|
-
// AnimatedImage both resolve it the same way (resolveHostNode + isSymbioteNode over their
|
|
29
|
-
// own @ViewChild), but that's Angular DI state this binder has no business touching.
|
|
30
|
-
// `label` is purely diagnostic (dlog), mirroring the constructor.name the pre-extraction
|
|
31
|
-
// dlog call used.
|
|
32
|
-
constructor(
|
|
33
|
-
private readonly resolveNode: () => ISymbioteNode | null,
|
|
34
|
-
private readonly label: string,
|
|
35
|
-
) {}
|
|
36
|
-
|
|
37
|
-
// Build a fresh AnimatedProps leaf from the current props (the React/Vue per-render
|
|
38
|
-
// useMemo(rest)), wire it into the graph, and swap the previous one out. Attach the NEW leaf
|
|
39
|
-
// BEFORE detaching the OLD one: a shared Value self-detaches (dropping its native node) the
|
|
40
|
-
// instant its child count hits zero, so detaching first would kill a running native animation
|
|
41
|
-
// on any unrelated re-render (mirrors RN's AnimatedComponent._attachProps). Then bind the
|
|
42
|
-
// committed node, go native if wanted, and rebind native events.
|
|
43
|
-
reconcile(props: Record<string, unknown>, wantsNative: boolean): void {
|
|
44
|
-
dlog(`${this.label} reconcile`);
|
|
45
|
-
const newLeaf = new AnimatedProps(props);
|
|
46
|
-
newLeaf.__attach();
|
|
47
|
-
if (this.attached !== null && this.attached !== newLeaf) this.attached.__detach();
|
|
48
|
-
this.attached = newLeaf;
|
|
49
|
-
|
|
50
|
-
this.bindNode(newLeaf, props, wantsNative);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Bind the leaf to the host's Fabric node THROUGH whenCommitted: under Angular's zoneless
|
|
54
|
-
// batched change detection the inner host's tag does not exist yet at ngAfterViewInit time
|
|
55
|
-
// (the same async-commit gotcha Vue documents), so binding eagerly would no-op. whenCommitted
|
|
56
|
-
// runs the action now if already committed, else after the commit that assigns the tag.
|
|
57
|
-
private bindNode(
|
|
58
|
-
leaf: AnimatedProps,
|
|
59
|
-
props: Record<string, unknown>,
|
|
60
|
-
wantsNative: boolean,
|
|
61
|
-
): void {
|
|
62
|
-
this.cancelBind?.();
|
|
63
|
-
this.cancelBind = undefined;
|
|
64
|
-
const node = this.resolveNode();
|
|
65
|
-
if (node === null) return;
|
|
66
|
-
this.cancelBind = whenCommitted(node, () => {
|
|
67
|
-
leaf.setNativeView(node);
|
|
68
|
-
if (wantsNative) leaf.__makeNative();
|
|
69
|
-
this.attachEvents(node, props);
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Native-attach any Animated.event prop (e.g. onScroll={Animated.event(…,{useNativeDriver:true})})
|
|
74
|
-
// to the committed node. attachNativeEventHandler no-ops (returns undefined) unless the prop is a
|
|
75
|
-
// native event handler with a committed tag, so the JS path stays the fallback. Rebound each
|
|
76
|
-
// reconcile so a new inline event re-attaches; detached first to avoid leaking the prior binding.
|
|
77
|
-
private attachEvents(node: ISymbioteNode, props: Record<string, unknown>): void {
|
|
78
|
-
this.detachEvents();
|
|
79
|
-
for (const key of Object.keys(props)) {
|
|
80
|
-
const attachment = attachNativeEventHandler(node, key, props[key]);
|
|
81
|
-
if (attachment !== undefined) this.eventDetachers.push(attachment.detach);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
private detachEvents(): void {
|
|
86
|
-
for (const detach of this.eventDetachers) detach();
|
|
87
|
-
this.eventDetachers = [];
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Tear down everything: cancel a still-pending bind, drop native event listeners, detach the
|
|
91
|
-
// current leaf from the value graph. Safe to call even if reconcile() was never called.
|
|
92
|
-
destroy(): void {
|
|
93
|
-
this.cancelBind?.();
|
|
94
|
-
this.cancelBind = undefined;
|
|
95
|
-
this.detachEvents();
|
|
96
|
-
if (this.attached !== null) {
|
|
97
|
-
this.attached.__detach();
|
|
98
|
-
this.attached = null;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
// Unit tests for AnimatedLeafBinder in isolation — no Angular involved. Both
|
|
2
|
-
// AnimatedComponentBase and AnimatedImage delegate their leaf-lifecycle (build a leaf,
|
|
3
|
-
// bind it to the committed Fabric node, swap leaves on re-render, tear down) to this Pure
|
|
4
|
-
// Fabrication; these tests exercise the binder directly against a real engine node
|
|
5
|
-
// (SymbioteSurface + the fake Fabric backend), so a break here isolates to the shared
|
|
6
|
-
// orchestration rather than either owning component.
|
|
7
|
-
import { beforeEach, describe, expect, it } from 'vitest';
|
|
8
|
-
import { installFabric } from '@symbiote-native/test-utils';
|
|
9
|
-
import {
|
|
10
|
-
AnimatedValue,
|
|
11
|
-
createElement,
|
|
12
|
-
createSurface,
|
|
13
|
-
type ISymbioteNode,
|
|
14
|
-
} from '@symbiote-native/engine';
|
|
15
|
-
import { AnimatedLeafBinder } from './animated-leaf-binder';
|
|
16
|
-
|
|
17
|
-
const fabric = installFabric();
|
|
18
|
-
let nextRootTag = 9001;
|
|
19
|
-
|
|
20
|
-
beforeEach(() => fabric.reset());
|
|
21
|
-
|
|
22
|
-
// A node that has already gone through a commit — whenCommitted fires synchronously.
|
|
23
|
-
function committedNode(): ISymbioteNode {
|
|
24
|
-
const surface = createSurface(nextRootTag++);
|
|
25
|
-
const node = createElement('RCTView');
|
|
26
|
-
surface.appendChild(node);
|
|
27
|
-
surface.commit();
|
|
28
|
-
return node;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// A node that is attached to a surface but has NOT been committed yet — whenCommitted
|
|
32
|
-
// must defer until the caller commits the surface.
|
|
33
|
-
function uncommittedNode(): { node: ISymbioteNode; commit: () => void } {
|
|
34
|
-
const surface = createSurface(nextRootTag++);
|
|
35
|
-
const node = createElement('RCTView');
|
|
36
|
-
surface.appendChild(node);
|
|
37
|
-
return { node, commit: () => surface.commit() };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// `appRoot()` unwraps RN's synthetic box-none AppContainer, which is ALSO an RCTView, so a
|
|
41
|
-
// plain `fabric.find(viewName === 'RCTView')` would match it instead of the node under test.
|
|
42
|
-
// Each test appends exactly one child, so the container's first (only) child is the real node.
|
|
43
|
-
function fakeView(): ReturnType<typeof fabric.find> {
|
|
44
|
-
return fabric.appRoot().children[0];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
describe('AnimatedLeafBinder', () => {
|
|
48
|
-
it('creates and attaches an AnimatedProps leaf, wiring value changes onto the host node', () => {
|
|
49
|
-
const node = committedNode();
|
|
50
|
-
const binder = new AnimatedLeafBinder(() => node, 'test');
|
|
51
|
-
const opacity = new AnimatedValue(1);
|
|
52
|
-
|
|
53
|
-
// The leaf itself never pushes the INITIAL value — that first paint comes from the
|
|
54
|
-
// owning component's own reduceProps template binding (out of scope here). The leaf
|
|
55
|
-
// only drives value CHANGES after it is bound.
|
|
56
|
-
binder.reconcile({ style: { opacity } }, false);
|
|
57
|
-
|
|
58
|
-
opacity.setValue(0.4);
|
|
59
|
-
expect(fakeView()?.props.opacity).toBe(0.4);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('binds to the host node via whenCommitted, deferring until the node is actually committed', () => {
|
|
63
|
-
const { node, commit } = uncommittedNode();
|
|
64
|
-
const binder = new AnimatedLeafBinder(() => node, 'test');
|
|
65
|
-
const opacity = new AnimatedValue(1);
|
|
66
|
-
|
|
67
|
-
binder.reconcile({ style: { opacity } }, false);
|
|
68
|
-
// Not committed yet: the value graph exists, but no Fabric node to flush onto.
|
|
69
|
-
opacity.setValue(0.5);
|
|
70
|
-
expect(fabric.created).toHaveLength(0);
|
|
71
|
-
|
|
72
|
-
commit();
|
|
73
|
-
// whenCommitted's post-commit retry binds the leaf now; a subsequent change flushes.
|
|
74
|
-
opacity.setValue(0.7);
|
|
75
|
-
expect(fakeView()?.props.opacity).toBe(0.7);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('attaches the new leaf before detaching the old one on a second reconcile', () => {
|
|
79
|
-
const node = committedNode();
|
|
80
|
-
const binder = new AnimatedLeafBinder(() => node, 'test');
|
|
81
|
-
const shared = new AnimatedValue(1);
|
|
82
|
-
const order: string[] = [];
|
|
83
|
-
const originalAddChild = shared.__addChild.bind(shared);
|
|
84
|
-
const originalRemoveChild = shared.__removeChild.bind(shared);
|
|
85
|
-
shared.__addChild = child => {
|
|
86
|
-
order.push('add');
|
|
87
|
-
originalAddChild(child);
|
|
88
|
-
};
|
|
89
|
-
shared.__removeChild = child => {
|
|
90
|
-
order.push('remove');
|
|
91
|
-
originalRemoveChild(child);
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
binder.reconcile({ style: { opacity: shared } }, false);
|
|
95
|
-
order.length = 0; // only care about the SECOND reconcile's swap order
|
|
96
|
-
|
|
97
|
-
binder.reconcile({ style: { opacity: shared } }, false);
|
|
98
|
-
|
|
99
|
-
expect(order).toEqual(['add', 'remove']);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('detaches the leaf and cancels any pending bind on destroy', () => {
|
|
103
|
-
const { node, commit } = uncommittedNode();
|
|
104
|
-
const binder = new AnimatedLeafBinder(() => node, 'test');
|
|
105
|
-
const opacity = new AnimatedValue(1);
|
|
106
|
-
|
|
107
|
-
binder.reconcile({ style: { opacity } }, false);
|
|
108
|
-
binder.destroy();
|
|
109
|
-
commit();
|
|
110
|
-
|
|
111
|
-
// The pending whenCommitted bind was cancelled by destroy, so committing afterward
|
|
112
|
-
// must not retroactively bind the (already-detached) leaf.
|
|
113
|
-
opacity.setValue(0.9);
|
|
114
|
-
expect(fakeView()?.props.opacity).toBeUndefined();
|
|
115
|
-
});
|
|
116
|
-
});
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
// Regression tests for two Android-only bugs AnimatedScrollView's bespoke template hit because
|
|
2
|
-
// it talks to the raw symbiote-scroll-view primitive directly instead of reusing the real
|
|
3
|
-
// ScrollView component (which already has both fixes — see scroll-view/shared.ts):
|
|
4
|
-
//
|
|
5
|
-
// 1. It projected <ng-content> straight into symbiote-scroll-view with no content wrapper. On
|
|
6
|
-
// Android, symbiote-scroll-content resolves to a plain RCTView, which Fabric view-flattens
|
|
7
|
-
// away unless collapsable:false pins it — so multiple projected children were hoisted up as
|
|
8
|
-
// direct children of the scroll view, which natively hosts exactly one ("ScrollView can
|
|
9
|
-
// host only one direct child" -> addViewAt crash).
|
|
10
|
-
// 2. It never defaulted nestedScrollEnabled. RN defaults nested scrolling ON (ScrollView.js
|
|
11
|
-
// `nestedScrollEnabled ?? true`), but Android needs it explicit or a ScrollView nested
|
|
12
|
-
// inside another scrollable container (this canary's own nested demo) never receives touch
|
|
13
|
-
// — it renders fine but is static, the outer ScrollView swallows the gesture.
|
|
14
|
-
//
|
|
15
|
-
// The fake Fabric harness doesn't simulate Android's real view-flattening or touch dispatch, so
|
|
16
|
-
// these tests only prove the structural/prop invariants that prevent both bugs, not the
|
|
17
|
-
// on-device symptoms themselves — those still need a real Android host.
|
|
18
|
-
import '@angular/compiler';
|
|
19
|
-
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
20
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
21
|
-
import { installFabric } from '@symbiote-native/test-utils';
|
|
22
|
-
import { mount, unmount } from '../../render';
|
|
23
|
-
import { AnimatedScrollView } from './create-animated-component';
|
|
24
|
-
|
|
25
|
-
const ROOT_TAG = 927;
|
|
26
|
-
const OVERRIDE_ROOT_TAG = 928;
|
|
27
|
-
const fabric = installFabric();
|
|
28
|
-
const tick = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
|
|
29
|
-
|
|
30
|
-
class AnimatedScrollViewApp {}
|
|
31
|
-
Component({
|
|
32
|
-
selector: 'animated-scroll-view-test',
|
|
33
|
-
standalone: true,
|
|
34
|
-
imports: [AnimatedScrollView],
|
|
35
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
36
|
-
template: `
|
|
37
|
-
<AnimatedScrollView>
|
|
38
|
-
<symbiote-view testID="a"></symbiote-view>
|
|
39
|
-
<symbiote-view testID="b"></symbiote-view>
|
|
40
|
-
</AnimatedScrollView>
|
|
41
|
-
`,
|
|
42
|
-
})(AnimatedScrollViewApp);
|
|
43
|
-
|
|
44
|
-
class AnimatedScrollViewOverrideApp {}
|
|
45
|
-
Component({
|
|
46
|
-
selector: 'animated-scroll-view-override-test',
|
|
47
|
-
standalone: true,
|
|
48
|
-
imports: [AnimatedScrollView],
|
|
49
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
50
|
-
template: `
|
|
51
|
-
<AnimatedScrollView [animatedProps]="{ nestedScrollEnabled: false }">
|
|
52
|
-
<symbiote-view testID="a"></symbiote-view>
|
|
53
|
-
</AnimatedScrollView>
|
|
54
|
-
`,
|
|
55
|
-
})(AnimatedScrollViewOverrideApp);
|
|
56
|
-
|
|
57
|
-
beforeEach(() => fabric.reset());
|
|
58
|
-
afterEach(() => {
|
|
59
|
-
unmount(ROOT_TAG);
|
|
60
|
-
unmount(OVERRIDE_ROOT_TAG);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
describe('AnimatedScrollView', () => {
|
|
64
|
-
it('wraps multiple projected children in a single non-collapsable content view', async () => {
|
|
65
|
-
mount(ROOT_TAG, AnimatedScrollViewApp);
|
|
66
|
-
await tick();
|
|
67
|
-
|
|
68
|
-
const scrollView = fabric.find(node => node.viewName === 'RCTScrollView');
|
|
69
|
-
expect(scrollView?.children).toHaveLength(1);
|
|
70
|
-
expect(scrollView?.props.nestedScrollEnabled).toBe(true);
|
|
71
|
-
|
|
72
|
-
const content = scrollView?.children[0];
|
|
73
|
-
expect(content?.viewName).toBe('RCTScrollContentView');
|
|
74
|
-
expect(content?.props.collapsable).toBe(false);
|
|
75
|
-
expect(content?.children.map(child => child.props.testID)).toEqual(['a', 'b']);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// Regression test for a THIRD bug in this same bespoke-template class, this one iOS-only (the
|
|
79
|
-
// inverse of the two Android bugs above): AnimatedScrollView never applied
|
|
80
|
-
// selectScrollIntrinsics' scrollViewBaseStyle (overflow: 'scroll') to its host node, unlike the
|
|
81
|
-
// real ScrollView component. On iOS Fabric a scroll view only clips its content to its own
|
|
82
|
-
// frame when `overflow: 'scroll'` is set; without it, content taller than the frame bleeds out
|
|
83
|
-
// over sibling views instead of scrolling clipped (Android's native ViewGroup clips regardless
|
|
84
|
-
// of the style prop, which is why this was invisible there). See
|
|
85
|
-
// core/components/src/view/render-scroll-view.ts's SCROLL_VIEW_BASE_VERTICAL comment.
|
|
86
|
-
it('applies the scroll-view base style (overflow: scroll) so content clips to the frame', async () => {
|
|
87
|
-
mount(ROOT_TAG, AnimatedScrollViewApp);
|
|
88
|
-
await tick();
|
|
89
|
-
|
|
90
|
-
const scrollView = fabric.find(node => node.viewName === 'RCTScrollView');
|
|
91
|
-
expect(scrollView?.props.overflow).toBe('scroll');
|
|
92
|
-
expect(scrollView?.props.flexDirection).toBe('column');
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('lets an explicit nestedScrollEnabled in animatedProps override the default', async () => {
|
|
96
|
-
mount(OVERRIDE_ROOT_TAG, AnimatedScrollViewOverrideApp);
|
|
97
|
-
await tick();
|
|
98
|
-
|
|
99
|
-
const scrollView = fabric.find(node => node.viewName === 'RCTScrollView');
|
|
100
|
-
expect(scrollView?.props.nestedScrollEnabled).toBe(false);
|
|
101
|
-
});
|
|
102
|
-
});
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
// Co-located Angular-driven test, Angular twin of adapters/react/src/modules/
|
|
2
|
-
// app-registry/app-registry.test.tsx. Proves the AppRegistry entry point:
|
|
3
|
-
// `registerComponent(appKey, () => App)` stores a runnable that calls `mount` (driving
|
|
4
|
-
// @symbiote-native/engine) AND bridges it to the host registrar (RN's own AppRegistry, injected via
|
|
5
|
-
// `setHostRegistrar`) so native can find it by key. Also proves the Angular-specific
|
|
6
|
-
// `setWrapperComponentProvider` seam: a pre-authored standalone wrapper receives the root's
|
|
7
|
-
// host node as projected `<ng-content>`, the AOT-safe twin of React's
|
|
8
|
-
// createElement(Wrapper, null, rootElement).
|
|
9
|
-
|
|
10
|
-
import '@angular/compiler';
|
|
11
|
-
import { Component, Input, type Type } from '@angular/core';
|
|
12
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
13
|
-
import { installFabric } from '@symbiote-native/test-utils';
|
|
14
|
-
import { AppRegistry, setHostRegistrar, type IAppParameters, type IRunnable } from '../..';
|
|
15
|
-
import { unmount } from '../../render';
|
|
16
|
-
|
|
17
|
-
const APP_KEY = 'canary';
|
|
18
|
-
const ROOT_TAG = 212;
|
|
19
|
-
const WRAPPED_APP_KEY = 'canary-wrapped';
|
|
20
|
-
const WRAPPED_ROOT_TAG = 213;
|
|
21
|
-
|
|
22
|
-
// Angular's mount() batches change detection on a microtask, so a committed native tag isn't
|
|
23
|
-
// available synchronously — assertions on the committed tree need one tick, same as Vue.
|
|
24
|
-
const tick = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
|
|
25
|
-
|
|
26
|
-
class TestText {}
|
|
27
|
-
Component({
|
|
28
|
-
selector: 'symbiote-text',
|
|
29
|
-
standalone: true,
|
|
30
|
-
template: '<ng-content></ng-content>',
|
|
31
|
-
})(TestText);
|
|
32
|
-
|
|
33
|
-
class SmokeComponent {}
|
|
34
|
-
Component({
|
|
35
|
-
selector: 'symbiote-app-registry-smoke',
|
|
36
|
-
standalone: true,
|
|
37
|
-
imports: [TestText],
|
|
38
|
-
template: '<symbiote-text>hi</symbiote-text>',
|
|
39
|
-
})(SmokeComponent);
|
|
40
|
-
|
|
41
|
-
class WrapperComponent {
|
|
42
|
-
@Input() label = '';
|
|
43
|
-
}
|
|
44
|
-
Component({
|
|
45
|
-
selector: 'symbiote-app-registry-wrapper',
|
|
46
|
-
standalone: true,
|
|
47
|
-
imports: [TestText],
|
|
48
|
-
template: '<symbiote-text>{{ label }}</symbiote-text><ng-content></ng-content>',
|
|
49
|
-
})(WrapperComponent);
|
|
50
|
-
|
|
51
|
-
const fabric = installFabric();
|
|
52
|
-
|
|
53
|
-
// The host registrar the native side drives (RN's AppRegistry stand-in).
|
|
54
|
-
const hostRunnables = new Map<string, IRunnable>();
|
|
55
|
-
|
|
56
|
-
beforeEach(() => {
|
|
57
|
-
fabric.reset();
|
|
58
|
-
hostRunnables.clear();
|
|
59
|
-
setHostRegistrar({
|
|
60
|
-
registerRunnable: (appKey: string, run: IRunnable): string => {
|
|
61
|
-
hostRunnables.set(appKey, run);
|
|
62
|
-
return appKey;
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
AppRegistry.registerComponent(APP_KEY, () => SmokeComponent);
|
|
66
|
-
});
|
|
67
|
-
afterEach(() => {
|
|
68
|
-
unmount(ROOT_TAG);
|
|
69
|
-
unmount(WRAPPED_ROOT_TAG);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
describe('AppRegistry', () => {
|
|
73
|
-
it('exposes the app key and bridges the runnable to the host registrar', () => {
|
|
74
|
-
expect(AppRegistry.getAppKeys()).toContain(APP_KEY);
|
|
75
|
-
expect(hostRunnables.get(APP_KEY)).toBeDefined();
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('mounts the tree when the host invokes the runnable with a rootTag', async () => {
|
|
79
|
-
const hostRun = hostRunnables.get(APP_KEY);
|
|
80
|
-
expect(hostRun).toBeDefined();
|
|
81
|
-
|
|
82
|
-
const nativeParams: IAppParameters = { rootTag: ROOT_TAG };
|
|
83
|
-
hostRun!(nativeParams);
|
|
84
|
-
await tick();
|
|
85
|
-
|
|
86
|
-
expect(fabric.find(n => n.viewName === 'RCTText')).toBeDefined();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('runApplication drives the same runnable locally', async () => {
|
|
90
|
-
AppRegistry.runApplication(APP_KEY, { rootTag: ROOT_TAG });
|
|
91
|
-
await tick();
|
|
92
|
-
|
|
93
|
-
expect(fabric.find(n => n.viewName === 'RCTText')).toBeDefined();
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('projects the root component into a registered wrapper via <ng-content>', async () => {
|
|
97
|
-
AppRegistry.setWrapperComponentProvider(() => WrapperComponent as Type<unknown>);
|
|
98
|
-
AppRegistry.registerComponent(WRAPPED_APP_KEY, () => SmokeComponent);
|
|
99
|
-
|
|
100
|
-
AppRegistry.runApplication(WRAPPED_APP_KEY, { rootTag: WRAPPED_ROOT_TAG });
|
|
101
|
-
await tick();
|
|
102
|
-
|
|
103
|
-
const texts = fabric.appRoot().children.flatMap(n => fabric.serialize([n]));
|
|
104
|
-
expect(texts.join('')).toContain('hi');
|
|
105
|
-
});
|
|
106
|
-
});
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import '@angular/compiler';
|
|
2
|
-
import { Component } from '@angular/core';
|
|
3
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
-
import * as engine from '@symbiote-native/engine';
|
|
5
|
-
import { installFabric } from '@symbiote-native/test-utils';
|
|
6
|
-
|
|
7
|
-
import { mount, unmount } from '../../render';
|
|
8
|
-
import { StatusBar } from './index';
|
|
9
|
-
|
|
10
|
-
const ROOT_TAG = 901;
|
|
11
|
-
const fabric = installFabric();
|
|
12
|
-
|
|
13
|
-
@Component({
|
|
14
|
-
selector: 'symbiote-status-bar-host',
|
|
15
|
-
standalone: true,
|
|
16
|
-
imports: [StatusBar],
|
|
17
|
-
template: ` <StatusBar [barStyle]="'dark-content'" [hidden]="true" [animated]="false" /> `,
|
|
18
|
-
})
|
|
19
|
-
class StatusBarHost {}
|
|
20
|
-
|
|
21
|
-
beforeEach(() => fabric.reset());
|
|
22
|
-
afterEach(() => unmount(ROOT_TAG));
|
|
23
|
-
|
|
24
|
-
describe('StatusBar', () => {
|
|
25
|
-
it('applies status bar props on mount and renders no Fabric node', async () => {
|
|
26
|
-
const spy = vi.spyOn(engine, 'applyStatusBarProps').mockReturnValue(undefined);
|
|
27
|
-
|
|
28
|
-
mount(ROOT_TAG, StatusBarHost);
|
|
29
|
-
await new Promise<void>(resolve => setTimeout(resolve, 0));
|
|
30
|
-
|
|
31
|
-
expect(spy).toHaveBeenCalledWith(
|
|
32
|
-
expect.objectContaining({
|
|
33
|
-
barStyle: 'dark-content',
|
|
34
|
-
hidden: true,
|
|
35
|
-
animated: false,
|
|
36
|
-
}),
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
const root = fabric.appRoot();
|
|
40
|
-
expect(root.children).toHaveLength(0);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('exposes imperative statics on the component value', () => {
|
|
44
|
-
expect(typeof StatusBar.setHidden).toBe('function');
|
|
45
|
-
expect(typeof StatusBar.setBarStyle).toBe('function');
|
|
46
|
-
expect(typeof StatusBar.setNetworkActivityIndicatorVisible).toBe('function');
|
|
47
|
-
expect(typeof StatusBar.setBackgroundColor).toBe('function');
|
|
48
|
-
expect(typeof StatusBar.setTranslucent).toBe('function');
|
|
49
|
-
});
|
|
50
|
-
});
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
// Integration coverage: run the real Angular runtime (createComponent + RendererFactory2)
|
|
2
|
-
// over the headless fake Fabric slot. Unlike renderer.test.ts, this proves mount() wires
|
|
3
|
-
// Angular's bootstrap to the Symbiote renderer, so a standalone template paints and updates
|
|
4
|
-
// through the engine.
|
|
5
|
-
|
|
6
|
-
import '@angular/compiler';
|
|
7
|
-
import { Component } from '@angular/core';
|
|
8
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
9
|
-
import { installFabric } from '@symbiote-native/test-utils';
|
|
10
|
-
import { mount, unmount } from './index';
|
|
11
|
-
|
|
12
|
-
const ROOT_TAG = 808;
|
|
13
|
-
|
|
14
|
-
const fabric = installFabric();
|
|
15
|
-
const tick = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
|
|
16
|
-
const drainAngularAndCommit = async (): Promise<void> => {
|
|
17
|
-
await tick();
|
|
18
|
-
await tick();
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
class TestView {}
|
|
22
|
-
Component({
|
|
23
|
-
selector: 'symbiote-view',
|
|
24
|
-
standalone: true,
|
|
25
|
-
template: '<ng-content></ng-content>',
|
|
26
|
-
})(TestView);
|
|
27
|
-
|
|
28
|
-
class TestText {}
|
|
29
|
-
Component({
|
|
30
|
-
selector: 'symbiote-text',
|
|
31
|
-
standalone: true,
|
|
32
|
-
template: '<ng-content></ng-content>',
|
|
33
|
-
})(TestText);
|
|
34
|
-
|
|
35
|
-
class SmokeComponent {
|
|
36
|
-
name = 'Angular';
|
|
37
|
-
count = 0;
|
|
38
|
-
boxStyle = { padding: 12 };
|
|
39
|
-
|
|
40
|
-
increment(): void {
|
|
41
|
-
this.count += 1;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
Component({
|
|
46
|
-
selector: 'symbiote-angular-smoke',
|
|
47
|
-
standalone: true,
|
|
48
|
-
imports: [TestView, TestText],
|
|
49
|
-
template: `<symbiote-view [style]="boxStyle"><symbiote-text>Hello {{ name }}</symbiote-text><symbiote-view testID="counter" (press)="increment()"><symbiote-text>tapped {{ count }}×</symbiote-text></symbiote-view></symbiote-view>`,
|
|
50
|
-
})(SmokeComponent);
|
|
51
|
-
|
|
52
|
-
beforeEach(() => fabric.reset());
|
|
53
|
-
afterEach(() => unmount(ROOT_TAG));
|
|
54
|
-
|
|
55
|
-
// A plain (non-OnPush) child component compiles as SignalView in Angular 20+, so an UNTOUCHED
|
|
56
|
-
// sibling child is skipped by the per-view CheckAlways/Dirty/RefreshView gate inside
|
|
57
|
-
// detectChangesInView — true with or without ApplicationRef (the gate lives inside
|
|
58
|
-
// detectChangesInView regardless of who calls it). This is NOT a claim that the root's own
|
|
59
|
-
// template is spared: markViewDirty (used by both native (event) bindings and
|
|
60
|
-
// ChangeDetectorRef.markForCheck(), see SymbioteHostPropsDirective) unconditionally sets
|
|
61
|
-
// RefreshView on every ancestor up to the root — a press anywhere always re-runs the root's
|
|
62
|
-
// own template, in every Angular app, regardless of scheduler. What this guards is narrower
|
|
63
|
-
// but still real: a SIBLING component with no dirty descendant of its own must not be dragged
|
|
64
|
-
// along for the ride.
|
|
65
|
-
let unrelatedRenderCount = 0;
|
|
66
|
-
class CounterChild {
|
|
67
|
-
count = 0;
|
|
68
|
-
|
|
69
|
-
increment(): void {
|
|
70
|
-
this.count += 1;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
Component({
|
|
74
|
-
selector: 'counter-child',
|
|
75
|
-
standalone: true,
|
|
76
|
-
imports: [TestView, TestText],
|
|
77
|
-
template:
|
|
78
|
-
'<symbiote-view testID="counter" (press)="increment()"><symbiote-text>tapped {{ count }}×</symbiote-text></symbiote-view>',
|
|
79
|
-
})(CounterChild);
|
|
80
|
-
|
|
81
|
-
class UnrelatedSiblingChild {
|
|
82
|
-
trackRender(): number {
|
|
83
|
-
unrelatedRenderCount++;
|
|
84
|
-
return unrelatedRenderCount;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
Component({
|
|
88
|
-
selector: 'unrelated-sibling-child',
|
|
89
|
-
standalone: true,
|
|
90
|
-
template: '<symbiote-text>unrelated {{ trackRender() }}</symbiote-text>',
|
|
91
|
-
})(UnrelatedSiblingChild);
|
|
92
|
-
|
|
93
|
-
class TargetedComponent {}
|
|
94
|
-
Component({
|
|
95
|
-
selector: 'symbiote-angular-targeted',
|
|
96
|
-
standalone: true,
|
|
97
|
-
imports: [TestView, CounterChild, UnrelatedSiblingChild],
|
|
98
|
-
template: `<symbiote-view><counter-child /><unrelated-sibling-child /></symbiote-view>`,
|
|
99
|
-
})(TargetedComponent);
|
|
100
|
-
|
|
101
|
-
describe('Angular mount', () => {
|
|
102
|
-
it('bootstraps a standalone component into a committed Fabric tree', async () => {
|
|
103
|
-
mount(ROOT_TAG, SmokeComponent);
|
|
104
|
-
await tick();
|
|
105
|
-
|
|
106
|
-
const root = fabric.appRoot();
|
|
107
|
-
expect(fabric.serialize(root.children)).toBe(
|
|
108
|
-
'RCTView(RCTText(RCTRawText "Hello Angular")RCTView(RCTText(RCTRawText "tapped 0×")))',
|
|
109
|
-
);
|
|
110
|
-
expect(root.children[0]?.props).toMatchObject({ padding: 12 });
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('runs Angular change detection after a native press and recommits text', async () => {
|
|
114
|
-
mount(ROOT_TAG, SmokeComponent);
|
|
115
|
-
await tick();
|
|
116
|
-
|
|
117
|
-
const counter = fabric.find(node => node.props.testID === 'counter');
|
|
118
|
-
expect(counter).toBeDefined();
|
|
119
|
-
|
|
120
|
-
fabric.fireEvent(counter?.instanceHandle, 'topTouchStart');
|
|
121
|
-
fabric.fireEvent(counter?.instanceHandle, 'topTouchEnd');
|
|
122
|
-
await drainAngularAndCommit();
|
|
123
|
-
|
|
124
|
-
expect(fabric.serialize(fabric.appRoot().children)).toContain('RCTRawText "tapped 1×"');
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('does not re-check a sibling child component on a press inside a different child', async () => {
|
|
128
|
-
unrelatedRenderCount = 0;
|
|
129
|
-
mount(ROOT_TAG, TargetedComponent);
|
|
130
|
-
await tick();
|
|
131
|
-
|
|
132
|
-
const afterFirstPaint = unrelatedRenderCount;
|
|
133
|
-
expect(afterFirstPaint).toBeGreaterThan(0);
|
|
134
|
-
|
|
135
|
-
const counter = fabric.find(node => node.props.testID === 'counter');
|
|
136
|
-
fabric.fireEvent(counter?.instanceHandle, 'topTouchStart');
|
|
137
|
-
fabric.fireEvent(counter?.instanceHandle, 'topTouchEnd');
|
|
138
|
-
await drainAngularAndCommit();
|
|
139
|
-
|
|
140
|
-
expect(fabric.serialize(fabric.appRoot().children)).toContain('RCTRawText "tapped 1×"');
|
|
141
|
-
// UnrelatedSiblingChild has no dirty descendant of its own, so it must not be re-checked
|
|
142
|
-
// just because CounterChild (a completely separate branch) got pressed. This does NOT prove
|
|
143
|
-
// the root's own template stays untouched — the root's template still re-runs on every
|
|
144
|
-
// press (see render.ts).
|
|
145
|
-
expect(unrelatedRenderCount).toBe(afterFirstPaint);
|
|
146
|
-
});
|
|
147
|
-
});
|