Skip to content

Commit

Permalink
Merge pull request #673 from adobe/staging
Browse files Browse the repository at this point in the history
staging -> main (Core 3.0.1/ Lifecycle 3.0.1/ Identity 3.0.1)
  • Loading branch information
yangyansong-adbe authored May 23, 2024
2 parents 257d036 + 6b49406 commit 55d1790
Show file tree
Hide file tree
Showing 37 changed files with 1,459 additions and 342 deletions.
1 change: 1 addition & 0 deletions code/core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ dependencies {
implementation(BuildConstants.Dependencies.ANDROIDX_LIFECYCLE_KTX)

androidTestImplementation(BuildConstants.Dependencies.MOCKITO_CORE)
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.3.0")
//TODO: Consider moving this to the aep-library plugin later
androidTestImplementation("com.linkedin.dexmaker:dexmaker-mockito-inline:2.28.3")
}
4 changes: 4 additions & 0 deletions code/core/src/androidTest/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
<application>
<meta-data android:name="DeviceInfoServiceTests.testGetPropertyFromManifest.do.not.delete" android:value="this_is_a_value"/>
<activity android:name="com.adobe.marketing.mobile.internal.DataMarshallerTests$TestActivity" />
<activity
android:name="com.adobe.marketing.mobile.services.ui.RestrictedConfigActivity"
android:configChanges="orientation|screenSize"
/>
</application>
<uses-permission android:name="android.permission.INTERNET" />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.io.Serializable

