From 8d0be21baad22339840d0e3ec8b7bb23fca9b7a9 Mon Sep 17 00:00:00 2001
From: Nicholas Sala <salanicholas.14@gmail.com>
Date: Fri, 15 Nov 2024 18:56:27 +0100
Subject: [PATCH 1/9] Created tests for CachingRepository

---
 gradle/libs.versions.toml                     |  4 +
 new-player/build.gradle.kts                   |  2 +
 .../repository/CachingRepositoryTest.kt       | 95 +++++++++++++++++++
 .../repository/MockMediaRepository.kt         | 79 +++++++++++++++
 4 files changed, 180 insertions(+)
 create mode 100644 new-player/src/test/java/net/newpipe/newplayer/repository/CachingRepositoryTest.kt
 create mode 100644 new-player/src/test/java/net/newpipe/newplayer/repository/MockMediaRepository.kt

diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 31dcd972..7a4a9872 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -25,10 +25,12 @@ junit = "4.13.2"
 junitVersion = "1.2.1"
 espressoCore = "3.6.1"
 appcompat = "1.7.0"
+kotlinxCoroutinesTest = "1.9.0"
 material = "1.12.0"
 androidx = "1.9.0"
 constraintlayout = "2.1.4"
 material3 = "1.2.1"
+mockk = "1.13.13"
 uiTooling = "1.6.8"
 materialIconsExtendedAndroid = "1.7.0-beta05"
 media3 = "1.3.1"
@@ -55,6 +57,7 @@ junit = { group = "junit", name = "junit", version.ref = "junit" }
 androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
 androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
 androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutinesTest" }
 material = { group = "com.google.android.material", name = "material", version.ref = "material" }
 androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "androidx" }
 androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
@@ -79,6 +82,7 @@ androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
 androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
 androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
 androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
+mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
 okhttp-android = { group = "com.squareup.okhttp3", name = "okhttp-android", version.ref = "okhttpAndroid" }
 coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" }
 reorderable = { group = "sh.calvin.reorderable", name = "reorderable", version.ref = "reorderable" }
