Update samples with ComposeTesting and remove TestUtils module (#82)
* Update CompanionPane with ComposeTesting * Update ComposeGallery with ComposeTesting * Update ListDetail with ComposeTesting * Update NavigationRail with ComposeTesting * Update TwoPage with ComposeTesting * Update DualView with ComposeTesting * Update ExtendedCanvas with ComposeTesting * Remove TestUtils module * Update wildcard import * Remove the magic number * Fix the ktlint error * Move to JC 1.1.0 * Remove JWM testing dependency * Address comments * Remove customized ViewSize for DualView * Update dependency * Update with ComposeTesting alpha02
This commit is contained in:
Родитель
7aadf7b78f
Коммит
b63ee37154
|
@ -59,9 +59,8 @@ dependencies {
|
|||
androidTestImplementation testDependencies.espressoCore
|
||||
androidTestImplementation testDependencies.composeUITest
|
||||
androidTestImplementation testDependencies.composeJunit
|
||||
androidTestImplementation testDependencies.windowTest
|
||||
androidTestImplementation testDependencies.uiAutomator
|
||||
androidTestImplementation project(':TestUtils')
|
||||
androidTestImplementation microsoftDependencies.composeTesting
|
||||
|
||||
implementation googleDependencies.material
|
||||
}
|
|
@ -13,9 +13,9 @@ import androidx.test.platform.app.InstrumentationRegistry
|
|||
import androidx.test.uiautomator.UiDevice
|
||||
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
|
||||
import com.microsoft.device.display.samples.companionpane.ui.theme.CompanionPaneAppTheme
|
||||
import com.microsoft.device.dualscreen.testutils.getString
|
||||
import com.microsoft.device.dualscreen.testutils.simulateHorizontalFold
|
||||
import com.microsoft.device.dualscreen.testutils.simulateVerticalFold
|
||||
import com.microsoft.device.dualscreen.testing.getString
|
||||
import com.microsoft.device.dualscreen.testing.simulateHorizontalFoldingFeature
|
||||
import com.microsoft.device.dualscreen.testing.simulateVerticalFoldingFeature
|
||||
import com.microsoft.device.dualscreen.windowstate.WindowMode
|
||||
import com.microsoft.device.dualscreen.windowstate.rememberWindowState
|
||||
import org.junit.Rule
|
||||
|
@ -94,8 +94,8 @@ class LayoutTest {
|
|||
}
|
||||
}
|
||||
|
||||
// Simulate vertical fold
|
||||
publisherRule.simulateVerticalFold(composeTestRule)
|
||||
// Simulate vertical foldingFeature
|
||||
publisherRule.simulateVerticalFoldingFeature(composeTestRule)
|
||||
|
||||
// Check that dual portrait panes are shown
|
||||
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.dual_port_pane1))
|
||||
|
@ -116,8 +116,8 @@ class LayoutTest {
|
|||
}
|
||||
}
|
||||
|
||||
// Simulate horizontal fold
|
||||
publisherRule.simulateHorizontalFold(composeTestRule)
|
||||
// Simulate horizontal foldingFeature
|
||||
publisherRule.simulateHorizontalFoldingFeature(composeTestRule)
|
||||
|
||||
// Check that dual landscape panes are shown
|
||||
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.dual_land_pane1))
|
||||
|
@ -137,8 +137,8 @@ class LayoutTest {
|
|||
}
|
||||
}
|
||||
|
||||
// Simulate horizontal fold
|
||||
publisherRule.simulateHorizontalFold(composeTestRule)
|
||||
// Simulate horizontal foldingFeature
|
||||
publisherRule.simulateHorizontalFoldingFeature(composeTestRule)
|
||||
|
||||
// Check that dual landscape panes are shown
|
||||
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.dual_land_pane1))
|
||||
|
@ -146,8 +146,8 @@ class LayoutTest {
|
|||
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.dual_land_pane2))
|
||||
.assertIsDisplayed()
|
||||
|
||||
// Simulate vertical fold
|
||||
publisherRule.simulateVerticalFold(composeTestRule)
|
||||
// Simulate vertical foldingFeature
|
||||
publisherRule.simulateVerticalFoldingFeature(composeTestRule)
|
||||
|
||||
// Check that dual portrait panes are shown
|
||||
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.dual_port_pane1))
|
||||
|
|
|
@ -12,7 +12,7 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
|||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
|
||||
import com.microsoft.device.display.samples.companionpane.ui.theme.CompanionPaneAppTheme
|
||||
import com.microsoft.device.dualscreen.testutils.getString
|
||||
import com.microsoft.device.dualscreen.testing.getString
|
||||
import com.microsoft.device.dualscreen.windowstate.WindowMode
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
|
|
@ -61,8 +61,7 @@ dependencies {
|
|||
androidTestImplementation testDependencies.espressoCore
|
||||
androidTestImplementation testDependencies.composeUITest
|
||||
androidTestImplementation testDependencies.composeJunit
|
||||
androidTestImplementation testDependencies.windowTest
|
||||
androidTestImplementation project(':TestUtils')
|
||||
androidTestImplementation microsoftDependencies.composeTesting
|
||||
|
||||
debugImplementation testDependencies.composeUITestManifest
|
||||
}
|
|
@ -23,9 +23,9 @@ import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
|
|||
import com.microsoft.device.display.samples.composegallery.models.DataProvider
|
||||
import com.microsoft.device.display.samples.composegallery.ui.ComposeGalleryTheme
|
||||
import com.microsoft.device.display.samples.composegallery.ui.view.ComposeGalleryApp
|
||||
import com.microsoft.device.dualscreen.testutils.getString
|
||||
import com.microsoft.device.dualscreen.testutils.simulateHorizontalFold
|
||||
import com.microsoft.device.dualscreen.testutils.simulateVerticalFold
|
||||
import com.microsoft.device.dualscreen.testing.getString
|
||||
import com.microsoft.device.dualscreen.testing.simulateHorizontalFoldingFeature
|
||||
import com.microsoft.device.dualscreen.testing.simulateVerticalFoldingFeature
|
||||
import com.microsoft.device.dualscreen.windowstate.WindowState
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -56,8 +56,8 @@ class PaneSynchronizationTest {
|
|||
}
|
||||
}
|
||||
|
||||
// Simulate a vertical fold so two panes are visible
|
||||
publisherRule.simulateVerticalFold(composeTestRule)
|
||||
// Simulate a vertical foldFeature so two panes are visible
|
||||
publisherRule.simulateVerticalFoldingFeature(composeTestRule)
|
||||
|
||||
// Scroll to end of list
|
||||
val index = 7
|
||||
|
@ -107,8 +107,8 @@ class PaneSynchronizationTest {
|
|||
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.switch_to_list))
|
||||
.performClick()
|
||||
|
||||
// Simulate a vertical fold so two panes are visible
|
||||
publisherRule.simulateVerticalFold(composeTestRule)
|
||||
// Simulate a vertical foldFeature so two panes are visible
|
||||
publisherRule.simulateVerticalFoldingFeature(composeTestRule)
|
||||
|
||||
// Check that third surface duo image is still displayed
|
||||
composeTestRule.onNode(
|
||||
|
@ -129,8 +129,8 @@ class PaneSynchronizationTest {
|
|||
}
|
||||
}
|
||||
|
||||
// Simulate a horizontal fold so one pane is still visible
|
||||
publisherRule.simulateHorizontalFold(composeTestRule)
|
||||
// Simulate a horizontal foldFeature so one pane is still visible
|
||||
publisherRule.simulateHorizontalFoldingFeature(composeTestRule)
|
||||
|
||||
// Check that the list view is displayed
|
||||
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.gallery_list))
|
||||
|
|
|
@ -20,7 +20,7 @@ import com.microsoft.device.display.samples.composegallery.ui.ComposeGalleryThem
|
|||
import com.microsoft.device.display.samples.composegallery.ui.view.ComposeGalleryApp
|
||||
import com.microsoft.device.display.samples.composegallery.ui.view.DetailPane
|
||||
import com.microsoft.device.display.samples.composegallery.ui.view.ListPane
|
||||
import com.microsoft.device.dualscreen.testutils.getString
|
||||
import com.microsoft.device.dualscreen.testing.getString
|
||||
import com.microsoft.device.dualscreen.windowstate.WindowState
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
|
|
@ -61,7 +61,6 @@ dependencies {
|
|||
androidTestImplementation testDependencies.espressoCore
|
||||
androidTestImplementation testDependencies.composeUITest
|
||||
androidTestImplementation testDependencies.composeJunit
|
||||
androidTestImplementation testDependencies.windowTest
|
||||
androidTestImplementation testDependencies.uiAutomator
|
||||
androidTestImplementation project(':TestUtils')
|
||||
androidTestImplementation microsoftDependencies.composeTesting
|
||||
}
|
|
@ -6,30 +6,28 @@
|
|||
package com.microsoft.device.display.samples.dualview
|
||||
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.SemanticsNodeInteraction
|
||||
import androidx.compose.ui.test.hasAnySibling
|
||||
import androidx.compose.ui.test.hasContentDescription
|
||||
import androidx.compose.ui.test.hasParent
|
||||
import androidx.compose.ui.test.hasScrollToIndexAction
|
||||
import androidx.compose.ui.test.hasText
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performScrollToIndex
|
||||
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
|
||||
import com.microsoft.device.display.samples.dualview.models.restaurants
|
||||
import com.microsoft.device.display.samples.dualview.ui.theme.DualViewAppTheme
|
||||
import com.microsoft.device.display.samples.dualview.ui.view.DualViewApp
|
||||
import com.microsoft.device.dualscreen.testutils.assertScreenshotMatchesReference
|
||||
import com.microsoft.device.dualscreen.testutils.getString
|
||||
import com.microsoft.device.dualscreen.testutils.simulateHorizontalFold
|
||||
import com.microsoft.device.dualscreen.testing.getString
|
||||
import com.microsoft.device.dualscreen.testing.simulateHorizontalFoldingFeature
|
||||
import com.microsoft.device.dualscreen.windowstate.WindowState
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TestRule
|
||||
|
||||
const val VIEW_SIZE = 400
|
||||
const val nonSelectionOption = -1
|
||||
|
||||
class MapImageTest {
|
||||
private val composeTestRule = createAndroidComposeRule<MainActivity>()
|
||||
|
@ -51,39 +49,15 @@ class MapImageTest {
|
|||
fun app_horizontalFold_mapUpdatesAfterRestaurantClick() {
|
||||
composeTestRule.setContent {
|
||||
DualViewAppTheme {
|
||||
DualViewApp(WindowState(hasFold = true, foldIsHorizontal = true, foldIsSeparating = true), viewSize = VIEW_SIZE)
|
||||
DualViewApp(WindowState(hasFold = true, foldIsHorizontal = true, foldIsSeparating = true))
|
||||
}
|
||||
}
|
||||
|
||||
// Simulate horizontal fold
|
||||
publisherRule.simulateHorizontalFold(composeTestRule)
|
||||
|
||||
clickRestaurantsAndPerformAction()
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls to and clicks each item in the restaurant list, and also performs the specified action
|
||||
* on each restaurant item node/reference image pair. The default action asserts that a screenshot
|
||||
* of the node matches the associated reference image.
|
||||
*
|
||||
* @param action: action to perform with Semantics Node and reference image pair
|
||||
*/
|
||||
@ExperimentalTestApi
|
||||
private fun clickRestaurantsAndPerformAction(action: (String, SemanticsNodeInteraction) -> Unit = ::assertScreenshotMatchesReference) {
|
||||
// Create list of reference images file names (from src/androidTest/assets folder)
|
||||
val referenceAssets =
|
||||
mutableListOf("unselected", "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth")
|
||||
referenceAssets.forEachIndexed { index, prefix ->
|
||||
referenceAssets[index] = prefix.plus("_map.png")
|
||||
}
|
||||
// Simulate horizontal foldFeature
|
||||
publisherRule.simulateHorizontalFoldingFeature(composeTestRule)
|
||||
|
||||
// Scrolls to and clicks each item in the restaurant list
|
||||
restaurants.forEachIndexed { index, rest ->
|
||||
// Perform specified action
|
||||
action(
|
||||
referenceAssets[index],
|
||||
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.map_image))
|
||||
)
|
||||
|
||||
// Scroll to next restaurant item
|
||||
composeTestRule.onNode(hasScrollToIndexAction()).performScrollToIndex(index)
|
||||
|
||||
|
@ -93,6 +67,18 @@ class MapImageTest {
|
|||
hasAnySibling(hasText(composeTestRule.getString(R.string.list_title)))
|
||||
)
|
||||
).performClick()
|
||||
|
||||
if (index == nonSelectionOption) {
|
||||
// Assert the unselected image placeholder is shown
|
||||
composeTestRule.onNodeWithContentDescription(
|
||||
composeTestRule.getString(R.string.map_description)
|
||||
).assertExists()
|
||||
} else {
|
||||
// Assert the shown selected image matches the item clicked from the list
|
||||
composeTestRule.onNodeWithContentDescription(
|
||||
composeTestRule.getString(rest.description)
|
||||
).assertExists()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ import com.microsoft.device.display.samples.dualview.ui.theme.typography
|
|||
import com.microsoft.device.display.samples.dualview.ui.view.RestaurantListView
|
||||
import com.microsoft.device.display.samples.dualview.ui.view.TextStyleKey
|
||||
import com.microsoft.device.display.samples.dualview.ui.view.narrowWidth
|
||||
import com.microsoft.device.dualscreen.testutils.getString
|
||||
import com.microsoft.device.dualscreen.testing.getString
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
|
|
|
@ -17,7 +17,7 @@ import com.microsoft.device.display.samples.dualview.ui.theme.DualViewAppTheme
|
|||
import com.microsoft.device.display.samples.dualview.ui.view.DualViewApp
|
||||
import com.microsoft.device.display.samples.dualview.ui.view.MapTopBar
|
||||
import com.microsoft.device.display.samples.dualview.ui.view.RestaurantTopBar
|
||||
import com.microsoft.device.dualscreen.testutils.getString
|
||||
import com.microsoft.device.dualscreen.testing.getString
|
||||
import com.microsoft.device.dualscreen.windowstate.WindowState
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
|
|
@ -18,7 +18,7 @@ import com.microsoft.device.dualscreen.windowstate.WindowState
|
|||
import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
fun DualViewApp(windowState: WindowState, viewSize: Int? = null) {
|
||||
fun DualViewApp(windowState: WindowState) {
|
||||
var selectedIndex by rememberSaveable { mutableStateOf(-1) }
|
||||
val updateSelectedIndex: (Int) -> Unit = { newIndex -> selectedIndex = newIndex }
|
||||
val pane1SizeWidthDp = windowState.pane1SizeDp().width.dp
|
||||
|
@ -30,7 +30,6 @@ fun DualViewApp(windowState: WindowState, viewSize: Int? = null) {
|
|||
viewWidth = with(LocalDensity.current) { viewWidth.toPx() }.roundToInt(),
|
||||
selectedIndex = selectedIndex,
|
||||
updateSelectedIndex = updateSelectedIndex,
|
||||
viewSize = viewSize
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -40,10 +39,9 @@ fun DualViewAppContent(
|
|||
viewWidth: Int,
|
||||
selectedIndex: Int,
|
||||
updateSelectedIndex: (Int) -> Unit,
|
||||
viewSize: Int? = null
|
||||
) {
|
||||
TwoPaneLayout(
|
||||
pane1 = { RestaurantViewWithTopBar(isDualScreen, viewWidth, selectedIndex, updateSelectedIndex) },
|
||||
pane2 = { MapViewWithTopBar(isDualScreen, selectedIndex, viewSize) }
|
||||
pane2 = { MapViewWithTopBar(isDualScreen, selectedIndex) }
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
package com.microsoft.device.display.samples.dualview.ui.view
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.gestures.detectDragGestures
|
||||
import androidx.compose.foundation.gestures.rememberTransformableState
|
||||
|
@ -12,8 +14,6 @@ import androidx.compose.foundation.gestures.transformable
|
|||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.material.AppBarDefaults
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.Scaffold
|
||||
|
@ -31,35 +31,31 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.microsoft.device.display.samples.dualview.R
|
||||
import com.microsoft.device.display.samples.dualview.models.restaurants
|
||||
import com.microsoft.device.display.samples.dualview.ui.theme.DualViewAppTheme
|
||||
import com.microsoft.device.dualscreen.twopanelayout.navigateToPane1
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
private const val nonSelection = -1
|
||||
|
||||
@Composable
|
||||
fun MapViewWithTopBar(isDualScreen: Boolean, selectedIndex: Int, viewSize: Int?) {
|
||||
fun MapViewWithTopBar(isDualScreen: Boolean, selectedIndex: Int) {
|
||||
Scaffold(
|
||||
topBar = { MapTopBar(isDualScreen, viewSize != null) }
|
||||
topBar = { MapTopBar(isDualScreen) }
|
||||
) {
|
||||
MapView(selectedIndex, viewSize)
|
||||
MapView(selectedIndex)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MapTopBar(isDualScreen: Boolean, customViewSize: Boolean = false) {
|
||||
fun MapTopBar(isDualScreen: Boolean) {
|
||||
TopAppBar(
|
||||
modifier = Modifier.testTag(stringResource(R.string.map_top_bar)),
|
||||
title = {
|
||||
|
@ -72,7 +68,6 @@ fun MapTopBar(isDualScreen: Boolean, customViewSize: Boolean = false) {
|
|||
)
|
||||
)
|
||||
},
|
||||
elevation = if (customViewSize) 0.dp else AppBarDefaults.TopAppBarElevation,
|
||||
actions = {
|
||||
if (!isDualScreen) {
|
||||
IconButton(onClick = { navigateToPane1() }) {
|
||||
|
@ -88,29 +83,28 @@ fun MapTopBar(isDualScreen: Boolean, customViewSize: Boolean = false) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun MapView(selectedIndex: Int, viewSize: Int? = null) {
|
||||
fun MapView(selectedIndex: Int) {
|
||||
var selectedMapId = R.drawable.unselected_map
|
||||
var selectedTitleId = R.string.map_description
|
||||
if (selectedIndex > nonSelection) {
|
||||
selectedMapId = restaurants[selectedIndex].mapImageResourceId
|
||||
selectedTitleId = restaurants[selectedIndex].description
|
||||
}
|
||||
val viewSizeDp = with(LocalDensity.current) { viewSize?.toDp() }
|
||||
val modifier = if (viewSizeDp == null) Modifier.clipToBounds() else Modifier.requiredSize(viewSizeDp)
|
||||
|
||||
Box(modifier = modifier.testTag(stringResource(R.string.map_image))) {
|
||||
ScalableImageView(imageId = selectedMapId)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun MapViewScreenshotPreview() {
|
||||
DualViewAppTheme {
|
||||
MapView(7, 150)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clipToBounds()
|
||||
.testTag(
|
||||
stringResource(R.string.map_image)
|
||||
)
|
||||
) {
|
||||
ScalableImageView(
|
||||
imageId = selectedMapId, selectedTitleId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ScalableImageView(imageId: Int) {
|
||||
fun ScalableImageView(@DrawableRes imageId: Int, @StringRes descriptionId: Int) {
|
||||
val minScale = 1f
|
||||
val maxScale = 8f
|
||||
val defaultScale = 2f
|
||||
|
@ -124,7 +118,7 @@ fun ScalableImageView(imageId: Int) {
|
|||
|
||||
Image(
|
||||
painter = painterResource(id = imageId),
|
||||
contentDescription = stringResource(R.string.map_description),
|
||||
contentDescription = stringResource(id = descriptionId),
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.graphicsLayer(
|
||||
|
|
|
@ -53,6 +53,5 @@ dependencies {
|
|||
androidTestImplementation testDependencies.androidxTestRules
|
||||
androidTestImplementation testDependencies.composeUITest
|
||||
androidTestImplementation testDependencies.composeJunit
|
||||
androidTestImplementation testDependencies.windowTest
|
||||
androidTestImplementation project(':TestUtils')
|
||||
androidTestImplementation microsoftDependencies.composeTesting
|
||||
}
|
|
@ -5,20 +5,18 @@
|
|||
|
||||
package com.microsoft.device.display.samples.extendedcanvas
|
||||
|
||||
import androidx.compose.ui.graphics.asAndroidBitmap
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.test.SemanticsMatcher
|
||||
import androidx.compose.ui.test.SemanticsNodeInteraction
|
||||
import androidx.compose.ui.test.assert
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.captureToImage
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.performGesture
|
||||
import androidx.compose.ui.test.performTouchInput
|
||||
import androidx.compose.ui.test.swipeLeft
|
||||
import com.microsoft.device.display.samples.extendedcanvas.ui.ExtendedCanvasAppsTheme
|
||||
import com.microsoft.device.dualscreen.testutils.compare
|
||||
import com.microsoft.device.dualscreen.testutils.getString
|
||||
import com.microsoft.device.dualscreen.testutils.zoomIn
|
||||
import com.microsoft.device.dualscreen.testutils.zoomOut
|
||||
import com.microsoft.device.dualscreen.testing.getString
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
|
@ -52,84 +50,25 @@ class ExtendedCanvasTest {
|
|||
}
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.map_image))
|
||||
.assertIsDisplayed()
|
||||
// Get node for the map image
|
||||
val mapImageNode =
|
||||
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.map_image))
|
||||
|
||||
// Take screenshot of initial map image state
|
||||
val original = composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.map_image))
|
||||
.captureToImage()
|
||||
.asAndroidBitmap()
|
||||
// Assert the map image is shown
|
||||
mapImageNode.assertIsDisplayed()
|
||||
|
||||
// Drag the map to the left
|
||||
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.map_image))
|
||||
.performTouchInput { swipeLeft() }
|
||||
mapImageNode.performTouchInput { swipeLeft() }
|
||||
|
||||
// Take screenshot of new map image state
|
||||
val afterSwipe =
|
||||
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.map_image))
|
||||
.captureToImage()
|
||||
.asAndroidBitmap()
|
||||
val defaultImageOffset = Offset.Zero
|
||||
|
||||
// Make sure bitmaps are not the same anymore
|
||||
assert(!original.compare(afterSwipe))
|
||||
// Make sure bitmap offset is not the same anymore
|
||||
mapImageNode.assertImageOffsetNotEquals(defaultImageOffset)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the map image can zoom in
|
||||
* Asserts that the image offset of the node doesn't match the given offset
|
||||
*/
|
||||
@Test
|
||||
fun mapView_testImageZoomsIn() {
|
||||
composeTestRule.setContent {
|
||||
ExtendedCanvasAppsTheme {
|
||||
ExtendedCanvasApp()
|
||||
}
|
||||
}
|
||||
|
||||
// Take screenshot of initial map image state
|
||||
val original = composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.map_image))
|
||||
.captureToImage()
|
||||
.asAndroidBitmap()
|
||||
|
||||
// Zoom in on map image map
|
||||
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.map_image))
|
||||
.performGesture { zoomIn() }
|
||||
|
||||
// Take screenshot of new state
|
||||
val afterZoom =
|
||||
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.map_image))
|
||||
.captureToImage()
|
||||
.asAndroidBitmap()
|
||||
|
||||
// Make sure bitmaps are not the same anymore
|
||||
assert(!original.compare(afterZoom))
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the map image can zoom out
|
||||
*/
|
||||
@Test
|
||||
fun mapView_testImageZoomsOut() {
|
||||
composeTestRule.setContent {
|
||||
ExtendedCanvasAppsTheme {
|
||||
ExtendedCanvasApp()
|
||||
}
|
||||
}
|
||||
|
||||
// Take screenshot of initial map image state
|
||||
val original = composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.map_image))
|
||||
.captureToImage()
|
||||
.asAndroidBitmap()
|
||||
|
||||
// Zoom out on map image map and take screenshot of new state
|
||||
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.map_image))
|
||||
.performGesture { zoomOut() }
|
||||
|
||||
val afterZoom =
|
||||
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.map_image))
|
||||
.captureToImage()
|
||||
.asAndroidBitmap()
|
||||
|
||||
// Make sure bitmaps are not the same anymore
|
||||
assert(!original.compare(afterZoom))
|
||||
}
|
||||
private fun SemanticsNodeInteraction.assertImageOffsetNotEquals(imageOffset: Offset) =
|
||||
assert(!SemanticsMatcher.expectValue(ImageOffsetKey, imageOffset))
|
||||
}
|
||||
|
|
|
@ -28,12 +28,18 @@ import androidx.compose.ui.layout.ContentScale
|
|||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.SemanticsPropertyKey
|
||||
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.sp
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
val ImageOffsetKey = SemanticsPropertyKey<Offset>("ImageOffsetKey")
|
||||
var SemanticsPropertyReceiver.imageOffset by ImageOffsetKey
|
||||
|
||||
@Composable
|
||||
fun ExtendedCanvasApp() {
|
||||
Scaffold(
|
||||
|
@ -74,6 +80,9 @@ fun ScaleImage() {
|
|||
contentDescription = stringResource(R.string.map_image),
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.semantics {
|
||||
imageOffset = offset
|
||||
}
|
||||
.graphicsLayer(
|
||||
scaleX = maxOf(minScale, minOf(maxScale, scale)),
|
||||
scaleY = maxOf(minScale, minOf(maxScale, scale)),
|
||||
|
|
|
@ -61,7 +61,6 @@ dependencies {
|
|||
androidTestImplementation testDependencies.espressoCore
|
||||
androidTestImplementation testDependencies.composeUITest
|
||||
androidTestImplementation testDependencies.composeJunit
|
||||
androidTestImplementation testDependencies.windowTest
|
||||
androidTestImplementation testDependencies.uiAutomator
|
||||
androidTestImplementation project(':TestUtils')
|
||||
androidTestImplementation microsoftDependencies.composeTesting
|
||||
}
|
|
@ -8,9 +8,9 @@ import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
|
|||
import com.microsoft.device.display.samples.listdetail.models.images
|
||||
import com.microsoft.device.display.samples.listdetail.ui.theme.ListDetailComposeSampleTheme
|
||||
import com.microsoft.device.display.samples.listdetail.ui.view.ListDetailApp
|
||||
import com.microsoft.device.dualscreen.testutils.getString
|
||||
import com.microsoft.device.dualscreen.testutils.simulateHorizontalFold
|
||||
import com.microsoft.device.dualscreen.testutils.simulateVerticalFold
|
||||
import com.microsoft.device.dualscreen.testing.getString
|
||||
import com.microsoft.device.dualscreen.testing.simulateHorizontalFoldingFeature
|
||||
import com.microsoft.device.dualscreen.testing.simulateVerticalFoldingFeature
|
||||
import com.microsoft.device.dualscreen.windowstate.WindowState
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -41,8 +41,8 @@ class ListDetailTest {
|
|||
}
|
||||
}
|
||||
|
||||
// Simulate vertical fold
|
||||
publisherRule.simulateVerticalFold(composeTestRule)
|
||||
// Simulate vertical foldFeature
|
||||
publisherRule.simulateVerticalFoldingFeature(composeTestRule)
|
||||
|
||||
images.forEachIndexed { index, _ ->
|
||||
// Click on list item
|
||||
|
@ -69,8 +69,8 @@ class ListDetailTest {
|
|||
}
|
||||
}
|
||||
|
||||
// Simulate horizontal fold
|
||||
publisherRule.simulateHorizontalFold(composeTestRule)
|
||||
// Simulate horizontal foldFeature
|
||||
publisherRule.simulateHorizontalFoldingFeature(composeTestRule)
|
||||
|
||||
// Assert the list view is now shown
|
||||
composeTestRule.onNodeWithTag(
|
||||
|
|
|
@ -14,7 +14,7 @@ import com.microsoft.device.display.samples.listdetail.ui.theme.ListDetailCompos
|
|||
import com.microsoft.device.display.samples.listdetail.ui.view.DetailViewTopBar
|
||||
import com.microsoft.device.display.samples.listdetail.ui.view.ListDetailApp
|
||||
import com.microsoft.device.display.samples.listdetail.ui.view.ListViewTopBar
|
||||
import com.microsoft.device.dualscreen.testutils.getString
|
||||
import com.microsoft.device.dualscreen.testing.getString
|
||||
import com.microsoft.device.dualscreen.windowstate.WindowState
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
|
|
@ -58,7 +58,6 @@ dependencies {
|
|||
androidTestImplementation testDependencies.espressoCore
|
||||
androidTestImplementation testDependencies.composeUITest
|
||||
androidTestImplementation testDependencies.composeJunit
|
||||
androidTestImplementation testDependencies.windowTest
|
||||
androidTestImplementation testDependencies.uiAutomator
|
||||
androidTestImplementation project(':TestUtils')
|
||||
androidTestImplementation microsoftDependencies.composeTesting
|
||||
}
|
|
@ -37,7 +37,7 @@ import com.microsoft.device.display.samples.navigationrail.ui.theme.NavigationRa
|
|||
import com.microsoft.device.display.samples.navigationrail.ui.view.ItemDetailView
|
||||
import com.microsoft.device.display.samples.navigationrail.ui.view.NavigationRailApp
|
||||
import com.microsoft.device.display.samples.navigationrail.ui.view.Pane2
|
||||
import com.microsoft.device.dualscreen.testutils.getString
|
||||
import com.microsoft.device.dualscreen.testing.getString
|
||||
import com.microsoft.device.dualscreen.windowstate.WindowState
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
|
|
@ -27,7 +27,7 @@ import com.microsoft.device.display.samples.navigationrail.models.DataProvider
|
|||
import com.microsoft.device.display.samples.navigationrail.ui.theme.NavigationRailAppTheme
|
||||
import com.microsoft.device.display.samples.navigationrail.ui.view.GalleryView
|
||||
import com.microsoft.device.display.samples.navigationrail.ui.view.NavigationRailApp
|
||||
import com.microsoft.device.dualscreen.testutils.getString
|
||||
import com.microsoft.device.dualscreen.testing.getString
|
||||
import com.microsoft.device.dualscreen.windowstate.WindowState
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
|
|
@ -19,7 +19,7 @@ import androidx.compose.ui.unit.dp
|
|||
import com.microsoft.device.display.samples.navigationrail.ui.theme.NavigationRailAppTheme
|
||||
import com.microsoft.device.display.samples.navigationrail.ui.view.GallerySections
|
||||
import com.microsoft.device.display.samples.navigationrail.ui.view.NavigationRailApp
|
||||
import com.microsoft.device.dualscreen.testutils.getString
|
||||
import com.microsoft.device.dualscreen.testing.getString
|
||||
import com.microsoft.device.dualscreen.windowstate.WindowState
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
|
|
@ -29,8 +29,8 @@ import com.microsoft.device.display.samples.navigationrail.models.DataProvider
|
|||
import com.microsoft.device.display.samples.navigationrail.ui.theme.NavigationRailAppTheme
|
||||
import com.microsoft.device.display.samples.navigationrail.ui.view.GallerySections
|
||||
import com.microsoft.device.display.samples.navigationrail.ui.view.NavigationRailApp
|
||||
import com.microsoft.device.dualscreen.testutils.getString
|
||||
import com.microsoft.device.dualscreen.testutils.simulateVerticalFold
|
||||
import com.microsoft.device.dualscreen.testing.getString
|
||||
import com.microsoft.device.dualscreen.testing.simulateVerticalFoldingFeature
|
||||
import com.microsoft.device.dualscreen.windowstate.WindowState
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -64,8 +64,8 @@ class PaneSynchronizationTest {
|
|||
}
|
||||
}
|
||||
|
||||
// Simulate a vertical fold
|
||||
publisherRule.simulateVerticalFold(composeTestRule)
|
||||
// Simulate a vertical foldFeature
|
||||
publisherRule.simulateVerticalFoldingFeature(composeTestRule)
|
||||
|
||||
for (gallery in GallerySections.values()) {
|
||||
// Click on gallery
|
||||
|
@ -89,8 +89,8 @@ class PaneSynchronizationTest {
|
|||
}
|
||||
}
|
||||
|
||||
// Simulate a vertical fold
|
||||
publisherRule.simulateVerticalFold(composeTestRule)
|
||||
// Simulate a vertical foldFeature
|
||||
publisherRule.simulateVerticalFoldingFeature(composeTestRule)
|
||||
|
||||
GallerySections.values().forEachIndexed { i, gallery ->
|
||||
// Click on gallery
|
||||
|
|
|
@ -8,7 +8,7 @@ products:
|
|||
description: "Samples showing how to use Jetpack Compose to achieve dual-screen user interface patterns."
|
||||
urlFragment: all
|
||||
---
|
||||
![build-test-check](https://github.com/microsoft/surface-duo-compose-samples/actions/workflows/build_test_check.yml/badge.svg) ![Compose Version](https://img.shields.io/badge/Jetpack%20Compose-1.1.0‐rc03-brightgreen)
|
||||
![build-test-check](https://github.com/microsoft/surface-duo-compose-samples/actions/workflows/build_test_check.yml/badge.svg) ![Compose Version](https://img.shields.io/badge/Jetpack%20Compose-1.1.0-brightgreen)
|
||||
|
||||
# Surface Duo Jetpack Compose Samples
|
||||
|
||||
|
@ -26,7 +26,7 @@ Please check out our page on [Jetpack Compose for Microsoft Surface Duo](https:/
|
|||
|
||||
## Prerequisites
|
||||
|
||||
- Jetpack Compose version: `1.1.0-rc03`
|
||||
- Jetpack Compose version: `1.1.0`
|
||||
|
||||
- Jetpack WindowManager version: `1.0.0`
|
||||
|
||||
|
@ -34,7 +34,7 @@ Please check out our page on [Jetpack Compose for Microsoft Surface Duo](https:/
|
|||
|
||||
## Microsoft Compose Libraries
|
||||
|
||||
The samples are built with Microsoft Compose libraries, [TwoPaneLayout](https://github.com/microsoft/surface-duo-compose-sdk/tree/main/TwoPaneLayout) and [WindowState](https://github.com/microsoft/surface-duo-compose-sdk/tree/main/WindowState).
|
||||
The samples are built with Microsoft Compose libraries, [TwoPaneLayout](https://github.com/microsoft/surface-duo-compose-sdk/tree/main/TwoPaneLayout), [WindowState](https://github.com/microsoft/surface-duo-compose-sdk/tree/main/WindowState) and [ComposeTesting](https://github.com/microsoft/surface-duo-compose-sdk/tree/main/ComposeTesting).
|
||||
|
||||
## Contents
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
/build
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
testInstrumentationRunner rootProject.ext.config.testInstrumentationRunner
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation testDependencies.composeJunit
|
||||
implementation testDependencies.windowTest
|
||||
implementation testDependencies.uiAutomator
|
||||
implementation androidxDependencies.ktxCore
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
~ Licensed under the MIT License.
|
||||
-->
|
||||
|
||||
<manifest package="com.microsoft.device.dualscreen.testutils" />
|
|
@ -1,94 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
package com.microsoft.device.dualscreen.testutils
|
||||
|
||||
import android.util.Log
|
||||
import android.view.Surface
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
|
||||
/**
|
||||
* DEVICE MODEL
|
||||
* -----------------------------------------------------------------------------------------------
|
||||
* The DeviceModel class and related helper functions can be used in dualscreen UI tests to help
|
||||
* calculate coordinates for simulated swipe gestures. Device properties are determined using
|
||||
* UiDevice.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Coordinates taken from dual portrait point of view
|
||||
* Dimensions available here: https://docs.microsoft.com /dual-screen/android/surface-duo-dimensions
|
||||
*/
|
||||
enum class DeviceModel(
|
||||
val paneWidth: Int,
|
||||
val paneHeight: Int,
|
||||
val foldSize: Int,
|
||||
val leftX: Int = paneWidth / 2,
|
||||
val rightX: Int = leftX + paneWidth + foldSize,
|
||||
val middleX: Int = paneWidth + foldSize / 2,
|
||||
val middleY: Int = paneHeight / 2,
|
||||
val bottomY: Int,
|
||||
val spanSteps: Int = 400,
|
||||
val unspanSteps: Int = 200,
|
||||
val switchSteps: Int = 100,
|
||||
val closeSteps: Int = 50,
|
||||
) {
|
||||
SurfaceDuo(paneWidth = 1350, paneHeight = 1800, foldSize = 84, bottomY = 1780),
|
||||
SurfaceDuo2(paneWidth = 1344, paneHeight = 1892, foldSize = 66, bottomY = 1870),
|
||||
Other(paneWidth = 0, paneHeight = 0, foldSize = 0, bottomY = 0);
|
||||
|
||||
override fun toString(): String {
|
||||
return "$name [leftX: $leftX rightX: $rightX middleX: $middleX middleY: $middleY bottomY: $bottomY]"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a device is a Surface Duo model
|
||||
*/
|
||||
fun UiDevice.isSurfaceDuo(): Boolean {
|
||||
val model = getDeviceModel()
|
||||
return model == DeviceModel.SurfaceDuo || model == DeviceModel.SurfaceDuo2
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hinge/fold size of a device
|
||||
*/
|
||||
fun UiDevice.getFoldSize(): Int {
|
||||
return getDeviceModel().foldSize
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the model of a device based on display width and height
|
||||
*/
|
||||
fun UiDevice.getDeviceModel(): DeviceModel {
|
||||
Log.d(
|
||||
"DeviceModel",
|
||||
"w: $displayWidth h: $displayHeight rotation: $displayRotation"
|
||||
)
|
||||
|
||||
return when (displayRotation) {
|
||||
Surface.ROTATION_0, Surface.ROTATION_180 -> getModelFromPaneWidth(displayWidth)
|
||||
Surface.ROTATION_90, Surface.ROTATION_270 -> getModelFromPaneWidth(displayHeight)
|
||||
else -> throw Error("Unknown rotation state $displayRotation")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to compare the pane width of a device to the pane widths of the defined device
|
||||
* models
|
||||
*/
|
||||
private fun UiDevice.getModelFromPaneWidth(paneWidth: Int): DeviceModel {
|
||||
for (model in DeviceModel.values()) {
|
||||
// pane width could be the width of a single pane, or the width of two panes + the width
|
||||
// of the hinge
|
||||
if (paneWidth == model.paneWidth || paneWidth == model.paneWidth * 2 + model.foldSize)
|
||||
return model
|
||||
}
|
||||
Log.d(
|
||||
"DeviceModel",
|
||||
"Unknown dualscreen device dimensions $displayWidth $displayHeight"
|
||||
)
|
||||
return DeviceModel.Other
|
||||
}
|
|
@ -1,137 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
package com.microsoft.device.dualscreen.testutils
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule
|
||||
import androidx.window.layout.FoldingFeature
|
||||
import androidx.window.testing.layout.FoldingFeature
|
||||
import androidx.window.testing.layout.TestWindowLayoutInfo
|
||||
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
|
||||
|
||||
/**
|
||||
* FOLD HELPER
|
||||
* -----------------------------------------------------------------------------------------------
|
||||
* These functions can be used in foldable UI tests to simulate the present of vertical and
|
||||
* horizontal folds/hinges. The folds are simulated using TestWindowLayoutInfo.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Simulate a vertical fold
|
||||
*
|
||||
* @param activityRule: test activity rule
|
||||
* @param center: location of center of fold
|
||||
* @param size: size of fold
|
||||
* @param state: state of fold
|
||||
*/
|
||||
fun <A : ComponentActivity> WindowLayoutInfoPublisherRule.simulateVerticalFold(
|
||||
activityRule: ActivityScenarioRule<A>,
|
||||
center: Int = -1,
|
||||
size: Int = 0,
|
||||
state: FoldingFeature.State = FoldingFeature.State.HALF_OPENED
|
||||
) {
|
||||
simulateFold(activityRule, center, size, state, FoldingFeature.Orientation.VERTICAL)
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate a horizontal fold
|
||||
*
|
||||
* @param activityRule: test activity rule
|
||||
* @param center: location of center of fold
|
||||
* @param size: size of fold
|
||||
* @param state: state of fold
|
||||
*/
|
||||
fun <A : ComponentActivity> WindowLayoutInfoPublisherRule.simulateHorizontalFold(
|
||||
activityRule: ActivityScenarioRule<A>,
|
||||
center: Int = -1,
|
||||
size: Int = 0,
|
||||
state: FoldingFeature.State = FoldingFeature.State.HALF_OPENED
|
||||
) {
|
||||
simulateFold(activityRule, center, size, state, FoldingFeature.Orientation.HORIZONTAL)
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate a fold with the given properties
|
||||
*
|
||||
* @param activityRule: test activity rule
|
||||
* @param center: location of center of fold
|
||||
* @param size: size of fold
|
||||
* @param state: state of fold
|
||||
* @param orientation: orientation of fold
|
||||
*/
|
||||
fun <A : ComponentActivity> WindowLayoutInfoPublisherRule.simulateFold(
|
||||
activityRule: ActivityScenarioRule<A>,
|
||||
center: Int,
|
||||
size: Int,
|
||||
state: FoldingFeature.State,
|
||||
orientation: FoldingFeature.Orientation,
|
||||
) {
|
||||
activityRule.scenario.onActivity { activity ->
|
||||
val fold = FoldingFeature(
|
||||
activity = activity,
|
||||
center = center,
|
||||
size = size,
|
||||
state = state,
|
||||
orientation = orientation
|
||||
)
|
||||
val windowLayoutInfo = TestWindowLayoutInfo(listOf(fold))
|
||||
overrideWindowLayoutInfo(windowLayoutInfo)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate a vertical fold in a Compose test
|
||||
*
|
||||
* @param composeTestRule: Compose android test rule
|
||||
* @param center: location of center of fold
|
||||
* @param size: size of fold
|
||||
* @param state: state of fold
|
||||
*/
|
||||
fun <A : ComponentActivity> WindowLayoutInfoPublisherRule.simulateVerticalFold(
|
||||
composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<A>, A>,
|
||||
center: Int = -1,
|
||||
size: Int = 0,
|
||||
state: FoldingFeature.State = FoldingFeature.State.HALF_OPENED
|
||||
) {
|
||||
simulateVerticalFold(composeTestRule.activityRule, center, size, state)
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate a horizontal fold in a Compose test
|
||||
*
|
||||
* @param composeTestRule: Compose android test rule
|
||||
* @param center: location of center of fold
|
||||
* @param size: size of fold
|
||||
* @param state: state of fold
|
||||
*/
|
||||
fun <A : ComponentActivity> WindowLayoutInfoPublisherRule.simulateHorizontalFold(
|
||||
composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<A>, A>,
|
||||
center: Int = -1,
|
||||
size: Int = 0,
|
||||
state: FoldingFeature.State = FoldingFeature.State.HALF_OPENED
|
||||
) {
|
||||
simulateHorizontalFold(composeTestRule.activityRule, center, size, state)
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate a fold with the given properties in a Compose test
|
||||
*
|
||||
* @param composeTestRule: Compose android test rule
|
||||
* @param center: location of center of fold
|
||||
* @param size: size of fold
|
||||
* @param state: state of fold
|
||||
* @param orientation: orientation of fold
|
||||
*/
|
||||
fun <A : ComponentActivity> WindowLayoutInfoPublisherRule.simulateFold(
|
||||
composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<A>, A>,
|
||||
center: Int,
|
||||
size: Int,
|
||||
state: FoldingFeature.State,
|
||||
orientation: FoldingFeature.Orientation,
|
||||
) {
|
||||
simulateFold(composeTestRule.activityRule, center, size, state, orientation)
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
package com.microsoft.device.dualscreen.testutils
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.ui.graphics.asAndroidBitmap
|
||||
import androidx.compose.ui.test.SemanticsNodeInteraction
|
||||
import androidx.compose.ui.test.captureToImage
|
||||
import androidx.core.graphics.toColor
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import java.io.FileOutputStream
|
||||
import kotlin.math.abs
|
||||
|
||||
/**
|
||||
* SCREENSHOT COMPARATOR
|
||||
* -----------------------------------------------------------------------------------------------
|
||||
* These functions can be used to take, save, and compare screenshots of composables in UI tests.
|
||||
*
|
||||
* Based on ScreenshotComparator.kt in the TestingCodelab project from the official Jetpack Compose codelab samples
|
||||
* https://github.com/googlecodelabs/android-compose-codelabs/blob/main/TestingCodelab/app/src/androidTest/java/com/example/compose/rally/ScreenshotComparator.kt
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check whether a screenshot of the current node matches the reference image
|
||||
*
|
||||
* @param referenceAsset: name of reference image (must be stored in the androidTest/assets folder)
|
||||
* @param node: Semantics Node to take screenshot of
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun assertScreenshotMatchesReference(
|
||||
referenceAsset: String,
|
||||
node: SemanticsNodeInteraction
|
||||
) {
|
||||
// Capture screenshot of composable
|
||||
val bitmap = node.captureToImage().asAndroidBitmap()
|
||||
|
||||
// Load reference screenshot from instrumentation test assets
|
||||
val referenceBitmap = InstrumentationRegistry.getInstrumentation().context.resources.assets.open(referenceAsset)
|
||||
.use { BitmapFactory.decodeStream(it) }
|
||||
|
||||
// Compare bitmaps
|
||||
assert(referenceBitmap.compare(bitmap))
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a screenshot of the current node to the device's internal storage - screenshots can be
|
||||
* retrieved via adb as described in these instructions:
|
||||
* https://stackoverflow.com/questions/40323126/where-do-i-find-the-saved-image-in-android
|
||||
*
|
||||
* @param filename: filename (including extension) of the screenshot
|
||||
* @param node: Semantics Node to take screenshot of
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun saveScreenshotToDevice(filename: String, node: SemanticsNodeInteraction) {
|
||||
// Capture screenshot of composable
|
||||
val bmp = node.captureToImage().asAndroidBitmap()
|
||||
|
||||
// Get path for saving file
|
||||
val path = InstrumentationRegistry.getInstrumentation().targetContext.filesDir.canonicalPath
|
||||
|
||||
// Compress bitmap and send to file
|
||||
FileOutputStream("$path/$filename").use { out ->
|
||||
bmp.compress(Bitmap.CompressFormat.PNG, 100, out)
|
||||
}
|
||||
|
||||
Log.d("Screenshot Comparator", "Saved screenshot to $path/$filename")
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether two bitmaps are the same, allowing for a small percentage of different pixels
|
||||
* due to differences between devices
|
||||
*
|
||||
* @param other: Bitmap to compare to
|
||||
* @return true if at least 99% of the bitmap pixels match, false otherwise
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun Bitmap.compare(other: Bitmap): Boolean {
|
||||
if (this.width != other.width || this.height != other.height) {
|
||||
return false
|
||||
}
|
||||
// Compare row by row to save memory on device
|
||||
val row1 = IntArray(width)
|
||||
val row2 = IntArray(width)
|
||||
var numDiffs = 0
|
||||
|
||||
for (column in 0 until height) {
|
||||
// Read one row per bitmap and compare
|
||||
this.getRow(row1, column)
|
||||
other.getRow(row2, column)
|
||||
row1.forEachIndexed { index, element ->
|
||||
if (!row2[index].isSimilarColor(element)) {
|
||||
numDiffs++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Throw error if greater than 1% of the bitmap's pixels are different
|
||||
if (numDiffs > 0.01 * width * width) {
|
||||
Log.d("Screen Comparator", "Sizes match but bitmap content has differences in $numDiffs pixels")
|
||||
return false
|
||||
}
|
||||
Log.d("Screen Comparator", "Number of different pixels: $numDiffs")
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether two colors (represented by integer values) are similar
|
||||
*
|
||||
* @param other: Color integer to compare to
|
||||
* @return true if all color components are within 0.5% of the original, false otherwise
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private fun Int.isSimilarColor(other: Int): Boolean {
|
||||
// Convert ints to color values
|
||||
val expectedColor = this.toColor()
|
||||
val actualColor = other.toColor()
|
||||
|
||||
// Compare individual color components
|
||||
val expectedComponents = expectedColor.components
|
||||
val actualComponents = actualColor.components
|
||||
|
||||
// Check that color components have equal sizes
|
||||
if (expectedComponents.size != actualComponents.size) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check that each color component is similar (within +/- 0.5%)
|
||||
val percentError = 0.005
|
||||
expectedComponents.forEachIndexed { index, comp ->
|
||||
// Calculate the error allowance for the color component
|
||||
val maxColorVal = expectedColor.colorSpace.getMaxValue(index)
|
||||
val minColorVal = expectedColor.colorSpace.getMinValue(index)
|
||||
val errorAllowance = percentError * (maxColorVal - minColorVal)
|
||||
|
||||
// Compare color component values
|
||||
if (abs(actualComponents[index] - comp) > errorAllowance) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun Bitmap.getRow(pixels: IntArray, column: Int) {
|
||||
this.getPixels(pixels, 0, width, 0, column, width, 1)
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
package com.microsoft.device.dualscreen.testutils
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule
|
||||
|
||||
/**
|
||||
* STRING HELPER
|
||||
* -----------------------------------------------------------------------------------------------
|
||||
* These functions can be used for string operations in UI tests to simplify testing code.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get resource string inside Compose test with resource id
|
||||
*
|
||||
* @param id: string resource id
|
||||
*/
|
||||
fun <A : ComponentActivity> AndroidComposeTestRule<ActivityScenarioRule<A>, A>.getString(@StringRes id: Int): String {
|
||||
return activity.getString(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get resource string inside Compose test with resource id and arguments
|
||||
*
|
||||
* @param id: string resource id
|
||||
* @param formatArgs: arguments to string
|
||||
*/
|
||||
fun <A : ComponentActivity> AndroidComposeTestRule<ActivityScenarioRule<A>, A>.getString(@StringRes id: Int, vararg formatArgs: Any): String {
|
||||
return activity.getString(id, *formatArgs)
|
||||
}
|
|
@ -1,172 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
package com.microsoft.device.dualscreen.testutils
|
||||
|
||||
import android.view.Surface
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
|
||||
/**
|
||||
* SWIPE HELPER
|
||||
* -----------------------------------------------------------------------------------------------
|
||||
* These functions can be used in dualscreen UI tests to simulate swipe gestures that affect
|
||||
* app display. The swipes are simulated using UiDevice, and the coordinates are calculated based
|
||||
* on the display width/height of the testing device (see DeviceModel.kt).
|
||||
*
|
||||
* Available gestures:
|
||||
* - span (display app in two panes)
|
||||
* - unspan (display app in one pane)
|
||||
* - close (close app)
|
||||
* - switch (switch app from one pane to the other)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helper method that sets up/cleans up a dualscreen swipe operation for automated testing
|
||||
* (freezes rotation, retrieves device model, performs swipe, unfreezes rotation)
|
||||
*/
|
||||
private fun UiDevice.dualscreenSwipeWrapper(swipe: (DeviceModel) -> Boolean) {
|
||||
freezeRotation()
|
||||
|
||||
val model = getDeviceModel()
|
||||
swipe(model)
|
||||
|
||||
unfreezeRotation()
|
||||
}
|
||||
|
||||
/**
|
||||
* Span app from the top/left pane
|
||||
*/
|
||||
fun UiDevice.spanFromStart() {
|
||||
dualscreenSwipeWrapper { model ->
|
||||
when (displayRotation) {
|
||||
Surface.ROTATION_0, Surface.ROTATION_180 ->
|
||||
swipe(model.leftX, model.bottomY, model.middleX, model.middleY, model.spanSteps)
|
||||
Surface.ROTATION_270 ->
|
||||
swipe(model.bottomY, model.leftX, model.middleY, model.middleX, model.spanSteps)
|
||||
Surface.ROTATION_90 ->
|
||||
swipe(model.bottomY, model.leftX, model.middleY, model.middleX, model.spanSteps)
|
||||
else -> throw Error("Unknown rotation state $displayRotation")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Span app from the bottom/right pane
|
||||
*/
|
||||
fun UiDevice.spanFromEnd() {
|
||||
dualscreenSwipeWrapper { model ->
|
||||
when (displayRotation) {
|
||||
Surface.ROTATION_0, Surface.ROTATION_180 ->
|
||||
swipe(model.rightX, model.bottomY, model.middleX, model.middleY, model.spanSteps)
|
||||
Surface.ROTATION_270 ->
|
||||
swipe(model.bottomY, model.rightX, model.middleY, model.middleX, model.spanSteps)
|
||||
Surface.ROTATION_90 ->
|
||||
swipe(model.bottomY, model.rightX, model.middleY, model.middleX, model.spanSteps)
|
||||
else -> throw Error("Unknown rotation state $displayRotation")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unspan app to the top/left pane
|
||||
*/
|
||||
fun UiDevice.unspanToStart() {
|
||||
dualscreenSwipeWrapper { model ->
|
||||
when (displayRotation) {
|
||||
Surface.ROTATION_0, Surface.ROTATION_180 ->
|
||||
swipe(model.rightX, model.bottomY, model.leftX, model.middleY, model.unspanSteps)
|
||||
Surface.ROTATION_270 ->
|
||||
swipe(model.bottomY, model.rightX, model.middleY, model.leftX, model.unspanSteps)
|
||||
Surface.ROTATION_90 ->
|
||||
swipe(model.bottomY, model.rightX, model.middleY, model.leftX, model.unspanSteps)
|
||||
else -> throw Error("Unknown rotation state $displayRotation")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unspan app to bottom/right pane
|
||||
*/
|
||||
fun UiDevice.unspanToEnd() {
|
||||
dualscreenSwipeWrapper { model ->
|
||||
when (displayRotation) {
|
||||
Surface.ROTATION_0, Surface.ROTATION_180 ->
|
||||
swipe(model.leftX, model.bottomY, model.rightX, model.middleY, model.unspanSteps)
|
||||
Surface.ROTATION_270 ->
|
||||
swipe(model.bottomY, model.leftX, model.middleY, model.rightX, model.unspanSteps)
|
||||
Surface.ROTATION_90 ->
|
||||
swipe(model.bottomY, model.leftX, model.middleY, model.rightX, model.unspanSteps)
|
||||
else -> throw Error("Unknown rotation state $displayRotation")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch app from bottom/right pane to top/left pane
|
||||
*/
|
||||
fun UiDevice.switchToStart() {
|
||||
dualscreenSwipeWrapper { model ->
|
||||
when (displayRotation) {
|
||||
Surface.ROTATION_0, Surface.ROTATION_180 ->
|
||||
swipe(model.rightX, model.bottomY, model.leftX, model.middleY, model.switchSteps)
|
||||
Surface.ROTATION_270 ->
|
||||
swipe(model.bottomY, model.rightX, model.middleY, model.leftX, model.switchSteps)
|
||||
Surface.ROTATION_90 ->
|
||||
swipe(model.bottomY, model.rightX, model.middleY, model.leftX, model.switchSteps)
|
||||
else -> throw Error("Unknown rotation state $displayRotation")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch app from top/left pane to bottom/right pane
|
||||
*/
|
||||
fun UiDevice.switchToEnd() {
|
||||
dualscreenSwipeWrapper { model ->
|
||||
when (displayRotation) {
|
||||
Surface.ROTATION_0, Surface.ROTATION_180 ->
|
||||
swipe(model.leftX, model.bottomY, model.rightX, model.middleY, model.switchSteps)
|
||||
Surface.ROTATION_270 ->
|
||||
swipe(model.bottomY, model.leftX, model.middleY, model.rightX, model.switchSteps)
|
||||
Surface.ROTATION_90 ->
|
||||
swipe(model.bottomY, model.leftX, model.middleY, model.rightX, model.switchSteps)
|
||||
else -> throw Error("Unknown rotation state $displayRotation")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close app from top/left pane
|
||||
*/
|
||||
fun UiDevice.closeStart() {
|
||||
dualscreenSwipeWrapper { model ->
|
||||
when (displayRotation) {
|
||||
Surface.ROTATION_0, Surface.ROTATION_180 ->
|
||||
swipe(model.leftX, model.bottomY, model.leftX, model.middleY, model.closeSteps)
|
||||
Surface.ROTATION_270 ->
|
||||
swipe(model.bottomY, model.leftX, model.middleY, model.leftX, model.closeSteps)
|
||||
Surface.ROTATION_90 ->
|
||||
swipe(model.bottomY, model.leftX, model.middleY, model.leftX, model.closeSteps)
|
||||
else -> throw Error("Unknown rotation state $displayRotation")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close app from bottom/right pane
|
||||
*/
|
||||
fun UiDevice.closeEnd() {
|
||||
dualscreenSwipeWrapper { model ->
|
||||
when (displayRotation) {
|
||||
Surface.ROTATION_0, Surface.ROTATION_180 ->
|
||||
swipe(model.rightX, model.bottomY, model.rightX, model.middleY, model.closeSteps)
|
||||
Surface.ROTATION_270 ->
|
||||
swipe(model.bottomY, model.rightX, model.middleY, model.rightX, model.closeSteps)
|
||||
Surface.ROTATION_90 ->
|
||||
swipe(model.bottomY, model.rightX, model.middleY, model.rightX, model.closeSteps)
|
||||
else -> throw Error("Unknown rotation state $displayRotation")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
package com.microsoft.device.dualscreen.testutils
|
||||
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
|
||||
data class ZoomCoordinates(
|
||||
val leftOuter: Offset,
|
||||
val leftInner: Offset,
|
||||
val rightInner: Offset,
|
||||
val rightOuter: Offset
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return "[LeftOuter: $leftOuter, LeftInner: $leftInner, RightInner: $rightInner, RightOuter: $rightOuter]"
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
package com.microsoft.device.dualscreen.testutils
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.test.GestureScope
|
||||
import androidx.compose.ui.test.bottom
|
||||
import androidx.compose.ui.test.left
|
||||
import androidx.compose.ui.test.pinch
|
||||
import androidx.compose.ui.test.right
|
||||
import androidx.compose.ui.test.top
|
||||
|
||||
/**
|
||||
* ZOOM HELPER
|
||||
* -----------------------------------------------------------------------------------------------
|
||||
* These functions can be used to perform zooming gestures during Compose UI tests.
|
||||
*/
|
||||
|
||||
const val PINCH_MILLIS: Long = 500
|
||||
|
||||
/**
|
||||
* Performs a zoom in gesture (swipes start towards center then move outwards)
|
||||
*
|
||||
* @param pinchMillis: number of milliseconds it takes to perform the pinch (default 500)
|
||||
*/
|
||||
fun GestureScope.zoomIn(pinchMillis: Long = PINCH_MILLIS) {
|
||||
val coords = setupZoomCoords()
|
||||
Log.d("ZoomHelper", "Zooming in: $coords")
|
||||
pinch(coords.leftInner, coords.leftOuter, coords.rightInner, coords.rightOuter, pinchMillis)
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a zoom out gesture (swipes start towards center then move outwards)
|
||||
*
|
||||
* @param pinchMillis: number of milliseconds it takes to perform the pinch (default 500)
|
||||
*/
|
||||
fun GestureScope.zoomOut(pinchMillis: Long = PINCH_MILLIS) {
|
||||
val coords = setupZoomCoords()
|
||||
Log.d("ZoomHelper", "Zooming out: $coords")
|
||||
pinch(coords.leftOuter, coords.leftInner, coords.rightOuter, coords.rightInner, pinchMillis)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates starting and ending zoom coordinates based on GestureScope properties
|
||||
*/
|
||||
private fun GestureScope.setupZoomCoords(): ZoomCoordinates {
|
||||
// Get height and width of node
|
||||
val width = (right - left).toLong()
|
||||
val height = (bottom - top).toLong()
|
||||
|
||||
// Set up zoom coordinates offsets
|
||||
return ZoomCoordinates(
|
||||
leftOuter = Offset(left + width * 0.25f, top + height * 0.3f),
|
||||
leftInner = Offset(left + width * 0.45f, top + height * 0.3f),
|
||||
rightInner = Offset(left + width * 0.55f, height * 0.7f),
|
||||
rightOuter = Offset(left + width * 0.75f, height * 0.7f),
|
||||
)
|
||||
}
|
|
@ -61,7 +61,6 @@ dependencies {
|
|||
androidTestImplementation testDependencies.espressoCore
|
||||
androidTestImplementation testDependencies.composeUITest
|
||||
androidTestImplementation testDependencies.composeJunit
|
||||
androidTestImplementation testDependencies.windowTest
|
||||
androidTestImplementation testDependencies.uiAutomator
|
||||
androidTestImplementation project(':TestUtils')
|
||||
androidTestImplementation microsoftDependencies.composeTesting
|
||||
}
|
|
@ -22,7 +22,7 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.microsoft.device.display.samples.twopage.ui.theme.TwoPageAppTheme
|
||||
import com.microsoft.device.display.samples.twopage.utils.PageLayout
|
||||
import com.microsoft.device.dualscreen.testutils.getString
|
||||
import com.microsoft.device.dualscreen.testing.getString
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
|
|
|
@ -19,9 +19,9 @@ import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
|
|||
import com.microsoft.device.display.samples.twopage.ui.theme.TwoPageAppTheme
|
||||
import com.microsoft.device.display.samples.twopage.ui.view.TwoPageApp
|
||||
import com.microsoft.device.display.samples.twopage.ui.view.TwoPageAppContent
|
||||
import com.microsoft.device.dualscreen.testutils.getString
|
||||
import com.microsoft.device.dualscreen.testutils.simulateHorizontalFold
|
||||
import com.microsoft.device.dualscreen.testutils.simulateVerticalFold
|
||||
import com.microsoft.device.dualscreen.testing.getString
|
||||
import com.microsoft.device.dualscreen.testing.simulateHorizontalFoldingFeature
|
||||
import com.microsoft.device.dualscreen.testing.simulateVerticalFoldingFeature
|
||||
import com.microsoft.device.dualscreen.windowstate.WindowState
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -65,8 +65,8 @@ class PageSwipeTest {
|
|||
}
|
||||
}
|
||||
|
||||
// Simulate horizontal fold
|
||||
publisherRule.simulateHorizontalFold(composeTestRule)
|
||||
// Simulate horizontal foldFeature
|
||||
publisherRule.simulateHorizontalFoldingFeature(composeTestRule)
|
||||
|
||||
swipeOnePageAtATime()
|
||||
}
|
||||
|
@ -132,8 +132,8 @@ class PageSwipeTest {
|
|||
}
|
||||
}
|
||||
|
||||
// Simulate vertical fold
|
||||
publisherRule.simulateVerticalFold(composeTestRule)
|
||||
// Simulate vertical foldFeature
|
||||
publisherRule.simulateVerticalFoldingFeature(composeTestRule)
|
||||
|
||||
val pageTags = listOf(R.string.page1_tag, R.string.page2_tag, R.string.page3_tag, R.string.page4_tag)
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
ext {
|
||||
gradlePluginVersion = '7.1.0'
|
||||
gradlePluginVersion = '7.1.1'
|
||||
kotlinVersion = "1.6.10"
|
||||
compileSdkVersion = 31
|
||||
targetSdkVersion = compileSdkVersion
|
||||
|
@ -30,7 +30,7 @@ ext {
|
|||
]
|
||||
|
||||
// Compose dependencies
|
||||
composeVersion = "1.1.0-rc03"
|
||||
composeVersion = "1.1.0"
|
||||
activityComposeVersion = "1.4.0"
|
||||
navigationComposeVersion = '2.4.0'
|
||||
composeDependencies = [
|
||||
|
@ -57,7 +57,6 @@ ext {
|
|||
composeJunit : "androidx.compose.ui:ui-test-junit4:$composeVersion",
|
||||
composeUITestManifest : "androidx.compose.ui:ui-test-manifest:$composeVersion",
|
||||
uiAutomator : "androidx.test.uiautomator:uiautomator:$uiAutomatorVersion",
|
||||
windowTest : "androidx.window:window-testing:$windowVersion",
|
||||
espressoCore : "androidx.test.espresso:espresso-core:$espressoVersion",
|
||||
androidJunit : "junit:junit:$junitVersion",
|
||||
mockitoCore : "org.mockito:mockito-core:$mockitoVersion",
|
||||
|
@ -72,8 +71,10 @@ ext {
|
|||
// Microsoft dependencies
|
||||
twoPaneLayoutVersion = "1.0.0-alpha10"
|
||||
windowStateVersion = "1.0.0-alpha02"
|
||||
composeTestingVersion = "1.0.0-alpha02"
|
||||
microsoftDependencies = [
|
||||
twoPaneLayout : "com.microsoft.device.dualscreen:twopanelayout:$twoPaneLayoutVersion",
|
||||
windowState : "com.microsoft.device.dualscreen:windowstate:$windowStateVersion",
|
||||
composeTesting : "com.microsoft.device.dualscreen.testing:testing-compose:$composeTestingVersion"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -5,4 +5,4 @@
|
|||
|
||||
rootProject.name = "ComposeSamples"
|
||||
include ':ListDetail', ':CompanionPane', ':DualView', ':ExtendedCanvas', ':ComposeGallery',
|
||||
':TwoPage', ':NavigationRail', ':TestUtils'
|
||||
':TwoPage', ':NavigationRail'
|
||||
|
|
Загрузка…
Ссылка в новой задаче