@RunWith(AndroidJUnit4::class)
class DataMarshallerTests {
Expand Down Expand Up @@ -176,6 +177,23 @@ class DataMarshallerTests {

@Test
fun marshalInvalidUrl_NoCrash() {
val throwsException = ObjectThrowsOnToString()
val intent =
Intent(ApplicationProvider.getApplicationContext(), TestActivity::class.java).apply {
putExtra("key", "value")
putExtra("exceptionKey", throwsException)
}

val activity = activityTestRule.launchActivity(intent)
val result = DataMarshaller.marshal(activity)
assertEquals(
mapOf("key" to "value"),
result
)
}

@Test
fun marshal_whenBundleThrowException_NoCrash() {
val intent =
Intent(ApplicationProvider.getApplicationContext(), TestActivity::class.java).apply {
data = Uri.parse("abc:abc")
Expand All @@ -188,7 +206,11 @@ class DataMarshallerTests {
result
)
}

private class ObjectThrowsOnToString : Serializable {
override fun toString(): String {
throw IllegalStateException("This is a test exception")
}
}
companion object {
const val LEGACY_PUSH_MESSAGE_ID = "adb_m_id"
const val PUSH_MESSAGE_ID_KEY = "pushmessageid"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
Copyright 2024 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

package com.adobe.marketing.mobile.services.ui

import androidx.activity.ComponentActivity

class RestrictedConfigActivity : ComponentActivity()
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
/*
Copyright 2024 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

package com.adobe.marketing.mobile.services.ui.message.views

import android.view.View
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.test.getUnclippedBoundsInRoot
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.adobe.marketing.mobile.services.ui.RestrictedConfigActivity
import com.adobe.marketing.mobile.services.ui.common.PresentationStateManager
import com.adobe.marketing.mobile.services.ui.message.InAppMessageSettings
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

/**
* AEPPresentable handles the orientation changes of a presentable by detaching and reattaching
* the presentable from the activity by listening to Application.ActivityLifecycleCallbacks.
* However, the test cannot be isolated to listen to ApplicationLifecycle changes at the screen level.
* So, the tests in this class are done on the MessageScreen which is attached to an activity that
* restricts orientation/screen size changes. So the rotation will not destroy the activity but it
* is expected that the MessageScreen will be recomposed to fit the new screen dimensions.
* While this is not the ideal test, it is the best we can do to test the
* orientation changes of the message screen along with [MessageScreenTests.testMessageScreenIsRestoredOnConfigurationChange]
* which tests the configuration changes of the message screen.
*/
@RunWith(AndroidJUnit4::class)
class MessageScreenOrientationTests {
@get: Rule
val composeTestRule = createAndroidComposeRule<RestrictedConfigActivity>()

private var onCreatedCalled = false
private var onDisposedCalled = false
private var onBackPressed = false
private val detectedGestures = mutableListOf<InAppMessageSettings.MessageGesture>()
private val presentationStateManager = PresentationStateManager()

private val HTML_TEXT_SAMPLE = "<html>\n" +
"<head>\n" +
"<title>A Sample HTML Page</title>\n" +
"</head>\n" +
"<body>\n" +
"\n" +
"<h1>This is a sample HTML page</h1>\n" +
"\n" +
"</body>\n" +
"</html>"

// ----------------------------------------------------------------------------------------------
// Test cases for orientation changes
// ----------------------------------------------------------------------------------------------
@Test
fun testMessageScreenIsRestoredOnOrientationChange() {
val instrumentation = InstrumentationRegistry.getInstrumentation()
val uiDevice = UiDevice.getInstance(instrumentation)

val heightPercentage = 95
val widthPercentage = 60
val settings = InAppMessageSettings.Builder()
.height(heightPercentage)
.width(widthPercentage)
.content(HTML_TEXT_SAMPLE)
.build()

val screenHeightDp = mutableStateOf(0.dp)
val screenWidthDp = mutableStateOf(0.dp)
val activityHeightDp = mutableStateOf(0.dp)
val activityWidthDp = mutableStateOf(0.dp)
composeTestRule.setContent { // setting our composable as content for test
// Get the screen dimensions
val currentConfiguration = LocalConfiguration.current
screenHeightDp.value = currentConfiguration.screenHeightDp.dp
screenWidthDp.value = currentConfiguration.screenWidthDp.dp

val activityRoot =
composeTestRule.activity.window.decorView.findViewById<View>(android.R.id.content)
activityHeightDp.value = with(LocalDensity.current) { activityRoot.height.toDp() }
activityWidthDp.value = with(LocalDensity.current) { activityRoot.width.toDp() }

MessageScreen(
presentationStateManager = presentationStateManager,
inAppMessageSettings = settings,
onCreated = { onCreatedCalled = true },
onDisposed = { onDisposedCalled = true },
onGestureDetected = { gesture -> detectedGestures.add(gesture) },
onBackPressed = { onBackPressed = true }
)
}

composeTestRule.waitForIdle()
composeTestRule.onNodeWithTag(MessageTestTags.MESSAGE_FRAME).assertDoesNotExist()

// Change the state of the presentation state manager to shown to display the message
presentationStateManager.onShown()
composeTestRule.waitForIdle()
MessageScreenTestHelper.validateMessageAppeared(
composeTestRule = composeTestRule,
withBackdrop = false,
clipped = false
)

// Verify that the message content is resized to fit the screen
val contentBounds = composeTestRule.onNodeWithTag(MessageTestTags.MESSAGE_CONTENT)
.getUnclippedBoundsInRoot()
val (expectedInitialHeight, expectedInitialWidth) = calculateDimensions(
screenHeightDp.value,
screenWidthDp.value,
activityHeightDp.value,
heightPercentage,
widthPercentage
)

MessageScreenTestHelper.validateViewSize(
contentBounds,
expectedInitialHeight,
expectedInitialWidth
)

assertTrue(onCreatedCalled)
assertFalse(onDisposedCalled)
assertFalse(onBackPressed)
assertTrue(detectedGestures.isEmpty())
resetState()

// Rotate the device to landscape
uiDevice.setOrientationLandscape()

// Wait for the device to stabilize
uiDevice.waitForIdle()
composeTestRule.waitForIdle()

// Verify that the message content is resized to fit the new orientation
MessageScreenTestHelper.validateMessageAppeared(
composeTestRule = composeTestRule,
withBackdrop = false,
clipped = false
)
val landscapeContentBounds = composeTestRule.onNodeWithTag(MessageTestTags.MESSAGE_CONTENT)
.getUnclippedBoundsInRoot()
val (expectedLandscapeHeight, expectedLandscapeWidth) = calculateDimensions(
screenHeightDp.value,
screenWidthDp.value,
activityHeightDp.value,
heightPercentage,
widthPercentage
)

MessageScreenTestHelper.validateViewSize(
landscapeContentBounds,
expectedLandscapeHeight,
expectedLandscapeWidth
)

// onCreated should not be called again due to orientation change restrictions
assertFalse(onCreatedCalled)
assertFalse(onDisposedCalled)
assertFalse(onBackPressed)
assertTrue(detectedGestures.isEmpty())
resetState()

// Rotate the device back to its original orientation
uiDevice.setOrientationNatural()

// Wait for the device to stabilize
uiDevice.waitForIdle()
composeTestRule.waitForIdle()
MessageScreenTestHelper.validateMessageAppeared(
composeTestRule = composeTestRule,
withBackdrop = false,
clipped = false
)

// Verify that the message content is restored to its original size
val (expectedNaturalHeight, expectedNaturalWidth) = calculateDimensions(
screenHeightDp.value,
screenWidthDp.value,
activityHeightDp.value,
heightPercentage,
widthPercentage
)

val naturalContentBounds = composeTestRule.onNodeWithTag(MessageTestTags.MESSAGE_CONTENT)
.getUnclippedBoundsInRoot()

MessageScreenTestHelper.validateViewSize(
naturalContentBounds,
expectedNaturalHeight,
expectedNaturalWidth
)
}

/**
* Calculates the expected height and width of the message content based on the screen dimensions
* If the height exceeds what is allowed by the activity (due to actionbar), it takes
* up the full height of the activity
* @param screenHeightDp the screen height in dp
* @param screenWidthDp the screen width in dp
* @param activityHeightDp the height of the activity in dp
* @param heightPercentage the percentage of the screen height the message content should take
* @param widthPercentage the percentage of the screen width the message content should take
* @return a pair of the expected height and width of the message content
*/
private fun calculateDimensions(
screenHeightDp: Dp,
screenWidthDp: Dp,
activityHeightDp: Dp,
heightPercentage: Int,
widthPercentage: Int
): Pair<Dp, Dp> {
val expectedHeight = if ((screenHeightDp * (heightPercentage / 100f)) > activityHeightDp) {
activityHeightDp
} else {
screenHeightDp * (heightPercentage / 100f)
}
val expectedWidth = screenWidthDp * (widthPercentage / 100f)

return Pair(expectedHeight, expectedWidth)
}

private fun resetState() {
onCreatedCalled = false
onDisposedCalled = false
onBackPressed = false
detectedGestures.clear()
}

@After
fun tearDown() {
resetState()
}
}
Loading

0 comments on commit 55d1790

Please sign in to comment.