@stripe/stripe-react-native 0.57.2 → 0.58.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 (147) hide show
  1. package/android/build.gradle +1 -0
  2. package/android/gradle.properties +1 -1
  3. package/android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementView.kt +0 -3
  4. package/android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementViewManager.kt +5 -3
  5. package/android/src/main/java/com/reactnativestripesdk/NavigationBarView.kt +12 -1
  6. package/android/src/main/java/com/reactnativestripesdk/PaymentElementConfig.kt +18 -0
  7. package/android/src/main/java/com/reactnativestripesdk/PaymentSheetManager.kt +136 -35
  8. package/android/src/main/java/com/reactnativestripesdk/StripeAbstractComposeView.kt +17 -5
  9. package/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt +9 -2
  10. package/android/src/main/java/com/reactnativestripesdk/customersheet/CustomerSheetManager.kt +38 -13
  11. package/android/src/main/java/com/reactnativestripesdk/utils/Mappers.kt +0 -2
  12. package/android/src/test/java/com/reactnativestripesdk/DrawableConversionPropertyTest.kt +224 -0
  13. package/android/src/test/java/com/reactnativestripesdk/DrawableConversionTest.kt +146 -0
  14. package/android/src/test/java/com/reactnativestripesdk/DrawableLoadingTest.kt +150 -0
  15. package/android/src/test/java/com/reactnativestripesdk/PaymentElementConfigTest.kt +138 -1
  16. package/android/src/test/java/com/reactnativestripesdk/PaymentOptionImageConsistencyTest.kt +186 -0
  17. package/ios/ConnectAccountOnboarding/ConnectAccountOnboardingView.swift +13 -19
  18. package/ios/StripeSdkImpl+Embedded.swift +4 -1
  19. package/ios/StripeSdkImpl+PaymentSheet.swift +28 -1
  20. package/ios/StripeSdkImpl.swift +26 -2
  21. package/jest/mock.js +6 -0
  22. package/jest/setup.js +30 -0
  23. package/lib/commonjs/components/AddToWalletButton.js +1 -1
  24. package/lib/commonjs/components/AddToWalletButton.js.map +1 -1
  25. package/lib/commonjs/components/AddressSheet.js +1 -1
  26. package/lib/commonjs/components/AddressSheet.js.map +1 -1
  27. package/lib/commonjs/components/AuBECSDebitForm.js +1 -1
  28. package/lib/commonjs/components/AuBECSDebitForm.js.map +1 -1
  29. package/lib/commonjs/components/CardField.js +1 -1
  30. package/lib/commonjs/components/CardField.js.map +1 -1
  31. package/lib/commonjs/components/CardForm.js +1 -1
  32. package/lib/commonjs/components/CardForm.js.map +1 -1
  33. package/lib/commonjs/components/PlatformPayButton.js +1 -1
  34. package/lib/commonjs/components/PlatformPayButton.js.map +1 -1
  35. package/lib/commonjs/components/StripeContainer.js +1 -1
  36. package/lib/commonjs/components/StripeContainer.js.map +1 -1
  37. package/lib/commonjs/connect/Components.js +1 -1
  38. package/lib/commonjs/connect/Components.js.map +1 -1
  39. package/lib/commonjs/connect/ConnectComponentsProvider.js +1 -1
  40. package/lib/commonjs/connect/ConnectComponentsProvider.js.map +1 -1
  41. package/lib/commonjs/connect/EmbeddedComponent.js +9 -4
  42. package/lib/commonjs/connect/EmbeddedComponent.js.map +1 -1
  43. package/lib/commonjs/connect/ModalCloseButton.js +1 -1
  44. package/lib/commonjs/connect/ModalCloseButton.js.map +1 -1
  45. package/lib/commonjs/connect/NavigationBar.js +1 -1
  46. package/lib/commonjs/connect/NavigationBar.js.map +1 -1
  47. package/lib/commonjs/helpers.js +1 -1
  48. package/lib/commonjs/hooks/useOnramp.js +1 -1
  49. package/lib/commonjs/hooks/useOnramp.js.map +1 -1
  50. package/lib/commonjs/specs/NativeAddToWalletButton.js +1 -1
  51. package/lib/commonjs/specs/NativeAddressSheet.js +1 -1
  52. package/lib/commonjs/specs/NativeApplePayButton.js +1 -1
  53. package/lib/commonjs/specs/NativeAuBECSDebitForm.js +1 -1
  54. package/lib/commonjs/specs/NativeCardField.js +1 -1
  55. package/lib/commonjs/specs/NativeCardField.js.map +1 -1
  56. package/lib/commonjs/specs/NativeCardForm.js +1 -1
  57. package/lib/commonjs/specs/NativeCardForm.js.map +1 -1
  58. package/lib/commonjs/specs/NativeConnectAccountOnboardingView.js +1 -1
  59. package/lib/commonjs/specs/NativeEmbeddedPaymentElement.js +1 -1
  60. package/lib/commonjs/specs/NativeEmbeddedPaymentElement.js.map +1 -1
  61. package/lib/commonjs/specs/NativeGooglePayButton.js +1 -1
  62. package/lib/commonjs/specs/NativeNavigationBar.js +1 -1
  63. package/lib/commonjs/specs/NativeStripeContainer.js +1 -1
  64. package/lib/commonjs/types/EmbeddedPaymentElement.js +1 -1
  65. package/lib/commonjs/types/EmbeddedPaymentElement.js.map +1 -1
  66. package/lib/commonjs/types/FinancialConnections.js.map +1 -1
  67. package/lib/commonjs/types/PaymentSheet.js +1 -1
  68. package/lib/commonjs/types/PaymentSheet.js.map +1 -1
  69. package/lib/module/components/AddToWalletButton.js +1 -1
  70. package/lib/module/components/AddToWalletButton.js.map +1 -1
  71. package/lib/module/components/AddressSheet.js +1 -1
  72. package/lib/module/components/AddressSheet.js.map +1 -1
  73. package/lib/module/components/AuBECSDebitForm.js +1 -1
  74. package/lib/module/components/AuBECSDebitForm.js.map +1 -1
  75. package/lib/module/components/CardField.js +1 -1
  76. package/lib/module/components/CardField.js.map +1 -1
  77. package/lib/module/components/CardForm.js +1 -1
  78. package/lib/module/components/CardForm.js.map +1 -1
  79. package/lib/module/components/PlatformPayButton.js +1 -1
  80. package/lib/module/components/PlatformPayButton.js.map +1 -1
  81. package/lib/module/components/StripeContainer.js +1 -1
  82. package/lib/module/components/StripeContainer.js.map +1 -1
  83. package/lib/module/connect/Components.js +1 -1
  84. package/lib/module/connect/Components.js.map +1 -1
  85. package/lib/module/connect/ConnectComponentsProvider.js +1 -1
  86. package/lib/module/connect/ConnectComponentsProvider.js.map +1 -1
  87. package/lib/module/connect/EmbeddedComponent.js +9 -4
  88. package/lib/module/connect/EmbeddedComponent.js.map +1 -1
  89. package/lib/module/connect/ModalCloseButton.js +1 -1
  90. package/lib/module/connect/ModalCloseButton.js.map +1 -1
  91. package/lib/module/connect/NavigationBar.js +1 -1
  92. package/lib/module/connect/NavigationBar.js.map +1 -1
  93. package/lib/module/helpers.js +1 -1
  94. package/lib/module/hooks/useOnramp.js +1 -1
  95. package/lib/module/hooks/useOnramp.js.map +1 -1
  96. package/lib/module/specs/NativeAddToWalletButton.js +1 -1
  97. package/lib/module/specs/NativeAddressSheet.js +1 -1
  98. package/lib/module/specs/NativeApplePayButton.js +1 -1
  99. package/lib/module/specs/NativeAuBECSDebitForm.js +1 -1
  100. package/lib/module/specs/NativeCardField.js +1 -1
  101. package/lib/module/specs/NativeCardField.js.map +1 -1
  102. package/lib/module/specs/NativeCardForm.js +1 -1
  103. package/lib/module/specs/NativeCardForm.js.map +1 -1
  104. package/lib/module/specs/NativeConnectAccountOnboardingView.js +1 -1
  105. package/lib/module/specs/NativeEmbeddedPaymentElement.js +1 -1
  106. package/lib/module/specs/NativeEmbeddedPaymentElement.js.map +1 -1
  107. package/lib/module/specs/NativeGooglePayButton.js +1 -1
  108. package/lib/module/specs/NativeNavigationBar.js +1 -1
  109. package/lib/module/specs/NativeStripeContainer.js +1 -1
  110. package/lib/module/types/EmbeddedPaymentElement.js +1 -1
  111. package/lib/module/types/EmbeddedPaymentElement.js.map +1 -1
  112. package/lib/module/types/FinancialConnections.js.map +1 -1
  113. package/lib/module/types/PaymentSheet.js +1 -1
  114. package/lib/module/types/PaymentSheet.js.map +1 -1
  115. package/lib/typescript/src/connect/Components.d.ts.map +1 -1
  116. package/lib/typescript/src/connect/EmbeddedComponent.d.ts.map +1 -1
  117. package/lib/typescript/src/connect/connectTypes.d.ts +5 -1
  118. package/lib/typescript/src/connect/connectTypes.d.ts.map +1 -1
  119. package/lib/typescript/src/hooks/useOnramp.d.ts +2 -1
  120. package/lib/typescript/src/hooks/useOnramp.d.ts.map +1 -1
  121. package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts +6 -1
  122. package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts.map +1 -1
  123. package/lib/typescript/src/types/FinancialConnections.d.ts +2 -0
  124. package/lib/typescript/src/types/FinancialConnections.d.ts.map +1 -1
  125. package/lib/typescript/src/types/PaymentSheet.d.ts +30 -0
  126. package/lib/typescript/src/types/PaymentSheet.d.ts.map +1 -1
  127. package/package.json +15 -5
  128. package/src/connect/Components.tsx +18 -11
  129. package/src/connect/EmbeddedComponent.tsx +223 -12
  130. package/src/connect/connectTypes.ts +5 -1
  131. package/src/hooks/useOnramp.tsx +5 -1
  132. package/src/types/EmbeddedPaymentElement.tsx +6 -1
  133. package/src/types/FinancialConnections.ts +2 -0
  134. package/src/types/PaymentSheet.ts +32 -0
  135. package/stripe-react-native.podspec +1 -1
  136. package/android/.idea/AndroidProjectSystem.xml +0 -6
  137. package/android/.idea/caches/deviceStreaming.xml +0 -1029
  138. package/android/.idea/compiler.xml +0 -6
  139. package/android/.idea/gradle.xml +0 -19
  140. package/android/.idea/migrations.xml +0 -10
  141. package/android/.idea/misc.xml +0 -10
  142. package/android/.idea/runConfigurations.xml +0 -17
  143. package/android/.idea/vcs.xml +0 -6
  144. package/android/local.properties +0 -8
  145. package/ios/StripeSdk.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  146. package/ios/StripeSdk.xcodeproj/project.xcworkspace/xcuserdata/tianzhao.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  147. package/ios/StripeSdk.xcodeproj/xcuserdata/tianzhao.xcuserdatad/xcschemes/xcschememanagement.plist +0 -19
