@thelacanians/vue-native-cli 0.4.12 → 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.
- package/dist/cli.js +1 -1
- package/native/android/.editorconfig +25 -0
- package/native/android/VueNativeCore/build.gradle.kts +25 -1
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/JSPolyfills.kt +17 -10
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/JSRuntime.kt +5 -5
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +13 -13
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/ComponentRegistry.kt +27 -27
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VActionSheetFactory.kt +6 -4
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VActivityIndicatorFactory.kt +1 -1
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VAlertDialogFactory.kt +24 -12
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VButtonFactory.kt +5 -2
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VImageFactory.kt +7 -7
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VInputFactory.kt +12 -12
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VKeyboardAvoidingFactory.kt +0 -1
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VListFactory.kt +5 -2
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VModalFactory.kt +5 -2
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VPickerFactory.kt +3 -2
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VPressableFactory.kt +5 -3
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VRootFactory.kt +5 -2
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VScrollViewFactory.kt +5 -2
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSectionListFactory.kt +5 -2
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSegmentedControlFactory.kt +3 -3
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VStatusBarFactory.kt +3 -3
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSwitchFactory.kt +0 -1
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VViewFactory.kt +9 -3
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VWebViewFactory.kt +7 -5
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/NativeComponentFactory.kt +5 -2
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Helpers/GestureHelper.kt +4 -1
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AnimationModule.kt +77 -21
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AsyncStorageModule.kt +20 -5
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/BackgroundTaskModule.kt +12 -3
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/BiometryModule.kt +5 -2
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/BluetoothModule.kt +88 -23
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/CalendarModule.kt +24 -11
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/ClipboardModule.kt +7 -2
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/ContactsModule.kt +24 -12
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/DeviceInfoModule.kt +14 -11
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/FileSystemModule.kt +79 -24
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/GeolocationModule.kt +10 -7
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/HapticsModule.kt +5 -5
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/HttpModule.kt +17 -8
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/IAPModule.kt +20 -5
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/KeyboardModule.kt +4 -1
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/LinkingModule.kt +12 -3
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NetworkModule.kt +4 -1
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NotificationsModule.kt +24 -6
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/OTAModule.kt +13 -5
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/PerformanceModule.kt +8 -2
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/PermissionsModule.kt +17 -8
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/SecureStorageModule.kt +20 -5
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/SensorsModule.kt +16 -4
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/ShareModule.kt +6 -3
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/SocialAuthModule.kt +4 -2
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/WebSocketModule.kt +26 -8
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Styling/StyleEngine.kt +127 -84
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Tags.kt +26 -26
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/VueNativeActivity.kt +1 -1
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ComponentRegistryTest.kt +173 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeBridgeTest.kt +436 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeModuleRegistryTest.kt +251 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/StyleEngineTest.kt +482 -0
- package/native/android/build.gradle.kts +1 -0
- package/native/ios/.swiftlint.yml +62 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +4 -1
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/ComponentRegistryTests.swift +237 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/NativeBridgeOperationTests.swift +398 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/NativeModuleRegistryTests.swift +203 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/StyleEngineTests.swift +381 -0
- 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
|