From bc2d7112ccbc2f98c4d34c559edd35ba12645ec1 Mon Sep 17 00:00:00 2001 From: "Pietro F. Maggi" Date: Fri, 16 Jul 2021 18:28:52 +0200 Subject: [PATCH 1/2] Add Espresso tests for Activities using Jetpack WindowManager --- WindowManager/app/build.gradle | 9 ++ .../DisplayFeaturesActivityTest.kt | 141 ++++++++++++++++++ .../SplitLayoutActivityTest.kt | 122 +++++++++++++++ .../windowmanagersample/TestActivity.kt | 24 --- .../windowmanagersample/SampleTools.kt | 18 ++- 5 files changed, 282 insertions(+), 32 deletions(-) create mode 100644 WindowManager/app/src/androidTest/java/com/example/windowmanagersample/DisplayFeaturesActivityTest.kt create mode 100644 WindowManager/app/src/androidTest/java/com/example/windowmanagersample/SplitLayoutActivityTest.kt delete mode 100644 WindowManager/app/src/androidTest/java/com/example/windowmanagersample/TestActivity.kt diff --git a/WindowManager/app/build.gradle b/WindowManager/app/build.gradle index 4426c8656..4d3028ee4 100644 --- a/WindowManager/app/build.gradle +++ b/WindowManager/app/build.gradle @@ -34,10 +34,19 @@ android { kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8.toString() + + // Allow usage of Kotlin's @OptIn. + freeCompilerArgs += '-Xopt-in=kotlin.RequiresOptIn' } buildFeatures { viewBinding = true } + + // Workaround for https://issuetracker.google.com/161465530 + packagingOptions { + pickFirst 'META-INF/AL2.0' + pickFirst 'META-INF/LGPL2.1' + } } dependencies { diff --git a/WindowManager/app/src/androidTest/java/com/example/windowmanagersample/DisplayFeaturesActivityTest.kt b/WindowManager/app/src/androidTest/java/com/example/windowmanagersample/DisplayFeaturesActivityTest.kt new file mode 100644 index 000000000..b39ad61ed --- /dev/null +++ b/WindowManager/app/src/androidTest/java/com/example/windowmanagersample/DisplayFeaturesActivityTest.kt @@ -0,0 +1,141 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed 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 + * + * https://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 CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.windowmanagersample + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withSubstring +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.window.FoldingFeature.Orientation.Companion.HORIZONTAL +import androidx.window.FoldingFeature.Orientation.Companion.VERTICAL +import androidx.window.FoldingFeature.State.Companion.FLAT +import androidx.window.FoldingFeature.State.Companion.HALF_OPENED +import androidx.window.WindowLayoutInfo +import androidx.window.testing.FoldingFeature +import androidx.window.testing.WindowLayoutInfoPublisherRule +import androidx.window.windowInfoRepository +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async +import kotlinx.coroutines.flow.take +import kotlinx.coroutines.flow.toCollection +import kotlinx.coroutines.test.TestCoroutineScope +import kotlinx.coroutines.test.runBlockingTest +import org.junit.Assert.assertEquals +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class DisplayFeaturesActivityTest { + private val activityRule = ActivityScenarioRule(DisplayFeaturesActivity::class.java) + private val publisherRule = WindowLayoutInfoPublisherRule() + + @OptIn(ExperimentalCoroutinesApi::class) + private val testScope = TestCoroutineScope() + + @get:Rule + val testRule: TestRule + + init { + testRule = RuleChain.outerRule(publisherRule).around(activityRule) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun testDeviceOpen_Flat(): Unit = testScope.runBlockingTest { + activityRule.scenario.onActivity { activity -> + val feature = FoldingFeature( + activity = activity, + state = FLAT, + orientation = HORIZONTAL + ) + val expected = WindowLayoutInfo.Builder().setDisplayFeatures(listOf(feature)).build() + + val values = mutableListOf() + val value = testScope.async { + activity.windowInfoRepository().windowLayoutInfo.take(1).toCollection(values) + } + publisherRule.overrideWindowLayoutInfo(expected) + runBlockingTest { + val newValues = value.await().toList() + assertEquals( + listOf(expected), + newValues + ) + } + } + onView(withId(R.id.state_update_log)).check(matches(withSubstring("state=FLAT"))) + onView(withId(R.id.current_state)).check(matches(withSubstring("is not separated"))) + onView(withId(R.id.current_state)).check(matches(withSubstring("Hinge is horizontal"))) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun testDeviceOpen_TableTop(): Unit = testScope.runBlockingTest { + activityRule.scenario.onActivity { activity -> + val feature = + FoldingFeature(activity = activity, state = HALF_OPENED, orientation = HORIZONTAL) + val expected = WindowLayoutInfo.Builder().setDisplayFeatures(listOf(feature)).build() + + val values = mutableListOf() + val value = testScope.async { + activity.windowInfoRepository().windowLayoutInfo.take(1).toCollection(values) + } + publisherRule.overrideWindowLayoutInfo(expected) + runBlockingTest { + val newValues = value.await().toList() + assertEquals( + listOf(expected), + newValues + ) + } + } + onView(withId(R.id.state_update_log)).check(matches(withSubstring("state=HALF_OPENED"))) + onView(withId(R.id.current_state)).check(matches(withSubstring("are separated"))) + onView(withId(R.id.current_state)).check(matches(withSubstring("Hinge is horizontal"))) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun testDeviceOpen_Book(): Unit = testScope.runBlockingTest { + activityRule.scenario.onActivity { activity -> + val feature = + FoldingFeature(activity = activity, state = HALF_OPENED, orientation = VERTICAL) + val expected = WindowLayoutInfo.Builder().setDisplayFeatures(listOf(feature)).build() + + val values = mutableListOf() + val value = testScope.async { + activity.windowInfoRepository().windowLayoutInfo.take(1).toCollection(values) + } + publisherRule.overrideWindowLayoutInfo(expected) + runBlockingTest { + val newValues = value.await().toList() + assertEquals( + listOf(expected), + newValues + ) + } + } + onView(withId(R.id.state_update_log)).check(matches(withSubstring("state=HALF_OPENED"))) + onView(withId(R.id.current_state)).check(matches(withSubstring("are separated"))) + onView(withId(R.id.current_state)).check(matches(withSubstring("Hinge is vertical"))) + } +} diff --git a/WindowManager/app/src/androidTest/java/com/example/windowmanagersample/SplitLayoutActivityTest.kt b/WindowManager/app/src/androidTest/java/com/example/windowmanagersample/SplitLayoutActivityTest.kt new file mode 100644 index 000000000..12f0ba11f --- /dev/null +++ b/WindowManager/app/src/androidTest/java/com/example/windowmanagersample/SplitLayoutActivityTest.kt @@ -0,0 +1,122 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed 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 + * + * https://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 CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.windowmanagersample + +import android.graphics.Rect +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.window.FoldingFeature +import androidx.window.FoldingFeature.State.Companion.HALF_OPENED +import androidx.window.FoldingFeature.Type.Companion.FOLD +import androidx.window.WindowLayoutInfo +import androidx.window.testing.WindowLayoutInfoPublisherRule +import androidx.window.windowInfoRepository +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.take +import kotlinx.coroutines.flow.toCollection +import kotlinx.coroutines.test.TestCoroutineScope +import kotlinx.coroutines.test.runBlockingTest +import org.junit.Assert +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SplitLayoutActivityTest { + private val activityRule = ActivityScenarioRule(SplitLayoutActivity::class.java) + private val publisherRule = WindowLayoutInfoPublisherRule() + + @OptIn(ExperimentalCoroutinesApi::class) + private val testScope = TestCoroutineScope() + + @get:Rule + val testRule: TestRule + + init { + testRule = RuleChain.outerRule(publisherRule).around(activityRule) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun testDeviceOpen_Vertical(): Unit = testScope.runBlockingTest { + activityRule.scenario.onActivity { activity -> + val bounds = activity.windowInfoRepository().currentWindowMetrics.bounds + val center = bounds.centerX() + val feature = FoldingFeature( + Rect(center, 0, center, bounds.height()), + FOLD, + HALF_OPENED + ) + val expected = WindowLayoutInfo.Builder().setDisplayFeatures(listOf(feature)).build() + + val values = mutableListOf() + val value = testScope.async { + activity.windowInfoRepository().windowLayoutInfo.take(1).toCollection(values) + } + publisherRule.overrideWindowLayoutInfo(expected) + runBlockingTest { + val newValues = value.await().toList() + Assert.assertEquals( + listOf(expected), + newValues + ) + delay(5000) + } + } + onView(withId(R.id.start_layout)).check(matches(isDisplayed())) + onView(withId(R.id.end_layout)).check(matches(isDisplayed())) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun testDeviceOpen_Horizontal(): Unit = testScope.runBlockingTest { + activityRule.scenario.onActivity { activity -> + val bounds = activity.windowInfoRepository().currentWindowMetrics.bounds + val center = bounds.centerY() + val feature = FoldingFeature( + Rect(0, center, bounds.height(), center), + FOLD, + HALF_OPENED + ) + val expected = WindowLayoutInfo.Builder().setDisplayFeatures(listOf(feature)).build() + + val values = mutableListOf() + val value = testScope.async { + activity.windowInfoRepository().windowLayoutInfo.take(1).toCollection(values) + } + publisherRule.overrideWindowLayoutInfo(expected) + runBlockingTest { + val newValues = value.await().toList() + Assert.assertEquals( + listOf(expected), + newValues + ) + delay(5000) + } + } + onView(withId(R.id.start_layout)).check(matches(isDisplayed())) + onView(withId(R.id.end_layout)).check(matches(isDisplayed())) + } +} diff --git a/WindowManager/app/src/androidTest/java/com/example/windowmanagersample/TestActivity.kt b/WindowManager/app/src/androidTest/java/com/example/windowmanagersample/TestActivity.kt deleted file mode 100644 index 88f661ff8..000000000 --- a/WindowManager/app/src/androidTest/java/com/example/windowmanagersample/TestActivity.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed 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 - * - * https://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 CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.windowmanagersample - -import android.app.Activity - -/** - * A test [Activity] for testing purposes. - */ -public class TestActivity : Activity() diff --git a/WindowManager/app/src/main/java/com/example/windowmanagersample/SampleTools.kt b/WindowManager/app/src/main/java/com/example/windowmanagersample/SampleTools.kt index 00e1c4bc2..248bb9e2a 100644 --- a/WindowManager/app/src/main/java/com/example/windowmanagersample/SampleTools.kt +++ b/WindowManager/app/src/main/java/com/example/windowmanagersample/SampleTools.kt @@ -66,13 +66,15 @@ fun getFeaturePositionInViewRect( * Gets the layout params for placing a rectangle indicating a display feature inside a * [FrameLayout]. */ -fun getLayoutParamsForFeatureInFrameLayout(displayFeature: DisplayFeature, view: FrameLayout): - FrameLayout.LayoutParams? { - val featureRectInView = getFeaturePositionInViewRect(displayFeature, view) ?: return null +fun getLayoutParamsForFeatureInFrameLayout( + displayFeature: DisplayFeature, + view: FrameLayout +): FrameLayout.LayoutParams? { + val featureRectInView = getFeaturePositionInViewRect(displayFeature, view) ?: return null - val lp = FrameLayout.LayoutParams(featureRectInView.width(), featureRectInView.height()) - lp.leftMargin = featureRectInView.left - lp.topMargin = featureRectInView.top + val lp = FrameLayout.LayoutParams(featureRectInView.width(), featureRectInView.height()) + lp.leftMargin = featureRectInView.left + lp.topMargin = featureRectInView.top - return lp - } + return lp +} From 76aa94bf13a4c5615b994b57966c75a2ef778a5a Mon Sep 17 00:00:00 2001 From: "Pietro F. Maggi" Date: Tue, 20 Jul 2021 14:35:10 +0200 Subject: [PATCH 2/2] move @OptIn(ExperimentalCoroutinesApi) at the class level --- .../windowmanagersample/DisplayFeaturesActivityTest.kt | 5 +---- .../example/windowmanagersample/SplitLayoutActivityTest.kt | 4 +--- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/WindowManager/app/src/androidTest/java/com/example/windowmanagersample/DisplayFeaturesActivityTest.kt b/WindowManager/app/src/androidTest/java/com/example/windowmanagersample/DisplayFeaturesActivityTest.kt index b39ad61ed..cccb5e38a 100644 --- a/WindowManager/app/src/androidTest/java/com/example/windowmanagersample/DisplayFeaturesActivityTest.kt +++ b/WindowManager/app/src/androidTest/java/com/example/windowmanagersample/DisplayFeaturesActivityTest.kt @@ -44,11 +44,11 @@ import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) class DisplayFeaturesActivityTest { private val activityRule = ActivityScenarioRule(DisplayFeaturesActivity::class.java) private val publisherRule = WindowLayoutInfoPublisherRule() - @OptIn(ExperimentalCoroutinesApi::class) private val testScope = TestCoroutineScope() @get:Rule @@ -58,7 +58,6 @@ class DisplayFeaturesActivityTest { testRule = RuleChain.outerRule(publisherRule).around(activityRule) } - @OptIn(ExperimentalCoroutinesApi::class) @Test fun testDeviceOpen_Flat(): Unit = testScope.runBlockingTest { activityRule.scenario.onActivity { activity -> @@ -87,7 +86,6 @@ class DisplayFeaturesActivityTest { onView(withId(R.id.current_state)).check(matches(withSubstring("Hinge is horizontal"))) } - @OptIn(ExperimentalCoroutinesApi::class) @Test fun testDeviceOpen_TableTop(): Unit = testScope.runBlockingTest { activityRule.scenario.onActivity { activity -> @@ -113,7 +111,6 @@ class DisplayFeaturesActivityTest { onView(withId(R.id.current_state)).check(matches(withSubstring("Hinge is horizontal"))) } - @OptIn(ExperimentalCoroutinesApi::class) @Test fun testDeviceOpen_Book(): Unit = testScope.runBlockingTest { activityRule.scenario.onActivity { activity -> diff --git a/WindowManager/app/src/androidTest/java/com/example/windowmanagersample/SplitLayoutActivityTest.kt b/WindowManager/app/src/androidTest/java/com/example/windowmanagersample/SplitLayoutActivityTest.kt index 12f0ba11f..f56811030 100644 --- a/WindowManager/app/src/androidTest/java/com/example/windowmanagersample/SplitLayoutActivityTest.kt +++ b/WindowManager/app/src/androidTest/java/com/example/windowmanagersample/SplitLayoutActivityTest.kt @@ -43,12 +43,12 @@ import org.junit.rules.RuleChain import org.junit.rules.TestRule import org.junit.runner.RunWith +@OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) class SplitLayoutActivityTest { private val activityRule = ActivityScenarioRule(SplitLayoutActivity::class.java) private val publisherRule = WindowLayoutInfoPublisherRule() - @OptIn(ExperimentalCoroutinesApi::class) private val testScope = TestCoroutineScope() @get:Rule @@ -58,7 +58,6 @@ class SplitLayoutActivityTest { testRule = RuleChain.outerRule(publisherRule).around(activityRule) } - @OptIn(ExperimentalCoroutinesApi::class) @Test fun testDeviceOpen_Vertical(): Unit = testScope.runBlockingTest { activityRule.scenario.onActivity { activity -> @@ -89,7 +88,6 @@ class SplitLayoutActivityTest { onView(withId(R.id.end_layout)).check(matches(isDisplayed())) } - @OptIn(ExperimentalCoroutinesApi::class) @Test fun testDeviceOpen_Horizontal(): Unit = testScope.runBlockingTest { activityRule.scenario.onActivity { activity ->