@@ -0,0 +1,224 @@
1
+ package com.reactnativestripesdk
2
+
3
+ import android.graphics.Color
4
+ import android.graphics.drawable.ColorDrawable
5
+ import kotlinx.coroutines.test.runTest
6
+ import org.junit.Assert.assertEquals
7
+ import org.junit.Assert.assertNotEquals
8
+ import org.junit.Assert.assertNotNull
9
+ import org.junit.Assert.assertTrue
10
+ import org.junit.Test
11
+ import org.junit.runner.RunWith
12
+ import org.robolectric.RobolectricTestRunner
13
+ import kotlin.random.Random
14
+
15
+ /**
16
+ * Property-based tests for drawable conversion.
17
+ * These tests verify mathematical properties that should always hold true.
18
+ */
19
+ @RunWith(RobolectricTestRunner::class)
20
+ class DrawableConversionPropertyTest {
21
+ @Test
22
+ fun `PROPERTY - conversion is idempotent`() =
23
+ runTest {
24
+ // Property: Converting the same drawable multiple times yields same result
25
+ repeat(20) {
26
+ val color =
27
+ Color.rgb(
28
+ Random.nextInt(256),
29
+ Random.nextInt(256),
30
+ Random.nextInt(256),
31
+ )
32
+ val width = Random.nextInt(50, 200)
33
+ val height = Random.nextInt(50, 200)
34
+
35
+ val drawable =
36
+ ColorDrawable(color).apply {
37
+ setBounds(0, 0, width, height)
38
+ }
39
+
40
+ val result1 = convertDrawableToBase64(drawable)
41
+ val result2 = convertDrawableToBase64(drawable)
42
+
43
+ assertEquals(
44
+ "Same drawable should always produce same base64 (iteration $it, ${width}x$height)",
45
+ result1,
46
+ result2,
47
+ )
48
+ }
49
+ }
50
+
51
+ @Test
52
+ fun `PROPERTY - different drawables produce different base64`() =
53
+ runTest {
54
+ val drawable1 =
55
+ ColorDrawable(Color.RED).apply {
56
+ setBounds(0, 0, 100, 100)
57
+ }
58
+ val drawable2 =
59
+ ColorDrawable(Color.BLUE).apply {
60
+ setBounds(0, 0, 100, 100)
61
+ }
62
+
63
+ val result1 = convertDrawableToBase64(drawable1)
64
+ val result2 = convertDrawableToBase64(drawable2)
65
+
66
+ assertNotEquals(
67
+ "Different colored drawables should produce different base64",
68
+ result1,
69
+ result2,
70
+ )
71
+ }
72
+
73
+ @Test
74
+ fun `PROPERTY - base64 length scales with drawable size`() =
75
+ runTest {
76
+ val small =
77
+ ColorDrawable(Color.RED).apply {
78
+ setBounds(0, 0, 50, 50)
79
+ }
80
+ val medium =
81
+ ColorDrawable(Color.RED).apply {
82
+ setBounds(0, 0, 100, 100)
83
+ }
84
+ val large =
85
+ ColorDrawable(Color.RED).apply {
86
+ setBounds(0, 0, 200, 200)
87
+ }
88
+
89
+ val smallResult = convertDrawableToBase64(small)
90
+ val mediumResult = convertDrawableToBase64(medium)
91
+ val largeResult = convertDrawableToBase64(large)
92
+
93
+ assertNotNull(smallResult)
94
+ assertNotNull(mediumResult)
95
+ assertNotNull(largeResult)
96
+
97
+ assertTrue(
98
+ "Medium drawable should produce longer base64 than small",
99
+ mediumResult!!.length > smallResult!!.length,
100
+ )
101
+ assertTrue(
102
+ "Large drawable should produce longer base64 than medium",
103
+ largeResult!!.length > mediumResult.length,
104
+ )
105
+ }
106
+
107
+ @Test
108
+ fun `PROPERTY - conversion preserves drawable dimensions`() =
109
+ runTest {
110
+ val testSizes =
111
+ listOf(
112
+ Pair(50, 50),
113
+ Pair(100, 75),
114
+ Pair(147, 105), // Typical card icon
115
+ Pair(200, 150),
116
+ Pair(250, 200),
117
+ )
118
+
119
+ testSizes.forEach { (width, height) ->
120
+ val drawable =
121
+ ColorDrawable(Color.GREEN).apply {
122
+ setBounds(0, 0, width, height)
123
+ }
124
+
125
+ val bitmap = getBitmapFromDrawable(drawable)
126
+
127
+ assertNotNull("Bitmap should not be null for ${width}x$height", bitmap)
128
+ assertEquals("Width should be preserved for ${width}x$height", width, bitmap!!.width)
129
+ assertEquals("Height should be preserved for ${width}x$height", height, bitmap.height)
130
+ }
131
+ }
132
+
133
+ @Test
134
+ fun `PROPERTY - null input produces null output consistently`() =
135
+ runTest {
136
+ // Invalid drawables (no bounds set) should consistently return null
137
+ val invalidDrawable1 = ColorDrawable(Color.RED)
138
+ val invalidDrawable2 = ColorDrawable(Color.BLUE)
139
+
140
+ val result1 = convertDrawableToBase64(invalidDrawable1)
141
+ val result2 = convertDrawableToBase64(invalidDrawable2)
142
+
143
+ assertNotNull("Result1 should be null for invalid drawable", result1 == null)
144
+ assertNotNull("Result2 should be null for invalid drawable", result2 == null)
145
+ }
146
+
147
+ @Test
148
+ fun `PROPERTY - aspect ratio is preserved in bitmap`() {
149
+ val testCases =
150
+ listOf(
151
+ Triple(100, 100, 1.0), // Square
152
+ Triple(200, 100, 2.0), // 2:1
153
+ Triple(147, 105, 1.4), // Card icon ratio
154
+ Triple(100, 200, 0.5), // Portrait
155
+ )
156
+
157
+ testCases.forEach { (width, height, expectedRatio) ->
158
+ val drawable =
159
+ ColorDrawable(Color.YELLOW).apply {
160
+ setBounds(0, 0, width, height)
161
+ }
162
+
163
+ val bitmap = getBitmapFromDrawable(drawable)
164
+
165
+ assertNotNull(bitmap)
166
+ val actualRatio = bitmap!!.width.toDouble() / bitmap.height.toDouble()
167
+ assertEquals(
168
+ "Aspect ratio should be preserved for ${width}x$height",
169
+ expectedRatio,
170
+ actualRatio,
171
+ 0.01, // Allow small floating point error
172
+ )
173
+ }
174
+ }
175
+
176
+ @Test
177
+ fun `PROPERTY - conversion is deterministic across multiple runs`() =
178
+ runTest {
179
+ // Same drawable, converted multiple times in sequence, should always yield same result
180
+ val drawable =
181
+ ColorDrawable(Color.CYAN).apply {
182
+ setBounds(0, 0, 120, 90)
183
+ }
184
+
185
+ val results = (1..15).map { convertDrawableToBase64(drawable) }
186
+
187
+ // All results should be identical
188
+ val firstResult = results.first()
189
+ results.forEach { result ->
190
+ assertEquals("All conversions should be identical", firstResult, result)
191
+ }
192
+ }
193
+
194
+ @Test
195
+ fun `PROPERTY - different sizes with same color produce different base64`() =
196
+ runTest {
197
+ val color = Color.rgb(100, 150, 200)
198
+
199
+ val sizes =
200
+ listOf(
201
+ Pair(50, 50),
202
+ Pair(75, 75),
203
+ Pair(100, 100),
204
+ Pair(125, 125),
205
+ )
206
+
207
+ val results =
208
+ sizes.map { (width, height) ->
209
+ val drawable =
210
+ ColorDrawable(color).apply {
211
+ setBounds(0, 0, width, height)
212
+ }
213
+ convertDrawableToBase64(drawable)
214
+ }
215
+
216
+ // All results should be different (different sizes → different images)
217
+ val uniqueResults = results.toSet()
218
+ assertEquals(
219
+ "Different sizes should produce different base64 even with same color",
220
+ results.size,
221
+ uniqueResults.size,
222
+ )
223
+ }
224
+ }
@@ -0,0 +1,146 @@
1
+ package com.reactnativestripesdk
2
+
3
+ import android.graphics.Bitmap
4
+ import android.graphics.Color
5
+ import android.graphics.drawable.ColorDrawable
6
+ import kotlinx.coroutines.test.runTest
7
+ import org.junit.Assert.assertEquals
8
+ import org.junit.Assert.assertNotNull
9
+ import org.junit.Assert.assertNull
10
+ import org.junit.Assert.assertTrue
11
+ import org.junit.Test
12
+ import org.junit.runner.RunWith
13
+ import org.robolectric.RobolectricTestRunner
14
+
15
+ @RunWith(RobolectricTestRunner::class)
16
+ class DrawableConversionTest {
17
+ // ============================================
18
+ // convertDrawableToBase64 Tests
19
+ // ============================================
20
+
21
+ @Test
22
+ fun `convertDrawableToBase64 returns non-null for valid drawable`() =
23
+ runTest {
24
+ val drawable =
25
+ ColorDrawable(Color.RED).apply {
26
+ setBounds(0, 0, 100, 100)
27
+ }
28
+
29
+ val result = convertDrawableToBase64(drawable)
30
+
31
+ assertNotNull("Base64 should not be null for valid drawable", result)
32
+ assertTrue("Base64 should not be empty", result!!.isNotEmpty())
33
+ }
34
+
35
+ @Test
36
+ fun `convertDrawableToBase64 returns consistent results for same drawable`() =
37
+ runTest {
38
+ val drawable =
39
+ ColorDrawable(Color.BLUE).apply {
40
+ setBounds(0, 0, 100, 100)
41
+ }
42
+
43
+ val result1 = convertDrawableToBase64(drawable)
44
+ val result2 = convertDrawableToBase64(drawable)
45
+ val result3 = convertDrawableToBase64(drawable)
46
+
47
+ assertEquals("Multiple calls should return identical base64", result1, result2)
48
+ assertEquals("Multiple calls should return identical base64", result2, result3)
49
+ }
50
+
51
+ @Test
52
+ fun `convertDrawableToBase64 returns null for invalid drawable`() =
53
+ runTest {
54
+ // Drawable with 0 intrinsic size (never set bounds)
55
+ val drawable = ColorDrawable(Color.RED)
56
+
57
+ val result = convertDrawableToBase64(drawable)
58
+
59
+ assertNull("Base64 should be null for invalid drawable", result)
60
+ }
61
+
62
+ @Test
63
+ fun `convertDrawableToBase64 result is valid base64 format`() =
64
+ runTest {
65
+ val drawable =
66
+ ColorDrawable(Color.GREEN).apply {
67
+ setBounds(0, 0, 50, 50)
68
+ }
69
+
70
+ val result = convertDrawableToBase64(drawable)
71
+
72
+ assertNotNull(result)
73
+ // Valid base64 should match pattern: [A-Za-z0-9+/=]+
74
+ assertTrue(
75
+ "Result should be valid base64",
76
+ result!!.matches(Regex("^[A-Za-z0-9+/=\\n]+$")),
77
+ )
78
+ }
79
+
80
+ @Test
81
+ fun `convertDrawableToBase64 result size is reasonable`() =
82
+ runTest {
83
+ val drawable =
84
+ ColorDrawable(Color.YELLOW).apply {
85
+ setBounds(0, 0, 100, 100)
86
+ }
87
+
88
+ val result = convertDrawableToBase64(drawable)
89
+
90
+ assertNotNull(result)
91
+ // Should be larger than tiny placeholder (134 bytes)
92
+ // but smaller than unreasonably large
93
+ assertTrue(
94
+ "Base64 should be larger than 200 chars (not a 1x1 placeholder)",
95
+ result!!.length > 200,
96
+ )
97
+ assertTrue(
98
+ "Base64 should be smaller than 100KB",
99
+ result.length < 100_000,
100
+ )
101
+ }
102
+
103
+ // ============================================
104
+ // getBitmapFromDrawable Tests
105
+ // ============================================
106
+
107
+ @Test
108
+ fun `getBitmapFromDrawable returns correct size bitmap`() {
109
+ val drawable =
110
+ ColorDrawable(Color.CYAN).apply {
111
+ setBounds(0, 0, 150, 100)
112
+ }
113
+
114
+ val bitmap = getBitmapFromDrawable(drawable)
115
+
116
+ assertNotNull("Bitmap should not be null", bitmap)
117
+ assertEquals("Bitmap width should match drawable", 150, bitmap!!.width)
118
+ assertEquals("Bitmap height should match drawable", 100, bitmap.height)
119
+ assertEquals("Bitmap should use ARGB_8888", Bitmap.Config.ARGB_8888, bitmap.config)
120
+ }
121
+
122
+ @Test
123
+ fun `getBitmapFromDrawable returns null for zero-size drawable`() {
124
+ val drawable = ColorDrawable(Color.MAGENTA)
125
+ // Don't set bounds, intrinsic size will be -1
126
+
127
+ val bitmap = getBitmapFromDrawable(drawable)
128
+
129
+ assertNull("Bitmap should be null for invalid drawable", bitmap)
130
+ }
131
+
132
+ @Test
133
+ fun `getBitmapFromDrawable preserves drawable dimensions`() {
134
+ // Test with typical card icon dimensions
135
+ val drawable =
136
+ ColorDrawable(Color.RED).apply {
137
+ setBounds(0, 0, 147, 105)
138
+ }
139
+
140
+ val bitmap = getBitmapFromDrawable(drawable)
141
+
142
+ assertNotNull(bitmap)
143
+ assertEquals("Width should be preserved", 147, bitmap!!.width)
144
+ assertEquals("Height should be preserved", 105, bitmap.height)
145
+ }
146
+ }
@@ -0,0 +1,150 @@
1
+ package com.reactnativestripesdk
2
+
3
+ import android.graphics.Canvas
4
+ import android.graphics.Color
5
+ import android.graphics.ColorFilter
6
+ import android.graphics.PixelFormat
7
+ import android.graphics.drawable.Drawable
8
+ import kotlinx.coroutines.delay
9
+ import kotlinx.coroutines.launch
10
+ import kotlinx.coroutines.test.runTest
11
+ import org.junit.Assert.assertNotNull
12
+ import org.junit.Assert.assertTrue
13
+ import org.junit.Test
14
+ import org.junit.runner.RunWith
15
+ import org.robolectric.RobolectricTestRunner
16
+
17
+ @RunWith(RobolectricTestRunner::class)
18
+ class DrawableLoadingTest {
19
+ /**
20
+ * Mock drawable that simulates DelegateDrawable's async loading behavior
21
+ */
22
+ class MockAsyncDrawable : Drawable() {
23
+ private var loaded = false
24
+ private var width = 1
25
+ private var height = 1
26
+
27
+ override fun getIntrinsicWidth(): Int = width
28
+
29
+ override fun getIntrinsicHeight(): Int = height
30
+
31
+ suspend fun simulateLoading(delayMs: Long = 100) {
32
+ delay(delayMs)
33
+ width = 150
34
+ height = 100
35
+ loaded = true
36
+ invalidateSelf() // Trigger callback
37
+ }
38
+
39
+ override fun draw(canvas: Canvas) {
40
+ // Draw a simple colored rectangle
41
+ canvas.drawColor(Color.RED)
42
+ }
43
+
44
+ override fun setAlpha(alpha: Int) {}
45
+
46
+ override fun setColorFilter(colorFilter: ColorFilter?) {}
47
+
48
+ override fun getOpacity(): Int = PixelFormat.OPAQUE
49
+ }
50
+
51
+ @Test
52
+ fun `waitForDrawableToLoad returns immediately for already loaded drawable`() =
53
+ runTest {
54
+ val drawable = MockAsyncDrawable()
55
+ // Pre-load the drawable
56
+ drawable.simulateLoading(0)
57
+
58
+ val startTime = System.currentTimeMillis()
59
+ val result = waitForDrawableToLoad(drawable, timeoutMs = 3000)
60
+ val elapsed = System.currentTimeMillis() - startTime
61
+
62
+ assertNotNull(result)
63
+ assertTrue("Should return quickly (< 100ms)", elapsed < 100)
64
+ assertTrue("Drawable should be loaded", result.intrinsicWidth > 1)
65
+ }
66
+
67
+ @Test
68
+ fun `waitForDrawableToLoad waits for drawable to load`() =
69
+ runTest {
70
+ val drawable = MockAsyncDrawable()
71
+
72
+ // Start async loading
73
+ launch {
74
+ drawable.simulateLoading(100)
75
+ }
76
+
77
+ val result = waitForDrawableToLoad(drawable, timeoutMs = 3000)
78
+
79
+ assertNotNull(result)
80
+ assertTrue("Drawable should be loaded after waiting", result.intrinsicWidth > 1)
81
+ assertTrue("Drawable should be loaded after waiting", result.intrinsicHeight > 1)
82
+ assertTrue("Width should be 150", result.intrinsicWidth == 150)
83
+ assertTrue("Height should be 100", result.intrinsicHeight == 100)
84
+ }
85
+
86
+ @Test
87
+ fun `waitForDrawableToLoad times out gracefully for drawable that never loads`() =
88
+ runTest {
89
+ val drawable = MockAsyncDrawable() // Never call simulateLoading()
90
+
91
+ val result = waitForDrawableToLoad(drawable, timeoutMs = 500)
92
+
93
+ // Should return the drawable even if timeout (best effort)
94
+ assertNotNull(result)
95
+ }
96
+
97
+ @Test
98
+ fun `convertDrawableToBase64 with async drawable returns consistent results`() =
99
+ runTest {
100
+ val drawable1 = MockAsyncDrawable()
101
+ val drawable2 = MockAsyncDrawable()
102
+
103
+ // Simulate both loading
104
+ launch { drawable1.simulateLoading(50) }
105
+ launch { drawable2.simulateLoading(100) }
106
+
107
+ val result1 = convertDrawableToBase64(drawable1)
108
+ val result2 = convertDrawableToBase64(drawable2)
109
+
110
+ assertNotNull("Result1 should not be null", result1)
111
+ assertNotNull("Result2 should not be null", result2)
112
+
113
+ // Both should be non-empty and similar length (same dimensions)
114
+ assertTrue("Both results should be non-empty", result1!!.isNotEmpty())
115
+ assertTrue("Both results should be non-empty", result2!!.isNotEmpty())
116
+
117
+ // They should be similar length since both are 150x100
118
+ val lengthDiff = kotlin.math.abs(result1.length - result2.length)
119
+ assertTrue(
120
+ "Results should have similar length (both 150x100), diff: $lengthDiff",
121
+ lengthDiff < result1.length * 0.1, // Within 10%
122
+ )
123
+ }
124
+
125
+ @Test
126
+ fun `waitForDrawableToLoad handles multiple concurrent calls`() =
127
+ runTest {
128
+ val drawable1 = MockAsyncDrawable()
129
+ val drawable2 = MockAsyncDrawable()
130
+ val drawable3 = MockAsyncDrawable()
131
+
132
+ // Start all loading at the same time
133
+ launch { drawable1.simulateLoading(50) }
134
+ launch { drawable2.simulateLoading(75) }
135
+ launch { drawable3.simulateLoading(100) }
136
+
137
+ // Wait for all concurrently
138
+ val result1 = waitForDrawableToLoad(drawable1, timeoutMs = 3000)
139
+ val result2 = waitForDrawableToLoad(drawable2, timeoutMs = 3000)
140
+ val result3 = waitForDrawableToLoad(drawable3, timeoutMs = 3000)
141
+
142
+ assertNotNull(result1)
143
+ assertNotNull(result2)
144
+ assertNotNull(result3)
145
+
146
+ assertTrue("All drawables should be loaded", result1.intrinsicWidth > 1)
147
+ assertTrue("All drawables should be loaded", result2.intrinsicWidth > 1)
148
+ assertTrue("All drawables should be loaded", result3.intrinsicWidth > 1)
149
+ }
150
+ }
@@ -4,16 +4,18 @@ import com.reactnativestripesdk.utils.PaymentSheetException
4
4
  import com.reactnativestripesdk.utils.readableArrayOf
