@thelacanians/vue-native-cli 0.4.15 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/dist/cli.js +329 -15
  2. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +118 -0
  3. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VViewFactory.kt +178 -1
  4. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/GeneratedModuleRegistry.kt +28 -0
  5. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModuleRegistry.kt +3 -0
  6. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ComponentFactoryTest.kt +674 -0
  7. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ErrorOverlayViewTest.kt +183 -0
  8. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/EventThrottleTest.kt +203 -0
  9. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/HotReloadManagerTest.kt +162 -0
  10. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/JSPolyfillsTest.kt +153 -0
  11. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeBridgeTest.kt +6 -3
  12. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeModuleTest.kt +475 -0
  13. package/native/android/gradle.properties +1 -0
  14. package/native/android/gradlew +1 -1
  15. package/native/ios/VueNativeCore/Package.swift +1 -1
  16. package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/EventThrottle.swift +1 -0
  17. package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +143 -5
  18. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VTextFactory.swift +43 -0
  19. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VViewFactory.swift +116 -4
  20. package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/GestureWrapper.swift +100 -0
  21. package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/GeneratedModuleRegistry.swift +28 -0
  22. package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModuleRegistry.swift +3 -0
  23. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/CertificatePinningTests.swift +190 -0
  24. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/ComponentFactoryTests.swift +585 -0
  25. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/EventThrottleTests.swift +161 -0
  26. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/HotReloadManagerTests.swift +88 -0
  27. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/JSPolyfillsTests.swift +319 -0
  28. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/NativeModuleTests.swift +400 -0
  29. package/native/macos/VueNativeMacOS/Package.swift +34 -0
  30. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/ErrorOverlayView.swift +112 -0
  31. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/EventThrottle.swift +58 -0
  32. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/HotReloadManager.swift +153 -0
  33. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSPolyfills.swift +696 -0
  34. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSRuntime.swift +347 -0
  35. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/NativeBridge.swift +877 -0
  36. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/VueNativeWindowController.swift +125 -0
  37. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/ComponentRegistry.swift +209 -0
  38. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActionSheetFactory.swift +155 -0
  39. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActivityIndicatorFactory.swift +85 -0
  40. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VAlertDialogFactory.swift +132 -0
  41. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VButtonFactory.swift +83 -0
  42. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VCheckboxFactory.swift +108 -0
  43. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VDropdownFactory.swift +155 -0
  44. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VImageFactory.swift +270 -0
  45. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VInputFactory.swift +257 -0
  46. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VKeyboardAvoidingFactory.swift +22 -0
  47. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VListFactory.swift +324 -0
  48. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VModalFactory.swift +231 -0
  49. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VOutlineViewFactory.swift +276 -0
  50. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPickerFactory.swift +134 -0
  51. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPressableFactory.swift +120 -0
  52. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VProgressBarFactory.swift +71 -0
  53. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRadioFactory.swift +193 -0
  54. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRefreshControlFactory.swift +25 -0
  55. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSafeAreaFactory.swift +46 -0
  56. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VScrollViewFactory.swift +190 -0
  57. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSectionListFactory.swift +374 -0
  58. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSegmentedControlFactory.swift +125 -0
  59. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSliderFactory.swift +131 -0
  60. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSplitViewFactory.swift +215 -0
  61. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VStatusBarFactory.swift +25 -0
  62. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSwitchFactory.swift +92 -0
  63. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VTextFactory.swift +336 -0
  64. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VToolbarFactory.swift +212 -0
  65. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VVideoFactory.swift +245 -0
  66. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VViewFactory.swift +314 -0
  67. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VWebViewFactory.swift +162 -0
  68. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/NativeComponentFactory.swift +54 -0
  69. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/ClickableView.swift +100 -0
  70. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/Extensions.swift +23 -0
  71. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/GestureWrapper.swift +183 -0
  72. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/NSColor+Hex.swift +78 -0
  73. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/FlippedView.swift +19 -0
  74. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/LayoutNode.swift +493 -0
  75. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AnimationModule.swift +354 -0
  76. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AppStateModule.swift +62 -0
  77. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/BiometryModule.swift +60 -0
  78. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/CameraModule.swift +167 -0
  79. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ClipboardModule.swift +34 -0
  80. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DeviceInfoModule.swift +49 -0
  81. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DragDropModule.swift +50 -0
  82. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/FileDialogModule.swift +86 -0
  83. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/HapticsModule.swift +42 -0
  84. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/KeyboardModule.swift +28 -0
  85. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/LinkingModule.swift +49 -0
  86. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/MenuModule.swift +95 -0
  87. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NativeModuleRegistry.swift +63 -0
  88. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NotificationsModule.swift +112 -0
  89. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/PermissionsModule.swift +149 -0
  90. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ShareModule.swift +37 -0
  91. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/WindowModule.swift +71 -0
  92. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Resources/vue-native-placeholder.js +2 -0
  93. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Styling/StyleEngine.swift +885 -0
  94. package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/ComponentFactoryTests.swift +80 -0
  95. package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/VueNativeMacOSTests.swift +149 -0
  96. package/native/shared/VueNativeShared/AGENTS.md +129 -0
  97. package/native/shared/VueNativeShared/Package.swift +14 -0
  98. package/native/shared/VueNativeShared/Sources/VueNativeShared/CertificatePinning.swift +134 -0
  99. package/native/shared/VueNativeShared/Sources/VueNativeShared/EventThrottle.swift +78 -0
  100. package/native/shared/VueNativeShared/Sources/VueNativeShared/HotReloadManager.swift +162 -0
  101. package/native/shared/VueNativeShared/Sources/VueNativeShared/JSRuntime.swift +412 -0
  102. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AsyncStorageModule.swift +68 -0
  103. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AudioModule.swift +359 -0
  104. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/DatabaseModule.swift +259 -0
  105. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/FileSystemModule.swift +233 -0
  106. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/GeolocationModule.swift +156 -0
  107. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/NetworkModule.swift +59 -0
  108. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/PerformanceModule.swift +113 -0
  109. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/SecureStorageModule.swift +119 -0
  110. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/WebSocketModule.swift +212 -0
  111. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeEventDispatcher.swift +6 -0
  112. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModule.swift +26 -0
  113. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModuleRegistry.swift +37 -0
  114. package/native/shared/VueNativeShared/Sources/VueNativeShared/SharedJSPolyfills.swift +673 -0
  115. package/native/shared/VueNativeShared/Tests/VueNativeSharedTests/VueNativeSharedTests.swift +44 -0
  116. package/package.json +8 -2
