diff --git a/samples/containers/app-poetry/src/androidTest/java/com/squareup/sample/poetryapp/PoetryAppTest.kt b/samples/containers/app-poetry/src/androidTest/java/com/squareup/sample/poetryapp/PoetryAppTest.kt index 6bc56540d7..5701642b4b 100644 --- a/samples/containers/app-poetry/src/androidTest/java/com/squareup/sample/poetryapp/PoetryAppTest.kt +++ b/samples/containers/app-poetry/src/androidTest/java/com/squareup/sample/poetryapp/PoetryAppTest.kt @@ -1,14 +1,13 @@ package com.squareup.sample.poetryapp +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.withText import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import com.squareup.sample.container.poetryapp.R -import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.internal.test.IdlingDispatcherRule -import com.squareup.workflow1.ui.internal.test.inAnyView import leakcanary.DetectLeaksAfterTestSuccess import org.junit.Rule import org.junit.Test @@ -16,17 +15,15 @@ import org.junit.rules.RuleChain import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -@OptIn(WorkflowUiExperimentalApi::class) class PoetryAppTest { private val scenarioRule = ActivityScenarioRule(PoetryActivity::class.java) - @get:Rule val rules = RuleChain.outerRule(DetectLeaksAfterTestSuccess()) + @get:Rule val rules: RuleChain = RuleChain.outerRule(DetectLeaksAfterTestSuccess()) .around(scenarioRule) .around(IdlingDispatcherRule) @Test fun launches() { - inAnyView(withText(R.string.poems)) - .check(matches(isDisplayed())) + onView(withText(R.string.poems)).check(matches(isDisplayed())) } } diff --git a/samples/containers/app-raven/src/androidTest/java/com/squareup/sample/ravenapp/RavenAppTest.kt b/samples/containers/app-raven/src/androidTest/java/com/squareup/sample/ravenapp/RavenAppTest.kt index b1644b1e95..e7ad5d0822 100644 --- a/samples/containers/app-raven/src/androidTest/java/com/squareup/sample/ravenapp/RavenAppTest.kt +++ b/samples/containers/app-raven/src/androidTest/java/com/squareup/sample/ravenapp/RavenAppTest.kt @@ -1,13 +1,12 @@ package com.squareup.sample.ravenapp +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.withText import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.internal.test.IdlingDispatcherRule -import com.squareup.workflow1.ui.internal.test.inAnyView import leakcanary.DetectLeaksAfterTestSuccess import org.junit.Rule import org.junit.Test @@ -15,17 +14,16 @@ import org.junit.rules.RuleChain import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -@OptIn(WorkflowUiExperimentalApi::class) class RavenAppTest { private val scenarioRule = ActivityScenarioRule(RavenActivity::class.java) - @get:Rule val rules = RuleChain.outerRule(DetectLeaksAfterTestSuccess()) + @get:Rule val rules: RuleChain = RuleChain.outerRule(DetectLeaksAfterTestSuccess()) .around(scenarioRule) .around(IdlingDispatcherRule) @Test fun launches() { - inAnyView(withText("The Raven")) + onView(withText("The Raven")) .check(matches(isDisplayed())) } } diff --git a/samples/containers/hello-back-button/src/androidTest/java/com/squareup/sample/hellobackbutton/HelloBackButtonEspressoTest.kt b/samples/containers/hello-back-button/src/androidTest/java/com/squareup/sample/hellobackbutton/HelloBackButtonEspressoTest.kt index c353358a50..9b4772a165 100644 --- a/samples/containers/hello-back-button/src/androidTest/java/com/squareup/sample/hellobackbutton/HelloBackButtonEspressoTest.kt +++ b/samples/containers/hello-back-button/src/androidTest/java/com/squareup/sample/hellobackbutton/HelloBackButtonEspressoTest.kt @@ -1,7 +1,10 @@ package com.squareup.sample.hellobackbutton +import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.pressBack import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.RootMatchers.isDialog import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText @@ -9,9 +12,6 @@ import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.internal.test.IdlingDispatcherRule -import com.squareup.workflow1.ui.internal.test.actuallyPressBack -import com.squareup.workflow1.ui.internal.test.inAnyView -import com.squareup.workflow1.ui.internal.test.retryBlocking import leakcanary.DetectLeaksAfterTestSuccess import org.junit.Rule import org.junit.Test @@ -24,29 +24,32 @@ class HelloBackButtonEspressoTest { private val scenarioRule = ActivityScenarioRule(HelloBackButtonActivity::class.java) - @get:Rule val rules = RuleChain.outerRule(DetectLeaksAfterTestSuccess()) + @get:Rule val rules: RuleChain = RuleChain.outerRule(DetectLeaksAfterTestSuccess()) .around(scenarioRule) .around(IdlingDispatcherRule) - @Test fun wrappedTakesPrecedence() = retryBlocking { - inAnyView(withId(R.id.hello_message)).apply { + @Test fun wrappedTakesPrecedence() { + onView(withId(R.id.hello_message)).apply { check(matches(withText("Able"))) perform(click()) check(matches(withText("Baker"))) perform(click()) check(matches(withText("Charlie"))) - actuallyPressBack() + perform(pressBack()) check(matches(withText("Baker"))) - actuallyPressBack() + perform(pressBack()) check(matches(withText("Able"))) } } - @Test fun outerHandlerAppliesIfWrappedHandlerIsNull() = retryBlocking { - inAnyView(withId(R.id.hello_message)).apply { - actuallyPressBack() - inAnyView(withText("Are you sure you want to do this thing?")) - .check(matches(isDisplayed())) + @Test fun outerHandlerAppliesIfWrappedHandlerIsNull() { + onView(withId(R.id.hello_message)).apply { + check(matches(isDisplayed())) + perform(pressBack()) } + + onView(withText("Are you sure you want to do this thing?")) + .inRoot(isDialog()) + .check(matches(isDisplayed())) } } diff --git a/samples/dungeon/app/src/androidTest/java/com/squareup/sample/dungeon/DungeonAppTest.kt b/samples/dungeon/app/src/androidTest/java/com/squareup/sample/dungeon/DungeonAppTest.kt index c38a2e8e02..df39036a2f 100644 --- a/samples/dungeon/app/src/androidTest/java/com/squareup/sample/dungeon/DungeonAppTest.kt +++ b/samples/dungeon/app/src/androidTest/java/com/squareup/sample/dungeon/DungeonAppTest.kt @@ -1,13 +1,12 @@ package com.squareup.sample.dungeon +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.withText import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.internal.test.IdlingDispatcherRule -import com.squareup.workflow1.ui.internal.test.inAnyView import leakcanary.DetectLeaksAfterTestSuccess import org.junit.Rule import org.junit.Test @@ -15,17 +14,16 @@ import org.junit.rules.RuleChain import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -@OptIn(WorkflowUiExperimentalApi::class) class DungeonAppTest { private val scenarioRule = ActivityScenarioRule(DungeonActivity::class.java) - @get:Rule val rules = RuleChain.outerRule(DetectLeaksAfterTestSuccess()) + @get:Rule val rules: RuleChain = RuleChain.outerRule(DetectLeaksAfterTestSuccess()) .around(scenarioRule) .around(IdlingDispatcherRule) @Test fun loadsBoardsList() { - inAnyView(withText(R.string.boards_list_label)) + onView(withText(R.string.boards_list_label)) .check(matches(isDisplayed())) } } diff --git a/samples/hello-workflow-fragment/src/androidTest/java/com/squareup/sample/helloworkflowfragment/HelloWorkflowFragmentAppTest.kt b/samples/hello-workflow-fragment/src/androidTest/java/com/squareup/sample/helloworkflowfragment/HelloWorkflowFragmentAppTest.kt index 78ad2d60ac..7ef655ea6a 100644 --- a/samples/hello-workflow-fragment/src/androidTest/java/com/squareup/sample/helloworkflowfragment/HelloWorkflowFragmentAppTest.kt +++ b/samples/hello-workflow-fragment/src/androidTest/java/com/squareup/sample/helloworkflowfragment/HelloWorkflowFragmentAppTest.kt @@ -1,14 +1,13 @@ package com.squareup.sample.helloworkflowfragment +import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.internal.test.IdlingDispatcherRule -import com.squareup.workflow1.ui.internal.test.inAnyView import leakcanary.DetectLeaksAfterTestSuccess import org.hamcrest.Matchers.containsString import org.junit.Rule @@ -17,25 +16,24 @@ import org.junit.rules.RuleChain import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -@OptIn(WorkflowUiExperimentalApi::class) class HelloWorkflowFragmentAppTest { private val scenarioRule = ActivityScenarioRule(HelloWorkflowFragmentActivity::class.java) - @get:Rule val rules = RuleChain.outerRule(DetectLeaksAfterTestSuccess()) + @get:Rule val rules: RuleChain = RuleChain.outerRule(DetectLeaksAfterTestSuccess()) .around(scenarioRule) .around(IdlingDispatcherRule) @Test fun togglesHelloAndGoodbye() { - inAnyView(withText(containsString("Hello"))) + onView(withText(containsString("Hello"))) .check(matches(isDisplayed())) .perform(click()) - inAnyView(withText(containsString("Goodbye"))) + onView(withText(containsString("Goodbye"))) .check(matches(isDisplayed())) .perform(click()) - inAnyView(withText(containsString("Hello"))) + onView(withText(containsString("Hello"))) .check(matches(isDisplayed())) } } diff --git a/samples/hello-workflow/src/androidTest/java/com/squareup/sample/helloworkflow/HelloWorkflowAppTest.kt b/samples/hello-workflow/src/androidTest/java/com/squareup/sample/helloworkflow/HelloWorkflowAppTest.kt index 66dbdb8559..5f688422ea 100644 --- a/samples/hello-workflow/src/androidTest/java/com/squareup/sample/helloworkflow/HelloWorkflowAppTest.kt +++ b/samples/hello-workflow/src/androidTest/java/com/squareup/sample/helloworkflow/HelloWorkflowAppTest.kt @@ -1,14 +1,13 @@ package com.squareup.sample.helloworkflow +import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.internal.test.IdlingDispatcherRule -import com.squareup.workflow1.ui.internal.test.inAnyView import leakcanary.DetectLeaksAfterTestSuccess import org.junit.Rule import org.junit.Test @@ -16,25 +15,24 @@ import org.junit.rules.RuleChain import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -@OptIn(WorkflowUiExperimentalApi::class) class HelloWorkflowAppTest { private val scenarioRule = ActivityScenarioRule(HelloWorkflowActivity::class.java) - @get:Rule val rules = RuleChain.outerRule(DetectLeaksAfterTestSuccess()) + @get:Rule val rules: RuleChain = RuleChain.outerRule(DetectLeaksAfterTestSuccess()) .around(scenarioRule) .around(IdlingDispatcherRule) @Test fun togglesHelloAndGoodbye() { - inAnyView(withText("Hello")) + onView(withText("Hello")) .check(matches(isDisplayed())) .perform(click()) - inAnyView(withText("Goodbye")) + onView(withText("Goodbye")) .check(matches(isDisplayed())) .perform(click()) - inAnyView(withText("Hello")) + onView(withText("Hello")) .check(matches(isDisplayed())) } } diff --git a/samples/nested-overlays/build.gradle.kts b/samples/nested-overlays/build.gradle.kts new file mode 100644 index 0000000000..7d31f7a461 --- /dev/null +++ b/samples/nested-overlays/build.gradle.kts @@ -0,0 +1,25 @@ +plugins { + id("com.android.application") + `kotlin-android` + `android-sample-app` + `android-ui-tests` +} + +android { + defaultConfig { + applicationId = "com.squareup.sample.nestedoverlays" + } + namespace = "com.squareup.sample.nestedoverlays" +} + +dependencies { + debugImplementation(libs.squareup.leakcanary.android) + + implementation(libs.androidx.activity.ktx) + implementation(libs.androidx.lifecycle.viewmodel.ktx) + implementation(libs.androidx.lifecycle.viewmodel.savedstate) + implementation(libs.androidx.viewbinding) + + implementation(project(":workflow-ui:core-android")) + implementation(project(":workflow-ui:core-common")) +} diff --git a/samples/nested-overlays/lint-baseline.xml b/samples/nested-overlays/lint-baseline.xml new file mode 100644 index 0000000000..4aec7fcd50 --- /dev/null +++ b/samples/nested-overlays/lint-baseline.xml @@ -0,0 +1,4 @@ + + + + diff --git a/samples/nested-overlays/src/androidTest/java/com/squareup/sample/nestedoverlays/NestedOverlaysAppTest.kt b/samples/nested-overlays/src/androidTest/java/com/squareup/sample/nestedoverlays/NestedOverlaysAppTest.kt new file mode 100644 index 0000000000..c616d62a14 --- /dev/null +++ b/samples/nested-overlays/src/androidTest/java/com/squareup/sample/nestedoverlays/NestedOverlaysAppTest.kt @@ -0,0 +1,99 @@ +package com.squareup.sample.nestedoverlays + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.ViewInteraction +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions.doesNotExist +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withParent +import androidx.test.espresso.matcher.ViewMatchers.withParentIndex +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.squareup.workflow1.ui.internal.test.IdlingDispatcherRule +import leakcanary.DetectLeaksAfterTestSuccess +import org.hamcrest.core.AllOf.allOf +import org.hamcrest.core.IsNot.not +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class NestedOverlaysAppTest { + + private val scenarioRule = ActivityScenarioRule(NestedOverlaysActivity::class.java) + + @get:Rule val rules: RuleChain = RuleChain.outerRule(DetectLeaksAfterTestSuccess()) + .around(scenarioRule) + .around(IdlingDispatcherRule) + + @Test fun basics() { + onTopCoverBody().assertDisplayed() + onTopCoverEverything().assertDisplayed() + onBottomCoverBody().assertDisplayed() + onBottomCoverEverything().assertDisplayed() + + onTopCoverBody().perform(click()) + onView(withText("Close")).perform(click()) + onTopCoverEverything().perform(click()) + onView(withText("Close")).perform(click()) + + onView(withText("Hide Top Bar")).perform(click()) + onTopCoverBody().assertNotDisplayed() + onTopCoverEverything().assertNotDisplayed() + onBottomCoverBody().assertDisplayed() + onBottomCoverEverything().assertDisplayed() + + onView(withText("Hide Bottom Bar")).perform(click()) + onTopCoverBody().assertNotDisplayed() + onTopCoverEverything().assertNotDisplayed() + onBottomCoverBody().assertNotDisplayed() + onBottomCoverEverything().assertNotDisplayed() + } + + // https://github.com/square/workflow-kotlin/issues/966 + @Test fun canInsertDialog() { + onTopCoverEverything().perform(click()) + onView(withText("Hide Top Bar")).check(doesNotExist()) + onView(withText("Cover Body")).perform(click()) + + // This line fails due to https://github.com/square/workflow-kotlin/issues/966 + // onView(withText("Hide Top Bar")).check(doesNotExist()) + + // Should continue to close the top sheet and assert that the inner sheet is visible. + } + + // So far can't express this in Espresso. Considering move to Maestro + // @Test fun canClickPastInnerWindow() { + // onView(allOf(withText("Cover Everything"), withParent(withParentIndex(0)))) + // .perform(click()) + // + // scenario.onActivity { activity -> + // onView(allOf(withText("Cover Everything"), withParent(withParentIndex(0)))) + // .inRoot(withDecorView(not(`is`(activity.window.decorView)))) + // .perform(click()) + // } + // } + + private fun ViewInteraction.assertNotDisplayed() { + check(matches(not(isDisplayed()))) + } + + private fun ViewInteraction.assertDisplayed() { + check(matches(isDisplayed())) + } + + private fun onBottomCoverEverything() = + onView(allOf(withText("Cover Everything"), withParent(withParentIndex(2)))) + + private fun onBottomCoverBody() = + onView(allOf(withText("Cover Body"), withParent(withParentIndex(2)))) + + private fun onTopCoverBody() = + onView(allOf(withText("Cover Body"), withParent(withParentIndex(0)))) + + private fun onTopCoverEverything() = + onView(allOf(withText("Cover Everything"), withParent(withParentIndex(0)))) +} diff --git a/samples/nested-overlays/src/main/AndroidManifest.xml b/samples/nested-overlays/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..594e1fde1d --- /dev/null +++ b/samples/nested-overlays/src/main/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + diff --git a/samples/nested-overlays/src/main/java/com/squareup/sample/nestedoverlays/ButtonBar.kt b/samples/nested-overlays/src/main/java/com/squareup/sample/nestedoverlays/ButtonBar.kt new file mode 100644 index 0000000000..37f9f7726f --- /dev/null +++ b/samples/nested-overlays/src/main/java/com/squareup/sample/nestedoverlays/ButtonBar.kt @@ -0,0 +1,53 @@ +package com.squareup.sample.nestedoverlays + +import android.graphics.drawable.ColorDrawable +import android.view.Gravity +import android.widget.LinearLayout +import androidx.annotation.ColorRes +import androidx.annotation.StringRes +import androidx.core.view.get +import com.squareup.workflow1.ui.AndroidScreen +import com.squareup.workflow1.ui.ScreenViewFactory +import com.squareup.workflow1.ui.ScreenViewHolder +import com.squareup.workflow1.ui.WorkflowUiExperimentalApi +import android.widget.Button as ButtonView + +data class Button( + @StringRes val name: Int, + val onClick: () -> Unit +) + +@OptIn(WorkflowUiExperimentalApi::class) +class ButtonBar( + vararg buttons: Button?, + @ColorRes val color: Int = -1, +) : AndroidScreen { + private val buttons: List