summaryrefslogtreecommitdiff
path: root/app/src
diff options
context:
space:
mode:
Diffstat (limited to 'app/src')
-rw-r--r--app/src/androidTest/java/org/terst/listen/ui/EpisodeTransportTest.kt105
-rw-r--r--app/src/androidTest/java/org/terst/listen/ui/NowPlayingBarTest.kt91
-rw-r--r--app/src/androidTest/java/org/terst/listen/ui/SkipReadoutTest.kt94
3 files changed, 290 insertions, 0 deletions
diff --git a/app/src/androidTest/java/org/terst/listen/ui/EpisodeTransportTest.kt b/app/src/androidTest/java/org/terst/listen/ui/EpisodeTransportTest.kt
new file mode 100644
index 0000000..26c6d4a
--- /dev/null
+++ b/app/src/androidTest/java/org/terst/listen/ui/EpisodeTransportTest.kt
@@ -0,0 +1,105 @@
+package org.terst.listen.ui
+
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Assert.assertEquals
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * The episode transport, driven as a composable rather than looked at.
+ *
+ * The first Compose test in this app. Everything visual has been verified by eye on the emulator
+ * until now, which catches "does it look right" and never catches "does the button do the thing
+ * when the state changes".
+ */
+@RunWith(AndroidJUnit4::class)
+class EpisodeTransportTest {
+
+ @get:Rule
+ val compose = createComposeRule()
+
+ private fun playback(isPlaying: Boolean, positionMs: Long = 0) = EpisodePlayback(
+ positionMs = positionMs,
+ durationMs = 600_000,
+ isPlaying = isPlaying,
+ breaks = emptyList(),
+ )
+
+ private fun actions(
+ onAd: () -> Unit = {},
+ onSkip: (Long) -> Unit = {},
+ onToggle: () -> Unit = {},
+ ) = EpisodeDetailActions(
+ enqueue = {}, download = {}, downloadFromPublisher = {}, togglePlayed = {},
+ markOlderPlayed = {}, togglePlayPause = onToggle, seekTo = {}, skip = onSkip,
+ setSpeed = {}, reportAd = onAd,
+ )
+
+ @Test
+ fun theAdButtonIsOfferedWhileSomethingIsPlaying() {
+ compose.setContent { EpisodeTransport(playback(isPlaying = true), 1.0f, actions()) }
+
+ compose.onNodeWithContentDescription("This is an ad").assertIsEnabled()
+ }
+
+ /**
+ * Nothing is playing, so there is no advertising to be in the middle of. An enabled button
+ * would record a claim about a position nobody is at.
+ */
+ @Test
+ fun theAdButtonIsNotOfferedWhenNothingIsPlaying() {
+ compose.setContent { EpisodeTransport(playback(isPlaying = false), 1.0f, actions()) }
+
+ compose.onNodeWithContentDescription("This is an ad").assertIsNotEnabled()
+ }
+
+ @Test
+ fun pressingTheAdButtonReportsIt() {
+ var reported = 0
+ compose.setContent { EpisodeTransport(playback(isPlaying = true), 1.0f, actions(onAd = { reported++ })) }
+
+ compose.onNodeWithContentDescription("This is an ad").performClick()
+
+ assertEquals(1, reported)
+ }
+
+ /** The primary control says what pressing it will do, which is the whole reason it is worded. */
+ @Test
+ fun theMainControlSaysPauseWhilePlaying() {
+ compose.setContent { EpisodeTransport(playback(isPlaying = true), 1.0f, actions()) }
+
+ compose.onNodeWithText("Pause").assertIsEnabled()
+ }
+
+ @Test
+ fun theMainControlSaysResumeWhenStoppedPartWayThrough() {
+ compose.setContent { EpisodeTransport(playback(isPlaying = false, positionMs = 60_000), 1.0f, actions()) }
+
+ compose.onNodeWithText("Resume").assertIsEnabled()
+ }
+
+ @Test
+ fun theMainControlSaysPlayFromTheStart() {
+ compose.setContent { EpisodeTransport(playback(isPlaying = false), 1.0f, actions()) }
+
+ compose.onNodeWithText("Play").assertIsEnabled()
+ }
+
+ @Test
+ fun theSkipButtonsMoveByTheAmountTheyAdvertise() {
+ val moved = mutableListOf<Long>()
+ compose.setContent { EpisodeTransport(playback(isPlaying = true), 1.0f, actions(onSkip = { moved += it })) }
+
+ compose.onNodeWithContentDescription("Back 15 seconds").performClick()
+ compose.onNodeWithContentDescription("Forward 25 seconds").performClick()
+
+ assertEquals(listOf(-15_000L, 25_000L), moved)
+ }
+}
diff --git a/app/src/androidTest/java/org/terst/listen/ui/NowPlayingBarTest.kt b/app/src/androidTest/java/org/terst/listen/ui/NowPlayingBarTest.kt
new file mode 100644
index 0000000..483e934
--- /dev/null
+++ b/app/src/androidTest/java/org/terst/listen/ui/NowPlayingBarTest.kt
@@ -0,0 +1,91 @@
+package org.terst.listen.ui
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Assert.assertEquals
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.terst.listen.playback.NowPlaying
+
+/**
+ * The only persistent chrome in the app, and the way back to whatever is playing.
+ *
+ * Worth testing because it is the thing that saves you when a screen is showing something else:
+ * if it stops naming the episode, or its play button stops reporting state, the app looks fine
+ * and strands you.
+ */
+@RunWith(AndroidJUnit4::class)
+class NowPlayingBarTest {
+
+ @get:Rule
+ val compose = createComposeRule()
+
+ private fun playing(isPlaying: Boolean = true) = NowPlaying(
+ episodeId = "e1",
+ title = "Ukraine's President Sits With NPR",
+ showTitle = "Up First from NPR",
+ isPlaying = isPlaying,
+ positionMs = 281_000,
+ durationMs = 908_000,
+ speed = 1.25f,
+ )
+
+ private fun actions(
+ onToggle: () -> Unit = {},
+ onOpenEpisode: () -> Unit = {},
+ onOpenQueue: () -> Unit = {},
+ ) = NowPlayingActions(
+ togglePlayPause = onToggle,
+ skipBack = {},
+ skipForward = {},
+ openEpisode = onOpenEpisode,
+ openQueue = onOpenQueue,
+ )
+
+ @Test
+ fun namesTheEpisodeAndTheShow() {
+ compose.setContent { NowPlayingBar(playing(), actions()) }
+
+ compose.onNodeWithText("Ukraine's President Sits With NPR", substring = true).assertIsDisplayed()
+ compose.onNodeWithText("Up First from NPR", substring = true).assertIsDisplayed()
+ }
+
+ @Test
+ fun tappingTheBarOpensWhatIsPlaying() {
+ var opened = 0
+ compose.setContent { NowPlayingBar(playing(), actions(onOpenEpisode = { opened++ })) }
+
+ compose.onNodeWithText("Ukraine's President Sits With NPR", substring = true).performClick()
+
+ assertEquals("the bar is the way back to the player", 1, opened)
+ }
+
+ @Test
+ fun offersPauseWhilePlaying() {
+ compose.setContent { NowPlayingBar(playing(isPlaying = true), actions()) }
+
+ compose.onNodeWithContentDescription("Pause").assertIsDisplayed()
+ }
+
+ @Test
+ fun offersPlayWhileStopped() {
+ compose.setContent { NowPlayingBar(playing(isPlaying = false), actions()) }
+
+ compose.onNodeWithContentDescription("Play").assertIsDisplayed()
+ }
+
+ @Test
+ fun theControlActuallyToggles() {
+ var toggled = 0
+ compose.setContent { NowPlayingBar(playing(), actions(onToggle = { toggled++ })) }
+
+ compose.onNodeWithContentDescription("Pause").performClick()
+
+ assertEquals(1, toggled)
+ }
+}
diff --git a/app/src/androidTest/java/org/terst/listen/ui/SkipReadoutTest.kt b/app/src/androidTest/java/org/terst/listen/ui/SkipReadoutTest.kt
new file mode 100644
index 0000000..49d8da3
--- /dev/null
+++ b/app/src/androidTest/java/org/terst/listen/ui/SkipReadoutTest.kt
@@ -0,0 +1,94 @@
+package org.terst.listen.ui
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.terst.listen.fingerprint.EpisodeSkips
+import org.terst.listen.fingerprint.SkipSummary
+
+/**
+ * The three numbers under the timeline, and the legend beside them.
+ *
+ * This is the sanity check Peter asked for: the gap between served and declared is the
+ * advertising in the file, and "cut" is what the detector took out, so a cut far from that gap
+ * is the detector being wrong. The point of testing it is that a readout which quietly stops
+ * saying one of the three looks completely normal.
+ */
+@RunWith(AndroidJUnit4::class)
+class SkipReadoutTest {
+
+ @get:Rule
+ val compose = createComposeRule()
+
+ private fun marker(kind: String) =
+ SkipSummary.BreakMarker(start = 0.1f, end = 0.2f, kind = kind, startMs = 60_000, endMs = 120_000)
+
+ @Test
+ fun showsAllThreeNumbersOnOneLine() {
+ val skips = EpisodeSkips(served = "15:08", declared = "13:30", cut = "0:47", status = null)
+
+ compose.setContent { SkipReadout(skips, listOf(marker("ad"))) }
+
+ compose.onNodeWithText("Served 15:08 · Declared 13:30 · Cut 0:47").assertIsDisplayed()
+ }
+
+ /**
+ * "Nothing found" and "not checked yet" are different answers, and collapsing them is the
+ * mistake the old readout made.
+ */
+ @Test
+ fun saysWhyWhenThereIsNothingToCut() {
+ val skips = EpisodeSkips(served = "15:08", declared = "13:30", cut = null, status = "Not checked yet")
+
+ compose.setContent { SkipReadout(skips, emptyList()) }
+
+ compose.onNodeWithText("Not checked yet").assertIsDisplayed()
+ }
+
+ @Test
+ fun theLegendNamesAdvertisingWhenSomeIsMarked() {
+ compose.setContent {
+ SkipReadout(EpisodeSkips(null, null, "0:47", null), listOf(marker("ad")))
+ }
+
+ compose.onNodeWithText(SkipSummary.LEGEND_ADVERTISING).assertIsDisplayed()
+ }
+
+ /**
+ * The one distinction that changes what happens: advertising is always cut, the show's own
+ * audio follows the setting. The legend only claims what is actually on the track.
+ */
+ @Test
+ fun theLegendNamesTheShowsOwnAudioWhenThatIsWhatIsMarked() {
+ compose.setContent {
+ SkipReadout(EpisodeSkips(null, null, "0:12", null), listOf(marker("outro")))
+ }
+
+ compose.onNodeWithText(SkipSummary.LEGEND_OWN_AUDIO).assertIsDisplayed()
+ compose.onNodeWithText(SkipSummary.LEGEND_ADVERTISING).assertDoesNotExist()
+ }
+
+ @Test
+ fun bothAreNamedWhenBothAreOnTheTrack() {
+ compose.setContent {
+ SkipReadout(EpisodeSkips(null, null, "1:00", null), listOf(marker("ad"), marker("cue")))
+ }
+
+ compose.onNodeWithText(SkipSummary.LEGEND_ADVERTISING).assertIsDisplayed()
+ compose.onNodeWithText(SkipSummary.LEGEND_OWN_AUDIO).assertIsDisplayed()
+ }
+
+ @Test
+ fun noLegendWhenNothingIsMarked() {
+ compose.setContent {
+ SkipReadout(EpisodeSkips("15:08", "13:30", null, "None found"), emptyList())
+ }
+
+ compose.onNodeWithText(SkipSummary.LEGEND_ADVERTISING).assertDoesNotExist()
+ compose.onNodeWithText(SkipSummary.LEGEND_OWN_AUDIO).assertDoesNotExist()
+ }
+}