@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.
Files changed (206) hide show
  1. package/README.md +1 -1
  2. package/bin/symbiote-angular-dev.cjs +20 -19
  3. package/build/angular/anchor-host-registry.js +2 -2
  4. package/build/angular/anchor-host-registry.js.map +1 -1
  5. package/build/angular/components/activity-indicator/index.android.d.ts +2 -1
  6. package/build/angular/components/activity-indicator/index.android.js +6 -3
  7. package/build/angular/components/activity-indicator/index.android.js.map +1 -1
  8. package/build/angular/components/activity-indicator/index.ios.d.ts +2 -1
  9. package/build/angular/components/activity-indicator/index.ios.js +6 -3
  10. package/build/angular/components/activity-indicator/index.ios.js.map +1 -1
  11. package/build/angular/components/activity-indicator/shared.js +3 -3
  12. package/build/angular/components/button.d.ts +4 -2
  13. package/build/angular/components/button.js +28 -17
  14. package/build/angular/components/button.js.map +1 -1
  15. package/build/angular/components/flat-list/index.d.ts +5 -3
  16. package/build/angular/components/flat-list/index.js +35 -38
  17. package/build/angular/components/flat-list/index.js.map +1 -1
  18. package/build/angular/components/image/index.android.d.ts +8 -3
  19. package/build/angular/components/image/index.android.js +39 -9
  20. package/build/angular/components/image/index.android.js.map +1 -1
  21. package/build/angular/components/image/index.ios.d.ts +8 -3
  22. package/build/angular/components/image/index.ios.js +39 -9
  23. package/build/angular/components/image/index.ios.js.map +1 -1
  24. package/build/angular/components/image/shared.d.ts +4 -1
  25. package/build/angular/components/image/shared.js +33 -2
  26. package/build/angular/components/image/shared.js.map +1 -1
  27. package/build/angular/components/image-background.d.ts +2 -1
  28. package/build/angular/components/image-background.js +6 -4
  29. package/build/angular/components/image-background.js.map +1 -1
  30. package/build/angular/components/input-accessory-view/index.d.ts +9 -4
  31. package/build/angular/components/input-accessory-view/index.js +43 -9
  32. package/build/angular/components/input-accessory-view/index.js.map +1 -1
  33. package/build/angular/components/keyboard-avoiding-view/index.d.ts +2 -1
  34. package/build/angular/components/keyboard-avoiding-view/index.js +6 -4
  35. package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -1
  36. package/build/angular/components/modal/index.d.ts +11 -8
  37. package/build/angular/components/modal/index.js +45 -28
  38. package/build/angular/components/modal/index.js.map +1 -1
  39. package/build/angular/components/pressable/index.d.ts +11 -5
  40. package/build/angular/components/pressable/index.js +61 -12
  41. package/build/angular/components/pressable/index.js.map +1 -1
  42. package/build/angular/components/refresh-control/index.d.ts +8 -4
  43. package/build/angular/components/refresh-control/index.js +52 -16
  44. package/build/angular/components/refresh-control/index.js.map +1 -1
  45. package/build/angular/components/safe-area-view/index.d.ts +9 -4
  46. package/build/angular/components/safe-area-view/index.js +46 -9
  47. package/build/angular/components/safe-area-view/index.js.map +1 -1
  48. package/build/angular/components/scroll-view/index.android.d.ts +2 -3
  49. package/build/angular/components/scroll-view/index.android.js +49 -52
  50. package/build/angular/components/scroll-view/index.android.js.map +1 -1
  51. package/build/angular/components/scroll-view/index.ios.d.ts +2 -3
  52. package/build/angular/components/scroll-view/index.ios.js +15 -17
  53. package/build/angular/components/scroll-view/index.ios.js.map +1 -1
  54. package/build/angular/components/scroll-view/projection.d.ts +3 -0
  55. package/build/angular/components/scroll-view/projection.js +68 -7
  56. package/build/angular/components/scroll-view/projection.js.map +1 -1
  57. package/build/angular/components/scroll-view/shared.d.ts +14 -5
  58. package/build/angular/components/scroll-view/shared.js +198 -69
  59. package/build/angular/components/scroll-view/shared.js.map +1 -1
  60. package/build/angular/components/scroll-view/sticky-header.js +14 -19
  61. package/build/angular/components/scroll-view/sticky-header.js.map +1 -1
  62. package/build/angular/components/section-list/index.d.ts +2 -1
  63. package/build/angular/components/section-list/index.js +31 -39
  64. package/build/angular/components/section-list/index.js.map +1 -1
  65. package/build/angular/components/switch/index.android.d.ts +7 -3
  66. package/build/angular/components/switch/index.android.js +38 -10
  67. package/build/angular/components/switch/index.android.js.map +1 -1
  68. package/build/angular/components/switch/index.ios.d.ts +7 -3
  69. package/build/angular/components/switch/index.ios.js +38 -10
  70. package/build/angular/components/switch/index.ios.js.map +1 -1
  71. package/build/angular/components/switch/shared.d.ts +12 -3
  72. package/build/angular/components/switch/shared.js +66 -5
  73. package/build/angular/components/switch/shared.js.map +1 -1
  74. package/build/angular/components/text-input.d.ts +10 -2
  75. package/build/angular/components/text-input.js +46 -6
  76. package/build/angular/components/text-input.js.map +1 -1
  77. package/build/angular/components/touchable/index.d.ts +17 -8
  78. package/build/angular/components/touchable/index.js +78 -19
  79. package/build/angular/components/touchable/index.js.map +1 -1
  80. package/build/angular/components/touchable-native-feedback/index.d.ts +6 -3
  81. package/build/angular/components/touchable-native-feedback/index.js +61 -25
  82. package/build/angular/components/touchable-native-feedback/index.js.map +1 -1
  83. package/build/angular/components/virtualized-list/directives.js +18 -18
  84. package/build/angular/components/virtualized-list/index.d.ts +12 -4
  85. package/build/angular/components/virtualized-list/index.js +163 -68
  86. package/build/angular/components/virtualized-list/index.js.map +1 -1
  87. package/build/angular/components/virtualized-section-list/directives.js +12 -12
  88. package/build/angular/components/virtualized-section-list/index.d.ts +2 -1
  89. package/build/angular/components/virtualized-section-list/index.js +6 -4
  90. package/build/angular/components/virtualized-section-list/index.js.map +1 -1
  91. package/build/angular/components.d.ts +1 -1
  92. package/build/angular/components.js +5 -0
  93. package/build/angular/components.js.map +1 -1
  94. package/build/angular/create-portal/index.js +29 -35
  95. package/build/angular/create-portal/index.js.map +1 -1
  96. package/build/angular/create-tunnel/index.js +19 -22
  97. package/build/angular/create-tunnel/index.js.map +1 -1
  98. package/build/angular/descriptor-to-angular/index.js +3 -3
  99. package/build/angular/index.d.ts +4 -2
  100. package/build/angular/index.js +2 -2
  101. package/build/angular/index.js.map +1 -1
  102. package/build/angular/modules/animated/animated-leaf-binder.d.ts +1 -7
  103. package/build/angular/modules/animated/animated-leaf-binder.js +22 -72
  104. package/build/angular/modules/animated/animated-leaf-binder.js.map +1 -1
  105. package/build/angular/modules/animated/create-animated-component.d.ts +11 -6
  106. package/build/angular/modules/animated/create-animated-component.js +83 -50
  107. package/build/angular/modules/animated/create-animated-component.js.map +1 -1
  108. package/build/angular/modules/app-registry/index.js.map +1 -1
  109. package/build/angular/modules/status-bar/index.js +3 -3
  110. package/build/angular/primitives/index.d.ts +1 -1
  111. package/build/angular/primitives/index.js +46 -46
  112. package/build/angular/primitives/index.js.map +1 -1
  113. package/build/angular/primitives/shared.d.ts +39 -1
  114. package/build/angular/primitives/shared.js +110 -9
  115. package/build/angular/primitives/shared.js.map +1 -1
  116. package/build/angular/render/index.js +23 -35
  117. package/build/angular/render/index.js.map +1 -1
  118. package/build/angular/services/color-scheme.service.js +3 -3
  119. package/build/angular/services/window-dimensions.service.js +3 -3
  120. package/package.json +13 -8
  121. package/src/anchor-host-registry.ts +2 -2
  122. package/src/components/activity-indicator/index.android.ts +2 -0
  123. package/src/components/activity-indicator/index.ios.ts +2 -0
  124. package/src/components/button.ts +28 -14
  125. package/src/components/flat-list/index.ts +34 -36
  126. package/src/components/image/index.android.ts +50 -6
  127. package/src/components/image/index.ios.ts +50 -6
  128. package/src/components/image/shared.ts +34 -2
  129. package/src/components/image-background.ts +2 -1
  130. package/src/components/input-accessory-view/index.ts +42 -4
  131. package/src/components/keyboard-avoiding-view/index.ts +7 -1
  132. package/src/components/modal/index.ts +51 -26
  133. package/src/components/pressable/index.ts +67 -8
  134. package/src/components/refresh-control/index.ts +54 -12
  135. package/src/components/safe-area-view/index.ts +50 -5
  136. package/src/components/scroll-view/index.android.ts +34 -41
  137. package/src/components/scroll-view/index.ios.ts +10 -21
  138. package/src/components/scroll-view/projection.ts +79 -7
  139. package/src/components/scroll-view/shared.ts +223 -68
  140. package/src/components/scroll-view/sticky-header.ts +11 -16
  141. package/src/components/section-list/index.ts +27 -36
  142. package/src/components/switch/index.android.ts +48 -7
  143. package/src/components/switch/index.ios.ts +48 -7
  144. package/src/components/switch/shared.ts +79 -3
  145. package/src/components/text-input.ts +52 -2
  146. package/src/components/touchable/index.ts +76 -16
  147. package/src/components/touchable-native-feedback/index.ts +62 -21
  148. package/src/components/virtualized-list/index.ts +137 -44
  149. package/src/components/virtualized-section-list/index.ts +2 -1
  150. package/src/components.ts +5 -0
  151. package/src/create-portal/index.ts +23 -29
  152. package/src/create-tunnel/index.ts +13 -16
  153. package/src/index.ts +30 -1
  154. package/src/modules/animated/animated-leaf-binder.ts +28 -77
  155. package/src/modules/animated/create-animated-component.ts +75 -36
  156. package/src/modules/app-registry/index.ts +6 -8
  157. package/src/primitives/index.ts +1 -0
  158. package/src/primitives/shared.ts +102 -3
  159. package/src/render/index.ts +23 -35
  160. package/build/angular/modules/animated/animated-leaf-binder/index.d.ts +0 -14
  161. package/build/angular/modules/animated/animated-leaf-binder/index.js +0 -91
  162. package/build/angular/modules/animated/animated-leaf-binder/index.js.map +0 -1
  163. package/src/__tests__/responder-change-detection.test.ts +0 -102
  164. package/src/__tests__/responder-nested-cd.test.ts +0 -113
  165. package/src/angular-gaps.test.ts +0 -142
  166. package/src/angular-imports.test.ts +0 -100
  167. package/src/components/activity-indicator/activity-indicator-class-style.test.ts +0 -44
  168. package/src/components/activity-indicator/activity-indicator.test.ts +0 -131
  169. package/src/components/flat-list/column-wrapper-style-class.test.ts +0 -116
  170. package/src/components/flat-list/flat-list-array-style.test.ts +0 -131
  171. package/src/components/flat-list/flat-list-class-style.test.ts +0 -74
  172. package/src/components/flat-list/flat-list-scroll-containment.test.ts +0 -107
  173. package/src/components/flat-list/flat-list.test.ts +0 -99
  174. package/src/components/image/image-class-style.test.ts +0 -46
  175. package/src/components/image-background-class-style.test.ts +0 -48
  176. package/src/components/image-background-style-class.test.ts +0 -79
  177. package/src/components/input-accessory-view/input-accessory-view.test.ts +0 -43
  178. package/src/components/keyboard-avoiding-view/keyboard-avoiding-view.test.ts +0 -54
  179. package/src/components/modal/modal.test.ts +0 -43
  180. package/src/components/pressable/pressable.test.ts +0 -85
  181. package/src/components/refresh-control/refresh-control.test.ts +0 -41
  182. package/src/components/safe-area-view/safe-area-view.test.ts +0 -43
  183. package/src/components/scroll-view/android-scroll-view-axis-projection.test.ts +0 -123
  184. package/src/components/scroll-view/content-container-style-class.test.ts +0 -75
  185. package/src/components/scroll-view/scroll-view-class-style.test.ts +0 -134
  186. package/src/components/scroll-view/scroll-view-projection.test.ts +0 -194
  187. package/src/components/switch/android-switch-class.test.ts +0 -42
  188. package/src/components/switch/switch.test.ts +0 -92
  189. package/src/components/text-input-class-style.test.ts +0 -54
  190. package/src/components/touchable/touchable.test.ts +0 -102
  191. package/src/components/touchable-native-feedback/touchable-native-feedback.test.ts +0 -49
  192. package/src/components/virtualized-list/virtualized-list-class-style.test.ts +0 -76
  193. package/src/components/virtualized-section-list/virtualized-section-list-class-style.test.ts +0 -60
  194. package/src/create-portal/create-portal.test.ts +0 -90
  195. package/src/create-tunnel/create-tunnel.test.ts +0 -120
  196. package/src/descriptor-to-angular/descriptor-outlet.test.ts +0 -94
  197. package/src/host-instance/host-instance.test.ts +0 -49
  198. package/src/modules/animated/animated-leaf-binder/animated-leaf-binder.test.ts +0 -116
  199. package/src/modules/animated/animated-leaf-binder/index.ts +0 -101
  200. package/src/modules/animated/animated-leaf-binder.test.ts +0 -116
  201. package/src/modules/animated/animated-scroll-view.test.ts +0 -102
  202. package/src/modules/app-registry/app-registry.test.ts +0 -106
  203. package/src/modules/status-bar/status-bar.test.ts +0 -50
  204. package/src/render/render.test.ts +0 -147
  205. package/src/renderer/renderer.test.ts +0 -203
  206. package/src/services/services.test.ts +0 -163
