@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,496 @@
1
+ /*
2
+ Copyright 2026 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the
4
+ "License"); you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+ http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
7
+ or agreed to in writing, software distributed under the License is
8
+ distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF
9
+ ANY KIND, either express or implied. See the License for the specific
10
+ language governing permissions and limitations under the License.
11
+ */
12
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react-native';
13
+ import React from 'react';
14
+ import { Linking } from 'react-native';
15
+ import { ThemeProvider } from '../../theme/ThemeProvider';
16
+ import Button from './Button';
17
+
18
+ // Mock Linking
19
+ jest.spyOn(Linking, 'openURL');
20
+ jest.spyOn(Linking, 'canOpenURL');
21
+
22
+ // Helper to render with theme
23
+ const renderWithTheme = (component: React.ReactElement, customThemes?: any) => {
24
+ return render(
25
+ <ThemeProvider customThemes={customThemes}>{component}</ThemeProvider>
26
+ );
27
+ };
28
+
29
+ describe('Button', () => {
30
+ const mockOnPress = jest.fn();
31
+
32
+ beforeEach(() => {
33
+ jest.clearAllMocks();
34
+ (Linking.canOpenURL as unknown as jest.Mock).mockResolvedValue(true as any);
35
+ (Linking.openURL as unknown as jest.Mock).mockResolvedValue(undefined as any);
36
+ });
37
+
38
+ afterEach(() => {
39
+ jest.clearAllMocks();
40
+ });
41
+
42
+ describe('Basic rendering', () => {
43
+ it('should render a button with the provided title', () => {
44
+ render(<Button title="Click Me" />);
45
+
46
+ const button = screen.getByText('Click Me');
47
+ expect(button).toBeTruthy();
48
+ });
49
+
50
+ it('should render with different title text', () => {
51
+ render(<Button title="Submit" />);
52
+
53
+ expect(screen.getByText('Submit')).toBeTruthy();
54
+ });
55
+
56
+ it('should render button as Pressable component', () => {
57
+ const { getByTestId } = render(
58
+ <Button title="Test" testID="test-button" />
59
+ );
60
+
61
+ const button = getByTestId('test-button');
62
+ expect(button.type).toBe('View'); // Pressable renders as View
63
+ });
64
+ });
65
+
66
+ describe('Press handling', () => {
67
+ it('should call onPress when the button is pressed', () => {
68
+ render(<Button title="Press Me" onPress={mockOnPress} />);
69
+
70
+ const button = screen.getByText('Press Me');
71
+ fireEvent.press(button);
72
+
73
+ expect(mockOnPress).toHaveBeenCalledTimes(1);
74
+ });
75
+
76
+ it('should call onPress with interactId when provided', () => {
77
+ render(
78
+ <Button
79
+ title="Press Me"
80
+ onPress={mockOnPress}
81
+ interactId="test-interact-id"
82
+ />
83
+ );
84
+
85
+ const button = screen.getByText('Press Me');
86
+ fireEvent.press(button);
87
+
88
+ // First argument should be interactId; second (event) may be undefined in tests
89
+ expect(mockOnPress).toHaveBeenCalled();
90
+ expect((mockOnPress.mock.calls[0] ?? [])[0]).toBe('test-interact-id');
91
+ });
92
+
93
+ it('should call onPress multiple times when pressed multiple times', () => {
94
+ render(<Button title="Press Me" onPress={mockOnPress} />);
95
+
96
+ const button = screen.getByText('Press Me');
97
+ fireEvent.press(button);
98
+ fireEvent.press(button);
99
+ fireEvent.press(button);
100
+
101
+ expect(mockOnPress).toHaveBeenCalledTimes(3);
102
+ });
103
+
104
+ it('should not throw error when onPress is not provided', () => {
105
+ render(<Button title="Press Me" />);
106
+
107
+ const button = screen.getByText('Press Me');
108
+ expect(() => fireEvent.press(button)).not.toThrow();
109
+ });
110
+ });
111
+
112
+ describe('actionUrl handling', () => {
113
+ it('should open URL when actionUrl is provided and button is pressed', async () => {
114
+ const testUrl = 'https://example.com';
115
+ render(<Button title="Link" actionUrl={testUrl} />);
116
+
117
+ const button = screen.getByText('Link');
118
+ fireEvent.press(button);
119
+
120
+ await waitFor(() =>
121
+ expect(Linking.openURL).toHaveBeenCalledWith(testUrl)
122
+ );
123
+ });
124
+
125
+ it('should call both onPress and open URL when both are provided', async () => {
126
+ const testUrl = 'https://example.com';
127
+ render(
128
+ <Button title="Link" actionUrl={testUrl} onPress={mockOnPress} />
129
+ );
130
+
131
+ const button = screen.getByText('Link');
132
+ fireEvent.press(button);
133
+
134
+ expect(mockOnPress).toHaveBeenCalledTimes(1);
135
+ await waitFor(() =>
136
+ expect(Linking.openURL).toHaveBeenCalledWith(testUrl)
137
+ );
138
+ });
139
+
140
+ it('should call onPress with interactId and open URL', async () => {
141
+ const testUrl = 'https://example.com';
142
+ render(
143
+ <Button
144
+ title="Link"
145
+ actionUrl={testUrl}
146
+ onPress={mockOnPress}
147
+ interactId="link-interact-id"
148
+ />
149
+ );
150
+
151
+ const button = screen.getByText('Link');
152
+ fireEvent.press(button);
153
+
154
+ expect(mockOnPress).toHaveBeenCalled();
155
+ expect((mockOnPress.mock.calls[0] ?? [])[0]).toBe('link-interact-id');
156
+ await waitFor(() =>
157
+ expect(Linking.openURL).toHaveBeenCalledWith(testUrl)
158
+ );
159
+ });
160
+
161
+ it('should call openURL even if it might fail', async () => {
162
+ const testUrl = 'https://example.com';
163
+ render(<Button title="Link" actionUrl={testUrl} />);
164
+
165
+ const button = screen.getByText('Link');
166
+ fireEvent.press(button);
167
+
168
+ // Verify the URL opening was attempted
169
+ await waitFor(() =>
170
+ expect(Linking.openURL).toHaveBeenCalledWith(testUrl)
171
+ );
172
+ });
173
+
174
+ it('should warn if openURL throws', async () => {
175
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
176
+ const testUrl = 'https://example.com';
177
+ (Linking.canOpenURL as unknown as jest.Mock).mockResolvedValueOnce(true as any);
178
+ (Linking.openURL as unknown as jest.Mock).mockImplementationOnce(
179
+ () => Promise.reject(new Error('open failed')) as any
180
+ );
181
+
182
+ render(<Button title="Link" actionUrl={testUrl} />);
183
+ const button = screen.getByText('Link');
184
+
185
+ expect(() => fireEvent.press(button)).not.toThrow();
186
+ await waitFor(() =>
187
+ expect(warnSpy).toHaveBeenCalledWith(
188
+ expect.stringContaining(`Failed to open URL: ${testUrl}`),
189
+ expect.any(Error)
190
+ )
191
+ );
192
+
193
+ warnSpy.mockRestore();
194
+ });
195
+
196
+ it('should not try to open URL when actionUrl is not provided', () => {
197
+ render(<Button title="No Link" onPress={mockOnPress} />);
198
+
199
+ const button = screen.getByText('No Link');
200
+ fireEvent.press(button);
201
+
202
+ expect(Linking.openURL).not.toHaveBeenCalled();
203
+ });
204
+
205
+ it('should not open URL if canOpenURL returns false', () => {
206
+ (Linking.canOpenURL as unknown as jest.Mock).mockResolvedValueOnce(false as any);
207
+ const testUrl = 'https://example.com';
208
+ render(<Button title="Link" actionUrl={testUrl} />);
209
+ const button = screen.getByText('Link');
210
+ fireEvent.press(button);
211
+ expect(Linking.openURL).not.toHaveBeenCalled();
212
+ });
213
+ });
214
+
215
+ describe('Theme support', () => {
216
+ it('should apply theme button text color when theme is provided', () => {
217
+ const customThemes = {
218
+ light: {
219
+ colors: {
220
+ buttonTextColor: '#FF5733'
221
+ }
222
+ },
223
+ dark: {
224
+ colors: {
225
+ buttonTextColor: '#FF5733'
226
+ }
227
+ }
228
+ };
229
+
230
+ renderWithTheme(<Button title="Themed" />, customThemes);
231
+
232
+ const text = screen.getByText('Themed');
233
+ expect(text.props.style).toEqual(
234
+ expect.arrayContaining([
235
+ expect.objectContaining({
236
+ color: '#FF5733'
237
+ })
238
+ ])
239
+ );
240
+ });
241
+
242
+ it('should handle theme with undefined buttonTextColor', () => {
243
+ const customThemes = {
244
+ light: {
245
+ colors: {}
246
+ },
247
+ dark: {
248
+ colors: {}
249
+ }
250
+ };
251
+
252
+ renderWithTheme(<Button title="Default" />, customThemes);
253
+
254
+ const text = screen.getByText('Default');
255
+ expect(text).toBeTruthy();
256
+ });
257
+
258
+ it('should render without theme provider', () => {
259
+ expect(() => {
260
+ render(<Button title="No Theme" />);
261
+ }).not.toThrow();
262
+ });
263
+ });
264
+
265
+ describe('Custom styles', () => {
266
+ it('should accept and apply custom textStyle', () => {
267
+ const customTextStyle = { fontSize: 20, fontWeight: 'bold' as const };
268
+ render(<Button title="Styled" textStyle={customTextStyle} />);
269
+
270
+ const text = screen.getByText('Styled');
271
+ expect(text.props.style).toEqual(
272
+ expect.arrayContaining([
273
+ expect.objectContaining({
274
+ fontSize: 20,
275
+ fontWeight: 'bold'
276
+ })
277
+ ])
278
+ );
279
+ });
280
+
281
+ it('should accept and apply array of textStyles', () => {
282
+ const textStyles = [
283
+ { fontSize: 16 },
284
+ { fontWeight: 'bold' as const },
285
+ { color: 'blue' }
286
+ ];
287
+ render(<Button title="Multi Style" textStyle={textStyles} />);
288
+
289
+ const text = screen.getByText('Multi Style');
290
+ const styles = JSON.stringify(text.props.style);
291
+ expect(styles).toContain('"fontSize":16');
292
+ expect(styles).toContain('"fontWeight":"bold"');
293
+ expect(styles).toContain('"color":"blue"');
294
+ });
295
+
296
+ it('should accept and apply custom Pressable style', () => {
297
+ const customStyle = { padding: 10, backgroundColor: 'red' };
298
+ const { getByTestId } = render(
299
+ <Button title="Styled Button" style={customStyle} testID="button" />
300
+ );
301
+
302
+ const button = getByTestId('button');
303
+ expect(button.props.style).toEqual(customStyle);
304
+ });
305
+
306
+ it('should merge theme color with custom textStyle', () => {
307
+ const customThemes = {
308
+ light: {
309
+ colors: {
310
+ buttonTextColor: '#FF5733'
311
+ }
312
+ },
313
+ dark: {
314
+ colors: {
315
+ buttonTextColor: '#FF5733'
316
+ }
317
+ }
318
+ };
319
+ const customTextStyle = { fontSize: 18 };
320
+
321
+ renderWithTheme(
322
+ <Button title="Merged" textStyle={customTextStyle} />,
323
+ customThemes
324
+ );
325
+
326
+ const text = screen.getByText('Merged');
327
+ expect(text.props.style).toEqual(
328
+ expect.arrayContaining([
329
+ expect.objectContaining({
330
+ color: '#FF5733'
331
+ }),
332
+ expect.objectContaining({
333
+ fontSize: 18
334
+ })
335
+ ])
336
+ );
337
+ });
338
+ });
339
+
340
+ describe('Additional Pressable props', () => {
341
+ it('should accept disabled prop', () => {
342
+ const { getByTestId } = render(
343
+ <Button title="Disabled" disabled={true} testID="button" />
344
+ );
345
+
346
+ const button = getByTestId('button');
347
+ expect(button.props.accessibilityState.disabled).toBe(true);
348
+ });
349
+
350
+ it('should accept accessibility props', () => {
351
+ const { getByTestId } = render(
352
+ <Button
353
+ title="Accessible"
354
+ testID="button"
355
+ accessibilityLabel="Submit form"
356
+ accessibilityHint="Double tap to submit the form"
357
+ />
358
+ );
359
+
360
+ const button = getByTestId('button');
361
+ expect(button.props.accessibilityLabel).toBe('Submit form');
362
+ expect(button.props.accessibilityHint).toBe(
363
+ 'Double tap to submit the form'
364
+ );
365
+ });
366
+
367
+ it('should accept testID prop', () => {
368
+ const { getByTestId } = render(
369
+ <Button title="Test ID" testID="my-button" />
370
+ );
371
+
372
+ const button = getByTestId('my-button');
373
+ expect(button).toBeTruthy();
374
+ });
375
+
376
+ it('should spread additional Pressable props', () => {
377
+ const { getByTestId } = render(
378
+ <Button
379
+ title="Extended"
380
+ testID="button"
381
+ hitSlop={10}
382
+ accessibilityRole="button"
383
+ />
384
+ );
385
+
386
+ const button = getByTestId('button');
387
+ expect(button.props.hitSlop).toBe(10);
388
+ expect(button.props.accessibilityRole).toBe('button');
389
+ });
390
+ });
391
+
392
+ describe('ID prop', () => {
393
+ it('should accept id prop without error', () => {
394
+ expect(() => {
395
+ render(<Button title="With ID" id="button-123" />);
396
+ }).not.toThrow();
397
+ });
398
+
399
+ it('should render correctly with id prop', () => {
400
+ render(<Button title="ID Button" id="unique-id" />);
401
+
402
+ const button = screen.getByText('ID Button');
403
+ expect(button).toBeTruthy();
404
+ });
405
+ });
406
+
407
+ describe('Edge cases', () => {
408
+ it('should handle empty title string', () => {
409
+ render(<Button title="" />);
410
+
411
+ // Should not throw, but may not be visible
412
+ expect(() => screen.getByText('')).not.toThrow();
413
+ });
414
+
415
+ it('should handle very long title text', () => {
416
+ const longTitle = 'A'.repeat(1000);
417
+ render(<Button title={longTitle} />);
418
+
419
+ const button = screen.getByText(longTitle);
420
+ expect(button).toBeTruthy();
421
+ });
422
+
423
+ it('should handle special characters in title', () => {
424
+ const specialTitle = '!@#$%^&*()_+-={}[]|:;<>?,./~`';
425
+ render(<Button title={specialTitle} />);
426
+
427
+ const button = screen.getByText(specialTitle);
428
+ expect(button).toBeTruthy();
429
+ });
430
+
431
+ it('should handle undefined interactId', () => {
432
+ render(<Button title="Test" onPress={mockOnPress} interactId={undefined} />);
433
+
434
+ const button = screen.getByText('Test');
435
+ fireEvent.press(button);
436
+
437
+ expect(mockOnPress).toHaveBeenCalled();
438
+ expect((mockOnPress.mock.calls[0] ?? [])[0]).toBeUndefined();
439
+ });
440
+
441
+ it('should handle empty string as actionUrl', () => {
442
+ render(<Button title="Empty URL" actionUrl="" />);
443
+
444
+ const button = screen.getByText('Empty URL');
445
+ fireEvent.press(button);
446
+
447
+ // Empty string is falsy, so openURL should not be called
448
+ expect(Linking.openURL).not.toHaveBeenCalled();
449
+ });
450
+ });
451
+
452
+ describe('Callback stability', () => {
453
+ it('should maintain stable callback reference', async () => {
454
+ const { rerender } = render(
455
+ <Button title="Stable" onPress={mockOnPress} actionUrl="https://example.com" />
456
+ );
457
+
458
+ const button = screen.getByText('Stable');
459
+ fireEvent.press(button);
460
+
461
+ expect(mockOnPress).toHaveBeenCalledTimes(1);
462
+ await waitFor(() => expect(Linking.openURL).toHaveBeenCalledTimes(1));
463
+
464
+ // Rerender with same props
465
+ rerender(
466
+ <Button title="Stable" onPress={mockOnPress} actionUrl="https://example.com" />
467
+ );
468
+
469
+ fireEvent.press(button);
470
+
471
+ expect(mockOnPress).toHaveBeenCalledTimes(2);
472
+ await waitFor(() => expect(Linking.openURL).toHaveBeenCalledTimes(2));
473
+ });
474
+
475
+ it('should update callback when dependencies change', () => {
476
+ const { rerender } = render(
477
+ <Button title="Dynamic" onPress={mockOnPress} interactId="id-1" />
478
+ );
479
+
480
+ const button = screen.getByText('Dynamic');
481
+ fireEvent.press(button);
482
+
483
+ expect(mockOnPress).toHaveBeenCalled();
484
+ expect((mockOnPress.mock.calls[0] ?? [])[0]).toBe('id-1');
485
+
486
+ // Rerender with different interactId
487
+ rerender(
488
+ <Button title="Dynamic" onPress={mockOnPress} interactId="id-2" />
489
+ );
490
+
491
+ fireEvent.press(button);
492
+
493
+ expect((mockOnPress.mock.calls[1] ?? [])[0]).toBe('id-2');
494
+ });
495
+ });
496
+ });
@@ -0,0 +1,76 @@
1
+ /*
2
+ Copyright 2026 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the
4
+ "License"); you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+ http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
7
+ or agreed to in writing, software distributed under the License is
8
+ distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF
9
+ ANY KIND, either express or implied. See the License for the specific
10
+ language governing permissions and limitations under the License.
11
+ */
12
+ import React, { useCallback } from 'react';
13
+ import {
14
+ AccessibilityRole,
15
+ GestureResponderEvent,
16
+ Linking,
17
+ Pressable,
18
+ PressableProps,
19
+ StyleProp,
20
+ Text,
21
+ TextStyle
22
+ } from 'react-native';
23
+ import { useTheme } from '../../theme/ThemeProvider';
24
+
25
+ export interface ButtonProps extends Omit<PressableProps, 'onPress'> {
26
+ actionUrl?: string;
27
+ id?: string;
28
+ title: string;
29
+ onPress?: (interactId?: string, event?: GestureResponderEvent) => void;
30
+ interactId?: string;
31
+ textStyle?: StyleProp<TextStyle>;
32
+ accessibilityRole?: AccessibilityRole;
33
+ }
34
+
35
+ const Button: React.FC<ButtonProps> = ({
36
+ actionUrl,
37
+ title,
38
+ onPress,
39
+ interactId,
40
+ textStyle,
41
+ style,
42
+ accessibilityRole = 'button',
43
+ ...props
44
+ }) => {
45
+ const { colors } = useTheme();
46
+ const handlePress = useCallback(async (event: GestureResponderEvent) => {
47
+ onPress?.(interactId, event);
48
+ if (actionUrl) {
49
+ try {
50
+ const supported = await Linking.canOpenURL(actionUrl);
51
+ if (supported) {
52
+ await Linking.openURL(actionUrl);
53
+ } else {
54
+ console.warn(`Cannot open URL: ${actionUrl}`);
55
+ }
56
+ } catch (error) {
57
+ console.warn(`Failed to open URL: ${actionUrl}`, error);
58
+ }
59
+ }
60
+ }, [actionUrl, interactId, onPress]);
61
+
62
+ return (
63
+ <Pressable
64
+ accessibilityRole={accessibilityRole}
65
+ onPress={handlePress}
66
+ style={style}
67
+ {...props}
68
+ >
69
+ <Text style={[{ color: colors?.buttonTextColor }, textStyle]}>
70
+ {title}
71
+ </Text>
72
+ </Pressable>
73
+ );
74
+ };
75
+
76
+ export default Button;