@thelacanians/vue-native-cli 0.4.15 → 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.
- package/dist/cli.js +329 -15
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +118 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VViewFactory.kt +178 -1
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/GeneratedModuleRegistry.kt +28 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModuleRegistry.kt +3 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ComponentFactoryTest.kt +674 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ErrorOverlayViewTest.kt +183 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/EventThrottleTest.kt +203 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/HotReloadManagerTest.kt +162 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/JSPolyfillsTest.kt +153 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeBridgeTest.kt +6 -3
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeModuleTest.kt +475 -0
- package/native/android/gradle.properties +1 -0
- package/native/android/gradlew +1 -1
- package/native/ios/VueNativeCore/Package.swift +1 -1
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/EventThrottle.swift +1 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +143 -5
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VTextFactory.swift +43 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VViewFactory.swift +116 -4
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/GestureWrapper.swift +100 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/GeneratedModuleRegistry.swift +28 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModuleRegistry.swift +3 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/CertificatePinningTests.swift +190 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/ComponentFactoryTests.swift +585 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/EventThrottleTests.swift +161 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/HotReloadManagerTests.swift +88 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/JSPolyfillsTests.swift +319 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/NativeModuleTests.swift +400 -0
- package/native/macos/VueNativeMacOS/Package.swift +34 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/ErrorOverlayView.swift +112 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/EventThrottle.swift +58 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/HotReloadManager.swift +153 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSPolyfills.swift +696 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSRuntime.swift +347 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/NativeBridge.swift +877 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/VueNativeWindowController.swift +125 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/ComponentRegistry.swift +209 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActionSheetFactory.swift +155 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActivityIndicatorFactory.swift +85 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VAlertDialogFactory.swift +132 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VButtonFactory.swift +83 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VCheckboxFactory.swift +108 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VDropdownFactory.swift +155 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VImageFactory.swift +270 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VInputFactory.swift +257 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VKeyboardAvoidingFactory.swift +22 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VListFactory.swift +324 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VModalFactory.swift +231 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VOutlineViewFactory.swift +276 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPickerFactory.swift +134 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPressableFactory.swift +120 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VProgressBarFactory.swift +71 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRadioFactory.swift +193 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRefreshControlFactory.swift +25 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSafeAreaFactory.swift +46 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VScrollViewFactory.swift +190 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSectionListFactory.swift +374 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSegmentedControlFactory.swift +125 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSliderFactory.swift +131 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSplitViewFactory.swift +215 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VStatusBarFactory.swift +25 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSwitchFactory.swift +92 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VTextFactory.swift +336 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VToolbarFactory.swift +212 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VVideoFactory.swift +245 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VViewFactory.swift +314 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VWebViewFactory.swift +162 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/NativeComponentFactory.swift +54 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/ClickableView.swift +100 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/Extensions.swift +23 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/GestureWrapper.swift +183 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/NSColor+Hex.swift +78 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/FlippedView.swift +19 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/LayoutNode.swift +493 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AnimationModule.swift +354 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AppStateModule.swift +62 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/BiometryModule.swift +60 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/CameraModule.swift +167 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ClipboardModule.swift +34 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DeviceInfoModule.swift +49 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DragDropModule.swift +50 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/FileDialogModule.swift +86 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/HapticsModule.swift +42 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/KeyboardModule.swift +28 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/LinkingModule.swift +49 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/MenuModule.swift +95 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NativeModuleRegistry.swift +63 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NotificationsModule.swift +112 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/PermissionsModule.swift +149 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ShareModule.swift +37 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/WindowModule.swift +71 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Resources/vue-native-placeholder.js +2 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Styling/StyleEngine.swift +885 -0
- package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/ComponentFactoryTests.swift +80 -0
- package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/VueNativeMacOSTests.swift +149 -0
- package/native/shared/VueNativeShared/AGENTS.md +129 -0
- package/native/shared/VueNativeShared/Package.swift +14 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/CertificatePinning.swift +134 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/EventThrottle.swift +78 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/HotReloadManager.swift +162 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/JSRuntime.swift +412 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AsyncStorageModule.swift +68 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AudioModule.swift +359 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/DatabaseModule.swift +259 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/FileSystemModule.swift +233 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/GeolocationModule.swift +156 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/NetworkModule.swift +59 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/PerformanceModule.swift +113 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/SecureStorageModule.swift +119 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/WebSocketModule.swift +212 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeEventDispatcher.swift +6 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModule.swift +26 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModuleRegistry.swift +37 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/SharedJSPolyfills.swift +673 -0
- package/native/shared/VueNativeShared/Tests/VueNativeSharedTests/VueNativeSharedTests.swift +44 -0
- package/package.json +8 -2
package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ErrorOverlayViewTest.kt
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
package com.vuenative.core
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.widget.Button
|
|
5
|
+
import android.widget.FrameLayout
|
|
6
|
+
import android.widget.LinearLayout
|
|
7
|
+
import android.widget.ScrollView
|
|
8
|
+
import android.widget.TextView
|
|
9
|
+
import androidx.appcompat.app.AppCompatActivity
|
|
10
|
+
import androidx.test.core.app.ApplicationProvider
|
|
11
|
+
import org.junit.Assert.assertEquals
|
|
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.Robolectric
|
|
19
|
+
import org.robolectric.RobolectricTestRunner
|
|
20
|
+
import org.robolectric.annotation.Config
|
|
21
|
+
|
|
22
|
+
@RunWith(RobolectricTestRunner::class)
|
|
23
|
+
@Config(sdk = [34])
|
|
24
|
+
class ErrorOverlayViewTest {
|
|
25
|
+
|
|
26
|
+
private lateinit var context: Context
|
|
27
|
+
|
|
28
|
+
@Before
|
|
29
|
+
fun setUp() {
|
|
30
|
+
context = ApplicationProvider.getApplicationContext()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private fun createActivity(): AppCompatActivity {
|
|
34
|
+
val controller = Robolectric.buildActivity(AppCompatActivity::class.java)
|
|
35
|
+
val activity = controller.get()
|
|
36
|
+
activity.setTheme(androidx.appcompat.R.style.Theme_AppCompat)
|
|
37
|
+
controller.create().start().resume()
|
|
38
|
+
return activity
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// -------------------------------------------------------------------------
|
|
42
|
+
// ErrorOverlayView is a singleton object
|
|
43
|
+
// -------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
@Test
|
|
46
|
+
fun testIsSingletonObject() {
|
|
47
|
+
val ref1 = ErrorOverlayView
|
|
48
|
+
val ref2 = ErrorOverlayView
|
|
49
|
+
assertTrue("ErrorOverlayView should be a singleton object", ref1 === ref2)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// -------------------------------------------------------------------------
|
|
53
|
+
// show() with non-activity context does not crash
|
|
54
|
+
// -------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
@Test
|
|
57
|
+
fun testShowWithNonActivityContext() {
|
|
58
|
+
// Application context is not an AppCompatActivity, so show() should return early
|
|
59
|
+
ErrorOverlayView.show(context, "Test error")
|
|
60
|
+
// Should not crash
|
|
61
|
+
assertTrue(true)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// -------------------------------------------------------------------------
|
|
65
|
+
// show() with Activity creates overlay
|
|
66
|
+
// -------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
@Test
|
|
69
|
+
fun testShowWithActivity() {
|
|
70
|
+
val activity = createActivity()
|
|
71
|
+
|
|
72
|
+
ErrorOverlayView.show(activity, "Runtime error: undefined is not a function")
|
|
73
|
+
|
|
74
|
+
val decorView = activity.window.decorView as? android.view.ViewGroup
|
|
75
|
+
assertNotNull("decorView should not be null", decorView)
|
|
76
|
+
|
|
77
|
+
val overlay = decorView?.findViewWithTag<FrameLayout>("vue_native_error_overlay")
|
|
78
|
+
assertNotNull("Overlay should be added to decorView", overlay)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// -------------------------------------------------------------------------
|
|
82
|
+
// Overlay contains title, error text, and dismiss button
|
|
83
|
+
// -------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
@Test
|
|
86
|
+
fun testOverlayContents() {
|
|
87
|
+
val activity = createActivity()
|
|
88
|
+
|
|
89
|
+
val errorMessage = "TypeError: Cannot read property 'value' of null"
|
|
90
|
+
ErrorOverlayView.show(activity, errorMessage)
|
|
91
|
+
|
|
92
|
+
val decorView = activity.window.decorView as android.view.ViewGroup
|
|
93
|
+
val overlay = decorView.findViewWithTag<FrameLayout>("vue_native_error_overlay")!!
|
|
94
|
+
|
|
95
|
+
// The overlay contains a card (LinearLayout)
|
|
96
|
+
val card = overlay.getChildAt(0) as LinearLayout
|
|
97
|
+
|
|
98
|
+
// Card should have 3 children: title, scroll (with error text), dismiss button
|
|
99
|
+
assertEquals("Card should have 3 children", 3, card.childCount)
|
|
100
|
+
|
|
101
|
+
// Title
|
|
102
|
+
val title = card.getChildAt(0) as TextView
|
|
103
|
+
assertEquals("Vue Native JS Error", title.text.toString())
|
|
104
|
+
|
|
105
|
+
// Scroll -> error text
|
|
106
|
+
val scroll = card.getChildAt(1) as ScrollView
|
|
107
|
+
val errorText = scroll.getChildAt(0) as TextView
|
|
108
|
+
assertEquals(errorMessage, errorText.text.toString())
|
|
109
|
+
|
|
110
|
+
// Dismiss button
|
|
111
|
+
val dismiss = card.getChildAt(2) as Button
|
|
112
|
+
assertEquals("Dismiss", dismiss.text.toString())
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// -------------------------------------------------------------------------
|
|
116
|
+
// Dismiss button removes overlay
|
|
117
|
+
// -------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
@Test
|
|
120
|
+
fun testDismissRemovesOverlay() {
|
|
121
|
+
val activity = createActivity()
|
|
122
|
+
|
|
123
|
+
ErrorOverlayView.show(activity, "Some error")
|
|
124
|
+
|
|
125
|
+
val decorView = activity.window.decorView as android.view.ViewGroup
|
|
126
|
+
var overlay = decorView.findViewWithTag<FrameLayout>("vue_native_error_overlay")
|
|
127
|
+
assertNotNull("Overlay should exist", overlay)
|
|
128
|
+
|
|
129
|
+
// Find and click the dismiss button
|
|
130
|
+
val card = overlay!!.getChildAt(0) as LinearLayout
|
|
131
|
+
val dismiss = card.getChildAt(2) as Button
|
|
132
|
+
dismiss.performClick()
|
|
133
|
+
|
|
134
|
+
overlay = decorView.findViewWithTag<FrameLayout>("vue_native_error_overlay")
|
|
135
|
+
assertNull("Overlay should be removed after dismiss", overlay)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// -------------------------------------------------------------------------
|
|
139
|
+
// Showing twice replaces old overlay
|
|
140
|
+
// -------------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
@Test
|
|
143
|
+
fun testShowTwiceReplacesOverlay() {
|
|
144
|
+
val activity = createActivity()
|
|
145
|
+
|
|
146
|
+
ErrorOverlayView.show(activity, "First error")
|
|
147
|
+
ErrorOverlayView.show(activity, "Second error")
|
|
148
|
+
|
|
149
|
+
val decorView = activity.window.decorView as android.view.ViewGroup
|
|
150
|
+
|
|
151
|
+
// Count overlays with the tag
|
|
152
|
+
var overlayCount = 0
|
|
153
|
+
for (i in 0 until decorView.childCount) {
|
|
154
|
+
if (decorView.getChildAt(i).tag == "vue_native_error_overlay") {
|
|
155
|
+
overlayCount++
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
assertEquals("Should only have one overlay", 1, overlayCount)
|
|
159
|
+
|
|
160
|
+
// Verify it shows the second error
|
|
161
|
+
val overlay = decorView.findViewWithTag<FrameLayout>("vue_native_error_overlay")!!
|
|
162
|
+
val card = overlay.getChildAt(0) as LinearLayout
|
|
163
|
+
val scroll = card.getChildAt(1) as ScrollView
|
|
164
|
+
val errorText = scroll.getChildAt(0) as TextView
|
|
165
|
+
assertEquals("Second error", errorText.text.toString())
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// -------------------------------------------------------------------------
|
|
169
|
+
// Overlay tag is correct
|
|
170
|
+
// -------------------------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
@Test
|
|
173
|
+
fun testOverlayTag() {
|
|
174
|
+
val activity = createActivity()
|
|
175
|
+
|
|
176
|
+
ErrorOverlayView.show(activity, "Error")
|
|
177
|
+
|
|
178
|
+
val decorView = activity.window.decorView as android.view.ViewGroup
|
|
179
|
+
val overlay = decorView.findViewWithTag<FrameLayout>("vue_native_error_overlay")
|
|
180
|
+
assertNotNull("Overlay should be findable by tag", overlay)
|
|
181
|
+
assertEquals("vue_native_error_overlay", overlay!!.tag)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
package com.vuenative.core
|
|
2
|
+
|
|
3
|
+
import android.os.Looper
|
|
4
|
+
import java.time.Duration
|
|
5
|
+
import org.junit.Assert.assertEquals
|
|
6
|
+
import org.junit.Assert.assertTrue
|
|
7
|
+
import org.junit.Before
|
|
8
|
+
import org.junit.Test
|
|
9
|
+
import org.junit.runner.RunWith
|
|
10
|
+
import org.robolectric.RobolectricTestRunner
|
|
11
|
+
import org.robolectric.Shadows
|
|
12
|
+
import org.robolectric.annotation.Config
|
|
13
|
+
|
|
14
|
+
@RunWith(RobolectricTestRunner::class)
|
|
15
|
+
@Config(sdk = [34])
|
|
16
|
+
class EventThrottleTest {
|
|
17
|
+
|
|
18
|
+
private val firedPayloads = mutableListOf<Any?>()
|
|
19
|
+
|
|
20
|
+
@Before
|
|
21
|
+
fun setUp() {
|
|
22
|
+
firedPayloads.clear()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private fun flush() {
|
|
26
|
+
Shadows.shadowOf(Looper.getMainLooper()).idle()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Resets lastFireTime to a very old value via reflection so that
|
|
31
|
+
* the first fire() call sees elapsed >= intervalMs.
|
|
32
|
+
* This is needed because Robolectric's SystemClock.uptimeMillis()
|
|
33
|
+
* returns 0 and lastFireTime starts at 0, so elapsed=0 < intervalMs.
|
|
34
|
+
*/
|
|
35
|
+
private fun ensureFirstFireImmediate(throttle: EventThrottle) {
|
|
36
|
+
val field = EventThrottle::class.java.getDeclaredField("lastFireTime")
|
|
37
|
+
field.isAccessible = true
|
|
38
|
+
field.setLong(throttle, -100_000L)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// -------------------------------------------------------------------------
|
|
42
|
+
// Initialization
|
|
43
|
+
// -------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
@Test
|
|
46
|
+
fun testCreation() {
|
|
47
|
+
val throttle = EventThrottle(intervalMs = 100L) { firedPayloads.add(it) }
|
|
48
|
+
// Should not fire anything on creation
|
|
49
|
+
assertEquals(0, firedPayloads.size)
|
|
50
|
+
throttle.cancel()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// -------------------------------------------------------------------------
|
|
54
|
+
// First event fires immediately
|
|
55
|
+
// -------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
@Test
|
|
58
|
+
fun testFirstEventFiresImmediately() {
|
|
59
|
+
val throttle = EventThrottle(intervalMs = 100L) { firedPayloads.add(it) }
|
|
60
|
+
ensureFirstFireImmediate(throttle)
|
|
61
|
+
|
|
62
|
+
throttle.fire("first")
|
|
63
|
+
|
|
64
|
+
assertEquals("First event should fire immediately", 1, firedPayloads.size)
|
|
65
|
+
assertEquals("first", firedPayloads[0])
|
|
66
|
+
throttle.cancel()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// -------------------------------------------------------------------------
|
|
70
|
+
// Events within window are throttled (trailing fires after delay)
|
|
71
|
+
// -------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
@Test
|
|
74
|
+
fun testEventsWithinWindowAreThrottled() {
|
|
75
|
+
val throttle = EventThrottle(intervalMs = 5000L) { firedPayloads.add(it) }
|
|
76
|
+
ensureFirstFireImmediate(throttle)
|
|
77
|
+
|
|
78
|
+
// First fires immediately
|
|
79
|
+
throttle.fire("first")
|
|
80
|
+
assertEquals(1, firedPayloads.size)
|
|
81
|
+
|
|
82
|
+
// Second should be throttled (within the 5000ms window)
|
|
83
|
+
throttle.fire("second")
|
|
84
|
+
assertEquals("Second event should be throttled", 1, firedPayloads.size)
|
|
85
|
+
|
|
86
|
+
// Third should update the pending payload but not fire
|
|
87
|
+
throttle.fire("third")
|
|
88
|
+
assertEquals("Third event should still be throttled", 1, firedPayloads.size)
|
|
89
|
+
|
|
90
|
+
// Advance the looper clock past the interval to let the trailing callback fire
|
|
91
|
+
Shadows.shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(6000))
|
|
92
|
+
|
|
93
|
+
assertEquals("Trailing event should fire with latest payload", 2, firedPayloads.size)
|
|
94
|
+
assertEquals("third", firedPayloads[1])
|
|
95
|
+
throttle.cancel()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// -------------------------------------------------------------------------
|
|
99
|
+
// Cancel stops pending trailing call
|
|
100
|
+
// -------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
@Test
|
|
103
|
+
fun testCancelStopsPendingTrailing() {
|
|
104
|
+
val throttle = EventThrottle(intervalMs = 5000L) { firedPayloads.add(it) }
|
|
105
|
+
ensureFirstFireImmediate(throttle)
|
|
106
|
+
|
|
107
|
+
// First fires immediately
|
|
108
|
+
throttle.fire("first")
|
|
109
|
+
assertEquals(1, firedPayloads.size)
|
|
110
|
+
|
|
111
|
+
// Second is throttled (trailing scheduled)
|
|
112
|
+
throttle.fire("second")
|
|
113
|
+
assertEquals(1, firedPayloads.size)
|
|
114
|
+
|
|
115
|
+
// Cancel before trailing fires
|
|
116
|
+
throttle.cancel()
|
|
117
|
+
flush()
|
|
118
|
+
|
|
119
|
+
assertEquals("Trailing should NOT fire after cancel", 1, firedPayloads.size)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// -------------------------------------------------------------------------
|
|
123
|
+
// Multiple fires with cancel in between
|
|
124
|
+
// -------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
@Test
|
|
127
|
+
fun testFireAfterCancel() {
|
|
128
|
+
val throttle = EventThrottle(intervalMs = 5000L) { firedPayloads.add(it) }
|
|
129
|
+
ensureFirstFireImmediate(throttle)
|
|
130
|
+
|
|
131
|
+
throttle.fire("first")
|
|
132
|
+
assertEquals(1, firedPayloads.size)
|
|
133
|
+
|
|
134
|
+
throttle.fire("second")
|
|
135
|
+
throttle.cancel()
|
|
136
|
+
flush()
|
|
137
|
+
|
|
138
|
+
assertEquals("Only the first event should have fired", 1, firedPayloads.size)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// -------------------------------------------------------------------------
|
|
142
|
+
// Payload is passed correctly
|
|
143
|
+
// -------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
@Test
|
|
146
|
+
fun testPayloadPassedCorrectly() {
|
|
147
|
+
val throttle = EventThrottle(intervalMs = 5000L) { firedPayloads.add(it) }
|
|
148
|
+
ensureFirstFireImmediate(throttle)
|
|
149
|
+
|
|
150
|
+
val payload = mapOf("x" to 10, "y" to 20)
|
|
151
|
+
throttle.fire(payload)
|
|
152
|
+
|
|
153
|
+
assertEquals(1, firedPayloads.size)
|
|
154
|
+
@Suppress("UNCHECKED_CAST")
|
|
155
|
+
val received = firedPayloads[0] as Map<String, Int>
|
|
156
|
+
assertEquals(10, received["x"])
|
|
157
|
+
assertEquals(20, received["y"])
|
|
158
|
+
throttle.cancel()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// -------------------------------------------------------------------------
|
|
162
|
+
// Null payload is valid
|
|
163
|
+
// -------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
@Test
|
|
166
|
+
fun testNullPayload() {
|
|
167
|
+
val throttle = EventThrottle(intervalMs = 5000L) { firedPayloads.add(it) }
|
|
168
|
+
ensureFirstFireImmediate(throttle)
|
|
169
|
+
|
|
170
|
+
throttle.fire(null)
|
|
171
|
+
|
|
172
|
+
assertEquals(1, firedPayloads.size)
|
|
173
|
+
assertEquals(null, firedPayloads[0])
|
|
174
|
+
throttle.cancel()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// -------------------------------------------------------------------------
|
|
178
|
+
// Default interval is 16ms
|
|
179
|
+
// -------------------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
@Test
|
|
182
|
+
fun testDefaultInterval() {
|
|
183
|
+
val throttle = EventThrottle { firedPayloads.add(it) }
|
|
184
|
+
ensureFirstFireImmediate(throttle)
|
|
185
|
+
throttle.fire("test")
|
|
186
|
+
assertEquals(1, firedPayloads.size)
|
|
187
|
+
throttle.cancel()
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// -------------------------------------------------------------------------
|
|
191
|
+
// Multiple cancel calls don't crash
|
|
192
|
+
// -------------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
@Test
|
|
195
|
+
fun testMultipleCancelCalls() {
|
|
196
|
+
val throttle = EventThrottle(intervalMs = 100L) { firedPayloads.add(it) }
|
|
197
|
+
throttle.cancel()
|
|
198
|
+
throttle.cancel()
|
|
199
|
+
throttle.cancel()
|
|
200
|
+
// Should not crash
|
|
201
|
+
assertTrue(true)
|
|
202
|
+
}
|
|
203
|
+
}
|
package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/HotReloadManagerTest.kt
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
package com.vuenative.core
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import androidx.test.core.app.ApplicationProvider
|
|
5
|
+
import org.junit.Assert.assertFalse
|
|
6
|
+
import org.junit.Assert.assertNotNull
|
|
7
|
+
import org.junit.Assert.assertTrue
|
|
8
|
+
import org.junit.Before
|
|
9
|
+
import org.junit.Test
|
|
10
|
+
import org.junit.runner.RunWith
|
|
11
|
+
import org.robolectric.RobolectricTestRunner
|
|
12
|
+
import org.robolectric.annotation.Config
|
|
13
|
+
|
|
14
|
+
@RunWith(RobolectricTestRunner::class)
|
|
15
|
+
@Config(sdk = [34])
|
|
16
|
+
class HotReloadManagerTest {
|
|
17
|
+
|
|
18
|
+
private lateinit var context: Context
|
|
19
|
+
private var lastReloadCode: String? = null
|
|
20
|
+
private lateinit var manager: HotReloadManager
|
|
21
|
+
|
|
22
|
+
@Before
|
|
23
|
+
fun setUp() {
|
|
24
|
+
// Reset ComponentRegistry singleton via reflection
|
|
25
|
+
val crField = ComponentRegistry::class.java.getDeclaredField("instance")
|
|
26
|
+
crField.isAccessible = true
|
|
27
|
+
crField.set(null, null)
|
|
28
|
+
|
|
29
|
+
// Reset NativeModuleRegistry singleton via reflection
|
|
30
|
+
val nmrField = NativeModuleRegistry::class.java.getDeclaredField("instance")
|
|
31
|
+
nmrField.isAccessible = true
|
|
32
|
+
nmrField.set(null, null)
|
|
33
|
+
|
|
34
|
+
context = ApplicationProvider.getApplicationContext()
|
|
35
|
+
lastReloadCode = null
|
|
36
|
+
|
|
37
|
+
val runtime = JSRuntime(context)
|
|
38
|
+
manager = HotReloadManager(runtime) { code ->
|
|
39
|
+
lastReloadCode = code
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// -------------------------------------------------------------------------
|
|
44
|
+
// Initialization
|
|
45
|
+
// -------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
@Test
|
|
48
|
+
fun testCreation() {
|
|
49
|
+
assertNotNull("HotReloadManager should be created", manager)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// -------------------------------------------------------------------------
|
|
53
|
+
// Initial connection state is disconnected
|
|
54
|
+
// -------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
@Test
|
|
57
|
+
fun testInitialConnectionState() {
|
|
58
|
+
val field = HotReloadManager::class.java.getDeclaredField("isConnected")
|
|
59
|
+
field.isAccessible = true
|
|
60
|
+
val connected = field.getBoolean(manager)
|
|
61
|
+
assertFalse("Should not be connected initially", connected)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// -------------------------------------------------------------------------
|
|
65
|
+
// disconnect() clears state
|
|
66
|
+
// -------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
@Test
|
|
69
|
+
fun testDisconnect() {
|
|
70
|
+
manager.disconnect()
|
|
71
|
+
|
|
72
|
+
val connField = HotReloadManager::class.java.getDeclaredField("isConnected")
|
|
73
|
+
connField.isAccessible = true
|
|
74
|
+
assertFalse("isConnected should be false after disconnect", connField.getBoolean(manager))
|
|
75
|
+
|
|
76
|
+
val devField = HotReloadManager::class.java.getDeclaredField("devServerUrl")
|
|
77
|
+
devField.isAccessible = true
|
|
78
|
+
val devUrl = devField.get(manager)
|
|
79
|
+
assertTrue("devServerUrl should be null after disconnect", devUrl == null)
|
|
80
|
+
|
|
81
|
+
val bundleField = HotReloadManager::class.java.getDeclaredField("bundleUrl")
|
|
82
|
+
bundleField.isAccessible = true
|
|
83
|
+
val bundleUrl = bundleField.get(manager)
|
|
84
|
+
assertTrue("bundleUrl should be null after disconnect", bundleUrl == null)
|
|
85
|
+
|
|
86
|
+
val wsField = HotReloadManager::class.java.getDeclaredField("wsSession")
|
|
87
|
+
wsField.isAccessible = true
|
|
88
|
+
val ws = wsField.get(manager)
|
|
89
|
+
assertTrue("wsSession should be null after disconnect", ws == null)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// -------------------------------------------------------------------------
|
|
93
|
+
// disconnect() is idempotent
|
|
94
|
+
// -------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
@Test
|
|
97
|
+
fun testDisconnectIdempotent() {
|
|
98
|
+
manager.disconnect()
|
|
99
|
+
manager.disconnect()
|
|
100
|
+
manager.disconnect()
|
|
101
|
+
// Should not throw
|
|
102
|
+
assertTrue(true)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// -------------------------------------------------------------------------
|
|
106
|
+
// connect() stores URLs
|
|
107
|
+
// -------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
@Test
|
|
110
|
+
fun testConnectStoresUrls() {
|
|
111
|
+
manager.connect("ws://localhost:3000", "http://localhost:3000/bundle.js")
|
|
112
|
+
|
|
113
|
+
val devField = HotReloadManager::class.java.getDeclaredField("devServerUrl")
|
|
114
|
+
devField.isAccessible = true
|
|
115
|
+
val devUrl = devField.get(manager) as? String
|
|
116
|
+
assertTrue("devServerUrl should be set", devUrl == "ws://localhost:3000")
|
|
117
|
+
|
|
118
|
+
val bundleField = HotReloadManager::class.java.getDeclaredField("bundleUrl")
|
|
119
|
+
bundleField.isAccessible = true
|
|
120
|
+
val bundleUrl = bundleField.get(manager) as? String
|
|
121
|
+
assertTrue("bundleUrl should be set", bundleUrl == "http://localhost:3000/bundle.js")
|
|
122
|
+
|
|
123
|
+
manager.disconnect()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// -------------------------------------------------------------------------
|
|
127
|
+
// After disconnect, can reconnect
|
|
128
|
+
// -------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
@Test
|
|
131
|
+
fun testReconnectAfterDisconnect() {
|
|
132
|
+
manager.connect("ws://localhost:3000", "http://localhost:3000/bundle.js")
|
|
133
|
+
manager.disconnect()
|
|
134
|
+
|
|
135
|
+
// Scope and job should be fresh after disconnect
|
|
136
|
+
val scopeJobField = HotReloadManager::class.java.getDeclaredField("scopeJob")
|
|
137
|
+
scopeJobField.isAccessible = true
|
|
138
|
+
val scopeJob = scopeJobField.get(manager) as kotlinx.coroutines.Job
|
|
139
|
+
assertFalse("scopeJob should not be cancelled after disconnect+reinit", scopeJob.isCancelled)
|
|
140
|
+
|
|
141
|
+
manager.connect("ws://localhost:4000", "http://localhost:4000/bundle.js")
|
|
142
|
+
|
|
143
|
+
val devField = HotReloadManager::class.java.getDeclaredField("devServerUrl")
|
|
144
|
+
devField.isAccessible = true
|
|
145
|
+
val devUrl = devField.get(manager) as? String
|
|
146
|
+
assertTrue("devServerUrl should be updated", devUrl == "ws://localhost:4000")
|
|
147
|
+
|
|
148
|
+
manager.disconnect()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// -------------------------------------------------------------------------
|
|
152
|
+
// httpClient exists
|
|
153
|
+
// -------------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
@Test
|
|
156
|
+
fun testHttpClientExists() {
|
|
157
|
+
val field = HotReloadManager::class.java.getDeclaredField("httpClient")
|
|
158
|
+
field.isAccessible = true
|
|
159
|
+
val client = field.get(manager)
|
|
160
|
+
assertNotNull("httpClient should not be null", client)
|
|
161
|
+
}
|
|
162
|
+
}
|