Record native title metrics and reuse SDK fixtures
This commit is contained in:
parent
0ca13e684f
commit
fc7eb94cc1
4 changed files with 36 additions and 14 deletions
|
|
@ -12,6 +12,11 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onRoot
|
||||
import androidx.compose.ui.test.onAllNodesWithTag
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.performSemanticsAction
|
||||
import androidx.compose.ui.semantics.SemanticsActions
|
||||
import androidx.compose.ui.text.TextLayoutResult
|
||||
import com.github.takahirom.roborazzi.captureRoboImage
|
||||
import org.json.JSONObject
|
||||
import org.junit.Before
|
||||
|
|
@ -97,6 +102,19 @@ class TitleCaptureTest {
|
|||
rule.waitForIdle()
|
||||
val directory = File(requireNotNull(System.getProperty("wolfskin.captureDirectory"))).apply { mkdirs() }
|
||||
rule.onRoot().captureRoboImage(File(directory, "st-${number.toString().padStart(3, '0')}.png").absolutePath)
|
||||
val heading = rule.onAllNodesWithTag("title-heading").fetchSemanticsNodes().singleOrNull()
|
||||
if (heading != null) {
|
||||
val layouts = mutableListOf<TextLayoutResult>()
|
||||
rule.onNodeWithTag("title-heading").performSemanticsAction(SemanticsActions.GetTextLayoutResult) { action -> action(layouts) }
|
||||
val layout = layouts.single()
|
||||
val metrics = JSONObject().put("title", layout.layoutInput.text.text).put("lineCount", layout.lineCount)
|
||||
.put("lineWidthsPx", org.json.JSONArray(List(layout.lineCount) { layout.getLineRight(it) - layout.getLineLeft(it) }))
|
||||
.put("ellipsized", org.json.JSONArray(List(layout.lineCount) { layout.isLineEllipsized(it) }))
|
||||
.put("maxWidthPx", layout.layoutInput.constraints.maxWidth).put("fontSizeSp", layout.layoutInput.style.fontSize.value)
|
||||
.put("requestedWeight", layout.layoutInput.style.fontWeight?.weight)
|
||||
.put("boundsPx", org.json.JSONArray(listOf(heading.boundsInRoot.left, heading.boundsInRoot.top, heading.boundsInRoot.right, heading.boundsInRoot.bottom)))
|
||||
File(directory, "st-${number.toString().padStart(3, '0')}-layout.json").writeText(metrics.toString(2))
|
||||
}
|
||||
}
|
||||
|
||||
private fun option(state: TitleState, id: String) { state.focus("header", state.options.indexOfFirst { it.id == id }) }
|
||||
|
|
@ -113,7 +131,7 @@ class TitleCaptureTest {
|
|||
@Test fun st046_tracks() = capture(46) { state, shell -> open(state, shell, "tracks") }
|
||||
@Test fun st047_mediaInfo() = capture(47) { state, shell -> open(state, shell, "more") }
|
||||
@Test fun st048_trailerNoApp() = capture(48) { state, _ -> option(state, "trailer"); state.toast = "No YouTube app is available to play this trailer." }
|
||||
@Test fun st049_watched() = capture(49) { state, _ -> state.restoreWatched(true); option(state, "watched") }
|
||||
@Test fun st049_watched() = capture(49, transform = { it.copy(watchTarget = it.watchTarget.copy(watched = true)) }) { state, _ -> option(state, "watched") }
|
||||
@Test fun st050_seriesConfirm() = capture(50, "Andor") { state, shell -> open(state, shell, "watched") }
|
||||
@Test fun st051_episodeRestart() = capture(51, "episode-sex-and-the-country-2001-s4e9", "mindel") { state, _ -> option(state, "restart") }
|
||||
@Test fun st052_filmCast() = capture(52) { state, _ -> state.focus("cast", 0) }
|
||||
|
|
|
|||
|
|
@ -24,8 +24,7 @@ import org.jellyfin.sdk.api.operations.TvShowsApi
|
|||
import org.jellyfin.sdk.api.operations.UserLibraryApi
|
||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.MediaSourceInfo
|
||||
import org.jellyfin.sdk.model.api.MediaStream
|
||||
import com.github.damontecres.wholphin.test.TestTracks
|
||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||
import org.jellyfin.sdk.model.api.request.GetEpisodesRequest
|
||||
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
||||
|
|
@ -73,14 +72,19 @@ class TitleReadFacadeTest {
|
|||
facade = TitleReadFacade(api, mockk<ImageUrlService>(relaxed = true), playback, repository, mockk<FavoriteWatchManager>(), context)
|
||||
}
|
||||
|
||||
private fun file(id: UUID, kind: BaseItemKind = BaseItemKind.MOVIE) = BaseItemDto(
|
||||
id = id, name = "A real file", type = kind, runTimeTicks = 6_000_000_000,
|
||||
mediaSources = listOf(MediaSourceInfo(id = sourceId, name = "Original", container = "mkv", mediaStreams = listOf(
|
||||
MediaStream(index = 0, type = MediaStreamType.VIDEO, displayTitle = "1080p"),
|
||||
MediaStream(index = 3, type = MediaStreamType.AUDIO, displayTitle = "English", isDefault = true),
|
||||
MediaStream(index = 8, type = MediaStreamType.SUBTITLE, displayTitle = "Dutch"),
|
||||
))),
|
||||
)
|
||||
private fun file(id: UUID, kind: BaseItemKind = BaseItemKind.MOVIE): BaseItemDto {
|
||||
val source = TestTracks.Builder().addVideo().addAudio().addSubtitle().buildMediaSourceInfo()
|
||||
val streams = source.mediaStreams.orEmpty().map { stream ->
|
||||
when (stream.type) {
|
||||
MediaStreamType.VIDEO -> stream.copy(index = 0, displayTitle = "1080p")
|
||||
MediaStreamType.AUDIO -> stream.copy(index = 3, displayTitle = "English", isDefault = true)
|
||||
MediaStreamType.SUBTITLE -> stream.copy(index = 8, displayTitle = "Dutch")
|
||||
else -> error("Unexpected track in the SDK fixture builder")
|
||||
}
|
||||
}
|
||||
return BaseItemDto(id = id, name = "A real file", type = kind, runTimeTicks = 6_000_000_000,
|
||||
mediaSources = listOf(source.copy(id = sourceId, name = "Original", container = "mkv", mediaStreams = streams)))
|
||||
}
|
||||
|
||||
@Test fun diskCopyPreservesFileAndNonContiguousStreamIndexesWithoutRatingsBeforeSignIn() = runTest {
|
||||
coEvery { library.getItem(firstId) } returns successResponse(file(firstId))
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package org.justinvisser.wolfskin.design
|
|||
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.layout.boundsInRoot
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
|
@ -45,7 +46,7 @@ fun TitleHeader(state: TitleState, active: Boolean, modifier: Modifier = Modifie
|
|||
if (state.data.hidden) append(" · Hidden from your picks")
|
||||
}, color = C.accentText, style = D.dLine)
|
||||
}
|
||||
Text(item.name, color = C.fg, style = WolfskinTokens.Type.displayDetail, maxLines = 2, overflow = TextOverflow.Ellipsis, modifier = Modifier.padding(bottom = D.titleBottom))
|
||||
Text(item.name, color = C.fg, style = WolfskinTokens.Type.displayDetail, maxLines = 2, overflow = TextOverflow.Ellipsis, modifier = Modifier.padding(bottom = D.titleBottom).testTag("title-heading"))
|
||||
Row(Modifier.heightIn(min = L.iconCaptionLine + D.factsBottom).padding(bottom = D.factsBottom), horizontalArrangement = Arrangement.spacedBy(D.factsGap), verticalAlignment = Alignment.CenterVertically) {
|
||||
item.year?.let { Text(it.toString(), color = C.muted, style = WolfskinTokens.Type.body) }
|
||||
if (item.kind == "movie" && item.runtimeSeconds > 0) Text(item.runtime, color = C.muted, style = WolfskinTokens.Type.body)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package org.justinvisser.wolfskin.design
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.core.tween
|
||||
|
|
@ -130,7 +129,7 @@ fun TitlePoster(item: TitleItem, focused: Boolean, modifier: Modifier = Modifier
|
|||
if (item.held && !tile) Box(Modifier.align(Alignment.BottomEnd).padding(L.badgeInset).size(L.badge).background(C.ok, CircleShape), contentAlignment = Alignment.Center) {
|
||||
TitleGlyph("check", C.onFocus, Modifier.size(L.buttonIcon))
|
||||
}
|
||||
AnimatedVisibility(focused && ratingsVisible, enter = fadeIn(tween(WolfskinTokens.Motion.base)), exit = fadeOut(tween(WolfskinTokens.Motion.base))) {
|
||||
androidx.compose.animation.AnimatedVisibility(focused && ratingsVisible, enter = fadeIn(tween(WolfskinTokens.Motion.base)), exit = fadeOut(tween(WolfskinTokens.Motion.base))) {
|
||||
TitleRatingPlate(item.ratings, Modifier.fillMaxWidth())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue