@adobe/react-native-aepmessaging 7.2.1 → 7.4.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 (293) hide show
  1. package/RCTAEPMessaging.podspec +1 -1
  2. package/README.md +145 -16
  3. package/android/src/main/java/com/adobe/marketing/mobile/reactnative/messaging/RCTAEPMessagingConstants.java +3 -0
  4. package/android/src/main/java/com/adobe/marketing/mobile/reactnative/messaging/RCTAEPMessagingModule.java +103 -33
  5. package/babel.config.js +3 -0
  6. package/dist/module/Messaging.js +334 -0
  7. package/dist/module/Messaging.js.map +1 -0
  8. package/dist/module/index.js +30 -0
  9. package/dist/module/index.js.map +1 -0
  10. package/dist/module/models/ContentCard.js +24 -0
  11. package/dist/module/models/ContentCard.js.map +1 -0
  12. package/dist/{models → module/models}/HTMLProposition.js +8 -9
  13. package/dist/module/models/HTMLProposition.js.map +1 -0
  14. package/dist/module/models/InAppMessage.js +4 -0
  15. package/dist/module/models/InAppMessage.js.map +1 -0
  16. package/dist/module/models/JSONProposition.js +22 -0
  17. package/dist/module/models/JSONProposition.js.map +1 -0
  18. package/dist/module/models/Message.js +182 -0
  19. package/dist/module/models/Message.js.map +1 -0
  20. package/dist/module/models/MessagingDelegate.js +4 -0
  21. package/dist/module/models/MessagingDelegate.js.map +1 -0
  22. package/dist/module/models/MessagingEdgeEventType.js +24 -0
  23. package/dist/module/models/MessagingEdgeEventType.js.map +1 -0
  24. package/dist/module/models/MessagingProposition.js +57 -0
  25. package/dist/module/models/MessagingProposition.js.map +1 -0
  26. package/dist/module/models/MessagingPropositionItem.js +4 -0
  27. package/dist/module/models/MessagingPropositionItem.js.map +1 -0
  28. package/dist/module/models/PersonalizationSchema.js +26 -0
  29. package/dist/module/models/PersonalizationSchema.js.map +1 -0
  30. package/dist/module/models/PropositionItem.js +113 -0
  31. package/dist/module/models/PropositionItem.js.map +1 -0
  32. package/dist/module/models/ScopeDetails.js +2 -0
  33. package/dist/module/models/ScopeDetails.js.map +1 -0
  34. package/dist/{models/JSONProposition.js → module/models/index.js} +14 -12
  35. package/dist/module/models/index.js.map +1 -0
  36. package/dist/module/ui/components/Button/Button.js +57 -0
  37. package/dist/module/ui/components/Button/Button.js.map +1 -0
  38. package/dist/module/ui/components/Button/Button.spec.js +476 -0
  39. package/dist/module/ui/components/Button/Button.spec.js.map +1 -0
  40. package/dist/module/ui/components/ContentCardView/ContentCardView.js +257 -0
  41. package/dist/module/ui/components/ContentCardView/ContentCardView.js.map +1 -0
  42. package/dist/module/ui/components/ContentCardView/ContentCardView.spec.js +363 -0
  43. package/dist/module/ui/components/ContentCardView/ContentCardView.spec.js.map +1 -0
  44. package/dist/module/ui/components/DismissButton/DismissButton.js +70 -0
  45. package/dist/module/ui/components/DismissButton/DismissButton.js.map +1 -0
  46. package/dist/module/ui/components/DismissButton/DismissButton.spec.js +279 -0
  47. package/dist/module/ui/components/DismissButton/DismissButton.spec.js.map +1 -0
  48. package/dist/module/ui/components/FullScreenCenterView/FullScreenCenterView.js +34 -0
  49. package/dist/module/ui/components/FullScreenCenterView/FullScreenCenterView.js.map +1 -0
  50. package/dist/module/ui/components/Inbox/EmptyState.js +64 -0
  51. package/dist/module/ui/components/Inbox/EmptyState.js.map +1 -0
  52. package/dist/module/ui/components/Inbox/Inbox.js +235 -0
  53. package/dist/module/ui/components/Inbox/Inbox.js.map +1 -0
  54. package/dist/module/ui/components/Inbox/Inbox.spec.js +847 -0
  55. package/dist/module/ui/components/Inbox/Inbox.spec.js.map +1 -0
  56. package/dist/module/ui/components/Pagination/Pagination.js +176 -0
  57. package/dist/module/ui/components/Pagination/Pagination.js.map +1 -0
  58. package/dist/module/ui/components/Pagination/Pagination.spec.js +193 -0
  59. package/dist/module/ui/components/Pagination/Pagination.spec.js.map +1 -0
  60. package/dist/module/ui/components/UnreadIcon/UnreadIcon.js +184 -0
  61. package/dist/module/ui/components/UnreadIcon/UnreadIcon.js.map +1 -0
  62. package/dist/module/ui/components/UnreadIcon/UnreadIcon.spec.js +815 -0
  63. package/dist/module/ui/components/UnreadIcon/UnreadIcon.spec.js.map +1 -0
  64. package/dist/{models/ContentCard.js → module/ui/components/index.js} +12 -12
  65. package/dist/module/ui/components/index.js.map +1 -0
  66. package/dist/module/ui/hooks/index.js +18 -0
  67. package/dist/module/ui/hooks/index.js.map +1 -0
  68. package/dist/module/ui/hooks/useAspectRatio.js +33 -0
  69. package/dist/module/ui/hooks/useAspectRatio.js.map +1 -0
  70. package/dist/module/ui/hooks/useAspectRatio.spec.js +65 -0
  71. package/dist/module/ui/hooks/useAspectRatio.spec.js.map +1 -0
  72. package/dist/module/ui/hooks/useContentCardUI.js +51 -0
  73. package/dist/module/ui/hooks/useContentCardUI.js.map +1 -0
  74. package/dist/module/ui/hooks/useContentCardUI.spec.js +85 -0
  75. package/dist/module/ui/hooks/useContentCardUI.spec.js.map +1 -0
  76. package/dist/module/ui/hooks/useInbox.js +49 -0
  77. package/dist/module/ui/hooks/useInbox.js.map +1 -0
  78. package/dist/module/ui/hooks/useInbox.spec.js +93 -0
  79. package/dist/module/ui/hooks/useInbox.spec.js.map +1 -0
  80. package/dist/module/ui/hooks/useInboxSettings.js +26 -0
  81. package/dist/module/ui/hooks/useInboxSettings.js.map +1 -0
  82. package/dist/module/ui/hooks/useInboxSettings.spec.js +50 -0
  83. package/dist/module/ui/hooks/useInboxSettings.spec.js.map +1 -0
  84. package/dist/module/ui/index.js +10 -0
  85. package/dist/module/ui/index.js.map +1 -0
  86. package/dist/module/ui/providers/InboxProvider.js +27 -0
  87. package/dist/module/ui/providers/InboxProvider.js.map +1 -0
  88. package/dist/module/ui/theme/Theme.js +2 -0
  89. package/dist/module/ui/theme/Theme.js.map +1 -0
  90. package/dist/module/ui/theme/ThemeProvider.js +112 -0
  91. package/dist/module/ui/theme/ThemeProvider.js.map +1 -0
  92. package/dist/{models/InAppMessage.js → module/ui/theme/index.js} +6 -3
  93. package/dist/module/ui/theme/index.js.map +1 -0
  94. package/dist/module/ui/types/ContentViewEvent.js +2 -0
  95. package/dist/module/ui/types/ContentViewEvent.js.map +1 -0
  96. package/dist/module/ui/types/Templates.js +26 -0
  97. package/dist/module/ui/types/Templates.js.map +1 -0
  98. package/dist/{models/ScopeDetails.js → module/ui/types/index.js} +6 -3
  99. package/dist/module/ui/types/index.js.map +1 -0
  100. package/dist/module/ui/utils/generateCardHash.js +50 -0
  101. package/dist/module/ui/utils/generateCardHash.js.map +1 -0
  102. package/dist/module/ui/utils/generateCardHash.spec.js +103 -0
  103. package/dist/module/ui/utils/generateCardHash.spec.js.map +1 -0
  104. package/dist/module/ui/utils/inboxStorage.js +65 -0
  105. package/dist/module/ui/utils/inboxStorage.js.map +1 -0
  106. package/dist/module/ui/utils/inboxStorage.spec.js +123 -0
  107. package/dist/module/ui/utils/inboxStorage.spec.js.map +1 -0
  108. package/dist/module/ui/utils/index.js +5 -0
  109. package/dist/module/ui/utils/index.js.map +1 -0
  110. package/dist/{Messaging.d.ts → typescript/Messaging.d.ts} +23 -7
  111. package/dist/typescript/Messaging.d.ts.map +1 -0
  112. package/dist/{index.d.ts → typescript/index.d.ts} +4 -2
  113. package/dist/typescript/index.d.ts.map +1 -0
  114. package/dist/typescript/models/ContentCard.d.ts +57 -0
  115. package/dist/typescript/models/ContentCard.d.ts.map +1 -0
  116. package/dist/{models → typescript/models}/HTMLProposition.d.ts +1 -0
  117. package/dist/typescript/models/HTMLProposition.d.ts.map +1 -0
  118. package/dist/{models → typescript/models}/InAppMessage.d.ts +1 -0
  119. package/dist/typescript/models/InAppMessage.d.ts.map +1 -0
  120. package/dist/{models → typescript/models}/JSONProposition.d.ts +1 -0
  121. package/dist/typescript/models/JSONProposition.d.ts.map +1 -0
  122. package/dist/{models → typescript/models}/Message.d.ts +14 -0
  123. package/dist/typescript/models/Message.d.ts.map +1 -0
  124. package/dist/{models → typescript/models}/MessagingDelegate.d.ts +1 -0
  125. package/dist/typescript/models/MessagingDelegate.d.ts.map +1 -0
  126. package/dist/{models → typescript/models}/MessagingEdgeEventType.d.ts +1 -0
  127. package/dist/typescript/models/MessagingEdgeEventType.d.ts.map +1 -0
  128. package/dist/{models → typescript/models}/MessagingProposition.d.ts +1 -0
  129. package/dist/typescript/models/MessagingProposition.d.ts.map +1 -0
  130. package/dist/{models → typescript/models}/MessagingPropositionItem.d.ts +1 -0
  131. package/dist/typescript/models/MessagingPropositionItem.d.ts.map +1 -0
  132. package/dist/{models → typescript/models}/PersonalizationSchema.d.ts +2 -0
  133. package/dist/typescript/models/PersonalizationSchema.d.ts.map +1 -0
  134. package/dist/{models → typescript/models}/PropositionItem.d.ts +1 -0
  135. package/dist/typescript/models/PropositionItem.d.ts.map +1 -0
  136. package/dist/{models → typescript/models}/ScopeDetails.d.ts +1 -0
  137. package/dist/typescript/models/ScopeDetails.d.ts.map +1 -0
  138. package/dist/typescript/models/index.d.ts +11 -0
  139. package/dist/typescript/models/index.d.ts.map +1 -0
  140. package/dist/typescript/ui/components/Button/Button.d.ts +14 -0
  141. package/dist/typescript/ui/components/Button/Button.d.ts.map +1 -0
  142. package/dist/typescript/ui/components/Button/Button.spec.d.ts +2 -0
  143. package/dist/typescript/ui/components/Button/Button.spec.d.ts.map +1 -0
  144. package/dist/typescript/ui/components/ContentCardView/ContentCardView.d.ts +39 -0
  145. package/dist/typescript/ui/components/ContentCardView/ContentCardView.d.ts.map +1 -0
  146. package/dist/typescript/ui/components/ContentCardView/ContentCardView.spec.d.ts +2 -0
  147. package/dist/typescript/ui/components/ContentCardView/ContentCardView.spec.d.ts.map +1 -0
  148. package/dist/typescript/ui/components/DismissButton/DismissButton.d.ts +13 -0
  149. package/dist/typescript/ui/components/DismissButton/DismissButton.d.ts.map +1 -0
  150. package/dist/typescript/ui/components/DismissButton/DismissButton.spec.d.ts +2 -0
  151. package/dist/typescript/ui/components/DismissButton/DismissButton.spec.d.ts.map +1 -0
  152. package/dist/typescript/ui/components/FullScreenCenterView/FullScreenCenterView.d.ts +5 -0
  153. package/dist/typescript/ui/components/FullScreenCenterView/FullScreenCenterView.d.ts.map +1 -0
  154. package/dist/typescript/ui/components/Inbox/EmptyState.d.ts +19 -0
  155. package/dist/typescript/ui/components/Inbox/EmptyState.d.ts.map +1 -0
  156. package/dist/typescript/ui/components/Inbox/Inbox.d.ts +21 -0
  157. package/dist/typescript/ui/components/Inbox/Inbox.d.ts.map +1 -0
  158. package/dist/typescript/ui/components/Inbox/Inbox.spec.d.ts +2 -0
  159. package/dist/typescript/ui/components/Inbox/Inbox.spec.d.ts.map +1 -0
  160. package/dist/typescript/ui/components/Pagination/Pagination.d.ts +14 -0
  161. package/dist/typescript/ui/components/Pagination/Pagination.d.ts.map +1 -0
  162. package/dist/typescript/ui/components/Pagination/Pagination.spec.d.ts +2 -0
  163. package/dist/typescript/ui/components/Pagination/Pagination.spec.d.ts.map +1 -0
  164. package/dist/typescript/ui/components/UnreadIcon/UnreadIcon.d.ts +14 -0
  165. package/dist/typescript/ui/components/UnreadIcon/UnreadIcon.d.ts.map +1 -0
  166. package/dist/typescript/ui/components/UnreadIcon/UnreadIcon.spec.d.ts +2 -0
  167. package/dist/typescript/ui/components/UnreadIcon/UnreadIcon.spec.d.ts.map +1 -0
  168. package/dist/typescript/ui/components/index.d.ts +10 -0
  169. package/dist/typescript/ui/components/index.d.ts.map +1 -0
  170. package/dist/typescript/ui/hooks/index.d.ts +4 -0
  171. package/dist/typescript/ui/hooks/index.d.ts.map +1 -0
  172. package/dist/typescript/ui/hooks/useAspectRatio.d.ts +3 -0
  173. package/dist/typescript/ui/hooks/useAspectRatio.d.ts.map +1 -0
  174. package/dist/typescript/ui/hooks/useAspectRatio.spec.d.ts +2 -0
  175. package/dist/typescript/ui/hooks/useAspectRatio.spec.d.ts.map +1 -0
  176. package/dist/typescript/ui/hooks/useContentCardUI.d.ts +14 -0
  177. package/dist/typescript/ui/hooks/useContentCardUI.d.ts.map +1 -0
  178. package/dist/typescript/ui/hooks/useContentCardUI.spec.d.ts +2 -0
  179. package/dist/typescript/ui/hooks/useContentCardUI.spec.d.ts.map +1 -0
  180. package/dist/typescript/ui/hooks/useInbox.d.ts +12 -0
  181. package/dist/typescript/ui/hooks/useInbox.d.ts.map +1 -0
  182. package/dist/typescript/ui/hooks/useInbox.spec.d.ts +2 -0
  183. package/dist/typescript/ui/hooks/useInbox.spec.d.ts.map +1 -0
  184. package/dist/typescript/ui/hooks/useInboxSettings.d.ts +7 -0
  185. package/dist/typescript/ui/hooks/useInboxSettings.d.ts.map +1 -0
  186. package/dist/typescript/ui/hooks/useInboxSettings.spec.d.ts +2 -0
  187. package/dist/typescript/ui/hooks/useInboxSettings.spec.d.ts.map +1 -0
  188. package/dist/typescript/ui/index.d.ts +8 -0
  189. package/dist/typescript/ui/index.d.ts.map +1 -0
  190. package/dist/typescript/ui/providers/InboxProvider.d.ts +56 -0
  191. package/dist/typescript/ui/providers/InboxProvider.d.ts.map +1 -0
  192. package/dist/typescript/ui/theme/Theme.d.ts +44 -0
  193. package/dist/typescript/ui/theme/Theme.d.ts.map +1 -0
  194. package/dist/typescript/ui/theme/ThemeProvider.d.ts +21 -0
  195. package/dist/typescript/ui/theme/ThemeProvider.d.ts.map +1 -0
  196. package/dist/typescript/ui/theme/index.d.ts +3 -0
  197. package/dist/typescript/ui/theme/index.d.ts.map +1 -0
  198. package/dist/typescript/ui/types/ContentViewEvent.d.ts +9 -0
  199. package/dist/typescript/ui/types/ContentViewEvent.d.ts.map +1 -0
  200. package/dist/typescript/ui/types/Templates.d.ts +43 -0
  201. package/dist/typescript/ui/types/Templates.d.ts.map +1 -0
  202. package/dist/typescript/ui/types/index.d.ts +3 -0
  203. package/dist/typescript/ui/types/index.d.ts.map +1 -0
  204. package/dist/typescript/ui/utils/generateCardHash.d.ts +21 -0
  205. package/dist/typescript/ui/utils/generateCardHash.d.ts.map +1 -0
  206. package/dist/typescript/ui/utils/generateCardHash.spec.d.ts +2 -0
  207. package/dist/typescript/ui/utils/generateCardHash.spec.d.ts.map +1 -0
  208. package/dist/typescript/ui/utils/inboxStorage.d.ts +20 -0
  209. package/dist/typescript/ui/utils/inboxStorage.d.ts.map +1 -0
  210. package/dist/typescript/ui/utils/inboxStorage.spec.d.ts +2 -0
  211. package/dist/typescript/ui/utils/inboxStorage.spec.d.ts.map +1 -0
  212. package/dist/typescript/ui/utils/index.d.ts +3 -0
  213. package/dist/typescript/ui/utils/index.d.ts.map +1 -0
  214. package/ios/src/RCTAEPMessaging.mm +15 -0
  215. package/ios/src/RCTAEPMessaging.swift +61 -3
  216. package/ios/src/RCTAEPMessagingConstants.swift +4 -1
  217. package/jest.config.js +15 -0
  218. package/package.json +33 -5
  219. package/src/Messaging.ts +288 -32
  220. package/src/index.ts +3 -3
  221. package/src/models/ContentCard.ts +52 -27
  222. package/src/models/HTMLProposition.ts +1 -1
  223. package/src/models/JSONProposition.ts +1 -1
  224. package/src/models/Message.ts +50 -0
  225. package/src/models/PersonalizationSchema.ts +1 -0
  226. package/src/models/index.ts +22 -0
  227. package/src/ui/components/Button/Button.spec.tsx +496 -0
  228. package/src/ui/components/Button/Button.tsx +76 -0
  229. package/src/ui/components/ContentCardView/ContentCardView.spec.tsx +278 -0
  230. package/src/ui/components/ContentCardView/ContentCardView.tsx +400 -0
  231. package/src/ui/components/DismissButton/DismissButton.spec.tsx +314 -0
  232. package/src/ui/components/DismissButton/DismissButton.tsx +100 -0
  233. package/src/ui/components/FullScreenCenterView/FullScreenCenterView.tsx +32 -0
  234. package/src/ui/components/Inbox/EmptyState.tsx +89 -0
  235. package/src/ui/components/Inbox/Inbox.spec.tsx +478 -0
  236. package/src/ui/components/Inbox/Inbox.tsx +275 -0
  237. package/src/ui/components/Pagination/Pagination.spec.tsx +159 -0
  238. package/src/ui/components/Pagination/Pagination.tsx +222 -0
  239. package/src/ui/components/UnreadIcon/UnreadIcon.spec.tsx +878 -0
  240. package/src/ui/components/UnreadIcon/UnreadIcon.tsx +234 -0
  241. package/src/ui/components/index.ts +22 -0
  242. package/{dist/models/MessagingPropositionItem.js → src/ui/hooks/index.ts} +5 -4
  243. package/src/ui/hooks/useAspectRatio.spec.tsx +66 -0
  244. package/src/ui/hooks/useAspectRatio.tsx +39 -0
  245. package/src/ui/hooks/useContentCardUI.spec.tsx +82 -0
  246. package/src/ui/hooks/useContentCardUI.ts +48 -0
  247. package/src/ui/hooks/useInbox.spec.tsx +87 -0
  248. package/src/ui/hooks/useInbox.ts +46 -0
  249. package/src/ui/hooks/useInboxSettings.spec.tsx +41 -0
  250. package/src/ui/hooks/useInboxSettings.ts +24 -0
  251. package/src/ui/index.ts +7 -0
  252. package/src/ui/providers/InboxProvider.tsx +79 -0
  253. package/src/ui/theme/Theme.ts +57 -0
  254. package/src/ui/theme/ThemeProvider.tsx +120 -0
  255. package/src/ui/theme/index.ts +14 -0
  256. package/src/ui/types/ContentViewEvent.ts +20 -0
  257. package/src/ui/types/Templates.ts +77 -0
  258. package/src/ui/types/index.ts +14 -0
  259. package/src/ui/utils/generateCardHash.spec.tsx +86 -0
  260. package/src/ui/utils/generateCardHash.ts +59 -0
  261. package/src/ui/utils/inboxStorage.spec.tsx +136 -0
  262. package/src/ui/utils/inboxStorage.ts +64 -0
  263. package/src/ui/utils/index.ts +3 -0
  264. package/tutorials/ContentCardCustomizationGuide.md +661 -0
  265. package/tutorials/ContentCards.md +419 -0
  266. package/tutorials/In-App Messaging.md +31 -0
  267. package/tutorials/Inbox.md +515 -0
  268. package/tutorials/resources/image-only-template.png +0 -0
  269. package/tutorials/resources/large-image-template.png +0 -0
  270. package/tutorials/resources/small-image-template.png +0 -0
  271. package/dist/Messaging.js +0 -151
  272. package/dist/Messaging.js.map +0 -1
  273. package/dist/index.js +0 -34
  274. package/dist/index.js.map +0 -1
  275. package/dist/models/ContentCard.d.ts +0 -51
  276. package/dist/models/ContentCard.js.map +0 -1
  277. package/dist/models/HTMLProposition.js.map +0 -1
  278. package/dist/models/InAppMessage.js.map +0 -1
  279. package/dist/models/JSONProposition.js.map +0 -1
  280. package/dist/models/Message.js +0 -114
  281. package/dist/models/Message.js.map +0 -1
  282. package/dist/models/MessagingDelegate.js +0 -14
  283. package/dist/models/MessagingDelegate.js.map +0 -1
  284. package/dist/models/MessagingEdgeEventType.js +0 -24
  285. package/dist/models/MessagingEdgeEventType.js.map +0 -1
  286. package/dist/models/MessagingProposition.js +0 -59
  287. package/dist/models/MessagingProposition.js.map +0 -1
  288. package/dist/models/MessagingPropositionItem.js.map +0 -1
  289. package/dist/models/PersonalizationSchema.js +0 -25
  290. package/dist/models/PersonalizationSchema.js.map +0 -1
  291. package/dist/models/PropositionItem.js +0 -78
  292. package/dist/models/PropositionItem.js.map +0 -1
  293. package/dist/models/ScopeDetails.js.map +0 -1
