@thelacanians/vue-native-cli 0.4.12 → 0.4.14

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 (70) hide show
  1. package/dist/cli.js +6 -1
  2. package/native/android/.editorconfig +25 -0
  3. package/native/android/VueNativeCore/build.gradle.kts +25 -1
  4. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/JSPolyfills.kt +17 -10
  5. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/JSRuntime.kt +5 -5
  6. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +13 -13
  7. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/ComponentRegistry.kt +27 -27
  8. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VActionSheetFactory.kt +6 -4
  9. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VActivityIndicatorFactory.kt +1 -1
  10. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VAlertDialogFactory.kt +24 -12
  11. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VButtonFactory.kt +5 -2
  12. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VImageFactory.kt +7 -7
  13. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VInputFactory.kt +12 -12
  14. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VKeyboardAvoidingFactory.kt +0 -1
  15. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VListFactory.kt +5 -2
  16. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VModalFactory.kt +5 -2
  17. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VPickerFactory.kt +3 -2
  18. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VPressableFactory.kt +5 -3
  19. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VRootFactory.kt +5 -2
  20. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VScrollViewFactory.kt +5 -2
  21. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSectionListFactory.kt +5 -2
  22. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSegmentedControlFactory.kt +3 -3
  23. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VStatusBarFactory.kt +3 -3
  24. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSwitchFactory.kt +0 -1
  25. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VViewFactory.kt +9 -3
  26. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VWebViewFactory.kt +7 -5
  27. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/NativeComponentFactory.kt +5 -2
  28. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Helpers/GestureHelper.kt +4 -1
  29. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AnimationModule.kt +77 -21
  30. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AsyncStorageModule.kt +20 -5
  31. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/BackgroundTaskModule.kt +12 -3
  32. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/BiometryModule.kt +5 -2
  33. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/BluetoothModule.kt +88 -23
  34. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/CalendarModule.kt +24 -11
  35. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/ClipboardModule.kt +7 -2
  36. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/ContactsModule.kt +24 -12
  37. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/DeviceInfoModule.kt +14 -11
  38. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/FileSystemModule.kt +79 -24
  39. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/GeolocationModule.kt +10 -7
  40. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/HapticsModule.kt +5 -5
  41. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/HttpModule.kt +17 -8
  42. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/IAPModule.kt +20 -5
  43. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/KeyboardModule.kt +4 -1
  44. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/LinkingModule.kt +12 -3
  45. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NetworkModule.kt +4 -1
  46. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NotificationsModule.kt +24 -6
  47. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/OTAModule.kt +13 -5
  48. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/PerformanceModule.kt +8 -2
  49. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/PermissionsModule.kt +17 -8
  50. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/SecureStorageModule.kt +20 -5
  51. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/SensorsModule.kt +16 -4
  52. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/ShareModule.kt +6 -3
  53. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/SocialAuthModule.kt +4 -2
  54. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/WebSocketModule.kt +26 -8
  55. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Styling/StyleEngine.kt +127 -84
  56. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Tags.kt +26 -26
  57. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/VueNativeActivity.kt +1 -1
  58. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ComponentRegistryTest.kt +173 -0
  59. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeBridgeTest.kt +436 -0
  60. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeModuleRegistryTest.kt +251 -0
  61. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/StyleEngineTest.kt +482 -0
  62. package/native/android/build.gradle.kts +1 -0
  63. package/native/ios/.swiftlint.yml +62 -0
  64. package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/JSPolyfills.swift +15 -2
  65. package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +4 -1
  66. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/ComponentRegistryTests.swift +237 -0
  67. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/NativeBridgeOperationTests.swift +398 -0
  68. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/NativeModuleRegistryTests.swift +203 -0
  69. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/StyleEngineTests.swift +381 -0
  70. package/package.json +1 -1
