diff --git a/lib/src/main/java/com/shopify/checkoutkit/CheckoutEventProcessor.kt b/lib/src/main/java/com/shopify/checkoutkit/CheckoutEventProcessor.kt index e174b9a4..118294ae 100644 --- a/lib/src/main/java/com/shopify/checkoutkit/CheckoutEventProcessor.kt +++ b/lib/src/main/java/com/shopify/checkoutkit/CheckoutEventProcessor.kt @@ -114,14 +114,17 @@ internal class NoopEventProcessor : CheckoutEventProcessor { * for handling checkout events and interacting with the Android operating system. * @param context from which we will launch intents. */ -public abstract class DefaultCheckoutEventProcessor(private val context: Context) : CheckoutEventProcessor { +public abstract class DefaultCheckoutEventProcessor( + private val context: Context, + private val log: LogWrapper = LogWrapper(), +) : CheckoutEventProcessor { override fun onCheckoutLinkClicked(uri: Uri) { when (uri.scheme) { "tel" -> context.launchPhoneApp(uri.schemeSpecificPart) "mailto" -> context.launchEmailApp(uri.schemeSpecificPart) "https", "http" -> context.launchBrowser(uri) - else -> println("Unrecognized scheme for uri $uri") + else -> log.w(TAG, "Unrecognized scheme for link clicked in checkout '$uri'") } } @@ -142,4 +145,8 @@ public abstract class DefaultCheckoutEventProcessor(private val context: Context val intent = Intent(Intent.ACTION_DIAL, Uri.fromParts("tel", phone, null)) startActivity(intent) } + + private companion object { + private const val TAG = "DefaultCheckoutEventProcessor" + } } diff --git a/lib/src/main/java/com/shopify/checkoutkit/LogWrapper.kt b/lib/src/main/java/com/shopify/checkoutkit/LogWrapper.kt new file mode 100644 index 00000000..776fd1b0 --- /dev/null +++ b/lib/src/main/java/com/shopify/checkoutkit/LogWrapper.kt @@ -0,0 +1,38 @@ +/* + * MIT License + * + * Copyright 2023-present, Shopify Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.shopify.checkoutkit + +import android.util.Log + +/** + * Wrap Log class static methods to allow testing + */ +public class LogWrapper { + public fun w(tag: String, msg: String) { + Log.w(tag, msg) + } + + public fun e(tag: String, msg: String) { + Log.e(tag, msg) + } +} diff --git a/lib/src/test/java/com/shopify/checkoutkit/DefaultCheckoutEventProcessorTest.kt b/lib/src/test/java/com/shopify/checkoutkit/DefaultCheckoutEventProcessorTest.kt new file mode 100644 index 00000000..c0f688d1 --- /dev/null +++ b/lib/src/test/java/com/shopify/checkoutkit/DefaultCheckoutEventProcessorTest.kt @@ -0,0 +1,111 @@ +/* + * MIT License + * + * Copyright 2023-present, Shopify Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.shopify.checkoutkit + +import android.content.Intent +import android.net.Uri +import androidx.activity.ComponentActivity +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.robolectric.Robolectric +import org.robolectric.RobolectricTestRunner +import org.robolectric.Shadows.shadowOf +import org.robolectric.shadows.ShadowActivity + +@RunWith(RobolectricTestRunner::class) +class DefaultCheckoutEventProcessorTest { + + private lateinit var activity: ComponentActivity + private lateinit var shadowActivity: ShadowActivity + + @Before + fun setUp() { + activity = Robolectric.buildActivity(ComponentActivity::class.java).get() + shadowActivity = shadowOf(activity) + } + + @Test + fun `onCheckoutLinkClicked with http scheme launches action view intent with uri as data`() { + val processor = processor(activity) + val uri = Uri.parse("https://shopify.com") + + processor.onCheckoutLinkClicked(uri) + + val intent = shadowActivity.peekNextStartedActivityForResult().intent + assertThat(intent.data).isEqualTo(uri) + intent.type = Intent.ACTION_VIEW + } + + @Test + fun `onCheckoutLinkClicked with mailto scheme launches email intent with to address`() { + val processor = processor(activity) + val uri = Uri.parse("mailto:test.user@shopify.com") + + processor.onCheckoutLinkClicked(uri) + + val intent = shadowActivity.peekNextStartedActivityForResult().intent + assertThat(intent.getStringArrayExtra(Intent.EXTRA_EMAIL)).isEqualTo(arrayOf("test.user@shopify.com")) + intent.type = "vnd.android.cursor.item/email" + } + + @Test + fun `onCheckoutLinkClicked with tel scheme launches action dial intent with phone number`() { + val processor = processor(activity) + val uri = Uri.parse("tel:0123456789") + + processor.onCheckoutLinkClicked(uri) + + val intent = shadowActivity.peekNextStartedActivityForResult().intent + assertThat(intent.data).isEqualTo(uri) + intent.type = Intent.ACTION_DIAL + } + + @Test + fun `onCheckoutLinkedClick with unhandled scheme logs warning`() { + val log = mock() + val processor = object: DefaultCheckoutEventProcessor(activity, log) { + override fun onCheckoutCompleted() {/* not implemented */} + override fun onCheckoutFailed(error: CheckoutException) {/* not implemented */} + override fun onCheckoutCanceled() {/* not implemented */} + } + + val uri = Uri.parse("ftp:lsklsm") + + processor.onCheckoutLinkClicked(uri) + + assertThat(shadowActivity.peekNextStartedActivityForResult()).isNull() + verify(log).w("DefaultCheckoutEventProcessor", "Unrecognized scheme for link clicked in checkout 'ftp:lsklsm'") + } + + private fun processor(activity: ComponentActivity): DefaultCheckoutEventProcessor { + return object: DefaultCheckoutEventProcessor(activity) { + override fun onCheckoutCompleted() {/* not implemented */} + override fun onCheckoutFailed(error: CheckoutException) {/* not implemented */} + override fun onCheckoutCanceled() {/* not implemented */} + } + } +}