diff --git a/demo/src/androidTest/java/com/paypal/messagesdemo/XmlDemoTest.kt b/demo/src/androidTest/java/com/paypal/messagesdemo/XmlDemoTest.kt index dd16269..d21be47 100644 --- a/demo/src/androidTest/java/com/paypal/messagesdemo/XmlDemoTest.kt +++ b/demo/src/androidTest/java/com/paypal/messagesdemo/XmlDemoTest.kt @@ -1,252 +1,25 @@ package com.paypal.messagesdemo import android.view.Gravity -import android.view.View -import android.widget.TextView -import androidx.annotation.ColorInt import androidx.core.content.ContextCompat import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.UiController -import androidx.test.espresso.ViewAction -import androidx.test.espresso.action.ViewActions.clearText import androidx.test.espresso.action.ViewActions.click -import androidx.test.espresso.action.ViewActions.scrollTo -import androidx.test.espresso.action.ViewActions.typeText import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers -import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText -import androidx.test.espresso.web.assertion.WebViewAssertions -import androidx.test.espresso.web.assertion.WebViewAssertions.webMatches import androidx.test.espresso.web.sugar.Web.onWebView -import androidx.test.espresso.web.webdriver.DriverAtoms -import androidx.test.espresso.web.webdriver.Locator import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner import com.paypal.messages.R import com.paypal.messages.config.message.style.PayPalMessageAlignment import com.paypal.messages.config.message.style.PayPalMessageColor import org.hamcrest.CoreMatchers.containsString -import org.hamcrest.Description -import org.hamcrest.Matcher -import org.hamcrest.TypeSafeMatcher import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import com.paypal.messagesdemo.R as Demo -// Check Text Color -object ColorMatcher { - fun withTextColor( - @ColorInt expectedColor: Int, - ): TypeSafeMatcher { - return object : TypeSafeMatcher() { - override fun describeTo(description: Description) { - description.appendText("with text color: ") - description.appendValue(expectedColor) - } - - override fun matchesSafely(view: View): Boolean { - if (view is TextView) { - return view.currentTextColor == expectedColor - } - return false - } - } - } -} - -// Check Positioning -object GravityMatcher { - fun withGravity(expectedGravity: Int): TypeSafeMatcher { - return object : TypeSafeMatcher() { - override fun describeTo(description: Description) { - description.appendText("with gravity: ") - description.appendValue(expectedGravity) - } - - override fun matchesSafely(view: View): Boolean { - if (view is TextView) { - return (view.gravity and Gravity.HORIZONTAL_GRAVITY_MASK) == expectedGravity - } - return false - } - } - } -} - -// Custom ViewAction to wait -fun waitFor(millis: Long): ViewAction { - return object : ViewAction { - override fun getConstraints(): Matcher { - return isRoot() - } - - override fun getDescription(): String { - return "wait for $millis milliseconds" - } - - override fun perform(uiController: UiController, view: View) { - uiController.loopMainThreadForAtLeast(millis) - } - } -} - -fun waitForApp(millis: Long) { - onView(isRoot()).perform(waitFor(millis)) -} - -fun clickMessage() { - onView(withId(R.id.content)).perform(click()) - waitFor(500) -} - -fun submit() { - onView(withId(Demo.id.submit)).perform(scrollTo()) - onView(withId(Demo.id.submit)).perform(click()) - waitFor(500) -} - -fun checkPi4TilePresent() { - onWebView(ViewMatchers.withId(R.id.ModalWebView)).withElement(DriverAtoms.findElement(Locator.TAG_NAME, "body")).check( - WebViewAssertions.webMatches(DriverAtoms.getText(), containsString("Interest-free payments every 2 weeks, starting today.")), - ) -} - -fun checkPayMonthlyTilePresent() { - onWebView(ViewMatchers.withId(R.id.ModalWebView)).withElement(DriverAtoms.findElement(Locator.TAG_NAME, "body")).check( - WebViewAssertions.webMatches(DriverAtoms.getText(), containsString("Split your purchase into equal monthly payments.")), - ) -} - -fun checkNiTilePresent() { - onWebView(ViewMatchers.withId(R.id.ModalWebView)).withElement(DriverAtoms.findElement(Locator.TAG_NAME, "body")).check( - WebViewAssertions.webMatches(DriverAtoms.getText(), containsString("No Interest if paid in full in 6 months for purchases of \$99+.")), - ) -} - -fun closeButtonPresent() { - onView(withId(R.id.ModalCloseButton)).check( - matches(ViewMatchers.isDisplayed()), - ) -} - -fun clickTileByIndex(index: Int) { - onWebView(ViewMatchers.withId(R.id.ModalWebView)) - .withElement(DriverAtoms.findElement(Locator.CSS_SELECTOR, ".tile:nth-of-type($index)")) - .perform(DriverAtoms.webClick()) -} - -fun clickPi4Tile() { - clickTileByIndex(1) -} - -fun clickPayMonthlyTile() { - clickTileByIndex(2) -} - -fun clickNiTile() { - clickTileByIndex(3) -} - -fun testDisclosure() { - onWebView(ViewMatchers.withId(R.id.ModalWebView)).withElement(DriverAtoms.findElement(Locator.TAG_NAME, "body")).check( - WebViewAssertions.webMatches(DriverAtoms.getText(), containsString("Find more disclosures")), - ) -} - -fun clickSeeOtherModalOptions() { - onWebView( - ViewMatchers.withId(R.id.ModalWebView), - ).withElement(DriverAtoms.findElement(Locator.ID, "productListLink")).perform(DriverAtoms.webClick()) -} - -fun clickDisclosure() { - onWebView( - ViewMatchers.withId(R.id.ModalWebView), - ).forceJavascriptEnabled().withElement(DriverAtoms.findElement(Locator.CLASS_NAME, "a")).perform(DriverAtoms.webClick()) -} - -fun modalContent(expectedText: String) { - onWebView(ViewMatchers.withId(R.id.ModalWebView)) - .withElement(DriverAtoms.findElement(Locator.TAG_NAME, "body")) - .check(WebViewAssertions.webMatches(DriverAtoms.getText(), containsString(expectedText))) -} - -fun typeCalculatorAmount(amount: String) { - onWebView(withId(R.id.ModalWebView)) - .withElement(DriverAtoms.findElement(Locator.CSS_SELECTOR, ".input ")) // Change to your input box selector - .perform(DriverAtoms.webKeys(amount)) -} - -fun clearCalculatorAmount() { - onWebView(withId(R.id.ModalWebView)) - .withElement(DriverAtoms.findElement(Locator.CSS_SELECTOR, ".input ")) // Change to your input box selector - .perform(DriverAtoms.clearElement()) -} - -fun checkPi4ModalContent() { - modalContent("Pay in 4") -} - -fun checkPayMonthlyContent() { - modalContent("Pay Monthly") -} - -fun checkNIContent() { - modalContent("Credit") -} - -fun closeModal() { - onView(withId(R.id.ModalCloseButton)).perform(click()) -} - -fun clickOffer(offerId: Int) { - onView(withId(offerId)).perform(click()) -} - -fun clickShortTermOffer() { - clickOffer(com.paypal.messagesdemo.R.id.offerShortTerm) -} - -fun clickLongTermOffer() { - clickOffer(com.paypal.messagesdemo.R.id.offerLongTerm) -} - -fun clickPayIn1() { - clickOffer(com.paypal.messagesdemo.R.id.offerPayIn1) -} - -fun clickNIOffer() { - clickOffer(com.paypal.messagesdemo.R.id.offerCredit) -} - -fun typeAmount(text: String) { - onView(withId(com.paypal.messagesdemo.R.id.amount)).perform(typeText(text)) -} - -fun clearAmount() { - onView(withId(com.paypal.messagesdemo.R.id.amount)).perform(clearText()) -} - -fun checkMessage(text: String) { - onView(withId(R.id.content)).check(matches(withText(containsString(text)))) -} - -fun checkMessageColor(activityScenarioRule: ActivityScenarioRule<*>, color: PayPalMessageColor) { - var expectedColor: Int? = null - - // Get the actual color value from the resource ID - activityScenarioRule.scenario.onActivity { activity -> - expectedColor = ContextCompat.getColor(activity, color.colorResId) - } - - // Use the custom matcher to check the text color of the TextView - onView(withId(R.id.content)) - .check(matches(ColorMatcher.withTextColor(expectedColor!!))) -} - @RunWith(AndroidJUnit4ClassRunner::class) public class XmlDemoTest { var expectedColor: Int? = null @@ -275,7 +48,7 @@ public class XmlDemoTest { @Test fun testGenericMessageAndModal() { - waitForApp(5000) + waitForApp(2000) // Check if SecondActivity is displayed by verifying a TextView in SecondActivity checkMessage("%paypal_logo% Buy now, pay later. Learn more") @@ -290,7 +63,7 @@ public class XmlDemoTest { @Test fun testGenericModalNavigatingTiles() { - waitForApp(5000) + waitForApp(2000) clickMessage() @@ -581,19 +354,15 @@ public class XmlDemoStyleOptionsTest { @Test fun testGenericMessage() { - // Perform a delay waitForApp(500) - // Check if SecondActivity is displayed by verifying a TextView in SecondActivity checkMessage("%paypal_logo% Buy now, pay later. Learn more") onView(withId(R.id.content)).check(matches(GravityMatcher.withGravity(Gravity.LEFT))) - // Get the actual color value from the resource ID activityScenarioRule.scenario.onActivity { activity -> expectedColor = ContextCompat.getColor(activity, PayPalMessageColor.BLACK.colorResId) } - // Use the custom matcher to check the text color of the TextView onView(withId(R.id.content)) .check(matches(ColorMatcher.withTextColor(expectedColor!!))) } @@ -642,21 +411,25 @@ public class XmlDemoStyleOptionsTest { @Test fun testLogoAlignment() { - waitForApp(5000) - onView(withId(Demo.id.styleAlternative)).perform(click()) - submit() - checkMessage("%paypal_logo% Buy now, pay later. Learn more") - + waitForApp(2000) onView(withId(Demo.id.styleInline)).perform(click()) submit() + waitForApp(2000) checkMessage("Buy now, pay later with %paypal_logo%. Learn more") + onView(withId(Demo.id.styleAlternative)).perform(click()) + submit() + waitForApp(2000) + checkMessage("%paypal_logo% Buy now, pay later. Learn more") + onView(withId(Demo.id.styleNone)).perform(click()) submit() + waitForApp(2000) checkMessage("Buy now, pay later with PayPal. Learn more") onView(withId(Demo.id.stylePrimary)).perform(click()) submit() + waitForApp(2000) checkMessage("%paypal_logo% Buy now, pay later. Learn more") waitForApp(1000) } diff --git a/demo/src/androidTest/java/common/EsspressoUi.kt b/demo/src/androidTest/java/common/EsspressoUi.kt new file mode 100644 index 0000000..60c6d77 --- /dev/null +++ b/demo/src/androidTest/java/common/EsspressoUi.kt @@ -0,0 +1,243 @@ +package com.paypal.messagesdemo + +import android.view.Gravity +import android.view.View +import android.widget.TextView +import androidx.annotation.ColorInt +import androidx.core.content.ContextCompat +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.UiController +import androidx.test.espresso.ViewAction +import androidx.test.espresso.action.ViewActions.clearText +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.scrollTo +import androidx.test.espresso.action.ViewActions.typeText +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.matcher.ViewMatchers.isRoot +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.espresso.web.assertion.WebViewAssertions +import androidx.test.espresso.web.assertion.WebViewAssertions.webMatches +import androidx.test.espresso.web.sugar.Web.onWebView +import androidx.test.espresso.web.webdriver.DriverAtoms +import androidx.test.espresso.web.webdriver.Locator +import androidx.test.ext.junit.rules.ActivityScenarioRule +import com.paypal.messages.R +import com.paypal.messages.config.message.style.PayPalMessageColor +import org.hamcrest.CoreMatchers.containsString +import org.hamcrest.Description +import org.hamcrest.Matcher +import org.hamcrest.TypeSafeMatcher +import com.paypal.messagesdemo.R as Demo + +// Check Text Color +object ColorMatcher { + fun withTextColor( + @ColorInt expectedColor: Int, + ): TypeSafeMatcher { + return object : TypeSafeMatcher() { + override fun describeTo(description: Description) { + description.appendText("with text color: ") + description.appendValue(expectedColor) + } + + override fun matchesSafely(view: View): Boolean { + if (view is TextView) { + return view.currentTextColor == expectedColor + } + return false + } + } + } +} + +// Check Positioning +object GravityMatcher { + fun withGravity(expectedGravity: Int): TypeSafeMatcher { + return object : TypeSafeMatcher() { + override fun describeTo(description: Description) { + description.appendText("with gravity: ") + description.appendValue(expectedGravity) + } + + override fun matchesSafely(view: View): Boolean { + if (view is TextView) { + return (view.gravity and Gravity.HORIZONTAL_GRAVITY_MASK) == expectedGravity + } + return false + } + } + } +} + +// Custom ViewAction to wait +fun waitFor(millis: Long): ViewAction { + return object : ViewAction { + override fun getConstraints(): Matcher { + return isRoot() + } + + override fun getDescription(): String { + return "wait for $millis milliseconds" + } + + override fun perform(uiController: UiController, view: View) { + uiController.loopMainThreadForAtLeast(millis) + } + } +} + +fun waitForApp(millis: Long) { + onView(isRoot()).perform(waitFor(millis)) +} + +fun clickMessage() { + onView(withId(R.id.content)).perform(click()) + waitFor(500) +} + +fun submit() { + onView(withId(Demo.id.submit)).perform(scrollTo()) + onView(withId(Demo.id.submit)).perform(click()) + waitFor(500) +} + +fun checkPi4TilePresent() { + onWebView(ViewMatchers.withId(R.id.ModalWebView)).withElement(DriverAtoms.findElement(Locator.TAG_NAME, "body")).check( + WebViewAssertions.webMatches(DriverAtoms.getText(), containsString("Interest-free payments every 2 weeks, starting today.")), + ) +} + +fun checkPayMonthlyTilePresent() { + onWebView(ViewMatchers.withId(R.id.ModalWebView)).withElement(DriverAtoms.findElement(Locator.TAG_NAME, "body")).check( + WebViewAssertions.webMatches(DriverAtoms.getText(), containsString("Split your purchase into equal monthly payments.")), + ) +} + +fun checkNiTilePresent() { + onWebView(ViewMatchers.withId(R.id.ModalWebView)).withElement(DriverAtoms.findElement(Locator.TAG_NAME, "body")).check( + WebViewAssertions.webMatches(DriverAtoms.getText(), containsString("No Interest if paid in full in 6 months for purchases of \$99+.")), + ) +} + +fun closeButtonPresent() { + onView(withId(R.id.ModalCloseButton)).check( + matches(ViewMatchers.isDisplayed()), + ) +} + +fun clickTileByIndex(index: Int) { + onWebView(ViewMatchers.withId(R.id.ModalWebView)) + .withElement(DriverAtoms.findElement(Locator.CSS_SELECTOR, ".tile:nth-of-type($index)")) + .perform(DriverAtoms.webClick()) +} + +fun clickPi4Tile() { + clickTileByIndex(1) +} + +fun clickPayMonthlyTile() { + clickTileByIndex(2) +} + +fun clickNiTile() { + clickTileByIndex(3) +} + +fun testDisclosure() { + onWebView(ViewMatchers.withId(R.id.ModalWebView)).withElement(DriverAtoms.findElement(Locator.TAG_NAME, "body")).check( + WebViewAssertions.webMatches(DriverAtoms.getText(), containsString("Find more disclosures")), + ) +} + +fun clickSeeOtherModalOptions() { + onWebView( + ViewMatchers.withId(R.id.ModalWebView), + ).withElement(DriverAtoms.findElement(Locator.ID, "productListLink")).perform(DriverAtoms.webClick()) +} + +fun clickDisclosure() { + onWebView( + ViewMatchers.withId(R.id.ModalWebView), + ).forceJavascriptEnabled().withElement(DriverAtoms.findElement(Locator.CLASS_NAME, "a")).perform(DriverAtoms.webClick()) +} + +fun modalContent(expectedText: String) { + onWebView(ViewMatchers.withId(R.id.ModalWebView)) + .withElement(DriverAtoms.findElement(Locator.TAG_NAME, "body")) + .check(WebViewAssertions.webMatches(DriverAtoms.getText(), containsString(expectedText))) +} + +fun typeCalculatorAmount(amount: String) { + onWebView(withId(R.id.ModalWebView)) + .withElement(DriverAtoms.findElement(Locator.CSS_SELECTOR, ".input ")) // Change to your input box selector + .perform(DriverAtoms.webKeys(amount)) +} + +fun clearCalculatorAmount() { + onWebView(withId(R.id.ModalWebView)) + .withElement(DriverAtoms.findElement(Locator.CSS_SELECTOR, ".input ")) // Change to your input box selector + .perform(DriverAtoms.clearElement()) +} + +fun checkPi4ModalContent() { + modalContent("Pay in 4") +} + +fun checkPayMonthlyContent() { + modalContent("Pay Monthly") +} + +fun checkNIContent() { + modalContent("Credit") +} + +fun closeModal() { + onView(withId(R.id.ModalCloseButton)).perform(click()) +} + +fun clickOffer(offerId: Int) { + onView(withId(offerId)).perform(click()) +} + +fun clickShortTermOffer() { + clickOffer(com.paypal.messagesdemo.R.id.offerShortTerm) +} + +fun clickLongTermOffer() { + clickOffer(com.paypal.messagesdemo.R.id.offerLongTerm) +} + +fun clickPayIn1() { + clickOffer(com.paypal.messagesdemo.R.id.offerPayIn1) +} + +fun clickNIOffer() { + clickOffer(com.paypal.messagesdemo.R.id.offerCredit) +} + +fun typeAmount(text: String) { + onView(withId(com.paypal.messagesdemo.R.id.amount)).perform(typeText(text)) +} + +fun clearAmount() { + onView(withId(com.paypal.messagesdemo.R.id.amount)).perform(clearText()) +} + +fun checkMessage(text: String) { + onView(withId(R.id.content)).check(matches(withText(containsString(text)))) +} + +fun checkMessageColor(activityScenarioRule: ActivityScenarioRule<*>, color: PayPalMessageColor) { + var expectedColor: Int? = null + + // Get the actual color value from the resource ID + activityScenarioRule.scenario.onActivity { activity -> + expectedColor = ContextCompat.getColor(activity, color.colorResId) + } + + // Use the custom matcher to check the text color of the TextView + onView(withId(R.id.content)) + .check(matches(ColorMatcher.withTextColor(expectedColor!!))) +} diff --git a/library/src/main/java/com/paypal/messages/PayPalMessageView.kt b/library/src/main/java/com/paypal/messages/PayPalMessageView.kt index 5041eff..ed0d092 100644 --- a/library/src/main/java/com/paypal/messages/PayPalMessageView.kt +++ b/library/src/main/java/com/paypal/messages/PayPalMessageView.kt @@ -322,8 +322,12 @@ class PayPalMessageView @JvmOverloads constructor( override fun onDetachedFromWindow() { super.onDetachedFromWindow() // The modal will not dismiss (destroy) itself, it will only hide/show when opening and closing - // so we need to cleanup the modal instance if the message is removed - this.modal?.dismiss() + // so we need to clean up the modal instance if the message is removed + this.modal?.let { + if (it.isAdded) { + it.dismiss() + } + } } /**