@@ -0,0 +1,400 @@
1
+ #if canImport(UIKit)
2
+ import XCTest
3
+ import UIKit
4
+ @testable import VueNativeCore
5
+
6
+ // NOTE: Not @MainActor — native modules dispatch callbacks via
7
+ // DispatchQueue.main.async which deadlocks with @MainActor + waitForExpectations.
8
+ final class NativeModuleTests: XCTestCase {
9
+
10
+ // MARK: - HapticsModule Tests
11
+
12
+ func testHapticsModuleName() {
13
+ let module = HapticsModule()
14
+ XCTAssertEqual(module.moduleName, "Haptics", "HapticsModule should be named 'Haptics'")
15
+ }
16
+
17
+ func testHapticsModuleVibrateDoesNotCrash() {
18
+ let module = HapticsModule()
19
+ let expectation = self.expectation(description: "vibrate callback")
20
+
21
+ module.invoke(method: "vibrate", args: ["medium"]) { result, error in
22
+ XCTAssertNil(error, "vibrate should not return an error")
23
+ expectation.fulfill()
24
+ }
25
+
26
+ waitForExpectations(timeout: 2.0)
27
+ }
28
+
29
+ func testHapticsModuleVibrateStyles() {
30
+ let module = HapticsModule()
31
+ let styles = ["light", "medium", "heavy", "rigid", "soft"]
32
+
33
+ for style in styles {
34
+ let expectation = self.expectation(description: "vibrate \(style)")
35
+ module.invoke(method: "vibrate", args: [style]) { _, error in
36
+ XCTAssertNil(error, "vibrate(\(style)) should not return an error")
37
+ expectation.fulfill()
38
+ }
39
+ }
40
+
41
+ waitForExpectations(timeout: 5.0)
42
+ }
43
+
44
+ func testHapticsModuleNotificationFeedback() {
45
+ let module = HapticsModule()
46
+ let types = ["success", "warning", "error"]
47
+
48
+ for type in types {
49
+ let expectation = self.expectation(description: "notificationFeedback \(type)")
50
+ module.invoke(method: "notificationFeedback", args: [type]) { _, error in
51
+ XCTAssertNil(error, "notificationFeedback(\(type)) should not return an error")
52
+ expectation.fulfill()
53
+ }
54
+ }
55
+
56
+ waitForExpectations(timeout: 5.0)
57
+ }
58
+
59
+ func testHapticsModuleSelectionChanged() {
60
+ let module = HapticsModule()
61
+ let expectation = self.expectation(description: "selectionChanged callback")
62
+
63
+ module.invoke(method: "selectionChanged", args: []) { _, error in
64
+ XCTAssertNil(error, "selectionChanged should not return an error")
65
+ expectation.fulfill()
66
+ }
67
+
68
+ waitForExpectations(timeout: 2.0)
69
+ }
70
+
71
+ func testHapticsModuleUnknownMethodReturnsError() {
72
+ let module = HapticsModule()
73
+ let expectation = self.expectation(description: "unknown method callback")
74
+
75
+ module.invoke(method: "nonexistent", args: []) { _, error in
76
+ XCTAssertNotNil(error, "Unknown method should return an error")
77
+ XCTAssertTrue(error!.contains("Unknown method"), "Error should mention 'Unknown method'")
78
+ expectation.fulfill()
79
+ }
80
+
81
+ waitForExpectations(timeout: 2.0)
82
+ }
83
+
84
+ // MARK: - ClipboardModule Tests
85
+
86
+ func testClipboardModuleName() {
87
+ let module = ClipboardModule()
88
+ XCTAssertEqual(module.moduleName, "Clipboard", "ClipboardModule should be named 'Clipboard'")
89
+ }
90
+
91
+ func testClipboardModuleCopy() {
92
+ let module = ClipboardModule()
93
+ let expectation = self.expectation(description: "copy callback")
94
+
95
+ module.invoke(method: "copy", args: ["test clipboard"]) { _, copyError in
96
+ XCTAssertNil(copyError, "copy should not return an error")
97
+ expectation.fulfill()
98
+ }
99
+
100
+ waitForExpectations(timeout: 5.0)
101
+ }
102
+
103
+ // NOTE: Clipboard paste test omitted — UIPasteboard.general.string (read)
104
+ // triggers iOS clipboard access permission dialog in the simulator,
105
+ // which blocks the test runner indefinitely.
106
+
107
+ func testClipboardModuleCopyMissingTextReturnsError() {
108
+ let module = ClipboardModule()
109
+ let expectation = self.expectation(description: "copy without text")
110
+
111
+ module.invoke(method: "copy", args: []) { _, error in
112
+ XCTAssertNotNil(error, "copy without text should return an error")
113
+ XCTAssertTrue(error!.contains("missing text"), "Error should indicate missing text")
114
+ expectation.fulfill()
115
+ }
116
+
117
+ waitForExpectations(timeout: 2.0)
118
+ }
119
+
120
+ func testClipboardModuleUnknownMethod() {
121
+ let module = ClipboardModule()
122
+ let expectation = self.expectation(description: "unknown method")
123
+
124
+ module.invoke(method: "nonexistent", args: []) { _, error in
125
+ XCTAssertNotNil(error, "Unknown method should return an error")
126
+ expectation.fulfill()
127
+ }
128
+
129
+ waitForExpectations(timeout: 2.0)
130
+ }
131
+
132
+ // MARK: - DeviceInfoModule Tests
133
+
134
+ func testDeviceInfoModuleName() {
135
+ let module = DeviceInfoModule()
136
+ XCTAssertEqual(module.moduleName, "DeviceInfo", "DeviceInfoModule should be named 'DeviceInfo'")
137
+ }
138
+
139
+ func testDeviceInfoModuleGetInfoReturnsDeviceData() {
140
+ let module = DeviceInfoModule()
141
+ let expectation = self.expectation(description: "getInfo callback")
142
+
143
+ module.invoke(method: "getInfo", args: []) { result, error in
144
+ XCTAssertNil(error, "getInfo should not return an error")
145
+ XCTAssertNotNil(result, "getInfo should return device info")
146
+
147
+ if let info = result as? [String: Any] {
148
+ XCTAssertNotNil(info["model"], "Should include model")
149
+ XCTAssertNotNil(info["systemVersion"], "Should include systemVersion")
150
+ XCTAssertNotNil(info["systemName"], "Should include systemName")
151
+ XCTAssertNotNil(info["screenWidth"], "Should include screenWidth")
152
+ XCTAssertNotNil(info["screenHeight"], "Should include screenHeight")
153
+ XCTAssertNotNil(info["scale"], "Should include scale")
154
+
155
+ // Verify screen dimensions are positive
156
+ let width = info["screenWidth"] as? CGFloat ?? 0
157
+ XCTAssertGreaterThan(width, 0, "screenWidth should be positive")
158
+ let height = info["screenHeight"] as? CGFloat ?? 0
159
+ XCTAssertGreaterThan(height, 0, "screenHeight should be positive")
160
+ let scale = info["scale"] as? CGFloat ?? 0
161
+ XCTAssertGreaterThan(scale, 0, "scale should be positive")
162
+ } else {
163
+ XCTFail("getInfo result should be a [String: Any] dictionary")
164
+ }
165
+
166
+ expectation.fulfill()
167
+ }
168
+
169
+ waitForExpectations(timeout: 2.0)
170
+ }
171
+
172
+ func testDeviceInfoModuleUnknownMethod() {
173
+ let module = DeviceInfoModule()
174
+ let expectation = self.expectation(description: "unknown method")
175
+
176
+ module.invoke(method: "nonexistent", args: []) { _, error in
177
+ XCTAssertNotNil(error, "Unknown method should return an error")
178
+ expectation.fulfill()
179
+ }
180
+
181
+ waitForExpectations(timeout: 2.0)
182
+ }
183
+
184
+ // MARK: - AsyncStorageModule Tests
185
+
186
+ func testAsyncStorageModuleName() {
187
+ let module = AsyncStorageModule()
188
+ XCTAssertEqual(module.moduleName, "AsyncStorage", "AsyncStorageModule should be named 'AsyncStorage'")
189
+ }
190
+
191
+ func testAsyncStorageSetAndGetItem() {
192
+ let module = AsyncStorageModule()
193
+ let testKey = "test_key_\(UUID().uuidString)"
194
+
195
+ // AsyncStorageModule dispatches to DispatchQueue.global, so we can
196
+ // chain set → get → remove within a single expectation to avoid
197
+ // multiple sequential waitForExpectations on @MainActor.
198
+ let expectation = self.expectation(description: "set, get, and remove")
199
+
200
+ module.invoke(method: "setItem", args: [testKey, "test_value"]) { _, setError in
201
+ XCTAssertNil(setError, "setItem should not return an error")
202
+
203
+ module.invoke(method: "getItem", args: [testKey]) { result, getError in
204
+ XCTAssertNil(getError, "getItem should not return an error")
205
+ XCTAssertEqual(result as? String, "test_value", "getItem should return the stored value")
206
+
207
+ // Clean up
208
+ module.invoke(method: "removeItem", args: [testKey]) { _, _ in
209
+ expectation.fulfill()
210
+ }
211
+ }
212
+ }
213
+
214
+ waitForExpectations(timeout: 5.0)
215
+ }
216
+
217
+ func testAsyncStorageRemoveItem() {
218
+ let module = AsyncStorageModule()
219
+ let testKey = "remove_test_\(UUID().uuidString)"
220
+ let expectation = self.expectation(description: "set, remove, verify")
221
+
222
+ // Chain: set → remove → get (verify nil) in one expectation
223
+ module.invoke(method: "setItem", args: [testKey, "to_remove"]) { _, _ in
224
+ module.invoke(method: "removeItem", args: [testKey]) { _, removeError in
225
+ XCTAssertNil(removeError, "removeItem should not return an error")
226
+
227
+ module.invoke(method: "getItem", args: [testKey]) { result, getError in
228
+ XCTAssertNil(getError, "getItem should not return an error")
229
+ XCTAssertNil(result, "getItem should return nil after removal")
230
+ expectation.fulfill()
231
+ }
232
+ }
233
+ }
234
+
235
+ waitForExpectations(timeout: 5.0)
236
+ }
237
+
238
+ func testAsyncStorageGetItemMissingKeyReturnsError() {
239
+ let module = AsyncStorageModule()
240
+ let expectation = self.expectation(description: "getItem missing key")
241
+
242
+ module.invoke(method: "getItem", args: []) { _, error in
243
+ XCTAssertNotNil(error, "getItem without key should return an error")
244
+ expectation.fulfill()
245
+ }
246
+
247
+ waitForExpectations(timeout: 2.0)
248
+ }
249
+
250
+ func testAsyncStorageSetItemMissingArgsReturnsError() {
251
+ let module = AsyncStorageModule()
252
+ let expectation = self.expectation(description: "setItem missing args")
253
+
254
+ module.invoke(method: "setItem", args: ["keyOnly"]) { _, error in
255
+ XCTAssertNotNil(error, "setItem with missing value should return an error")
256
+ expectation.fulfill()
257
+ }
258
+
259
+ waitForExpectations(timeout: 2.0)
260
+ }
261
+
262
+ func testAsyncStorageUnknownMethod() {
263
+ let module = AsyncStorageModule()
264
+ let expectation = self.expectation(description: "unknown method")
265
+
266
+ module.invoke(method: "nonexistent", args: []) { _, error in
267
+ XCTAssertNotNil(error, "Unknown method should return an error")
268
+ expectation.fulfill()
269
+ }
270
+
271
+ waitForExpectations(timeout: 2.0)
272
+ }
273
+
274
+ // MARK: - AnimationModule Tests
275
+
276
+ func testAnimationModuleName() {
277
+ let module = AnimationModule()
278
+ XCTAssertEqual(module.moduleName, "Animation", "AnimationModule should be named 'Animation'")
279
+ }
280
+
281
+ func testAnimationModuleInvokeSyncReturnsNil() {
282
+ let module = AnimationModule()
283
+ let result = module.invokeSync(method: "timing", args: [])
284
+ XCTAssertNil(result, "invokeSync should return nil for AnimationModule")
285
+ }
286
+
287
+ func testAnimationModuleUnknownMethodReturnsError() {
288
+ let module = AnimationModule()
289
+ let expectation = self.expectation(description: "unknown method")
290
+
291
+ module.invoke(method: "nonexistent", args: []) { _, error in
292
+ XCTAssertNotNil(error, "Unknown method should return an error")
293
+ XCTAssertTrue(error!.contains("unknown method"), "Error should mention unknown method")
294
+ expectation.fulfill()
295
+ }
296
+
297
+ waitForExpectations(timeout: 2.0)
298
+ }
299
+
300
+ // MARK: - NetworkModule Tests
301
+
302
+ func testNetworkModuleName() {
303
+ let module = NetworkModule(bridge: NativeBridge.shared)
304
+ XCTAssertEqual(module.moduleName, "Network", "NetworkModule should be named 'Network'")
305
+ }
306
+
307
+ func testNetworkModuleGetStatusReturnsInfo() {
308
+ let module = NetworkModule(bridge: NativeBridge.shared)
309
+ let expectation = self.expectation(description: "getStatus callback")
310
+
311
+ module.invoke(method: "getStatus", args: []) { result, error in
312
+ XCTAssertNil(error, "getStatus should not return an error")
313
+ XCTAssertNotNil(result, "getStatus should return connection info")
314
+
315
+ if let info = result as? [String: Any] {
316
+ XCTAssertNotNil(info["isConnected"], "Should include isConnected")
317
+ XCTAssertNotNil(info["connectionType"], "Should include connectionType")
318
+ }
319
+ expectation.fulfill()
320
+ }
321
+
322
+ waitForExpectations(timeout: 2.0)
323
+ }
324
+
325
+ func testNetworkModuleUnknownMethod() {
326
+ let module = NetworkModule(bridge: NativeBridge.shared)
327
+ let expectation = self.expectation(description: "unknown method")
328
+
329
+ module.invoke(method: "nonexistent", args: []) { _, error in
330
+ XCTAssertNotNil(error, "Unknown method should return an error")
331
+ expectation.fulfill()
332
+ }
333
+
334
+ waitForExpectations(timeout: 2.0)
335
+ }
336
+
337
+ func testNetworkModuleInvokeSyncReturnsNil() {
338
+ let module = NetworkModule(bridge: NativeBridge.shared)
339
+ let result = module.invokeSync(method: "getStatus", args: [])
340
+ XCTAssertNil(result, "invokeSync should return nil")
341
+ }
342
+
343
+ // MARK: - KeyboardModule Tests
344
+
345
+ func testKeyboardModuleName() {
346
+ let module = KeyboardModule()
347
+ XCTAssertEqual(module.moduleName, "Keyboard", "KeyboardModule should be named 'Keyboard'")
348
+ }
349
+
350
+ func testKeyboardModuleDismissDoesNotCrash() {
351
+ let module = KeyboardModule()
352
+ let expectation = self.expectation(description: "dismiss callback")
353
+
354
+ module.invoke(method: "dismiss", args: []) { _, error in
355
+ XCTAssertNil(error, "dismiss should not return an error")
356
+ expectation.fulfill()
357
+ }
358
+
359
+ waitForExpectations(timeout: 2.0)
360
+ }
361
+
362
+ func testKeyboardModuleGetHeight() {
363
+ let module = KeyboardModule()
364
+ let expectation = self.expectation(description: "getHeight callback")
365
+
366
+ module.invoke(method: "getHeight", args: []) { result, error in
367
+ XCTAssertNil(error, "getHeight should not return an error")
368
+ XCTAssertNotNil(result, "getHeight should return keyboard info")
369
+
370
+ if let info = result as? [String: Any] {
371
+ XCTAssertNotNil(info["height"], "Should include height")
372
+ XCTAssertNotNil(info["isVisible"], "Should include isVisible")
373
+ }
374
+ expectation.fulfill()
375
+ }
376
+
377
+ waitForExpectations(timeout: 2.0)
378
+ }
379
+
380
+ func testKeyboardModuleUnknownMethod() {
381
+ let module = KeyboardModule()
382
+ let expectation = self.expectation(description: "unknown method")
383
+
384
+ module.invoke(method: "nonexistent", args: []) { _, error in
385
+ XCTAssertNotNil(error, "Unknown method should return an error")
386
+ expectation.fulfill()
387
+ }
388
+
389
+ waitForExpectations(timeout: 2.0)
390
+ }
391
+
392
+ // MARK: - NativeModule Protocol Default Implementation
393
+
394
+ func testNativeModuleDefaultInvokeSyncReturnsNil() {
395
+ let module = HapticsModule()
396
+ let result = module.invokeSync(method: "vibrate", args: ["light"])
397
+ XCTAssertNil(result, "Default invokeSync should return nil")
398
+ }
399
+ }
400
+ #endif
@@ -0,0 +1,34 @@
1
+ // swift-tools-version: 6.0
2
+ import PackageDescription
3
+
4
+ let package = Package(
5
+ name: "VueNativeMacOS",
6
+ platforms: [.macOS(.v15)],
7
+ products: [
8
+ .library(name: "VueNativeMacOS", targets: ["VueNativeMacOS"])
9
+ ],
10
+ dependencies: [
11
+ .package(path: "../../shared/VueNativeShared"),
12
+ ],
13
+ targets: [
14
+ .target(
15
+ name: "VueNativeMacOS",
16
+ dependencies: ["VueNativeShared"],
17
+ path: "Sources/VueNativeMacOS",
18
+ resources: [
19
+ .copy("Resources/vue-native-placeholder.js")
20
+ ],
21
+ swiftSettings: [
22
+ .swiftLanguageMode(.v5)
23
+ ]
24
+ ),
25
+ .testTarget(
26
+ name: "VueNativeMacOSTests",
27
+ dependencies: ["VueNativeMacOS"],
28
+ path: "Tests/VueNativeMacOSTests",
29
+ swiftSettings: [
30
+ .swiftLanguageMode(.v5)
31
+ ]
32
+ )
33
+ ]
34
+ )
@@ -0,0 +1,112 @@
1
+ import AppKit
2
+
3
+ /// Full-window red error overlay shown in dev mode when a JS exception occurs.
4
+ /// macOS version using NSView instead of UIView.
5
+ @MainActor
6
+ final class ErrorOverlayView: FlippedView {
7
+
8
+ private let titleLabel = NSTextField(labelWithString: "JavaScript Error")
9
+ private let messageLabel = NSTextField(wrappingLabelWithString: "")
10
+ private let dismissButton = NSButton(title: "Dismiss", target: nil, action: nil)
11
+ private let scrollView = NSScrollView()
12
+
13
+ override init(frame: NSRect) {
14
+ super.init(frame: frame)
15
+ setupUI()
16
+ }
17
+
18
+ required init?(coder: NSCoder) { fatalError() }
19
+
20
+ private func setupUI() {
21
+ layer?.backgroundColor = NSColor(red: 0.8, green: 0.1, blue: 0.1, alpha: 0.95).cgColor
22
+
23
+ titleLabel.textColor = .white
24
+ titleLabel.font = .systemFont(ofSize: 20, weight: .bold)
25
+ titleLabel.alignment = .center
26
+ titleLabel.isBordered = false
27
+ titleLabel.isEditable = false
28
+ titleLabel.drawsBackground = false
29
+
30
+ messageLabel.textColor = .white
31
+ messageLabel.font = .monospacedSystemFont(ofSize: 13, weight: .regular)
32
+ messageLabel.isEditable = false
33
+ messageLabel.isBordered = false
34
+ messageLabel.drawsBackground = false
35
+ messageLabel.maximumNumberOfLines = 0
36
+ messageLabel.lineBreakMode = .byWordWrapping
37
+
38
+ dismissButton.target = self
39
+ dismissButton.action = #selector(dismissOverlay)
40
+ dismissButton.bezelStyle = .rounded
41
+
42
+ scrollView.documentView = messageLabel
43
+ scrollView.hasVerticalScroller = true
44
+ scrollView.hasHorizontalScroller = false
45
+ scrollView.drawsBackground = false
46
+ scrollView.borderType = .noBorder
47
+
48
+ addSubview(titleLabel)
49
+ addSubview(scrollView)
50
+ addSubview(dismissButton)
51
+ }
52
+
53
+ override func layout() {
54
+ super.layout()
55
+
56
+ let padding: CGFloat = 20
57
+ let topPadding: CGFloat = 20
58
+
59
+ titleLabel.frame = CGRect(x: padding, y: topPadding, width: bounds.width - padding * 2, height: 30)
60
+
61
+ let buttonHeight: CGFloat = 30
62
+ dismissButton.frame = CGRect(
63
+ x: padding,
64
+ y: bounds.height - buttonHeight - padding,
65
+ width: bounds.width - padding * 2,
66
+ height: buttonHeight
67
+ )
68
+
69
+ scrollView.frame = CGRect(
70
+ x: padding,
71
+ y: topPadding + 40,
72
+ width: bounds.width - padding * 2,
73
+ height: dismissButton.frame.minY - topPadding - 50
74
+ )
75
+
76
+ // Size the message label to fill scroll view width
77
+ messageLabel.frame = CGRect(
78
+ x: 0, y: 0,
79
+ width: scrollView.contentSize.width,
80
+ height: 0
81
+ )
82
+ messageLabel.sizeToFit()
83
+ }
84
+
85
+ func show(error: String, in window: NSWindow) {
86
+ messageLabel.stringValue = error
87
+ guard let contentView = window.contentView else { return }
88
+
89
+ frame = contentView.bounds
90
+ autoresizingMask = [.width, .height]
91
+ contentView.addSubview(self)
92
+ needsLayout = true
93
+ }
94
+
95
+ @objc private func dismissOverlay() {
96
+ removeFromSuperview()
97
+ }
98
+
99
+ static func show(error: String) {
100
+ DispatchQueue.main.async {
101
+ guard let window = NSApp.mainWindow ?? NSApp.windows.first else { return }
102
+
103
+ // Remove any existing overlay
104
+ window.contentView?.subviews
105
+ .compactMap { $0 as? ErrorOverlayView }
106
+ .forEach { $0.removeFromSuperview() }
107
+
108
+ let overlay = ErrorOverlayView(frame: window.contentView?.bounds ?? .zero)
109
+ overlay.show(error: error, in: window)
110
+ }
111
+ }
112
+ }
@@ -0,0 +1,58 @@
1
+ import Foundation
2
+ import QuartzCore
3
+
4
+ /// Throttles high-frequency event handlers to avoid flooding the JS bridge.
5
+ ///
6
+ /// When a high-frequency event (scroll, slider drag, text input) fires many
7
+ /// times per frame, each invocation becomes a bridge round-trip. This utility
8
+ /// ensures at most one call per `interval` seconds, with a trailing call
9
+ /// to deliver the latest value.
10
+ ///
11
+ /// Default interval: 16ms (~60 FPS).
12
+ final class EventThrottle {
13
+
14
+ let interval: TimeInterval
15
+ private let handler: (Any?) -> Void
16
+ private var lastFireTime: CFTimeInterval = 0
17
+ private var pendingTrailing = false
18
+ private var latestPayload: Any?
19
+ private var trailingTimer: DispatchSourceTimer?
20
+
21
+ init(interval: TimeInterval = 0.016, handler: @escaping (Any?) -> Void) {
22
+ self.interval = interval
23
+ self.handler = handler
24
+ }
25
+
26
+ deinit {
27
+ trailingTimer?.cancel()
28
+ }
29
+
30
+ func fire(_ payload: Any?) {
31
+ let now = CACurrentMediaTime()
32
+ let elapsed = now - lastFireTime
33
+
34
+ latestPayload = payload
35
+
36
+ if elapsed >= interval {
37
+ lastFireTime = now
38
+ pendingTrailing = false
39
+ trailingTimer?.cancel()
40
+ trailingTimer = nil
41
+ handler(payload)
42
+ } else if !pendingTrailing {
43
+ pendingTrailing = true
44
+ let remaining = interval - elapsed
45
+ let timer = DispatchSource.makeTimerSource(queue: .main)
46
+ timer.schedule(deadline: .now() + remaining)
47
+ timer.setEventHandler { [weak self] in
48
+ guard let self = self else { return }
49
+ self.lastFireTime = CACurrentMediaTime()
50
+ self.pendingTrailing = false
51
+ self.trailingTimer = nil
52
+ self.handler(self.latestPayload)
53
+ }
54
+ trailingTimer = timer
55
+ timer.resume()
56
+ }
57
+ }
58
+ }