@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
|
@@ -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
|
+
}
|