diff --git a/new-player/build.gradle.kts b/new-player/build.gradle.kts
index ec8c434b..5acf227f 100644
--- a/new-player/build.gradle.kts
+++ b/new-player/build.gradle.kts
@@ -81,6 +81,8 @@ dependencies {
     ksp(libs.androidx.hilt.compiler)
 
     testImplementation(libs.junit)
+    testImplementation(libs.kotlinx.coroutines.test)
+    testImplementation(libs.mockk)
 
     androidTestImplementation(libs.androidx.junit)
     androidTestImplementation(libs.androidx.espresso.core)
diff --git a/new-player/src/test/java/net/newpipe/newplayer/repository/CachingRepositoryTest.kt b/new-player/src/test/java/net/newpipe/newplayer/repository/CachingRepositoryTest.kt
new file mode 100644
index 00000000..0aea6727
--- /dev/null
+++ b/new-player/src/test/java/net/newpipe/newplayer/repository/CachingRepositoryTest.kt
@@ -0,0 +1,95 @@
+package net.newpipe.newplayer.repository
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.Test
+import org.junit.Assert.*
+import org.junit.BeforeClass
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class CachingRepositoryTest {
+    val mockMediaRepository = MockMediaRepository()
+    val delayTestRepository = DelayTestRepository(mockMediaRepository, 100)
+    val repository = CachingRepository(delayTestRepository)
+
+    companion object {
+        @JvmStatic
+        @BeforeClass
+        fun init() {
+            Dispatchers.setMain(StandardTestDispatcher())
+        }
+
+        @JvmStatic
+        @BeforeClass
+        fun reset() {
+            Dispatchers.resetMain()
+        }
+    }
+
+    @Test
+    fun getRepoInfo() {
+        val repoInfo = repository.getRepoInfo()
+        assertTrue(repoInfo.pullsDataFromNetwork)
+        assertTrue(repoInfo.canHandleTimestampedLinks)
+    }
+
+    @Test
+    fun getMetaInfo() = runTest {
+        val metaInfo = repository.getMetaInfo("test")
+        assertEquals("Test title", metaInfo.title)
+    }
+
+    @Test
+    fun getStreams_returnsCorrectList() = runTest {
+        val streams = repository.getStreams("item")
+        assertEquals(3, streams.size)
+        assertEquals("item1", streams.get(0).item)
+        assertEquals("item2", streams.get(1).item)
+        assertEquals("item3", streams.get(2).item)
+    }
+
+    @Test
+    fun getSubtitles_returnsCorrectList() = runTest {
+        val subtitles = repository.getSubtitles("item")
+        assertEquals(3, subtitles.size)
+        assertEquals("subtitle1", subtitles.get(0).identifier)
+        assertEquals("subtitle2", subtitles.get(1).identifier)
+        assertEquals("subtitle3", subtitles.get(2).identifier)
+    }
+
+    @Test
+    fun getPreviewThumbnail() = runTest {
+        val previewThumbnail = repository.getPreviewThumbnail("item", 1000)
+        assertNull(previewThumbnail)
+    }
+
+    @Test
+    fun getPreviewThumbnailInfo() = runTest {
+        val previewThumbnailInfo = repository.getPreviewThumbnailsInfo("item")
+        assertEquals(10, previewThumbnailInfo.count)
+        assertEquals(500, previewThumbnailInfo.distanceInMS)
+    }
+
+    @Test
+    fun getChapters_returnsCorrectList() = runTest {
+        val chapters = repository.getChapters("item")
+        assertEquals(3, chapters.size)
+        assertEquals("chapter1", chapters.get(0).chapterTitle)
+        assertEquals("chapter2", chapters.get(1).chapterTitle)
+        assertEquals("chapter3", chapters.get(2).chapterTitle)
+    }
+
+    @Test
+    fun getTimestampLink() = runTest {
+        assertEquals("test/link", repository.getTimestampLink("item", 0))
+    }
+
+    @Test
+    fun getHttpDataSourceFactory() {
+        assertNotNull(repository.getHttpDataSourceFactory("item"))
+    }
+}
\ No newline at end of file
diff --git a/new-player/src/test/java/net/newpipe/newplayer/repository/MockMediaRepository.kt b/new-player/src/test/java/net/newpipe/newplayer/repository/MockMediaRepository.kt
new file mode 100644
index 00000000..8ce9e4f6
--- /dev/null
+++ b/new-player/src/test/java/net/newpipe/newplayer/repository/MockMediaRepository.kt
@@ -0,0 +1,79 @@
+/* NewPlayer
+ *
+ * @author Christian Schabesberger
+ *
+ * Copyright (C) NewPipe e.V. 2024 <code(at)newpipe-ev.de>
+ *
+ * NewPlayer is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * NewPlayer is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with NewPlayer.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.newpipe.newplayer.repository
+
+import android.net.Uri
+import androidx.media3.common.MediaMetadata
+import androidx.media3.datasource.DefaultHttpDataSource
+import androidx.media3.datasource.HttpDataSource
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.mockkStatic
+import net.newpipe.newplayer.data.Chapter
+import net.newpipe.newplayer.data.Stream
+import net.newpipe.newplayer.data.Subtitle
+
+/**
+ * Simple MediaRepository with mock values
+ */
+class MockMediaRepository : MediaRepository {
+    val uriMock: Uri
+
+    init {
+        mockkStatic(Uri::class)
+        every { Uri.parse(any()) } returns mockk("Uri")
+        uriMock = Uri.parse("test/uri")
+    }
+
+    override fun getRepoInfo() = MediaRepository.RepoMetaInfo(canHandleTimestampedLinks = true, pullsDataFromNetwork = true)
+
+    override suspend fun getMetaInfo(item: String) = MediaMetadata.Builder().setTitle("Test title").build()
+
+    override suspend fun getStreams(item: String) = listOf(
+        Stream("item1", uriMock, emptyList()),
+        Stream("item2", uriMock, emptyList()),
+        Stream("item3", uriMock, emptyList())
+    )
+
+    override suspend fun getSubtitles(item: String) = listOf(
+        Subtitle(uriMock, "subtitle1"),
+        Subtitle(uriMock, "subtitle2"),
+        Subtitle(uriMock, "subtitle3")
+    )
+
+    override suspend fun getPreviewThumbnail(item: String, timestampInMs: Long) = null
+
+    override suspend fun getPreviewThumbnailsInfo(item: String) = MediaRepository.PreviewThumbnailsInfo(10, 500)
+
+    override suspend fun getChapters(item: String) = listOf(
+        Chapter(0, "chapter1", null),
+        Chapter(5000, "chapter2", null),
+        Chapter(1000, "chapter3", null),
+    )
+
+    override suspend fun getTimestampLink(item: String, timestampInSeconds: Long) = "test/link"
+
+    override fun getHttpDataSourceFactory(item: String): HttpDataSource.Factory {
+        val factory = DefaultHttpDataSource.Factory()
+        factory.setUserAgent("TestUserAgent")
+        return factory
+    }
+}
\ No newline at end of file

From f21d57df07085fba05725d96422e90796e5c943e Mon Sep 17 00:00:00 2001
From: Nicholas Sala <salanicholas.14@gmail.com>
Date: Mon, 18 Nov 2024 12:15:48 +0100
Subject: [PATCH 2/9] Improved CachingRepository tests by checking the cache
 mechanism

---
 .../repository/CachingRepositoryTest.kt       | 80 +++++++++++++++++--
 .../repository/MockMediaRepository.kt         | 20 -----
 2 files changed, 72 insertions(+), 28 deletions(-)

diff --git a/new-player/src/test/java/net/newpipe/newplayer/repository/CachingRepositoryTest.kt b/new-player/src/test/java/net/newpipe/newplayer/repository/CachingRepositoryTest.kt
index 0aea6727..5ea246ee 100644
--- a/new-player/src/test/java/net/newpipe/newplayer/repository/CachingRepositoryTest.kt
+++ b/new-player/src/test/java/net/newpipe/newplayer/repository/CachingRepositoryTest.kt
@@ -1,5 +1,8 @@
 package net.newpipe.newplayer.repository
 
+import io.mockk.clearMocks
+import io.mockk.coVerify
+import io.mockk.spyk
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
@@ -8,13 +11,13 @@ import kotlinx.coroutines.test.runTest
 import kotlinx.coroutines.test.setMain
 import org.junit.Test
 import org.junit.Assert.*
+import org.junit.Before
 import org.junit.BeforeClass
 
 @OptIn(ExperimentalCoroutinesApi::class)
 class CachingRepositoryTest {
-    val mockMediaRepository = MockMediaRepository()
-    val delayTestRepository = DelayTestRepository(mockMediaRepository, 100)
-    val repository = CachingRepository(delayTestRepository)
+    val mockMediaRepository = spyk(MockMediaRepository())
+    val repository = CachingRepository(mockMediaRepository)
 
     companion object {
         @JvmStatic
@@ -30,6 +33,11 @@ class CachingRepositoryTest {
         }
     }
 
+    @Before
+    fun resetSpy() {
+        clearMocks(mockMediaRepository)
+    }
+
     @Test
     fun getRepoInfo() {
         val repoInfo = repository.getRepoInfo()
@@ -39,10 +47,18 @@ class CachingRepositoryTest {
 
     @Test
     fun getMetaInfo() = runTest {
-        val metaInfo = repository.getMetaInfo("test")
+        val metaInfo = repository.getMetaInfo("item")
         assertEquals("Test title", metaInfo.title)
     }
 
+    @Test
+    fun getMetaInfo_callActualRepositoryOnceForSameItem() = runTest {
+        repository.getMetaInfo("item")
+        repository.getMetaInfo("item")
+        repository.getMetaInfo("item")
+        coVerify (exactly = 1) { mockMediaRepository.getMetaInfo("item") }
+    }
+
     @Test
     fun getStreams_returnsCorrectList() = runTest {
         val streams = repository.getStreams("item")
@@ -52,6 +68,14 @@ class CachingRepositoryTest {
         assertEquals("item3", streams.get(2).item)
     }
 
+    @Test
+    fun getStreams_callActualRepositoryOnceForSameItem() = runTest {
+        repository.getStreams("item")
+        repository.getStreams("item")
+        repository.getStreams("item")
+        coVerify (exactly = 1) { mockMediaRepository.getStreams("item") }
+    }
+
     @Test
     fun getSubtitles_returnsCorrectList() = runTest {
         val subtitles = repository.getSubtitles("item")
@@ -61,6 +85,14 @@ class CachingRepositoryTest {
         assertEquals("subtitle3", subtitles.get(2).identifier)
     }
 
+    @Test
+    fun getSubtitles_callActualRepositoryOnceForSameItem() = runTest {
+        repository.getSubtitles("item")
+        repository.getSubtitles("item")
+        repository.getSubtitles("item")
+        coVerify (exactly = 1) { mockMediaRepository.getSubtitles("item") }
+    }
+
     @Test
     fun getPreviewThumbnail() = runTest {
         val previewThumbnail = repository.getPreviewThumbnail("item", 1000)
@@ -68,10 +100,26 @@ class CachingRepositoryTest {
     }
 
     @Test
-    fun getPreviewThumbnailInfo() = runTest {
-        val previewThumbnailInfo = repository.getPreviewThumbnailsInfo("item")
-        assertEquals(10, previewThumbnailInfo.count)
-        assertEquals(500, previewThumbnailInfo.distanceInMS)
+    fun getPreviewThumbnail_callActualRepositoryOnceForSameItem() = runTest {
+        repository.getPreviewThumbnail("item", 1000)
+        repository.getPreviewThumbnail("item", 1000)
+        repository.getPreviewThumbnail("item", 1000)
+        coVerify (exactly = 1) { mockMediaRepository.getPreviewThumbnail("item", 1000) }
+    }
+
+    @Test
+    fun getPreviewThumbnailsInfo() = runTest {
+        val previewThumbnailsInfo = repository.getPreviewThumbnailsInfo("item")
+        assertEquals(10, previewThumbnailsInfo.count)
+        assertEquals(500, previewThumbnailsInfo.distanceInMS)
+    }
+
+    @Test
+    fun getPreviewThumbnailsInfo_callActualRepositoryOnceForSameItem() = runTest {
+        repository.getPreviewThumbnailsInfo("item")
+        repository.getPreviewThumbnailsInfo("item")
+        repository.getPreviewThumbnailsInfo("item")
+        coVerify (exactly = 1) { mockMediaRepository.getPreviewThumbnailsInfo("item") }
     }
 
     @Test
@@ -83,11 +131,27 @@ class CachingRepositoryTest {
         assertEquals("chapter3", chapters.get(2).chapterTitle)
     }
 
+    @Test
+    fun getChapters_callActualRepositoryOnceForSameItem() = runTest {
+        repository.getChapters("item")
+        repository.getChapters("item")
+        repository.getChapters("item")
+        coVerify (exactly = 1) { mockMediaRepository.getChapters("item") }
+    }
+
     @Test
     fun getTimestampLink() = runTest {
         assertEquals("test/link", repository.getTimestampLink("item", 0))
     }
 
+    @Test
+    fun getTimestampLink_callActualRepositoryOnceForSameItem() = runTest {
+        repository.getTimestampLink("item", 0)
+        repository.getTimestampLink("item", 0)
+        repository.getTimestampLink("item", 0)
+        coVerify (exactly = 1) { mockMediaRepository.getTimestampLink("item", 0) }
+    }
+
     @Test
     fun getHttpDataSourceFactory() {
         assertNotNull(repository.getHttpDataSourceFactory("item"))
diff --git a/new-player/src/test/java/net/newpipe/newplayer/repository/MockMediaRepository.kt b/new-player/src/test/java/net/newpipe/newplayer/repository/MockMediaRepository.kt
index 8ce9e4f6..22e273e6 100644
--- a/new-player/src/test/java/net/newpipe/newplayer/repository/MockMediaRepository.kt
+++ b/new-player/src/test/java/net/newpipe/newplayer/repository/MockMediaRepository.kt
@@ -1,23 +1,3 @@
-/* NewPlayer
- *
- * @author Christian Schabesberger
- *
- * Copyright (C) NewPipe e.V. 2024 <code(at)newpipe-ev.de>
- *
- * NewPlayer is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * NewPlayer is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with NewPlayer.  If not, see <http://www.gnu.org/licenses/>.
- */
-
 package net.newpipe.newplayer.repository
 
 import android.net.Uri

From 94e1015ac932815091b01b28cfa5fb2d57d9dfff Mon Sep 17 00:00:00 2001
From: Nicholas Sala <salanicholas.14@gmail.com>
Date: Tue, 19 Nov 2024 18:00:23 +0100
Subject: [PATCH 3/9] Created PrefetchingRepository tests

---
 .../repository/CachingRepositoryTest.kt       |  30 +++
 .../repository/PrefetchingRepositoryTest.kt   | 195 ++++++++++++++++++
 2 files changed, 225 insertions(+)
 create mode 100644 new-player/src/test/java/net/newpipe/newplayer/repository/PrefetchingRepositoryTest.kt

diff --git a/new-player/src/test/java/net/newpipe/newplayer/repository/CachingRepositoryTest.kt b/new-player/src/test/java/net/newpipe/newplayer/repository/CachingRepositoryTest.kt
index 5ea246ee..48dd3f93 100644
--- a/new-player/src/test/java/net/newpipe/newplayer/repository/CachingRepositoryTest.kt
+++ b/new-player/src/test/java/net/newpipe/newplayer/repository/CachingRepositoryTest.kt
@@ -156,4 +156,34 @@ class CachingRepositoryTest {
     fun getHttpDataSourceFactory() {
         assertNotNull(repository.getHttpDataSourceFactory("item"))
     }
+
+    // TODO
+//    @Test
+//    fun flush_flushTheCaches() = runTest {
+//        repository.getMetaInfo("item")
+//        repository.getStreams("item")
+//        repository.getSubtitles("item")
+//        repository.getPreviewThumbnail("item", 1000)
+//        repository.getPreviewThumbnailsInfo("item")
+//        repository.getChapters("item")
+//        repository.getTimestampLink("item", 0)
+//
+//        repository.flush()
+//
+//        repository.getMetaInfo("item")
+//        repository.getStreams("item")
+//        repository.getSubtitles("item")
+//        repository.getPreviewThumbnail("item", 1000)
+//        repository.getPreviewThumbnailsInfo("item")
+//        repository.getChapters("item")
+//        repository.getTimestampLink("item", 0)
+//
+//        coVerify (exactly = 2) { mockMediaRepository.getMetaInfo("item") }
+//        coVerify (exactly = 2) { mockMediaRepository.getStreams("item") }
+//        coVerify (exactly = 2) { mockMediaRepository.getSubtitles("item") }
+//        coVerify (exactly = 2) { mockMediaRepository.getPreviewThumbnail("item", 1000) }
+//        coVerify (exactly = 2) { mockMediaRepository.getPreviewThumbnailsInfo("item") }
+//        coVerify (exactly = 2) { mockMediaRepository.getChapters("item") }
+//        coVerify (exactly = 2) { mockMediaRepository.getTimestampLink("item", 0) }
+//    }
 }
\ No newline at end of file
diff --git a/new-player/src/test/java/net/newpipe/newplayer/repository/PrefetchingRepositoryTest.kt b/new-player/src/test/java/net/newpipe/newplayer/repository/PrefetchingRepositoryTest.kt
new file mode 100644
index 00000000..f9625ee1
--- /dev/null
+++ b/new-player/src/test/java/net/newpipe/newplayer/repository/PrefetchingRepositoryTest.kt
@@ -0,0 +1,195 @@
+package net.newpipe.newplayer.repository;
+
+import io.mockk.clearMocks
+import io.mockk.coVerify
+import io.mockk.spyk
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Test
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class PrefetchingRepositoryTest {
+    val mockMediaRepository = spyk(MockMediaRepository())
+    val cachingRepository = spyk(CachingRepository(mockMediaRepository))
+    val repository = PrefetchingRepository(cachingRepository)
+
+    companion object {
+        @JvmStatic
+        @BeforeClass
+        fun init() {
+            Dispatchers.setMain(StandardTestDispatcher())
+        }
+
+        @JvmStatic
+        @BeforeClass
+        fun reset() {
+            Dispatchers.resetMain()
+        }
+    }
+
+    @Before
+    fun clean() {
+        clearMocks(mockMediaRepository)
+        repository.reset()
+    }
+
+    @Test
+    fun getMetaInfo() = runTest {
+        val metaInfo = repository.getMetaInfo("item")
+        assertEquals("Test title", metaInfo.title)
+    }
+
+    @Test
+    fun getMetaInfo_prefetchAllItemData() = runTest {
+        repository.getMetaInfo("item")
+
+        coVerify (exactly = 2) { mockMediaRepository.getMetaInfo("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getStreams("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getSubtitles("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getPreviewThumbnailsInfo("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getChapters("item") }
+    }
+
+    @Test
+    fun getStreams_returnsCorrectList() = runTest {
+        val streams = repository.getStreams("item")
+        assertEquals(3, streams.size)
+        assertEquals("item1", streams.get(0).item)
+        assertEquals("item2", streams.get(1).item)
+        assertEquals("item3", streams.get(2).item)
+    }
+
+    @Test
+    fun getStreams_prefetchAllItemData() = runTest {
+        repository.getStreams("item")
+
+        coVerify (exactly = 1) { mockMediaRepository.getMetaInfo("item") }
+        coVerify (exactly = 2) { mockMediaRepository.getStreams("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getSubtitles("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getPreviewThumbnailsInfo("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getChapters("item") }
+    }
+
+    @Test
+    fun getSubtitles_returnsCorrectList() = runTest {
+        val subtitles = repository.getSubtitles("item")
+        assertEquals(3, subtitles.size)
+        assertEquals("subtitle1", subtitles.get(0).identifier)
+        assertEquals("subtitle2", subtitles.get(1).identifier)
+        assertEquals("subtitle3", subtitles.get(2).identifier)
+    }
+
+    @Test
+    fun getSubtitles_prefetchAllItemData() = runTest {
+        repository.getSubtitles("item")
+
+        coVerify (exactly = 1) { mockMediaRepository.getMetaInfo("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getStreams("item") }
+        coVerify (exactly = 2) { mockMediaRepository.getSubtitles("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getPreviewThumbnailsInfo("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getChapters("item") }
+    }
+
+    @Test
+    fun getPreviewThumbnail() = runTest {
+        val previewThumbnail = repository.getPreviewThumbnail("item", 1000)
+        assertNull(previewThumbnail)
+    }
+
+    @Test
+    fun getPreviewThumbnail_prefetchAllItemData() = runTest {
+        repository.getPreviewThumbnail("item", 1000)
+
+        coVerify (exactly = 1) { mockMediaRepository.getMetaInfo("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getStreams("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getSubtitles("item") }
+        coVerify (exactly = 2) { mockMediaRepository.getPreviewThumbnailsInfo("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getChapters("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getPreviewThumbnail("item",1000) }
+    }
+
+    @Test
+    fun getPreviewThumbnailsInfo() = runTest {
+        val previewThumbnailsInfo = repository.getPreviewThumbnailsInfo("item")
+        assertEquals(10, previewThumbnailsInfo.count)
+        assertEquals(500, previewThumbnailsInfo.distanceInMS)
+    }
+
+    @Test
+    fun getPreviewThumbnailsInfo_prefetchAllItemData() = runTest {
+        repository.getPreviewThumbnailsInfo("item")
+
+        coVerify (exactly = 1) { mockMediaRepository.getMetaInfo("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getStreams("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getSubtitles("item") }
+        coVerify (exactly = 2) { mockMediaRepository.getPreviewThumbnailsInfo("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getChapters("item") }
+    }
+
+    @Test
+    fun getChapters_returnsCorrectList() = runTest {
+        val chapters = repository.getChapters("item")
+        assertEquals(3, chapters.size)
+        assertEquals("chapter1", chapters.get(0).chapterTitle)
+        assertEquals("chapter2", chapters.get(1).chapterTitle)
+        assertEquals("chapter3", chapters.get(2).chapterTitle)
+    }
+
+    @Test
+    fun getChapters_prefetchAllItemData() = runTest {
+        repository.getChapters("item")
+
+        coVerify (exactly = 1) { mockMediaRepository.getMetaInfo("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getStreams("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getSubtitles("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getPreviewThumbnailsInfo("item") }
+        coVerify (exactly = 2) { mockMediaRepository.getChapters("item") }
+    }
+
+    @Test
+    fun prefetch() = runTest {
+        repository.prefetch("item")
+
+        coVerify (exactly = 1) { mockMediaRepository.getMetaInfo("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getStreams("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getSubtitles("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getPreviewThumbnailsInfo("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getChapters("item") }
+
+        //This request should not call the prefetch since data are already prefetched
+        repository.getMetaInfo("item")
+
+        //Check that the repository was called only one time for each type of data since prefetch was already done
+        // except for getMetaInfo which is used here to trigger the automatic prefetch
+        coVerify (exactly = 2) { mockMediaRepository.getMetaInfo("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getStreams("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getSubtitles("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getPreviewThumbnailsInfo("item") }
+        coVerify (exactly = 1) { mockMediaRepository.getChapters("item") }
+    }
+
+    @Test
+    fun reset_resetAlreadySeenItemsInfo() = runTest {
+        repository.getMetaInfo("item")
+        repository.reset()
+        repository.getMetaInfo("item")
+        repository.reset()
+        repository.getMetaInfo("item")
+        repository.reset()
+
+        coVerify (exactly = 6) { cachingRepository.getMetaInfo("item") }
+        coVerify (exactly = 3) { cachingRepository.getStreams("item") }
+        coVerify (exactly = 3) { cachingRepository.getSubtitles("item") }
+        coVerify (exactly = 3) { cachingRepository.getPreviewThumbnailsInfo("item") }
+        coVerify (exactly = 3) { cachingRepository.getChapters("item") }
+    }
+
+}

From baff73e2e695d29aeae39a045b708345fbb269b3 Mon Sep 17 00:00:00 2001
From: Nicholas Sala <salanicholas.14@gmail.com>
Date: Wed, 20 Nov 2024 07:54:42 +0100
Subject: [PATCH 4/9] Fix prefetch function in PrefetchingRepository

---
 .../repository/PrefetchingRepository.kt       |  5 ++++-
 .../repository/PrefetchingRepositoryTest.kt   | 22 +++++++++++--------
 2 files changed, 17 insertions(+), 10 deletions(-)

diff --git a/new-player/src/main/java/net/newpipe/newplayer/repository/PrefetchingRepository.kt b/new-player/src/main/java/net/newpipe/newplayer/repository/PrefetchingRepository.kt
index bbaa3af2..eb2e1fde 100644
--- a/new-player/src/main/java/net/newpipe/newplayer/repository/PrefetchingRepository.kt
+++ b/new-player/src/main/java/net/newpipe/newplayer/repository/PrefetchingRepository.kt
@@ -118,7 +118,10 @@ class PrefetchingRepository(
      * Manually trigger a prefetch of [item] without performing an actual request.
      */
     suspend fun prefetch(item:String) {
-        requestAll(item)
+        if(!hasBeenSeenBefore.contains(item)) {
+            hasBeenSeenBefore.add(item)
+            requestAll(item)
+        }
     }
 
     /**
diff --git a/new-player/src/test/java/net/newpipe/newplayer/repository/PrefetchingRepositoryTest.kt b/new-player/src/test/java/net/newpipe/newplayer/repository/PrefetchingRepositoryTest.kt
index f9625ee1..c60d8543 100644
--- a/new-player/src/test/java/net/newpipe/newplayer/repository/PrefetchingRepositoryTest.kt
+++ b/new-player/src/test/java/net/newpipe/newplayer/repository/PrefetchingRepositoryTest.kt
@@ -38,6 +38,7 @@ class PrefetchingRepositoryTest {
     @Before
     fun clean() {
         clearMocks(mockMediaRepository)
+        clearMocks(cachingRepository)
         repository.reset()
     }
 
@@ -155,25 +156,28 @@ class PrefetchingRepositoryTest {
     }
 
     @Test
-    fun prefetch() = runTest {
+    fun prefetch_prefetchInformationOfNewItem() = runTest {
         repository.prefetch("item")
+        repository.getTimestampLink("item", 1000)
 
         coVerify (exactly = 1) { mockMediaRepository.getMetaInfo("item") }
         coVerify (exactly = 1) { mockMediaRepository.getStreams("item") }
         coVerify (exactly = 1) { mockMediaRepository.getSubtitles("item") }
         coVerify (exactly = 1) { mockMediaRepository.getPreviewThumbnailsInfo("item") }
         coVerify (exactly = 1) { mockMediaRepository.getChapters("item") }
+    }
 
-        //This request should not call the prefetch since data are already prefetched
+    @Test
+    fun prefetch_notPrefetchInformationOfAlreadySeenItem() = runTest {
         repository.getMetaInfo("item")
+        clearMocks(cachingRepository)
+        repository.prefetch("item")
 
-        //Check that the repository was called only one time for each type of data since prefetch was already done
-        // except for getMetaInfo which is used here to trigger the automatic prefetch
-        coVerify (exactly = 2) { mockMediaRepository.getMetaInfo("item") }
-        coVerify (exactly = 1) { mockMediaRepository.getStreams("item") }
-        coVerify (exactly = 1) { mockMediaRepository.getSubtitles("item") }
-        coVerify (exactly = 1) { mockMediaRepository.getPreviewThumbnailsInfo("item") }
-        coVerify (exactly = 1) { mockMediaRepository.getChapters("item") }
+        coVerify (exactly = 0) { cachingRepository.getMetaInfo("item") }
+        coVerify (exactly = 0) { cachingRepository.getStreams("item") }
+        coVerify (exactly = 0) { cachingRepository.getSubtitles("item") }
+        coVerify (exactly = 0) { cachingRepository.getPreviewThumbnailsInfo("item") }
+        coVerify (exactly = 0) { cachingRepository.getChapters("item") }
     }
 
     @Test

From e3bb450c96d8e4ff93b57d224e2e93e96691607e Mon Sep 17 00:00:00 2001
From: Nicholas Sala <salanicholas.14@gmail.com>
Date: Wed, 20 Nov 2024 14:14:09 +0100
Subject: [PATCH 5/9] PrefetchingRepository tests completed

---
 .../newpipe/newplayer/repository/PrefetchingRepositoryTest.kt  | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/new-player/src/test/java/net/newpipe/newplayer/repository/PrefetchingRepositoryTest.kt b/new-player/src/test/java/net/newpipe/newplayer/repository/PrefetchingRepositoryTest.kt
index c60d8543..dbb81f7d 100644
--- a/new-player/src/test/java/net/newpipe/newplayer/repository/PrefetchingRepositoryTest.kt
+++ b/new-player/src/test/java/net/newpipe/newplayer/repository/PrefetchingRepositoryTest.kt
@@ -158,6 +158,7 @@ class PrefetchingRepositoryTest {
     @Test
     fun prefetch_prefetchInformationOfNewItem() = runTest {
         repository.prefetch("item")
+        //TODO: understand why without this call the spy is not updated and the test fails
         repository.getTimestampLink("item", 1000)
 
         coVerify (exactly = 1) { mockMediaRepository.getMetaInfo("item") }
@@ -188,6 +189,8 @@ class PrefetchingRepositoryTest {
         repository.reset()
         repository.getMetaInfo("item")
         repository.reset()
+        //TODO: understand why without this call the spy is not updated and the test fails
+        repository.getTimestampLink("item", 1000)
 
         coVerify (exactly = 6) { cachingRepository.getMetaInfo("item") }
         coVerify (exactly = 3) { cachingRepository.getStreams("item") }

From c8c8b41c8aa26c891db31e93bab8bc6f89414524 Mon Sep 17 00:00:00 2001
From: Nicholas Sala <salanicholas.14@gmail.com>
Date: Fri, 22 Nov 2024 10:58:30 +0100
Subject: [PATCH 6/9] Prepared test class for NewPlayerImpl

---
 .../newpipe/newplayer/NewPlayerImpltest.kt    | 50 +++++++++++++++++++
 1 file changed, 50 insertions(+)
 create mode 100644 new-player/src/test/java/net/newpipe/newplayer/NewPlayerImpltest.kt

diff --git a/new-player/src/test/java/net/newpipe/newplayer/NewPlayerImpltest.kt b/new-player/src/test/java/net/newpipe/newplayer/NewPlayerImpltest.kt
new file mode 100644
index 00000000..53062fe1
--- /dev/null
+++ b/new-player/src/test/java/net/newpipe/newplayer/NewPlayerImpltest.kt
@@ -0,0 +1,50 @@
+package net.newpipe.newplayer
+
+import android.app.Activity
+import android.app.Application
+import io.mockk.mockk
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import net.newpipe.newplayer.repository.DelayTestRepository
+import net.newpipe.newplayer.repository.MockMediaRepository
+import org.junit.BeforeClass
+import org.junit.Test
+
+//TODO
+//  * onPlayBackError
+//  * prepare
+//  * play
+//  * pause
+//  * addToPlaylist
+//  * movePlaylistItem
+//  * removePlaylistItem
+//  * playStream
+//  * selectChapter
+//  * release
+//  * getItemFromMediaItem
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class NewPlayerImpltest {
+    val app = mockk<Application>(relaxed = true)
+    val playerActivityClass = Activity::class.java
+    val repository = DelayTestRepository(MockMediaRepository(), 100)
+    val player = NewPlayerImpl(app, playerActivityClass, repository)
+
+    companion object {
+        @JvmStatic
+        @BeforeClass
+        fun init() {
+            Dispatchers.setMain(StandardTestDispatcher())
+        }
+
+        @JvmStatic
+        @BeforeClass
+        fun reset() {
+            Dispatchers.resetMain()
+        }
+    }
+
+}
\ No newline at end of file

From e3091c47fe8dc9083cb11d70c479fbe840a8e437 Mon Sep 17 00:00:00 2001
From: Nicholas Sala <salanicholas.14@gmail.com>
Date: Wed, 4 Dec 2024 14:36:39 +0100
Subject: [PATCH 7/9] NewPlayerImpl tests: tested prepare function

---
 .../newpipe/newplayer/NewPlayerImpltest.kt    | 108 +++++++++++++++++-
 1 file changed, 105 insertions(+), 3 deletions(-)

diff --git a/new-player/src/test/java/net/newpipe/newplayer/NewPlayerImpltest.kt b/new-player/src/test/java/net/newpipe/newplayer/NewPlayerImpltest.kt
index 53062fe1..8422c906 100644
--- a/new-player/src/test/java/net/newpipe/newplayer/NewPlayerImpltest.kt
+++ b/new-player/src/test/java/net/newpipe/newplayer/NewPlayerImpltest.kt
@@ -2,7 +2,25 @@ package net.newpipe.newplayer
 
 import android.app.Activity
 import android.app.Application
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.content.pm.ServiceInfo
+import android.os.Looper
+import android.text.TextUtils
+import android.util.Log
+import androidx.media3.exoplayer.ExoPlayer
+import androidx.media3.session.MediaController
+import androidx.media3.session.SessionToken
+import com.google.common.util.concurrent.Futures
+import io.mockk.clearMocks
+import io.mockk.every
 import io.mockk.mockk
+import io.mockk.mockkConstructor
+import io.mockk.mockkStatic
+import io.mockk.spyk
+import io.mockk.verify
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
@@ -10,12 +28,12 @@ import kotlinx.coroutines.test.resetMain
 import kotlinx.coroutines.test.setMain
 import net.newpipe.newplayer.repository.DelayTestRepository
 import net.newpipe.newplayer.repository.MockMediaRepository
+import org.junit.Before
 import org.junit.BeforeClass
 import org.junit.Test
 
 //TODO
 //  * onPlayBackError
-//  * prepare
 //  * play
 //  * pause
 //  * addToPlaylist
@@ -28,10 +46,46 @@ import org.junit.Test
 
 @OptIn(ExperimentalCoroutinesApi::class)
 class NewPlayerImpltest {
-    val app = mockk<Application>(relaxed = true)
+    val mockExoPlayer = mockk<ExoPlayer>(relaxed = true)
+    val mockApp = mockk<Application>(relaxed = true)
     val playerActivityClass = Activity::class.java
     val repository = DelayTestRepository(MockMediaRepository(), 100)
-    val player = NewPlayerImpl(app, playerActivityClass, repository)
+    var player = NewPlayerImpl(mockApp, playerActivityClass, repository)
+
+    init {
+        mockkStatic(Looper::class)
+        mockkStatic(TextUtils::class)
+        mockkStatic(Log::class)
+        mockkStatic(ExoPlayer::class)
+        mockkStatic(ExoPlayer.Builder::class)
+        mockkStatic(TextUtils::class)
+        mockkConstructor(ComponentName::class)
+        mockkConstructor(Intent::class)
+        mockkConstructor(SessionToken::class)
+        mockkConstructor(ExoPlayer.Builder::class)
+        mockkConstructor(MediaController.Builder::class)
+
+        val mockLooper = mockk<Looper>(relaxed = true)
+        val mockMediaController = mockk<MediaController>(relaxed = true)
+        val mockPackageManager = mockk<PackageManager>(relaxed = true)
+        val mockResolveInfo = mockk<ResolveInfo>(relaxed = true)
+        val mockServiceInfo = mockk<ServiceInfo>(relaxed = true)
+        mockResolveInfo.serviceInfo = mockServiceInfo
+        mockServiceInfo.name = "ComponentNameTest"
+
+        every { Looper.myLooper() } returns mockLooper
+        every { TextUtils.isEmpty(any()) } returns true
+        every { Log.i(any(), any()) } returns 1
+        every { anyConstructed<ComponentName>().packageName } returns "net.newpipe.newplayer.test"
+        every { anyConstructed<ComponentName>().className } returns "ComponentNameTest"
+        every { anyConstructed<ComponentName>().hashCode() } returns 1
+        every { anyConstructed<Intent>().setPackage(any()) } returns mockk<Intent>(relaxed = true)
+        every { anyConstructed<ExoPlayer.Builder>().build() } returns mockExoPlayer
+        every { TextUtils.equals(any(), any()) } returns true
+        every { anyConstructed<MediaController.Builder>().buildAsync() } returns Futures.immediateFuture(mockMediaController)
+        every { mockApp.packageManager } returns mockPackageManager
+        every { mockPackageManager.queryIntentServices(any(), any<Int>()) } returns listOf(mockResolveInfo)
+    }
 
     companion object {
         @JvmStatic
@@ -47,4 +101,52 @@ class NewPlayerImpltest {
         }
     }
 
+    @Before
+    fun resetSpy() {
+        clearMocks(mockExoPlayer)
+        player = NewPlayerImpl(mockApp, playerActivityClass, repository)
+    }
+
+    @Test
+    fun prepare_setupNewExoplayer() {
+        player.prepare()
+        verify (exactly = 1) { anyConstructed<ExoPlayer.Builder>().build() }
+    }
+
+    @Test
+    fun prepare_notSetupNewExoPlayerWhenAlreadySetUp() {
+        // Setup new ExoPlayer
+        player.prepare()
+
+        // Call prepare with an already set up ExoPlayer
+        player.prepare()
+
+        //ExoPlayer should be built only one time
+        verify (exactly = 1) { anyConstructed<ExoPlayer.Builder>().build() }
+    }
+
+    @Test
+    fun prepare_prepareExoPlayer() {
+        player.prepare()
+        verify (exactly = 1) { mockExoPlayer.prepare() }
+    }
+
+    @Test
+    fun prepare_setUpMediaController() {
+        player.prepare()
+        verify (exactly = 1) { anyConstructed<MediaController.Builder>().buildAsync() }
+    }
+
+    @Test
+    fun prepare_notSetUpMediaController() {
+        // Setup media controller
+        player.prepare()
+
+        // Call prepare with an already set up media controller
+        player.prepare()
+
+        //Media controller should be built only one time
+        verify (exactly = 1) { anyConstructed<MediaController.Builder>().buildAsync() }
+    }
+
 }
\ No newline at end of file

From ab39f5dede2066d44b588e0185799be18c6e06da Mon Sep 17 00:00:00 2001
From: Nicholas Sala <salanicholas.14@gmail.com>
Date: Wed, 4 Dec 2024 15:51:57 +0100
Subject: [PATCH 8/9] NewPlayerImpl tests: tested play and pause functions

---
 .../newpipe/newplayer/NewPlayerImpltest.kt    | 43 +++++++++++++------
 1 file changed, 30 insertions(+), 13 deletions(-)

diff --git a/new-player/src/test/java/net/newpipe/newplayer/NewPlayerImpltest.kt b/new-player/src/test/java/net/newpipe/newplayer/NewPlayerImpltest.kt
index 8422c906..3bb5e4ce 100644
--- a/new-player/src/test/java/net/newpipe/newplayer/NewPlayerImpltest.kt
+++ b/new-player/src/test/java/net/newpipe/newplayer/NewPlayerImpltest.kt
@@ -19,7 +19,6 @@ import io.mockk.every
 import io.mockk.mockk
 import io.mockk.mockkConstructor
 import io.mockk.mockkStatic
-import io.mockk.spyk
 import io.mockk.verify
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -32,18 +31,6 @@ import org.junit.Before
 import org.junit.BeforeClass
 import org.junit.Test
 
-//TODO
-//  * onPlayBackError
-//  * play
-//  * pause
-//  * addToPlaylist
-//  * movePlaylistItem
-//  * removePlaylistItem
-//  * playStream
-//  * selectChapter
-//  * release
-//  * getItemFromMediaItem
-
 @OptIn(ExperimentalCoroutinesApi::class)
 class NewPlayerImpltest {
     val mockExoPlayer = mockk<ExoPlayer>(relaxed = true)
@@ -107,6 +94,13 @@ class NewPlayerImpltest {
         player = NewPlayerImpl(mockApp, playerActivityClass, repository)
     }
 
+    @Test
+    fun onPlayBackError_pauseExoPlayer() {
+        player.prepare()
+        player.onPlayBackError(Exception("test"))
+        verify (exactly = 1) { mockExoPlayer.pause() }
+    }
+
     @Test
     fun prepare_setupNewExoplayer() {
         player.prepare()
@@ -149,4 +143,27 @@ class NewPlayerImpltest {
         verify (exactly = 1) { anyConstructed<MediaController.Builder>().buildAsync() }
     }
 
+    @Test
+    fun play_playIfCurrentMediaItemIsNotNull() {
+        player.prepare()
+        player.play()
+        verify (exactly = 1) { mockExoPlayer.play() }
+    }
+
+    @Test
+    fun play_notPlayIfCurrentMediaItemIsNull() {
+        player.prepare()
+        every { mockExoPlayer.currentMediaItem } returns null
+        player.play()
+        verify (exactly = 0) { mockExoPlayer.play() }
+    }
+
+    @Test
+    fun pause() {
+        player.prepare()
+        player.pause()
+        verify (exactly = 1) { mockExoPlayer.pause() }
+    }
+
+    
 }
\ No newline at end of file

From 99eac56dfc95b50fb3e1afa92085838a5da4bdf7 Mon Sep 17 00:00:00 2001
From: Nicholas Sala <salanicholas.14@gmail.com>
Date: Sun, 8 Dec 2024 17:22:17 +0100
Subject: [PATCH 9/9] NewPlayerImpl tests: tested addToPlaylist,
 movePlaylistItem, removePlaylistItem, selectChapter and release functions

---
 .../newpipe/newplayer/NewPlayerImpltest.kt    | 79 ++++++++++++++++++-
 .../repository/CachingRepositoryTest.kt       | 59 +++++++-------
 2 files changed, 105 insertions(+), 33 deletions(-)

diff --git a/new-player/src/test/java/net/newpipe/newplayer/NewPlayerImpltest.kt b/new-player/src/test/java/net/newpipe/newplayer/NewPlayerImpltest.kt
index 3bb5e4ce..7263dfa3 100644
--- a/new-player/src/test/java/net/newpipe/newplayer/NewPlayerImpltest.kt
+++ b/new-player/src/test/java/net/newpipe/newplayer/NewPlayerImpltest.kt
@@ -10,6 +10,7 @@ import android.content.pm.ServiceInfo
 import android.os.Looper
 import android.text.TextUtils
 import android.util.Log
+import androidx.media3.common.MediaItem
 import androidx.media3.exoplayer.ExoPlayer
 import androidx.media3.session.MediaController
 import androidx.media3.session.SessionToken
@@ -22,18 +23,20 @@ import io.mockk.mockkStatic
 import io.mockk.verify
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.resetMain
 import kotlinx.coroutines.test.setMain
 import net.newpipe.newplayer.repository.DelayTestRepository
 import net.newpipe.newplayer.repository.MockMediaRepository
 import org.junit.Before
 import org.junit.BeforeClass
+import org.junit.Ignore
 import org.junit.Test
 
 @OptIn(ExperimentalCoroutinesApi::class)
 class NewPlayerImpltest {
     val mockExoPlayer = mockk<ExoPlayer>(relaxed = true)
+    val mockMediaController = mockk<MediaController>(relaxed = true)
     val mockApp = mockk<Application>(relaxed = true)
     val playerActivityClass = Activity::class.java
     val repository = DelayTestRepository(MockMediaRepository(), 100)
@@ -53,7 +56,6 @@ class NewPlayerImpltest {
         mockkConstructor(MediaController.Builder::class)
 
         val mockLooper = mockk<Looper>(relaxed = true)
-        val mockMediaController = mockk<MediaController>(relaxed = true)
         val mockPackageManager = mockk<PackageManager>(relaxed = true)
         val mockResolveInfo = mockk<ResolveInfo>(relaxed = true)
         val mockServiceInfo = mockk<ServiceInfo>(relaxed = true)
@@ -78,7 +80,7 @@ class NewPlayerImpltest {
         @JvmStatic
         @BeforeClass
         fun init() {
-            Dispatchers.setMain(StandardTestDispatcher())
+            Dispatchers.setMain(UnconfinedTestDispatcher())
         }
 
         @JvmStatic
@@ -165,5 +167,74 @@ class NewPlayerImpltest {
         verify (exactly = 1) { mockExoPlayer.pause() }
     }
 
-    
+    @Test
+    fun addToPlaylist_prepareExoPlayerIfNotPrepared() {
+        player.addToPlaylist("item")
+        verify (exactly = 1) { mockExoPlayer.prepare() }
+    }
+
+    @Test
+    fun addToPlaylist_notPrepareExoPlayerIfAlreadyPrepared() {
+        player.prepare()
+        clearMocks(mockExoPlayer)
+        player.addToPlaylist("item")
+        verify (exactly = 0) { mockExoPlayer.prepare() }
+    }
+
+    @Ignore("Find a way to test code inside launchJobAndCollectError")
+    @Test
+    fun addToPlaylist_addMediaSource() {
+        player.addToPlaylist("item")
+//        try {
+//            Dispatchers.Main.job.join()
+//        } catch (e : Exception) { }
+//        coVerify (exactly = 2) { mockExoPlayer.addMediaSource(any()) }
+    }
+
+    @Test
+    fun movePlaylistItem() {
+        player.prepare()
+        player.movePlaylistItem(0, 1)
+        verify (exactly = 1){ mockExoPlayer.moveMediaItem(0, 1) }
+    }
+
+    @Test
+    fun removePlaylistItem_removeItem() {
+        player.prepare()
+        val mediaItem = MediaItem.Builder().setMediaId("123").build()
+        every { mockExoPlayer.mediaItemCount } returns 1
+        every { mockExoPlayer.getMediaItemAt(any()) } returns mediaItem
+        player.removePlaylistItem(123)
+        verify (exactly = 1) { mockExoPlayer.removeMediaItem(0) }
+    }
+
+    @Test
+    fun removePlaylistItem_notRemoveItem() {
+        player.prepare()
+        val mediaItem = MediaItem.Builder().setMediaId("123").build()
+        every { mockExoPlayer.mediaItemCount } returns 1
+        every { mockExoPlayer.getMediaItemAt(any()) } returns mediaItem
+        player.removePlaylistItem(124)
+        verify (exactly = 0) { mockExoPlayer.removeMediaItem(0) }
+    }
+
+    @Ignore("Mock currentChapters.value and test the selection of a chapter")
+    @Test
+    fun selectChapter() {
+        player.selectChapter(0)
+    }
+
+    @Test(expected = IndexOutOfBoundsException::class)
+    fun selectChapter_throwsException() {
+        player.selectChapter(3)
+    }
+
+    @Test
+    fun release() {
+        player.prepare()
+        player.release()
+        verify (exactly = 1) { mockMediaController.release() }
+        verify (exactly = 1) { mockExoPlayer.release() }
+    }
+
 }
\ No newline at end of file
diff --git a/new-player/src/test/java/net/newpipe/newplayer/repository/CachingRepositoryTest.kt b/new-player/src/test/java/net/newpipe/newplayer/repository/CachingRepositoryTest.kt
index 48dd3f93..bec4e4d1 100644
--- a/new-player/src/test/java/net/newpipe/newplayer/repository/CachingRepositoryTest.kt
+++ b/new-player/src/test/java/net/newpipe/newplayer/repository/CachingRepositoryTest.kt
@@ -13,6 +13,7 @@ import org.junit.Test
 import org.junit.Assert.*
 import org.junit.Before
 import org.junit.BeforeClass
+import org.junit.Ignore
 
 @OptIn(ExperimentalCoroutinesApi::class)
 class CachingRepositoryTest {
@@ -157,33 +158,33 @@ class CachingRepositoryTest {
         assertNotNull(repository.getHttpDataSourceFactory("item"))
     }
 
-    // TODO
-//    @Test
-//    fun flush_flushTheCaches() = runTest {
-//        repository.getMetaInfo("item")
-//        repository.getStreams("item")
-//        repository.getSubtitles("item")
-//        repository.getPreviewThumbnail("item", 1000)
-//        repository.getPreviewThumbnailsInfo("item")
-//        repository.getChapters("item")
-//        repository.getTimestampLink("item", 0)
-//
-//        repository.flush()
-//
-//        repository.getMetaInfo("item")
-//        repository.getStreams("item")
-//        repository.getSubtitles("item")
-//        repository.getPreviewThumbnail("item", 1000)
-//        repository.getPreviewThumbnailsInfo("item")
-//        repository.getChapters("item")
-//        repository.getTimestampLink("item", 0)
-//
-//        coVerify (exactly = 2) { mockMediaRepository.getMetaInfo("item") }
-//        coVerify (exactly = 2) { mockMediaRepository.getStreams("item") }
-//        coVerify (exactly = 2) { mockMediaRepository.getSubtitles("item") }
-//        coVerify (exactly = 2) { mockMediaRepository.getPreviewThumbnail("item", 1000) }
-//        coVerify (exactly = 2) { mockMediaRepository.getPreviewThumbnailsInfo("item") }
-//        coVerify (exactly = 2) { mockMediaRepository.getChapters("item") }
-//        coVerify (exactly = 2) { mockMediaRepository.getTimestampLink("item", 0) }
-//    }
+    @Ignore("Test flush if the job is cancelled using cacheRepoScope and the test is interrupted")
+    @Test
+    fun flush_flushTheCaches() = runTest {
+        repository.getMetaInfo("item")
+        repository.getStreams("item")
+        repository.getSubtitles("item")
+        repository.getPreviewThumbnail("item", 1000)
+        repository.getPreviewThumbnailsInfo("item")
+        repository.getChapters("item")
+        repository.getTimestampLink("item", 0)
+
+        repository.flush()
+
+        repository.getMetaInfo("item")
+        repository.getStreams("item")
+        repository.getSubtitles("item")
+        repository.getPreviewThumbnail("item", 1000)
+        repository.getPreviewThumbnailsInfo("item")
+        repository.getChapters("item")
+        repository.getTimestampLink("item", 0)
+
+        coVerify (exactly = 2) { mockMediaRepository.getMetaInfo("item") }
+        coVerify (exactly = 2) { mockMediaRepository.getStreams("item") }
+        coVerify (exactly = 2) { mockMediaRepository.getSubtitles("item") }
+        coVerify (exactly = 2) { mockMediaRepository.getPreviewThumbnail("item", 1000) }
+        coVerify (exactly = 2) { mockMediaRepository.getPreviewThumbnailsInfo("item") }
+        coVerify (exactly = 2) { mockMediaRepository.getChapters("item") }
+        coVerify (exactly = 2) { mockMediaRepository.getTimestampLink("item", 0) }
+    }
 }
\ No newline at end of file