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.espressoCore
|
||||||
androidTestImplementation testDependencies.composeUITest
|
androidTestImplementation testDependencies.composeUITest
|
||||||
androidTestImplementation testDependencies.composeJunit
|
androidTestImplementation testDependencies.composeJunit
|
||||||
androidTestImplementation testDependencies.windowTest
|
|
||||||
androidTestImplementation testDependencies.uiAutomator
|
androidTestImplementation testDependencies.uiAutomator
|
||||||
androidTestImplementation project(':TestUtils')
|
androidTestImplementation microsoftDependencies.composeTesting
|
||||||
|
|
||||||
implementation googleDependencies.material
|
implementation googleDependencies.material
|
||||||
}
|
}
|
|
@ -13,9 +13,9 @@ import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import androidx.test.uiautomator.UiDevice
|
import androidx.test.uiautomator.UiDevice
|
||||||
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
|
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
|
||||||
import com.microsoft.device.display.samples.companionpane.ui.theme.CompanionPaneAppTheme
|
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.testutils.simulateHorizontalFold
|
import com.microsoft.device.dualscreen.testing.simulateHorizontalFoldingFeature
|
||||||
import com.microsoft.device.dualscreen.testutils.simulateVerticalFold
|
import com.microsoft.device.dualscreen.testing.simulateVerticalFoldingFeature
|
||||||
import com.microsoft.device.dualscreen.windowstate.WindowMode
|
import com.microsoft.device.dualscreen.windowstate.WindowMode
|
||||||
import com.microsoft.device.dualscreen.windowstate.rememberWindowState
|
import com.microsoft.device.dualscreen.windowstate.rememberWindowState
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
|
@ -94,8 +94,8 @@ class LayoutTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulate vertical fold
|
// Simulate vertical foldingFeature
|
||||||
publisherRule.simulateVerticalFold(composeTestRule)
|
publisherRule.simulateVerticalFoldingFeature(composeTestRule)
|
||||||
|
|
||||||
// Check that dual portrait panes are shown
|
// Check that dual portrait panes are shown
|
||||||
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.dual_port_pane1))
|
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.dual_port_pane1))
|
||||||
|
@ -116,8 +116,8 @@ class LayoutTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulate horizontal fold
|
// Simulate horizontal foldingFeature
|
||||||
publisherRule.simulateHorizontalFold(composeTestRule)
|
publisherRule.simulateHorizontalFoldingFeature(composeTestRule)
|
||||||
|
|
||||||
// Check that dual landscape panes are shown
|
// Check that dual landscape panes are shown
|
||||||
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.dual_land_pane1))
|
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.dual_land_pane1))
|
||||||
|
@ -137,8 +137,8 @@ class LayoutTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulate horizontal fold
|
// Simulate horizontal foldingFeature
|
||||||
publisherRule.simulateHorizontalFold(composeTestRule)
|
publisherRule.simulateHorizontalFoldingFeature(composeTestRule)
|
||||||
|
|
||||||
// Check that dual landscape panes are shown
|
// Check that dual landscape panes are shown
|
||||||
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.dual_land_pane1))
|
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.dual_land_pane1))
|
||||||
|
@ -146,8 +146,8 @@ class LayoutTest {
|
||||||
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.dual_land_pane2))
|
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.dual_land_pane2))
|
||||||
.assertIsDisplayed()
|
.assertIsDisplayed()
|
||||||
|
|
||||||
// Simulate vertical fold
|
// Simulate vertical foldingFeature
|
||||||
publisherRule.simulateVerticalFold(composeTestRule)
|
publisherRule.simulateVerticalFoldingFeature(composeTestRule)
|
||||||
|
|
||||||
// Check that dual portrait panes are shown
|
// Check that dual portrait panes are shown
|
||||||
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.dual_port_pane1))
|
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.compose.ui.test.onNodeWithTag
|
||||||
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
|
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
|
||||||
import com.microsoft.device.display.samples.companionpane.ui.theme.CompanionPaneAppTheme
|
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 com.microsoft.device.dualscreen.windowstate.WindowMode
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
|
@ -61,8 +61,7 @@ dependencies {
|
||||||
androidTestImplementation testDependencies.espressoCore
|
androidTestImplementation testDependencies.espressoCore
|
||||||
androidTestImplementation testDependencies.composeUITest
|
androidTestImplementation testDependencies.composeUITest
|
||||||
androidTestImplementation testDependencies.composeJunit
|
androidTestImplementation testDependencies.composeJunit
|
||||||
androidTestImplementation testDependencies.windowTest
|
androidTestImplementation microsoftDependencies.composeTesting
|
||||||
androidTestImplementation project(':TestUtils')
|
|
||||||
|
|
||||||
debugImplementation testDependencies.composeUITestManifest
|
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.models.DataProvider
|
||||||
import com.microsoft.device.display.samples.composegallery.ui.ComposeGalleryTheme
|
import com.microsoft.device.display.samples.composegallery.ui.ComposeGalleryTheme
|
||||||
import com.microsoft.device.display.samples.composegallery.ui.view.ComposeGalleryApp
|
import com.microsoft.device.display.samples.composegallery.ui.view.ComposeGalleryApp
|
||||||
import com.microsoft.device.dualscreen.testutils.getString
|
import com.microsoft.device.dualscreen.testing.getString
|
||||||
import com.microsoft.device.dualscreen.testutils.simulateHorizontalFold
|
import com.microsoft.device.dualscreen.testing.simulateHorizontalFoldingFeature
|
||||||
import com.microsoft.device.dualscreen.testutils.simulateVerticalFold
|
import com.microsoft.device.dualscreen.testing.simulateVerticalFoldingFeature
|
||||||
import com.microsoft.device.dualscreen.windowstate.WindowState
|
import com.microsoft.device.dualscreen.windowstate.WindowState
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -56,8 +56,8 @@ class PaneSynchronizationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulate a vertical fold so two panes are visible
|
// Simulate a vertical foldFeature so two panes are visible
|
||||||
publisherRule.simulateVerticalFold(composeTestRule)
|
publisherRule.simulateVerticalFoldingFeature(composeTestRule)
|
||||||
|
|
||||||
// Scroll to end of list
|
// Scroll to end of list
|
||||||
val index = 7
|
val index = 7
|
||||||
|
@ -107,8 +107,8 @@ class PaneSynchronizationTest {
|
||||||
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.switch_to_list))
|
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.switch_to_list))
|
||||||
.performClick()
|
.performClick()
|
||||||
|
|
||||||
// Simulate a vertical fold so two panes are visible
|
// Simulate a vertical foldFeature so two panes are visible
|
||||||
publisherRule.simulateVerticalFold(composeTestRule)
|
publisherRule.simulateVerticalFoldingFeature(composeTestRule)
|
||||||
|
|
||||||
// Check that third surface duo image is still displayed
|
// Check that third surface duo image is still displayed
|
||||||
composeTestRule.onNode(
|
composeTestRule.onNode(
|
||||||
|
@ -129,8 +129,8 @@ class PaneSynchronizationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulate a horizontal fold so one pane is still visible
|
// Simulate a horizontal foldFeature so one pane is still visible
|
||||||
publisherRule.simulateHorizontalFold(composeTestRule)
|
publisherRule.simulateHorizontalFoldingFeature(composeTestRule)
|
||||||
|
|
||||||
// Check that the list view is displayed
|
// Check that the list view is displayed
|
||||||
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.gallery_list))
|
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.ComposeGalleryApp
|
||||||
import com.microsoft.device.display.samples.composegallery.ui.view.DetailPane
|
import com.microsoft.device.display.samples.composegallery.ui.view.DetailPane
|
||||||
import com.microsoft.device.display.samples.composegallery.ui.view.ListPane
|
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 com.microsoft.device.dualscreen.windowstate.WindowState
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
|
@ -61,7 +61,6 @@ dependencies {
|
||||||
androidTestImplementation testDependencies.espressoCore
|
androidTestImplementation testDependencies.espressoCore
|
||||||
androidTestImplementation testDependencies.composeUITest
|
androidTestImplementation testDependencies.composeUITest
|
||||||
androidTestImplementation testDependencies.composeJunit
|
androidTestImplementation testDependencies.composeJunit
|
||||||
androidTestImplementation testDependencies.windowTest
|
|
||||||
androidTestImplementation testDependencies.uiAutomator
|
androidTestImplementation testDependencies.uiAutomator
|
||||||
androidTestImplementation project(':TestUtils')
|
androidTestImplementation microsoftDependencies.composeTesting
|
||||||
}
|
}
|
|
@ -6,30 +6,28 @@
|
||||||
package com.microsoft.device.display.samples.dualview
|
package com.microsoft.device.display.samples.dualview
|
||||||
|
|
||||||
import androidx.compose.ui.test.ExperimentalTestApi
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.SemanticsNodeInteraction
|
|
||||||
import androidx.compose.ui.test.hasAnySibling
|
import androidx.compose.ui.test.hasAnySibling
|
||||||
import androidx.compose.ui.test.hasContentDescription
|
import androidx.compose.ui.test.hasContentDescription
|
||||||
import androidx.compose.ui.test.hasParent
|
import androidx.compose.ui.test.hasParent
|
||||||
import androidx.compose.ui.test.hasScrollToIndexAction
|
import androidx.compose.ui.test.hasScrollToIndexAction
|
||||||
import androidx.compose.ui.test.hasText
|
import androidx.compose.ui.test.hasText
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
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.performClick
|
||||||
import androidx.compose.ui.test.performScrollToIndex
|
import androidx.compose.ui.test.performScrollToIndex
|
||||||
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
|
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
|
||||||
import com.microsoft.device.display.samples.dualview.models.restaurants
|
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.theme.DualViewAppTheme
|
||||||
import com.microsoft.device.display.samples.dualview.ui.view.DualViewApp
|
import com.microsoft.device.display.samples.dualview.ui.view.DualViewApp
|
||||||
import com.microsoft.device.dualscreen.testutils.assertScreenshotMatchesReference
|
import com.microsoft.device.dualscreen.testing.getString
|
||||||
import com.microsoft.device.dualscreen.testutils.getString
|
import com.microsoft.device.dualscreen.testing.simulateHorizontalFoldingFeature
|
||||||
import com.microsoft.device.dualscreen.testutils.simulateHorizontalFold
|
|
||||||
import com.microsoft.device.dualscreen.windowstate.WindowState
|
import com.microsoft.device.dualscreen.windowstate.WindowState
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.RuleChain
|
import org.junit.rules.RuleChain
|
||||||
import org.junit.rules.TestRule
|
import org.junit.rules.TestRule
|
||||||
|
|
||||||
const val VIEW_SIZE = 400
|
const val nonSelectionOption = -1
|
||||||
|
|
||||||
class MapImageTest {
|
class MapImageTest {
|
||||||
private val composeTestRule = createAndroidComposeRule<MainActivity>()
|
private val composeTestRule = createAndroidComposeRule<MainActivity>()
|
||||||
|
@ -51,39 +49,15 @@ class MapImageTest {
|
||||||
fun app_horizontalFold_mapUpdatesAfterRestaurantClick() {
|
fun app_horizontalFold_mapUpdatesAfterRestaurantClick() {
|
||||||
composeTestRule.setContent {
|
composeTestRule.setContent {
|
||||||
DualViewAppTheme {
|
DualViewAppTheme {
|
||||||
DualViewApp(WindowState(hasFold = true, foldIsHorizontal = true, foldIsSeparating = true), viewSize = VIEW_SIZE)
|
DualViewApp(WindowState(hasFold = true, foldIsHorizontal = true, foldIsSeparating = true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulate horizontal fold
|
// Simulate horizontal foldFeature
|
||||||
publisherRule.simulateHorizontalFold(composeTestRule)
|
publisherRule.simulateHorizontalFoldingFeature(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")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Scrolls to and clicks each item in the restaurant list
|
||||||
restaurants.forEachIndexed { index, rest ->
|
restaurants.forEachIndexed { index, rest ->
|
||||||
// Perform specified action
|
|
||||||
action(
|
|
||||||
referenceAssets[index],
|
|
||||||
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.map_image))
|
|
||||||
)
|
|
||||||
|
|
||||||
// Scroll to next restaurant item
|
// Scroll to next restaurant item
|
||||||
composeTestRule.onNode(hasScrollToIndexAction()).performScrollToIndex(index)
|
composeTestRule.onNode(hasScrollToIndexAction()).performScrollToIndex(index)
|
||||||
|
|
||||||
|
@ -93,6 +67,18 @@ class MapImageTest {
|
||||||
hasAnySibling(hasText(composeTestRule.getString(R.string.list_title)))
|
hasAnySibling(hasText(composeTestRule.getString(R.string.list_title)))
|
||||||
)
|
)
|
||||||
).performClick()
|
).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.RestaurantListView
|
||||||
import com.microsoft.device.display.samples.dualview.ui.view.TextStyleKey
|
import com.microsoft.device.display.samples.dualview.ui.view.TextStyleKey
|
||||||
import com.microsoft.device.display.samples.dualview.ui.view.narrowWidth
|
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.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.RuleChain
|
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.DualViewApp
|
||||||
import com.microsoft.device.display.samples.dualview.ui.view.MapTopBar
|
import com.microsoft.device.display.samples.dualview.ui.view.MapTopBar
|
||||||
import com.microsoft.device.display.samples.dualview.ui.view.RestaurantTopBar
|
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 com.microsoft.device.dualscreen.windowstate.WindowState
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
|
@ -18,7 +18,7 @@ import com.microsoft.device.dualscreen.windowstate.WindowState
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DualViewApp(windowState: WindowState, viewSize: Int? = null) {
|
fun DualViewApp(windowState: WindowState) {
|
||||||
var selectedIndex by rememberSaveable { mutableStateOf(-1) }
|
var selectedIndex by rememberSaveable { mutableStateOf(-1) }
|
||||||
val updateSelectedIndex: (Int) -> Unit = { newIndex -> selectedIndex = newIndex }
|
val updateSelectedIndex: (Int) -> Unit = { newIndex -> selectedIndex = newIndex }
|
||||||
val pane1SizeWidthDp = windowState.pane1SizeDp().width.dp
|
val pane1SizeWidthDp = windowState.pane1SizeDp().width.dp
|
||||||
|
@ -30,7 +30,6 @@ fun DualViewApp(windowState: WindowState, viewSize: Int? = null) {
|
||||||
viewWidth = with(LocalDensity.current) { viewWidth.toPx() }.roundToInt(),
|
viewWidth = with(LocalDensity.current) { viewWidth.toPx() }.roundToInt(),
|
||||||
selectedIndex = selectedIndex,
|
selectedIndex = selectedIndex,
|
||||||
updateSelectedIndex = updateSelectedIndex,
|
updateSelectedIndex = updateSelectedIndex,
|
||||||
viewSize = viewSize
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,10 +39,9 @@ fun DualViewAppContent(
|
||||||
viewWidth: Int,
|
viewWidth: Int,
|
||||||
selectedIndex: Int,
|
selectedIndex: Int,
|
||||||
updateSelectedIndex: (Int) -> Unit,
|
updateSelectedIndex: (Int) -> Unit,
|
||||||
viewSize: Int? = null
|
|
||||||
) {
|
) {
|
||||||
TwoPaneLayout(
|
TwoPaneLayout(
|
||||||
pane1 = { RestaurantViewWithTopBar(isDualScreen, viewWidth, selectedIndex, updateSelectedIndex) },
|
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
|
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.Image
|
||||||
import androidx.compose.foundation.gestures.detectDragGestures
|
import androidx.compose.foundation.gestures.detectDragGestures
|
||||||
import androidx.compose.foundation.gestures.rememberTransformableState
|
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.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.offset
|
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.Icon
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.IconButton
|
||||||
import androidx.compose.material.Scaffold
|
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.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
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.IntOffset
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.microsoft.device.display.samples.dualview.R
|
import com.microsoft.device.display.samples.dualview.R
|
||||||
import com.microsoft.device.display.samples.dualview.models.restaurants
|
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 com.microsoft.device.dualscreen.twopanelayout.navigateToPane1
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
private const val nonSelection = -1
|
private const val nonSelection = -1
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MapViewWithTopBar(isDualScreen: Boolean, selectedIndex: Int, viewSize: Int?) {
|
fun MapViewWithTopBar(isDualScreen: Boolean, selectedIndex: Int) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { MapTopBar(isDualScreen, viewSize != null) }
|
topBar = { MapTopBar(isDualScreen) }
|
||||||
) {
|
) {
|
||||||
MapView(selectedIndex, viewSize)
|
MapView(selectedIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MapTopBar(isDualScreen: Boolean, customViewSize: Boolean = false) {
|
fun MapTopBar(isDualScreen: Boolean) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
modifier = Modifier.testTag(stringResource(R.string.map_top_bar)),
|
modifier = Modifier.testTag(stringResource(R.string.map_top_bar)),
|
||||||
title = {
|
title = {
|
||||||
|
@ -72,7 +68,6 @@ fun MapTopBar(isDualScreen: Boolean, customViewSize: Boolean = false) {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
elevation = if (customViewSize) 0.dp else AppBarDefaults.TopAppBarElevation,
|
|
||||||
actions = {
|
actions = {
|
||||||
if (!isDualScreen) {
|
if (!isDualScreen) {
|
||||||
IconButton(onClick = { navigateToPane1() }) {
|
IconButton(onClick = { navigateToPane1() }) {
|
||||||
|
@ -88,29 +83,28 @@ fun MapTopBar(isDualScreen: Boolean, customViewSize: Boolean = false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MapView(selectedIndex: Int, viewSize: Int? = null) {
|
fun MapView(selectedIndex: Int) {
|
||||||
var selectedMapId = R.drawable.unselected_map
|
var selectedMapId = R.drawable.unselected_map
|
||||||
|
var selectedTitleId = R.string.map_description
|
||||||
if (selectedIndex > nonSelection) {
|
if (selectedIndex > nonSelection) {
|
||||||
selectedMapId = restaurants[selectedIndex].mapImageResourceId
|
selectedMapId = restaurants[selectedIndex].mapImageResourceId
|
||||||
|
selectedTitleId = restaurants[selectedIndex].description
|
||||||
}
|
}
|
||||||
val viewSizeDp = with(LocalDensity.current) { viewSize?.toDp() }
|
Box(
|
||||||
val modifier = if (viewSizeDp == null) Modifier.clipToBounds() else Modifier.requiredSize(viewSizeDp)
|
modifier = Modifier
|
||||||
|
.clipToBounds()
|
||||||
Box(modifier = modifier.testTag(stringResource(R.string.map_image))) {
|
.testTag(
|
||||||
ScalableImageView(imageId = selectedMapId)
|
stringResource(R.string.map_image)
|
||||||
}
|
)
|
||||||
}
|
) {
|
||||||
|
ScalableImageView(
|
||||||
@Preview
|
imageId = selectedMapId, selectedTitleId
|
||||||
@Composable
|
)
|
||||||
fun MapViewScreenshotPreview() {
|
|
||||||
DualViewAppTheme {
|
|
||||||
MapView(7, 150)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ScalableImageView(imageId: Int) {
|
fun ScalableImageView(@DrawableRes imageId: Int, @StringRes descriptionId: Int) {
|
||||||
val minScale = 1f
|
val minScale = 1f
|
||||||
val maxScale = 8f
|
val maxScale = 8f
|
||||||
val defaultScale = 2f
|
val defaultScale = 2f
|
||||||
|
@ -124,7 +118,7 @@ fun ScalableImageView(imageId: Int) {
|
||||||
|
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(id = imageId),
|
painter = painterResource(id = imageId),
|
||||||
contentDescription = stringResource(R.string.map_description),
|
contentDescription = stringResource(id = descriptionId),
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.graphicsLayer(
|
.graphicsLayer(
|
||||||
|
|
|
@ -53,6 +53,5 @@ dependencies {
|
||||||
androidTestImplementation testDependencies.androidxTestRules
|
androidTestImplementation testDependencies.androidxTestRules
|
||||||
androidTestImplementation testDependencies.composeUITest
|
androidTestImplementation testDependencies.composeUITest
|
||||||
androidTestImplementation testDependencies.composeJunit
|
androidTestImplementation testDependencies.composeJunit
|
||||||
androidTestImplementation testDependencies.windowTest
|
androidTestImplementation microsoftDependencies.composeTesting
|
||||||
androidTestImplementation project(':TestUtils')
|
|
||||||
}
|
}
|
|
@ -5,20 +5,18 @@
|
||||||
|
|
||||||
package com.microsoft.device.display.samples.extendedcanvas
|
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.assertIsDisplayed
|
||||||
import androidx.compose.ui.test.captureToImage
|
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
import androidx.compose.ui.test.onNodeWithTag
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
import androidx.compose.ui.test.performGesture
|
|
||||||
import androidx.compose.ui.test.performTouchInput
|
import androidx.compose.ui.test.performTouchInput
|
||||||
import androidx.compose.ui.test.swipeLeft
|
import androidx.compose.ui.test.swipeLeft
|
||||||
import com.microsoft.device.display.samples.extendedcanvas.ui.ExtendedCanvasAppsTheme
|
import com.microsoft.device.display.samples.extendedcanvas.ui.ExtendedCanvasAppsTheme
|
||||||
import com.microsoft.device.dualscreen.testutils.compare
|
import com.microsoft.device.dualscreen.testing.getString
|
||||||
import com.microsoft.device.dualscreen.testutils.getString
|
|
||||||
import com.microsoft.device.dualscreen.testutils.zoomIn
|
|
||||||
import com.microsoft.device.dualscreen.testutils.zoomOut
|
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
@ -52,84 +50,25 @@ class ExtendedCanvasTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.map_image))
|
// Get node for the map image
|
||||||
.assertIsDisplayed()
|
val mapImageNode =
|
||||||
|
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.map_image))
|
||||||
|
|
||||||
// Take screenshot of initial map image state
|
// Assert the map image is shown
|
||||||
val original = composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.map_image))
|
mapImageNode.assertIsDisplayed()
|
||||||
.captureToImage()
|
|
||||||
.asAndroidBitmap()
|
|
||||||
|
|
||||||
// Drag the map to the left
|
// Drag the map to the left
|
||||||
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.map_image))
|
mapImageNode.performTouchInput { swipeLeft() }
|
||||||
.performTouchInput { swipeLeft() }
|
|
||||||
|
|
||||||
// Take screenshot of new map image state
|
val defaultImageOffset = Offset.Zero
|
||||||
val afterSwipe =
|
|
||||||
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.map_image))
|
|
||||||
.captureToImage()
|
|
||||||
.asAndroidBitmap()
|
|
||||||
|
|
||||||
// Make sure bitmaps are not the same anymore
|
// Make sure bitmap offset is not the same anymore
|
||||||
assert(!original.compare(afterSwipe))
|
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
|
private fun SemanticsNodeInteraction.assertImageOffsetNotEquals(imageOffset: Offset) =
|
||||||
fun mapView_testImageZoomsIn() {
|
assert(!SemanticsMatcher.expectValue(ImageOffsetKey, imageOffset))
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,12 +28,18 @@ import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
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.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
val ImageOffsetKey = SemanticsPropertyKey<Offset>("ImageOffsetKey")
|
||||||
|
var SemanticsPropertyReceiver.imageOffset by ImageOffsetKey
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ExtendedCanvasApp() {
|
fun ExtendedCanvasApp() {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
@ -74,6 +80,9 @@ fun ScaleImage() {
|
||||||
contentDescription = stringResource(R.string.map_image),
|
contentDescription = stringResource(R.string.map_image),
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.semantics {
|
||||||
|
imageOffset = offset
|
||||||
|
}
|
||||||
.graphicsLayer(
|
.graphicsLayer(
|
||||||
scaleX = maxOf(minScale, minOf(maxScale, scale)),
|
scaleX = maxOf(minScale, minOf(maxScale, scale)),
|
||||||
scaleY = maxOf(minScale, minOf(maxScale, scale)),
|
scaleY = maxOf(minScale, minOf(maxScale, scale)),
|
||||||
|
|
|
@ -61,7 +61,6 @@ dependencies {
|
||||||
androidTestImplementation testDependencies.espressoCore
|
androidTestImplementation testDependencies.espressoCore
|
||||||
androidTestImplementation testDependencies.composeUITest
|
androidTestImplementation testDependencies.composeUITest
|
||||||
androidTestImplementation testDependencies.composeJunit
|
androidTestImplementation testDependencies.composeJunit
|
||||||
androidTestImplementation testDependencies.windowTest
|
|
||||||
androidTestImplementation testDependencies.uiAutomator
|
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.models.images
|
||||||
import com.microsoft.device.display.samples.listdetail.ui.theme.ListDetailComposeSampleTheme
|
import com.microsoft.device.display.samples.listdetail.ui.theme.ListDetailComposeSampleTheme
|
||||||
import com.microsoft.device.display.samples.listdetail.ui.view.ListDetailApp
|
import com.microsoft.device.display.samples.listdetail.ui.view.ListDetailApp
|
||||||
import com.microsoft.device.dualscreen.testutils.getString
|
import com.microsoft.device.dualscreen.testing.getString
|
||||||
import com.microsoft.device.dualscreen.testutils.simulateHorizontalFold
|
import com.microsoft.device.dualscreen.testing.simulateHorizontalFoldingFeature
|
||||||
import com.microsoft.device.dualscreen.testutils.simulateVerticalFold
|
import com.microsoft.device.dualscreen.testing.simulateVerticalFoldingFeature
|
||||||
import com.microsoft.device.dualscreen.windowstate.WindowState
|
import com.microsoft.device.dualscreen.windowstate.WindowState
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -41,8 +41,8 @@ class ListDetailTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulate vertical fold
|
// Simulate vertical foldFeature
|
||||||
publisherRule.simulateVerticalFold(composeTestRule)
|
publisherRule.simulateVerticalFoldingFeature(composeTestRule)
|
||||||
|
|
||||||
images.forEachIndexed { index, _ ->
|
images.forEachIndexed { index, _ ->
|
||||||
// Click on list item
|
// Click on list item
|
||||||
|
@ -69,8 +69,8 @@ class ListDetailTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulate horizontal fold
|
// Simulate horizontal foldFeature
|
||||||
publisherRule.simulateHorizontalFold(composeTestRule)
|
publisherRule.simulateHorizontalFoldingFeature(composeTestRule)
|
||||||
|
|
||||||
// Assert the list view is now shown
|
// Assert the list view is now shown
|
||||||
composeTestRule.onNodeWithTag(
|
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.DetailViewTopBar
|
||||||
import com.microsoft.device.display.samples.listdetail.ui.view.ListDetailApp
|
import com.microsoft.device.display.samples.listdetail.ui.view.ListDetailApp
|
||||||
import com.microsoft.device.display.samples.listdetail.ui.view.ListViewTopBar
|
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 com.microsoft.device.dualscreen.windowstate.WindowState
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
|
@ -58,7 +58,6 @@ dependencies {
|
||||||
androidTestImplementation testDependencies.espressoCore
|
androidTestImplementation testDependencies.espressoCore
|
||||||
androidTestImplementation testDependencies.composeUITest
|
androidTestImplementation testDependencies.composeUITest
|
||||||
androidTestImplementation testDependencies.composeJunit
|
androidTestImplementation testDependencies.composeJunit
|
||||||
androidTestImplementation testDependencies.windowTest
|
|
||||||
androidTestImplementation testDependencies.uiAutomator
|
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.ItemDetailView
|
||||||
import com.microsoft.device.display.samples.navigationrail.ui.view.NavigationRailApp
|
import com.microsoft.device.display.samples.navigationrail.ui.view.NavigationRailApp
|
||||||
import com.microsoft.device.display.samples.navigationrail.ui.view.Pane2
|
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 com.microsoft.device.dualscreen.windowstate.WindowState
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
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.theme.NavigationRailAppTheme
|
||||||
import com.microsoft.device.display.samples.navigationrail.ui.view.GalleryView
|
import com.microsoft.device.display.samples.navigationrail.ui.view.GalleryView
|
||||||
import com.microsoft.device.display.samples.navigationrail.ui.view.NavigationRailApp
|
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 com.microsoft.device.dualscreen.windowstate.WindowState
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
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.theme.NavigationRailAppTheme
|
||||||
import com.microsoft.device.display.samples.navigationrail.ui.view.GallerySections
|
import com.microsoft.device.display.samples.navigationrail.ui.view.GallerySections
|
||||||
import com.microsoft.device.display.samples.navigationrail.ui.view.NavigationRailApp
|
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 com.microsoft.device.dualscreen.windowstate.WindowState
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
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.theme.NavigationRailAppTheme
|
||||||
import com.microsoft.device.display.samples.navigationrail.ui.view.GallerySections
|
import com.microsoft.device.display.samples.navigationrail.ui.view.GallerySections
|
||||||
import com.microsoft.device.display.samples.navigationrail.ui.view.NavigationRailApp
|
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.testutils.simulateVerticalFold
|
import com.microsoft.device.dualscreen.testing.simulateVerticalFoldingFeature
|
||||||
import com.microsoft.device.dualscreen.windowstate.WindowState
|
import com.microsoft.device.dualscreen.windowstate.WindowState
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -64,8 +64,8 @@ class PaneSynchronizationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulate a vertical fold
|
// Simulate a vertical foldFeature
|
||||||
publisherRule.simulateVerticalFold(composeTestRule)
|
publisherRule.simulateVerticalFoldingFeature(composeTestRule)
|
||||||
|
|
||||||
for (gallery in GallerySections.values()) {
|
for (gallery in GallerySections.values()) {
|
||||||
// Click on gallery
|
// Click on gallery
|
||||||
|
@ -89,8 +89,8 @@ class PaneSynchronizationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulate a vertical fold
|
// Simulate a vertical foldFeature
|
||||||
publisherRule.simulateVerticalFold(composeTestRule)
|
publisherRule.simulateVerticalFoldingFeature(composeTestRule)
|
||||||
|
|
||||||
GallerySections.values().forEachIndexed { i, gallery ->
|
GallerySections.values().forEachIndexed { i, gallery ->
|
||||||
// Click on gallery
|
// Click on gallery
|
||||||
|
|
|
@ -8,7 +8,7 @@ products:
|
||||||
description: "Samples showing how to use Jetpack Compose to achieve dual-screen user interface patterns."
|
description: "Samples showing how to use Jetpack Compose to achieve dual-screen user interface patterns."
|
||||||
urlFragment: all
|
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
|
# Surface Duo Jetpack Compose Samples
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ Please check out our page on [Jetpack Compose for Microsoft Surface Duo](https:/
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- Jetpack Compose version: `1.1.0-rc03`
|
- Jetpack Compose version: `1.1.0`
|
||||||
|
|
||||||
- Jetpack WindowManager version: `1.0.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
|
## 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
|
## 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.espressoCore
|
||||||
androidTestImplementation testDependencies.composeUITest
|
androidTestImplementation testDependencies.composeUITest
|
||||||
androidTestImplementation testDependencies.composeJunit
|
androidTestImplementation testDependencies.composeJunit
|
||||||
androidTestImplementation testDependencies.windowTest
|
|
||||||
androidTestImplementation testDependencies.uiAutomator
|
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 androidx.compose.ui.unit.dp
|
||||||
import com.microsoft.device.display.samples.twopage.ui.theme.TwoPageAppTheme
|
import com.microsoft.device.display.samples.twopage.ui.theme.TwoPageAppTheme
|
||||||
import com.microsoft.device.display.samples.twopage.utils.PageLayout
|
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.Rule
|
||||||
import org.junit.Test
|
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.theme.TwoPageAppTheme
|
||||||
import com.microsoft.device.display.samples.twopage.ui.view.TwoPageApp
|
import com.microsoft.device.display.samples.twopage.ui.view.TwoPageApp
|
||||||
import com.microsoft.device.display.samples.twopage.ui.view.TwoPageAppContent
|
import com.microsoft.device.display.samples.twopage.ui.view.TwoPageAppContent
|
||||||
import com.microsoft.device.dualscreen.testutils.getString
|
import com.microsoft.device.dualscreen.testing.getString
|
||||||
import com.microsoft.device.dualscreen.testutils.simulateHorizontalFold
|
import com.microsoft.device.dualscreen.testing.simulateHorizontalFoldingFeature
|
||||||
import com.microsoft.device.dualscreen.testutils.simulateVerticalFold
|
import com.microsoft.device.dualscreen.testing.simulateVerticalFoldingFeature
|
||||||
import com.microsoft.device.dualscreen.windowstate.WindowState
|
import com.microsoft.device.dualscreen.windowstate.WindowState
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -65,8 +65,8 @@ class PageSwipeTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulate horizontal fold
|
// Simulate horizontal foldFeature
|
||||||
publisherRule.simulateHorizontalFold(composeTestRule)
|
publisherRule.simulateHorizontalFoldingFeature(composeTestRule)
|
||||||
|
|
||||||
swipeOnePageAtATime()
|
swipeOnePageAtATime()
|
||||||
}
|
}
|
||||||
|
@ -132,8 +132,8 @@ class PageSwipeTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulate vertical fold
|
// Simulate vertical foldFeature
|
||||||
publisherRule.simulateVerticalFold(composeTestRule)
|
publisherRule.simulateVerticalFoldingFeature(composeTestRule)
|
||||||
|
|
||||||
val pageTags = listOf(R.string.page1_tag, R.string.page2_tag, R.string.page3_tag, R.string.page4_tag)
|
val pageTags = listOf(R.string.page1_tag, R.string.page2_tag, R.string.page3_tag, R.string.page4_tag)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
gradlePluginVersion = '7.1.0'
|
gradlePluginVersion = '7.1.1'
|
||||||
kotlinVersion = "1.6.10"
|
kotlinVersion = "1.6.10"
|
||||||
compileSdkVersion = 31
|
compileSdkVersion = 31
|
||||||
targetSdkVersion = compileSdkVersion
|
targetSdkVersion = compileSdkVersion
|
||||||
|
@ -30,7 +30,7 @@ ext {
|
||||||
]
|
]
|
||||||
|
|
||||||
// Compose dependencies
|
// Compose dependencies
|
||||||
composeVersion = "1.1.0-rc03"
|
composeVersion = "1.1.0"
|
||||||
activityComposeVersion = "1.4.0"
|
activityComposeVersion = "1.4.0"
|
||||||
navigationComposeVersion = '2.4.0'
|
navigationComposeVersion = '2.4.0'
|
||||||
composeDependencies = [
|
composeDependencies = [
|
||||||
|
@ -57,7 +57,6 @@ ext {
|
||||||
composeJunit : "androidx.compose.ui:ui-test-junit4:$composeVersion",
|
composeJunit : "androidx.compose.ui:ui-test-junit4:$composeVersion",
|
||||||
composeUITestManifest : "androidx.compose.ui:ui-test-manifest:$composeVersion",
|
composeUITestManifest : "androidx.compose.ui:ui-test-manifest:$composeVersion",
|
||||||
uiAutomator : "androidx.test.uiautomator:uiautomator:$uiAutomatorVersion",
|
uiAutomator : "androidx.test.uiautomator:uiautomator:$uiAutomatorVersion",
|
||||||
windowTest : "androidx.window:window-testing:$windowVersion",
|
|
||||||
espressoCore : "androidx.test.espresso:espresso-core:$espressoVersion",
|
espressoCore : "androidx.test.espresso:espresso-core:$espressoVersion",
|
||||||
androidJunit : "junit:junit:$junitVersion",
|
androidJunit : "junit:junit:$junitVersion",
|
||||||
mockitoCore : "org.mockito:mockito-core:$mockitoVersion",
|
mockitoCore : "org.mockito:mockito-core:$mockitoVersion",
|
||||||
|
@ -72,8 +71,10 @@ ext {
|
||||||
// Microsoft dependencies
|
// Microsoft dependencies
|
||||||
twoPaneLayoutVersion = "1.0.0-alpha10"
|
twoPaneLayoutVersion = "1.0.0-alpha10"
|
||||||
windowStateVersion = "1.0.0-alpha02"
|
windowStateVersion = "1.0.0-alpha02"
|
||||||
|
composeTestingVersion = "1.0.0-alpha02"
|
||||||
microsoftDependencies = [
|
microsoftDependencies = [
|
||||||
twoPaneLayout : "com.microsoft.device.dualscreen:twopanelayout:$twoPaneLayoutVersion",
|
twoPaneLayout : "com.microsoft.device.dualscreen:twopanelayout:$twoPaneLayoutVersion",
|
||||||
windowState : "com.microsoft.device.dualscreen:windowstate:$windowStateVersion",
|
windowState : "com.microsoft.device.dualscreen:windowstate:$windowStateVersion",
|
||||||
|
composeTesting : "com.microsoft.device.dualscreen.testing:testing-compose:$composeTestingVersion"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,4 +5,4 @@
|
||||||
|
|
||||||
rootProject.name = "ComposeSamples"
|
rootProject.name = "ComposeSamples"
|
||||||
include ':ListDetail', ':CompanionPane', ':DualView', ':ExtendedCanvas', ':ComposeGallery',
|
include ':ListDetail', ':CompanionPane', ':DualView', ':ExtendedCanvas', ':ComposeGallery',
|
||||||
':TwoPage', ':NavigationRail', ':TestUtils'
|
':TwoPage', ':NavigationRail'
|
||||||
|
|
Загрузка…
Ссылка в новой задаче