@symbiote-native/angular 0.6.0 → 0.6.2

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 (193) hide show
  1. package/README.md +1 -1
  2. package/babel-register-composed.cjs +11 -3
  3. package/build/angular/anchor-host-registry.d.ts +2 -0
  4. package/build/angular/anchor-host-registry.js +84 -0
  5. package/build/angular/anchor-host-registry.js.map +1 -0
  6. package/build/angular/components/input-accessory-view/index.d.ts +2 -1
  7. package/build/angular/components/input-accessory-view/index.js +28 -39
  8. package/build/angular/components/input-accessory-view/index.js.map +1 -1
  9. package/build/angular/components/scroll-view/projection.js +70 -48
  10. package/build/angular/components/scroll-view/projection.js.map +1 -1
  11. package/build/angular/components/scroll-view/shared.js +28 -25
  12. package/build/angular/components/scroll-view/shared.js.map +1 -1
  13. package/build/angular/components/scroll-view/sticky-header.d.ts +6 -7
  14. package/build/angular/components/scroll-view/sticky-header.js +82 -65
  15. package/build/angular/components/scroll-view/sticky-header.js.map +1 -1
  16. package/build/angular/components/touchable/index.d.ts +2 -5
  17. package/build/angular/components/touchable/index.js +36 -56
  18. package/build/angular/components/touchable/index.js.map +1 -1
  19. package/build/angular/components/virtualized-list/index.d.ts +5 -20
  20. package/build/angular/components/virtualized-list/index.js +145 -275
  21. package/build/angular/components/virtualized-list/index.js.map +1 -1
  22. package/build/angular/components/virtualized-section-list/index.js +2 -4
  23. package/build/angular/components/virtualized-section-list/index.js.map +1 -1
  24. package/build/angular/index.d.ts +2 -1
  25. package/build/angular/index.js +6 -1
  26. package/build/angular/index.js.map +1 -1
  27. package/build/angular/renderer/index.d.ts +0 -1
  28. package/build/angular/renderer/index.js +5 -60
  29. package/build/angular/renderer/index.js.map +1 -1
  30. package/package.json +8 -4
  31. package/src/anchor-host-registry.ts +88 -0
  32. package/src/components/input-accessory-view/index.ts +30 -39
  33. package/src/components/scroll-view/projection.ts +76 -46
  34. package/src/components/scroll-view/shared.ts +27 -19
  35. package/src/components/scroll-view/sticky-header.ts +89 -64
  36. package/src/components/touchable/index.ts +52 -66
  37. package/src/components/virtualized-list/index.ts +157 -368
  38. package/src/components/virtualized-section-list/index.ts +6 -3
  39. package/src/index.ts +6 -1
  40. package/src/renderer/index.ts +5 -63
  41. package/build/bootstrap.d.ts +0 -7
  42. package/build/bootstrap.js +0 -14
  43. package/build/components/activity-indicator/index.android.d.ts +0 -9
  44. package/build/components/activity-indicator/index.android.js +0 -64
  45. package/build/components/activity-indicator/index.d.ts +0 -1
  46. package/build/components/activity-indicator/index.ios.d.ts +0 -5
  47. package/build/components/activity-indicator/index.ios.js +0 -64
  48. package/build/components/activity-indicator/index.js +0 -3
  49. package/build/components/activity-indicator/shared.d.ts +0 -64
  50. package/build/components/activity-indicator/shared.js +0 -424
  51. package/build/components/button.d.ts +0 -60
  52. package/build/components/button.js +0 -456
  53. package/build/components/flat-list/index.d.ts +0 -119
  54. package/build/components/flat-list/index.js +0 -691
  55. package/build/components/image/index.android.d.ts +0 -7
  56. package/build/components/image/index.android.js +0 -88
  57. package/build/components/image/index.d.ts +0 -1
  58. package/build/components/image/index.ios.d.ts +0 -7
  59. package/build/components/image/index.ios.js +0 -88
  60. package/build/components/image/index.js +0 -2
  61. package/build/components/image/shared.d.ts +0 -101
  62. package/build/components/image/shared.js +0 -405
  63. package/build/components/image-background.d.ts +0 -78
  64. package/build/components/image-background.js +0 -583
  65. package/build/components/input-accessory-view/index.d.ts +0 -56
  66. package/build/components/input-accessory-view/index.js +0 -403
  67. package/build/components/keyboard-avoiding-view/index.d.ts +0 -79
  68. package/build/components/keyboard-avoiding-view/index.js +0 -504
  69. package/build/components/modal/index.d.ts +0 -89
  70. package/build/components/modal/index.js +0 -529
  71. package/build/components/pressable/index.d.ts +0 -120
  72. package/build/components/pressable/index.js +0 -639
  73. package/build/components/refresh-control/index.d.ts +0 -83
  74. package/build/components/refresh-control/index.js +0 -509
  75. package/build/components/safe-area-view/index.d.ts +0 -54
  76. package/build/components/safe-area-view/index.js +0 -388
  77. package/build/components/scroll-view/index.android.d.ts +0 -16
  78. package/build/components/scroll-view/index.android.js +0 -228
  79. package/build/components/scroll-view/index.d.ts +0 -3
  80. package/build/components/scroll-view/index.ios.d.ts +0 -6
  81. package/build/components/scroll-view/index.ios.js +0 -113
  82. package/build/components/scroll-view/index.js +0 -5
  83. package/build/components/scroll-view/projection.d.ts +0 -32
  84. package/build/components/scroll-view/projection.js +0 -311
  85. package/build/components/scroll-view/shared.d.ts +0 -225
  86. package/build/components/scroll-view/shared.js +0 -757
  87. package/build/components/scroll-view/sticky-header.d.ts +0 -38
  88. package/build/components/scroll-view/sticky-header.js +0 -257
  89. package/build/components/section-list/index.d.ts +0 -113
  90. package/build/components/section-list/index.js +0 -768
  91. package/build/components/switch/index.android.d.ts +0 -17
  92. package/build/components/switch/index.android.js +0 -107
  93. package/build/components/switch/index.d.ts +0 -1
  94. package/build/components/switch/index.ios.d.ts +0 -16
  95. package/build/components/switch/index.ios.js +0 -106
  96. package/build/components/switch/index.js +0 -3
  97. package/build/components/switch/shared.d.ts +0 -37
  98. package/build/components/switch/shared.js +0 -176
  99. package/build/components/text-input.d.ts +0 -150
  100. package/build/components/text-input.js +0 -821
  101. package/build/components/touchable/index.d.ts +0 -241
  102. package/build/components/touchable/index.js +0 -1672
  103. package/build/components/touchable-native-feedback/index.d.ts +0 -80
  104. package/build/components/touchable-native-feedback/index.js +0 -545
  105. package/build/components/virtualized-list/directives.d.ts +0 -41
  106. package/build/components/virtualized-list/directives.js +0 -249
  107. package/build/components/virtualized-list/index.d.ts +0 -265
  108. package/build/components/virtualized-list/index.js +0 -1343
  109. package/build/components/virtualized-section-list/directives.d.ts +0 -28
  110. package/build/components/virtualized-section-list/directives.js +0 -148
  111. package/build/components/virtualized-section-list/index.d.ts +0 -175
  112. package/build/components/virtualized-section-list/index.js +0 -854
  113. package/build/components.d.ts +0 -39
  114. package/build/components.js +0 -37
  115. package/build/create-portal/index.d.ts +0 -17
  116. package/build/create-portal/index.js +0 -128
  117. package/build/create-tunnel/index.d.ts +0 -29
  118. package/build/create-tunnel/index.js +0 -181
  119. package/build/descriptor-to-angular/index.d.ts +0 -19
  120. package/build/descriptor-to-angular/index.js +0 -186
  121. package/build/host-instance/index.d.ts +0 -2
  122. package/build/host-instance/index.js +0 -36
  123. package/build/index.d.ts +0 -21
  124. package/build/index.js +0 -40
  125. package/build/modules/animated/animated-leaf-binder/index.d.ts +0 -14
  126. package/build/modules/animated/animated-leaf-binder/index.js +0 -90
  127. package/build/modules/animated/animated-leaf-binder.d.ts +0 -14
  128. package/build/modules/animated/animated-leaf-binder.js +0 -90
  129. package/build/modules/animated/create-animated-component.d.ts +0 -48
  130. package/build/modules/animated/create-animated-component.js +0 -425
  131. package/build/modules/animated/index.d.ts +0 -51
  132. package/build/modules/animated/index.js +0 -58
  133. package/build/modules/app-registry/index.d.ts +0 -7
  134. package/build/modules/app-registry/index.js +0 -22
  135. package/build/modules/status-bar/index.d.ts +0 -17
  136. package/build/modules/status-bar/index.js +0 -121
  137. package/build/primitives/index.d.ts +0 -32
  138. package/build/primitives/index.js +0 -386
  139. package/build/primitives/shared.d.ts +0 -83
  140. package/build/primitives/shared.js +0 -230
  141. package/build/render/index.d.ts +0 -11
  142. package/build/render/index.js +0 -170
  143. package/build/renderer/index.d.ts +0 -38
  144. package/build/renderer/index.js +0 -355
  145. package/build/services/color-scheme.service.d.ts +0 -13
  146. package/build/services/color-scheme.service.js +0 -72
  147. package/build/services/index.d.ts +0 -2
  148. package/build/services/index.js +0 -2
  149. package/build/services/window-dimensions.service.d.ts +0 -13
  150. package/build/services/window-dimensions.service.js +0 -83
  151. package/src/__tests__/responder-change-detection.test.ts +0 -102
  152. package/src/__tests__/responder-nested-cd.test.ts +0 -113
  153. package/src/angular-gaps.test.ts +0 -142
  154. package/src/angular-imports.test.ts +0 -100
  155. package/src/components/activity-indicator/activity-indicator-class-style.test.ts +0 -44
  156. package/src/components/activity-indicator/activity-indicator.test.ts +0 -131
  157. package/src/components/flat-list/column-wrapper-style-class.test.ts +0 -116
  158. package/src/components/flat-list/flat-list-array-style.test.ts +0 -131
  159. package/src/components/flat-list/flat-list-class-style.test.ts +0 -74
  160. package/src/components/flat-list/flat-list-scroll-containment.test.ts +0 -107
  161. package/src/components/flat-list/flat-list.test.ts +0 -99
  162. package/src/components/image/image-class-style.test.ts +0 -46
  163. package/src/components/image-background-class-style.test.ts +0 -48
  164. package/src/components/image-background-style-class.test.ts +0 -79
  165. package/src/components/input-accessory-view/input-accessory-view.test.ts +0 -43
  166. package/src/components/keyboard-avoiding-view/keyboard-avoiding-view.test.ts +0 -54
  167. package/src/components/modal/modal.test.ts +0 -43
  168. package/src/components/pressable/pressable.test.ts +0 -85
  169. package/src/components/refresh-control/refresh-control.test.ts +0 -41
  170. package/src/components/safe-area-view/safe-area-view.test.ts +0 -43
  171. package/src/components/scroll-view/android-scroll-view-axis-projection.test.ts +0 -123
  172. package/src/components/scroll-view/content-container-style-class.test.ts +0 -75
  173. package/src/components/scroll-view/scroll-view-class-style.test.ts +0 -134
  174. package/src/components/scroll-view/scroll-view-projection.test.ts +0 -194
  175. package/src/components/switch/android-switch-class.test.ts +0 -42
  176. package/src/components/switch/switch.test.ts +0 -92
  177. package/src/components/text-input-class-style.test.ts +0 -54
  178. package/src/components/touchable/touchable.test.ts +0 -102
  179. package/src/components/touchable-native-feedback/touchable-native-feedback.test.ts +0 -49
  180. package/src/components/virtualized-list/virtualized-list-class-style.test.ts +0 -76
  181. package/src/components/virtualized-section-list/virtualized-section-list-class-style.test.ts +0 -60
  182. package/src/create-portal/create-portal.test.ts +0 -90
  183. package/src/create-tunnel/create-tunnel.test.ts +0 -120
  184. package/src/descriptor-to-angular/descriptor-outlet.test.ts +0 -94
  185. package/src/host-instance/host-instance.test.ts +0 -49
  186. package/src/modules/animated/animated-leaf-binder/animated-leaf-binder.test.ts +0 -116
  187. package/src/modules/animated/animated-leaf-binder.test.ts +0 -116
  188. package/src/modules/animated/animated-scroll-view.test.ts +0 -102
  189. package/src/modules/app-registry/app-registry.test.ts +0 -106
  190. package/src/modules/status-bar/status-bar.test.ts +0 -50
  191. package/src/render/render.test.ts +0 -147
  192. package/src/renderer/renderer.test.ts +0 -202
  193. package/src/services/services.test.ts +0 -163