@@ -0,0 +1,878 @@
1
+
2
+ /*
3
+ Copyright 2026 Adobe. All rights reserved.
4
+ This file is licensed to you under the Apache License, Version 2.0 (the
5
+ "License"); you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+ http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
8
+ or agreed to in writing, software distributed under the License is
9
+ distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF
10
+ ANY KIND, either express or implied. See the License for the specific
11
+ language governing permissions and limitations under the License.
12
+ */
13
+ import { act, render, waitFor } from '@testing-library/react-native';
14
+ import React from 'react';
15
+ import { Image, useColorScheme } from 'react-native';
16
+ import InboxProvider from '../../providers/InboxProvider';
17
+ import UnreadIcon from './UnreadIcon';
18
+
19
+ // Mock useColorScheme
20
+ jest.mock('react-native/Libraries/Utilities/useColorScheme');
21
+ const mockUseColorScheme = useColorScheme as jest.MockedFunction<
22
+ typeof useColorScheme
23
+ >;
24
+
25
+ describe('UnreadIcon', () => {
26
+ const mockInboxSettings = {
27
+ content: {
28
+ heading: { content: 'Test' },
29
+ layout: { orientation: 'vertical' as const },
30
+ capacity: 10,
31
+ emptyStateSettings: { message: { content: 'Empty' } },
32
+ unread_indicator: {
33
+ unread_bg: {
34
+ clr: {
35
+ light: '#FFF3E0',
36
+ dark: '#2D1B0E',
37
+ },
38
+ },
39
+ unread_icon: {
40
+ placement: 'topright' as const,
41
+ image: {
42
+ url: 'https://example.com/icon.png',
43
+ darkUrl: '',
44
+ },
45
+ },
46
+ },
47
+ isUnreadEnabled: true,
48
+ },
49
+ showPagination: false,
50
+ };
51
+
52
+ beforeEach(() => {
53
+ jest.clearAllMocks();
54
+ mockUseColorScheme.mockReturnValue('light');
55
+ });
56
+
57
+ afterEach(() => {
58
+ jest.clearAllMocks();
59
+ });
60
+
61
+ describe('Basic rendering', () => {
62
+ it('should render successfully with inbox settings', () => {
63
+ const { getByTestId } = render(
64
+ <InboxProvider settings={mockInboxSettings}>
65
+ <UnreadIcon testID="unread-icon" />
66
+ </InboxProvider>
67
+ );
68
+ expect(getByTestId('unread-icon')).toBeTruthy();
69
+ });
70
+
71
+ it('should render with custom size', () => {
72
+ const { getByTestId } = render(
73
+ <InboxProvider settings={mockInboxSettings}>
74
+ <UnreadIcon testID="unread-icon" size={30} />
75
+ </InboxProvider>
76
+ );
77
+ expect(getByTestId('unread-icon')).toBeTruthy();
78
+ });
79
+
80
+ it('should render without crashing when settings provide null', () => {
81
+ expect(() => {
82
+ render(
83
+ <InboxProvider settings={null as any}>
84
+ <UnreadIcon type="dot" />
85
+ </InboxProvider>
86
+ );
87
+ }).not.toThrow();
88
+ });
89
+ });
90
+
91
+ describe('Placement positions', () => {
92
+ it('should render with topright placement', () => {
93
+ expect(() => {
94
+ render(
95
+ <InboxProvider settings={mockInboxSettings}>
96
+ <UnreadIcon />
97
+ </InboxProvider>
98
+ );
99
+ }).not.toThrow();
100
+ });
101
+
102
+ it('should render with topleft placement', () => {
103
+ const settings = {
104
+ ...mockInboxSettings,
105
+ content: {
106
+ ...mockInboxSettings.content,
107
+ unread_indicator: {
108
+ ...mockInboxSettings.content.unread_indicator,
109
+ unread_icon: {
110
+ ...mockInboxSettings.content.unread_indicator.unread_icon,
111
+ placement: 'topleft' as const,
112
+ },
113
+ },
114
+ },
115
+ };
116
+
117
+ expect(() => {
118
+ render(
119
+ <InboxProvider settings={settings}>
120
+ <UnreadIcon />
121
+ </InboxProvider>
122
+ );
123
+ }).not.toThrow();
124
+ });
125
+
126
+ it('should render with bottomright placement', () => {
127
+ const settings = {
128
+ ...mockInboxSettings,
129
+ content: {
130
+ ...mockInboxSettings.content,
131
+ unread_indicator: {
132
+ ...mockInboxSettings.content.unread_indicator,
133
+ unread_icon: {
134
+ ...mockInboxSettings.content.unread_indicator.unread_icon,
135
+ placement: 'bottomright' as const,
136
+ },
137
+ },
138
+ },
139
+ };
140
+
141
+ expect(() => {
142
+ render(
143
+ <InboxProvider settings={settings}>
144
+ <UnreadIcon />
145
+ </InboxProvider>
146
+ );
147
+ }).not.toThrow();
148
+ });
149
+
150
+ it('should render with bottomleft placement', () => {
151
+ const settings = {
152
+ ...mockInboxSettings,
153
+ content: {
154
+ ...mockInboxSettings.content,
155
+ unread_indicator: {
156
+ ...mockInboxSettings.content.unread_indicator,
157
+ unread_icon: {
158
+ ...mockInboxSettings.content.unread_indicator.unread_icon,
159
+ placement: 'bottomleft' as const,
160
+ },
161
+ },
162
+ },
163
+ };
164
+
165
+ expect(() => {
166
+ render(
167
+ <InboxProvider settings={settings}>
168
+ <UnreadIcon />
169
+ </InboxProvider>
170
+ );
171
+ }).not.toThrow();
172
+ });
173
+
174
+ it('falls back to topright when props.position is unknown and no context placement', () => {
175
+ const settingsWithoutIndicator = {
176
+ ...mockInboxSettings,
177
+ content: {
178
+ ...mockInboxSettings.content,
179
+ unread_indicator: undefined,
180
+ },
181
+ };
182
+
183
+ const { getByTestId } = render(
184
+ <InboxProvider settings={settingsWithoutIndicator}>
185
+ <UnreadIcon testID="unread-icon" position={'unknown' as any} />
186
+ </InboxProvider>
187
+ );
188
+ const container = getByTestId('unread-icon');
189
+ const styles = (Array.isArray(container.props.style) ? container.props.style : [container.props.style]).flat(Infinity);
190
+ const hasTopRight = styles.some((s: any) => s && s.top === 6 && s.right === 6);
191
+ expect(hasTopRight).toBe(true);
192
+ });
193
+
194
+ it('falls back to topright when context placement is unknown', () => {
195
+ const settingsWithUnknownPlacement = {
196
+ ...mockInboxSettings,
197
+ content: {
198
+ ...mockInboxSettings.content,
199
+ unread_indicator: {
200
+ ...mockInboxSettings.content.unread_indicator,
201
+ unread_icon: { placement: 'unknown' as any, image: { url: '' } },
202
+ },
203
+ },
204
+ };
205
+
206
+ const { getByTestId } = render(
207
+ <InboxProvider settings={settingsWithUnknownPlacement}>
208
+ <UnreadIcon testID="unread-icon" />
209
+ </InboxProvider>
210
+ );
211
+ const container = getByTestId('unread-icon');
212
+ const styles = (Array.isArray(container.props.style) ? container.props.style : [container.props.style]).flat(Infinity);
213
+ const hasTopRight = styles.some((s: any) => s && s.top === 6 && s.right === 6);
214
+ expect(hasTopRight).toBe(true);
215
+ });
216
+ });
217
+
218
+ describe('Light mode rendering', () => {
219
+ beforeEach(() => {
220
+ mockUseColorScheme.mockReturnValue('light');
221
+ });
222
+
223
+ it('should render in light mode with image URL', () => {
224
+ expect(() => {
225
+ render(
226
+ <InboxProvider settings={mockInboxSettings}>
227
+ <UnreadIcon />
228
+ </InboxProvider>
229
+ );
230
+ }).not.toThrow();
231
+ });
232
+
233
+ it('should render dot when URL is empty string in light mode', () => {
234
+ const settings = {
235
+ ...mockInboxSettings,
236
+ content: {
237
+ ...mockInboxSettings.content,
238
+ unread_indicator: {
239
+ ...mockInboxSettings.content.unread_indicator,
240
+ unread_icon: {
241
+ ...mockInboxSettings.content.unread_indicator.unread_icon,
242
+ image: {
243
+ url: '',
244
+ darkUrl: '',
245
+ },
246
+ },
247
+ },
248
+ },
249
+ };
250
+
251
+ expect(() => {
252
+ render(
253
+ <InboxProvider settings={settings}>
254
+ <UnreadIcon />
255
+ </InboxProvider>
256
+ );
257
+ }).not.toThrow();
258
+ });
259
+ });
260
+
261
+ describe('Dark mode rendering', () => {
262
+ beforeEach(() => {
263
+ mockUseColorScheme.mockReturnValue('dark');
264
+ });
265
+
266
+ it('should render in dark mode with darkUrl provided', () => {
267
+ const settings = {
268
+ ...mockInboxSettings,
269
+ content: {
270
+ ...mockInboxSettings.content,
271
+ unread_indicator: {
272
+ ...mockInboxSettings.content.unread_indicator,
273
+ unread_icon: {
274
+ ...mockInboxSettings.content.unread_indicator.unread_icon,
275
+ image: {
276
+ url: 'https://example.com/light.png',
277
+ darkUrl: 'https://example.com/dark.png',
278
+ },
279
+ },
280
+ },
281
+ },
282
+ };
283
+
284
+ expect(() => {
285
+ render(
286
+ <InboxProvider settings={settings}>
287
+ <UnreadIcon />
288
+ </InboxProvider>
289
+ );
290
+ }).not.toThrow();
291
+ });
292
+
293
+ it('should render dot when darkUrl is empty string in dark mode', () => {
294
+ expect(() => {
295
+ render(
296
+ <InboxProvider settings={mockInboxSettings}>
297
+ <UnreadIcon />
298
+ </InboxProvider>
299
+ );
300
+ }).not.toThrow();
301
+ });
302
+
303
+ it('should fallback to light mode image when no darkUrl provided', () => {
304
+ const settings = {
305
+ ...mockInboxSettings,
306
+ content: {
307
+ ...mockInboxSettings.content,
308
+ unread_indicator: {
309
+ ...mockInboxSettings.content.unread_indicator,
310
+ unread_icon: {
311
+ placement: 'topright' as const,
312
+ image: {
313
+ url: 'https://example.com/icon.png',
314
+ },
315
+ },
316
+ },
317
+ },
318
+ };
319
+
320
+ expect(() => {
321
+ render(
322
+ <InboxProvider settings={settings}>
323
+ <UnreadIcon />
324
+ </InboxProvider>
325
+ );
326
+ }).not.toThrow();
327
+ });
328
+ });
329
+
330
+ describe('Props-based rendering', () => {
331
+ it('should render with custom source prop when no settings provided', () => {
332
+ const settingsWithoutImage = {
333
+ ...mockInboxSettings,
334
+ content: {
335
+ ...mockInboxSettings.content,
336
+ unread_indicator: undefined,
337
+ },
338
+ };
339
+
340
+ expect(() => {
341
+ render(
342
+ <InboxProvider settings={settingsWithoutImage}>
343
+ <UnreadIcon source={{ uri: 'https://custom.com/icon.png' }} />
344
+ </InboxProvider>
345
+ );
346
+ }).not.toThrow();
347
+ });
348
+
349
+ it('should render with custom darkSource prop', () => {
350
+ mockUseColorScheme.mockReturnValue('dark');
351
+
352
+ const settingsWithoutImage = {
353
+ ...mockInboxSettings,
354
+ content: {
355
+ ...mockInboxSettings.content,
356
+ unread_indicator: undefined,
357
+ },
358
+ };
359
+
360
+ expect(() => {
361
+ render(
362
+ <InboxProvider settings={settingsWithoutImage}>
363
+ <UnreadIcon
364
+ source={{ uri: 'https://custom.com/light.png' }}
365
+ darkSource={{ uri: 'https://custom.com/dark.png' }}
366
+ />
367
+ </InboxProvider>
368
+ );
369
+ }).not.toThrow();
370
+ });
371
+
372
+ it('should render with custom position prop', () => {
373
+ const settingsWithoutImage = {
374
+ ...mockInboxSettings,
375
+ content: {
376
+ ...mockInboxSettings.content,
377
+ unread_indicator: undefined,
378
+ },
379
+ };
380
+
381
+ expect(() => {
382
+ render(
383
+ <InboxProvider settings={settingsWithoutImage}>
384
+ <UnreadIcon position="bottomleft" />
385
+ </InboxProvider>
386
+ );
387
+ }).not.toThrow();
388
+ });
389
+
390
+ it('should render as dot when type prop is "dot"', () => {
391
+ const settingsWithoutImage = {
392
+ ...mockInboxSettings,
393
+ content: {
394
+ ...mockInboxSettings.content,
395
+ unread_indicator: undefined,
396
+ },
397
+ };
398
+
399
+ expect(() => {
400
+ render(
401
+ <InboxProvider settings={settingsWithoutImage}>
402
+ <UnreadIcon type="dot" />
403
+ </InboxProvider>
404
+ );
405
+ }).not.toThrow();
406
+ });
407
+
408
+ it('should render as image when type prop is "image"', () => {
409
+ const settingsWithoutImage = {
410
+ ...mockInboxSettings,
411
+ content: {
412
+ ...mockInboxSettings.content,
413
+ unread_indicator: undefined,
414
+ },
415
+ };
416
+
417
+ expect(() => {
418
+ render(
419
+ <InboxProvider settings={settingsWithoutImage}>
420
+ <UnreadIcon
421
+ type="image"
422
+ source={{ uri: 'https://custom.com/icon.png' }}
423
+ />
424
+ </InboxProvider>
425
+ );
426
+ }).not.toThrow();
427
+ });
428
+
429
+ it('renders default dot when no unread_indicator and no image props provided', () => {
430
+ // With no unread_indicator and no source/darkSource props, default content should be a dot (no Image)
431
+ const settingsWithoutIndicator = {
432
+ ...mockInboxSettings,
433
+ content: {
434
+ ...mockInboxSettings.content,
435
+ unread_indicator: undefined,
436
+ },
437
+ };
438
+
439
+ const { UNSAFE_queryByType } = render(
440
+ <InboxProvider settings={settingsWithoutIndicator}>
441
+ <UnreadIcon />
442
+ </InboxProvider>
443
+ );
444
+ expect(UNSAFE_queryByType(Image)).toBeNull();
445
+ });
446
+ });
447
+
448
+ describe('Custom styles', () => {
449
+ it('should accept and apply custom imageStyle', () => {
450
+ const customImageStyle = { opacity: 0.8 };
451
+
452
+ expect(() => {
453
+ render(
454
+ <InboxProvider settings={mockInboxSettings}>
455
+ <UnreadIcon imageStyle={customImageStyle} />
456
+ </InboxProvider>
457
+ );
458
+ }).not.toThrow();
459
+ });
460
+
461
+ it('should accept and apply custom containerStyle', () => {
462
+ const customContainerStyle = { padding: 5 };
463
+
464
+ expect(() => {
465
+ render(
466
+ <InboxProvider settings={mockInboxSettings}>
467
+ <UnreadIcon containerStyle={customContainerStyle} />
468
+ </InboxProvider>
469
+ );
470
+ }).not.toThrow();
471
+ });
472
+
473
+ it('should handle both imageStyle and containerStyle together', () => {
474
+ expect(() => {
475
+ render(
476
+ <InboxProvider settings={mockInboxSettings}>
477
+ <UnreadIcon
478
+ imageStyle={{ opacity: 0.8 }}
479
+ containerStyle={{ padding: 5 }}
480
+ />
481
+ </InboxProvider>
482
+ );
483
+ }).not.toThrow();
484
+ });
485
+
486
+ it('renders Image when renderType is image via darkSource prop only (branch: imageSource || darkImageSource)', () => {
487
+ mockUseColorScheme.mockReturnValue('dark');
488
+ const settingsWithoutIndicator = {
489
+ ...mockInboxSettings,
490
+ content: {
491
+ ...mockInboxSettings.content,
492
+ unread_indicator: undefined,
493
+ },
494
+ };
495
+
496
+ const { UNSAFE_getByType } = render(
497
+ <InboxProvider settings={settingsWithoutIndicator}>
498
+ <UnreadIcon type="dot" darkSource={{ uri: 'https://custom.com/dark.png' }} />
499
+ </InboxProvider>
500
+ );
501
+
502
+ const imageComponent = UNSAFE_getByType(Image);
503
+ expect(imageComponent.props.source).toEqual({ uri: 'https://custom.com/dark.png' });
504
+ });
505
+ });
506
+
507
+ describe('Size variations', () => {
508
+ it('should render with default size of 20', () => {
509
+ expect(() => {
510
+ render(
511
+ <InboxProvider settings={mockInboxSettings}>
512
+ <UnreadIcon />
513
+ </InboxProvider>
514
+ );
515
+ }).not.toThrow();
516
+ });
517
+
518
+ it('should render with custom size of 30', () => {
519
+ expect(() => {
520
+ render(
521
+ <InboxProvider settings={mockInboxSettings}>
522
+ <UnreadIcon size={30} />
523
+ </InboxProvider>
524
+ );
525
+ }).not.toThrow();
526
+ });
527
+
528
+ it('should render with custom size of 15', () => {
529
+ expect(() => {
530
+ render(
531
+ <InboxProvider settings={mockInboxSettings}>
532
+ <UnreadIcon size={15} />
533
+ </InboxProvider>
534
+ );
535
+ }).not.toThrow();
536
+ });
537
+
538
+ it('should handle very large size', () => {
539
+ expect(() => {
540
+ render(
541
+ <InboxProvider settings={mockInboxSettings}>
542
+ <UnreadIcon size={100} />
543
+ </InboxProvider>
544
+ );
545
+ }).not.toThrow();
546
+ });
547
+
548
+ it('should handle very small size', () => {
549
+ expect(() => {
550
+ render(
551
+ <InboxProvider settings={mockInboxSettings}>
552
+ <UnreadIcon size={5} />
553
+ </InboxProvider>
554
+ );
555
+ }).not.toThrow();
556
+ });
557
+ });
558
+
559
+ describe('Context settings priority', () => {
560
+ it('should prioritize context settings over props', () => {
561
+ expect(() => {
562
+ render(
563
+ <InboxProvider settings={mockInboxSettings}>
564
+ <UnreadIcon position="bottomleft" />
565
+ </InboxProvider>
566
+ );
567
+ }).not.toThrow();
568
+ });
569
+
570
+ it('should use props when context settings are not available', () => {
571
+ const settingsWithoutImage = {
572
+ ...mockInboxSettings,
573
+ content: {
574
+ ...mockInboxSettings.content,
575
+ unread_indicator: undefined,
576
+ },
577
+ };
578
+
579
+ expect(() => {
580
+ render(
581
+ <InboxProvider settings={settingsWithoutImage}>
582
+ <UnreadIcon
583
+ position="bottomleft"
584
+ source={{ uri: 'https://custom.com/icon.png' }}
585
+ />
586
+ </InboxProvider>
587
+ );
588
+ }).not.toThrow();
589
+ });
590
+ });
591
+
592
+ describe('Image error handling', () => {
593
+ it('should render without crashing when image URL is invalid', () => {
594
+ const settings = {
595
+ ...mockInboxSettings,
596
+ content: {
597
+ ...mockInboxSettings.content,
598
+ unread_indicator: {
599
+ ...mockInboxSettings.content.unread_indicator,
600
+ unread_icon: {
601
+ ...mockInboxSettings.content.unread_indicator.unread_icon,
602
+ image: {
603
+ url: 'invalid-url',
604
+ darkUrl: '',
605
+ },
606
+ },
607
+ },
608
+ },
609
+ };
610
+
611
+ expect(() => {
612
+ render(
613
+ <InboxProvider settings={settings}>
614
+ <UnreadIcon />
615
+ </InboxProvider>
616
+ );
617
+ }).not.toThrow();
618
+ });
619
+
620
+ it('renders dot when image load fails (onError branch)', async () => {
621
+ const settings = {
622
+ ...mockInboxSettings,
623
+ content: {
624
+ ...mockInboxSettings.content,
625
+ unread_indicator: {
626
+ ...mockInboxSettings.content.unread_indicator,
627
+ unread_icon: {
628
+ ...mockInboxSettings.content.unread_indicator.unread_icon,
629
+ image: {
630
+ url: 'https://example.com/icon.png',
631
+ darkUrl: '',
632
+ },
633
+ },
634
+ },
635
+ },
636
+ };
637
+
638
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
639
+
640
+ const { UNSAFE_getByType } = render(
641
+ <InboxProvider settings={settings}>
642
+ <UnreadIcon testID="unread-icon" />
643
+ </InboxProvider>
644
+ );
645
+
646
+ const image = UNSAFE_getByType(Image);
647
+ await act(async () => {
648
+ image.props.onError({ nativeEvent: { error: 'failed' } });
649
+ });
650
+
651
+ await waitFor(() => {
652
+ expect(warnSpy).toHaveBeenCalledWith(
653
+ 'Failed to load unread icon image:',
654
+ 'failed'
655
+ );
656
+ });
657
+
658
+ warnSpy.mockRestore();
659
+ });
660
+ });
661
+
662
+ describe('Edge cases', () => {
663
+ it('should handle undefined image URLs', () => {
664
+ const settings = {
665
+ ...mockInboxSettings,
666
+ content: {
667
+ ...mockInboxSettings.content,
668
+ unread_indicator: {
669
+ ...mockInboxSettings.content.unread_indicator,
670
+ unread_icon: {
671
+ placement: 'topright' as const,
672
+ image: {
673
+ url: undefined as any,
674
+ darkUrl: undefined,
675
+ },
676
+ },
677
+ },
678
+ },
679
+ };
680
+
681
+ expect(() => {
682
+ render(
683
+ <InboxProvider settings={settings}>
684
+ <UnreadIcon />
685
+ </InboxProvider>
686
+ );
687
+ }).not.toThrow();
688
+ });
689
+
690
+ it('should handle zero size', () => {
691
+ expect(() => {
692
+ render(
693
+ <InboxProvider settings={mockInboxSettings}>
694
+ <UnreadIcon size={0} />
695
+ </InboxProvider>
696
+ );
697
+ }).not.toThrow();
698
+ });
699
+
700
+ it('should handle negative size', () => {
701
+ expect(() => {
702
+ render(
703
+ <InboxProvider settings={mockInboxSettings}>
704
+ <UnreadIcon size={-10} />
705
+ </InboxProvider>
706
+ );
707
+ }).not.toThrow();
708
+ });
709
+ });
710
+
711
+ describe('Color scheme switching', () => {
712
+ it('should adapt to color scheme changes from light to dark', () => {
713
+ mockUseColorScheme.mockReturnValue('light');
714
+
715
+ const { rerender } = render(
716
+ <InboxProvider settings={mockInboxSettings}>
717
+ <UnreadIcon />
718
+ </InboxProvider>
719
+ );
720
+
721
+ // Switch to dark mode
722
+ mockUseColorScheme.mockReturnValue('dark');
723
+
724
+ expect(() => {
725
+ rerender(
726
+ <InboxProvider settings={mockInboxSettings}>
727
+ <UnreadIcon />
728
+ </InboxProvider>
729
+ );
730
+ }).not.toThrow();
731
+ });
732
+
733
+ it('should handle null color scheme', () => {
734
+ mockUseColorScheme.mockReturnValue(null);
735
+
736
+ expect(() => {
737
+ render(
738
+ <InboxProvider settings={mockInboxSettings}>
739
+ <UnreadIcon />
740
+ </InboxProvider>
741
+ );
742
+ }).not.toThrow();
743
+ });
744
+ });
745
+
746
+ describe('Behavioral verification', () => {
747
+ it('should render an Image when valid URL is provided', () => {
748
+ const { UNSAFE_getByType } = render(
749
+ <InboxProvider settings={mockInboxSettings}>
750
+ <UnreadIcon />
751
+ </InboxProvider>
752
+ );
753
+
754
+ // Should render an Image component when URL is provided
755
+ expect(() => UNSAFE_getByType(Image)).not.toThrow();
756
+ });
757
+
758
+ it('should render dot when image URLs are empty', () => {
759
+ const settings = {
760
+ ...mockInboxSettings,
761
+ content: {
762
+ ...mockInboxSettings.content,
763
+ unread_indicator: {
764
+ ...mockInboxSettings.content.unread_indicator,
765
+ unread_icon: {
766
+ ...mockInboxSettings.content.unread_indicator.unread_icon,
767
+ image: {
768
+ url: '',
769
+ darkUrl: '',
770
+ },
771
+ },
772
+ },
773
+ },
774
+ };
775
+
776
+ const { UNSAFE_queryByType } = render(
777
+ <InboxProvider settings={settings}>
778
+ <UnreadIcon />
779
+ </InboxProvider>
780
+ );
781
+
782
+ // Should not render Image when URLs are empty
783
+ expect(UNSAFE_queryByType(Image)).toBeNull();
784
+ });
785
+
786
+ it('should render image when source is provided even with type="dot"', () => {
787
+ const settingsWithoutImage = {
788
+ ...mockInboxSettings,
789
+ content: {
790
+ ...mockInboxSettings.content,
791
+ unread_indicator: undefined,
792
+ },
793
+ };
794
+
795
+ const { UNSAFE_getByType } = render(
796
+ <InboxProvider settings={settingsWithoutImage}>
797
+ <UnreadIcon type="dot" source={{ uri: 'https://example.com/icon.png' }} />
798
+ </InboxProvider>
799
+ );
800
+
801
+ // Should render Image when source is provided, even if type is "dot"
802
+ // The presence of source overrides the type prop
803
+ expect(() => UNSAFE_getByType(Image)).not.toThrow();
804
+ });
805
+
806
+ it('should use darkUrl in dark mode when provided', () => {
807
+ mockUseColorScheme.mockReturnValue('dark');
808
+
809
+ const settings = {
810
+ ...mockInboxSettings,
811
+ content: {
812
+ ...mockInboxSettings.content,
813
+ unread_indicator: {
814
+ ...mockInboxSettings.content.unread_indicator,
815
+ unread_icon: {
816
+ placement: 'topright' as const,
817
+ image: {
818
+ url: 'https://example.com/light.png',
819
+ darkUrl: 'https://example.com/dark.png',
820
+ },
821
+ },
822
+ },
823
+ },
824
+ };
825
+
826
+ const { UNSAFE_getByType } = render(
827
+ <InboxProvider settings={settings}>
828
+ <UnreadIcon />
829
+ </InboxProvider>
830
+ );
831
+
832
+ const imageComponent = UNSAFE_getByType(Image);
833
+ expect(imageComponent.props.source).toEqual({ uri: 'https://example.com/dark.png' });
834
+ });
835
+
836
+ it('should fallback to light URL when no darkUrl in dark mode', () => {
837
+ mockUseColorScheme.mockReturnValue('dark');
838
+
839
+ const settings = {
840
+ ...mockInboxSettings,
841
+ content: {
842
+ ...mockInboxSettings.content,
843
+ unread_indicator: {
844
+ ...mockInboxSettings.content.unread_indicator,
845
+ unread_icon: {
846
+ placement: 'topright' as const,
847
+ image: {
848
+ url: 'https://example.com/light.png',
849
+ },
850
+ },
851
+ },
852
+ },
853
+ };
854
+
855
+ const { UNSAFE_getByType } = render(
856
+ <InboxProvider settings={settings}>
857
+ <UnreadIcon />
858
+ </InboxProvider>
859
+ );
860
+
861
+ const imageComponent = UNSAFE_getByType(Image);
862
+ expect(imageComponent.props.source).toEqual({ uri: 'https://example.com/light.png' });
863
+ });
864
+
865
+ it('should use light URL in light mode', () => {
866
+ mockUseColorScheme.mockReturnValue('light');
867
+
868
+ const { UNSAFE_getByType } = render(
869
+ <InboxProvider settings={mockInboxSettings}>
870
+ <UnreadIcon />
871
+ </InboxProvider>
872
+ );
873
+
874
+ const imageComponent = UNSAFE_getByType(Image);
875
+ expect(imageComponent.props.source).toEqual({ uri: 'https://example.com/icon.png' });
876
+ });
877
+ });
878
+ });