@@ -0,0 +1,436 @@
1
+ package com.vuenative.core
2
+
3
+ import android.content.Context
4
+ import android.os.Looper
5
+ import android.view.ViewGroup
6
+ import android.widget.FrameLayout
7
+ import androidx.test.core.app.ApplicationProvider
8
+ import com.google.android.flexbox.FlexboxLayout
9
+ import org.junit.After
10
+ import org.junit.Assert.assertEquals
11
+ import org.junit.Assert.assertFalse
12
+ import org.junit.Assert.assertNotNull
13
+ import org.junit.Assert.assertNull
14
+ import org.junit.Assert.assertTrue
15
+ import org.junit.Before
16
+ import org.junit.Test
17
+ import org.junit.runner.RunWith
18
+ import org.robolectric.RobolectricTestRunner
19
+ import org.robolectric.Shadows
20
+ import org.robolectric.annotation.Config
21
+
22
+ @RunWith(RobolectricTestRunner::class)
23
+ @Config(sdk = [34])
24
+ class NativeBridgeTest {
25
+
26
+ private lateinit var context: Context
27
+ private lateinit var bridge: NativeBridge
28
+
29
+ @Before
30
+ fun setUp() {
31
+ // Reset ComponentRegistry singleton via reflection
32
+ val crField = ComponentRegistry::class.java.getDeclaredField("instance")
33
+ crField.isAccessible = true
34
+ crField.set(null, null)
35
+
36
+ // Reset NativeModuleRegistry singleton via reflection
37
+ val nmrField = NativeModuleRegistry::class.java.getDeclaredField("instance")
38
+ nmrField.isAccessible = true
39
+ nmrField.set(null, null)
40
+
41
+ context = ApplicationProvider.getApplicationContext()
42
+ bridge = NativeBridge(context)
43
+
44
+ // Set up a host container so setRootView can attach views
45
+ val container = FrameLayout(context)
46
+ bridge.hostContainer = container
47
+ }
48
+
49
+ @After
50
+ fun tearDown() {
51
+ bridge.clearAllRegistries()
52
+ }
53
+
54
+ private fun flush() {
55
+ Shadows.shadowOf(Looper.getMainLooper()).idle()
56
+ }
57
+
58
+ // -------------------------------------------------------------------------
59
+ // create
60
+ // -------------------------------------------------------------------------
61
+
62
+ @Test
63
+ fun testCreate() {
64
+ bridge.processOperations("""[{"op":"create","args":[1,"VView"]}]""")
65
+ flush()
66
+
67
+ assertNotNull("nodeViews[1] should exist", bridge.nodeViews[1])
68
+ assertEquals("VView", bridge.nodeTypes[1])
69
+ assertTrue("VView should create a FlexboxLayout", bridge.nodeViews[1] is FlexboxLayout)
70
+ }
71
+
72
+ // -------------------------------------------------------------------------
73
+ // createText
74
+ // -------------------------------------------------------------------------
75
+
76
+ @Test
77
+ fun testCreateText() {
78
+ bridge.processOperations("""[{"op":"createText","args":[2,"Hello"]}]""")
79
+ flush()
80
+
81
+ assertNotNull("nodeViews[2] should exist", bridge.nodeViews[2])
82
+ assertTrue("createText should produce VTextNodeView", bridge.nodeViews[2] is VTextNodeView)
83
+ assertEquals("Hello", (bridge.nodeViews[2] as VTextNodeView).text.toString())
84
+ }
85
+
86
+ // -------------------------------------------------------------------------
87
+ // appendChild
88
+ // -------------------------------------------------------------------------
89
+
90
+ @Test
91
+ fun testAppendChild() {
92
+ bridge.processOperations(
93
+ """[
94
+ {"op":"create","args":[1,"VView"]},
95
+ {"op":"create","args":[2,"VView"]},
96
+ {"op":"appendChild","args":[1,2]}
97
+ ]"""
98
+ )
99
+ flush()
100
+
101
+ val parent = bridge.nodeViews[1] as ViewGroup
102
+ val child = bridge.nodeViews[2]!!
103
+ assertTrue("Child should be a child of parent", parent.indexOfChild(child) >= 0)
104
+ assertEquals("nodeParents should track parent", 1, bridge.nodeParents[2])
105
+ assertTrue("nodeChildren should track child", bridge.nodeChildren[1]?.contains(2) == true)
106
+ }
107
+
108
+ // -------------------------------------------------------------------------
109
+ // removeChild
110
+ // -------------------------------------------------------------------------
111
+
112
+ @Test
113
+ fun testRemoveChild() {
114
+ bridge.processOperations(
115
+ """[
116
+ {"op":"create","args":[1,"VView"]},
117
+ {"op":"create","args":[2,"VView"]},
118
+ {"op":"appendChild","args":[1,2]}
119
+ ]"""
120
+ )
121
+ flush()
122
+
123
+ // Verify child is attached
124
+ val parent = bridge.nodeViews[1] as ViewGroup
125
+ assertNotNull(bridge.nodeViews[2])
126
+ assertTrue(parent.indexOfChild(bridge.nodeViews[2]!!) >= 0)
127
+
128
+ // Remove child
129
+ bridge.processOperations("""[{"op":"removeChild","args":[2]}]""")
130
+ flush()
131
+
132
+ assertFalse("nodeViews should not contain removed child", bridge.nodeViews.containsKey(2))
133
+ assertNull("nodeParents should not contain removed child", bridge.nodeParents[2])
134
+ assertEquals("Parent ViewGroup should have 0 children", 0, parent.childCount)
135
+ }
136
+
137
+ // -------------------------------------------------------------------------
138
+ // insertBefore
139
+ // -------------------------------------------------------------------------
140
+
141
+ @Test
142
+ fun testInsertBefore() {
143
+ bridge.processOperations(
144
+ """[
145
+ {"op":"create","args":[1,"VView"]},
146
+ {"op":"create","args":[2,"VView"]},
147
+ {"op":"create","args":[3,"VView"]},
148
+ {"op":"appendChild","args":[1,2]},
149
+ {"op":"insertBefore","args":[1,3,2]}
150
+ ]"""
151
+ )
152
+ flush()
153
+
154
+ val parent = bridge.nodeViews[1] as ViewGroup
155
+ val child2 = bridge.nodeViews[2]!!
156
+ val child3 = bridge.nodeViews[3]!!
157
+
158
+ val idx2 = parent.indexOfChild(child2)
159
+ val idx3 = parent.indexOfChild(child3)
160
+ assertTrue("child3 should be before child2", idx3 < idx2)
161
+ assertEquals("child3 should be at index 0", 0, idx3)
162
+ assertEquals("child2 should be at index 1", 1, idx2)
163
+ }
164
+
165
+ // -------------------------------------------------------------------------
166
+ // updateProp
167
+ // -------------------------------------------------------------------------
168
+
169
+ @Test
170
+ fun testUpdateProp() {
171
+ bridge.processOperations(
172
+ """[
173
+ {"op":"create","args":[1,"VView"]},
174
+ {"op":"updateProp","args":[1,"backgroundColor","#ff0000"]}
175
+ ]"""
176
+ )
177
+ flush()
178
+
179
+ val view = bridge.nodeViews[1]!!
180
+ // After setting backgroundColor, the view should have a GradientDrawable background
181
+ assertNotNull("View background should be set", view.background)
182
+ assertTrue(
183
+ "Background should be a GradientDrawable",
184
+ view.background is android.graphics.drawable.GradientDrawable
185
+ )
186
+ }
187
+
188
+ // -------------------------------------------------------------------------
189
+ // updateStyle
190
+ // -------------------------------------------------------------------------
191
+
192
+ @Test
193
+ fun testUpdateStyle() {
194
+ bridge.processOperations(
195
+ """[
196
+ {"op":"create","args":[1,"VView"]},
197
+ {"op":"updateStyle","args":[1,{"opacity":0.5}]}
198
+ ]"""
199
+ )
200
+ flush()
201
+
202
+ val view = bridge.nodeViews[1]!!
203
+ assertEquals("alpha should be 0.5", 0.5f, view.alpha, 0.01f)
204
+ }
205
+
206
+ // -------------------------------------------------------------------------
207
+ // setText
208
+ // -------------------------------------------------------------------------
209
+
210
+ @Test
211
+ fun testSetText() {
212
+ bridge.processOperations(
213
+ """[
214
+ {"op":"createText","args":[1,"Hello"]},
215
+ {"op":"setText","args":[1,"World"]}
216
+ ]"""
217
+ )
218
+ flush()
219
+
220
+ val textView = bridge.nodeViews[1] as VTextNodeView
221
+ assertEquals("World", textView.text.toString())
222
+ }
223
+
224
+ // -------------------------------------------------------------------------
225
+ // addEventListener
226
+ // -------------------------------------------------------------------------
227
+
228
+ @Test
229
+ fun testAddEventListener() {
230
+ bridge.processOperations(
231
+ """[
232
+ {"op":"create","args":[1,"VView"]},
233
+ {"op":"addEventListener","args":[1,"press"]}
234
+ ]"""
235
+ )
236
+ flush()
237
+
238
+ assertTrue(
239
+ "eventHandlers should have entry for '1:press'",
240
+ bridge.eventHandlers.containsKey("1:press")
241
+ )
242
+ }
243
+
244
+ // -------------------------------------------------------------------------
245
+ // removeEventListener
246
+ // -------------------------------------------------------------------------
247
+
248
+ @Test
249
+ fun testRemoveEventListener() {
250
+ bridge.processOperations(
251
+ """[
252
+ {"op":"create","args":[1,"VView"]},
253
+ {"op":"addEventListener","args":[1,"press"]},
254
+ {"op":"removeEventListener","args":[1,"press"]}
255
+ ]"""
256
+ )
257
+ flush()
258
+
259
+ assertFalse(
260
+ "eventHandlers should not have entry for '1:press'",
261
+ bridge.eventHandlers.containsKey("1:press")
262
+ )
263
+ }
264
+
265
+ // -------------------------------------------------------------------------
266
+ // setRootView
267
+ // -------------------------------------------------------------------------
268
+
269
+ @Test
270
+ fun testSetRootView() {
271
+ bridge.processOperations(
272
+ """[
273
+ {"op":"create","args":[1,"VView"]},
274
+ {"op":"setRootView","args":[1]}
275
+ ]"""
276
+ )
277
+ flush()
278
+
279
+ assertNotNull("rootView should be set", bridge.rootView)
280
+ assertEquals("rootView should be nodeViews[1]", bridge.nodeViews[1], bridge.rootView)
281
+ assertEquals(
282
+ "hostContainer should have 1 child",
283
+ 1,
284
+ bridge.hostContainer!!.childCount
285
+ )
286
+ }
287
+
288
+ // -------------------------------------------------------------------------
289
+ // clearAllRegistries
290
+ // -------------------------------------------------------------------------
291
+
292
+ @Test
293
+ fun testClearAllRegistries() {
294
+ bridge.processOperations(
295
+ """[
296
+ {"op":"create","args":[1,"VView"]},
297
+ {"op":"create","args":[2,"VView"]},
298
+ {"op":"appendChild","args":[1,2]}
299
+ ]"""
300
+ )
301
+ flush()
302
+
303
+ // Verify state exists
304
+ assertTrue(bridge.nodeViews.isNotEmpty())
305
+ assertTrue(bridge.nodeTypes.isNotEmpty())
306
+ assertTrue(bridge.nodeParents.isNotEmpty())
307
+ assertTrue(bridge.nodeChildren.isNotEmpty())
308
+
309
+ bridge.clearAllRegistries()
310
+
311
+ assertTrue("nodeViews should be empty", bridge.nodeViews.isEmpty())
312
+ assertTrue("nodeTypes should be empty", bridge.nodeTypes.isEmpty())
313
+ assertTrue("eventHandlers should be empty", bridge.eventHandlers.isEmpty())
314
+ assertTrue("nodeParents should be empty", bridge.nodeParents.isEmpty())
315
+ assertTrue("nodeChildren should be empty", bridge.nodeChildren.isEmpty())
316
+ assertNull("rootView should be null", bridge.rootView)
317
+ }
318
+
319
+ // -------------------------------------------------------------------------
320
+ // fireEvent
321
+ // -------------------------------------------------------------------------
322
+
323
+ @Test
324
+ fun testFireEvent() {
325
+ var capturedNodeId = -1
326
+ var capturedEventName = ""
327
+ var capturedPayloadJson = ""
328
+
329
+ bridge.onFireEvent = { nodeId, eventName, payloadJson ->
330
+ capturedNodeId = nodeId
331
+ capturedEventName = eventName
332
+ capturedPayloadJson = payloadJson
333
+ }
334
+
335
+ bridge.fireEvent(42, "press", mapOf("x" to 10, "y" to 20))
336
+
337
+ assertEquals(42, capturedNodeId)
338
+ assertEquals("press", capturedEventName)
339
+ assertTrue(
340
+ "Payload should contain x and y",
341
+ capturedPayloadJson.contains("\"x\"") && capturedPayloadJson.contains("\"y\"")
342
+ )
343
+ }
344
+
345
+ // -------------------------------------------------------------------------
346
+ // dispatchGlobalEvent
347
+ // -------------------------------------------------------------------------
348
+
349
+ @Test
350
+ fun testDispatchGlobalEvent() {
351
+ var capturedEventName = ""
352
+ var capturedPayloadJson = ""
353
+
354
+ bridge.onDispatchGlobalEvent = { eventName, payloadJson ->
355
+ capturedEventName = eventName
356
+ capturedPayloadJson = payloadJson
357
+ }
358
+
359
+ bridge.dispatchGlobalEvent("networkChange", mapOf("connected" to "true"))
360
+
361
+ assertEquals("networkChange", capturedEventName)
362
+ assertTrue(
363
+ "Payload should contain connected",
364
+ capturedPayloadJson.contains("\"connected\"")
365
+ )
366
+ }
367
+
368
+ // -------------------------------------------------------------------------
369
+ // invalidJson
370
+ // -------------------------------------------------------------------------
371
+
372
+ @Test
373
+ fun testInvalidJson() {
374
+ // Should not crash
375
+ bridge.processOperations("this is not json")
376
+ flush()
377
+
378
+ // Verify bridge still works after invalid JSON
379
+ bridge.processOperations("""[{"op":"create","args":[1,"VView"]}]""")
380
+ flush()
381
+ assertNotNull(bridge.nodeViews[1])
382
+ }
383
+
384
+ // -------------------------------------------------------------------------
385
+ // unknownOperation
386
+ // -------------------------------------------------------------------------
387
+
388
+ @Test
389
+ fun testUnknownOperation() {
390
+ // Should not crash
391
+ bridge.processOperations("""[{"op":"unknown","args":[]}]""")
392
+ flush()
393
+
394
+ // Verify bridge still works after unknown operation
395
+ bridge.processOperations("""[{"op":"create","args":[1,"VView"]}]""")
396
+ flush()
397
+ assertNotNull(bridge.nodeViews[1])
398
+ }
399
+
400
+ // -------------------------------------------------------------------------
401
+ // cleanupNode
402
+ // -------------------------------------------------------------------------
403
+
404
+ @Test
405
+ fun testCleanupNode() {
406
+ // Create a parent with a child, child has a grandchild
407
+ bridge.processOperations(
408
+ """[
409
+ {"op":"create","args":[1,"VView"]},
410
+ {"op":"create","args":[2,"VView"]},
411
+ {"op":"create","args":[3,"VView"]},
412
+ {"op":"appendChild","args":[1,2]},
413
+ {"op":"appendChild","args":[2,3]},
414
+ {"op":"addEventListener","args":[3,"press"]}
415
+ ]"""
416
+ )
417
+ flush()
418
+
419
+ // Verify everything exists
420
+ assertNotNull(bridge.nodeViews[2])
421
+ assertNotNull(bridge.nodeViews[3])
422
+ assertTrue(bridge.eventHandlers.containsKey("3:press"))
423
+
424
+ // Remove child 2 (which should also clean up grandchild 3)
425
+ bridge.processOperations("""[{"op":"removeChild","args":[2]}]""")
426
+ flush()
427
+
428
+ assertFalse("nodeViews should not contain child 2", bridge.nodeViews.containsKey(2))
429
+ assertFalse("nodeViews should not contain grandchild 3", bridge.nodeViews.containsKey(3))
430
+ assertFalse("nodeTypes should not contain child 2", bridge.nodeTypes.containsKey(2))
431
+ assertFalse("nodeTypes should not contain grandchild 3", bridge.nodeTypes.containsKey(3))
432
+ assertFalse("eventHandlers for grandchild should be cleaned up", bridge.eventHandlers.containsKey("3:press"))
433
+ assertNull("nodeParents for child 2 should be null", bridge.nodeParents[2])
434
+ assertNull("nodeParents for grandchild 3 should be null", bridge.nodeParents[3])
435
+ }
436
+ }
@@ -0,0 +1,251 @@
1
+ package com.vuenative.core
2
+
3
+ import android.content.Context
4
+ import androidx.test.core.app.ApplicationProvider
5
+ import org.junit.Assert.assertEquals
6
+ import org.junit.Assert.assertNotNull
7
+ import org.junit.Assert.assertNull
8
+ import org.junit.Assert.assertTrue
9
+ import org.junit.Before
10
+ import org.junit.Test
11
+ import org.junit.runner.RunWith
12
+ import org.robolectric.RobolectricTestRunner
13
+ import org.robolectric.annotation.Config
14
+
15
+ /**
16
+ * Mock NativeModule for testing purposes.
17
+ */
18
+ class MockNativeModule(override val moduleName: String) : NativeModule {
19
+ var lastMethod: String? = null
20
+ var lastArgs: List<Any?> = emptyList()
21
+ var resultToReturn: Any? = null
22
+
23
+ override fun invoke(method: String, args: List<Any?>, bridge: NativeBridge, callback: (Any?, String?) -> Unit) {
24
+ lastMethod = method
25
+ lastArgs = args
26
+ callback(resultToReturn, null)
27
+ }
28
+
29
+ override fun invokeSync(method: String, args: List<Any?>, bridge: NativeBridge): Any? {
30
+ lastMethod = method
31
+ lastArgs = args
32
+ return resultToReturn
33
+ }
34
+ }
35
+
36
+ @RunWith(RobolectricTestRunner::class)
37
+ @Config(sdk = [34])
38
+ class NativeModuleRegistryTest {
39
+
40
+ private lateinit var context: Context
41
+ private lateinit var registry: NativeModuleRegistry
42
+ private lateinit var bridge: NativeBridge
43
+
44
+ @Before
45
+ fun setUp() {
46
+ // Reset NativeModuleRegistry singleton via reflection
47
+ val nmrField = NativeModuleRegistry::class.java.getDeclaredField("instance")
48
+ nmrField.isAccessible = true
49
+ nmrField.set(null, null)
50
+
51
+ // Reset ComponentRegistry singleton via reflection (needed for NativeBridge)
52
+ val crField = ComponentRegistry::class.java.getDeclaredField("instance")
53
+ crField.isAccessible = true
54
+ crField.set(null, null)
55
+
56
+ context = ApplicationProvider.getApplicationContext()
57
+ registry = NativeModuleRegistry.getInstance(context)
58
+ bridge = NativeBridge(context)
59
+ }
60
+
61
+ // -------------------------------------------------------------------------
62
+ // Register and get module
63
+ // -------------------------------------------------------------------------
64
+
65
+ @Test
66
+ fun testRegisterAndGetModule() {
67
+ val mock = MockNativeModule("TestModule")
68
+ registry.register(mock)
69
+
70
+ val retrieved = registry.getModule("TestModule")
71
+ assertNotNull("getModule should return the registered module", retrieved)
72
+ assertEquals("TestModule", retrieved!!.moduleName)
73
+ assertTrue("Retrieved module should be the same instance", retrieved === mock)
74
+ }
75
+
76
+ // -------------------------------------------------------------------------
77
+ // Get unknown module returns null
78
+ // -------------------------------------------------------------------------
79
+
80
+ @Test
81
+ fun testGetUnknownModule() {
82
+ val result = registry.getModule("nonexistent")
83
+ assertNull("getModule for unknown name should return null", result)
84
+ }
85
+
86
+ // -------------------------------------------------------------------------
87
+ // invoke — async
88
+ // -------------------------------------------------------------------------
89
+
90
+ @Test
91
+ fun testInvoke() {
92
+ val mock = MockNativeModule("TestModule")
93
+ mock.resultToReturn = mapOf("success" to true)
94
+ registry.register(mock)
95
+
96
+ var callbackResult: Any? = null
97
+ var callbackError: String? = "not_called"
98
+
99
+ registry.invoke("TestModule", "doSomething", listOf("arg1", 42), bridge) { result, error ->
100
+ callbackResult = result
101
+ callbackError = error
102
+ }
103
+
104
+ assertEquals("doSomething", mock.lastMethod)
105
+ assertEquals(listOf("arg1", 42), mock.lastArgs)
106
+ assertNotNull("Callback result should be non-null", callbackResult)
107
+ assertNull("Callback error should be null", callbackError)
108
+ }
109
+
110
+ // -------------------------------------------------------------------------
111
+ // invoke unknown module — callback receives error
112
+ // -------------------------------------------------------------------------
113
+
114
+ @Test
115
+ fun testInvokeUnknownModule() {
116
+ var callbackResult: Any? = "not_null"
117
+ var callbackError: String? = null
118
+
119
+ registry.invoke("NonExistent", "method", emptyList(), bridge) { result, error ->
120
+ callbackResult = result
121
+ callbackError = error
122
+ }
123
+
124
+ assertNull("Result should be null for unknown module", callbackResult)
125
+ assertNotNull("Error should be non-null for unknown module", callbackError)
126
+ assertTrue(
127
+ "Error should mention the module name",
128
+ callbackError!!.contains("NonExistent")
129
+ )
130
+ }
131
+
132
+ // -------------------------------------------------------------------------
133
+ // invokeSync
134
+ // -------------------------------------------------------------------------
135
+
136
+ @Test
137
+ fun testInvokeSync() {
138
+ val mock = MockNativeModule("SyncModule")
139
+ mock.resultToReturn = "sync_result"
140
+ registry.register(mock)
141
+
142
+ val result = registry.invokeSync("SyncModule", "getInfo", listOf("key"), bridge)
143
+
144
+ assertEquals("sync_result", result)
145
+ assertEquals("getInfo", mock.lastMethod)
146
+ assertEquals(listOf("key"), mock.lastArgs)
147
+ }
148
+
149
+ // -------------------------------------------------------------------------
150
+ // invokeSync unknown module — returns null
151
+ // -------------------------------------------------------------------------
152
+
153
+ @Test
154
+ fun testInvokeSyncUnknownModule() {
155
+ val result = registry.invokeSync("NonExistent", "method", emptyList(), bridge)
156
+ assertNull("invokeSync for unknown module should return null", result)
157
+ }
158
+
159
+ // -------------------------------------------------------------------------
160
+ // registerDefaults — key modules are present
161
+ // -------------------------------------------------------------------------
162
+
163
+ @Test
164
+ fun testRegisterDefaults() {
165
+ // Some modules (e.g. SecureStorageModule) may throw during initialize()
166
+ // in the Robolectric environment (KeyStore not available). Wrap with try/catch
167
+ // and verify the modules that registered successfully.
168
+ try {
169
+ registry.registerDefaults(bridge)
170
+ } catch (_: Exception) {
171
+ // Some modules may fail to initialize in test — that's OK
172
+ }
173
+
174
+ // Spot-check modules that register before SecureStorageModule (which throws
175
+ // java.security.KeyStoreException in Robolectric, halting the forEach loop).
176
+ // Modules registered in order: Haptics, AsyncStorage, Clipboard, DeviceInfo,
177
+ // Network, AppState, Linking, Share, Animation, Keyboard, Permissions,
178
+ // Geolocation, Notifications, Http, Biometry, Camera, SecureStorage(throws)...
179
+ assertNotNull("Haptics module should be registered", registry.getModule("Haptics"))
180
+ assertNotNull("AsyncStorage module should be registered", registry.getModule("AsyncStorage"))
181
+ assertNotNull("Clipboard module should be registered", registry.getModule("Clipboard"))
182
+ assertNotNull("DeviceInfo module should be registered", registry.getModule("DeviceInfo"))
183
+ assertNotNull("Network module should be registered", registry.getModule("Network"))
184
+ assertNotNull("Animation module should be registered", registry.getModule("Animation"))
185
+ assertNotNull("Http module should be registered", registry.getModule("Http"))
186
+ }
187
+
188
+ // -------------------------------------------------------------------------
189
+ // Register overwrite — second module wins
190
+ // -------------------------------------------------------------------------
191
+
192
+ @Test
193
+ fun testRegisterOverwrite() {
194
+ val first = MockNativeModule("Shared")
195
+ first.resultToReturn = "first"
196
+ registry.register(first)
197
+
198
+ val second = MockNativeModule("Shared")
199
+ second.resultToReturn = "second"
200
+ registry.register(second)
201
+
202
+ val retrieved = registry.getModule("Shared")
203
+ assertNotNull(retrieved)
204
+
205
+ // Invoke and verify second module's result is used
206
+ val result = registry.invokeSync("Shared", "test", emptyList(), bridge)
207
+ assertEquals("second", result)
208
+ assertTrue("Retrieved module should be the second one", retrieved === second)
209
+ }
210
+
211
+ // -------------------------------------------------------------------------
212
+ // invoke with exception in module
213
+ // -------------------------------------------------------------------------
214
+
215
+ @Test
216
+ fun testInvokeWithModuleException() {
217
+ val throwingModule = object : NativeModule {
218
+ override val moduleName = "Thrower"
219
+ override fun invoke(method: String, args: List<Any?>, bridge: NativeBridge, callback: (Any?, String?) -> Unit) {
220
+ throw RuntimeException("Module exploded")
221
+ }
222
+ }
223
+ registry.register(throwingModule)
224
+
225
+ var callbackResult: Any? = "not_null"
226
+ var callbackError: String? = null
227
+
228
+ registry.invoke("Thrower", "boom", emptyList(), bridge) { result, error ->
229
+ callbackResult = result
230
+ callbackError = error
231
+ }
232
+
233
+ assertNull("Result should be null when module throws", callbackResult)
234
+ assertNotNull("Error should be set when module throws", callbackError)
235
+ assertTrue(
236
+ "Error message should contain exception message",
237
+ callbackError!!.contains("Module exploded")
238
+ )
239
+ }
240
+
241
+ // -------------------------------------------------------------------------
242
+ // Singleton behavior
243
+ // -------------------------------------------------------------------------
244
+
245
+ @Test
246
+ fun testSingletonReturnsSameInstance() {
247
+ val instance1 = NativeModuleRegistry.getInstance(context)
248
+ val instance2 = NativeModuleRegistry.getInstance(context)
249
+ assertTrue("getInstance should return the same instance", instance1 === instance2)
250
+ }
251
+ }