@@ -1,113 +0,0 @@
1
- // The device-faithful shape: a ROOT component whose template embeds a CHILD component that
2
- // owns the responder state + the {{status}} binding (App -> ResponderDemo). Angular 20 compiles
3
- // components as SignalView (not CheckAlways), so a plain (non-signal) state mutation inside a
4
- // flat-bag responder callback does NOT dirty the child's reactive consumer, and the scheduler's
5
- // root detectChanges() will NOT descend into it — the child's {{status}} used to stay stale
6
- // ("pan does nothing"). SymbioteHostPropsDirective now refreshes its own host component's view
7
- // right after the callback runs, so the nested child repaints.
8
-
9
- import '@angular/compiler';
10
- import { Component } from '@angular/core';
11
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
12
- import { installFabric, type IFakeNode } from '@symbiote-native/test-utils';
13
-
14
- import { mount, unmount } from '../render';
15
- import { ViewHost as View, TextHost as Text, SymbioteHostPropsDirective } from '../primitives';
16
- import { registerComposedComponent } from '../renderer';
17
-
18
- const ROOT_TAG = 972;
19
- const TOUCH_START = 'topTouchStart';
20
- const TOUCH_MOVE = 'topTouchMove';
21
- const TOUCH_END = 'topTouchEnd';
22
-
23
- registerComposedComponent('nested-responder-inner');
24
-
25
- const fabric = installFabric();
26
-
27
- function findCommitted(predicate: (node: IFakeNode) => boolean): IFakeNode | undefined {
28
- const stack = [...fabric.committed];
29
- while (stack.length > 0) {
30
- const node = stack.pop();
31
- if (node === undefined) continue;
32
- if (predicate(node)) return node;
33
- stack.push(...node.children);
34
- }
35
- return undefined;
36
- }
37
-
38
- function statusText(testID: string): string | undefined {
39
- const node = findCommitted(n => n.props.testID === testID);
40
- const raw = node?.children[0];
41
- return typeof raw?.props.text === 'string' ? raw.props.text : undefined;
42
- }
43
-
44
- function handleFor(testID: string): unknown {
45
- const node = fabric.find(n => n.props.testID === testID);
46
- if (!node) throw new Error(`no node created with testID=${testID}`);
47
- return node.instanceHandle;
48
- }
49
-
50
- const flush = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
51
-
52
- @Component({
53
- selector: 'nested-responder-inner',
54
- standalone: true,
55
- imports: [View, Text, SymbioteHostPropsDirective],
56
- template: `
57
- <View [symbioteHostProps]="handlers"></View>
58
- <Text [symbioteHostProps]="statusProps">{{ status }}</Text>
59
- `,
60
- })
61
- class NestedResponderInner {
62
- status = 'idle';
63
- statusProps = { testID: 'nested-status' };
64
- handlers = {
65
- testID: 'nested-chip',
66
- onStartShouldSetResponder: () => true,
67
- onResponderGrant: () => {
68
- this.status = 'granted';
69
- },
70
- onResponderMove: () => {
71
- this.status = 'moving';
72
- },
73
- onResponderRelease: () => {
74
- this.status = 'released';
75
- },
76
- };
77
- }
78
-
79
- @Component({
80
- selector: 'nested-responder-outer',
81
- standalone: true,
82
- imports: [View, NestedResponderInner],
83
- template: `
84
- <View>
85
- <nested-responder-inner></nested-responder-inner>
86
- </View>
87
- `,
88
- })
89
- class NestedResponderOuter {}
90
-
91
- beforeEach(() => fabric.reset());
92
- afterEach(() => unmount(ROOT_TAG));
93
-
94
- describe('Angular nested-child responder CD', () => {
95
- it('re-renders a nested child component`s state mutated in a flat-bag responder callback', async () => {
96
- mount(ROOT_TAG, NestedResponderOuter);
97
- await flush();
98
- expect(statusText('nested-status')).toBe('idle');
99
-
100
- const chip = handleFor('nested-chip');
101
- fabric.fireEvent(chip, TOUCH_START);
102
- await flush();
103
- expect(statusText('nested-status')).toBe('granted');
104
-
105
- fabric.fireEvent(chip, TOUCH_MOVE);
106
- await flush();
107
- expect(statusText('nested-status')).toBe('moving');
108
-
109
- fabric.fireEvent(chip, TOUCH_END);
110
- await flush();
111
- expect(statusText('nested-status')).toBe('released');
112
- });
113
- });
@@ -1,142 +0,0 @@
1
- /// <reference types="node" />
2
-
3
- import { readFileSync } from 'node:fs';
4
- import { describe, expect, it } from 'vitest';
5
-
6
- const readSource = (path: string): string => readFileSync(path, 'utf8');
7
-
8
- describe('Angular adapter gap regressions', () => {
9
- it('VirtualizedList exposes accessibility and aria inputs and forwards them to ScrollView', () => {
10
- const source = readSource('adapters/angular/src/components/virtualized-list/index.ts');
11
-
12
- expect(source).toContain('@Input() accessibilityLabel?: string');
13
- expect(source).toContain('@Input() ariaBusy?: boolean');
14
- expect(source).toContain('[accessibilityLabel]="foldedAccessibility.accessibilityLabel"');
15
- expect(source).toContain('[accessibilityState]="foldedAccessibility.accessibilityState"');
16
- expect(source).toContain('[accessibilityRole]="foldedAccessibility.accessibilityRole"');
17
- });
18
-
19
- it('Animated namespace exposes AOT-safe built-in entries and documents runtime HOC as a non-goal', () => {
20
- const animatedSource = readSource('adapters/angular/src/modules/animated/index.ts');
21
- const componentSource = readSource(
22
- 'adapters/angular/src/modules/animated/create-animated-component.ts',
23
- );
24
-
25
- expect(componentSource).toContain('export const AnimatedFlatList');
26
- expect(componentSource).toContain('export const AnimatedSectionList');
27
- expect(componentSource).toContain('if (base === View) return AnimatedView');
28
- expect(componentSource).toContain('if (base === Text) return AnimatedText');
29
- expect(componentSource).toContain('if (base === Image) return AnimatedImage');
30
- expect(componentSource).toContain('if (base === ScrollView) return AnimatedScrollView');
31
- expect(componentSource).toContain('Angular cannot synthesize a component at runtime');
32
- expect(componentSource).toContain('no JIT under AOT/Metro');
33
- expect(componentSource).toContain(
34
- 'Author an explicit standalone @Component extending AnimatedComponentBase',
35
- );
36
- expect(animatedSource).toContain('View: AnimatedView');
37
- expect(animatedSource).toContain('Text: AnimatedText');
38
- expect(animatedSource).toContain('Image: AnimatedImage');
39
- expect(animatedSource).toContain('ScrollView: AnimatedScrollView');
40
- expect(animatedSource).toContain('FlatList: AnimatedFlatList');
41
- expect(animatedSource).toContain('SectionList: AnimatedSectionList');
42
- });
43
-
44
- it('AnimatedImage uses composed Image props and normalized load events', () => {
45
- const source = readSource('adapters/angular/src/modules/animated/create-animated-component.ts');
46
-
47
- expect(source).toContain('export class AnimatedImage extends ImageBase');
48
- expect(source).toContain('inputs: ANIMATED_IMAGE_INPUTS');
49
- expect(source).toContain('outputs: IMAGE_OUTPUTS');
50
- expect(source).toContain('const resolved = resolveImageProps(reduced)');
51
- expect(source).toContain('(load)="handleLoad($event)"');
52
- expect(source).toContain('(error)="handleError($event)"');
53
- });
54
-
55
- it('Image prop resolution is shared with AnimatedImage', () => {
56
- const imageSource = readSource('adapters/angular/src/components/image/shared.ts');
57
-
58
- expect(imageSource).toContain('export function resolveImageProps');
59
- expect(imageSource).toContain('get imageProps(): Record<string, unknown>');
60
- expect(imageSource).toContain('return resolveImageProps(this.imageInputProps)');
61
- });
62
-
63
- it('Button forwards its full accessibility and TV-focus surface through TouchableOpacity', () => {
64
- const buttonSource = readSource('adapters/angular/src/components/button.ts');
65
- const touchableSource = readSource('adapters/angular/src/components/touchable/index.ts');
66
- const pressableSource = readSource('adapters/angular/src/components/pressable/index.ts');
67
-
68
- for (const binding of [
69
- '[accessible]="true"',
70
- '[accessibilityLabelledBy]="accessibilityLabelledBy"',
71
- '[importantForAccessibility]="importantForAccessibility"',
72
- '[accessibilityLiveRegion]="accessibilityLiveRegion"',
73
- '[screenReaderFocusable]="screenReaderFocusable"',
74
- '[accessibilityViewIsModal]="accessibilityViewIsModal"',
75
- '[accessibilityElementsHidden]="accessibilityElementsHidden"',
76
- '[accessibilityIgnoresInvertColors]="accessibilityIgnoresInvertColors"',
77
- '[accessibilityLanguage]="accessibilityLanguage"',
78
- '[accessibilityRespondsToUserInteraction]="accessibilityRespondsToUserInteraction"',
79
- '[accessibilityShowsLargeContentViewer]="accessibilityShowsLargeContentViewer"',
80
- '[accessibilityLargeContentTitle]="accessibilityLargeContentTitle"',
81
- '(accessibilityAction)="accessibilityAction.emit($event)"',
82
- '(accessibilityTap)="accessibilityTap.emit($event)"',
83
- '(magicTap)="magicTap.emit($event)"',
84
- '(accessibilityEscape)="accessibilityEscape.emit($event)"',
85
- '[ariaModal]="ariaModal"',
86
- '[ariaValueMax]="ariaValueMax"',
87
- '[ariaValueMin]="ariaValueMin"',
88
- '[ariaValueNow]="ariaValueNow"',
89
- '[ariaValueText]="ariaValueText"',
90
- '[hasTVPreferredFocus]="hasTVPreferredFocus"',
91
- '[nextFocusDown]="nextFocusDown"',
92
- '[nextFocusForward]="nextFocusForward"',
93
- '[nextFocusLeft]="nextFocusLeft"',
94
- '[nextFocusRight]="nextFocusRight"',
95
- '[nextFocusUp]="nextFocusUp"',
96
- ]) {
97
- expect(buttonSource).toContain(binding);
98
- }
99
-
100
- for (const binding of [
101
- '[accessible]="accessible"',
102
- '[accessibilityLabelledBy]="accessibilityLabelledBy"',
103
- '[importantForAccessibility]="importantForAccessibility"',
104
- '[accessibilityLiveRegion]="accessibilityLiveRegion"',
105
- '[screenReaderFocusable]="screenReaderFocusable"',
106
- '[accessibilityViewIsModal]="accessibilityViewIsModal"',
107
- '[accessibilityElementsHidden]="accessibilityElementsHidden"',
108
- '[accessibilityIgnoresInvertColors]="accessibilityIgnoresInvertColors"',
109
- '[accessibilityLanguage]="accessibilityLanguage"',
110
- '[accessibilityRespondsToUserInteraction]="accessibilityRespondsToUserInteraction"',
111
- '[accessibilityShowsLargeContentViewer]="accessibilityShowsLargeContentViewer"',
112
- '[accessibilityLargeContentTitle]="accessibilityLargeContentTitle"',
113
- '(accessibilityAction)="accessibilityAction.emit($event)"',
114
- '(accessibilityTap)="accessibilityTap.emit($event)"',
115
- '(magicTap)="magicTap.emit($event)"',
116
- '(accessibilityEscape)="accessibilityEscape.emit($event)"',
117
- '[ariaModal]="ariaModal"',
118
- '[ariaValueMax]="ariaValueMax"',
119
- '[ariaValueMin]="ariaValueMin"',
120
- '[ariaValueNow]="ariaValueNow"',
121
- '[ariaValueText]="ariaValueText"',
122
- '[hasTVPreferredFocus]="hasTVPreferredFocus"',
123
- '[nextFocusDown]="nextFocusDown"',
124
- '[nextFocusForward]="nextFocusForward"',
125
- '[nextFocusLeft]="nextFocusLeft"',
126
- '[nextFocusRight]="nextFocusRight"',
127
- '[nextFocusUp]="nextFocusUp"',
128
- ]) {
129
- expect(touchableSource).toContain(binding);
130
- }
131
-
132
- // Pressable forwards its resolved props through the shared SymbioteHostPropsDirective
133
- // (adapters/angular/src/primitives/shared.ts) rather than one `[prop]="x"` binding per
134
- // key, so the contract to check is: the binding exists, and the `hostProps` getter it
135
- // reads from actually assembles `accessible` / the folded accessibility bag / TV-focus.
136
- expect(pressableSource).toContain('[symbioteHostProps]="hostProps"');
137
- expect(pressableSource).toContain('accessible: this.accessible');
138
- expect(pressableSource).toContain('...this.foldedAccessibility');
139
- expect(pressableSource).toContain('hasTVPreferredFocus: this.hasTVPreferredFocus');
140
- expect(pressableSource).toContain('(accessibilityAction)="emit(accessibilityAction, $event)"');
141
- });
142
- });
@@ -1,100 +0,0 @@
1
- // Angular source-import harness regression. Vitest imports adapter source (`src/*.ts`) directly,
2
- // not the partial-Ivy `ngc` output Metro consumes. The adapter tsconfig must therefore enable
3
- // TypeScript's legacy decorator lowering so Node never sees raw `@Component` / `@Directive`
4
- // syntax. Importing `@angular/compiler` enables JIT metadata creation for these source-only tests;
5
- // production still proves AOT/partial-Ivy through `pnpm --filter @symbiote-native/angular ng:build`.
6
-
7
- import '@angular/compiler';
8
- import { ElementRef, Injector, runInInjectionContext } from '@angular/core';
9
- import { describe, expect, it } from 'vitest';
10
- import { Image, ScrollView, Text, View } from './components';
11
- import { VirtualizedList, VListItemDirective } from './components/virtualized-list';
12
- import { SymbioteHostPropsDirective } from './primitives';
13
- import {
14
- Animated,
15
- AnimatedImage,
16
- AnimatedScrollView,
17
- AnimatedText,
18
- AnimatedView,
19
- createAnimatedComponent,
20
- } from './modules/animated';
21
-
22
- interface IAngularCompiledComponent {
23
- ɵcmp?: { selectors?: unknown };
24
- }
25
-
26
- interface IAngularCompiledDirective {
27
- ɵdir?: { selectors?: unknown };
28
- }
29
-
30
- describe('Angular source imports under Vitest', () => {
31
- it('imports decorated adapter components and directives directly from src', () => {
32
- expect(VirtualizedList).toBeTypeOf('function');
33
- expect(VListItemDirective).toBeTypeOf('function');
34
- expect(AnimatedView).toBeTypeOf('function');
35
- expect(SymbioteHostPropsDirective).toBeTypeOf('function');
36
- });
37
-
38
- it('keeps Angular decorator metadata available to the JIT test runtime', () => {
39
- expect((VirtualizedList as IAngularCompiledComponent).ɵcmp?.selectors).toEqual([
40
- ['VirtualizedList'],
41
- ]);
42
- expect((AnimatedView as IAngularCompiledComponent).ɵcmp?.selectors).toEqual([
43
- ['AnimatedView'],
44
- ['symbiote-animated-view'],
45
- ]);
46
- expect((VListItemDirective as IAngularCompiledDirective).ɵdir?.selectors).toEqual([
47
- ['', 'vListItem', ''],
48
- ]);
49
- expect((SymbioteHostPropsDirective as IAngularCompiledDirective).ɵdir?.selectors).toEqual([
50
- ['', 'symbioteHostProps', ''],
51
- ]);
52
- });
53
-
54
- it('imports the Animated namespace without losing component identity', () => {
55
- expect(Animated.View).toBe(AnimatedView);
56
- expect(Animated.Text).toBe(AnimatedText);
57
- expect(Animated.Image).toBe(AnimatedImage);
58
- expect(Animated.ScrollView).toBe(AnimatedScrollView);
59
- expect(Animated.FlatList).toBeTypeOf('function');
60
- expect(Animated.SectionList).toBeTypeOf('function');
61
- });
62
-
63
- it('keeps createAnimatedComponent limited to pre-authored AOT-safe Angular wrappers', () => {
64
- class CustomComponent {}
65
-
66
- expect(createAnimatedComponent(View)).toBe(AnimatedView);
67
- expect(createAnimatedComponent(Text)).toBe(AnimatedText);
68
- expect(createAnimatedComponent(Image)).toBe(AnimatedImage);
69
- expect(createAnimatedComponent(ScrollView)).toBe(AnimatedScrollView);
70
- expect(() => createAnimatedComponent(CustomComponent)).toThrow(
71
- /Angular cannot synthesize a component at runtime \(no JIT compiler under AOT\/Metro\)/,
72
- );
73
- expect(() => createAnimatedComponent(CustomComponent)).toThrow(
74
- /Author an explicit standalone @Component extending AnimatedComponentBase instead/,
75
- );
76
- });
77
-
78
- it('resolves AnimatedImage props through the composed Image path', () => {
79
- // AnimatedImage is an ANCHOR_HOST_COMPONENT: its field initializer injects its own
80
- // ElementRef (anchorHostStyle merge, see create-animated-component.ts), so constructing it
81
- // outside Angular's component machinery needs an explicit injection context.
82
- const injector = Injector.create({
83
- providers: [{ provide: ElementRef, useValue: new ElementRef({}) }],
84
- });
85
- const image = runInInjectionContext(injector, () => new AnimatedImage());
86
- image.src = 'https://example.invalid/image.png';
87
- image.width = 32;
88
- image.height = 24;
89
- image.alt = 'Preview';
90
- image.animatedProps = { style: { opacity: 0.5 }, testID: 'animated-image' };
91
-
92
- expect(image.animatedImageProps).toMatchObject({
93
- testID: 'animated-image',
94
- accessible: true,
95
- accessibilityLabel: 'Preview',
96
- source: [{ uri: 'https://example.invalid/image.png' }],
97
- style: [undefined, [{ width: 32, height: 24 }, { opacity: 0.5 }]],
98
- });
99
- });
100
- });
@@ -1,44 +0,0 @@
1
- // Regression test for the anchor/class bug: ActivityIndicator is its own
2
- // ANCHOR_HOST_COMPONENTS entry AND renders through DescriptorOutlet (`symbiote-descriptor-outlet`,
3
- // itself also an anchor entry) — a class= on <ActivityIndicator> resolves onto ActivityIndicator's
4
- // OWN anchor, two levels up from the real committed wrapper View the descriptor builds, and needs
5
- // its OWN anchorHostStyle merge (see shared.ts's `descriptor` getter). Mirrors pressable.test.ts's
6
- // "resolves a class=" case.
7
- import '@angular/compiler';
8
- import { Component } from '@angular/core';
9
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
10
- import { clearGlobalStyles, registerStyles } from '@symbiote-native/engine';
11
- import { installFabric } from '@symbiote-native/test-utils';
12
-
13
- import { mount, unmount } from '../../render';
14
- import { ActivityIndicator } from './index';
15
-
16
- const ROOT_TAG = 909;
17
- const fabric = installFabric();
18
-
19
- @Component({
20
- selector: 'symbiote-activity-indicator-class-host',
21
- standalone: true,
22
- imports: [ActivityIndicator],
23
- template: `<ActivityIndicator class="card" />`,
24
- })
25
- class ActivityIndicatorClassHost {}
26
-
27
- beforeEach(() => {
28
- fabric.reset();
29
- registerStyles({ card: { backgroundColor: 'red' } });
30
- });
31
- afterEach(() => {
32
- unmount(ROOT_TAG);
33
- clearGlobalStyles();
34
- });
35
-
36
- describe('ActivityIndicator anchor class= resolution', () => {
37
- it('resolves a class= on the ActivityIndicator use site onto the real committed wrapper View', async () => {
38
- mount(ROOT_TAG, ActivityIndicatorClassHost);
39
- await new Promise<void>(resolve => setTimeout(resolve, 0));
40
-
41
- const node = fabric.find(n => n.props.backgroundColor === 'red');
42
- expect(node, 'a real Fabric node carries the class-derived style').toBeDefined();
43
- });
44
- });
@@ -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
- });