expo-modules-core 0.4.6 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +6 -6
  3. package/android/build.gradle +30 -2
  4. package/android/src/main/java/expo/modules/adapters/react/ModuleRegistryAdapter.java +27 -5
  5. package/android/src/main/java/expo/modules/adapters/react/NativeModulesProxy.java +49 -5
  6. package/android/src/main/java/expo/modules/core/BasePackage.java +6 -0
  7. package/android/src/main/java/expo/modules/core/ModulePriorities.kt +25 -0
  8. package/android/src/main/java/expo/modules/core/interfaces/ActivityEventListener.java +3 -1
  9. package/android/src/main/java/expo/modules/core/interfaces/ApplicationLifecycleListener.java +10 -0
  10. package/android/src/main/java/expo/modules/core/interfaces/Package.java +4 -0
  11. package/android/src/main/java/expo/modules/core/interfaces/ReactActivityHandler.java +21 -0
  12. package/android/src/main/java/expo/modules/core/interfaces/ReactActivityLifecycleListener.java +14 -0
  13. package/android/src/main/java/expo/modules/core/interfaces/ReactNativeHostHandler.java +70 -0
  14. package/android/src/main/java/expo/modules/core/utilities/KotlinUtilities.kt +23 -0
  15. package/android/src/main/java/expo/modules/kotlin/AppContext.kt +166 -0
  16. package/android/src/main/java/expo/modules/kotlin/DynamicExtenstions.kt +9 -0
  17. package/android/src/main/java/expo/modules/kotlin/ExpoModulesHelper.kt +18 -0
  18. package/android/src/main/java/expo/modules/kotlin/KPromiseWrapper.kt +24 -0
  19. package/android/src/main/java/expo/modules/kotlin/KotlinInteropModuleRegistry.kt +98 -0
  20. package/android/src/main/java/expo/modules/kotlin/ModuleHolder.kt +41 -0
  21. package/android/src/main/java/expo/modules/kotlin/ModuleRegistry.kt +56 -0
  22. package/android/src/main/java/expo/modules/kotlin/ModulesProvider.kt +7 -0
  23. package/android/src/main/java/expo/modules/kotlin/Promise.kt +13 -0
  24. package/android/src/main/java/expo/modules/kotlin/ReactLifecycleDelegate.kt +39 -0
  25. package/android/src/main/java/expo/modules/kotlin/ReadableArrayIterator.kt +14 -0
  26. package/android/src/main/java/expo/modules/kotlin/ReadableTypeExtensions.kt +18 -0
  27. package/android/src/main/java/expo/modules/kotlin/allocators/ObjectConstructor.kt +5 -0
  28. package/android/src/main/java/expo/modules/kotlin/allocators/ObjectConstructorFactory.kt +31 -0
  29. package/android/src/main/java/expo/modules/kotlin/allocators/UnsafeAllocator.kt +49 -0
  30. package/android/src/main/java/expo/modules/kotlin/events/EventListener.kt +39 -0
  31. package/android/src/main/java/expo/modules/kotlin/events/EventName.kt +31 -0
  32. package/android/src/main/java/expo/modules/kotlin/events/EventsDefinition.kt +3 -0
  33. package/android/src/main/java/expo/modules/kotlin/events/KEventEmitterWrapper.kt +26 -0
  34. package/android/src/main/java/expo/modules/kotlin/events/OnActivityResultPayload.kt +8 -0
  35. package/android/src/main/java/expo/modules/kotlin/exception/CodedException.kt +70 -0
  36. package/android/src/main/java/expo/modules/kotlin/methods/AnyMethod.kt +50 -0
  37. package/android/src/main/java/expo/modules/kotlin/methods/Method.kt +14 -0
  38. package/android/src/main/java/expo/modules/kotlin/methods/PromiseMethod.kt +15 -0
  39. package/android/src/main/java/expo/modules/kotlin/modules/Module.kt +24 -0
  40. package/android/src/main/java/expo/modules/kotlin/modules/ModuleDefinitionBuilder.kt +227 -0
  41. package/android/src/main/java/expo/modules/kotlin/modules/ModuleDefinitionData.kt +16 -0
  42. package/android/src/main/java/expo/modules/kotlin/records/Field.kt +5 -0
  43. package/android/src/main/java/expo/modules/kotlin/records/Record.kt +3 -0
  44. package/android/src/main/java/expo/modules/kotlin/records/RecordTypeConverter.kt +55 -0
  45. package/android/src/main/java/expo/modules/kotlin/types/AnyType.kt +14 -0
  46. package/android/src/main/java/expo/modules/kotlin/types/ArrayTypeConverter.kt +44 -0
  47. package/android/src/main/java/expo/modules/kotlin/types/BasicTypeConverters.kt +60 -0
  48. package/android/src/main/java/expo/modules/kotlin/types/EnumTypeConverter.kt +84 -0
  49. package/android/src/main/java/expo/modules/kotlin/types/ListTypeConverter.kt +25 -0
  50. package/android/src/main/java/expo/modules/kotlin/types/MapTypeConverter.kt +39 -0
  51. package/android/src/main/java/expo/modules/kotlin/types/PairTypeConverter.kt +28 -0
  52. package/android/src/main/java/expo/modules/kotlin/types/TypeConverter.kt +19 -0
  53. package/android/src/main/java/expo/modules/kotlin/types/TypeConverterProvider.kt +107 -0
  54. package/android/src/main/java/expo/modules/kotlin/views/AnyViewProp.kt +10 -0
  55. package/android/src/main/java/expo/modules/kotlin/views/ConcreteViewProp.kt +17 -0
  56. package/android/src/main/java/expo/modules/kotlin/views/GroupViewManagerWrapper.kt +22 -0
  57. package/android/src/main/java/expo/modules/kotlin/views/SimpleViewManagerWrapper.kt +21 -0
  58. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerDefinition.kt +41 -0
  59. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerDefinitionBuilder.kt +40 -0
  60. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerWrapperDelegate.kt +21 -0
  61. package/android/src/main/java/expo/modules/kotlin/views/ViewWrapperDelegateHolder.kt +5 -0
  62. package/build/NativeModulesProxy.native.d.ts +4 -0
  63. package/build/NativeModulesProxy.native.js +14 -1
  64. package/build/NativeModulesProxy.native.js.map +1 -1
  65. package/build/NativeModulesProxy.types.d.ts +3 -0
  66. package/build/NativeModulesProxy.types.js.map +1 -1
  67. package/build/NativeViewManagerAdapter.native.js +1 -1
  68. package/build/NativeViewManagerAdapter.native.js.map +1 -1
  69. package/ios/AppDelegates/EXAppDelegateWrapper.h +19 -0
  70. package/ios/AppDelegates/EXAppDelegateWrapper.m +45 -0
  71. package/ios/AppDelegates/EXAppDelegatesLoader.h +15 -0
  72. package/ios/AppDelegates/EXAppDelegatesLoader.m +30 -0
  73. package/ios/AppDelegates/EXLegacyAppDelegateWrapper.h +16 -0
  74. package/ios/{EXAppDelegateWrapper.m → AppDelegates/EXLegacyAppDelegateWrapper.m} +2 -2
  75. package/ios/AppDelegates/ExpoAppDelegate.swift +282 -0
  76. package/ios/AppDelegates/ExpoAppDelegateSubscriber.swift +24 -0
  77. package/ios/EXAppDefines.h +26 -0
  78. package/ios/EXAppDefines.m +61 -0
  79. package/ios/ExpoModulesCore.podspec +8 -3
  80. package/ios/JSI/ExpoModulesProxySpec.h +24 -0
  81. package/ios/JSI/ExpoModulesProxySpec.mm +135 -0
  82. package/ios/JSI/JSIConversions.h +42 -0
  83. package/ios/JSI/JSIConversions.mm +164 -0
  84. package/ios/JSI/JSIInstaller.h +19 -0
  85. package/ios/JSI/JSIInstaller.mm +22 -0
  86. package/ios/ModuleRegistryAdapter/EXModuleRegistryAdapter.m +1 -6
  87. package/ios/NativeModulesProxy/EXNativeModulesProxy.h +6 -0
  88. package/ios/NativeModulesProxy/{EXNativeModulesProxy.m → EXNativeModulesProxy.mm} +91 -18
  89. package/ios/ReactDelegates/EXRCTBridgeDelegateInterceptor.h +16 -0
  90. package/ios/ReactDelegates/EXRCTBridgeDelegateInterceptor.m +49 -0
  91. package/ios/ReactDelegates/EXReactDelegateWrapper+Private.h +18 -0
  92. package/ios/ReactDelegates/EXReactDelegateWrapper.h +25 -0
  93. package/ios/ReactDelegates/EXReactDelegateWrapper.m +40 -0
  94. package/ios/ReactDelegates/ExpoReactDelegate.swift +37 -0
  95. package/ios/ReactDelegates/ExpoReactDelegateHandler.swift +52 -0
  96. package/ios/ReactDelegates/ModulePriorities.swift +20 -0
  97. package/ios/Services/EXReactNativeEventEmitter.h +6 -0
  98. package/ios/Services/EXReactNativeEventEmitter.m +15 -0
  99. package/ios/Swift/AppContext.swift +14 -1
  100. package/ios/Swift/Arguments/AnyArgument.swift +14 -0
  101. package/ios/Swift/Arguments/AnyArgumentType.swift +13 -0
  102. package/ios/Swift/Arguments/ArgumentType.swift +24 -0
  103. package/ios/Swift/Arguments/ConvertibleArgument.swift +15 -0
  104. package/ios/Swift/Arguments/Convertibles.swift +107 -0
  105. package/ios/Swift/Arguments/Types/ArrayArgumentType.swift +42 -0
  106. package/ios/Swift/Arguments/Types/ConvertibleArgumentType.swift +16 -0
  107. package/ios/Swift/Arguments/Types/EnumArgumentType.swift +105 -0
  108. package/ios/Swift/Arguments/Types/OptionalArgumentType.swift +49 -0
  109. package/ios/Swift/Arguments/Types/PromiseArgumentType.swift +15 -0
  110. package/ios/Swift/Arguments/Types/RawArgumentType.swift +25 -0
  111. package/ios/Swift/Conversions.swift +199 -7
  112. package/ios/Swift/EventListener.swift +37 -5
  113. package/ios/Swift/Functions/AnyFunction.swift +42 -0
  114. package/ios/Swift/{Methods/ConcreteMethod.swift → Functions/ConcreteFunction.swift} +32 -34
  115. package/ios/Swift/ModuleHolder.swift +86 -20
  116. package/ios/Swift/ModuleRegistry.swift +19 -8
  117. package/ios/Swift/Modules/AnyModule.swift +8 -8
  118. package/ios/Swift/Modules/Module.swift +11 -0
  119. package/ios/Swift/Modules/ModuleDefinition.swift +55 -15
  120. package/ios/Swift/Modules/ModuleDefinitionBuilder.swift +1 -1
  121. package/ios/Swift/Modules/ModuleDefinitionComponents.swift +149 -54
  122. package/ios/Swift/ModulesProvider.swift +19 -0
  123. package/ios/Swift/Promise.swift +1 -1
  124. package/ios/Swift/Records/Field.swift +1 -1
  125. package/ios/Swift/Records/Record.swift +8 -1
  126. package/ios/Swift/SwiftInteropBridge.swift +46 -17
  127. package/ios/Swift/Views/AnyViewProp.swift +2 -2
  128. package/ios/Swift/Views/ConcreteViewProp.swift +37 -10
  129. package/ios/Swift/Views/ViewModuleWrapper.swift +9 -4
  130. package/ios/Swift.h +9 -0
  131. package/ios/Tests/ArgumentTypeSpec.swift +145 -0
  132. package/ios/Tests/ConstantsSpec.swift +36 -0
  133. package/ios/Tests/ConvertiblesSpec.swift +265 -0
  134. package/ios/Tests/EXAppDefinesTest.m +99 -0
  135. package/ios/Tests/{MethodSpec.swift → FunctionSpec.swift} +69 -54
  136. package/ios/Tests/FunctionWithConvertiblesSpec.swift +66 -0
  137. package/ios/Tests/Mocks/ModuleMocks.swift +21 -7
  138. package/ios/Tests/ModuleEventListenersSpec.swift +17 -16
  139. package/ios/Tests/ModuleRegistrySpec.swift +4 -7
  140. package/package.json +3 -3
  141. package/src/NativeModulesProxy.native.ts +22 -2
  142. package/src/NativeModulesProxy.types.ts +8 -0
  143. package/src/NativeViewManagerAdapter.native.tsx +1 -1
  144. package/android/src/main/java/expo/modules/core/interfaces/ApplicationLifecycleListener.kt +0 -9
  145. package/android/src/main/java/expo/modules/core/interfaces/ReactActivityLifecycleListener.kt +0 -11
  146. package/android/src/main/java/expo/modules/core/interfaces/ReactNativeHostHandler.kt +0 -51
  147. package/ios/EXAppDelegateWrapper.h +0 -13
  148. package/ios/Swift/Methods/AnyArgumentType.swift +0 -48
  149. package/ios/Swift/Methods/AnyMethod.swift +0 -31
  150. package/ios/Swift/Methods/AnyMethodArgument.swift +0 -13