5
5
  import com.reactnativestripesdk.utils.readableMapOf
6
6
  import com.stripe.android.paymentelement.PaymentMethodOptionsSetupFutureUsagePreview
7
+ import com.stripe.android.paymentsheet.CardFundingFilteringPrivatePreview
7
8
  import com.stripe.android.paymentsheet.PaymentSheet
8
9
  import org.junit.Assert.assertEquals
9
10
  import org.junit.Assert.assertNotNull
10
11
  import org.junit.Assert.assertNull
12
+ import org.junit.Assert.assertTrue
11
13
  import org.junit.Test
12
14
  import org.junit.runner.RunWith
13
15
  import org.robolectric.RobolectricTestRunner
14
16
 
15
17
  @RunWith(RobolectricTestRunner::class)
16
- @OptIn(PaymentMethodOptionsSetupFutureUsagePreview::class)
18
+ @OptIn(PaymentMethodOptionsSetupFutureUsagePreview::class, CardFundingFilteringPrivatePreview::class)
17
19
  class PaymentElementConfigTest {
18
20
  // ============================================
19
21
  // buildIntentConfiguration Tests
@@ -973,4 +975,139 @@ class PaymentElementConfigTest {
973
975
  val result = buildBillingDetailsCollectionConfiguration(params)
974
976
  assertNotNull(result)
975
977
  }
978
+
979
+ // ============================================
980
+ // mapToAllowedCardFundingTypes Tests
981
+ // ============================================
982
+
983
+ @Test
984
+ fun mapToAllowedCardFundingTypes_NullParams_ReturnsNull() {
985
+ val result = mapToAllowedCardFundingTypes(null)
986
+ assertNull(result)
987
+ }
988
+
989
+ @Test
990
+ fun mapToAllowedCardFundingTypes_NoCardFundingFiltering_ReturnsNull() {
991
+ val params =
992
+ readableMapOf(
993
+ "someOtherKey" to "value",
994
+ )
995
+ val result = mapToAllowedCardFundingTypes(params)
996
+ assertNull(result)
997
+ }
998
+
999
+ @Test
1000
+ fun mapToAllowedCardFundingTypes_DebitOnly_ReturnsList() {
1001
+ val params =
1002
+ readableMapOf(
1003
+ "cardFundingFiltering" to
1004
+ readableMapOf(
1005
+ "allowedCardFundingTypes" to readableArrayOf("debit"),
1006
+ ),
1007
+ )
1008
+ val result = mapToAllowedCardFundingTypes(params)
1009
+ assertNotNull(result)
1010
+ assertEquals(1, result?.size)
1011
+ assertEquals(PaymentSheet.CardFundingType.Debit, result?.get(0))
1012
+ }
1013
+
1014
+ @Test
1015
+ fun mapToAllowedCardFundingTypes_CreditOnly_ReturnsList() {
1016
+ val params =
1017
+ readableMapOf(
1018
+ "cardFundingFiltering" to
1019
+ readableMapOf(
1020
+ "allowedCardFundingTypes" to readableArrayOf("credit"),
1021
+ ),
1022
+ )
1023
+ val result = mapToAllowedCardFundingTypes(params)
1024
+ assertNotNull(result)
1025
+ assertEquals(1, result?.size)
1026
+ assertEquals(PaymentSheet.CardFundingType.Credit, result?.get(0))
1027
+ }
1028
+
1029
+ @Test
1030
+ fun mapToAllowedCardFundingTypes_MultipleTypes_ReturnsList() {
1031
+ val params =
1032
+ readableMapOf(
1033
+ "cardFundingFiltering" to
1034
+ readableMapOf(
1035
+ "allowedCardFundingTypes" to readableArrayOf("debit", "credit", "prepaid"),
1036
+ ),
1037
+ )
1038
+ val result = mapToAllowedCardFundingTypes(params)
1039
+ assertNotNull(result)
1040
+ assertEquals(3, result?.size)
1041
+ assertTrue(result!!.contains(PaymentSheet.CardFundingType.Debit))
1042
+ assertTrue(result.contains(PaymentSheet.CardFundingType.Credit))
1043
+ assertTrue(result.contains(PaymentSheet.CardFundingType.Prepaid))
1044
+ }
1045
+
1046
+ @Test
1047
+ fun mapToAllowedCardFundingTypes_AllFourTypes_ReturnsList() {
1048
+ val params =
1049
+ readableMapOf(
1050
+ "cardFundingFiltering" to
1051
+ readableMapOf(
1052
+ "allowedCardFundingTypes" to readableArrayOf("debit", "credit", "prepaid", "unknown"),
1053
+ ),
1054
+ )
1055
+ val result = mapToAllowedCardFundingTypes(params)
1056
+ assertNotNull(result)
1057
+ assertEquals(4, result?.size)
1058
+ }
1059
+
1060
+ @Test
1061
+ fun mapToAllowedCardFundingTypes_EmptyArray_ReturnsNull() {
1062
+ val params =
1063
+ readableMapOf(
1064
+ "cardFundingFiltering" to
1065
+ readableMapOf(
1066
+ "allowedCardFundingTypes" to readableArrayOf(),
1067
+ ),
1068
+ )
1069
+ val result = mapToAllowedCardFundingTypes(params)
1070
+ assertNull(result)
1071
+ }
1072
+
1073
+ @Test
1074
+ fun mapToAllowedCardFundingTypes_InvalidTypes_Filtered() {
1075
+ val params =
1076
+ readableMapOf(
1077
+ "cardFundingFiltering" to
1078
+ readableMapOf(
1079
+ "allowedCardFundingTypes" to readableArrayOf("invalid", "debit", "not_a_type"),
1080
+ ),
1081
+ )
1082
+ val result = mapToAllowedCardFundingTypes(params)
1083
+ assertNotNull(result)
1084
+ assertEquals(1, result?.size)
1085
+ assertEquals(PaymentSheet.CardFundingType.Debit, result?.get(0))
1086
+ }
1087
+
1088
+ @Test
1089
+ fun mapToAllowedCardFundingTypes_OnlyInvalidTypes_ReturnsNull() {
1090
+ val params =
1091
+ readableMapOf(
1092
+ "cardFundingFiltering" to
1093
+ readableMapOf(
1094
+ "allowedCardFundingTypes" to readableArrayOf("invalid", "not_valid"),
1095
+ ),
1096
+ )
1097
+ val result = mapToAllowedCardFundingTypes(params)
1098
+ assertNull(result)
1099
+ }
1100
+
1101
+ @Test
1102
+ fun mapToAllowedCardFundingTypes_MissingAllowedCardFundingTypes_ReturnsNull() {
1103
+ val params =
1104
+ readableMapOf(
1105
+ "cardFundingFiltering" to
1106
+ readableMapOf(
1107
+ "someOtherKey" to "value",
1108
+ ),
1109
+ )
1110
+ val result = mapToAllowedCardFundingTypes(params)
1111
+ assertNull(result)
1112
+ }
976
1113
  }