@thelacanians/vue-native-cli 0.4.11 → 0.4.13

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 (69) hide show
  1. package/dist/cli.js +1 -1
  2. package/native/android/.editorconfig +25 -0
  3. package/native/android/VueNativeCore/build.gradle.kts +25 -1
  4. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/JSPolyfills.kt +17 -10
  5. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/JSRuntime.kt +5 -5
  6. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +13 -13
  7. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/ComponentRegistry.kt +27 -27
  8. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VActionSheetFactory.kt +6 -4
  9. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VActivityIndicatorFactory.kt +1 -1
  10. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VAlertDialogFactory.kt +24 -12
  11. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VButtonFactory.kt +5 -2
  12. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VImageFactory.kt +7 -7
  13. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VInputFactory.kt +12 -12
  14. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VKeyboardAvoidingFactory.kt +0 -1
  15. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VListFactory.kt +5 -2
  16. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VModalFactory.kt +5 -2
  17. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VPickerFactory.kt +3 -2
  18. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VPressableFactory.kt +5 -3
  19. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VRootFactory.kt +5 -2
  20. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VScrollViewFactory.kt +5 -2
  21. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSectionListFactory.kt +5 -2
  22. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSegmentedControlFactory.kt +3 -3
  23. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VStatusBarFactory.kt +3 -3
  24. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSwitchFactory.kt +0 -1
  25. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VViewFactory.kt +9 -3
  26. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VWebViewFactory.kt +7 -5
  27. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/NativeComponentFactory.kt +5 -2
  28. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Helpers/GestureHelper.kt +4 -1
  29. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AnimationModule.kt +77 -21
  30. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AsyncStorageModule.kt +20 -5
  31. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/BackgroundTaskModule.kt +12 -3
  32. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/BiometryModule.kt +5 -2
  33. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/BluetoothModule.kt +88 -23
  34. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/CalendarModule.kt +24 -11
  35. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/ClipboardModule.kt +7 -2
  36. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/ContactsModule.kt +24 -12
  37. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/DeviceInfoModule.kt +14 -11
  38. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/FileSystemModule.kt +79 -24
  39. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/GeolocationModule.kt +10 -7
  40. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/HapticsModule.kt +5 -5
  41. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/HttpModule.kt +17 -8
  42. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/IAPModule.kt +20 -5
  43. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/KeyboardModule.kt +4 -1
  44. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/LinkingModule.kt +12 -3
  45. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NetworkModule.kt +4 -1
  46. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NotificationsModule.kt +24 -6
  47. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/OTAModule.kt +13 -5
  48. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/PerformanceModule.kt +8 -2
  49. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/PermissionsModule.kt +17 -8
  50. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/SecureStorageModule.kt +20 -5
  51. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/SensorsModule.kt +16 -4
  52. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/ShareModule.kt +6 -3
  53. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/SocialAuthModule.kt +4 -2
  54. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/WebSocketModule.kt +26 -8
  55. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Styling/StyleEngine.kt +127 -84
  56. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Tags.kt +26 -26
  57. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/VueNativeActivity.kt +1 -1
  58. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ComponentRegistryTest.kt +173 -0
  59. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeBridgeTest.kt +436 -0
  60. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeModuleRegistryTest.kt +251 -0
  61. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/StyleEngineTest.kt +482 -0
  62. package/native/android/build.gradle.kts +1 -0
  63. package/native/ios/.swiftlint.yml +62 -0
  64. package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +4 -1
  65. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/ComponentRegistryTests.swift +237 -0
  66. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/NativeBridgeOperationTests.swift +398 -0
  67. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/NativeModuleRegistryTests.swift +203 -0
  68. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/StyleEngineTests.swift +381 -0
  69. package/package.json +1 -1