@@ -1,131 +0,0 @@
1
- import '@angular/compiler';
2
- import { Component, signal } from '@angular/core';
3
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
- import { installFabric, type IFakeNode } from '@symbiote-native/test-utils';
5
-
6
- import { mount, unmount } from '../../render';
7
- import { ActivityIndicator } from './index';
8
-
9
- const ROOT_TAG = 905;
10
- const fabric = installFabric();
11
-
12
- let capturedHost: ActivityIndicatorHost | undefined;
13
-
14
- function findCommitted(predicate: (node: IFakeNode) => boolean): IFakeNode | undefined {
15
- const visit = (node: IFakeNode): IFakeNode | undefined => {
16
- if (predicate(node)) return node;
17
- for (const child of node.children) {
18
- const found = visit(child);
19
- if (found) return found;
20
- }
21
- return undefined;
22
- };
23
- for (const node of fabric.committed) {
24
- const found = visit(node);
25
- if (found) return found;
26
- }
27
- return undefined;
28
- }
29
-
30
- function findSpinner(): IFakeNode {
31
- const node = findCommitted(n => n.viewName === 'ActivityIndicatorView');
32
- if (!node) throw new Error('no ActivityIndicatorView was created');
33
- return node;
34
- }
35
-
36
- function findWrapper(): IFakeNode {
37
- const node = findCommitted(n => n.viewName === 'RCTView' && n.props.pointerEvents !== 'box-none');
38
- if (!node) throw new Error('no ActivityIndicator wrapper was created');
39
- return node;
40
- }
41
-
42
- @Component({
43
- selector: 'symbiote-activity-indicator-host',
44
- standalone: true,
45
- imports: [ActivityIndicator],
46
- template: `
47
- <ActivityIndicator
48
- [size]="size()"
49
- [color]="color()"
50
- [animating]="animating()"
51
- [testID]="testID()"
52
- [accessible]="true"
53
- [accessibilityLabel]="'loading'"
54
- (layout)="onLayout($event)"
55
- />
56
- `,
57
- })
58
- class ActivityIndicatorHost {
59
- readonly size = signal<'small' | 'large' | number>('large');
60
- readonly color = signal('#0000ff');
61
- readonly animating = signal(false);
62
- readonly testID = signal('spinner-wrapper');
63
- onLayout = vi.fn();
64
-
65
- constructor() {
66
- // Captures the live component instance so the test can drive its signals after mount.
67
- // eslint-disable-next-line @typescript-eslint/no-this-alias
68
- capturedHost = this;
69
- }
70
- }
71
-
72
- beforeEach(() => {
73
- capturedHost = undefined;
74
- fabric.reset();
75
- });
76
- afterEach(() => unmount(ROOT_TAG));
77
-
78
- describe('ActivityIndicator', () => {
79
- it('renders through the shared descriptor shape and forwards wrapper props', async () => {
80
- mount(ROOT_TAG, ActivityIndicatorHost);
81
- await new Promise<void>(resolve => setTimeout(resolve, 0));
82
-
83
- expect(fabric.serialize(fabric.appRoot().children)).toBe('RCTView(ActivityIndicatorView)');
84
-
85
- const spinner = findSpinner();
86
- expect(spinner.props.animating).toBe(false);
87
- expect(spinner.props.color).toBe('#0000ff');
88
- expect(spinner.props.hidesWhenStopped).toBe(true);
89
- expect(spinner.props.size).toBe('large');
90
- expect(spinner.props.width).toBe(36);
91
- expect(spinner.props.height).toBe(36);
92
-
93
- const wrapper = findWrapper();
94
- expect(wrapper.props.testID).toBe('spinner-wrapper');
95
- expect(wrapper.props.accessible).toBe(true);
96
- expect(wrapper.props.accessibilityLabel).toBe('loading');
97
- expect(wrapper.props.alignItems).toBe('center');
98
- expect(wrapper.props.justifyContent).toBe('center');
99
-
100
- fabric.fireEvent(wrapper.instanceHandle, 'topLayout', {});
101
- expect(capturedHost?.onLayout).toHaveBeenCalledOnce();
102
- });
103
-
104
- it('patches the descriptor without remounting the native wrapper', async () => {
105
- mount(ROOT_TAG, ActivityIndicatorHost);
106
- await new Promise<void>(resolve => setTimeout(resolve, 0));
107
-
108
- const before = findWrapper();
109
- const createdBefore = fabric.counts.createNode;
110
-
111
- if (!capturedHost) throw new Error('host was not captured');
112
- capturedHost.size.set(48);
113
- capturedHost.color.set('#ff0000');
114
- capturedHost.animating.set(true);
115
- capturedHost.testID.set('spinner-wrapper-updated');
116
- await Promise.resolve();
117
- await new Promise<void>(resolve => setTimeout(resolve, 0));
118
-
119
- const after = findWrapper();
120
- expect(after.instanceHandle).toBe(before.instanceHandle);
121
- expect(fabric.counts.createNode).toBe(createdBefore);
122
- expect(after.props.testID).toBe('spinner-wrapper-updated');
123
- const spinner = findSpinner();
124
- expect(spinner.props.color).toBe('#ff0000');
125
- expect(spinner.props.animating).toBe(true);
126
- expect(spinner.props.width).toBe(48);
127
- expect(spinner.props.height).toBe(48);
128
- // Updating from string size to numeric size sends null to reset the previous native enum.
129
- expect(spinner.props.size).toBeNull();
130
- });
131
- });
@@ -1,116 +0,0 @@
1
- // Regression guard: columnWrapperStyle previously accepted ONLY a JS style object/array. It now
2
- // also resolves a class-name string through the shared style registry, merged onto the same
3
- // flex-row wrapper as an object/array value (see index.ts's ngOnChanges). Mirrors the Vue twin
4
- // (column-wrapper-style-class.test.ts) and flat-list.test.ts's row-packing shape.
5
- import '@angular/compiler';
6
- import { Component } from '@angular/core';
7
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
8
- import { clearGlobalStyles, registerStyles } from '@symbiote-native/engine';
9
- import { installFabric, type IFakeNode } from '@symbiote-native/test-utils';
10
-
11
- import { mount, unmount } from '../../render';
12
- import { FlatList } from './index';
13
- import { VListItemDirective } from '../virtualized-list/directives';
14
-
15
- const ROOT_TAG = 953;
16
- const fabric = installFabric();
17
- const tick = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
18
-
19
- interface IRow {
20
- id: string;
21
- label: string;
22
- }
23
-
24
- const rows: IRow[] = Array.from({ length: 4 }, (_unused, index) => ({
25
- id: `r-${index}`,
26
- label: `row-${index}`,
27
- }));
28
-
29
- function walk(nodes: IFakeNode[], visit: (node: IFakeNode) => void): void {
30
- for (const node of nodes) {
31
- visit(node);
32
- walk(node.children, visit);
33
- }
34
- }
35
-
36
- function rowsWithFlexDirection(): IFakeNode[] {
37
- const found: IFakeNode[] = [];
38
- walk(fabric.committed, node => {
39
- if (node.viewName === 'RCTView' && node.props.flexDirection === 'row') found.push(node);
40
- });
41
- return found;
42
- }
43
-
44
- @Component({
45
- selector: 'symbiote-flatlist-column-style-class-host',
46
- standalone: true,
47
- imports: [FlatList, VListItemDirective],
48
- template: `
49
- <FlatList
50
- [data]="rows"
51
- [numColumns]="2"
52
- [keyExtractor]="keyExtractor"
53
- [columnWrapperStyle]="'gap8'"
54
- >
55
- <ng-template vListItem let-item>
56
- <symbiote-text [testID]="item.id">{{ item.label }}</symbiote-text>
57
- </ng-template>
58
- </FlatList>
59
- `,
60
- })
61
- class FlatListColumnStyleClassHost {
62
- rows = rows;
63
- keyExtractor = (item: IRow): string => item.id;
64
- }
65
-
66
- @Component({
67
- selector: 'symbiote-flatlist-column-style-object-host',
68
- standalone: true,
69
- imports: [FlatList, VListItemDirective],
70
- template: `
71
- <FlatList
72
- [data]="rows"
73
- [numColumns]="2"
74
- [keyExtractor]="keyExtractor"
75
- [columnWrapperStyle]="{ gap: 4 }"
76
- >
77
- <ng-template vListItem let-item>
78
- <symbiote-text [testID]="item.id">{{ item.label }}</symbiote-text>
79
- </ng-template>
80
- </FlatList>
81
- `,
82
- })
83
- class FlatListColumnStyleObjectHost {
84
- rows = rows;
85
- keyExtractor = (item: IRow): string => item.id;
86
- }
87
-
88
- beforeEach(() => {
89
- fabric.reset();
90
- registerStyles({ gap8: { gap: 8 } });
91
- });
92
- afterEach(() => {
93
- unmount(ROOT_TAG);
94
- clearGlobalStyles();
95
- });
96
-
97
- describe('FlatList columnWrapperStyle class-name support', () => {
98
- it('resolves a class-name string onto the row wrapper alongside flexDirection', async () => {
99
- mount(ROOT_TAG, FlatListColumnStyleClassHost);
100
- await tick();
101
- await tick();
102
-
103
- const found = rowsWithFlexDirection();
104
- expect(found.length, 'two flex-row rows for 4 items in 2 columns').toBe(2);
105
- expect(found[0].props.gap).toBe(8);
106
- });
107
-
108
- it('still accepts an ordinary style object unchanged', async () => {
109
- mount(ROOT_TAG, FlatListColumnStyleObjectHost);
110
- await tick();
111
- await tick();
112
-
113
- const found = rowsWithFlexDirection();
114
- expect(found[0].props.gap).toBe(4);
115
- });
116
- });
@@ -1,131 +0,0 @@
1
- import '@angular/compiler';
2
- import { Component } from '@angular/core';
3
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4
- import { installFabric } from '@symbiote-native/test-utils';
5
- import { flattenStyle } from '@symbiote-native/engine';
6
-
7
- import { mount, unmount } from '../../render';
8
- import { FlatList } from './index';
9
- import { VListItemDirective } from '../virtualized-list/directives';
10
-
11
- const ROOT_TAG = 905;
12
- const fabric = installFabric();
13
-
14
- interface IChip {
15
- id: string;
16
- n: number;
17
- }
18
-
19
- const chips: IChip[] = Array.from({ length: 24 }, (_unused, index) => ({
20
- id: `c-${index}`,
21
- n: index,
22
- }));
23
-
24
- // Regression coverage for a real device bug: Angular compiles a literal `[style]="…"` binding to
25
- // the built-in ɵɵstyleMap instruction (NOT a regular @Input() property write), regardless of the
26
- // target's declared type — it only understands a flat style object, never an array. RN's own
27
- // `style={[base, override]}` composition idiom (used by examples/angular/App.ts's "FlatList · 24
28
- // chips" demo for the per-item computed backgroundColor) crashed deep inside Angular's own styling
29
- // engine (`prop.indexOf is not a function`), and the crash landed inside a zoneless
30
- // SymbioteChangeDetectionScheduler tick with no catch around it — so the retry kept re-firing
31
- // forever (RAM climbing, constant re-render log spam on device) while the item never painted its
32
- // style. Fixed by flattening any array-composed style with the engine's own `flattenStyle` before
33
- // it reaches a `[style]=` binding — required of app authors (see App.ts) AND of every adapter
34
- // component that forwards its own `style` @Input() onward (see the `resolvedStyle` getters/fields
35
- // in virtualized-list, section-list, virtualized-section-list — flat-list's own `rowStyle` had the
36
- // identical landmine, always assigning an array).
37
- @Component({
38
- selector: 'symbiote-chip-list-host',
39
- standalone: true,
40
- imports: [FlatList, VListItemDirective],
41
- template: `
42
- <FlatList
43
- testID="chips-list"
44
- [data]="chips"
45
- [horizontal]="true"
46
- [keyExtractor]="keyExtractor"
47
- [getItemLayout]="getItemLayout"
48
- [style]="{ height: 84 }"
49
- >
50
- <ng-template vListItem let-item>
51
- <symbiote-view
52
- [testID]="'chip-' + item.n"
53
- [style]="flattenStyle([{ height: 72, width: 64 }, { backgroundColor: chipColor(item) }])"
54
- >
55
- <symbiote-text>{{ item.n }}</symbiote-text>
56
- </symbiote-view>
57
- </ng-template>
58
- </FlatList>
59
- `,
60
- })
61
- class ChipListHost {
62
- chips = chips;
63
- flattenStyle = flattenStyle;
64
- keyExtractor = (item: IChip): string => item.id;
65
- getItemLayout = (
66
- _data: unknown,
67
- index: number,
68
- ): { length: number; offset: number; index: number } => ({
69
- length: 64,
70
- offset: 64 * index,
71
- index,
72
- });
73
- chipColor(item: IChip): string {
74
- return `hsl(${(item.n * 47) % 360}, 70%, 50%)`;
75
- }
76
- }
77
-
78
- beforeEach(() => fabric.reset());
79
- afterEach(() => unmount(ROOT_TAG));
80
-
81
- function findCommitted(
82
- nodes: typeof fabric.committed,
83
- predicate: (n: (typeof nodes)[number]) => boolean,
84
- ): (typeof nodes)[number] | undefined {
85
- for (const node of nodes) {
86
- if (predicate(node)) return node;
87
- const found = findCommitted(node.children, predicate);
88
- if (found !== undefined) return found;
89
- }
90
- return undefined;
91
- }
92
-
93
- describe('FlatList array-composed item style', () => {
94
- it('flattens to a plain object instead of crashing ɵɵstyleMap, and commits it', async () => {
95
- mount(ROOT_TAG, ChipListHost);
96
- await new Promise<void>(resolve => setTimeout(resolve, 0));
97
- await new Promise<void>(resolve => setTimeout(resolve, 0));
98
-
99
- const chip0 = findCommitted(fabric.committed, node => node.props.testID === 'chip-0');
100
- expect(chip0?.props).toMatchObject({
101
- height: 72,
102
- width: 64,
103
- backgroundColor: 'hsl(0, 70%, 50%)',
104
- });
105
- });
106
-
107
- it('does not free-run change detection once the window settles', async () => {
108
- mount(ROOT_TAG, ChipListHost);
109
- await new Promise<void>(resolve => setTimeout(resolve, 0));
110
- await new Promise<void>(resolve => setTimeout(resolve, 0));
111
-
112
- const scroll = fabric.find(node => node.viewName === 'RCTScrollView');
113
- if (scroll !== undefined) {
114
- fabric.fireEvent(scroll.instanceHandle, 'topLayout', {
115
- layout: { x: 0, y: 0, width: 360, height: 84 },
116
- });
117
- }
118
- await new Promise<void>(resolve => setTimeout(resolve, 0));
119
- await new Promise<void>(resolve => setTimeout(resolve, 0));
120
-
121
- let previous = fabric.counts.completeRoot;
122
- let stillGrowing = false;
123
- for (let tick = 0; tick < 10; tick += 1) {
124
- await new Promise<void>(resolve => setTimeout(resolve, 0));
125
- const current = fabric.counts.completeRoot;
126
- if (current > previous) stillGrowing = true;
127
- previous = current;
128
- }
129
- expect(stillGrowing).toBe(false);
130
- });
131
- });
@@ -1,74 +0,0 @@
1
- // Regression test for the anchor/class bug: FlatList is its own
2
- // ANCHOR_HOST_COMPONENTS entry, separate from the VirtualizedList/ScrollView anchors it wraps two
3
- // levels down — a class= on <FlatList> resolves onto FlatList's OWN anchor and needs its OWN
4
- // anchorHostStyle merge (see index.ts's ngOnChanges), it is NOT transitively fixed by
5
- // VirtualizedList's or ScrollView's own fixes. Mirrors pressable.test.ts's "resolves a class=" case.
6
- import '@angular/compiler';
7
- import { Component } from '@angular/core';
8
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
9
- import { clearGlobalStyles, registerStyles } from '@symbiote-native/engine';
10
- import { installFabric } from '@symbiote-native/test-utils';
11
-
12
- import { mount, unmount } from '../../render';
13
- import { FlatList } from './index';
14
- import { VListItemDirective } from '../virtualized-list/directives';
15
-
16
- const ROOT_TAG = 951;
17
- const fabric = installFabric();
18
- const tick = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
19
-
20
- interface IRow {
21
- id: string;
22
- }
23
-
24
- const rows: IRow[] = [{ id: 'a' }, { id: 'b' }];
25
-
26
- @Component({
27
- selector: 'symbiote-flatlist-class-host',
28
- standalone: true,
29
- imports: [FlatList, VListItemDirective],
30
- template: `
31
- <FlatList
32
- class="card"
33
- [data]="rows"
34
- [keyExtractor]="keyExtractor"
35
- [getItemLayout]="getItemLayout"
36
- >
37
- <ng-template vListItem let-item>
38
- <symbiote-text [testID]="item.id">{{ item.id }}</symbiote-text>
39
- </ng-template>
40
- </FlatList>
41
- `,
42
- })
43
- class FlatListClassHost {
44
- rows = rows;
45
- keyExtractor = (item: IRow): string => item.id;
46
- getItemLayout = (
47
- _data: unknown,
48
- index: number,
49
- ): { length: number; offset: number; index: number } => ({
50
- length: 30,
51
- offset: 30 * index,
52
- index,
53
- });
54
- }
55
-
56
- beforeEach(() => {
57
- fabric.reset();
58
- registerStyles({ card: { backgroundColor: 'red' } });
59
- });
60
- afterEach(() => {
61
- unmount(ROOT_TAG);
62
- clearGlobalStyles();
63
- });
64
-
65
- describe('FlatList anchor class= resolution', () => {
66
- it('resolves a class= on the FlatList use site onto the real committed scroll host', async () => {
67
- mount(ROOT_TAG, FlatListClassHost);
68
- await tick();
69
- await tick();
70
-
71
- const node = fabric.find(n => n.props.backgroundColor === 'red');
72
- expect(node, 'a real Fabric node carries the class-derived style').toBeDefined();
73
- });
74
- });
@@ -1,107 +0,0 @@
1
- import '@angular/compiler';
2
- import { Component } from '@angular/core';
3
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4
- import { installFabric } from '@symbiote-native/test-utils';
5
-
6
- import { mount, unmount } from '../../render';
7
- import { FlatList } from './index';
8
- import { VListItemDirective } from '../virtualized-list/directives';
9
-
10
- const ROOT_TAG = 907;
11
- const fabric = installFabric();
12
-
13
- interface IChip {
14
- id: string;
15
- n: number;
16
- }
17
-
18
- const chips: IChip[] = Array.from({ length: 24 }, (_unused, index) => ({
19
- id: `c-${index}`,
20
- n: index,
21
- }));
22
-
23
- // Mirrors examples/angular/App.ts's "FlatList · 24 chips" demo end to end: a horizontal FlatList
24
- // with getItemLayout. Regression coverage for a bug fixed 2026-07: cell content used to land
25
- // OUTSIDE the ScrollView's content view entirely — as top-level siblings
26
- // of the committed RCTScrollView node, instead of children of its RCTScrollContentView — which is
27
- // why the device screenshot showed full-width vertically-stacked chips instead of a small
28
- // horizontal row. Root cause was `<ng-content>` declared once per `@if`/`@else` branch in
29
- // scroll-view/index.ios.ts (a documented Angular limitation, angular/angular#53310); fixed by
30
- // collapsing to a single unconditional host tag since iOS never needed axis-specific tags to begin
31
- // with (`horizontal` already flows as a plain prop). This test pins the CORRECT tree shape.
32
- @Component({
33
- selector: 'symbiote-chip-container-host',
34
- standalone: true,
35
- imports: [FlatList, VListItemDirective],
36
- template: `
37
- <FlatList
38
- testID="chips-list"
39
- [data]="chips"
40
- [horizontal]="true"
41
- [keyExtractor]="keyExtractor"
42
- [getItemLayout]="getItemLayout"
43
- [style]="chipListStyle"
44
- >
45
- <ng-template vListItem let-item>
46
- <symbiote-view [testID]="'chip-' + item.n" [style]="chipCardStyle">
47
- <symbiote-text>{{ item.n }}</symbiote-text>
48
- </symbiote-view>
49
- </ng-template>
50
- </FlatList>
51
- `,
52
- })
53
- class ChipContainerHost {
54
- chips = chips;
55
- chipListStyle = { height: 84 };
56
- chipCardStyle = { width: 72, height: 64, borderRadius: 14 };
57
- keyExtractor = (item: IChip): string => item.id;
58
- getItemLayout = (
59
- _data: unknown,
60
- index: number,
61
- ): { length: number; offset: number; index: number } => ({
62
- length: 64,
63
- offset: 64 * index,
64
- index,
65
- });
66
- }
67
-
68
- beforeEach(() => fabric.reset());
69
- afterEach(() => unmount(ROOT_TAG));
70
-
71
- function findWithin(
72
- root: (typeof fabric.committed)[number],
73
- predicate: (n: (typeof fabric.committed)[number]) => boolean,
74
- ): (typeof fabric.committed)[number] | undefined {
75
- if (predicate(root)) return root;
76
- for (const child of root.children) {
77
- const found = findWithin(child, predicate);
78
- if (found !== undefined) return found;
79
- }
80
- return undefined;
81
- }
82
-
83
- function findTop(
84
- nodes: typeof fabric.committed,
85
- predicate: (n: (typeof nodes)[number]) => boolean,
86
- ): (typeof nodes)[number] | undefined {
87
- for (const node of nodes) {
88
- const found = findWithin(node, predicate);
89
- if (found !== undefined) return found;
90
- }
91
- return undefined;
92
- }
93
-
94
- describe('FlatList cells stay inside the ScrollView content view', () => {
95
- it('nests chip cells under RCTScrollContentView, not as siblings of RCTScrollView', async () => {
96
- mount(ROOT_TAG, ChipContainerHost);
97
- await new Promise<void>(resolve => setTimeout(resolve, 0));
98
- await new Promise<void>(resolve => setTimeout(resolve, 0));
99
-
100
- const scroll = findTop(fabric.committed, node => node.viewName === 'RCTScrollView');
101
- expect(scroll).toBeDefined();
102
- if (scroll === undefined) return;
103
-
104
- const chip0InsideScroll = findWithin(scroll, node => node.props.testID === 'chip-0');
105
- expect(chip0InsideScroll).toBeDefined();
106
- });
107
- });
@@ -1,99 +0,0 @@
1
- import '@angular/compiler';
2
- import { Component } from '@angular/core';
3
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4
- import { installFabric } from '@symbiote-native/test-utils';
5
-
6
- import { mount, unmount } from '../../render';
7
- import { FlatList } from './index';
8
- import {
9
- VListEmptyDirective,
10
- VListFooterDirective,
11
- VListHeaderDirective,
12
- VListItemDirective,
13
- } from '../virtualized-list/directives';
14
-
15
- const ROOT_TAG = 904;
16
- const fabric = installFabric();
17
-
18
- interface IRow {
19
- id: string;
20
- n: number;
21
- }
22
-
23
- const rows: IRow[] = Array.from({ length: 5 }, (_unused, index) => ({
24
- id: `r-${index}`,
25
- n: index,
26
- }));
27
-
28
- // Regression coverage for a real bug: FlatList's single-column path used to forward the app's
29
- // <ng-template vListItem> to the inner VirtualizedList via a bare <ng-content></ng-content>
30
- // passthrough. Angular's @ContentChild does NOT resolve a directive across that second projection
31
- // hop (it only sees what was projected directly onto the querying component's own tag), so
32
- // VirtualizedList's itemDir stayed undefined and every cell rendered empty — a real device symptom
33
- // (a blank list) that headless testing never caught because no test existed for this component
34
- // family. Fixed by having FlatList capture its own @ContentChild (a single, direct hop) and
35
- // re-stamp explicit <ng-template>s onto <VirtualizedList>, mirroring VirtualizedSectionList's own
36
- // (already-working) re-stamp pattern. SectionList had the identical bug, fixed the same way.
37
- @Component({
38
- selector: 'symbiote-flatlist-host',
39
- standalone: true,
40
- imports: [
41
- FlatList,
42
- VListItemDirective,
43
- VListHeaderDirective,
44
- VListFooterDirective,
45
- VListEmptyDirective,
46
- ],
47
- template: `
48
- <FlatList
49
- testID="rows-list"
50
- [data]="rows"
51
- [keyExtractor]="keyExtractor"
52
- [getItemLayout]="getItemLayout"
53
- [style]="{ height: 120 }"
54
- >
55
- <ng-template vListHeader>
56
- <symbiote-text testID="header">header</symbiote-text>
57
- </ng-template>
58
- <ng-template vListItem let-item>
59
- <symbiote-text [testID]="'row-' + item.n">{{ 'row ' + item.n }}</symbiote-text>
60
- </ng-template>
61
- <ng-template vListFooter>
62
- <symbiote-text testID="footer">footer</symbiote-text>
63
- </ng-template>
64
- </FlatList>
65
- `,
66
- })
67
- class FlatListHost {
68
- rows = rows;
69
- keyExtractor = (item: IRow): string => item.id;
70
- getItemLayout = (
71
- _data: unknown,
72
- index: number,
73
- ): { length: number; offset: number; index: number } => ({
74
- length: 30,
75
- offset: 30 * index,
76
- index,
77
- });
78
- }
79
-
80
- beforeEach(() => fabric.reset());
81
- afterEach(() => unmount(ROOT_TAG));
82
-
83
- describe('FlatList', () => {
84
- it('stamps the header, every row, and the footer through the projected templates', async () => {
85
- mount(ROOT_TAG, FlatListHost);
86
- await new Promise<void>(resolve => setTimeout(resolve, 0));
87
- await new Promise<void>(resolve => setTimeout(resolve, 0));
88
-
89
- const texts = fabric.created
90
- .filter(node => node.props.testID !== undefined)
91
- .map(node => node.props.testID);
92
-
93
- expect(texts).toContain('header');
94
- expect(texts).toContain('footer');
95
- for (let index = 0; index < rows.length; index += 1) {
96
- expect(texts).toContain(`row-${index}`);
97
- }
98
- });
99
- });