diff --git a/app/src/main/java/hu/bbara/purefin/PurefinActivity.kt b/app/src/main/java/hu/bbara/purefin/PurefinActivity.kt index ee2f5013..ae8085f9 100644 --- a/app/src/main/java/hu/bbara/purefin/PurefinActivity.kt +++ b/app/src/main/java/hu/bbara/purefin/PurefinActivity.kt @@ -22,6 +22,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.lifecycle.lifecycleScope import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator import androidx.navigation3.runtime.EntryProviderScope @@ -192,7 +194,10 @@ class PurefinActivity : ComponentActivity() { NavDisplay( backStack = backStack, onBack = { navigationManager.pop() }, - modifier = Modifier.fillMaxSize().background(backgroundDark), + modifier = Modifier + .fillMaxSize() + .background(backgroundDark) + .semantics { testTagsAsResourceId = true }, transitionSpec = { fadeIn( animationSpec = tween( @@ -227,7 +232,9 @@ class PurefinActivity : ComponentActivity() { } } } else { - LoginScreen() + LoginScreen( + modifier = Modifier.semantics { testTagsAsResourceId = true } + ) } } } diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/AppBottomBar.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/AppBottomBar.kt index 724fa3b9..ee4a8b1f 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/AppBottomBar.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/AppBottomBar.kt @@ -9,6 +9,8 @@ import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag @Composable fun AppBottomBar( @@ -16,17 +18,21 @@ fun AppBottomBar( isOnline: Boolean, onTabSelected: (Int) -> Unit ) { - NavigationBar { + NavigationBar( + modifier = Modifier.testTag(BottomNavigationTag) + ) { if (isOnline) { NavigationBarItem( selected = selectedTab == 0, onClick = { onTabSelected(0) }, + modifier = Modifier.testTag(BottomNavigationHomeTag), icon = { Icon(Icons.Outlined.Home, contentDescription = "Home") }, label = { Text("Home") } ) NavigationBarItem( selected = selectedTab == 1, onClick = { onTabSelected(1) }, + modifier = Modifier.testTag(BottomNavigationLibrariesTag), icon = { Icon(Icons.Outlined.Collections, contentDescription = "Libraries") }, label = { Text("Libraries") } ) @@ -34,8 +40,14 @@ fun AppBottomBar( NavigationBarItem( selected = !isOnline || selectedTab == 2, onClick = { onTabSelected(2) }, + modifier = Modifier.testTag(BottomNavigationDownloadsTag), icon = { Icon(Icons.Outlined.Download, contentDescription = "Downloads") }, label = { Text("Downloads") } ) } } + +internal const val BottomNavigationTag = "bottom-navigation" +internal const val BottomNavigationHomeTag = "bottom-navigation-home" +internal const val BottomNavigationLibrariesTag = "bottom-navigation-libraries" +internal const val BottomNavigationDownloadsTag = "bottom-navigation-downloads" diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/download/components/DownloadingItemRow.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/download/components/DownloadingItemRow.kt index 5c6ca14c..37707e64 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/download/components/DownloadingItemRow.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/download/components/DownloadingItemRow.kt @@ -22,6 +22,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.testTag import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow @@ -33,9 +34,10 @@ import hu.bbara.purefin.core.feature.downloads.ActiveDownloadItem internal fun DownloadingItemRow( item: ActiveDownloadItem, onCancel: (String) -> Unit, + modifier: Modifier = Modifier ) { Card( - modifier = Modifier.fillMaxWidth(), + modifier = modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant), shape = RoundedCornerShape(12.dp) ) { @@ -91,7 +93,10 @@ internal fun DownloadingItemRow( ) } } - IconButton(onClick = { onCancel(item.contentId) }) { + IconButton( + onClick = { onCancel(item.contentId) }, + modifier = Modifier.testTag("$DownloadsCancelButtonTagPrefix${item.contentId}") + ) { Icon( imageVector = Icons.Outlined.Close, contentDescription = "Cancel download", @@ -101,3 +106,5 @@ internal fun DownloadingItemRow( } } } + +internal const val DownloadsCancelButtonTagPrefix = "downloads-cancel-button-" diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/download/components/DownloadsContent.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/download/components/DownloadsContent.kt index f0ec7ec8..69491cad 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/download/components/DownloadsContent.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/download/components/DownloadsContent.kt @@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -38,7 +39,9 @@ fun DownloadsContent( if (isEmpty) { Column( - modifier = modifier.fillMaxSize(), + modifier = modifier + .fillMaxSize() + .testTag(DownloadsEmptyStateTag), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { @@ -62,7 +65,9 @@ fun DownloadsContent( contentPadding = PaddingValues(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp), - modifier = modifier.background(MaterialTheme.colorScheme.background) + modifier = modifier + .testTag(DownloadsGridTag) + .background(MaterialTheme.colorScheme.background) ) { if (activeDownloads.value.isNotEmpty()) { item(key = "downloading-header", span = { GridItemSpan(maxLineSpan) }) { @@ -80,7 +85,8 @@ fun DownloadsContent( ) { item -> DownloadingItemRow( item = item, - onCancel = { viewModel.cancelDownload(it) } + onCancel = { viewModel.cancelDownload(it) }, + modifier = Modifier.testTag("$DownloadsActiveItemTagPrefix${item.contentId}") ) } if (downloads.value.isNotEmpty()) { @@ -101,7 +107,13 @@ fun DownloadsContent( onMovieSelected = viewModel::onMovieSelected, onSeriesSelected = viewModel::onSeriesSelected, onEpisodeSelected = { _, _, _ -> }, + modifier = Modifier.testTag("$DownloadsItemTagPrefix${item.id}") ) } } } + +internal const val DownloadsEmptyStateTag = "downloads-empty-state" +internal const val DownloadsGridTag = "downloads-grid" +internal const val DownloadsActiveItemTagPrefix = "downloads-active-item-" +internal const val DownloadsItemTagPrefix = "downloads-item-" diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/episode/components/EpisodeComponents.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/episode/components/EpisodeComponents.kt index 09397648..7e2cf945 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/episode/components/EpisodeComponents.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/episode/components/EpisodeComponents.kt @@ -24,6 +24,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -71,6 +72,7 @@ internal fun EpisodeTopBar( ), modifier = Modifier .height(52.dp) + .testTag(EpisodeSeriesButtonTag) .clip(CircleShape) ) { Text( @@ -123,7 +125,9 @@ internal fun EpisodeDetails( text = mediaPlayButtonText(episode.progress, episode.watched), progress = mediaPlaybackProgress(episode.progress), onClick = playAction, - modifier = Modifier.sizeIn(maxWidth = 200.dp) + modifier = Modifier + .sizeIn(maxWidth = 200.dp) + .testTag(EpisodePlayButtonTag) ) VerticalDivider( color = MaterialTheme.colorScheme.secondary, @@ -143,6 +147,7 @@ internal fun EpisodeDetails( is DownloadState.Failed -> Icons.Outlined.Download }, height = 48.dp, + modifier = Modifier.testTag(EpisodeDownloadButtonTag), onClick = onDownloadClick ) } @@ -168,3 +173,7 @@ internal fun EpisodeDetails( // ) } } + +internal const val EpisodeSeriesButtonTag = "episode-series-button" +internal const val EpisodePlayButtonTag = "episode-play-button" +internal const val EpisodeDownloadButtonTag = "episode-download-button" diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/HomeContent.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/HomeContent.kt index 3b9ea49b..b5743256 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/HomeContent.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/HomeContent.kt @@ -20,6 +20,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp import hu.bbara.purefin.core.model.LibraryUiModel import hu.bbara.purefin.core.model.MediaUiModel @@ -80,7 +81,9 @@ fun HomeContent( PullToRefreshBox( isRefreshing = isRefreshing, onRefresh = onRefresh, - modifier = modifier.fillMaxSize() + modifier = modifier + .fillMaxSize() + .testTag(HomeContentTag) ) { Box( modifier = Modifier @@ -89,7 +92,9 @@ fun HomeContent( ) { LazyColumn( state = listState, - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize() + .testTag(HomeContentViewportTag), contentPadding = PaddingValues(top = 16.dp, bottom = 24.dp), verticalArrangement = Arrangement.spacedBy(24.dp) ) { @@ -97,7 +102,8 @@ fun HomeContent( item(key = "featured") { SuggestionsSection( items = suggestions, - onItemOpen = { item -> onMediaSelected(item) } + onItemOpen = { item -> onMediaSelected(item) }, + modifier = Modifier.testTag(HomeFeaturedSectionTag) ) } } @@ -107,7 +113,8 @@ fun HomeContent( ContinueWatchingSection( items = continueWatching, onMarkAsWatched = onMarkAsWatched, - onMediaSelected = onMediaSelected + onMediaSelected = onMediaSelected, + modifier = Modifier.testTag(HomeContinueWatchingSectionTag) ) } } @@ -116,7 +123,8 @@ fun HomeContent( item(key = "next-up") { NextUpSection( items = nextUp, - onMediaSelected = onMediaSelected + onMediaSelected = onMediaSelected, + modifier = Modifier.testTag(HomeNextUpSectionTag) ) } } @@ -129,7 +137,8 @@ fun HomeContent( library = library, items = libraryContent[library.id].orEmpty(), onLibrarySelected = onLibrarySelected, - onMediaSelected = onMediaSelected + onMediaSelected = onMediaSelected, + modifier = Modifier.testTag("$HomeLibrarySectionTagPrefix${library.id}") ) } @@ -137,11 +146,20 @@ fun HomeContent( item(key = "empty-state") { HomeEmptyState( onRefresh = onRefresh, - onBrowseLibrariesClick = onBrowseLibrariesClick + onBrowseLibrariesClick = onBrowseLibrariesClick, + modifier = Modifier.testTag(HomeEmptyStateTag) ) } } } } } -} \ No newline at end of file +} + +internal const val HomeContentTag = "home-content" +internal const val HomeContentViewportTag = "home-content-viewport" +internal const val HomeFeaturedSectionTag = "home-section-featured" +internal const val HomeContinueWatchingSectionTag = "home-section-continue-watching" +internal const val HomeNextUpSectionTag = "home-section-next-up" +internal const val HomeLibrarySectionTagPrefix = "home-section-library-" +internal const val HomeEmptyStateTag = "home-empty-state" diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/continuewatching/ContinueWatchingSection.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/continuewatching/ContinueWatchingSection.kt index ea457671..e1114539 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/continuewatching/ContinueWatchingSection.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/continuewatching/ContinueWatchingSection.kt @@ -12,6 +12,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp import hu.bbara.purefin.ui.common.header.SectionHeader import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsKey @@ -41,7 +42,9 @@ fun ContinueWatchingSection( LazyRow( contentPadding = PaddingValues(horizontal = 16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp), - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .testTag(HomeContinueWatchingRowTag), state = listState ) { itemsIndexed(items = items, key = { _, item -> item.id }) { index, item -> @@ -49,9 +52,13 @@ fun ContinueWatchingSection( item = item, sharedBoundsKey = homeMediaSharedBoundsKey("continue-$index", item.id), onMediaSelected = onMediaSelected, - onMarkAsWatched = onMarkAsWatched + onMarkAsWatched = onMarkAsWatched, + modifier = Modifier.testTag("$HomeContinueWatchingItemTagPrefix$index") ) } } } } + +internal const val HomeContinueWatchingRowTag = "home-continue-watching-row" +internal const val HomeContinueWatchingItemTagPrefix = "home-continue-watching-item-" diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/featured/SuggestionsSection.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/featured/SuggestionsSection.kt index f0489646..047d202d 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/featured/SuggestionsSection.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/featured/SuggestionsSection.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsKey import hu.bbara.purefin.core.model.MediaUiModel @@ -48,13 +49,16 @@ fun SuggestionsSection( pageSize = PageSize.Fixed(320.dp), contentPadding = PaddingValues(horizontal = 16.dp), pageSpacing = 16.dp, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() + .testTag(HomeFeaturedPagerTag) ) { page -> val item = items[page] SuggestionCard( item = item, sharedBoundsKey = homeMediaSharedBoundsKey("suggestion-$page", item.id), - onClick = { onItemOpen(item) } + onClick = { onItemOpen(item) }, + modifier = Modifier.testTag("$HomeFeaturedItemTagPrefix$page") ) } if (items.size > 1) { @@ -86,3 +90,6 @@ fun SuggestionsSection( } } } + +internal const val HomeFeaturedPagerTag = "home-featured-pager" +internal const val HomeFeaturedItemTagPrefix = "home-featured-item-" diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/library/LibraryPosterSection.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/library/LibraryPosterSection.kt index 7d181ead..e58b32e2 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/library/LibraryPosterSection.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/library/LibraryPosterSection.kt @@ -12,6 +12,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp import hu.bbara.purefin.ui.common.header.SectionHeader import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsKey @@ -47,7 +48,9 @@ fun LibraryPosterSection( LazyRow( contentPadding = PaddingValues(horizontal = 16.dp), horizontalArrangement = Arrangement.spacedBy(14.dp), - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .testTag("$HomeLibraryRowTagPrefix${library.id}"), state = listState ) { itemsIndexed(items = items, key = { _, item -> item.id }) { index, item -> @@ -57,9 +60,13 @@ fun LibraryPosterSection( origin = "library-${library.id}-$index", mediaId = item.id ), - onMediaSelected = onMediaSelected + onMediaSelected = onMediaSelected, + modifier = Modifier.testTag("$HomeLibraryItemTagPrefix${library.id}-$index") ) } } } } + +internal const val HomeLibraryRowTagPrefix = "home-library-row-" +internal const val HomeLibraryItemTagPrefix = "home-library-item-" diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/nextup/NextUpSection.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/nextup/NextUpSection.kt index 2f3ea813..2117b6b7 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/nextup/NextUpSection.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/nextup/NextUpSection.kt @@ -12,6 +12,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp import hu.bbara.purefin.ui.common.header.SectionHeader import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsKey @@ -40,16 +41,22 @@ fun NextUpSection( LazyRow( contentPadding = PaddingValues(horizontal = 16.dp), horizontalArrangement = Arrangement.spacedBy(14.dp), - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .testTag(HomeNextUpRowTag), state = listState ) { itemsIndexed(items = items, key = { _, item -> item.id }) { index, item -> NextUpCard( uiModel = item, sharedBoundsKey = homeMediaSharedBoundsKey("next-up-$index", item.id), - onMediaSelected = onMediaSelected + onMediaSelected = onMediaSelected, + modifier = Modifier.testTag("$HomeNextUpItemTagPrefix$index") ) } } } } + +internal const val HomeNextUpRowTag = "home-next-up-row" +internal const val HomeNextUpItemTagPrefix = "home-next-up-item-" diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/search/SearchScreen.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/search/SearchScreen.kt index d6ef8b53..4076953c 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/search/SearchScreen.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/search/SearchScreen.kt @@ -47,6 +47,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextOverflow @@ -147,6 +148,7 @@ private fun SearchScreenContent( ) }, modifier = modifier .fillMaxSize() + .testTag(SearchScreenTag) .background(scheme.background) ) { innerPadding -> Column( @@ -261,7 +263,10 @@ private fun SearchField( }, trailingIcon = { if (query.isNotBlank()) { - IconButton(onClick = { onQueryChange("") }) { + IconButton( + onClick = { onQueryChange("") }, + modifier = Modifier.testTag(SearchClearButtonTag) + ) { Icon( imageVector = Icons.Outlined.Close, contentDescription = "Clear search", @@ -286,6 +291,7 @@ private fun SearchField( ), modifier = modifier .fillMaxWidth() + .testTag(SearchQueryFieldTag) .height(68.dp) ) } @@ -298,18 +304,23 @@ private fun SearchResultsGrid( ) { Column( verticalArrangement = Arrangement.spacedBy(16.dp), - modifier = modifier.fillMaxWidth() + modifier = modifier + .fillMaxWidth() + .testTag(SearchResultsGridTag) ) { - results.chunked(2).forEach { rowItems -> + results.chunked(2).forEachIndexed { rowIndex, rowItems -> Row( horizontalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth() ) { - rowItems.forEach { item -> + rowItems.forEachIndexed { columnIndex, item -> + val itemIndex = rowIndex * 2 + columnIndex SearchResultCard( item = item, onClick = { onResultClick(item) }, - modifier = Modifier.weight(1f) + modifier = Modifier + .weight(1f) + .testTag("$SearchResultItemTagPrefix$itemIndex") ) } if (rowItems.size == 1) { @@ -367,6 +378,7 @@ private fun GenreSelector( FilterChip( selected = false, onClick = { showGenres = true }, + modifier = Modifier.testTag(SearchGenreSelectorTag), leadingIcon = { Icon( imageVector = Icons.Outlined.Tune, @@ -388,11 +400,12 @@ private fun GenreSelector( ) } ) - visibleGenres.forEach { genre -> + visibleGenres.forEachIndexed { index, genre -> GenreChip( genre = genre, selected = genre.name in selectedGenreNames, - onClick = { onGenreSelected(genre) } + onClick = { onGenreSelected(genre) }, + modifier = Modifier.testTag("$SearchGenreChipTagPrefix$index") ) } } @@ -400,6 +413,7 @@ private fun GenreSelector( if (showGenres) { AlertDialog( onDismissRequest = { showGenres = false }, + modifier = Modifier.testTag(SearchGenreDialogTag), title = { Text(text = "Select genres") }, @@ -412,11 +426,12 @@ private fun GenreSelector( .heightIn(max = 360.dp) .verticalScroll(rememberScrollState()) ) { - genres.forEach { genre -> + genres.forEachIndexed { index, genre -> GenreChip( genre = genre, selected = genre.name in selectedGenreNames, - onClick = { onGenreSelected(genre) } + onClick = { onGenreSelected(genre) }, + modifier = Modifier.testTag("$SearchGenreDialogChipTagPrefix$index") ) } } @@ -514,3 +529,13 @@ private val previewSearchResults = listOf( type = MediaKind.MOVIE ) ) + +internal const val SearchScreenTag = "search-screen" +internal const val SearchQueryFieldTag = "search-query-field" +internal const val SearchClearButtonTag = "search-clear-button" +internal const val SearchResultsGridTag = "search-results-grid" +internal const val SearchResultItemTagPrefix = "search-result-item-" +internal const val SearchGenreSelectorTag = "search-genre-selector" +internal const val SearchGenreChipTagPrefix = "search-genre-chip-" +internal const val SearchGenreDialogTag = "search-genre-dialog" +internal const val SearchGenreDialogChipTagPrefix = "search-genre-dialog-chip-" diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/libraries/components/LibrariesContent.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/libraries/components/LibrariesContent.kt index 4caa5e36..2f8b8cbc 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/libraries/components/LibrariesContent.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/libraries/components/LibrariesContent.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp import hu.bbara.purefin.core.model.LibraryUiModel @@ -17,7 +18,7 @@ fun LibrariesContent( onLibrarySelected: (LibraryUiModel) -> Unit, modifier: Modifier = Modifier, ) { - BoxWithConstraints(modifier = modifier) { + BoxWithConstraints(modifier = modifier.testTag(LibrariesOverviewGridTag)) { val minCellSize = if (maxWidth >= 600.dp) 220.dp else 160.dp LazyVerticalGrid( @@ -29,9 +30,13 @@ fun LibrariesContent( items(items, key = { it.id }) { item -> LibraryListItem( uiModel = item, - onClick = { onLibrarySelected(item) } + onClick = { onLibrarySelected(item) }, + modifier = Modifier.testTag("$LibrariesOverviewItemTagPrefix${item.id}") ) } } } } + +internal const val LibrariesOverviewGridTag = "libraries-overview-grid" +internal const val LibrariesOverviewItemTagPrefix = "libraries-overview-item-" diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/library/LibraryScreen.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/library/LibraryScreen.kt index 909bd030..27b443c7 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/library/LibraryScreen.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/library/LibraryScreen.kt @@ -17,6 +17,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import hu.bbara.purefin.core.feature.browse.library.LibraryViewModel @@ -42,7 +43,7 @@ fun LibraryScreen( val libraryItems = viewModel.contents.collectAsState() Scaffold( - modifier = modifier, + modifier = modifier.testTag(LibraryScreenTag), containerColor = MaterialTheme.colorScheme.background, contentColor = MaterialTheme.colorScheme.onBackground, topBar = { @@ -68,7 +69,11 @@ internal fun LibraryPosterGrid( modifier: Modifier = Modifier, viewModel: LibraryViewModel = hiltViewModel() ) { - BoxWithConstraints(modifier = modifier.background(MaterialTheme.colorScheme.background)) { + BoxWithConstraints( + modifier = modifier + .testTag(LibraryPosterGridTag) + .background(MaterialTheme.colorScheme.background) + ) { val minCellSize = if (maxWidth >= 600.dp) 220.dp else 120.dp LazyVerticalGrid( @@ -88,7 +93,8 @@ internal fun LibraryPosterGrid( is SeriesUiModel -> viewModel.onSeriesSelected(item.id) is EpisodeUiModel -> Unit } - } + }, + modifier = Modifier.testTag("$LibraryPosterItemTagPrefix${item.id}") ) { when (item) { is MovieUiModel, is EpisodeUiModel -> { @@ -107,3 +113,7 @@ internal fun LibraryPosterGrid( } } } + +internal const val LibraryScreenTag = "library-screen" +internal const val LibraryPosterGridTag = "library-poster-grid" +internal const val LibraryPosterItemTagPrefix = "library-poster-item-" diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/login/LoginContent.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/login/LoginContent.kt index 40171fbd..c2f90e1c 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/login/LoginContent.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/login/LoginContent.kt @@ -22,6 +22,7 @@ import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -47,6 +48,7 @@ fun LoginContent( Column( modifier = modifier .fillMaxSize() + .testTag(LoginContentTag) .background(scheme.background) .verticalScroll(rememberScrollState()) .padding(horizontal = 24.dp, vertical = 20.dp), @@ -84,6 +86,7 @@ fun LoginContent( style = MaterialTheme.typography.bodySmall, modifier = Modifier .fillMaxWidth() + .testTag(LoginErrorMessageTag) .background( scheme.errorContainer, RoundedCornerShape(12.dp) @@ -121,13 +124,15 @@ private fun ServerSearchContent( modifier = Modifier.padding(top = 2.dp, bottom = 16.dp).fillMaxWidth() ) - PurefinComplexTextField( - label = "Server URL", - value = state.serverUrl, - onValueChange = callbacks.onServerUrlChange, - placeholder = "http://192.168.1.100:8096", - leadingIcon = Icons.Default.Storage - ) + Column(modifier = Modifier.testTag(LoginServerUrlFieldTag)) { + PurefinComplexTextField( + label = "Server URL", + value = state.serverUrl, + onValueChange = callbacks.onServerUrlChange, + placeholder = "http://192.168.1.100:8096", + leadingIcon = Icons.Default.Storage + ) + } Spacer(modifier = Modifier.height(16.dp)) @@ -137,6 +142,7 @@ private fun ServerSearchContent( enabled = !state.isSearching, modifier = Modifier .fillMaxWidth() + .testTag(LoginFindServerButtonTag) .height(48.dp) ) @@ -149,10 +155,12 @@ private fun ServerSearchContent( modifier = Modifier.fillMaxWidth() ) Spacer(modifier = Modifier.height(8.dp)) - state.discoveredServers.forEach { server -> + state.discoveredServers.forEachIndexed { index, server -> TextButton( onClick = { callbacks.onDiscoveredServerClick(server) }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() + .testTag("$LoginDiscoveredServerTagPrefix$index") ) { Text( text = server.name?.let { "$it\n${server.address}" } ?: server.address, @@ -186,7 +194,9 @@ private fun LoginPhaseContent( ) TextButton( onClick = callbacks.onChangeServer, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() + .testTag(LoginChangeServerButtonTag) ) { Text("Change server") } @@ -199,7 +209,9 @@ private fun LoginPhaseContent( color = scheme.primary, style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.Bold), textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() + .testTag(LoginQuickConnectCodeTag) ) Text( text = "Approve this code in another Jellyfin client.", @@ -209,14 +221,20 @@ private fun LoginPhaseContent( modifier = Modifier.fillMaxWidth().padding(top = 4.dp) ) Spacer(modifier = Modifier.height(10.dp)) - TextButton(onClick = callbacks.onCancelQuickConnect) { + TextButton( + onClick = callbacks.onCancelQuickConnect, + modifier = Modifier.testTag(LoginQuickConnectCancelButtonTag) + ) { Text("Cancel Quick Connect") } } ?: PurefinTextButton( content = { Text("Quick Connect") }, onClick = callbacks.onQuickConnect, enabled = !state.isQuickConnecting, - modifier = Modifier.fillMaxWidth().height(48.dp) + modifier = Modifier + .fillMaxWidth() + .testTag(LoginQuickConnectButtonTag) + .height(48.dp) ) } else { Text( @@ -238,23 +256,27 @@ private fun LoginPhaseContent( Spacer(modifier = Modifier.height(12.dp)) - PurefinComplexTextField( - label = "Username", - value = state.username, - onValueChange = callbacks.onUsernameChange, - placeholder = "Enter your username", - leadingIcon = Icons.Default.Person - ) + Column(modifier = Modifier.testTag(LoginUsernameFieldTag)) { + PurefinComplexTextField( + label = "Username", + value = state.username, + onValueChange = callbacks.onUsernameChange, + placeholder = "Enter your username", + leadingIcon = Icons.Default.Person + ) + } Spacer(modifier = Modifier.height(12.dp)) - PurefinPasswordField( - label = "Password", - value = state.password, - onValueChange = callbacks.onPasswordChange, - placeholder = "Enter your password", - leadingIcon = Icons.Default.Lock, - ) + Column(modifier = Modifier.testTag(LoginPasswordFieldTag)) { + PurefinPasswordField( + label = "Password", + value = state.password, + onValueChange = callbacks.onPasswordChange, + placeholder = "Enter your password", + leadingIcon = Icons.Default.Lock, + ) + } Spacer(modifier = Modifier.height(24.dp)) @@ -264,6 +286,20 @@ private fun LoginPhaseContent( enabled = !state.isQuickConnecting, modifier = Modifier .fillMaxWidth() + .testTag(LoginConnectButtonTag) .height(48.dp) ) } + +internal const val LoginContentTag = "login-content" +internal const val LoginFindServerButtonTag = "login-find-server-button" +internal const val LoginServerUrlFieldTag = "login-server-url-field" +internal const val LoginDiscoveredServerTagPrefix = "login-discovered-server-" +internal const val LoginChangeServerButtonTag = "login-change-server-button" +internal const val LoginUsernameFieldTag = "login-username-field" +internal const val LoginPasswordFieldTag = "login-password-field" +internal const val LoginQuickConnectButtonTag = "login-quick-connect-button" +internal const val LoginQuickConnectCodeTag = "login-quick-connect-code" +internal const val LoginQuickConnectCancelButtonTag = "login-quick-connect-cancel-button" +internal const val LoginConnectButtonTag = "login-connect-button" +internal const val LoginErrorMessageTag = "login-error-message" diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/movie/components/MovieComponents.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/movie/components/MovieComponents.kt index a1451d6c..15d05c4d 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/movie/components/MovieComponents.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/movie/components/MovieComponents.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -85,7 +86,9 @@ internal fun MovieDetails( text = mediaPlayButtonText(movie.progress, movie.watched), progress = mediaPlaybackProgress(movie.progress), onClick = playAction, - modifier = Modifier.sizeIn(maxWidth = 200.dp) + modifier = Modifier + .sizeIn(maxWidth = 200.dp) + .testTag(MoviePlayButtonTag) ) VerticalDivider( color = MaterialTheme.colorScheme.secondary, @@ -105,6 +108,7 @@ internal fun MovieDetails( is DownloadState.Failed -> Icons.Outlined.Download }, height = 48.dp, + modifier = Modifier.testTag(MovieDownloadButtonTag), onClick = onDownloadClick ) } @@ -130,3 +134,6 @@ internal fun MovieDetails( // ) } } + +internal const val MoviePlayButtonTag = "movie-play-button" +internal const val MovieDownloadButtonTag = "movie-download-button" diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/player/components/PlayerControlsOverlay.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/player/components/PlayerControlsOverlay.kt index 83967f15..927f5f08 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/player/components/PlayerControlsOverlay.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/player/components/PlayerControlsOverlay.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import hu.bbara.purefin.core.player.model.PlayerUiState @@ -70,6 +71,7 @@ fun PlayerControlsOverlay( Box( modifier = modifier .fillMaxSize() + .testTag(PlayerControlsOverlayTag) .background( Brush.verticalGradient( listOf( @@ -149,6 +151,7 @@ private fun TopBar( GhostIconButton( icon = Icons.Outlined.PlaylistPlay, contentDescription = "Queue", + modifier = Modifier.testTag(PlayerQueueButtonTag), onClick = onOpenQueue ) GhostIconButton(icon = Icons.Outlined.Cast, contentDescription = "Cast", onClick = onCast) @@ -192,7 +195,8 @@ private fun BottomSection( adMarkers = uiState.ads, onSeek = onScrub, onScrubStarted = onScrubStart, - onScrubFinished = onScrubFinished + onScrubFinished = onScrubFinished, + modifier = Modifier.testTag(PlayerSeekBarTag) ) Spacer(modifier = Modifier.height(8.dp)) Box( @@ -209,30 +213,35 @@ private fun BottomSection( icon = Icons.Outlined.SkipPrevious, contentDescription = "Previous", onClick = onPrevious, + modifier = Modifier.testTag(PlayerPreviousButtonTag), size = 64 ) PurefinIconButton( icon = Icons.Outlined.Replay10, contentDescription = "Seek backward", onClick = onSeekBackward, + modifier = Modifier.testTag(PlayerSeekBackwardButtonTag), size = 64 ) PurefinIconButton( icon = if (uiState.isPlaying) Icons.Outlined.Pause else Icons.Outlined.PlayArrow, contentDescription = "Play/Pause", onClick = onPlayPause, + modifier = Modifier.testTag(PlayerPlayPauseButtonTag), size = 64 ) PurefinIconButton( icon = Icons.Outlined.Forward30, contentDescription = "Seek forward", onClick = onSeekForward, + modifier = Modifier.testTag(PlayerSeekForwardButtonTag), size = 64 ) PurefinIconButton( icon = Icons.Outlined.SkipNext, contentDescription = "Next", onClick = onNext, + modifier = Modifier.testTag(PlayerNextButtonTag), size = 64 ) } @@ -263,7 +272,9 @@ private fun BottomSection( if (uiState.activeSkippableSegmentEndMs != null) { SkipSegmentButton( onClick = onSkipSegment, - modifier = Modifier.align(Alignment.CenterEnd) + modifier = Modifier + .align(Alignment.CenterEnd) + .testTag(PlayerSkipSegmentButtonTag) ) } } @@ -271,6 +282,16 @@ private fun BottomSection( } } +internal const val PlayerControlsOverlayTag = "player-controls-overlay" +internal const val PlayerQueueButtonTag = "player_queue_button" +internal const val PlayerSeekBarTag = "player_seek_bar" +internal const val PlayerPreviousButtonTag = "player_previous_button" +internal const val PlayerSeekBackwardButtonTag = "player_seek_backward_button" +internal const val PlayerPlayPauseButtonTag = "player_play_pause_button" +internal const val PlayerSeekForwardButtonTag = "player_seek_forward_button" +internal const val PlayerNextButtonTag = "player_next_button" +internal const val PlayerSkipSegmentButtonTag = "player_skip_segment_button" + @Composable fun SkipSegmentButton( onClick: () -> Unit, diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/player/components/PlayerQueuePanel.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/player/components/PlayerQueuePanel.kt index 85b51711..6a21ff9d 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/player/components/PlayerQueuePanel.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/player/components/PlayerQueuePanel.kt @@ -26,6 +26,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -40,7 +41,12 @@ fun PlayerQueuePanel( modifier: Modifier = Modifier ) { val scheme = MaterialTheme.colorScheme - Box(modifier = modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) { + Box( + modifier = modifier + .fillMaxWidth() + .testTag(PlayerPlaylistPanelTag), + contentAlignment = Alignment.CenterEnd + ) { Surface( modifier = Modifier .fillMaxHeight() @@ -78,13 +84,23 @@ fun PlayerQueuePanel( enter = fadeIn(), exit = fadeOut() ) { - LazyColumn(verticalArrangement = Arrangement.spacedBy(10.dp)) { + LazyColumn( + verticalArrangement = Arrangement.spacedBy(10.dp), + modifier = Modifier.testTag(PlayerPlaylistRowTag) + ) { items(uiState.queue, key = { item -> item.id }) { item -> QueueRow( title = item.title, artworkUrl = item.artworkUrl, isCurrent = item.isCurrent, - onClick = { onSelect(item.id) } + onClick = { onSelect(item.id) }, + modifier = Modifier.testTag( + if (item.isCurrent) { + PlayerPlaylistCurrentItemTag + } else { + "${PlayerPlaylistItemTagPrefix}${item.id}" + } + ) ) } } @@ -106,11 +122,12 @@ private fun QueueRow( subtitle: String? = null, artworkUrl: String?, isCurrent: Boolean, - onClick: () -> Unit + onClick: () -> Unit, + modifier: Modifier = Modifier ) { val scheme = MaterialTheme.colorScheme Row( - modifier = Modifier + modifier = modifier .fillMaxWidth() .clip(RoundedCornerShape(12.dp)) .background( @@ -160,3 +177,8 @@ private fun QueueRow( } } } + +internal const val PlayerPlaylistPanelTag = "player_playlist_panel" +internal const val PlayerPlaylistRowTag = "player_playlist_row" +internal const val PlayerPlaylistCurrentItemTag = "player_playlist_current_item" +internal const val PlayerPlaylistItemTagPrefix = "player_playlist_item_" diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/player/components/TrackSelectionButtons.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/player/components/TrackSelectionButtons.kt index 0da40b24..f43d407c 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/player/components/TrackSelectionButtons.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/player/components/TrackSelectionButtons.kt @@ -32,6 +32,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -63,7 +64,7 @@ fun QualitySelectionButton( ) } }, - modifier = modifier + modifier = modifier.testTag(PlayerQualityButtonTag) ) } @@ -92,7 +93,7 @@ fun AudioSelectionButton( ) } }, - modifier = modifier + modifier = modifier.testTag(PlayerAudioButtonTag) ) } @@ -121,7 +122,7 @@ fun SubtitlesSelectionButton( ) } }, - modifier = modifier + modifier = modifier.testTag(PlayerSubtitlesButtonTag) ) } @@ -139,6 +140,7 @@ private fun TrackSelectionPanel( Box( modifier = modifier .fillMaxWidth() + .testTag(PlayerTrackPanelTag) .fillMaxHeight() .windowInsetsPadding(WindowInsets.safeDrawing) .navigationBarsPadding() @@ -199,12 +201,20 @@ private fun TrackSelectionPanel( .heightIn(max = 292.dp) .verticalScroll(rememberScrollState()) ) { - options.forEach { option -> + options.forEachIndexed { index, option -> val selected = option.id == selectedId TrackOptionItem( label = option.label, selected = selected, - onClick = { onSelect(option) } + onClick = { onSelect(option) }, + modifier = Modifier.testTag( + when { + selected -> PlayerTrackSelectedItemTag + index == 0 -> PlayerTrackFirstItemTag + index == options.lastIndex -> PlayerTrackLastItemTag + else -> "$PlayerTrackItemTagPrefix$index" + } + ) ) } } @@ -213,6 +223,15 @@ private fun TrackSelectionPanel( } } +internal const val PlayerQualityButtonTag = "player_quality_button" +internal const val PlayerAudioButtonTag = "player_audio_button" +internal const val PlayerSubtitlesButtonTag = "player_subtitles_button" +internal const val PlayerTrackPanelTag = "player_track_panel" +internal const val PlayerTrackSelectedItemTag = "player_track_selected_item" +internal const val PlayerTrackFirstItemTag = "player_track_first_item" +internal const val PlayerTrackLastItemTag = "player_track_last_item" +internal const val PlayerTrackItemTagPrefix = "player_track_item_" + @Composable private fun TrackOptionItem( label: String, diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/series/components/SeriesComponents.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/series/components/SeriesComponents.kt index f53a96a1..6e26e4c1 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/series/components/SeriesComponents.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/series/components/SeriesComponents.kt @@ -50,6 +50,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -146,14 +147,17 @@ internal fun SeriesActionButtons( text = mediaPlayButtonText(nextUpEpisode?.progress, nextUpEpisode?.watched), progress = mediaPlaybackProgress(nextUpEpisode?.progress), onClick = playAction ?: {}, - modifier = Modifier.sizeIn(maxWidth = 200.dp) + modifier = Modifier + .sizeIn(maxWidth = 200.dp) + .testTag(SeriesPlayButtonTag) ) Spacer(modifier = Modifier.width(12.dp)) MediaActionButton( backgroundColor = MaterialTheme.colorScheme.surface, iconColor = MaterialTheme.colorScheme.onSurface, icon = Icons.Outlined.Add, - height = 48.dp + height = 48.dp, + modifier = Modifier.testTag(SeriesAddButtonTag) ) Spacer(modifier = Modifier.width(12.dp)) MediaActionButton( @@ -165,6 +169,7 @@ internal fun SeriesActionButtons( else -> Icons.Outlined.Download }, height = 48.dp, + modifier = Modifier.testTag(SeriesDownloadButtonTag), onClick = { showDownloadDialog = true } ) } @@ -200,6 +205,7 @@ private fun DownloadOptionsDialog( ) { AlertDialog( onDismissRequest = onDismiss, + modifier = Modifier.testTag(SeriesDownloadDialogTag), title = { Text("Download") }, text = { Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { @@ -211,7 +217,10 @@ private fun DownloadOptionsDialog( }, confirmButton = { Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - TextButton(onClick = { onDownloadOptionSelected(SeriesDownloadOption.SEASON) }) { + TextButton( + onClick = { onDownloadOptionSelected(SeriesDownloadOption.SEASON) }, + modifier = Modifier.testTag(SeriesDownloadSeasonButtonTag) + ) { Text( when (seasonDownloadState) { is DownloadState.Downloaded -> "$selectedSeasonName Downloaded" @@ -220,7 +229,10 @@ private fun DownloadOptionsDialog( } ) } - TextButton(onClick = { onDownloadOptionSelected(SeriesDownloadOption.SERIES) }) { + TextButton( + onClick = { onDownloadOptionSelected(SeriesDownloadOption.SERIES) }, + modifier = Modifier.testTag(SeriesDownloadAllButtonTag) + ) { Text("Download All") } } @@ -229,6 +241,7 @@ private fun DownloadOptionsDialog( if (isSmartDownloadEnabled) { TextButton( onClick = { onDownloadOptionSelected(SeriesDownloadOption.DELETE_SMART) }, + modifier = Modifier.testTag(SeriesSmartDownloadButtonTag), colors = ButtonDefaults.textButtonColors( contentColor = MaterialTheme.colorScheme.error ) @@ -236,7 +249,10 @@ private fun DownloadOptionsDialog( Text("Delete Smart Downloads") } } else { - TextButton(onClick = { onDownloadOptionSelected(SeriesDownloadOption.SMART) }) { + TextButton( + onClick = { onDownloadOptionSelected(SeriesDownloadOption.SMART) }, + modifier = Modifier.testTag(SeriesSmartDownloadButtonTag) + ) { Text("Smart Download") } } @@ -258,11 +274,13 @@ internal fun SeasonTabs( horizontalArrangement = Arrangement.spacedBy(20.dp), verticalAlignment = Alignment.CenterVertically ) { - seasons.forEach { season -> + seasons.forEachIndexed { index, season -> SeasonTab( name = season.name, isSelected = season == selectedSeason, - modifier = Modifier.clickable { onSelect(season) } + modifier = Modifier + .testTag("$SeriesSeasonTabTagPrefix$index") + .clickable { onSelect(season) } ) } } @@ -313,7 +331,7 @@ internal fun EpisodeCarousel(episodes: List, modifier: Modifier = Modif LazyRow( state = listState, - modifier = modifier, + modifier = modifier.testTag(SeriesEpisodeCarouselTag), horizontalArrangement = Arrangement.spacedBy(16.dp) ) { items(episodes, key = { episode -> episode.id }) { episode -> @@ -332,6 +350,7 @@ private fun EpisodeCard( Column( modifier = Modifier .width(260.dp) + .testTag("$SeriesEpisodeCardTagPrefix${episode.id}") .clickable { viewModel.onSelectEpisode( seriesId = episode.seriesId, seasonId = episode.seasonId, @@ -416,6 +435,17 @@ private fun EpisodeCard( } } +internal const val SeriesPlayButtonTag = "series-play-button" +internal const val SeriesAddButtonTag = "series-add-button" +internal const val SeriesDownloadButtonTag = "series-download-button" +internal const val SeriesDownloadDialogTag = "series-download-dialog" +internal const val SeriesDownloadSeasonButtonTag = "series-download-season-button" +internal const val SeriesDownloadAllButtonTag = "series-download-all-button" +internal const val SeriesSmartDownloadButtonTag = "series-smart-download-button" +internal const val SeriesSeasonTabTagPrefix = "series-season-tab-" +internal const val SeriesEpisodeCarouselTag = "series-episode-carousel" +internal const val SeriesEpisodeCardTagPrefix = "series-episode-card-" + @Composable internal fun CastRow(cast: List, modifier: Modifier = Modifier) { //TODO fix