Keep catalogue outcomes and recovery consistent across views
This commit is contained in:
parent
c1559e2513
commit
b1b15b384b
21 changed files with 340 additions and 216 deletions
|
|
@ -14,7 +14,8 @@ import androidx.compose.ui.graphics.asImageBitmap
|
|||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onRoot
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
import androidx.compose.ui.test.SemanticsMatcher
|
||||
import androidx.compose.ui.semantics.SemanticsProperties
|
||||
import androidx.compose.ui.semantics.SemanticsActions
|
||||
import androidx.compose.ui.text.TextLayoutResult
|
||||
import org.json.JSONArray
|
||||
|
|
@ -64,23 +65,23 @@ class DiscoverCaptureTest {
|
|||
rule.waitForIdle()
|
||||
val directory = File(requireNotNull(System.getProperty("wolfskin.captureDirectory"))).apply { mkdirs() }
|
||||
rule.onRoot().captureRoboImage(File(directory, "$name.png").absolutePath)
|
||||
recordTypography(directory, name, state)
|
||||
recordTypography(directory, name)
|
||||
}
|
||||
|
||||
private fun recordTypography(directory: File, name: String, state: DiscoverState) {
|
||||
private fun recordTypography(directory: File, name: String) {
|
||||
val rows = JSONArray()
|
||||
val labels = listOfNotNull(state.hero?.title, "Request", "For", "Justin (you)", "Hidden picks", "Netflix", "Popular movies")
|
||||
labels.forEach { text ->
|
||||
rule.onAllNodesWithText(text, useUnmergedTree = true).fetchSemanticsNodes().forEach { node ->
|
||||
val layouts = mutableListOf<TextLayoutResult>()
|
||||
node.config[SemanticsActions.GetTextLayoutResult].action?.invoke(layouts)
|
||||
layouts.forEach { layout ->
|
||||
rows.put(JSONObject().put("text", text).put("lines", layout.lineCount)
|
||||
.put("fontSizeSp", layout.layoutInput.style.fontSize.value).put("fontWeight", layout.layoutInput.style.fontWeight?.weight)
|
||||
.put("lineHeightSp", layout.layoutInput.style.lineHeight.value).put("trim", layout.layoutInput.style.lineHeightStyle?.trim.toString())
|
||||
.put("density", layout.layoutInput.density.density).put("fontScale", layout.layoutInput.density.fontScale)
|
||||
.put("left", node.boundsInRoot.left).put("top", node.boundsInRoot.top).put("width", node.boundsInRoot.width).put("height", node.boundsInRoot.height))
|
||||
}
|
||||
rule.onAllNodes(SemanticsMatcher.keyIsDefined(SemanticsProperties.Text), useUnmergedTree = true).fetchSemanticsNodes().forEach { node ->
|
||||
val layouts = mutableListOf<TextLayoutResult>()
|
||||
if (SemanticsActions.GetTextLayoutResult in node.config) node.config[SemanticsActions.GetTextLayoutResult].action?.invoke(layouts)
|
||||
layouts.forEach { layout ->
|
||||
val text = layout.layoutInput.text.text
|
||||
val lines = JSONArray()
|
||||
repeat(layout.lineCount) { index -> lines.put(text.substring(layout.getLineStart(index), layout.getLineEnd(index, visibleEnd = true))) }
|
||||
rows.put(JSONObject().put("text", text).put("lines", layout.lineCount).put("lineTexts", lines)
|
||||
.put("fontSizeSp", layout.layoutInput.style.fontSize.value).put("fontWeight", layout.layoutInput.style.fontWeight?.weight)
|
||||
.put("lineHeightSp", layout.layoutInput.style.lineHeight.value.takeIf { it.isFinite() } ?: JSONObject.NULL).put("trim", layout.layoutInput.style.lineHeightStyle?.trim.toString())
|
||||
.put("density", layout.layoutInput.density.density).put("fontScale", layout.layoutInput.density.fontScale)
|
||||
.put("left", node.boundsInRoot.left).put("top", node.boundsInRoot.top).put("width", node.boundsInRoot.width).put("height", node.boundsInRoot.height))
|
||||
}
|
||||
}
|
||||
File(directory, "$name-type-metrics.json").writeText(rows.toString(2))
|
||||
|
|
|
|||
|
|
@ -117,6 +117,50 @@ class DiscoverKeysTest {
|
|||
assertEquals(RequestPhase.Receipt, (state.overlay as DiscoverOverlayState.Request).state.phase)
|
||||
}
|
||||
|
||||
@Test fun closingAnUncertainReceiptNeverTurnsReopeningIntoAnotherPost() {
|
||||
operations.next = RequestPhase.NotConfirmed
|
||||
replay("Request a pick for yourself")
|
||||
press(Key.Back, Key.DirectionCenter)
|
||||
assertEquals(RequestPhase.NotConfirmed, (state.overlay as DiscoverOverlayState.Request).state.phase)
|
||||
operations.next = RequestPhase.Receipt
|
||||
press(Key.DirectionCenter)
|
||||
assertEquals(1, operations.sent.size)
|
||||
assertEquals(1, operations.checked)
|
||||
assertEquals(RequestPhase.Receipt, (state.overlay as DiscoverOverlayState.Request).state.phase)
|
||||
}
|
||||
|
||||
@Test fun requestingForAnotherPersonKeepsYourOwnRequestedAction() {
|
||||
replay("Request a pick for yourself")
|
||||
press(Key.DirectionCenter)
|
||||
press(Key.DirectionRight, Key.DirectionCenter)
|
||||
press(Key.DirectionCenter, Key.DirectionCenter)
|
||||
assertEquals(setOf("justin", "mindel"), state.requests.getValue(state.title!!.key).map { it.requester }.toSet())
|
||||
press(Key.DirectionCenter)
|
||||
press(Key.DirectionLeft, Key.DirectionCenter)
|
||||
assertTrue(shell.requestsVisible)
|
||||
assertEquals(2, operations.sent.size)
|
||||
}
|
||||
|
||||
@Test fun choosingAnUncertainRecipientAgainNeverResubmits() {
|
||||
operations.next = RequestPhase.NotConfirmed
|
||||
replay("Open Discover")
|
||||
press(Key.DirectionCenter)
|
||||
press(Key.DirectionRight, Key.DirectionCenter)
|
||||
press(Key.DirectionDown, Key.DirectionCenter)
|
||||
press(Key.DirectionCenter)
|
||||
assertEquals("shared", operations.sent.single().requester)
|
||||
press(Key.Back, Key.DirectionCenter)
|
||||
press(Key.DirectionDown, Key.DirectionCenter)
|
||||
press(Key.DirectionCenter)
|
||||
assertEquals(1, operations.sent.size)
|
||||
assertEquals(RequestPhase.NotConfirmed, (state.overlay as DiscoverOverlayState.Request).state.phase)
|
||||
operations.next = RequestPhase.Receipt
|
||||
press(Key.DirectionCenter)
|
||||
assertEquals(1, operations.sent.size)
|
||||
assertEquals(1, operations.checked)
|
||||
assertEquals(RequestPhase.Receipt, (state.overlay as DiscoverOverlayState.Request).state.phase)
|
||||
}
|
||||
|
||||
@Test fun unknownSeriesSeasonsNeverSubmitAndBackRestoresOpener() {
|
||||
replay("Open Discover")
|
||||
rule.runOnIdle { state.openTitle(snapshot.picks.first { it.type == "tv" }); state.openRequest(false) }
|
||||
|
|
@ -155,6 +199,37 @@ class DiscoverKeysTest {
|
|||
assertEquals(GridTail.ShowMore, state.collection!!.tail)
|
||||
}
|
||||
|
||||
@Test fun collectionRetryReadsNewTitlesAndOpensTheFirstAppendedTitle() {
|
||||
shell.pickerVisible = false
|
||||
val lane = state.lanes.first { it.id == "trending" }
|
||||
state.openCollection(lane.copy(titles = lane.fullTitles.take(6), fullTitles = lane.fullTitles.take(6), tail = DiscoverTail.Retry))
|
||||
val grid = state.collection!!
|
||||
grid.index = grid.tailIndex
|
||||
launch()
|
||||
press(Key.DirectionCenter)
|
||||
assertEquals(GridTail.None, grid.tail)
|
||||
assertEquals(lane.fullTitles.map { it.key }, grid.items.map { it.id })
|
||||
assertEquals(lane.fullTitles[6].key, grid.focusedTitle?.id)
|
||||
press(Key.DirectionCenter)
|
||||
assertEquals(lane.fullTitles[6], state.title)
|
||||
}
|
||||
|
||||
@Test fun collectionDismissalAndUndoKeepTheOriginalOrderAndFocus() {
|
||||
shell.pickerVisible = false
|
||||
state.openCollection(state.lanes.first { it.id == "pk-movie" })
|
||||
val grid = state.collection!!
|
||||
val original = grid.items.map { it.id }
|
||||
launch()
|
||||
press(Key.DirectionCenter)
|
||||
val opener = state.title!!
|
||||
press(Key.DirectionRight, Key.DirectionRight)
|
||||
press(Key.DirectionCenter)
|
||||
assertFalse(grid.items.any { it.id == opener.key })
|
||||
press(Key.DirectionCenter)
|
||||
assertEquals(original, grid.items.map { it.id })
|
||||
assertEquals(opener.key, grid.focusedTitle?.id)
|
||||
}
|
||||
|
||||
@Test fun collectionBackRestoresTheExactOrigin() {
|
||||
shell.pickerVisible = false; shell.enterDestination("discover")
|
||||
state.focus(3, 2)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.design
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
/** Feed projection must not lose titles when a contribution row is too short to display. */
|
||||
class DiscoverLaneProjectionTest {
|
||||
@Test fun discardedSeedGroupRemainsAvailableInCatalogue() {
|
||||
val titles = (1L..6L).map { DiscoverTitle(it, "movie", "Film $it") }
|
||||
val snapshot = DiscoverSnapshot(picks = titles, seeds = listOf(DiscoverSeed("Seed", titles.subList(3, 5))),
|
||||
lanes = listOf(DiscoverLane("trending", "Trending", titles)), status = DiscoverStatus.Ready)
|
||||
val rows = projectDiscoverLanes(snapshot, emptyList(), "all", emptyMap())
|
||||
assertEquals(listOf("pk-movie", "trending"), rows.map { it.id })
|
||||
assertEquals(titles.take(3), rows.first().titles)
|
||||
assertEquals(titles.drop(3), rows.last().titles)
|
||||
}
|
||||
|
||||
@Test fun catalogueDeduplicationKeepsMediaTypesDistinctAndHiddenPicksLast() {
|
||||
val movie = DiscoverTitle(1, "movie", "Film")
|
||||
val series = DiscoverTitle(1, "tv", "Series")
|
||||
val hidden = DiscoverTitle(2, "movie", "Hidden")
|
||||
val later = DiscoverTitle(3, "movie", "Later")
|
||||
val snapshot = DiscoverSnapshot(hidden = listOf(hidden), status = DiscoverStatus.Ready, lanes = listOf(
|
||||
DiscoverLane("first", "First", listOf(movie, series, hidden)),
|
||||
DiscoverLane("second", "Second", listOf(movie, series, later))))
|
||||
val rows = projectDiscoverLanes(snapshot, emptyList(), "all", mapOf(hidden.key to hidden))
|
||||
assertEquals(listOf(listOf(movie, series), listOf(later), listOf(hidden)), rows.map { it.titles })
|
||||
assertEquals("hidden", rows.last().id)
|
||||
val filtered = projectDiscoverLanes(snapshot, emptyList(), "tv", mapOf(hidden.key to hidden))
|
||||
assertEquals(listOf(listOf(series)), filtered.map { it.titles })
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue