@react-native-firebase/app 26.4.0 → 27.1.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 (242) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +10 -27
  3. package/RNFBApp.podspec +13 -5
  4. package/android/build.gradle +3 -0
  5. package/android/src/main/java/io/invertase/firebase/common/RNFBHandleCollisionException.kt +29 -0
  6. package/android/src/main/java/io/invertase/firebase/common/RNFBHandleMap.kt +146 -0
  7. package/android/src/main/java/io/invertase/firebase/common/TaskExecutorService.kt +156 -0
  8. package/android/src/main/java/io/invertase/firebase/common/UniversalFirebaseModule.kt +44 -0
  9. package/android/src/main/java/io/invertase/firebase/common/UniversalFirebasePreferences.kt +100 -0
  10. package/android/src/reactnative/java/io/invertase/firebase/app/NativeRNFBTurboApp.kt +250 -0
  11. package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseApp.kt +64 -0
  12. package/android/src/reactnative/java/io/invertase/firebase/app/{ReactNativeFirebaseAppInitProvider.java → ReactNativeFirebaseAppInitProvider.kt} +3 -4
  13. package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseAppPackage.kt +47 -0
  14. package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseAppRegistrar.kt +33 -0
  15. package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseVersion.java +1 -1
  16. package/android/src/reactnative/java/io/invertase/firebase/common/RCTConvertFirebase.kt +185 -0
  17. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseEvent.kt +35 -0
  18. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseEventEmitter.kt +662 -0
  19. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseJSON.kt +103 -0
  20. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseMeta.kt +96 -0
  21. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseModule.kt +140 -0
  22. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebasePreferences.kt +109 -0
  23. package/android/src/reactnative/java/io/invertase/firebase/common/SharedUtils.kt +451 -0
  24. package/android/src/reactnative/java/io/invertase/firebase/interfaces/{ContextProvider.java → ContextProvider.kt} +8 -8
  25. package/android/src/reactnative/java/io/invertase/firebase/interfaces/{NativeError.java → NativeError.kt} +8 -8
  26. package/android/src/reactnative/java/io/invertase/firebase/interfaces/{NativeEvent.java → NativeEvent.kt} +6 -6
  27. package/android/src/reactnative/java/io/invertase/firebase/utils/NativeRNFBTurboUtils.kt +219 -0
  28. package/android/src/test/java/expo/core/ModuleRegistry.java +9 -0
  29. package/android/src/test/java/io/invertase/firebase/app/NativeRNFBTurboAppTest.java +182 -59
  30. package/android/src/test/java/io/invertase/firebase/app/ReactNativeFirebaseAppComponentsJavaCompatibilityTest.java +70 -0
  31. package/android/src/test/java/io/invertase/firebase/app/ReactNativeFirebaseAppJavaCompatibilityTest.java +60 -0
  32. package/android/src/test/java/io/invertase/firebase/app/ReactNativeFirebaseAppPackageTest.java +165 -0
  33. package/android/src/test/java/io/invertase/firebase/app/ReactNativeFirebaseAppTest.kt +167 -0
  34. package/android/src/test/java/io/invertase/firebase/common/RCTConvertFirebaseJavaCompatibilityTest.java +112 -0
  35. package/android/src/test/java/io/invertase/firebase/common/RCTConvertFirebaseTest.kt +317 -0
  36. package/android/src/test/java/io/invertase/firebase/common/RNFBHandleMapTest.kt +309 -0
  37. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseEventEmitterJavaCompatibilityTest.java +176 -0
  38. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseEventEmitterTest.java +143 -12
  39. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseEventTest.kt +61 -0
  40. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseJSONJavaCompatibilityTest.java +85 -0
  41. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseJSONTest.kt +296 -0
  42. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseMetaJavaCompatibilityTest.java +67 -0
  43. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseMetaTest.kt +246 -0
  44. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseModuleTest.kt +254 -0
  45. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebasePreferencesJavaCompatibilityTest.java +87 -0
  46. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebasePreferencesTest.kt +260 -0
  47. package/android/src/test/java/io/invertase/firebase/common/SharedUtilsJavaCompatibilityTest.java +255 -0
  48. package/android/src/test/java/io/invertase/firebase/common/SharedUtilsTest.kt +674 -0
  49. package/android/src/test/java/io/invertase/firebase/common/TaskExecutorServiceTest.kt +290 -0
  50. package/android/src/test/java/io/invertase/firebase/common/UniversalFirebaseModuleJavaCompatibilityTest.java +178 -0
  51. package/android/src/test/java/io/invertase/firebase/common/UniversalFirebasePreferencesJavaCompatibilityTest.java +99 -0
  52. package/android/src/test/java/io/invertase/firebase/common/UniversalFirebasePreferencesTest.kt +217 -0
  53. package/android/src/test/java/io/invertase/firebase/commoncompat/ReactNativeFirebaseModuleJavaCompatibilityTest.java +176 -0
  54. package/android/src/test/java/io/invertase/firebase/commoncompat/ReactNativeFirebaseModuleKotlinCompatibilityTest.kt +52 -0
  55. package/android/src/test/java/io/invertase/firebase/interfaces/InterfacesJavaCompatibilityTest.java +144 -0
  56. package/android/src/test/java/io/invertase/firebase/interfaces/NativeEventTest.kt +56 -0
  57. package/android/src/test/java/io/invertase/firebase/utils/NativeRNFBTurboUtilsTest.java +517 -21
  58. package/dist/module/FirebaseApp.js +10 -0
  59. package/dist/module/FirebaseApp.js.map +1 -1
  60. package/dist/module/common/deeps.js +3 -0
  61. package/dist/module/common/deeps.js.map +1 -1
  62. package/dist/module/internal/FirebaseModule.js.map +1 -1
  63. package/dist/module/internal/RNFBNativeEventEmitter.js +12 -1
  64. package/dist/module/internal/RNFBNativeEventEmitter.js.map +1 -1
  65. package/dist/module/internal/SharedEventEmitter.js +3 -1
  66. package/dist/module/internal/SharedEventEmitter.js.map +1 -1
  67. package/dist/module/internal/index.js +1 -0
  68. package/dist/module/internal/index.js.map +1 -1
  69. package/dist/module/internal/registry/app.js +7 -1
  70. package/dist/module/internal/registry/app.js.map +1 -1
  71. package/dist/module/types/app.js.map +1 -1
  72. package/dist/module/types/internal.js +2 -0
  73. package/dist/module/version.js +1 -1
  74. package/dist/typescript/lib/FirebaseApp.d.ts +7 -0
  75. package/dist/typescript/lib/FirebaseApp.d.ts.map +1 -1
  76. package/dist/typescript/lib/common/deeps.d.ts.map +1 -1
  77. package/dist/typescript/lib/internal/FirebaseModule.d.ts +2 -3
  78. package/dist/typescript/lib/internal/FirebaseModule.d.ts.map +1 -1
  79. package/dist/typescript/lib/internal/NativeModules.d.ts +4 -1
  80. package/dist/typescript/lib/internal/NativeModules.d.ts.map +1 -1
  81. package/dist/typescript/lib/internal/RNFBNativeEventEmitter.d.ts +2 -2
  82. package/dist/typescript/lib/internal/RNFBNativeEventEmitter.d.ts.map +1 -1
  83. package/dist/typescript/lib/internal/SharedEventEmitter.d.ts +3 -3
  84. package/dist/typescript/lib/internal/SharedEventEmitter.d.ts.map +1 -1
  85. package/dist/typescript/lib/internal/index.d.ts +1 -0
  86. package/dist/typescript/lib/internal/index.d.ts.map +1 -1
  87. package/dist/typescript/lib/internal/registry/app.d.ts.map +1 -1
  88. package/dist/typescript/lib/types/app.d.ts +10 -3
  89. package/dist/typescript/lib/types/app.d.ts.map +1 -1
  90. package/dist/typescript/lib/types/internal.d.ts +12 -0
  91. package/dist/typescript/lib/types/internal.d.ts.map +1 -1
  92. package/dist/typescript/lib/version.d.ts +1 -1
  93. package/firebase-schema.json +4 -0
  94. package/firebase_spm.rb +552 -96
  95. package/ios/RNFBApp/RCTConvert+FIRApp.h +2 -5
  96. package/ios/RNFBApp/RCTConvert+FIRApp.m +11 -6
  97. package/ios/RNFBApp/RCTConvert+FIROptions.h +2 -5
  98. package/ios/RNFBApp/RCTConvert+FIROptions.m +11 -10
  99. package/ios/RNFBApp/RCTConvertFIRApp.swift +86 -0
  100. package/ios/RNFBApp/RCTConvertFIROptions.swift +259 -0
  101. package/ios/RNFBApp/RNFBAppCustomAuthDomains.swift +63 -0
  102. package/ios/RNFBApp/RNFBAppInitializeOptionsMapper.swift +113 -0
  103. package/ios/RNFBApp/RNFBAppLogLevelMapper.swift +53 -0
  104. package/ios/RNFBApp/RNFBAppModule+CustomAuthDomain.m +35 -0
  105. package/ios/RNFBApp/RNFBAppModule.h +6 -2
  106. package/ios/RNFBApp/RNFBAppModule.mm +65 -228
  107. package/ios/RNFBApp/RNFBAppModuleFirebase.swift +439 -0
  108. package/ios/RNFBApp/RNFBAppModuleImplementation.h +68 -0
  109. package/ios/RNFBApp/RNFBAppModuleImplementation.m +195 -0
  110. package/ios/RNFBApp/RNFBHandleMap.m +27 -56
  111. package/ios/RNFBApp/RNFBHandleMapStorage.swift +90 -0
  112. package/ios/RNFBApp/RNFBInitializeAppArguments.swift +82 -0
  113. package/ios/RNFBApp/RNFBJSON.m +22 -38
  114. package/ios/RNFBApp/RNFBJSONImplementation.swift +71 -0
  115. package/ios/RNFBApp/RNFBMeta.m +24 -26
  116. package/ios/RNFBApp/RNFBMetaStorage.swift +76 -0
  117. package/ios/RNFBApp/RNFBNullSentinelDecoding.swift +135 -0
  118. package/ios/RNFBApp/RNFBNullSentinelInterceptor.h +30 -3
  119. package/ios/RNFBApp/RNFBNullSentinelInterceptor.m +28 -5
  120. package/ios/RNFBApp/RNFBPreferences.m +22 -20
  121. package/ios/RNFBApp/RNFBPreferencesStorage.swift +101 -0
  122. package/ios/RNFBApp/RNFBRCTEventEmitter.m +36 -93
  123. package/ios/RNFBApp/RNFBRCTEventEmitterCore.swift +161 -0
  124. package/ios/RNFBApp/RNFBSharedUtils.h +2 -5
  125. package/ios/RNFBApp/RNFBSharedUtils.m +109 -198
  126. package/ios/RNFBApp/RNFBSharedUtilsConfig.swift +72 -0
  127. package/ios/RNFBApp/RNFBSharedUtilsFIRApp.swift +271 -0
  128. package/ios/RNFBApp/RNFBSharedUtilsFormatting.swift +59 -0
  129. package/ios/RNFBApp/RNFBSharedUtilsPromiseRejection.swift +88 -0
  130. package/ios/RNFBApp/RNFBUtilsHelpers.swift +116 -0
  131. package/ios/RNFBApp/RNFBUtilsModule+Foundation.m +44 -0
  132. package/ios/RNFBApp/RNFBUtilsModule+PhotoAssets.m +47 -0
  133. package/ios/RNFBApp/RNFBUtilsModule.h +11 -1
  134. package/ios/RNFBApp/RNFBUtilsModule.mm +10 -85
  135. package/ios/RNFBApp/RNFBUtilsModuleImplementation.h +32 -0
  136. package/ios/RNFBApp/RNFBUtilsModuleImplementation.m +56 -0
  137. package/ios/RNFBApp/RNFBVersion.m +1 -1
  138. package/ios/RNFBApp.xcodeproj/project.pbxproj +100 -0
  139. package/ios/RNFBAppUnitTests/HostStubs/FirebaseCore/FIRApp.m +171 -0
  140. package/ios/RNFBAppUnitTests/HostStubs/FirebaseCore/FirebaseCore.h +78 -0
  141. package/ios/RNFBAppUnitTests/HostStubs/FirebaseInstallations/FIRInstallations.m +7 -0
  142. package/ios/RNFBAppUnitTests/HostStubs/FirebaseInstallations/FirebaseInstallations.h +8 -0
  143. package/ios/RNFBAppUnitTests/HostStubs/RNFBAppModuleHostStub.m +55 -0
  144. package/ios/RNFBAppUnitTests/HostStubs/RNFBAppTurboModules.h +89 -0
  145. package/ios/RNFBAppUnitTests/HostStubs/RNFBAppTurboModules.mm +30 -0
  146. package/ios/RNFBAppUnitTests/HostStubs/React/RCTBridge.h +19 -0
  147. package/ios/RNFBAppUnitTests/HostStubs/React/RCTBridge.m +32 -0
  148. package/ios/RNFBAppUnitTests/HostStubs/React/RCTBridgeModule.h +19 -0
  149. package/ios/RNFBAppUnitTests/HostStubs/React/RCTConvert.h +28 -0
  150. package/ios/RNFBAppUnitTests/HostStubs/React/RCTConvert.m +23 -0
  151. package/ios/RNFBAppUnitTests/HostStubs/React/RCTEventEmitter.h +9 -0
  152. package/ios/RNFBAppUnitTests/HostStubs/React/RCTInvalidating.h +24 -0
  153. package/ios/RNFBAppUnitTests/HostStubs/React/RCTUtils.h +9 -0
  154. package/ios/RNFBAppUnitTests/HostStubs/module.modulemap +13 -0
  155. package/ios/RNFBAppUnitTests/RCTConvertFIRAppFacadeTests.m +61 -0
  156. package/ios/RNFBAppUnitTests/RCTConvertFIRAppTests.swift +73 -0
  157. package/ios/RNFBAppUnitTests/RCTConvertFIROptionsFacadeTests.m +57 -0
  158. package/ios/RNFBAppUnitTests/RCTConvertFIROptionsTests.swift +243 -0
  159. package/ios/RNFBAppUnitTests/RNFBAppCustomAuthDomainsFacadeTests.m +73 -0
  160. package/ios/RNFBAppUnitTests/RNFBAppCustomAuthDomainsTests.swift +70 -0
  161. package/ios/RNFBAppUnitTests/RNFBAppInitializeOptionsMapperTests.swift +190 -0
  162. package/ios/RNFBAppUnitTests/RNFBAppLogLevelFacadeTests.m +76 -0
  163. package/ios/RNFBAppUnitTests/RNFBAppLogLevelMapperTests.swift +59 -0
  164. package/ios/RNFBAppUnitTests/RNFBAppModuleFirebaseFacadeTests.m +215 -0
  165. package/ios/RNFBAppUnitTests/RNFBAppModuleFirebaseTests.swift +430 -0
  166. package/ios/RNFBAppUnitTests/RNFBAppModuleLifecycleTests.m +206 -0
  167. package/ios/RNFBAppUnitTests/RNFBAppModuleShellTests.mm +367 -0
  168. package/ios/RNFBAppUnitTests/RNFBAppUnitTests.xcodeproj/project.pbxproj +355 -2
  169. package/ios/RNFBAppUnitTests/RNFBFirebaseAppClientTests.swift +165 -0
  170. package/ios/RNFBAppUnitTests/RNFBHandleMapStorageTests.swift +121 -0
  171. package/ios/RNFBAppUnitTests/RNFBInitializeAppArgumentsTests.swift +256 -0
  172. package/ios/RNFBAppUnitTests/RNFBJSONImplementationTests.swift +126 -0
  173. package/ios/RNFBAppUnitTests/RNFBJSONTests.m +172 -0
  174. package/ios/RNFBAppUnitTests/RNFBMetaStorageTests.swift +106 -0
  175. package/ios/RNFBAppUnitTests/RNFBMetaTests.m +65 -0
  176. package/ios/RNFBAppUnitTests/RNFBNullSentinelDecodingTests.swift +286 -0
  177. package/ios/RNFBAppUnitTests/RNFBNullSentinelInterceptorTests.m +312 -0
  178. package/ios/RNFBAppUnitTests/RNFBPreferencesStorageTests.swift +105 -0
  179. package/ios/RNFBAppUnitTests/RNFBPreferencesTests.m +110 -0
  180. package/ios/RNFBAppUnitTests/RNFBRCTEventEmitterCoreTests.swift +163 -0
  181. package/ios/RNFBAppUnitTests/RNFBRCTEventEmitterFacadeTests.m +141 -0
  182. package/ios/RNFBAppUnitTests/RNFBSharedUtilsConfigFacadeTests.m +100 -0
  183. package/ios/RNFBAppUnitTests/RNFBSharedUtilsConfigTests.swift +234 -0
  184. package/ios/RNFBAppUnitTests/RNFBSharedUtilsFIRAppFacadeTests.m +145 -0
  185. package/ios/RNFBAppUnitTests/RNFBSharedUtilsFIRAppTests.swift +173 -0
  186. package/ios/RNFBAppUnitTests/RNFBSharedUtilsFormattingFacadeTests.m +38 -0
  187. package/ios/RNFBAppUnitTests/RNFBSharedUtilsFormattingTests.swift +82 -0
  188. package/ios/RNFBAppUnitTests/RNFBSharedUtilsNullSentinelTests.m +35 -0
  189. package/ios/RNFBAppUnitTests/RNFBSharedUtilsPromiseRejectionFacadeTests.m +107 -0
  190. package/ios/RNFBAppUnitTests/RNFBSharedUtilsPromiseRejectionTests.swift +133 -0
  191. package/ios/RNFBAppUnitTests/RNFBUtilsHelpersTests.swift +144 -0
  192. package/ios/RNFBAppUnitTests/RNFBUtilsModuleFacadeTests.m +72 -0
  193. package/ios/RNFBAppUnitTests/RNFBUtilsModuleShellTests.mm +282 -0
  194. package/ios/RNFBFirebase/Package.swift +53 -0
  195. package/ios/RNFBFirebase/Sources/RNFBFirebase/RNFBFirebase.swift +285 -0
  196. package/ios/RNFBFirebase/Sources/RNFBFirebaseBridge/Public/.gitkeep +0 -0
  197. package/ios/RNFBFirebase/Sources/RNFBFirebaseBridge/RNFBFirebaseBridge.m +169 -0
  198. package/ios/RNFBFirebase/Sources/RNFBFirebaseBridge/include/RNFBFirebaseBridge.h +52 -0
  199. package/ios/RNFBFirebaseUnitTests/RNFBAppDynamicFacadeTests.swift +240 -0
  200. package/{android/src/main/java/io/invertase/firebase/common/RNFBHandleCollisionException.java → ios/RNFBFirebaseUnitTests/RNFBFirebaseClientImport.swift} +23 -14
  201. package/ios/RNFBFirebaseUnitTests/RNFBFirebaseUnitTests.xcodeproj/project.pbxproj +588 -0
  202. package/ios/RNFBFirebaseUnitTests/RNFBFirebaseUnitTests.xcodeproj/xcshareddata/xcschemes/RNFBFirebaseUnitTests.xcscheme +83 -0
  203. package/ios_config.sh +50 -22
  204. package/lib/FirebaseApp.ts +10 -0
  205. package/lib/common/deeps.ts +3 -0
  206. package/lib/internal/FirebaseModule.ts +6 -3
  207. package/lib/internal/NativeModules.ts +4 -1
  208. package/lib/internal/RNFBNativeEventEmitter.ts +14 -5
  209. package/lib/internal/SharedEventEmitter.ts +5 -1
  210. package/lib/internal/index.ts +1 -0
  211. package/lib/internal/react-native-vendor-emitter.d.ts +11 -0
  212. package/lib/internal/registry/app.ts +7 -1
  213. package/lib/types/app.ts +10 -3
  214. package/lib/types/internal.ts +19 -0
  215. package/lib/version.ts +1 -1
  216. package/package.json +8 -8
  217. package/plugin/build/ios/appDelegate.js +2 -2
  218. package/plugin/build/ios/podfile.js +16 -16
  219. package/plugin/build/pluginConfig.d.ts +4 -2
  220. package/plugin/src/ios/appDelegate.ts +2 -2
  221. package/plugin/src/ios/podfile.ts +20 -23
  222. package/plugin/src/pluginConfig.ts +4 -2
  223. package/android/src/main/java/io/invertase/firebase/common/RNFBHandleMap.java +0 -150
  224. package/android/src/main/java/io/invertase/firebase/common/TaskExecutorService.java +0 -148
  225. package/android/src/main/java/io/invertase/firebase/common/UniversalFirebaseModule.java +0 -62
  226. package/android/src/main/java/io/invertase/firebase/common/UniversalFirebasePreferences.java +0 -91
  227. package/android/src/reactnative/java/io/invertase/firebase/app/NativeRNFBTurboApp.java +0 -224
  228. package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseApp.java +0 -46
  229. package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseAppPackage.java +0 -49
  230. package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseAppRegistrar.java +0 -35
  231. package/android/src/reactnative/java/io/invertase/firebase/common/RCTConvertFirebase.java +0 -192
  232. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseEvent.java +0 -53
  233. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseEventEmitter.java +0 -606
  234. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseJSON.java +0 -109
  235. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseMeta.java +0 -101
  236. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseModule.java +0 -146
  237. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebasePreferences.java +0 -96
  238. package/android/src/reactnative/java/io/invertase/firebase/common/SharedUtils.java +0 -441
  239. package/android/src/reactnative/java/io/invertase/firebase/utils/NativeRNFBTurboUtils.java +0 -223
  240. package/android/src/test/java/io/invertase/firebase/common/RNFBHandleMapTest.java +0 -275
  241. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseJSONTest.java +0 -108
  242. package/android/src/test/java/io/invertase/firebase/common/TaskExecutorServiceTest.java +0 -97
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Copyright (c) 2016-present Invertase Limited & Contributors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this library except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ #import <XCTest/XCTest.h>
18
+
19
+ #import "RNFBHandleMapStorage-Swift.inc"
20
+ #import "RNFBJSON.h"
21
+
22
+ @interface RNFBJSONTests : XCTestCase
23
+ @end
24
+
25
+ @implementation RNFBJSONTests
26
+
27
+ - (RNFBJSON *)jsonWithObject:(NSObject *)object {
28
+ RNFBJSON *json = [[RNFBJSON alloc] init];
29
+ RNFBJSONImplementation *implementation =
30
+ [[RNFBJSONImplementation alloc] initWithJSONObject:object];
31
+ [json setValue:implementation forKey:@"implementation"];
32
+ return json;
33
+ }
34
+
35
+ - (void)testSharedUsesStableDispatchOnceIdentity {
36
+ RNFBJSON *first = RNFBJSON.shared;
37
+ XCTAssertNotNil(first);
38
+ XCTAssertIdentical(first, RNFBJSON.shared);
39
+ }
40
+
41
+ - (void)testPublicSelectorsAndReturnEncodingsAreUnchanged {
42
+ NSArray<NSString *> *selectorNames = @[
43
+ @"contains:",
44
+ @"getBooleanValue:defaultValue:",
45
+ @"getStringValue:defaultValue:",
46
+ @"getArrayValue:defaultValue:",
47
+ @"getAll",
48
+ @"getRawJSON",
49
+ ];
50
+ for (NSString *selectorName in selectorNames) {
51
+ XCTAssertTrue([[RNFBJSON shared] respondsToSelector:NSSelectorFromString(selectorName)]);
52
+ }
53
+ XCTAssertTrue([RNFBJSON respondsToSelector:@selector(shared)]);
54
+
55
+ NSMethodSignature *contains = [RNFBJSON instanceMethodSignatureForSelector:@selector(contains:)];
56
+ XCTAssertEqual(strcmp(contains.methodReturnType, @encode(BOOL)), 0);
57
+ NSMethodSignature *boolean =
58
+ [RNFBJSON instanceMethodSignatureForSelector:@selector(getBooleanValue:defaultValue:)];
59
+ XCTAssertEqual(strcmp(boolean.methodReturnType, @encode(BOOL)), 0);
60
+ XCTAssertEqual(strcmp([boolean getArgumentTypeAtIndex:3], @encode(BOOL)), 0);
61
+
62
+ NSArray<NSString *> *objectReturnSelectors = @[
63
+ @"getStringValue:defaultValue:", @"getArrayValue:defaultValue:", @"getAll", @"getRawJSON"
64
+ ];
65
+ for (NSString *selectorName in objectReturnSelectors) {
66
+ NSMethodSignature *signature =
67
+ [RNFBJSON instanceMethodSignatureForSelector:NSSelectorFromString(selectorName)];
68
+ XCTAssertEqual(strcmp(signature.methodReturnType, @encode(id)), 0);
69
+ }
70
+ NSMethodSignature *shared = [RNFBJSON methodSignatureForSelector:@selector(shared)];
71
+ XCTAssertEqual(strcmp(shared.methodReturnType, @encode(id)), 0);
72
+ }
73
+
74
+ - (void)testFacadePreservesKVCContainsDefaultsAndUncheckedObjectReturns {
75
+ NSDictionary *dictionary = @{
76
+ @"boolean" : @YES,
77
+ @"booleanFalse" : @NO,
78
+ @"numericZero" : @0,
79
+ @"booleanString" : @"YES",
80
+ @"string" : @"value",
81
+ @"array" : @[ @"one" ],
82
+ @"numberAsString" : @42,
83
+ @"stringAsArray" : @"unchecked",
84
+ @"null" : NSNull.null,
85
+ @"count" : @"count value",
86
+ @"@count" : @"at-count value",
87
+ };
88
+ RNFBJSON *json = [self jsonWithObject:dictionary];
89
+
90
+ XCTAssertTrue([json contains:@"boolean"]);
91
+ XCTAssertTrue([json contains:@"booleanFalse"]);
92
+ XCTAssertTrue([json contains:@"numericZero"]);
93
+ XCTAssertTrue([json contains:@"null"]);
94
+ XCTAssertFalse([json contains:@"missing"]);
95
+ XCTAssertTrue([json getBooleanValue:@"boolean" defaultValue:NO]);
96
+ XCTAssertFalse([json getBooleanValue:@"booleanFalse" defaultValue:YES]);
97
+ XCTAssertFalse([json getBooleanValue:@"numericZero" defaultValue:YES]);
98
+ XCTAssertTrue([json getBooleanValue:@"booleanString" defaultValue:NO]);
99
+ XCTAssertTrue([json getBooleanValue:@"missing" defaultValue:YES]);
100
+ XCTAssertEqualObjects([json getStringValue:@"missing" defaultValue:@"fallback"], @"fallback");
101
+ XCTAssertEqualObjects([json getArrayValue:@"missing" defaultValue:@[ @"fallback" ]],
102
+ (@[ @"fallback" ]));
103
+ XCTAssertEqualObjects([json getStringValue:@"string" defaultValue:@"fallback"], @"value");
104
+ XCTAssertEqualObjects([json getArrayValue:@"array" defaultValue:@[]], (@[ @"one" ]));
105
+
106
+ id uncheckedString = [json getStringValue:@"numberAsString" defaultValue:@"fallback"];
107
+ id uncheckedArray = [json getArrayValue:@"stringAsArray" defaultValue:@[]];
108
+ XCTAssertEqualObjects(uncheckedString, @42);
109
+ XCTAssertEqualObjects(uncheckedArray, @"unchecked");
110
+ XCTAssertIdentical([json getStringValue:@"null" defaultValue:@"fallback"], NSNull.null);
111
+
112
+ for (NSString *key in @[ @"count", @"@count" ]) {
113
+ XCTAssertEqualObjects([json getStringValue:key defaultValue:@"fallback"],
114
+ [dictionary valueForKey:key]);
115
+ XCTAssertEqual([json contains:key], [dictionary valueForKey:key] != nil);
116
+ }
117
+ }
118
+
119
+ - (void)testGetBooleanValueThrowsForNSNullArrayAndDictionaryReceivers {
120
+ NSDictionary *dictionary = @{
121
+ @"null" : NSNull.null,
122
+ @"array" : @[ @"one" ],
123
+ @"object" : @{@"nested" : @"value"},
124
+ };
125
+ RNFBJSON *json = [self jsonWithObject:dictionary];
126
+
127
+ XCTAssertThrowsSpecificNamed([json getBooleanValue:@"null" defaultValue:YES], NSException,
128
+ NSInvalidArgumentException);
129
+ XCTAssertThrowsSpecificNamed([json getBooleanValue:@"array" defaultValue:YES], NSException,
130
+ NSInvalidArgumentException);
131
+ XCTAssertThrowsSpecificNamed([json getBooleanValue:@"object" defaultValue:YES], NSException,
132
+ NSInvalidArgumentException);
133
+
134
+ @try {
135
+ [json getBooleanValue:@"null" defaultValue:YES];
136
+ XCTFail(@"expected NSNull boolValue to throw");
137
+ } @catch (NSException *exception) {
138
+ XCTAssertEqualObjects(exception.name, NSInvalidArgumentException);
139
+ XCTAssertTrue([exception.reason containsString:@"boolValue"]);
140
+ }
141
+ }
142
+
143
+ - (void)testGetAllCopiesItemsAndReturnsIndependentImmutableSnapshot {
144
+ NSMutableString *mutableString = [NSMutableString stringWithString:@"before"];
145
+ NSMutableArray *mutableArray = [NSMutableArray arrayWithObject:@"first"];
146
+ NSDictionary *source = @{@"string" : mutableString, @"array" : mutableArray};
147
+ RNFBJSON *json = [self jsonWithObject:source];
148
+
149
+ NSDictionary *snapshot = [json getAll];
150
+
151
+ XCTAssertNotIdentical(snapshot, source);
152
+ XCTAssertNotIdentical(snapshot[@"string"], mutableString);
153
+ XCTAssertNotIdentical(snapshot[@"array"], mutableArray);
154
+ [mutableString appendString:@"-after"];
155
+ [mutableArray addObject:@"second"];
156
+ XCTAssertEqualObjects(snapshot[@"string"], @"before");
157
+ XCTAssertEqualObjects(snapshot[@"array"], (@[ @"first" ]));
158
+ }
159
+
160
+ - (void)testGetAllPreservesNonDictionaryExceptionBehavior {
161
+ RNFBJSON *json = [self jsonWithObject:@[ @"valid", @"JSON", @"array" ]];
162
+
163
+ XCTAssertThrowsSpecificNamed([json getAll], NSException, NSInvalidArgumentException);
164
+ }
165
+
166
+ - (void)testRawFacadeUsesCurrentBundleValueAndSwiftDecoder {
167
+ NSString *raw = [[NSBundle mainBundle].infoDictionary valueForKey:@"firebase_json_raw"];
168
+ XCTAssertEqualObjects([RNFBJSON.shared getRawJSON],
169
+ [RNFBJSONImplementation rawJSONFromRawValue:raw]);
170
+ }
171
+
172
+ @end
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Copyright (c) 2016-present Invertase Limited & Contributors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this library except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import Foundation
18
+ import XCTest
19
+
20
+ final class RNFBMetaStorageTests: XCTestCase {
21
+ private func storage(_ plist: [String: Any]) -> RNFBMetaStorage {
22
+ RNFBMetaStorage(infoDictionary: plist)
23
+ }
24
+
25
+ func testMissingKeysUseDefaultsAndAreNotContained() {
26
+ let meta = storage([:])
27
+
28
+ XCTAssertFalse(meta.contains("missing"))
29
+ XCTAssertTrue(meta.getBooleanValue("missing", defaultValue: true))
30
+ XCTAssertFalse(meta.getBooleanValue("missing", defaultValue: false))
31
+ XCTAssertEqual(meta.getStringValue("missing", defaultValue: "fallback"), "fallback")
32
+ XCTAssertEqual(meta.getAll() as NSDictionary, NSDictionary())
33
+ }
34
+
35
+ func testPrefixIsAppliedAndUnprefixedKeysAreIgnored() {
36
+ let meta = storage([
37
+ "rnfirebase_present": "value",
38
+ "other_key": "ignored",
39
+ "rnfirebase": "not-prefixed-enough",
40
+ ])
41
+
42
+ XCTAssertTrue(meta.contains("present"))
43
+ XCTAssertFalse(meta.contains("other_key"))
44
+ XCTAssertEqual(meta.getStringValue("present", defaultValue: "fallback"), "value")
45
+ XCTAssertEqual(meta.getStringValue("other_key", defaultValue: "fallback"), "fallback")
46
+
47
+ let all = meta.getAll()
48
+ XCTAssertEqual(all as NSDictionary, ["rnfirebase_present": "value"] as NSDictionary)
49
+ XCTAssertNil(all["other_key"])
50
+ XCTAssertNil(all["rnfirebase"])
51
+ }
52
+
53
+ func testGetAllReturnsPrefixedKeysUnstripped() {
54
+ let meta = storage([
55
+ "rnfirebase_string": "abc",
56
+ "rnfirebase_boolean_true": true,
57
+ "rnfirebase_boolean_false": false,
58
+ "unrelated": 1,
59
+ ])
60
+
61
+ let all = meta.getAll()
62
+ XCTAssertEqual(Set(all.keys), Set([
63
+ "rnfirebase_string",
64
+ "rnfirebase_boolean_true",
65
+ "rnfirebase_boolean_false",
66
+ ]))
67
+ XCTAssertEqual(all["rnfirebase_string"] as? String, "abc")
68
+ XCTAssertEqual(all["rnfirebase_boolean_true"] as? Bool, true)
69
+ XCTAssertEqual(all["rnfirebase_boolean_false"] as? Bool, false)
70
+ }
71
+
72
+ func testPresentBooleanFalseAndNumericZeroOverrideDefaultYes() {
73
+ let meta = storage([
74
+ "rnfirebase_boolFalse": false,
75
+ "rnfirebase_numericZero": 0,
76
+ "rnfirebase_boolTrue": true,
77
+ "rnfirebase_numericOne": 1,
78
+ "rnfirebase_boolStringYes": "YES",
79
+ "rnfirebase_boolStringNo": "0",
80
+ ])
81
+
82
+ XCTAssertTrue(meta.contains("boolFalse"))
83
+ XCTAssertTrue(meta.contains("numericZero"))
84
+ XCTAssertFalse(meta.getBooleanValue("boolFalse", defaultValue: true))
85
+ XCTAssertFalse(meta.getBooleanValue("numericZero", defaultValue: true))
86
+ XCTAssertTrue(meta.getBooleanValue("boolTrue", defaultValue: false))
87
+ XCTAssertTrue(meta.getBooleanValue("numericOne", defaultValue: false))
88
+ XCTAssertTrue(meta.getBooleanValue("boolStringYes", defaultValue: false))
89
+ XCTAssertFalse(meta.getBooleanValue("boolStringNo", defaultValue: true))
90
+ }
91
+
92
+ func testStringLookupPreservesPresentValues() {
93
+ let meta = storage([
94
+ "rnfirebase_string": "abc",
95
+ ])
96
+
97
+ XCTAssertEqual(meta.getStringValue("string", defaultValue: "fallback"), "abc")
98
+ }
99
+
100
+ func testDefaultInitializerUsesMainBundleInfoDictionary() {
101
+ let production = RNFBMetaStorage()
102
+ let fromBundle = RNFBMetaStorage(infoDictionary: Bundle.main.infoDictionary ?? [:])
103
+
104
+ XCTAssertEqual(production.getAll() as NSDictionary, fromBundle.getAll() as NSDictionary)
105
+ }
106
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Copyright (c) 2016-present Invertase Limited & Contributors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this library except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ #import <XCTest/XCTest.h>
18
+
19
+ #import "RNFBHandleMapStorage-Swift.inc"
20
+ #import "RNFBMeta.h"
21
+
22
+ @interface RNFBMetaTests : XCTestCase
23
+ @end
24
+
25
+ @implementation RNFBMetaTests
26
+
27
+ - (void)testPublicClassSelectorsAndReturnEncodingsAreUnchanged {
28
+ NSArray<NSString *> *selectorNames = @[
29
+ @"contains:",
30
+ @"getBooleanValue:defaultValue:",
31
+ @"getStringValue:defaultValue:",
32
+ @"getAll",
33
+ ];
34
+ for (NSString *selectorName in selectorNames) {
35
+ XCTAssertTrue([RNFBMeta respondsToSelector:NSSelectorFromString(selectorName)]);
36
+ }
37
+
38
+ NSMethodSignature *contains = [RNFBMeta methodSignatureForSelector:@selector(contains:)];
39
+ XCTAssertEqual(strcmp(contains.methodReturnType, @encode(BOOL)), 0);
40
+
41
+ NSMethodSignature *boolean = [RNFBMeta methodSignatureForSelector:@selector(getBooleanValue:
42
+ defaultValue:)];
43
+ XCTAssertEqual(strcmp(boolean.methodReturnType, @encode(BOOL)), 0);
44
+ XCTAssertEqual(strcmp([boolean getArgumentTypeAtIndex:3], @encode(BOOL)), 0);
45
+
46
+ NSMethodSignature *string = [RNFBMeta methodSignatureForSelector:@selector(getStringValue:
47
+ defaultValue:)];
48
+ XCTAssertEqual(strcmp(string.methodReturnType, @encode(id)), 0);
49
+
50
+ NSMethodSignature *getAll = [RNFBMeta methodSignatureForSelector:@selector(getAll)];
51
+ XCTAssertEqual(strcmp(getAll.methodReturnType, @encode(id)), 0);
52
+ }
53
+
54
+ - (void)testFacadeForwardsToSharedMainBundleStorage {
55
+ // Smoke: class methods are callable and getAll returns a dictionary (may be empty).
56
+ XCTAssertNotNil([RNFBMeta getAll]);
57
+ XCTAssertTrue([[RNFBMeta getAll] isKindOfClass:[NSDictionary class]]);
58
+ XCTAssertFalse([RNFBMeta contains:@"__rnfb_meta_unit_test_missing_key__"]);
59
+ XCTAssertTrue([RNFBMeta getBooleanValue:@"__rnfb_meta_unit_test_missing_key__" defaultValue:YES]);
60
+ XCTAssertEqualObjects([RNFBMeta getStringValue:@"__rnfb_meta_unit_test_missing_key__"
61
+ defaultValue:@"fallback"],
62
+ @"fallback");
63
+ }
64
+
65
+ @end
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Copyright (c) 2016-present Invertase Limited & Contributors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this library except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import Foundation
18
+ import XCTest
19
+
20
+ final class RNFBNullSentinelDecodingTests: XCTestCase {
21
+ private func decode(_ value: Any?) -> Any? {
22
+ RNFBNullSentinelDecoder.decodeNullSentinels(value)
23
+ }
24
+
25
+ private func sentinel(_ flag: Any = true) -> NSDictionary {
26
+ ["__rnfbNull": flag] as NSDictionary
27
+ }
28
+
29
+ private func serverTimestamp() -> NSDictionary {
30
+ [".sv": "timestamp"] as NSDictionary
31
+ }
32
+
33
+ func testNilAndNonContainersPreserveIdentity() {
34
+ XCTAssertNil(decode(nil))
35
+
36
+ let number = NSNumber(value: 42)
37
+ let string = "hello" as NSString
38
+ let date = NSDate()
39
+ let data = NSData()
40
+ let null = NSNull()
41
+
42
+ XCTAssertTrue(decode(number) as AnyObject === number)
43
+ XCTAssertTrue(decode(string) as AnyObject === string)
44
+ XCTAssertTrue(decode(date) as AnyObject === date)
45
+ XCTAssertTrue(decode(data) as AnyObject === data)
46
+ XCTAssertTrue(decode(null) as AnyObject === null)
47
+ }
48
+
49
+ func testDecodeAndDecodeNullSentinelsSelectorsAreEquivalent() {
50
+ let input: NSDictionary = ["a": sentinel(), "b": "keep"]
51
+ let viaCanonical = RNFBNullSentinelDecoder.decodeNullSentinels(input) as! NSDictionary
52
+ let viaAlias = RNFBNullSentinelDecoder.decode(input) as! NSDictionary
53
+ XCTAssertEqual(viaCanonical, viaAlias)
54
+ XCTAssertTrue(viaCanonical["a"] is NSNull)
55
+ XCTAssertEqual(viaCanonical["b"] as? String, "keep")
56
+ }
57
+
58
+ func testRootSentinelBecomesNSNull() {
59
+ XCTAssertTrue(decode(sentinel()) is NSNull)
60
+ XCTAssertTrue(decode(sentinel(NSNumber(value: 1))) is NSNull)
61
+ }
62
+
63
+ func testRootSentinelRequiresExactlyOneKeyAndTruthyFlag() {
64
+ XCTAssertFalse(decode(sentinel(false)) is NSNull)
65
+ XCTAssertEqual(decode(sentinel(false)) as? NSDictionary, ["__rnfbNull": false] as NSDictionary)
66
+
67
+ let extraKey = ["__rnfbNull": true, "other": 1] as NSDictionary
68
+ XCTAssertEqual(decode(extraKey) as? NSDictionary, extraKey)
69
+
70
+ let empty = decode(NSDictionary()) as! NSDictionary
71
+ XCTAssertEqual(empty.count, 0)
72
+ XCTAssertTrue(empty is NSMutableDictionary)
73
+ }
74
+
75
+ func testStringFlagIsNotASentinel() {
76
+ for flag in ["true", "YES", "yes", "1"] as [NSString] {
77
+ let input = sentinel(flag)
78
+ XCTAssertEqual(decode(input) as? NSDictionary, input)
79
+ }
80
+ }
81
+
82
+ func testNonBoolRespondingFlagIsNotASentinel() {
83
+ let exotic = sentinel(NSDate())
84
+ let decoded = decode(exotic) as! NSDictionary
85
+ XCTAssertEqual(decoded.count, 1)
86
+ XCTAssertTrue(decoded["__rnfbNull"] is NSDate)
87
+ }
88
+
89
+ func testRootSentinelFlagNonNumberUnchangedWithoutCrash() {
90
+ let nullFlag = ["__rnfbNull": NSNull()] as NSDictionary
91
+ let dictFlag = ["__rnfbNull": ["a": 1] as NSDictionary] as NSDictionary
92
+ let arrayFlag = ["__rnfbNull": [1] as NSArray] as NSDictionary
93
+
94
+ XCTAssertEqual(decode(nullFlag) as? NSDictionary, nullFlag)
95
+ XCTAssertEqual(decode(dictFlag) as? NSDictionary, dictFlag)
96
+ XCTAssertEqual(decode(arrayFlag) as? NSDictionary, arrayFlag)
97
+ }
98
+
99
+ func testRootServerValueUnchanged() {
100
+ let input = serverTimestamp()
101
+ XCTAssertEqual(decode(input) as? NSDictionary, input)
102
+ }
103
+
104
+ func testNestedDictionarySentinelsBecomeNSNull() {
105
+ let input: NSDictionary = [
106
+ "a": sentinel(),
107
+ "b": "keep",
108
+ "nested": [
109
+ "inner": sentinel(),
110
+ "n": 1,
111
+ "falseFlag": sentinel(false),
112
+ "empty": NSDictionary(),
113
+ ] as NSDictionary,
114
+ ]
115
+
116
+ let result = decode(input) as! NSDictionary
117
+ XCTAssertTrue(result["a"] is NSNull)
118
+ XCTAssertEqual(result["b"] as? String, "keep")
119
+ let nested = result["nested"] as! NSDictionary
120
+ XCTAssertTrue(nested["inner"] is NSNull)
121
+ XCTAssertEqual(nested["n"] as? Int, 1)
122
+ XCTAssertEqual(nested["falseFlag"] as? NSDictionary, ["__rnfbNull": false] as NSDictionary)
123
+ XCTAssertEqual((nested["empty"] as? NSDictionary)?.count, 0)
124
+ }
125
+
126
+ func testNestedSentinelWithExtraKeysUnchanged() {
127
+ let input: NSDictionary = ["child": ["__rnfbNull": true, "other": "x"] as NSDictionary]
128
+ XCTAssertEqual(decode(input) as? NSDictionary, input)
129
+ }
130
+
131
+ func testNestedSentinelFlagNonNumberUnchanged() {
132
+ let input: NSDictionary = [
133
+ "nullFlag": ["__rnfbNull": NSNull()] as NSDictionary,
134
+ "dictFlag": ["__rnfbNull": ["a": 1] as NSDictionary] as NSDictionary,
135
+ "arrayFlag": ["__rnfbNull": [1] as NSArray] as NSDictionary,
136
+ "stringFlag": ["__rnfbNull": "true"] as NSDictionary,
137
+ ]
138
+ XCTAssertEqual(decode(input) as? NSDictionary, input)
139
+
140
+ let arrayInput: NSArray = [
141
+ ["__rnfbNull": NSNull()] as NSDictionary,
142
+ ["__rnfbNull": ["a": 1] as NSDictionary] as NSDictionary,
143
+ ["__rnfbNull": [1] as NSArray] as NSDictionary,
144
+ ["__rnfbNull": "true"] as NSDictionary,
145
+ ]
146
+ XCTAssertEqual(decode(arrayInput) as? NSArray, arrayInput)
147
+ }
148
+
149
+ func testValidSentinelSiblingDecodedWhileNonNumberFlagPassesThrough() {
150
+ let input: NSDictionary = [
151
+ "valid": sentinel(),
152
+ "guarded": ["__rnfbNull": NSNull()] as NSDictionary,
153
+ "list": [sentinel(), ["__rnfbNull": [1] as NSArray] as NSDictionary] as NSArray,
154
+ ]
155
+ let result = decode(input) as! NSDictionary
156
+ XCTAssertTrue(result["valid"] is NSNull)
157
+ XCTAssertEqual(result["guarded"] as? NSDictionary, ["__rnfbNull": NSNull()] as NSDictionary)
158
+ let list = result["list"] as! NSArray
159
+ XCTAssertTrue(list[0] is NSNull)
160
+ XCTAssertEqual(list[1] as? NSDictionary, ["__rnfbNull": [1] as NSArray] as NSDictionary)
161
+ }
162
+
163
+ func testArrayWalkPreservesNSNullAndReplacesSentinels() {
164
+ let input: NSArray = [
165
+ sentinel(),
166
+ NSNull(),
167
+ "x",
168
+ 3,
169
+ serverTimestamp(),
170
+ [sentinel(), 2] as NSArray,
171
+ ]
172
+
173
+ let result = decode(input) as! NSArray
174
+ XCTAssertEqual(result.count, 6)
175
+ XCTAssertTrue(result[0] is NSNull)
176
+ XCTAssertTrue(result[1] is NSNull)
177
+ XCTAssertEqual(result[2] as? String, "x")
178
+ XCTAssertEqual(result[3] as? Int, 3)
179
+ XCTAssertEqual(result[4] as? NSDictionary, serverTimestamp())
180
+ let nested = result[5] as! NSArray
181
+ XCTAssertTrue(nested[0] is NSNull)
182
+ XCTAssertEqual(nested[1] as? Int, 2)
183
+ XCTAssertTrue(result is NSMutableArray)
184
+ }
185
+
186
+ func testRootEmptyArrayReturnsEmptyMutableArray() {
187
+ let decoded = decode([] as NSArray) as! NSArray
188
+ XCTAssertEqual(decoded.count, 0)
189
+ XCTAssertTrue(decoded is NSMutableArray)
190
+ }
191
+
192
+ func testNestedArraysSentinelsDecodedAtEveryLevel() {
193
+ let input: NSArray = [[sentinel(), [sentinel(), "leaf"] as NSArray] as NSArray, [] as NSArray]
194
+ let result = decode(input) as! NSArray
195
+ let first = result[0] as! NSArray
196
+ XCTAssertTrue(first[0] is NSNull)
197
+ XCTAssertTrue((first[1] as! NSArray)[0] is NSNull)
198
+ XCTAssertEqual((first[1] as! NSArray)[1] as? String, "leaf")
199
+ XCTAssertEqual((result[1] as! NSArray).count, 0)
200
+ }
201
+
202
+ func testArrayInsideDictionaryAndMixedMatrixDecoded() {
203
+ let input: NSDictionary = [
204
+ "list": [sentinel(), "x", [sentinel()] as NSArray] as NSArray,
205
+ "items": [
206
+ ["id": 1, "note": sentinel()] as NSDictionary,
207
+ ["id": 2, "tags": [sentinel()] as NSArray] as NSDictionary,
208
+ ] as NSArray,
209
+ ]
210
+ let result = decode(input) as! NSDictionary
211
+ let list = result["list"] as! NSArray
212
+ XCTAssertTrue(list[0] is NSNull)
213
+ XCTAssertEqual(list[1] as? String, "x")
214
+ XCTAssertTrue((list[2] as! NSArray)[0] is NSNull)
215
+
216
+ let items = result["items"] as! NSArray
217
+ XCTAssertTrue((items[0] as! NSDictionary)["note"] is NSNull)
218
+ XCTAssertTrue(((items[1] as! NSDictionary)["tags"] as! NSArray)[0] is NSNull)
219
+ }
220
+
221
+ func testChildAndParentTypeMatrix() {
222
+ let dictInput: NSDictionary = [
223
+ "dictChild": ["k": 1] as NSDictionary,
224
+ "arrayChild": ["leaf", [2] as NSArray, sentinel(), NSNull()] as NSArray,
225
+ "primitiveChild": "text",
226
+ "nullChild": NSNull(),
227
+ ]
228
+ let arrayRoot: NSArray = [
229
+ ["inner": sentinel()] as NSDictionary,
230
+ ["nested"] as NSArray,
231
+ 3,
232
+ sentinel(),
233
+ NSNull(),
234
+ ]
235
+
236
+ let decodedDict = decode(dictInput) as! NSDictionary
237
+ XCTAssertEqual(decodedDict["dictChild"] as? NSDictionary, ["k": 1] as NSDictionary)
238
+ let arrayChild = decodedDict["arrayChild"] as! NSArray
239
+ XCTAssertEqual(arrayChild[0] as? String, "leaf")
240
+ XCTAssertTrue(arrayChild[2] is NSNull)
241
+ XCTAssertTrue(decodedDict["nullChild"] is NSNull)
242
+
243
+ let decodedArray = decode(arrayRoot) as! NSArray
244
+ XCTAssertTrue((decodedArray[0] as! NSDictionary)["inner"] is NSNull)
245
+ XCTAssertTrue(decodedArray[3] is NSNull)
246
+ XCTAssertTrue(decodedArray[4] is NSNull)
247
+ }
248
+
249
+ func testContainersReturnNewMutableCopiesEvenWithoutSentinels() {
250
+ let dict = ["k": "v"] as NSDictionary
251
+ let array = ["a", 1] as NSArray
252
+
253
+ let decodedDict = decode(dict)
254
+ let decodedArray = decode(array)
255
+
256
+ XCTAssertFalse(decodedDict as AnyObject === dict)
257
+ XCTAssertFalse(decodedArray as AnyObject === array)
258
+ XCTAssertTrue(decodedDict is NSMutableDictionary)
259
+ XCTAssertTrue(decodedArray is NSMutableArray)
260
+ XCTAssertEqual(decodedDict as? NSDictionary, dict)
261
+ XCTAssertEqual(decodedArray as? NSArray, array)
262
+ }
263
+
264
+ func testCountOneNumberFalseIsNotSentinel() {
265
+ let input: NSDictionary = [
266
+ "a": ["__rnfbNull": false] as NSDictionary,
267
+ "b": ["__rnfbNull": NSNumber(value: 0)] as NSDictionary,
268
+ ]
269
+ XCTAssertEqual(decode(input) as? NSDictionary, input)
270
+ }
271
+
272
+ func testDeepNestingDoesNotOverflow() {
273
+ let depth = 1000
274
+ var leaf: Any = ["value": sentinel()] as NSDictionary
275
+ for _ in 0..<depth {
276
+ leaf = ["child": leaf] as NSDictionary
277
+ }
278
+
279
+ var cursor = decode(leaf) as! NSDictionary
280
+ for _ in 0..<depth {
281
+ XCTAssertTrue(cursor is NSMutableDictionary)
282
+ cursor = cursor["child"] as! NSDictionary
283
+ }
284
+ XCTAssertTrue(cursor["value"] is NSNull)
285
+ }
286
+ }