@@ -5,12 +5,12 @@ import UIKit
5
5
  */
6
6
  public protocol AnyViewProp: AnyDefinition {
7
7
  /**
8
- Name of the prop.
8
+ Name of the view prop that JavaScript refers to.
9
9
  */
10
10
  var name: String { get }
11
11
 
12
12
  /**
13
13
  Function that sets the underlying prop value for given view.
14
14
  */
15
- func set(value: Any?, onView: UIView)
15
+ func set(value: Any, onView: UIView) throws
16
16
  }
@@ -1,30 +1,57 @@
1
+ // Copyright 2021-present 650 Industries. All rights reserved.
2
+
1
3
  import UIKit
2
4
 
3
5
  /**
4
6
  Specialized class for the view prop. Specifies the prop name and its setter.
5
7
  */
6
- public class ConcreteViewProp<ViewType: UIView, PropType>: AnyViewProp {
8
+ public final class ConcreteViewProp<ViewType: UIView, PropType: AnyArgument>: AnyViewProp {
7
9
  public typealias SetterType = (ViewType, PropType) -> Void
8
10
 
11
+ /**
12
+ Name of the view prop that JavaScript refers to.
13
+ */
9
14
  public let name: String
10
15
 
11
- let setter: SetterType
16
+ /**
17
+ An argument type wrapper for the prop's value type.
18
+ */
19
+ private let propType: AnyArgumentType
20
+
21
+ /**
22
+ Closure to call to set the actual property on the given view.
23
+ */
24
+ private let setter: SetterType
12
25
 
13
- init(_ name: String, _ setter: @escaping SetterType) {
26
+ internal init(name: String, propType: AnyArgumentType, setter: @escaping SetterType) {
14
27
  self.name = name
28
+ self.propType = propType
15
29
  self.setter = setter
16
30
  }
17
31
 
18
- public func set(value: Any?, onView view: UIView) {
19
- // Method's signature must be type-erased for `AnyViewProp` protocol,
20
- // so we have to get UIView and cast it to the generic type.
21
- // TODO: (@tsapeta) Throw an error instead of crashing the app.
32
+ /**
33
+ Function that sets the underlying prop value for given view.
34
+ */
35
+ public func set(value: Any, onView view: UIView) throws {
36
+ // Method's signature must be type-erased to conform to `AnyViewProp` protocol.
37
+ // Given view must be castable to the generic `ViewType` type.
22
38
  guard let view = view as? ViewType else {
23
- fatalError("Given view must subclass UIView")
39
+ throw IncompatibleViewError(propName: name, viewType: ViewType.self)
24
40
  }
25
- guard let value = value as? PropType else {
26
- fatalError("Given value `\(String(describing: value))` cannot be casted to `\(String(describing: PropType.self))`")
41
+ guard let value = try propType.cast(value) as? PropType else {
42
+ throw Conversions.CastingError<PropType>(value: value)
27
43
  }
28
44
  setter(view, value)
29
45
  }
30
46
  }
47
+
48
+ /**
49
+ An error that is thrown when the view passed to prop's setter doesn't match the type in setter's definition.
50
+ */
51
+ internal struct IncompatibleViewError: CodedError {
52
+ let propName: String
53
+ let viewType: UIView.Type
54
+ var description: String {
55
+ "Tried to set prop `\(propName)` on the view that isn't `\(viewType)`"
56
+ }
57
+ }
@@ -16,7 +16,7 @@ protocol DynamicModuleWrapperProtocol {
16
16
  We're generating its subclasses in runtime as a workaround.
17
17
  */
18
18
  @objc
19
- public class ViewModuleWrapper: RCTViewManager, DynamicModuleWrapperProtocol {
19
+ public final class ViewModuleWrapper: RCTViewManager, DynamicModuleWrapperProtocol {
20
20
  /**
21
21
  A reference to the module holder that stores the module definition.
22
22
  Enforced unwrapping is required since it can be set right after the object is initialized.
@@ -100,14 +100,19 @@ public class ViewModuleWrapper: RCTViewManager, DynamicModuleWrapperProtocol {
100
100
  The setter for `proxiedProperties` prop. In Objective-C style, this function is generated by `RCT_CUSTOM_VIEW_PROPERTY` macro.
101
101
  */
102
102
  @objc
103
- public func set_proxiedProperties(_ json: Any?, forView view: UIView, withDefaultView defaultView: UIView) {
104
- guard let json = json as? [String: Any?],
103
+ public func set_proxiedProperties(_ json: Any, forView view: UIView, withDefaultView defaultView: UIView) {
104
+ guard let json = json as? [String: Any],
105
105
  let props = wrappedModuleHolder.definition.viewManager?.propsDict() else {
106
106
  return
107
107
  }
108
108
  for (key, value) in json {
109
109
  if let prop = props[key] {
110
- prop.set(value: value, onView: view)
110
+ let value = Conversions.fromNSObject(value)
111
+
112
+ // TODO: @tsapeta: Figure out better way to rethrow errors from here.
113
+ // Adding `throws` keyword to the function results in different
114
+ // method signature in Objective-C. Maybe just call `RCTLogError`?
115
+ try? prop.set(value: value, onView: view)
111
116
  }
112
117
  }
113
118
  }
package/ios/Swift.h ADDED
@@ -0,0 +1,9 @@
1
+ // Copyright 2018-present 650 Industries. All rights reserved.
2
+
3
+ // When `use_frameworks!` is used, the generated Swift header is inside ExpoModulesCore module.
4
+ // Otherwise, it's available only locally with double-quoted imports.
5
+ #if __has_include(<ExpoModulesCore/ExpoModulesCore-Swift.h>)
6
+ #import <ExpoModulesCore/ExpoModulesCore-Swift.h>
7
+ #else
8
+ #import "ExpoModulesCore-Swift.h"
9
+ #endif
@@ -0,0 +1,145 @@
1
+ // Copyright 2021-present 650 Industries. All rights reserved.
2
+
3
+ import Quick
4
+ import Nimble
5
+
6
+ @testable import ExpoModulesCore
7
+
8
+ class ArgumentTypeSpec: QuickSpec {
9
+ override func spec() {
10
+
11
+ it("casts primitives") {
12
+ let type = ArgumentType(Int.self)
13
+ let value = 123
14
+ let anyValue = value as Any
15
+
16
+ expect(try type.cast(anyValue)).to(be(value))
17
+ }
18
+
19
+ it("casts optional types") {
20
+ let type = ArgumentType(Double?.self)
21
+ let value: Double? = nil
22
+ let anyValue = value as Any
23
+ let result = try type.cast(anyValue)
24
+
25
+ expect(result).to(beAKindOf(Double?.self))
26
+
27
+ // Since this `nil` is in fact of non-optional `Any` type, under the hood it's described as `Optional` enum.
28
+ // Simply checking `result == nil` does NOT work here, see `Optional.isNil` extension implementation.
29
+ expect(Optional.isNil(result)) == true
30
+ }
31
+
32
+ it("throws null cast error") {
33
+ let type = ArgumentType(Double.self) // non-optional (!)
34
+ let value: Double? = nil
35
+ let anyValue = value as Any
36
+
37
+ expect { try type.cast(anyValue) }.to(throwError(errorType: Conversions.NullCastError<Double>.self))
38
+ }
39
+
40
+ it("casts arrays") {
41
+ let type = ArgumentType([Double].self)
42
+ let value = 9.9
43
+ let anyValue = [value] as [Any]
44
+ let result = try type.cast(anyValue) as! [Any]
45
+
46
+ expect(result).to(beAKindOf([Double].self))
47
+ expect((result as! [Double]).first) == value
48
+ }
49
+
50
+ it("casts convertibles") {
51
+ let type = ArgumentType(ConvertibleTestStruct.self)
52
+ let value = "expo is the best"
53
+ let result = try type.cast(value)
54
+
55
+ expect(result).to(beAKindOf(ConvertibleTestStruct.self))
56
+ expect((result as! ConvertibleTestStruct).value) == value
57
+ }
58
+
59
+ it("casts array of convertibles") {
60
+ let type = ArgumentType([ConvertibleTestStruct].self)
61
+ let value = ["expo is the best"]
62
+ let result = try type.cast(value)
63
+
64
+ expect(result).to(beAKindOf([ConvertibleTestStruct].self))
65
+ expect((result as! [ConvertibleTestStruct]).first!.value) == value.first
66
+ }
67
+
68
+ it("casts array of array of convertibles") {
69
+ let type = ArgumentType([[ConvertibleTestStruct]].self)
70
+ let value = [["expo is the best"]]
71
+ let result = try type.cast(value)
72
+
73
+ expect(result).to(beAKindOf([[ConvertibleTestStruct]].self))
74
+ expect((result as! [[ConvertibleTestStruct]]).first!.first!.value) == value.first!.first
75
+ }
76
+
77
+ describe("EnumArgumentType") {
78
+ it("casts from String") {
79
+ let type = ArgumentType(StringTestEnum.self)
80
+ let input = "expo"
81
+ let output = try type.cast(input)
82
+
83
+ expect(output).to(beAKindOf(StringTestEnum.self))
84
+ expect(output as? StringTestEnum) == StringTestEnum.expo
85
+ expect(output as? StringTestEnum) == StringTestEnum(rawValue: input)
86
+ expect((output as! StringTestEnum).rawValue) == input
87
+ expect((output as! EnumArgument).anyRawValue).to(beAKindOf(String.self))
88
+ }
89
+
90
+ it("casts from Int") {
91
+ let type = ArgumentType(IntTestEnum.self)
92
+ let input: Int = -1
93
+ let output = try type.cast(input)
94
+
95
+ expect(output).to(beAKindOf(IntTestEnum.self))
96
+ expect(output as? IntTestEnum) == IntTestEnum.negative
97
+ expect(output as? IntTestEnum) == IntTestEnum(rawValue: input)
98
+ expect((output as! IntTestEnum).rawValue) == input
99
+ expect((output as! EnumArgument).anyRawValue).to(beAKindOf(Int.self))
100
+ }
101
+
102
+ it("throws casting error") {
103
+ let type = ArgumentType(IntTestEnum.self)
104
+
105
+ // "841" is not a raw value of any `IntTestEnum` case
106
+ expect { try type.cast("string instead of int") }.to(throwError {
107
+ expect($0).to(beAKindOf(EnumCastingError.self))
108
+ })
109
+ }
110
+
111
+ it("throws no such value error") {
112
+ let type = ArgumentType(IntTestEnum.self)
113
+
114
+ // "841" is not a raw value of any `IntTestEnum` case
115
+ expect { try type.cast(841) }.to(throwError {
116
+ expect($0).to(beAKindOf(EnumNoSuchValueError.self))
117
+ })
118
+ }
119
+
120
+ it("gets a list of all raw values") {
121
+ expect(StringTestEnum.allRawValues as? [String]) == ["hello", "expo"]
122
+ expect(IntTestEnum.allRawValues as? [Int]) == [-1, 1]
123
+ }
124
+ }
125
+ }
126
+ }
127
+
128
+ struct ConvertibleTestStruct: ConvertibleArgument {
129
+ let value: String
130
+
131
+ static func convert(from value: Any?) throws -> ConvertibleTestStruct {
132
+ guard let str = value as? String else { fatalError() }
133
+ return ConvertibleTestStruct(value: str)
134
+ }
135
+ }
136
+
137
+ enum StringTestEnum: String, EnumArgument {
138
+ case hello
139
+ case expo
140
+ }
141
+
142
+ enum IntTestEnum: Int, EnumArgument {
143
+ case negative = -1
144
+ case positive = 1
145
+ }
@@ -0,0 +1,36 @@
1
+ import Quick
2
+ import Nimble
3
+
4
+ @testable import ExpoModulesCore
5
+
6
+ class ConstantsSpec: QuickSpec {
7
+ override func spec() {
8
+ let appContext = AppContext()
9
+
10
+ it("takes closure resolving to dictionary") {
11
+ let holder = mockModuleHolder(appContext) {
12
+ $0.constants {
13
+ return ["test": 123]
14
+ }
15
+ }
16
+ expect(holder.getConstants()["test"] as? Int) == 123
17
+ }
18
+
19
+ it("takes the dictionary") {
20
+ let holder = mockModuleHolder(appContext) {
21
+ $0.constants(["test": 123])
22
+ }
23
+ expect(holder.getConstants()["test"] as? Int) == 123
24
+ }
25
+
26
+ it("merges multiple constants definitions") {
27
+ let holder = mockModuleHolder(appContext) {
28
+ $0.constants(["test": 456, "test2": 789])
29
+ $0.constants(["test": 123])
30
+ }
31
+ let consts = holder.getConstants()
32
+ expect(consts["test"] as? Int) == 123
33
+ expect(consts["test2"] as? Int) == 789
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,265 @@
1
+ // Copyright 2018-present 650 Industries. All rights reserved.
2
+
3
+ import CoreGraphics
4
+ import Quick
5
+ import Nimble
6
+
7
+ @testable import ExpoModulesCore
8
+
9
+ class ConvertiblesSpec: QuickSpec {
10
+ override func spec() {
11
+ describe("URL") {
12
+ it("converts from remote url") {
13
+ let remoteUrlString = "https://expo.dev"
14
+ let url = try URL.convert(from: remoteUrlString)
15
+
16
+ expect(url.path) == ""
17
+ expect(url.absoluteString) == remoteUrlString
18
+ }
19
+
20
+ it("converts from file url") {
21
+ let fileUrlString = "file:///expo/tmp"
22
+ let url = try URL.convert(from: fileUrlString)
23
+
24
+ expect(url.path) == "/expo/tmp"
25
+ expect(url.absoluteString) == fileUrlString
26
+ expect(url.isFileURL) == true
27
+ }
28
+
29
+ it("converts from file path") {
30
+ let filePath = "/expo/image.png"
31
+ let url = try URL.convert(from: filePath)
32
+
33
+ expect(url.path) == filePath
34
+ expect(url.absoluteString) == "file://\(filePath)"
35
+ expect(url.isFileURL) == true
36
+ }
37
+
38
+ it("throws when no string") {
39
+ expect { try URL.convert(from: 29.5) }.to(
40
+ throwError(errorType: Conversions.ConvertingError<URL>.self)
41
+ )
42
+ }
43
+ }
44
+
45
+ describe("CGPoint") {
46
+ let x = -8.3
47
+ let y = 4.6
48
+
49
+ it("converts from array of doubles") {
50
+ let point = try CGPoint.convert(from: [x, y])
51
+
52
+ expect(point.x) == x
53
+ expect(point.y) == y
54
+ }
55
+
56
+ it("converts from dict") {
57
+ let point = try CGPoint.convert(from: ["x": x, "y": y])
58
+
59
+ expect(point.x) == x
60
+ expect(point.y) == y
61
+ }
62
+
63
+ it("throws when array size is unexpected") { // different than two
64
+ expect { try CGPoint.convert(from: []) }.to(throwError(errorType: Conversions.ConvertingError<CGPoint>.self))
65
+ expect { try CGPoint.convert(from: [x]) }.to(throwError(errorType: Conversions.ConvertingError<CGPoint>.self))
66
+ expect { try CGPoint.convert(from: [x, y, x]) }.to(throwError(errorType: Conversions.ConvertingError<CGPoint>.self))
67
+ }
68
+
69
+ it("throws when dict is missing some keys") {
70
+ expect { try CGPoint.convert(from: ["test": x]) }.to(throwError {
71
+ expect($0).to(beAKindOf(Conversions.MissingKeysError<Double>.self))
72
+ expect(($0 as! CodedError).description) == Conversions.MissingKeysError<Double>(keys: ["x", "y"]).description
73
+ })
74
+ }
75
+
76
+ it("throws when dict has uncastable keys") {
77
+ expect { try CGPoint.convert(from: ["x": x, "y": "string"]) }.to(throwError {
78
+ expect($0).to(beAKindOf(Conversions.CastingValuesError<Double>.self))
79
+ expect(($0 as! CodedError).description) == Conversions.CastingValuesError<Double>(keys: ["y"]).description
80
+ })
81
+ }
82
+ }
83
+
84
+ describe("CGSize") {
85
+ let width = 52.8
86
+ let height = 81.7
87
+
88
+ it("converts from array of doubles") {
89
+ let size = try CGSize.convert(from: [width, height])
90
+
91
+ expect(size.width) == width
92
+ expect(size.height) == height
93
+ }
94
+
95
+ it("converts from dict") {
96
+ let size = try CGSize.convert(from: ["width": width, "height": height])
97
+
98
+ expect(size.width) == width
99
+ expect(size.height) == height
100
+ }
101
+
102
+ it("throws when array size is unexpected") { // different than two
103
+ expect { try CGSize.convert(from: []) }.to(throwError(errorType: Conversions.ConvertingError<CGSize>.self))
104
+ expect { try CGSize.convert(from: [width]) }.to(throwError(errorType: Conversions.ConvertingError<CGSize>.self))
105
+ expect { try CGSize.convert(from: [width, height, width]) }.to(throwError(errorType: Conversions.ConvertingError<CGSize>.self))
106
+ }
107
+
108
+ it("throws when dict is missing some keys") {
109
+ expect { try CGSize.convert(from: ["width": width]) }.to(throwError {
110
+ expect($0).to(beAKindOf(Conversions.MissingKeysError<Double>.self))
111
+ expect(($0 as! CodedError).description) == Conversions.MissingKeysError<Double>(keys: ["height"]).description
112
+ })
113
+ }
114
+
115
+ it("throws when dict has uncastable keys") {
116
+ expect { try CGSize.convert(from: ["width": "test", "height": height]) }.to(throwError {
117
+ expect($0).to(beAKindOf(Conversions.CastingValuesError<Double>.self))
118
+ expect(($0 as! CodedError).description) == Conversions.CastingValuesError<Double>(keys: ["width"]).description
119
+ })
120
+ }
121
+ }
122
+
123
+ describe("CGVector") {
124
+ let dx = 11.6
125
+ let dy = -4.0
126
+
127
+ it("converts from array of doubles") {
128
+ let vector = try CGVector.convert(from: [dx, dy])
129
+
130
+ expect(vector.dx) == dx
131
+ expect(vector.dy) == dy
132
+ }
133
+
134
+ it("converts from dict") {
135
+ let vector = try CGVector.convert(from: ["dx": dx, "dy": dy])
136
+
137
+ expect(vector.dx) == dx
138
+ expect(vector.dy) == dy
139
+ }
140
+
141
+ it("throws when array size is unexpected") { // different than two
142
+ expect { try CGVector.convert(from: []) }.to(throwError(errorType: Conversions.ConvertingError<CGVector>.self))
143
+ expect { try CGVector.convert(from: [dx]) }.to(throwError(errorType: Conversions.ConvertingError<CGVector>.self))
144
+ expect { try CGVector.convert(from: [dx, dy, dx]) }.to(throwError(errorType: Conversions.ConvertingError<CGVector>.self))
145
+ }
146
+
147
+ it("throws when dict is missing some keys") {
148
+ expect { try CGVector.convert(from: ["dx": dx]) }.to(throwError {
149
+ expect($0).to(beAKindOf(Conversions.MissingKeysError<Double>.self))
150
+ expect(($0 as! CodedError).description) == Conversions.MissingKeysError<Double>(keys: ["dy"]).description
151
+ })
152
+ }
153
+
154
+ it("throws when dict has uncastable keys") {
155
+ expect { try CGVector.convert(from: ["dx": "dx", "dy": dy]) }.to(throwError {
156
+ expect($0).to(beAKindOf(Conversions.CastingValuesError<Double>.self))
157
+ expect(($0 as! CodedError).description) == Conversions.CastingValuesError<Double>(keys: ["dx"]).description
158
+ })
159
+ }
160
+ }
161
+
162
+ describe("CGRect") {
163
+ let x = -8.3
164
+ let y = 4.6
165
+ let width = 52.8
166
+ let height = 81.7
167
+
168
+ it("converts from array of doubles") {
169
+ let rect = try CGRect.convert(from: [x, y, width, height])
170
+
171
+ expect(rect.origin.x) == x
172
+ expect(rect.origin.y) == y
173
+ expect(rect.width) == width
174
+ expect(rect.height) == height
175
+ }
176
+
177
+ it("converts from dict") {
178
+ let rect = try CGRect.convert(from: ["x": x, "y": y, "width": width, "height": height])
179
+
180
+ expect(rect.origin.x) == x
181
+ expect(rect.origin.y) == y
182
+ expect(rect.width) == width
183
+ expect(rect.height) == height
184
+ }
185
+
186
+ it("throws when array size is unexpected") { // different than four
187
+ expect { try CGRect.convert(from: [x]) }.to(throwError(errorType: Conversions.ConvertingError<CGRect>.self))
188
+ expect { try CGRect.convert(from: [x, y]) }.to(throwError(errorType: Conversions.ConvertingError<CGRect>.self))
189
+ expect { try CGRect.convert(from: [x, y, width, height, y]) }.to(throwError(errorType: Conversions.ConvertingError<CGRect>.self))
190
+ }
191
+
192
+ it("throws when dict is missing some keys") {
193
+ expect { try CGRect.convert(from: ["x": x]) }.to(throwError {
194
+ expect($0).to(beAKindOf(Conversions.MissingKeysError<Double>.self))
195
+ expect(($0 as! CodedError).description) == Conversions.MissingKeysError<Double>(keys: ["y", "width", "height"]).description
196
+ })
197
+ }
198
+
199
+ it("throws when dict has uncastable keys") {
200
+ expect { try CGRect.convert(from: ["x": x, "y": nil, "width": width, "height": "\(height)"]) }.to(throwError {
201
+ expect($0).to(beAKindOf(Conversions.CastingValuesError<Double>.self))
202
+ expect(($0 as! CodedError).description) == Conversions.CastingValuesError<Double>(keys: ["y", "height"]).description
203
+ })
204
+ }
205
+ }
206
+
207
+ describe("UIColor/CGColor") {
208
+ func testColorComponents(_ color: CGColor, _ red: CGFloat, _ green: CGFloat, _ blue: CGFloat, _ alpha: CGFloat) {
209
+ expect(color.components?[0]) == red / 255.0
210
+ expect(color.components?[1]) == green / 255.0
211
+ expect(color.components?[2]) == blue / 255.0
212
+ expect(color.components?[3]) == alpha / 255.0
213
+ }
214
+ func testInvalidHexColor(_ hex: String) {
215
+ expect { try CGColor.convert(from: hex) }.to(throwError {
216
+ expect($0).to(beAKindOf(Conversions.InvalidHexColorError.self))
217
+ expect(($0 as! CodedError).description) == Conversions.InvalidHexColorError(hex: hex).description
218
+ })
219
+ }
220
+
221
+ it("converts from ARGB int") {
222
+ // NOTE: int representation has alpha channel at the beginning
223
+ let color = try CGColor.convert(from: 0x5147AC7F)
224
+ testColorComponents(color, 0x47, 0xAC, 0x7F, 0x51)
225
+ }
226
+
227
+ it("converts from RGBA hex string") {
228
+ let color = try CGColor.convert(from: "47AC7F51")
229
+ testColorComponents(color, 0x47, 0xAC, 0x7F, 0x51)
230
+ }
231
+
232
+ it("converts from #RGBA hex string") {
233
+ let color = try CGColor.convert(from: " #47AC7F51")
234
+ testColorComponents(color, 0x47, 0xAC, 0x7F, 0x51)
235
+ }
236
+
237
+ it("converts from 3-character shorthand hex string") {
238
+ let color = try CGColor.convert(from: "C2B ")
239
+ testColorComponents(color, 0xCC, 0x22, 0xBB, 0xFF)
240
+ }
241
+
242
+ it("converts from 4-character shorthand hex string") {
243
+ let color = try CGColor.convert(from: " #9EA5 ")
244
+ testColorComponents(color, 0x99, 0xEE, 0xAA, 0x55)
245
+ }
246
+
247
+ it("throws when hex string is invalid") {
248
+ testInvalidHexColor("")
249
+ testInvalidHexColor("#21")
250
+ testInvalidHexColor("ABCDEFGH")
251
+ testInvalidHexColor("1122334455")
252
+ testInvalidHexColor("XYZ")
253
+ testInvalidHexColor("!@#$%")
254
+ }
255
+
256
+ it("throws when int overflows") {
257
+ let hex = 0xBBAA88FF2
258
+ expect { try CGColor.convert(from: hex) }.to(throwError {
259
+ expect($0).to(beAKindOf(Conversions.HexColorOverflowError.self))
260
+ expect(($0 as! CodedError).description) == Conversions.HexColorOverflowError(hex: UInt64(hex)).description
261
+ })
262
+ }
263
+ }
264
+ }
265
+ }
@@ -0,0 +1,99 @@
1
+ // Copyright 2016-present 650 Industries. All rights reserved.
2
+
3
+ #import <XCTest/XCTest.h>
4
+ #import <ExpoModulesCore/EXAppDefines.h>
5
+
6
+ @interface EXAppDefines (EXAppDefinesWithUnloader)
7
+
8
+ + (void)_unload;
9
+
10
+ @end
11
+
12
+ @interface EXAppDefinesTest : XCTestCase
13
+
14
+ @end
15
+
16
+ @implementation EXAppDefinesTest
17
+
18
+ - (void)setUp
19
+ {
20
+ // EXAppDefines expects to load just once.
21
+ // To make it testable with difference test cases,
22
+ // we call the internal private `_unload` method to reset state.
23
+ [EXAppDefines performSelector:@selector(_unload)];
24
+ }
25
+
26
+ - (void)test_load
27
+ {
28
+ NSDictionary *defines = @{
29
+ @"APP_DEBUG": @(YES),
30
+ @"APP_RCT_DEBUG": @(YES),
31
+ @"APP_RCT_DEV": @(YES),
32
+ };
33
+ XCTAssertNoThrow([EXAppDefines load:defines]);
34
+ }
35
+
36
+ - (void)test_load_throwIfLoadedTwice
37
+ {
38
+ NSDictionary *defines = @{
39
+ @"APP_DEBUG": @(YES),
40
+ @"APP_RCT_DEBUG": @(YES),
41
+ @"APP_RCT_DEV": @(YES),
42
+ };
43
+ XCTAssertNoThrow([EXAppDefines load:defines]);
44
+ XCTAssertThrows([EXAppDefines load:defines]);
45
+ }
46
+
47
+ - (void)test_loadAndGetAppDebug_shouldMatchDebugDefines
48
+ {
49
+ NSDictionary *defines = @{
50
+ @"APP_DEBUG": @(YES),
51
+ @"APP_RCT_DEBUG": @(YES),
52
+ @"APP_RCT_DEV": @(YES),
53
+ };
54
+ [EXAppDefines load:defines];
55
+ XCTAssertEqual(EXAppDefines.APP_DEBUG, YES);
56
+ }
57
+
58
+ - (void)test_loadAndGetAppDebug_shouldMatchReleaseDefines
59
+ {
60
+ NSDictionary *defines = @{
61
+ @"APP_DEBUG": @(NO),
62
+ @"APP_RCT_DEBUG": @(NO),
63
+ @"APP_RCT_DEV": @(NO),
64
+ };
65
+ [EXAppDefines load:defines];
66
+ XCTAssertEqual(EXAppDefines.APP_DEBUG, NO);
67
+ }
68
+
69
+ - (void)test_getters_returnsDefaultValues
70
+ {
71
+ XCTAssertNoThrow([EXAppDefines load:@{}]);
72
+ XCTAssertEqual(EXAppDefines.APP_DEBUG, NO);
73
+ XCTAssertEqual(EXAppDefines.APP_RCT_DEBUG, NO);
74
+ XCTAssertEqual(EXAppDefines.APP_RCT_DEV, NO);
75
+ XCTAssertEqual(EXAppDefines.getAllDefines.count, 0u);
76
+ }
77
+
78
+ - (void)test_getAppDebug_throwIfNotLoaded
79
+ {
80
+ XCTAssertThrows(EXAppDefines.APP_DEBUG);
81
+ }
82
+
83
+ - (void)test_passExtraDefines_shouldGetMatchedDefines
84
+ {
85
+ NSDictionary *defines = @{
86
+ @"APP_DEBUG": @(YES),
87
+ @"APP_RCT_DEBUG": @(YES),
88
+ @"APP_RCT_DEV": @(YES),
89
+ @"foo": @1,
90
+ @"bar": @2,
91
+ };
92
+
93
+ [EXAppDefines load:defines];
94
+ NSDictionary *result = EXAppDefines.getAllDefines;
95
+ XCTAssertEqual([result[@"foo"] intValue], 1);
96
+ XCTAssertEqual([result[@"bar"] intValue], 2);
97
+ }
98
+
99
+ @end