@@ -0,0 +1,203 @@
1
+ #if canImport(UIKit)
2
+ import XCTest
3
+ import UIKit
4
+ @testable import VueNativeCore
5
+
6
+ @MainActor
7
+ final class NativeModuleRegistryTests: XCTestCase {
8
+
9
+ // MARK: - Properties
10
+
11
+ private var moduleRegistry: NativeModuleRegistry!
12
+
13
+ // MARK: - Setup / Teardown
14
+
15
+ override func setUp() {
16
+ super.setUp()
17
+ moduleRegistry = NativeModuleRegistry.shared
18
+ }
19
+
20
+ override func tearDown() {
21
+ moduleRegistry = nil
22
+ super.tearDown()
23
+ }
24
+
25
+ // MARK: - Register and Invoke
26
+
27
+ func testRegisterAndInvokeModule() {
28
+ let mock = MockNativeModule(name: "TestModule")
29
+ mock.resultToReturn = ["status": "ok"]
30
+ moduleRegistry.register(mock)
31
+
32
+ let expectation = self.expectation(description: "invoke callback called")
33
+
34
+ moduleRegistry.invoke(module: "TestModule", method: "doSomething", args: ["arg1", 42]) { result, error in
35
+ XCTAssertNil(error, "Error should be nil for successful invocation")
36
+ XCTAssertNotNil(result, "Result should not be nil")
37
+ if let dict = result as? [String: String] {
38
+ XCTAssertEqual(dict["status"], "ok", "Result should match the configured return value")
39
+ }
40
+ expectation.fulfill()
41
+ }
42
+
43
+ waitForExpectations(timeout: 1.0)
44
+
45
+ XCTAssertEqual(mock.lastMethod, "doSomething", "Method name should be recorded")
46
+ XCTAssertEqual(mock.lastArgs?.count, 2, "Args should be passed through")
47
+ XCTAssertEqual(mock.lastArgs?[0] as? String, "arg1", "First arg should be 'arg1'")
48
+ XCTAssertEqual(mock.lastArgs?[1] as? Int, 42, "Second arg should be 42")
49
+ }
50
+
51
+ // MARK: - Invoke Unknown Module
52
+
53
+ func testInvokeUnknownModuleCallsCallbackWithError() {
54
+ let expectation = self.expectation(description: "invoke callback called with error")
55
+
56
+ moduleRegistry.invoke(module: "UnknownModule", method: "test", args: []) { result, error in
57
+ XCTAssertNil(result, "Result should be nil for unknown module")
58
+ XCTAssertNotNil(error, "Error should not be nil for unknown module")
59
+ XCTAssertTrue(
60
+ error!.contains("not found"),
61
+ "Error message should indicate module not found, got: \(error!)"
62
+ )
63
+ expectation.fulfill()
64
+ }
65
+
66
+ waitForExpectations(timeout: 1.0)
67
+ }
68
+
69
+ // MARK: - InvokeSync Unknown Module
70
+
71
+ func testInvokeSyncUnknownModuleReturnsNil() {
72
+ let result = moduleRegistry.invokeSync(module: "UnknownSyncModule", method: "test", args: [])
73
+ XCTAssertNil(result, "invokeSync should return nil for unknown module")
74
+ }
75
+
76
+ // MARK: - InvokeSync with Mock
77
+
78
+ func testInvokeSyncCallsThroughToModule() {
79
+ let mock = MockNativeModule(name: "SyncTestModule")
80
+ mock.resultToReturn = 99
81
+ moduleRegistry.register(mock)
82
+
83
+ let result = moduleRegistry.invokeSync(module: "SyncTestModule", method: "getCount", args: ["param"])
84
+
85
+ XCTAssertEqual(result as? Int, 99, "invokeSync should return the module's result")
86
+ XCTAssertEqual(mock.lastMethod, "getCount", "Method name should be recorded")
87
+ XCTAssertEqual(mock.lastArgs?.count, 1, "Args should be passed through")
88
+ XCTAssertEqual(mock.lastArgs?[0] as? String, "param", "First arg should be 'param'")
89
+ }
90
+
91
+ // MARK: - Register Overwrites Existing Module
92
+
93
+ func testRegisterOverwritesModuleWithSameName() {
94
+ let mock1 = MockNativeModule(name: "OverwriteModule")
95
+ mock1.resultToReturn = "first"
96
+ moduleRegistry.register(mock1)
97
+
98
+ let mock2 = MockNativeModule(name: "OverwriteModule")
99
+ mock2.resultToReturn = "second"
100
+ moduleRegistry.register(mock2)
101
+
102
+ let result = moduleRegistry.invokeSync(module: "OverwriteModule", method: "test", args: [])
103
+ XCTAssertEqual(result as? String, "second", "The second registered module should take precedence")
104
+ XCTAssertTrue(mock2.lastMethod == "test", "Second mock should have been invoked")
105
+ XCTAssertNil(mock1.lastMethod, "First mock should NOT have been invoked")
106
+ }
107
+
108
+ // MARK: - Individual Known Modules Can Be Registered
109
+
110
+ func testKnownModulesCanBeRegisteredIndividually() {
111
+ // registerDefaults() triggers all modules including ones that need a real
112
+ // app context (e.g. NotificationsModule accesses Bundle.main which crashes
113
+ // in the xctest host). Instead, register a few safe modules individually
114
+ // and verify they are invokable.
115
+ moduleRegistry.register(HapticsModule())
116
+ moduleRegistry.register(AsyncStorageModule())
117
+ moduleRegistry.register(ClipboardModule())
118
+ moduleRegistry.register(DeviceInfoModule())
119
+
120
+ let knownModules = ["Haptics", "AsyncStorage", "Clipboard", "DeviceInfo"]
121
+
122
+ for moduleName in knownModules {
123
+ let expectation = self.expectation(description: "\(moduleName) registered")
124
+
125
+ moduleRegistry.invoke(module: moduleName, method: "__nonexistent__", args: []) { _, error in
126
+ // The module should be found (no "not found" error).
127
+ // It may return a method-level error, but NOT a module-not-found error.
128
+ if let error = error {
129
+ XCTAssertFalse(
130
+ error.contains("not found"),
131
+ "Module '\(moduleName)' should be registered, but got error: \(error)"
132
+ )
133
+ }
134
+ expectation.fulfill()
135
+ }
136
+ }
137
+
138
+ waitForExpectations(timeout: 2.0)
139
+ }
140
+
141
+ // MARK: - Multiple Args Passed Correctly
142
+
143
+ func testMultipleArgsPassed() {
144
+ let mock = MockNativeModule(name: "ArgsTestModule")
145
+ mock.resultToReturn = nil
146
+ moduleRegistry.register(mock)
147
+
148
+ let expectation = self.expectation(description: "invoke callback called")
149
+
150
+ let args: [Any] = ["hello", 42, true, 3.14]
151
+ moduleRegistry.invoke(module: "ArgsTestModule", method: "multiArgs", args: args) { _, _ in
152
+ expectation.fulfill()
153
+ }
154
+
155
+ waitForExpectations(timeout: 1.0)
156
+
157
+ XCTAssertEqual(mock.lastArgs?.count, 4, "All 4 args should be passed")
158
+ XCTAssertEqual(mock.lastArgs?[0] as? String, "hello", "First arg should be 'hello'")
159
+ XCTAssertEqual(mock.lastArgs?[1] as? Int, 42, "Second arg should be 42")
160
+ XCTAssertEqual(mock.lastArgs?[2] as? Bool, true, "Third arg should be true")
161
+ XCTAssertEqual(mock.lastArgs?[3] as? Double, 3.14, "Fourth arg should be 3.14")
162
+ }
163
+
164
+ // MARK: - Empty Args
165
+
166
+ func testEmptyArgs() {
167
+ let mock = MockNativeModule(name: "EmptyArgsModule")
168
+ mock.resultToReturn = "done"
169
+ moduleRegistry.register(mock)
170
+
171
+ let result = moduleRegistry.invokeSync(module: "EmptyArgsModule", method: "noArgs", args: [])
172
+ XCTAssertEqual(result as? String, "done", "invokeSync should work with empty args")
173
+ XCTAssertEqual(mock.lastArgs?.count, 0, "Args array should be empty")
174
+ }
175
+ }
176
+
177
+ // MARK: - MockNativeModule
178
+
179
+ /// A test-only NativeModule that records invocations for verification.
180
+ private final class MockNativeModule: NativeModule {
181
+
182
+ var moduleName: String
183
+ var lastMethod: String?
184
+ var lastArgs: [Any]?
185
+ var resultToReturn: Any?
186
+
187
+ init(name: String) {
188
+ self.moduleName = name
189
+ }
190
+
191
+ func invoke(method: String, args: [Any], callback: @escaping (Any?, String?) -> Void) {
192
+ lastMethod = method
193
+ lastArgs = args
194
+ callback(resultToReturn, nil)
195
+ }
196
+
197
+ func invokeSync(method: String, args: [Any]) -> Any? {
198
+ lastMethod = method
199
+ lastArgs = args
200
+ return resultToReturn
201
+ }
202
+ }
203
+ #endif
@@ -0,0 +1,381 @@
1
+ #if canImport(UIKit)
2
+ import XCTest
3
+ import UIKit
4
+ @testable import VueNativeCore
5
+
6
+ @MainActor
7
+ final class StyleEngineTests: XCTestCase {
8
+
9
+ // MARK: - Properties
10
+
11
+ private var view: UIView!
12
+
13
+ // MARK: - Setup / Teardown
14
+
15
+ override func setUp() {
16
+ super.setUp()
17
+ view = UIView()
18
+ // Enable FlexLayout on the test view
19
+ _ = view.flex
20
+ }
21
+
22
+ override func tearDown() {
23
+ view = nil
24
+ super.tearDown()
25
+ }
26
+
27
+ // MARK: - yogaValue Tests
28
+
29
+ func testYogaValueWithDouble() {
30
+ let result = StyleEngine.yogaValue(42.5)
31
+ XCTAssertEqual(result, 42.5, "yogaValue should convert Double to CGFloat")
32
+ }
33
+
34
+ func testYogaValueWithInt() {
35
+ let result = StyleEngine.yogaValue(10)
36
+ XCTAssertEqual(result, 10.0, "yogaValue should convert Int to CGFloat")
37
+ }
38
+
39
+ func testYogaValueWithCGFloat() {
40
+ let cgValue: CGFloat = 33.3
41
+ let result = StyleEngine.yogaValue(cgValue)
42
+ XCTAssertEqual(result, 33.3, "yogaValue should pass through CGFloat")
43
+ }
44
+
45
+ func testYogaValueWithNumericString() {
46
+ let result = StyleEngine.yogaValue("25.5")
47
+ XCTAssertEqual(result, 25.5, "yogaValue should parse numeric strings")
48
+ }
49
+
50
+ func testYogaValueWithNil() {
51
+ let result = StyleEngine.yogaValue(nil)
52
+ XCTAssertNil(result, "yogaValue should return nil for nil input")
53
+ }
54
+
55
+ func testYogaValueWithNonNumericString() {
56
+ let result = StyleEngine.yogaValue("hello")
57
+ XCTAssertNil(result, "yogaValue should return nil for non-numeric strings")
58
+ }
59
+
60
+ // MARK: - isAuto Tests
61
+
62
+ func testIsAutoWithLowercaseAuto() {
63
+ XCTAssertTrue(StyleEngine.isAuto("auto"), "isAuto should return true for 'auto'")
64
+ }
65
+
66
+ func testIsAutoWithUppercaseAuto() {
67
+ XCTAssertTrue(StyleEngine.isAuto("AUTO"), "isAuto should return true for 'AUTO'")
68
+ }
69
+
70
+ func testIsAutoWithMixedCaseAuto() {
71
+ XCTAssertTrue(StyleEngine.isAuto("Auto"), "isAuto should return true for 'Auto'")
72
+ }
73
+
74
+ func testIsAutoWithOtherString() {
75
+ XCTAssertFalse(StyleEngine.isAuto("something"), "isAuto should return false for non-auto strings")
76
+ }
77
+
78
+ func testIsAutoWithNil() {
79
+ XCTAssertFalse(StyleEngine.isAuto(nil), "isAuto should return false for nil")
80
+ }
81
+
82
+ // MARK: - asPercent Tests
83
+
84
+ func testAsPercentWith50Percent() {
85
+ let result = StyleEngine.asPercent("50%")
86
+ XCTAssertEqual(result, 50.0, "asPercent should extract 50 from '50%'")
87
+ }
88
+
89
+ func testAsPercentWith100Percent() {
90
+ let result = StyleEngine.asPercent("100%")
91
+ XCTAssertEqual(result, 100.0, "asPercent should extract 100 from '100%'")
92
+ }
93
+
94
+ func testAsPercentWithInvalidPercentString() {
95
+ let result = StyleEngine.asPercent("abc%")
96
+ XCTAssertNil(result, "asPercent should return nil for 'abc%' (non-numeric prefix)")
97
+ }
98
+
99
+ func testAsPercentWithoutPercentSign() {
100
+ let result = StyleEngine.asPercent("50")
101
+ XCTAssertNil(result, "asPercent should return nil for strings without '%' suffix")
102
+ }
103
+
104
+ func testAsPercentWithNil() {
105
+ let result = StyleEngine.asPercent(nil)
106
+ XCTAssertNil(result, "asPercent should return nil for nil input")
107
+ }
108
+
109
+ // MARK: - backgroundColor Tests
110
+
111
+ func testApplyBackgroundColorRed() {
112
+ StyleEngine.apply(key: "backgroundColor", value: "#ff0000", to: view)
113
+
114
+ XCTAssertNotNil(view.backgroundColor, "backgroundColor should be set")
115
+
116
+ var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
117
+ view.backgroundColor!.getRed(&r, green: &g, blue: &b, alpha: &a)
118
+ XCTAssertEqual(r, 1.0, accuracy: 0.01, "Red component should be 1.0")
119
+ XCTAssertEqual(g, 0.0, accuracy: 0.01, "Green component should be 0.0")
120
+ XCTAssertEqual(b, 0.0, accuracy: 0.01, "Blue component should be 0.0")
121
+ }
122
+
123
+ func testApplyBackgroundColorNilClearsColor() {
124
+ view.backgroundColor = .red
125
+ StyleEngine.apply(key: "backgroundColor", value: nil, to: view)
126
+ XCTAssertNil(view.backgroundColor, "backgroundColor should be nil after applying nil value")
127
+ }
128
+
129
+ // MARK: - opacity Tests
130
+
131
+ func testApplyOpacity() {
132
+ StyleEngine.apply(key: "opacity", value: 0.5, to: view)
133
+ XCTAssertEqual(view.alpha, 0.5, accuracy: 0.001, "view.alpha should be 0.5")
134
+ }
135
+
136
+ func testApplyOpacityNilResetsToOne() {
137
+ view.alpha = 0.3
138
+ StyleEngine.apply(key: "opacity", value: nil, to: view)
139
+ XCTAssertEqual(view.alpha, 1.0, accuracy: 0.001, "view.alpha should reset to 1.0 when opacity is nil")
140
+ }
141
+
142
+ // MARK: - borderRadius Tests
143
+
144
+ func testApplyBorderRadius() {
145
+ StyleEngine.apply(key: "borderRadius", value: 10.0, to: view)
146
+ XCTAssertEqual(view.layer.cornerRadius, 10.0, accuracy: 0.001, "cornerRadius should be 10")
147
+ XCTAssertTrue(view.clipsToBounds, "clipsToBounds should be true when borderRadius > 0")
148
+ }
149
+
150
+ // MARK: - borderWidth Tests
151
+
152
+ func testApplyBorderWidth() {
153
+ StyleEngine.apply(key: "borderWidth", value: 2.0, to: view)
154
+ XCTAssertEqual(view.layer.borderWidth, 2.0, accuracy: 0.001, "borderWidth should be 2")
155
+ }
156
+
157
+ // MARK: - borderColor Tests
158
+
159
+ func testApplyBorderColorGreen() {
160
+ StyleEngine.apply(key: "borderColor", value: "#00ff00", to: view)
161
+ XCTAssertNotNil(view.layer.borderColor, "borderColor should be set")
162
+
163
+ let color = UIColor(cgColor: view.layer.borderColor!)
164
+ var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
165
+ color.getRed(&r, green: &g, blue: &b, alpha: &a)
166
+ XCTAssertEqual(r, 0.0, accuracy: 0.01, "Red component should be 0.0")
167
+ XCTAssertEqual(g, 1.0, accuracy: 0.01, "Green component should be 1.0")
168
+ XCTAssertEqual(b, 0.0, accuracy: 0.01, "Blue component should be 0.0")
169
+ }
170
+
171
+ // MARK: - display Tests
172
+
173
+ func testApplyDisplayNone() {
174
+ StyleEngine.apply(key: "display", value: "none", to: view)
175
+ XCTAssertTrue(view.isHidden, "view.isHidden should be true when display is 'none'")
176
+ }
177
+
178
+ func testApplyDisplayFlex() {
179
+ view.isHidden = true
180
+ StyleEngine.apply(key: "display", value: "flex", to: view)
181
+ XCTAssertFalse(view.isHidden, "view.isHidden should be false when display is 'flex'")
182
+ }
183
+
184
+ // MARK: - overflow Tests
185
+
186
+ func testApplyOverflowHidden() {
187
+ StyleEngine.apply(key: "overflow", value: "hidden", to: view)
188
+ XCTAssertTrue(view.clipsToBounds, "clipsToBounds should be true when overflow is 'hidden'")
189
+ }
190
+
191
+ func testApplyOverflowVisible() {
192
+ view.clipsToBounds = true
193
+ StyleEngine.apply(key: "overflow", value: "visible", to: view)
194
+ XCTAssertFalse(view.clipsToBounds, "clipsToBounds should be false when overflow is 'visible'")
195
+ }
196
+
197
+ // MARK: - hidden Tests
198
+
199
+ func testApplyHiddenTrue() {
200
+ StyleEngine.apply(key: "hidden", value: true, to: view)
201
+ XCTAssertTrue(view.isHidden, "view.isHidden should be true")
202
+ }
203
+
204
+ func testApplyHiddenFalse() {
205
+ view.isHidden = true
206
+ StyleEngine.apply(key: "hidden", value: false, to: view)
207
+ XCTAssertFalse(view.isHidden, "view.isHidden should be false")
208
+ }
209
+
210
+ // MARK: - zIndex Tests
211
+
212
+ func testApplyZIndex() {
213
+ StyleEngine.apply(key: "zIndex", value: 5.0, to: view)
214
+ XCTAssertEqual(view.layer.zPosition, 5.0, accuracy: 0.001, "zPosition should be 5")
215
+ }
216
+
217
+ // MARK: - Shadow Tests
218
+
219
+ func testApplyShadowOpacity() {
220
+ StyleEngine.apply(key: "shadowOpacity", value: 0.5, to: view)
221
+ XCTAssertEqual(view.layer.shadowOpacity, 0.5, accuracy: 0.001, "shadowOpacity should be 0.5")
222
+ }
223
+
224
+ func testApplyShadowRadius() {
225
+ StyleEngine.apply(key: "shadowRadius", value: 10.0, to: view)
226
+ XCTAssertEqual(view.layer.shadowRadius, 10.0, accuracy: 0.001, "shadowRadius should be 10")
227
+ }
228
+
229
+ func testApplyShadowOffsetX() {
230
+ StyleEngine.apply(key: "shadowOffsetX", value: 5.0, to: view)
231
+ XCTAssertEqual(view.layer.shadowOffset.width, 5.0, accuracy: 0.001, "shadowOffset.width should be 5")
232
+ }
233
+
234
+ func testApplyShadowOffsetY() {
235
+ StyleEngine.apply(key: "shadowOffsetY", value: 3.0, to: view)
236
+ XCTAssertEqual(view.layer.shadowOffset.height, 3.0, accuracy: 0.001, "shadowOffset.height should be 3")
237
+ }
238
+
239
+ // MARK: - Internal Props Tests
240
+
241
+ func testInternalPropStoreAndRetrieve() {
242
+ StyleEngine.apply(key: "__myProp", value: "hello", to: view)
243
+ let retrieved = StyleEngine.getInternalProp("__myProp", from: view)
244
+ XCTAssertEqual(retrieved as? String, "hello", "Internal prop should be stored and retrievable")
245
+ }
246
+
247
+ func testInternalPropNilRemovesProp() {
248
+ StyleEngine.apply(key: "__myProp", value: "hello", to: view)
249
+ StyleEngine.apply(key: "__myProp", value: nil, to: view)
250
+ let retrieved = StyleEngine.getInternalProp("__myProp", from: view)
251
+ XCTAssertNil(retrieved, "Internal prop should be nil after setting to nil")
252
+ }
253
+
254
+ // MARK: - Accessibility Tests
255
+
256
+ func testAccessibilityLabel() {
257
+ StyleEngine.apply(key: "accessibilityLabel", value: "Test Label", to: view)
258
+ XCTAssertEqual(view.accessibilityLabel, "Test Label", "accessibilityLabel should be set")
259
+ XCTAssertTrue(view.isAccessibilityElement, "isAccessibilityElement should be true")
260
+ }
261
+
262
+ func testAccessibilityRoleButton() {
263
+ StyleEngine.apply(key: "accessibilityRole", value: "button", to: view)
264
+ XCTAssertTrue(
265
+ view.accessibilityTraits.contains(.button),
266
+ "accessibilityTraits should contain .button"
267
+ )
268
+ XCTAssertTrue(view.isAccessibilityElement, "isAccessibilityElement should be true")
269
+ }
270
+
271
+ // MARK: - Text Property Tests
272
+
273
+ func testFontSizeOnUILabel() {
274
+ let label = UILabel()
275
+ _ = label.flex
276
+ StyleEngine.apply(key: "fontSize", value: 24.0, to: label)
277
+ XCTAssertEqual(label.font.pointSize, 24.0, accuracy: 0.1, "Font size should be 24")
278
+ }
279
+
280
+ func testTextColorOnUILabel() {
281
+ let label = UILabel()
282
+ _ = label.flex
283
+ StyleEngine.apply(key: "color", value: "#0000ff", to: label)
284
+
285
+ var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
286
+ label.textColor.getRed(&r, green: &g, blue: &b, alpha: &a)
287
+ XCTAssertEqual(r, 0.0, accuracy: 0.01, "Red component should be 0.0")
288
+ XCTAssertEqual(g, 0.0, accuracy: 0.01, "Green component should be 0.0")
289
+ XCTAssertEqual(b, 1.0, accuracy: 0.01, "Blue component should be 1.0")
290
+ }
291
+
292
+ func testTextAlignCenterOnUILabel() {
293
+ let label = UILabel()
294
+ _ = label.flex
295
+ StyleEngine.apply(key: "textAlign", value: "center", to: label)
296
+ XCTAssertEqual(label.textAlignment, .center, "textAlignment should be .center")
297
+ }
298
+
299
+ // MARK: - applyStyles Batch Tests
300
+
301
+ func testApplyStylesBatch() {
302
+ let styles: [String: Any] = [
303
+ "opacity": 0.7,
304
+ "borderRadius": 8.0,
305
+ "borderWidth": 1.0,
306
+ ]
307
+ StyleEngine.applyStyles(styles, to: view)
308
+
309
+ XCTAssertEqual(view.alpha, 0.7, accuracy: 0.001, "alpha should be 0.7")
310
+ XCTAssertEqual(view.layer.cornerRadius, 8.0, accuracy: 0.001, "cornerRadius should be 8")
311
+ XCTAssertEqual(view.layer.borderWidth, 1.0, accuracy: 0.001, "borderWidth should be 1")
312
+ }
313
+
314
+ // MARK: - Transform Tests
315
+
316
+ func testApplyTransformRotate() {
317
+ let transforms: [[String: Any]] = [["rotate": "90deg"]]
318
+ StyleEngine.apply(key: "transform", value: transforms, to: view)
319
+ XCTAssertFalse(
320
+ view.transform.isIdentity,
321
+ "Transform should not be identity after applying a 90deg rotation"
322
+ )
323
+ }
324
+
325
+ func testApplyTransformNilResetsToIdentity() {
326
+ // First apply a transform
327
+ let transforms: [[String: Any]] = [["rotate": "45deg"]]
328
+ StyleEngine.apply(key: "transform", value: transforms, to: view)
329
+ XCTAssertFalse(view.transform.isIdentity, "Transform should not be identity after rotation")
330
+
331
+ // Then reset by applying nil (non-array value resets to identity)
332
+ StyleEngine.apply(key: "transform", value: nil, to: view)
333
+ XCTAssertTrue(view.transform.isIdentity, "Transform should be identity after applying nil")
334
+ }
335
+
336
+ func testApplyTransformScale() {
337
+ let transforms: [[String: Any]] = [["scale": 2.0]]
338
+ StyleEngine.apply(key: "transform", value: transforms, to: view)
339
+ XCTAssertFalse(
340
+ view.transform.isIdentity,
341
+ "Transform should not be identity after applying scale"
342
+ )
343
+ // Verify scale components
344
+ XCTAssertEqual(view.transform.a, 2.0, accuracy: 0.001, "Scale X should be 2.0")
345
+ XCTAssertEqual(view.transform.d, 2.0, accuracy: 0.001, "Scale Y should be 2.0")
346
+ }
347
+
348
+ // MARK: - Text Property Fallback Tests (non-UILabel)
349
+
350
+ func testTextPropsIgnoredOnNonLabel() {
351
+ // Text properties should not crash on non-UILabel views
352
+ StyleEngine.apply(key: "fontSize", value: 24.0, to: view)
353
+ StyleEngine.apply(key: "color", value: "#ff0000", to: view)
354
+ StyleEngine.apply(key: "textAlign", value: "center", to: view)
355
+ // No assertion failure means the test passes
356
+ }
357
+
358
+ // MARK: - Edge Cases
359
+
360
+ func testApplyUnknownKeyDoesNotCrash() {
361
+ // Unknown keys that don't match any category should be silently ignored
362
+ StyleEngine.apply(key: "nonExistentProperty", value: "test", to: view)
363
+ }
364
+
365
+ func testBorderRadiusZeroDoesNotEnableClipping() {
366
+ view.clipsToBounds = false
367
+ StyleEngine.apply(key: "borderRadius", value: 0.0, to: view)
368
+ // cornerRadius 0 should not force clipsToBounds to true
369
+ // (the code only sets clipsToBounds when num > 0)
370
+ XCTAssertEqual(view.layer.cornerRadius, 0.0, accuracy: 0.001, "cornerRadius should be 0")
371
+ }
372
+
373
+ func testShadowOffsetPreservesOtherAxis() {
374
+ // Set both axes independently and verify they don't interfere
375
+ StyleEngine.apply(key: "shadowOffsetX", value: 5.0, to: view)
376
+ StyleEngine.apply(key: "shadowOffsetY", value: 3.0, to: view)
377
+ XCTAssertEqual(view.layer.shadowOffset.width, 5.0, accuracy: 0.001, "X should be preserved")
378
+ XCTAssertEqual(view.layer.shadowOffset.height, 3.0, accuracy: 0.001, "Y should be set correctly")
379
+ }
380
+ }
381
+ #endif
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thelacanians/vue-native-cli",
3
- "version": "0.4.11",
3
+ "version": "0.4.13",
4
4
  "description": "CLI for creating and running Vue Native apps",
5
5
  "license": "MIT",
6
6
  "author": "Vue Native Contributors",