Add 38 UI tests for LongPressMenu
This commit is contained in:
parent
2a28e7a805
commit
dc7ed1ce8d
@ -4,12 +4,14 @@ import android.content.Context
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.ui.test.SemanticsNodeInteraction
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.Assert.fail
|
||||
|
||||
val ctx: Context
|
||||
get() = ApplicationProvider.getApplicationContext<Context>()
|
||||
get() = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
|
||||
fun putBooleanInPrefs(@StringRes key: Int, value: Boolean) {
|
||||
PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||
@ -37,3 +39,33 @@ fun SemanticsNodeInteractionsProvider.onNodeWithText(
|
||||
): SemanticsNodeInteraction {
|
||||
return this.onNodeWithText(ctx.getString(text), substring, ignoreCase, useUnmergedTree)
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as the original `onNodeWithContentDescription` except that this takes a [StringRes] instead of a [String].
|
||||
*/
|
||||
fun SemanticsNodeInteractionsProvider.onNodeWithContentDescription(
|
||||
@StringRes text: Int,
|
||||
substring: Boolean = false,
|
||||
ignoreCase: Boolean = false,
|
||||
useUnmergedTree: Boolean = false
|
||||
): SemanticsNodeInteraction {
|
||||
return this.onNodeWithContentDescription(ctx.getString(text), substring, ignoreCase, useUnmergedTree)
|
||||
}
|
||||
|
||||
fun <T : Comparable<T>> assertInRange(l: T, r: T, value: T) {
|
||||
if (l > r) {
|
||||
fail("Invalid range passed to `assertInRange`: [$l, $r]")
|
||||
}
|
||||
if (value !in l..r) {
|
||||
fail("Expected $value to be in range [$l, $r]")
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : Comparable<T>> assertNotInRange(l: T, r: T, value: T) {
|
||||
if (l > r) {
|
||||
fail("Invalid range passed to `assertInRange`: [$l, $r]")
|
||||
}
|
||||
if (value in l..r) {
|
||||
fail("Expected $value to NOT be in range [$l, $r]")
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,651 @@
|
||||
package org.schabi.newpipe.ui.components.menu
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.semantics.SemanticsProperties.ProgressBarRangeInfo
|
||||
import androidx.compose.ui.test.SemanticsMatcher
|
||||
import androidx.compose.ui.test.assert
|
||||
import androidx.compose.ui.test.assertHasClickAction
|
||||
import androidx.compose.ui.test.assertHasNoClickAction
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.assertIsEnabled
|
||||
import androidx.compose.ui.test.assertIsNotEnabled
|
||||
import androidx.compose.ui.test.hasScrollAction
|
||||
import androidx.compose.ui.test.isDisplayed
|
||||
import androidx.compose.ui.test.isNotDisplayed
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onAllNodesWithTag
|
||||
import androidx.compose.ui.test.onFirst
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performTouchInput
|
||||
import androidx.compose.ui.test.swipeUp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.test.espresso.Espresso
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.device.DeviceInteraction.Companion.setDisplaySize
|
||||
import androidx.test.espresso.device.EspressoDevice.Companion.onDevice
|
||||
import androidx.test.espresso.device.rules.DisplaySizeRule
|
||||
import androidx.test.espresso.device.sizeclass.HeightSizeClass
|
||||
import androidx.test.espresso.device.sizeclass.WidthSizeClass
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.temporal.ChronoUnit
|
||||
import kotlinx.coroutines.delay
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.assertInRange
|
||||
import org.schabi.newpipe.assertNotInRange
|
||||
import org.schabi.newpipe.ctx
|
||||
import org.schabi.newpipe.extractor.stream.StreamType
|
||||
import org.schabi.newpipe.onNodeWithContentDescription
|
||||
import org.schabi.newpipe.onNodeWithText
|
||||
import org.schabi.newpipe.ui.components.menu.LongPressAction.Type.BackgroundShuffled
|
||||
import org.schabi.newpipe.ui.components.menu.LongPressAction.Type.Enqueue
|
||||
import org.schabi.newpipe.ui.components.menu.LongPressAction.Type.PlayWithKodi
|
||||
import org.schabi.newpipe.ui.components.menu.LongPressAction.Type.ShowChannelDetails
|
||||
import org.schabi.newpipe.ui.components.menu.LongPressAction.Type.ShowDetails
|
||||
import org.schabi.newpipe.ui.theme.AppTheme
|
||||
import org.schabi.newpipe.util.Either
|
||||
import org.schabi.newpipe.util.Localization
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class LongPressMenuTest {
|
||||
@get:Rule(order = 1)
|
||||
val composeRule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
// Test rule for restoring device to its starting display size when a test case finishes.
|
||||
// See https://developer.android.com/training/testing/different-screens/tools#resize-displays.
|
||||
@get:Rule(order = 2)
|
||||
val displaySizeRule: DisplaySizeRule = DisplaySizeRule()
|
||||
|
||||
private fun getLongPressable(
|
||||
title: String = "title",
|
||||
url: String? = "https://example.com",
|
||||
thumbnailUrl: String? = "android.resource://${ctx.packageName}/${R.drawable.placeholder_thumbnail_video}",
|
||||
uploader: String? = "uploader",
|
||||
uploaderUrl: String? = "https://example.com",
|
||||
viewCount: Long? = 42,
|
||||
streamType: StreamType? = StreamType.VIDEO_STREAM,
|
||||
uploadDate: Either<String, OffsetDateTime>? = Either.left("2026"),
|
||||
decoration: LongPressable.Decoration? = LongPressable.Decoration.Duration(9478)
|
||||
) = LongPressable(title, url, thumbnailUrl, uploader, uploaderUrl, viewCount, streamType, uploadDate, decoration)
|
||||
|
||||
private fun setLongPressMenu(
|
||||
longPressable: LongPressable = getLongPressable(),
|
||||
longPressActions: List<LongPressAction> = LongPressAction.Type.entries.map { it.buildAction { } },
|
||||
onDismissRequest: () -> Unit = {},
|
||||
isHeaderEnabled: Boolean = true,
|
||||
actionArrangement: List<LongPressAction.Type> = LongPressAction.Type.entries
|
||||
) {
|
||||
storeIsHeaderEnabledToSettings(ctx, isHeaderEnabled)
|
||||
storeLongPressActionArrangementToSettings(ctx, actionArrangement)
|
||||
composeRule.setContent {
|
||||
var isMenuVisible by rememberSaveable { mutableStateOf(true) }
|
||||
if (isMenuVisible) {
|
||||
AppTheme {
|
||||
LongPressMenu(longPressable, longPressActions, {
|
||||
isMenuVisible = false
|
||||
onDismissRequest()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the three tests below all call this function to ensure that the editor button is shown
|
||||
// independently of the long press menu contents
|
||||
private fun assertEditorIsEnteredAndExitedProperly() {
|
||||
composeRule.onNodeWithContentDescription(R.string.long_press_menu_enabled_actions_description)
|
||||
.assertDoesNotExist()
|
||||
composeRule.onNodeWithContentDescription(R.string.edit)
|
||||
.performClick()
|
||||
composeRule.waitUntil {
|
||||
composeRule.onNodeWithText(R.string.long_press_menu_enabled_actions)
|
||||
.isDisplayed()
|
||||
}
|
||||
|
||||
composeRule.onNodeWithContentDescription(R.string.edit)
|
||||
.assertDoesNotExist()
|
||||
Espresso.pressBack()
|
||||
composeRule.waitUntil {
|
||||
composeRule.onNodeWithContentDescription(R.string.edit)
|
||||
.isDisplayed()
|
||||
}
|
||||
|
||||
composeRule.onNodeWithText(R.string.long_press_menu_enabled_actions)
|
||||
.assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEditorButton1() {
|
||||
setLongPressMenu(isHeaderEnabled = false, actionArrangement = listOf())
|
||||
assertEditorIsEnteredAndExitedProperly()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEditorButton2() {
|
||||
setLongPressMenu(isHeaderEnabled = true, actionArrangement = listOf(PlayWithKodi))
|
||||
assertEditorIsEnteredAndExitedProperly()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEditorButton3() {
|
||||
setLongPressMenu(isHeaderEnabled = true, longPressActions = listOf(), actionArrangement = LongPressAction.Type.entries)
|
||||
assertEditorIsEnteredAndExitedProperly()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShowChannelDetails1() {
|
||||
var pressedCount = 0
|
||||
var dismissedCount = 0
|
||||
setLongPressMenu(
|
||||
onDismissRequest = { dismissedCount += 1 },
|
||||
longPressable = getLongPressable(uploader = "A", uploaderUrl = "https://example.com"),
|
||||
longPressActions = listOf(ShowChannelDetails.buildAction { pressedCount += 1 }),
|
||||
actionArrangement = listOf()
|
||||
)
|
||||
|
||||
composeRule.onNodeWithText(R.string.show_channel_details, substring = true)
|
||||
.assertDoesNotExist()
|
||||
composeRule.onNodeWithText("A", substring = true)
|
||||
.assertIsDisplayed()
|
||||
composeRule.onNodeWithTag("ShowChannelDetails")
|
||||
.performClick()
|
||||
composeRule.waitUntil { dismissedCount == 1 }
|
||||
assertEquals(1, pressedCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShowChannelDetails2() {
|
||||
var pressedCount = 0
|
||||
var dismissedCount = 0
|
||||
setLongPressMenu(
|
||||
onDismissRequest = { dismissedCount += 1 },
|
||||
longPressable = getLongPressable(uploader = null, uploaderUrl = "https://example.com"),
|
||||
longPressActions = listOf(ShowChannelDetails.buildAction { pressedCount += 1 }),
|
||||
actionArrangement = listOf()
|
||||
)
|
||||
|
||||
composeRule.onNodeWithText(R.string.show_channel_details, substring = true)
|
||||
.assertIsDisplayed()
|
||||
composeRule.onNodeWithTag("ShowChannelDetails")
|
||||
.performClick()
|
||||
composeRule.waitUntil { dismissedCount == 1 }
|
||||
assertEquals(1, pressedCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShowChannelDetails3() {
|
||||
var pressedCount = 0
|
||||
var dismissedCount = 0
|
||||
setLongPressMenu(
|
||||
onDismissRequest = { dismissedCount += 1 },
|
||||
longPressable = getLongPressable(uploader = null, uploaderUrl = null),
|
||||
longPressActions = listOf(ShowChannelDetails.buildAction { pressedCount += 1 }),
|
||||
actionArrangement = listOf()
|
||||
)
|
||||
|
||||
composeRule.onNodeWithText(R.string.show_channel_details, substring = true)
|
||||
.assertIsDisplayed()
|
||||
composeRule.onNodeWithTag("ShowChannelDetails")
|
||||
.performClick()
|
||||
composeRule.waitUntil { dismissedCount == 1 }
|
||||
assertEquals(1, pressedCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShowChannelDetails4() {
|
||||
setLongPressMenu(
|
||||
longPressable = getLongPressable(uploader = "A", uploaderUrl = "https://example.com"),
|
||||
longPressActions = listOf(),
|
||||
actionArrangement = listOf()
|
||||
)
|
||||
composeRule.onNodeWithTag("ShowChannelDetails")
|
||||
.assertHasNoClickAction()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShowChannelDetails5() {
|
||||
setLongPressMenu(
|
||||
longPressable = getLongPressable(uploader = "A", uploaderUrl = "https://example.com"),
|
||||
longPressActions = listOf(ShowChannelDetails.buildAction {}),
|
||||
actionArrangement = listOf(ShowChannelDetails)
|
||||
)
|
||||
composeRule.onNodeWithTag("ShowChannelDetails")
|
||||
.assertHasNoClickAction()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHeaderContents() {
|
||||
val longPressable = getLongPressable()
|
||||
setLongPressMenu(longPressable = longPressable)
|
||||
composeRule.onNodeWithText(longPressable.title)
|
||||
.assertIsDisplayed()
|
||||
composeRule.onNodeWithText(longPressable.uploader!!, substring = true)
|
||||
.assertIsDisplayed()
|
||||
composeRule.onNodeWithText(longPressable.uploadDate!!.value.toString(), substring = true)
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHeaderViewCount1() {
|
||||
setLongPressMenu(getLongPressable(viewCount = 0, streamType = StreamType.VIDEO_STREAM))
|
||||
composeRule.onNodeWithText(ctx.getString(R.string.no_views), substring = true)
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHeaderViewCount2() {
|
||||
setLongPressMenu(getLongPressable(viewCount = 0, streamType = StreamType.LIVE_STREAM))
|
||||
composeRule.onNodeWithText(ctx.getString(R.string.no_one_watching), substring = true)
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHeaderUploadDate1() {
|
||||
setLongPressMenu(getLongPressable(uploadDate = Either.left("abcd")))
|
||||
composeRule.onNodeWithText("abcd", substring = true)
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHeaderUploadDate2() {
|
||||
val date = OffsetDateTime.now()
|
||||
.minus(2, ChronoUnit.HOURS)
|
||||
.minus(50, ChronoUnit.MILLIS)
|
||||
setLongPressMenu(getLongPressable(uploadDate = Either.right(date)))
|
||||
composeRule.onNodeWithText("2 hours ago", substring = true)
|
||||
.assertIsDisplayed()
|
||||
composeRule.onNodeWithText(date.toString(), substring = true)
|
||||
.assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHeaderDuration() {
|
||||
setLongPressMenu(
|
||||
longPressable = getLongPressable(decoration = LongPressable.Decoration.Duration(123)),
|
||||
isHeaderEnabled = true
|
||||
)
|
||||
composeRule.onNodeWithTag("LongPressMenuHeader")
|
||||
.assertIsDisplayed()
|
||||
composeRule.onNodeWithTag("LongPressMenuHeaderThumbnail")
|
||||
.assertIsDisplayed()
|
||||
composeRule.onNodeWithText(Localization.getDurationString(123))
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHeaderLive() {
|
||||
setLongPressMenu(
|
||||
longPressable = getLongPressable(decoration = LongPressable.Decoration.Duration(123)),
|
||||
isHeaderEnabled = true
|
||||
)
|
||||
composeRule.onNodeWithTag("LongPressMenuHeader")
|
||||
.assertIsDisplayed()
|
||||
composeRule.onNodeWithTag("LongPressMenuHeaderThumbnail")
|
||||
.assertIsDisplayed()
|
||||
composeRule.onNodeWithText(Localization.getDurationString(123))
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHeaderPlaylist() {
|
||||
setLongPressMenu(
|
||||
longPressable = getLongPressable(decoration = LongPressable.Decoration.Duration(123)),
|
||||
isHeaderEnabled = true
|
||||
)
|
||||
composeRule.onNodeWithTag("LongPressMenuHeader")
|
||||
.assertIsDisplayed()
|
||||
composeRule.onNodeWithTag("LongPressMenuHeaderThumbnail")
|
||||
.assertIsDisplayed()
|
||||
composeRule.onNodeWithText(Localization.getDurationString(123))
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHeaderNoDecoration() {
|
||||
setLongPressMenu(
|
||||
longPressable = getLongPressable(decoration = null),
|
||||
isHeaderEnabled = true
|
||||
)
|
||||
composeRule.onNodeWithTag("LongPressMenuHeader")
|
||||
.assertIsDisplayed()
|
||||
composeRule.onNodeWithTag("LongPressMenuHeaderThumbnail")
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHeaderHidden() {
|
||||
setLongPressMenu(
|
||||
longPressable = getLongPressable(decoration = LongPressable.Decoration.Duration(123)),
|
||||
isHeaderEnabled = false
|
||||
)
|
||||
composeRule.onNodeWithTag("LongPressMenuHeader")
|
||||
.assertDoesNotExist()
|
||||
composeRule.onNodeWithText(Localization.getDurationString(123))
|
||||
.assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDurationNotShownIfNoThumbnailInHeader() {
|
||||
setLongPressMenu(
|
||||
longPressable = getLongPressable(
|
||||
thumbnailUrl = null,
|
||||
decoration = LongPressable.Decoration.Duration(123)
|
||||
)
|
||||
)
|
||||
composeRule.onNodeWithTag("LongPressMenuHeaderThumbnail")
|
||||
.assertDoesNotExist()
|
||||
composeRule.onNodeWithText(Localization.getDurationString(123))
|
||||
.assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLiveNotShownIfNoThumbnailInHeader() {
|
||||
setLongPressMenu(
|
||||
longPressable = getLongPressable(
|
||||
thumbnailUrl = null,
|
||||
decoration = LongPressable.Decoration.Live
|
||||
)
|
||||
)
|
||||
composeRule.onNodeWithTag("LongPressMenuHeaderThumbnail")
|
||||
.assertDoesNotExist()
|
||||
composeRule.onNodeWithText(R.string.duration_live, ignoreCase = true)
|
||||
.assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPlaylistStillShownIfNoThumbnailInHeader() {
|
||||
setLongPressMenu(
|
||||
longPressable = getLongPressable(
|
||||
thumbnailUrl = null,
|
||||
decoration = LongPressable.Decoration.Playlist(573)
|
||||
)
|
||||
)
|
||||
composeRule.onNodeWithTag("LongPressMenuHeaderThumbnail")
|
||||
.assertDoesNotExist()
|
||||
composeRule.onNodeWithText("573")
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHeaderSpansAllWidthIfSmallScreen() {
|
||||
onDevice().setDisplaySize(
|
||||
widthSizeClass = WidthSizeClass.COMPACT,
|
||||
heightSizeClass = HeightSizeClass.MEDIUM
|
||||
)
|
||||
setLongPressMenu()
|
||||
val row = composeRule
|
||||
.onAllNodesWithTag("LongPressMenuGridRow")
|
||||
.onFirst()
|
||||
.fetchSemanticsNode()
|
||||
.boundsInRoot
|
||||
val header = composeRule.onNodeWithTag("LongPressMenuHeader")
|
||||
.fetchSemanticsNode()
|
||||
.boundsInRoot
|
||||
assertInRange(row.left, row.left + 24.dp.value, header.left)
|
||||
assertInRange(row.right - 24.dp.value, row.right, header.right)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHeaderIsNotFullWidthIfLargeScreen() {
|
||||
onDevice().setDisplaySize(
|
||||
widthSizeClass = WidthSizeClass.EXPANDED,
|
||||
heightSizeClass = HeightSizeClass.MEDIUM
|
||||
)
|
||||
setLongPressMenu()
|
||||
val row = composeRule
|
||||
.onAllNodesWithTag("LongPressMenuGridRow")
|
||||
.onFirst()
|
||||
.fetchSemanticsNode()
|
||||
.boundsInRoot
|
||||
val header = composeRule.onNodeWithTag("LongPressMenuHeader")
|
||||
.fetchSemanticsNode()
|
||||
.boundsInRoot
|
||||
assertInRange(row.left, row.left + 24.dp.value, header.left)
|
||||
assertNotInRange(row.right - 24.dp.value, row.right, header.right)
|
||||
}
|
||||
|
||||
// the tests below all call this function to test, under different conditions, that the shown
|
||||
// actions are the intersection between the available and the enabled actions
|
||||
fun assertOnlyAndAllArrangedActionsDisplayed(
|
||||
availableActions: List<LongPressAction.Type>,
|
||||
actionArrangement: List<LongPressAction.Type>,
|
||||
expectedShownActions: List<LongPressAction.Type>
|
||||
) {
|
||||
setLongPressMenu(
|
||||
longPressActions = availableActions.map { it.buildAction {} },
|
||||
isHeaderEnabled = ((availableActions.size + actionArrangement.size) % 2) == 0,
|
||||
actionArrangement = actionArrangement
|
||||
)
|
||||
for (type in LongPressAction.Type.entries) {
|
||||
composeRule.onNodeWithText(type.label)
|
||||
.apply {
|
||||
if (type in expectedShownActions) {
|
||||
assertExists()
|
||||
assertHasClickAction()
|
||||
} else {
|
||||
assertDoesNotExist()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnlyAndAllArrangedActionsDisplayed1() {
|
||||
assertOnlyAndAllArrangedActionsDisplayed(
|
||||
availableActions = LongPressAction.Type.entries,
|
||||
actionArrangement = listOf(),
|
||||
expectedShownActions = listOf()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnlyAndAllArrangedActionsDisplayed2() {
|
||||
assertOnlyAndAllArrangedActionsDisplayed(
|
||||
availableActions = LongPressAction.Type.entries,
|
||||
actionArrangement = listOf(PlayWithKodi, ShowChannelDetails),
|
||||
expectedShownActions = listOf(PlayWithKodi, ShowChannelDetails)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnlyAndAllArrangedActionsDisplayed3() {
|
||||
assertOnlyAndAllArrangedActionsDisplayed(
|
||||
availableActions = LongPressAction.Type.entries,
|
||||
actionArrangement = getDefaultEnabledLongPressActions(ctx),
|
||||
expectedShownActions = getDefaultEnabledLongPressActions(ctx)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnlyAndAllAvailableActionsDisplayed1() {
|
||||
assertOnlyAndAllArrangedActionsDisplayed(
|
||||
availableActions = listOf(),
|
||||
actionArrangement = LongPressAction.Type.entries,
|
||||
expectedShownActions = listOf()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnlyAndAllAvailableActionsDisplayed2() {
|
||||
assertOnlyAndAllArrangedActionsDisplayed(
|
||||
availableActions = listOf(PlayWithKodi, ShowChannelDetails),
|
||||
actionArrangement = LongPressAction.Type.entries,
|
||||
expectedShownActions = listOf(PlayWithKodi, ShowChannelDetails)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnlyAndAllAvailableActionsDisplayed3() {
|
||||
assertOnlyAndAllArrangedActionsDisplayed(
|
||||
availableActions = getDefaultEnabledLongPressActions(ctx),
|
||||
actionArrangement = LongPressAction.Type.entries,
|
||||
expectedShownActions = getDefaultEnabledLongPressActions(ctx)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnlyAndAllArrangedAndAvailableActionsDisplayed1() {
|
||||
assertOnlyAndAllArrangedActionsDisplayed(
|
||||
availableActions = listOf(),
|
||||
actionArrangement = listOf(),
|
||||
expectedShownActions = listOf()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnlyAndAllArrangedAndAvailableActionsDisplayed2() {
|
||||
assertOnlyAndAllArrangedActionsDisplayed(
|
||||
availableActions = listOf(ShowDetails, ShowChannelDetails),
|
||||
actionArrangement = listOf(ShowDetails, Enqueue),
|
||||
expectedShownActions = listOf(ShowDetails)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnlyAndAllArrangedAndAvailableActionsDisplayed3() {
|
||||
assertOnlyAndAllArrangedActionsDisplayed(
|
||||
availableActions = LongPressAction.Type.entries,
|
||||
actionArrangement = LongPressAction.Type.entries,
|
||||
expectedShownActions = LongPressAction.Type.entries
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFewActionsOnLargeScreenAreNotScrollable() {
|
||||
assertOnlyAndAllArrangedActionsDisplayed(
|
||||
availableActions = listOf(ShowDetails, ShowChannelDetails),
|
||||
actionArrangement = listOf(ShowDetails, ShowChannelDetails),
|
||||
expectedShownActions = listOf(ShowDetails, ShowChannelDetails)
|
||||
)
|
||||
|
||||
// try to scroll and confirm that items don't move because the menu is not overflowing the
|
||||
// screen height
|
||||
composeRule.onNodeWithTag("LongPressMenuGrid")
|
||||
.assert(hasScrollAction())
|
||||
val originalPosition = composeRule.onNodeWithText(ShowDetails.label)
|
||||
.fetchSemanticsNode()
|
||||
.positionOnScreen
|
||||
composeRule.onNodeWithTag("LongPressMenuGrid")
|
||||
.performTouchInput { swipeUp() }
|
||||
val finalPosition = composeRule.onNodeWithText(ShowDetails.label)
|
||||
.fetchSemanticsNode()
|
||||
.positionOnScreen
|
||||
assertEquals(originalPosition, finalPosition)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAllActionsOnSmallScreenAreScrollable() {
|
||||
onDevice().setDisplaySize(
|
||||
widthSizeClass = WidthSizeClass.COMPACT,
|
||||
heightSizeClass = HeightSizeClass.COMPACT
|
||||
)
|
||||
assertOnlyAndAllArrangedActionsDisplayed(
|
||||
availableActions = LongPressAction.Type.entries,
|
||||
actionArrangement = LongPressAction.Type.entries,
|
||||
expectedShownActions = LongPressAction.Type.entries
|
||||
)
|
||||
|
||||
val anItemIsNotVisible = LongPressAction.Type.entries.any {
|
||||
composeRule.onNodeWithText(it.label).isNotDisplayed()
|
||||
}
|
||||
assertEquals(true, anItemIsNotVisible)
|
||||
|
||||
// try to scroll and confirm that items move
|
||||
composeRule.onNodeWithTag("LongPressMenuGrid")
|
||||
.assert(hasScrollAction())
|
||||
val originalPosition = composeRule.onNodeWithText(Enqueue.label)
|
||||
.fetchSemanticsNode()
|
||||
.positionOnScreen
|
||||
composeRule.onNodeWithTag("LongPressMenuGrid")
|
||||
.performTouchInput { swipeUp() }
|
||||
val finalPosition = composeRule.onNodeWithText(Enqueue.label)
|
||||
.fetchSemanticsNode()
|
||||
.positionOnScreen
|
||||
assertNotEquals(originalPosition, finalPosition)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEnabledDisabledActions() {
|
||||
setLongPressMenu(
|
||||
longPressActions = listOf(
|
||||
ShowDetails.buildAction(enabled = { true }) {},
|
||||
Enqueue.buildAction(enabled = { false }) {}
|
||||
)
|
||||
)
|
||||
composeRule.onNodeWithText(ShowDetails.label)
|
||||
.assertIsEnabled()
|
||||
.assertHasClickAction()
|
||||
composeRule.onNodeWithText(Enqueue.label)
|
||||
.assertIsNotEnabled()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClickingActionDismissesDialog() {
|
||||
var pressedCount = 0
|
||||
var dismissedCount = 0
|
||||
setLongPressMenu(
|
||||
onDismissRequest = { dismissedCount += 1 },
|
||||
longPressActions = listOf(PlayWithKodi.buildAction { pressedCount += 1 })
|
||||
)
|
||||
|
||||
composeRule.onNodeWithText(PlayWithKodi.label)
|
||||
.performClick()
|
||||
composeRule.waitUntil { dismissedCount == 1 }
|
||||
assertEquals(1, pressedCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActionLoading() {
|
||||
var dismissedCount = 0
|
||||
setLongPressMenu(
|
||||
onDismissRequest = { dismissedCount += 1 },
|
||||
longPressActions = listOf(BackgroundShuffled.buildAction { delay(500) })
|
||||
)
|
||||
|
||||
composeRule.onNode(SemanticsMatcher.keyIsDefined(ProgressBarRangeInfo))
|
||||
.assertDoesNotExist()
|
||||
composeRule.onNodeWithText(BackgroundShuffled.label)
|
||||
.performClick()
|
||||
composeRule.waitUntil {
|
||||
composeRule.onNode(SemanticsMatcher.keyIsDefined(ProgressBarRangeInfo))
|
||||
.isDisplayed()
|
||||
}
|
||||
assertEquals(0, dismissedCount)
|
||||
composeRule.waitUntil { dismissedCount == 1 }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActionError() {
|
||||
var dismissedCount = 0
|
||||
composeRule.activity.setTheme(R.style.DarkTheme)
|
||||
setLongPressMenu(
|
||||
onDismissRequest = { dismissedCount += 1 },
|
||||
longPressActions = listOf(
|
||||
BackgroundShuffled.buildAction { throw Throwable("Whatever") }
|
||||
)
|
||||
)
|
||||
|
||||
onView(withId(com.google.android.material.R.id.snackbar_text))
|
||||
.check(doesNotExist())
|
||||
composeRule.onNodeWithText(BackgroundShuffled.label)
|
||||
.performClick()
|
||||
composeRule.waitUntil { dismissedCount == 1 }
|
||||
onView(withId(com.google.android.material.R.id.snackbar_text))
|
||||
.check(matches(withText(R.string.error_snackbar_message)))
|
||||
}
|
||||
}
|
||||
@ -65,8 +65,11 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.Placeholder
|
||||
import androidx.compose.ui.text.PlaceholderVerticalAlign
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
@ -249,12 +252,15 @@ private fun LongPressMenuContent(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(scrollState)
|
||||
.testTag("LongPressMenuGrid")
|
||||
) {
|
||||
var actionIndex = if (header != null) -1 else 0 // -1 indicates the header
|
||||
while (actionIndex < actions.size) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag("LongPressMenuGridRow")
|
||||
) {
|
||||
var rowIndex = 0
|
||||
while (rowIndex < buttonsPerRow) {
|
||||
@ -294,6 +300,7 @@ private fun LongPressMenuContent(
|
||||
// only item on the row anyway
|
||||
.fillMaxWidth()
|
||||
.weight(maxHeaderWidthInButtonsFullSpan.toFloat())
|
||||
.testTag("LongPressMenuHeader")
|
||||
)
|
||||
rowIndex += maxHeaderWidthInButtonsFullSpan
|
||||
} else {
|
||||
@ -310,6 +317,7 @@ private fun LongPressMenuContent(
|
||||
.heightIn(min = ThumbnailHeight)
|
||||
.fillMaxWidth()
|
||||
.weight(headerWidthInButtonsReducedSpan.toFloat())
|
||||
.testTag("LongPressMenuHeader")
|
||||
)
|
||||
rowIndex += headerWidthInButtonsReducedSpan
|
||||
}
|
||||
@ -404,6 +412,7 @@ fun LongPressMenuHeader(
|
||||
.height(ThumbnailHeight)
|
||||
.widthIn(max = ThumbnailHeight * 16 / 9) // 16:9 thumbnail at most
|
||||
.clip(MaterialTheme.shapes.large)
|
||||
.testTag("LongPressMenuHeaderThumbnail")
|
||||
)
|
||||
}
|
||||
|
||||
@ -461,6 +470,12 @@ fun LongPressMenuHeader(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
.semantics(mergeDescendants = true) {
|
||||
contentDescription = ctx.getString(
|
||||
R.string.items_in_playlist,
|
||||
decoration.itemCount
|
||||
)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Default.PlaylistPlay,
|
||||
@ -522,6 +537,7 @@ fun LongPressMenuHeader(
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.fadedMarquee(edgeWidth = 12.dp)
|
||||
.testTag("ShowChannelDetails")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -916,4 +916,5 @@
|
||||
<string name="background_shuffled">Background\nshuffled</string>
|
||||
<string name="popup_shuffled">Popup\nshuffled</string>
|
||||
<string name="play_shuffled">Play\nshuffled</string>
|
||||
<string name="items_in_playlist">%d items in playlist</string>
|
||||
</resources>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user