What 505 real channels showed that 20 demo ones could not
All checks were successful
Build TV app / build (push) Successful in 42s
All checks were successful
Build TV app / build (push) Successful in 42s
The favourite count sat at zero while the backend already had two: the rail read the favourites off the StateFlow inside the composable instead of collecting it, so it never recomposed — a regression from making favourites work for both source types. The list collects again. Real EPG titles broke the fixture shortener in both directions. "BL: VfB Stuttgart - Saison 25-26" became a fixture against a season, and "… FC Bayern München - VfB Stuttgart - 1. Halbzeit" grew a third team; a season, a matchday, a half or a year is now rejected, and a second dash ends the away side. The kick-off chip also gave way in the row: the line below already carries the time, and "Bayer Leverkusen - VfB Stuttgart" needed the width to fit at all. Merged provider EPGs list the same programme several times, minutes apart — the day plan showed The Big Bang Theory three times and two entries marked as running at once. XmltvParser.collapseDuplicates folds a repeat into the entry whose slot it falls inside. The login code expired before anyone could confirm it: Authentik hands out a minute, and the TV dropped back to step one without a word. It fetches a fresh code instead, up to eight times. Tested against the household's own Dispatcharr through api.tv.beckm4nn.net. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
74
tests/unit/DuplicateProgrammesTest.kt
Normal file
74
tests/unit/DuplicateProgrammesTest.kt
Normal file
@@ -0,0 +1,74 @@
|
||||
package dev.castarr.tv.data
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class DuplicateProgrammesTest {
|
||||
|
||||
private fun p(startMin: Int, stopMin: Int, title: String) =
|
||||
Programme(startMin * 60_000L, stopMin * 60_000L, title)
|
||||
|
||||
/** The real case: one channel, one episode, three EPG entries. */
|
||||
@Test
|
||||
fun `repeats a few minutes apart collapse into one`() {
|
||||
val collapsed = XmltvParser.collapseDuplicates(
|
||||
listOf(
|
||||
p(1090, 1140, "The Big Bang Theory"),
|
||||
p(1095, 1145, "The Big Bang Theory"),
|
||||
p(1120, 1170, "The Big Bang Theory"),
|
||||
)
|
||||
)
|
||||
assertEquals(1, collapsed.size)
|
||||
assertEquals(1090 * 60_000L, collapsed.first().start)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a genuine repeat later in the evening survives`() {
|
||||
val collapsed = XmltvParser.collapseDuplicates(
|
||||
listOf(
|
||||
p(1090, 1140, "Tagesschau"),
|
||||
p(1300, 1320, "Tagesschau"),
|
||||
)
|
||||
)
|
||||
assertEquals(2, collapsed.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `different programmes at the same time both stay`() {
|
||||
val collapsed = XmltvParser.collapseDuplicates(
|
||||
listOf(
|
||||
p(1090, 1140, "Sportschau"),
|
||||
p(1092, 1142, "Tagesschau"),
|
||||
)
|
||||
)
|
||||
assertEquals(2, collapsed.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a repeat inside the first slot collapses even beyond the tolerance`() {
|
||||
val collapsed = XmltvParser.collapseDuplicates(
|
||||
listOf(
|
||||
p(1000, 1200, "Fußball: Konferenz"),
|
||||
p(1100, 1300, "Fußball: Konferenz"),
|
||||
)
|
||||
)
|
||||
assertEquals(1, collapsed.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an unsorted list is handled in time order`() {
|
||||
val collapsed = XmltvParser.collapseDuplicates(
|
||||
listOf(
|
||||
p(1095, 1145, "Two and a Half Men"),
|
||||
p(1090, 1140, "Two and a Half Men"),
|
||||
)
|
||||
)
|
||||
assertEquals(1, collapsed.size)
|
||||
assertEquals(1090 * 60_000L, collapsed.first().start)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an empty list stays empty`() {
|
||||
assertEquals(emptyList<Programme>(), XmltvParser.collapseDuplicates(emptyList()))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user