diff --git a/app/build.gradle b/app/build.gradle index b69d15e6..d25c5014 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -31,8 +31,8 @@ android { testApplicationId "ac.mdiq.podcini.tests" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - versionCode 3020284 - versionName "6.12.6" + versionCode 3020285 + versionName "6.12.7" applicationId "ac.mdiq.podcini.R" def commit = "" @@ -171,7 +171,7 @@ android { dependencies { implementation libs.androidx.material3.android - implementation libs.androidx.material3 +// implementation libs.androidx.material3 // implementation libs.androidx.ui.viewbinding implementation libs.androidx.fragment.compose // implementation libs.androidx.material.icons.extended @@ -182,7 +182,7 @@ dependencies { def composeBom = libs.androidx.compose.bom implementation composeBom - androidTestImplementation composeBom +// androidTestImplementation composeBom // implementation libs.androidx.material implementation libs.androidx.ui.tooling.preview debugImplementation libs.androidx.ui.tooling @@ -257,24 +257,24 @@ dependencies { compileOnly libs.wearable // this one can not be updated? TODO: need to get an alternative - androidTestImplementation libs.nanohttpd - - androidTestImplementation libs.androidx.espresso.core - androidTestImplementation libs.androidx.espresso.contrib - androidTestImplementation libs.androidx.espresso.intents - androidTestImplementation libs.androidx.runner - androidTestImplementation libs.androidx.rules - androidTestImplementation libs.androidx.junit - androidTestImplementation libs.awaitility +// androidTestImplementation libs.nanohttpd +// +// androidTestImplementation libs.androidx.espresso.core +// androidTestImplementation libs.androidx.espresso.contrib +// androidTestImplementation libs.androidx.espresso.intents +// androidTestImplementation libs.androidx.runner +// androidTestImplementation libs.androidx.rules +// androidTestImplementation libs.androidx.junit +// androidTestImplementation libs.awaitility // Non-free dependencies: - testImplementation libs.androidx.core - testImplementation libs.awaitility - testImplementation libs.junit - testImplementation libs.mockito.inline - testImplementation libs.robolectric - testImplementation libs.javax.inject +// testImplementation libs.androidx.core +// testImplementation libs.awaitility +// testImplementation libs.junit +// testImplementation libs.mockito.inline +// testImplementation libs.robolectric +// testImplementation libs.javax.inject playImplementation libs.play.services.base freeImplementation libs.conscrypt.android diff --git a/app/src/androidTest/assets/30sec.mp3 b/app/src/androidTest/assets/30sec.mp3 deleted file mode 100644 index 48e39843..00000000 Binary files a/app/src/androidTest/assets/30sec.mp3 and /dev/null differ diff --git a/app/src/androidTest/assets/3sec.mp3 b/app/src/androidTest/assets/3sec.mp3 deleted file mode 100644 index 8ae450d0..00000000 Binary files a/app/src/androidTest/assets/3sec.mp3 and /dev/null differ diff --git a/app/src/androidTest/kotlin/ac/test/podcini/EspressoTestUtils.kt b/app/src/androidTest/kotlin/ac/test/podcini/EspressoTestUtils.kt deleted file mode 100644 index d2c27f35..00000000 --- a/app/src/androidTest/kotlin/ac/test/podcini/EspressoTestUtils.kt +++ /dev/null @@ -1,210 +0,0 @@ -package de.test.podcini - -import ac.mdiq.podcini.R -import ac.mdiq.podcini.playback.service.PlaybackService -import ac.mdiq.podcini.preferences.UserPreferences -import ac.mdiq.podcini.ui.activity.MainActivity -import ac.mdiq.podcini.ui.dialog.RatingDialog -import ac.mdiq.podcini.ui.dialog.RatingDialog.saveRated -import ac.mdiq.podcini.ui.fragment.NavDrawerFragment -import android.content.Context -import android.content.Intent -import android.view.View -import androidx.annotation.IdRes -import androidx.annotation.StringRes -import androidx.preference.PreferenceManager -import androidx.recyclerview.widget.RecyclerView -import androidx.test.espresso.* -import androidx.test.espresso.action.ViewActions -import androidx.test.espresso.assertion.ViewAssertions -import androidx.test.espresso.contrib.DrawerActions -import androidx.test.espresso.contrib.RecyclerViewActions -import androidx.test.espresso.matcher.ViewMatchers -import androidx.test.espresso.util.HumanReadables -import androidx.test.espresso.util.TreeIterables -import androidx.test.platform.app.InstrumentationRegistry -import junit.framework.AssertionFailedError -import org.awaitility.Awaitility -import org.awaitility.core.ConditionTimeoutException -import org.hamcrest.Matcher -import org.hamcrest.Matchers -import java.io.File -import java.util.concurrent.TimeUnit -import java.util.concurrent.TimeoutException - -object EspressoTestUtils { - /** - * Perform action of waiting for a specific view id. - * https://stackoverflow.com/a/49814995/ - * @param viewMatcher The view to wait for. - * @param millis The timeout of until when to wait for. - */ - fun waitForView(viewMatcher: Matcher, millis: Long): ViewAction { - return object : ViewAction { - override fun getConstraints(): Matcher { - return ViewMatchers.isRoot() - } - - override fun getDescription(): String { - return "wait for a specific view for $millis millis." - } - - override fun perform(uiController: UiController, view: View) { - uiController.loopMainThreadUntilIdle() - val startTime = System.currentTimeMillis() - val endTime = startTime + millis - - do { - for (child in TreeIterables.breadthFirstViewTraversal(view)) { - // found view with required ID - if (viewMatcher.matches(child)) { - return - } - } - - uiController.loopMainThreadForAtLeast(50) - } while (System.currentTimeMillis() < endTime) - - // timeout happens - throw PerformException.Builder() - .withActionDescription(this.description) - .withViewDescription(HumanReadables.describe(view)) - .withCause(TimeoutException()) - .build() - } - } - } - - /** - * Wait until a certain view becomes visible, but at the longest until the timeout. - * Unlike [.waitForView] it doesn't stick to the initial root view. - * - * @param viewMatcher The view to wait for. - * @param timeoutMillis Maximum waiting period in milliseconds. - * @throws Exception Throws an Exception in case of a timeout. - */ - @Throws(Exception::class) - fun waitForViewGlobally(viewMatcher: Matcher, timeoutMillis: Long) { - val startTime = System.currentTimeMillis() - val endTime = startTime + timeoutMillis - - do { - try { - Espresso.onView(viewMatcher).check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - // no Exception thrown -> check successful - return - } catch (ignore: NoMatchingViewException) { - // check was not successful "not found" -> continue waiting - } catch (ignore: AssertionFailedError) { - } - Thread.sleep(50) - } while (System.currentTimeMillis() < endTime) - - throw Exception("Timeout after $timeoutMillis ms") - } - - /** - * Perform action of waiting for a specific view id. - * https://stackoverflow.com/a/30338665/ - * @param id The id of the child to click. - */ - fun clickChildViewWithId(@IdRes id: Int): ViewAction { - return object : ViewAction { - override fun getConstraints(): Matcher { - return ViewMatchers.isRoot() - } - - override fun getDescription(): String { - return "Click on a child view with specified id." - } - - override fun perform(uiController: UiController, view: View) { - val v = view.findViewById(id) - v.performClick() - } - } - } - - /** - * Clear all app databases. - */ - fun clearPreferences() { - val root = InstrumentationRegistry.getInstrumentation().targetContext.filesDir.parentFile - val sharedPreferencesFileNames = File(root, "shared_prefs").list() - for (fileName in sharedPreferencesFileNames) { - println("Cleared database: $fileName") - InstrumentationRegistry.getInstrumentation().targetContext.getSharedPreferences( - fileName.replace(".xml", ""), Context.MODE_PRIVATE).edit().clear().commit() - } - - InstrumentationRegistry.getInstrumentation().targetContext - .getSharedPreferences(MainActivity.PREF_NAME, Context.MODE_PRIVATE) - .edit() - .putBoolean(MainActivity.Extras.prefMainActivityIsFirstLaunch.name, false) - .commit() - - PreferenceManager.getDefaultSharedPreferences(InstrumentationRegistry.getInstrumentation().targetContext) - .edit() - .putString(UserPreferences.Prefs.prefAutoUpdateIntervall.name, "0") - .commit() - - RatingDialog.init(InstrumentationRegistry.getInstrumentation().targetContext) - saveRated() - } - - fun setLaunchScreen(tag: String?) { - InstrumentationRegistry.getInstrumentation().targetContext - .getSharedPreferences(NavDrawerFragment.PREF_NAME, Context.MODE_PRIVATE) - .edit() - .putString(NavDrawerFragment.PREF_LAST_FRAGMENT_TAG, tag) - .commit() - PreferenceManager.getDefaultSharedPreferences(InstrumentationRegistry.getInstrumentation().targetContext) - .edit() - .putString(UserPreferences.Prefs.prefDefaultPage.name, UserPreferences.DEFAULT_PAGE_REMEMBER) - .commit() - } - - fun clearDatabase() { -// init(InstrumentationRegistry.getInstrumentation().targetContext) -// deleteDatabase() -// val adapter = getInstance() -// adapter.open() -// adapter.close() - } - - fun clickPreference(@StringRes title: Int) { - Espresso.onView(ViewMatchers.withId(com.bytehamster.lib.preferencesearch.R.id.recycler_view)).perform( - RecyclerViewActions.actionOnItem( - Matchers.allOf(ViewMatchers.hasDescendant(ViewMatchers.withText(title)), - ViewMatchers.hasDescendant(ViewMatchers.withId(android.R.id.widget_frame))), - ViewActions.click())) - } - - fun openNavDrawer() { - Espresso.onView(ViewMatchers.isRoot()).perform(waitForView(ViewMatchers.withId(R.id.main_layout), 1000)) - Espresso.onView(ViewMatchers.withId(R.id.main_layout)).perform(DrawerActions.open()) - } - - fun onDrawerItem(viewMatcher: Matcher?): ViewInteraction { - return Espresso.onView(Matchers.allOf(viewMatcher, ViewMatchers.withId(R.id.txtvTitle))) - } - - fun tryKillPlaybackService() { - val context = InstrumentationRegistry.getInstrumentation().targetContext - context.stopService(Intent(context, PlaybackService::class.java)) - try { - // Android has no reliable way to stop a service instantly. - // Calling stopSelf marks allows the system to destroy the service but the actual call - // to onDestroy takes until the next GC of the system, which we can not influence. - // Try to wait for the service at least a bit. - Awaitility.await().atMost(10, TimeUnit.SECONDS).until { !PlaybackService.isRunning } - } catch (e: ConditionTimeoutException) { - e.printStackTrace() - } - InstrumentationRegistry.getInstrumentation().waitForIdleSync() - } - - fun actionBarOverflow(): Matcher { - return Matchers.allOf(ViewMatchers.isDisplayed(), ViewMatchers.withContentDescription("More options")) - } -} diff --git a/app/src/androidTest/kotlin/ac/test/podcini/IgnoreOnCi.kt b/app/src/androidTest/kotlin/ac/test/podcini/IgnoreOnCi.kt deleted file mode 100644 index 26cf9500..00000000 --- a/app/src/androidTest/kotlin/ac/test/podcini/IgnoreOnCi.kt +++ /dev/null @@ -1,9 +0,0 @@ -package de.test.podcini - -/** - * Tests with this annotation are ignored on CI. This could be reasonable - * if the performance of the CI server is not enough to provide a reliable result. - */ -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.CLASS) -annotation class IgnoreOnCi diff --git a/app/src/androidTest/kotlin/ac/test/podcini/NthMatcher.kt b/app/src/androidTest/kotlin/ac/test/podcini/NthMatcher.kt deleted file mode 100644 index dfd8f3b6..00000000 --- a/app/src/androidTest/kotlin/ac/test/podcini/NthMatcher.kt +++ /dev/null @@ -1,30 +0,0 @@ -package de.test.podcini - -import org.hamcrest.BaseMatcher -import org.hamcrest.Description -import org.hamcrest.Matcher -import java.util.concurrent.atomic.AtomicInteger - -object NthMatcher { - fun first(matcher: Matcher): Matcher { - return nth(matcher, 1) - } - - fun nth(matcher: Matcher, index: Int): Matcher { - return object : BaseMatcher() { - var count: AtomicInteger = AtomicInteger(0) - - override fun matches(item: Any): Boolean { - if (matcher.matches(item)) { - return count.incrementAndGet() == index - } - return false - } - - override fun describeTo(description: Description) { - description.appendText("Item #$index ") - description.appendDescriptionOf(matcher) - } - } - } -} diff --git a/app/src/androidTest/kotlin/ac/test/podcini/dialogs/ShareDialogTest.kt b/app/src/androidTest/kotlin/ac/test/podcini/dialogs/ShareDialogTest.kt deleted file mode 100644 index 4b88f6b9..00000000 --- a/app/src/androidTest/kotlin/ac/test/podcini/dialogs/ShareDialogTest.kt +++ /dev/null @@ -1,66 +0,0 @@ -package de.test.podcini.dialogs - -import android.content.Context -import android.content.Intent -import androidx.recyclerview.widget.RecyclerView -import androidx.test.espresso.Espresso -import androidx.test.espresso.action.ViewActions -import androidx.test.espresso.contrib.RecyclerViewActions -import androidx.test.espresso.intent.rule.IntentsTestRule -import androidx.test.espresso.matcher.ViewMatchers -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import ac.mdiq.podcini.R -import ac.mdiq.podcini.ui.activity.MainActivity -import ac.mdiq.podcini.ui.fragment.AllEpisodesFragment -import de.test.podcini.EspressoTestUtils -import de.test.podcini.NthMatcher -import de.test.podcini.ui.UITestUtils -import org.hamcrest.CoreMatchers -import org.hamcrest.Matchers -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - - -/** - * User interface tests for share dialog. - */ -@RunWith(AndroidJUnit4::class) -class ShareDialogTest { - @Rule - var activityRule: IntentsTestRule = IntentsTestRule(MainActivity::class.java, false, false) - - protected var context: Context? = null - - @Before - @Throws(Exception::class) - fun setUp() { - context = InstrumentationRegistry.getInstrumentation().targetContext - EspressoTestUtils.clearPreferences() - EspressoTestUtils.clearDatabase() - EspressoTestUtils.setLaunchScreen(AllEpisodesFragment.TAG) - val uiTestUtils = UITestUtils(context!!) - uiTestUtils.setup() - uiTestUtils.addLocalFeedData(true) - - activityRule.launchActivity(Intent()) - - EspressoTestUtils.openNavDrawer() - EspressoTestUtils.onDrawerItem(ViewMatchers.withText(R.string.episodes_label)).perform(ViewActions.click()) - val allEpisodesMatcher = Matchers.allOf(ViewMatchers.withId(R.id.recyclerView), - ViewMatchers.isDisplayed(), - ViewMatchers.hasMinimumChildCount(2)) - Espresso.onView(ViewMatchers.isRoot()).perform(EspressoTestUtils.waitForView(allEpisodesMatcher, 1000)) - Espresso.onView(allEpisodesMatcher) - .perform(RecyclerViewActions.actionOnItemAtPosition(0, ViewActions.click())) - Espresso.onView(NthMatcher.first(EspressoTestUtils.actionBarOverflow())).perform(ViewActions.click()) - } - - @Test - fun testShareDialogDisplayed() { - Espresso.onView(ViewMatchers.withText(R.string.share_label)).perform(ViewActions.click()) - Espresso.onView(CoreMatchers.allOf(ViewMatchers.isDisplayed(), ViewMatchers.withText(R.string.share_label))) - } -} diff --git a/app/src/androidTest/kotlin/ac/test/podcini/playback/PlaybackTest.kt b/app/src/androidTest/kotlin/ac/test/podcini/playback/PlaybackTest.kt deleted file mode 100644 index afe00e9b..00000000 --- a/app/src/androidTest/kotlin/ac/test/podcini/playback/PlaybackTest.kt +++ /dev/null @@ -1,307 +0,0 @@ -package de.test.podcini.playback - -import ac.mdiq.podcini.R -import ac.mdiq.podcini.playback.ServiceStatusHandler -import ac.mdiq.podcini.playback.base.InTheatre.curQueue -import ac.mdiq.podcini.playback.base.MediaPlayerBase -import ac.mdiq.podcini.playback.base.PlayerStatus -import ac.mdiq.podcini.preferences.UserPreferences -import ac.mdiq.podcini.receiver.MediaButtonReceiver.Companion.createIntent -import ac.mdiq.podcini.storage.database.Episodes.getEpisode -import ac.mdiq.podcini.storage.database.Episodes.getEpisodes -import ac.mdiq.podcini.storage.database.Queues.clearQueue -import ac.mdiq.podcini.storage.database.Queues.getInQueueEpisodeIds -import ac.mdiq.podcini.storage.model.EpisodeFilter.Companion.unfiltered -import ac.mdiq.podcini.storage.model.EpisodeSortOrder -import ac.mdiq.podcini.ui.activity.MainActivity -import android.content.Context -import android.content.Intent -import android.view.KeyEvent -import androidx.preference.PreferenceManager -import androidx.recyclerview.widget.RecyclerView -import androidx.test.espresso.Espresso -import androidx.test.espresso.action.ViewActions -import androidx.test.espresso.contrib.RecyclerViewActions -import androidx.test.espresso.matcher.ViewMatchers -import androidx.test.filters.LargeTest -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.rule.ActivityTestRule -import de.test.podcini.EspressoTestUtils -import de.test.podcini.IgnoreOnCi -import de.test.podcini.ui.UITestUtils -import kotlinx.coroutines.runBlocking -import org.awaitility.Awaitility -import org.hamcrest.Matchers -import org.junit.* -import java.util.concurrent.TimeUnit - -/** - * Test cases for starting and ending playback from the MainActivity and AudioPlayerActivity. - */ -@LargeTest -@IgnoreOnCi -class PlaybackTest { - @get:Rule - var activityTestRule: ActivityTestRule = ActivityTestRule(MainActivity::class.java, false, false) - - private var uiTestUtils: UITestUtils? = null - protected lateinit var context: Context - private var controller: ServiceStatusHandler? = null - - @Before - @Throws(Exception::class) - fun setUp() { - context = InstrumentationRegistry.getInstrumentation().targetContext - EspressoTestUtils.clearPreferences() - EspressoTestUtils.clearDatabase() - - uiTestUtils = UITestUtils(context) - uiTestUtils!!.setup() - } - - @After - @Throws(Exception::class) - fun tearDown() { - activityTestRule.finishActivity() - EspressoTestUtils.tryKillPlaybackService() - uiTestUtils!!.tearDown() - if (controller != null) { - controller!!.release() - } - } - - private fun setupPlaybackController() { - controller = object : ServiceStatusHandler(activityTestRule.activity) { - override fun loadMediaInfo() { - // Do nothing - } - } - controller?.init() - } - - @Test - @Throws(Exception::class) - fun testContinousPlaybackOffMultipleEpisodes() { - setContinuousPlaybackPreference(false) - uiTestUtils!!.addLocalFeedData(true) - activityTestRule.launchActivity(Intent()) - setupPlaybackController() - playFromQueue(0) - Awaitility.await().atMost(5, TimeUnit.SECONDS) - .until { MediaPlayerBase.status == PlayerStatus.INITIALIZED } - } - - @Test - @Throws(Exception::class) - fun testContinuousPlaybackOnMultipleEpisodes() { - setContinuousPlaybackPreference(true) - uiTestUtils!!.addLocalFeedData(true) - activityTestRule.launchActivity(Intent()) - - val queue = curQueue.episodes - val first = queue[0] - val second = queue[1] - - playFromQueue(0) -// Awaitility.await().atMost(2, TimeUnit.SECONDS).until { first.media!!.id == currentlyPlayingFeedMediaId } -// Awaitility.await().atMost(6, TimeUnit.SECONDS).until { second.media!!.id == currentlyPlayingFeedMediaId } - } - - - @Test - @Throws(Exception::class) - fun testReplayEpisodeContinuousPlaybackOn() { - replayEpisodeCheck(true) - } - - @Test - @Throws(Exception::class) - fun testReplayEpisodeContinuousPlaybackOff() { - replayEpisodeCheck(false) - } - - @Test - @Throws(Exception::class) - fun testSmartMarkAsPlayed_Skip_Average() { - doTestSmartMarkAsPlayed_Skip_ForEpisode(0) - } - - @Test - @Throws(Exception::class) - fun testSmartMarkAsPlayed_Skip_LastEpisodeInQueue() { - doTestSmartMarkAsPlayed_Skip_ForEpisode(-1) - } - - @Test - @Throws(Exception::class) - fun testSmartMarkAsPlayed_Pause_WontAffectItem() { - setSmartMarkAsPlayedPreference(60) - - uiTestUtils!!.addLocalFeedData(true) - activityTestRule.launchActivity(Intent()) - setupPlaybackController() - - val fiIdx = 0 - val feedItem = curQueue.episodes[fiIdx] - - playFromQueue(fiIdx) - - // let playback run a bit then pause - Awaitility.await() - .atMost(1000, TimeUnit.MILLISECONDS) - .until { PlayerStatus.PLAYING == MediaPlayerBase.status } - pauseEpisode() - Awaitility.await() - .atMost(1000, TimeUnit.MILLISECONDS) - .until { PlayerStatus.PAUSED == MediaPlayerBase.status } - - Assert.assertThat("Ensure even with smart mark as play, after pause, the item remains in the queue.", - curQueue.episodes, Matchers.hasItems(feedItem)) - Assert.assertThat("Ensure even with smart mark as play, after pause, the item played status remains false.", - getEpisode(feedItem.id)!!.isPlayed(), Matchers.`is`(false)) - } - - @Test - @Throws(Exception::class) - fun testStartLocal() { - uiTestUtils!!.addLocalFeedData(true) - activityTestRule.launchActivity(Intent()) - runBlocking { clearQueue().join() } - startLocalPlayback() - } - - @Test - @Throws(Exception::class) - fun testPlayingItemAddsToQueue() { - uiTestUtils!!.addLocalFeedData(true) - activityTestRule.launchActivity(Intent()) - runBlocking { clearQueue().join() } - val queue = curQueue.episodes - Assert.assertEquals(0, queue.size.toLong()) - startLocalPlayback() - Awaitility.await().atMost(1, TimeUnit.SECONDS).until { 1 == curQueue.episodes.size } - } - - @Test - @Throws(Exception::class) - fun testContinousPlaybackOffSingleEpisode() { - setContinuousPlaybackPreference(false) - uiTestUtils!!.addLocalFeedData(true) - activityTestRule.launchActivity(Intent()) - runBlocking { clearQueue().join() } - startLocalPlayback() - } - - protected fun setContinuousPlaybackPreference(value: Boolean) { - val prefs = PreferenceManager.getDefaultSharedPreferences(context) - prefs.edit().putBoolean(UserPreferences.Prefs.prefFollowQueue.name, value).commit() - } - - protected fun setSkipKeepsEpisodePreference(value: Boolean) { - val prefs = PreferenceManager.getDefaultSharedPreferences(context) - prefs.edit().putBoolean(UserPreferences.Prefs.prefSkipKeepsEpisode.name, value).commit() - } - - protected fun setSmartMarkAsPlayedPreference(smartMarkAsPlayedSecs: Int) { - val prefs = PreferenceManager.getDefaultSharedPreferences(context) - prefs.edit().putString(UserPreferences.Prefs.prefSmartMarkAsPlayedSecs.name, - smartMarkAsPlayedSecs.toString(10)) - .commit() - } - - private fun skipEpisode() { - context.sendBroadcast(createIntent(context, KeyEvent.KEYCODE_MEDIA_NEXT)) - } - - protected fun pauseEpisode() { - context.sendBroadcast(createIntent(context, KeyEvent.KEYCODE_MEDIA_PAUSE)) - } - - protected fun startLocalPlayback() { - EspressoTestUtils.openNavDrawer() - EspressoTestUtils.onDrawerItem(ViewMatchers.withText(R.string.episodes_label)).perform(ViewActions.click()) - - val episodes = getEpisodes(0, 10, unfiltered(), EpisodeSortOrder.DATE_NEW_OLD) - val allEpisodesMatcher = Matchers.allOf(ViewMatchers.withId(R.id.recyclerView), - ViewMatchers.isDisplayed(), - ViewMatchers.hasMinimumChildCount(2)) - Espresso.onView(ViewMatchers.isRoot()).perform(EspressoTestUtils.waitForView(allEpisodesMatcher, 1000)) - Espresso.onView(allEpisodesMatcher).perform(RecyclerViewActions.actionOnItemAtPosition( - 0, - EspressoTestUtils.clickChildViewWithId(R.id.secondaryActionButton))) - - val media = episodes[0].media -// Awaitility.await().atMost(1, TimeUnit.SECONDS).until { media!!.id == currentlyPlayingFeedMediaId } - } - - /** - * - * @param itemIdx The 0-based index of the episode to be played in the queue. - */ - protected fun playFromQueue(itemIdx: Int) { - val queue = curQueue.episodes - - val queueMatcher = Matchers.allOf(ViewMatchers.withId(R.id.recyclerView), - ViewMatchers.isDisplayed(), - ViewMatchers.hasMinimumChildCount(2)) - Espresso.onView(ViewMatchers.isRoot()).perform(EspressoTestUtils.waitForView(queueMatcher, 1000)) - Espresso.onView(queueMatcher).perform(RecyclerViewActions.actionOnItemAtPosition( - itemIdx, - EspressoTestUtils.clickChildViewWithId(R.id.secondaryActionButton))) - - val media = queue[itemIdx].media -// Awaitility.await().atMost(1, TimeUnit.SECONDS).until { media!!.id == currentlyPlayingFeedMediaId } - } - - /** - * Check if an episode can be played twice without problems. - */ - @Throws(Exception::class) - protected fun replayEpisodeCheck(followQueue: Boolean) { - setContinuousPlaybackPreference(followQueue) - uiTestUtils!!.addLocalFeedData(true) - runBlocking { clearQueue().join() } - activityTestRule.launchActivity(Intent()) - val episodes = getEpisodes(0, 10, unfiltered(), EpisodeSortOrder.DATE_NEW_OLD) - - startLocalPlayback() - val media = episodes[0].media -// Awaitility.await().atMost(1, TimeUnit.SECONDS).until { media!!.id == currentlyPlayingFeedMediaId } -// -// Awaitility.await().atMost(5, TimeUnit.SECONDS).until { media!!.id != currentlyPlayingFeedMediaId } -// -// startLocalPlayback() -// -// Awaitility.await().atMost(1, TimeUnit.SECONDS).until { media!!.id == currentlyPlayingFeedMediaId } - } - - @Throws(Exception::class) - protected fun doTestSmartMarkAsPlayed_Skip_ForEpisode(itemIdxNegAllowed: Int) { - setSmartMarkAsPlayedPreference(60) - // ensure when an episode is skipped, it is removed due to smart as played - setSkipKeepsEpisodePreference(false) - uiTestUtils!!.setMediaFileName("30sec.mp3") - uiTestUtils!!.addLocalFeedData(true) - - val queue = getInQueueEpisodeIds().toList() - val fiIdx = if (itemIdxNegAllowed >= 0) { - itemIdxNegAllowed - } else { // negative index: count from the end, with -1 being the last one, etc. - queue.size + itemIdxNegAllowed - } - val feedItemId = queue.get(fiIdx) -// queue.removeIndex(fiIdx) - Assert.assertFalse(queue.contains(feedItemId)) // Verify that episode is in queue only once - - activityTestRule.launchActivity(Intent()) - playFromQueue(fiIdx) - - skipEpisode() - - // assert item no longer in queue (needs to wait till skip is asynchronously processed) - Awaitility.await() - .atMost(5000, TimeUnit.MILLISECONDS) - .until { !getInQueueEpisodeIds().contains(feedItemId) } - Assert.assertTrue(getEpisode(feedItemId)!!.isPlayed()) - } -} diff --git a/app/src/androidTest/kotlin/ac/test/podcini/service/download/HttpDownloaderTest.kt b/app/src/androidTest/kotlin/ac/test/podcini/service/download/HttpDownloaderTest.kt deleted file mode 100644 index 5781607d..00000000 --- a/app/src/androidTest/kotlin/ac/test/podcini/service/download/HttpDownloaderTest.kt +++ /dev/null @@ -1,291 +0,0 @@ -package de.test.podcini.service.download - -import ac.mdiq.podcini.net.download.DownloadError -import ac.mdiq.podcini.net.download.service.Downloader -import ac.mdiq.podcini.net.download.service.HttpDownloader -import ac.mdiq.podcini.net.download.service.DownloadRequest -import ac.mdiq.podcini.preferences.UserPreferences.init -import ac.mdiq.podcini.util.Logd -import androidx.test.filters.LargeTest -import androidx.test.platform.app.InstrumentationRegistry -import de.test.podcini.util.service.download.HTTPBin -import org.junit.After -import org.junit.Assert -import org.junit.Before -import org.junit.Test -import java.io.File -import java.io.IOException -import java.io.Serializable - -@LargeTest -class HttpDownloaderTest { - private var url404: String? = null - private var urlAuth: String? = null - private var destDir: File? = null - private var httpServer: HTTPBin? = null - - @After - @Throws(Exception::class) - fun tearDown() { - val contents = destDir!!.listFiles() - if (contents != null) { - for (f in contents) { - Assert.assertTrue(f.delete()) - } - } - httpServer!!.stop() - } - - @Before - @Throws(Exception::class) - fun setUp() { - init(InstrumentationRegistry.getInstrumentation().targetContext) - destDir = InstrumentationRegistry.getInstrumentation().targetContext.getExternalFilesDir(DOWNLOAD_DIR) - Assert.assertNotNull(destDir) - Assert.assertTrue(destDir!!.exists()) - httpServer = HTTPBin() - httpServer!!.start() - url404 = httpServer!!.baseUrl + "/status/404" - urlAuth = httpServer!!.baseUrl + "/basic-auth/user/passwd" - } - - private fun setupFeedFile(downloadUrl: String?, title: String, deleteExisting: Boolean): FeedFileImpl { - val feedfile = FeedFileImpl(downloadUrl) - val fileUrl = File(destDir, title).absolutePath - val file = File(fileUrl) - if (deleteExisting) { - Logd(TAG, "Deleting file: " + file.delete()) - } - feedfile.setFile_url(fileUrl) - return feedfile - } - - private fun download(url: String?, title: String, expectedResult: Boolean, deleteExisting: Boolean = true, - username: String? = null, password: String? = null): Downloader { - - val feedFile: FeedFile = setupFeedFile(url, title, deleteExisting) - val request = DownloadRequest(feedFile.getFile_url()!!, url!!, title, 0, feedFile.getTypeAsInt(), - username, password, null, false) - val downloader: Downloader = HttpDownloader(request) - downloader.call() - val status = downloader.result - Assert.assertNotNull(status) - Assert.assertEquals(expectedResult, status.isSuccessful) - // the file should not exist if the download has failed and deleteExisting was true - Assert.assertTrue(!deleteExisting || File(feedFile.getFile_url()!!).exists() == expectedResult) - return downloader - } - - @Test - fun testPassingHttp() { - download(httpServer!!.baseUrl + "/status/200", "test200", true) - } - - @Test - fun testRedirect() { - download(httpServer!!.baseUrl + "/redirect/4", "testRedirect", true) - } - - @Test - fun testGzip() { - download(httpServer!!.baseUrl + "/gzip/100", "testGzip", true) - } - - @Test - fun test404() { - download(url404, "test404", false) - } - - @Test - fun testCancel() { - val url = httpServer!!.baseUrl + "/delay/3" - val feedFile = setupFeedFile(url, "delay", true) - val downloader: Downloader = HttpDownloader(DownloadRequest(feedFile.getFile_url()!!, url, "delay", 0, - feedFile.getTypeAsInt(), null, null, null, false)) - val t: Thread = object : Thread() { - override fun run() { - downloader.call() - } - } - t.start() - downloader.cancel() - try { - t.join() - } catch (e: InterruptedException) { - e.printStackTrace() - } - val result = downloader.result - Assert.assertFalse(result.isSuccessful) - } - - @Test - fun testDeleteOnFailShouldDelete() { - val downloader = download(url404, "testDeleteOnFailShouldDelete", false, true, null, null) - Assert.assertFalse(File(downloader.downloadRequest.destination!!).exists()) - } - - @Test - @Throws(IOException::class) - fun testDeleteOnFailShouldNotDelete() { - val filename = "testDeleteOnFailShouldDelete" - val dest = File(destDir, filename) - dest.delete() - Assert.assertTrue(dest.createNewFile()) - val downloader = download(url404, filename, false, false, null, null) - Assert.assertTrue(File(downloader.downloadRequest.destination!!).exists()) - } - - @Test - @Throws(InterruptedException::class) - fun testAuthenticationShouldSucceed() { - download(urlAuth, "testAuthSuccess", true, true, "user", "passwd") - } - - @Test - fun testAuthenticationShouldFail() { - val downloader = download(urlAuth, "testAuthSuccess", false, true, "user", "Wrong passwd") - Assert.assertEquals(DownloadError.ERROR_UNAUTHORIZED, downloader.result.reason) - } - - /* TODO: replace with smaller test file - public void testUrlWithSpaces() { - download("http://acedl.noxsolutions.com/ace/Don't Call Salman Rushdie Sneezy in Finland.mp3", "testUrlWithSpaces", true); - } - */ - private class FeedFileImpl(download_url: String?) : FeedFile(null, download_url, false) { - override fun getHumanReadableIdentifier(): String? { - return download_url - } - - override fun getTypeAsInt(): Int { - return 0 - } - } - - companion object { - private val TAG: String = HttpDownloaderTest::class.simpleName ?: "Anonymous" - private const val DOWNLOAD_DIR = "testdownloads" - } -} - -abstract class FeedFile(@JvmField var file_url: String? = null, - @JvmField var download_url: String? = null, - private var downloaded: Boolean = false) : FeedComponent(), Serializable { - - /** - * Creates a new FeedFile object. - * - * @param file_url The location of the FeedFile. If this is null, the downloaded-attribute - * will automatically be set to false. - * @param download_url The location where the FeedFile can be downloaded. - * @param downloaded true if the FeedFile has been downloaded, false otherwise. This parameter - * will automatically be interpreted as false if the file_url is null. - */ - init { -// Log.d("FeedFile", "$file_url $download_url $downloaded") - this.downloaded = (file_url != null) && downloaded - } - - abstract fun getTypeAsInt(): Int - - /** - * Update this FeedFile's attributes with the attributes from another - * FeedFile. This method should only update attributes which where read from - * the feed. - */ - fun updateFromOther(other: FeedFile) { - super.updateFromOther(other) - this.download_url = other.download_url - } - - /** - * Compare's this FeedFile's attribute values with another FeedFile's - * attribute values. This method will only compare attributes which were - * read from the feed. - * - * @return true if attribute values are different, false otherwise - */ - fun compareWithOther(other: FeedFile): Boolean { - if (super.compareWithOther(other)) return true - if (download_url != other.download_url) return true - - return false - } - - /** - * Returns true if the file exists at file_url. - */ - fun fileExists(): Boolean { - if (file_url == null) return false - else { - val f = File(file_url!!) - return f.exists() - } - } - - fun getFile_url(): String? { - return file_url - } - - /** - * Changes the file_url of this FeedFile. Setting this value to - * null will also set the downloaded-attribute to false. - */ - open fun setFile_url(file_url: String?) { - this.file_url = file_url - if (file_url == null) downloaded = false - } - - fun isDownloaded(): Boolean { - return downloaded - } - - open fun setDownloaded(downloaded: Boolean) { - this.downloaded = downloaded - } -} - -/** - * Represents every possible component of a feed - * - * @author daniel - */ -// only used in test -abstract class FeedComponent internal constructor() { - open var id: Long = 0 - - /** - * Update this FeedComponent's attributes with the attributes from another - * FeedComponent. This method should only update attributes which where read from - * the feed. - */ - fun updateFromOther(other: FeedComponent?) {} - - /** - * Compare's this FeedComponent's attribute values with another FeedComponent's - * attribute values. This method will only compare attributes which were - * read from the feed. - * - * @return true if attribute values are different, false otherwise - */ - fun compareWithOther(other: FeedComponent?): Boolean { - return false - } - - /** - * Should return a non-null, human-readable String so that the item can be - * identified by the user. Can be title, download-url, etc. - */ - abstract fun getHumanReadableIdentifier(): String? - - override fun equals(o: Any?): Boolean { - if (this === o) return true - if (o !is FeedComponent) return false - - return id == o.id - } - - override fun hashCode(): Int { - return (id xor (id ushr 32)).toInt() - } -} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/ac/test/podcini/service/playback/MediaPlayerBaseTest.kt b/app/src/androidTest/kotlin/ac/test/podcini/service/playback/MediaPlayerBaseTest.kt deleted file mode 100644 index 749b2fbf..00000000 --- a/app/src/androidTest/kotlin/ac/test/podcini/service/playback/MediaPlayerBaseTest.kt +++ /dev/null @@ -1,872 +0,0 @@ -package de.test.podcini.service.playback - -import ac.mdiq.podcini.playback.base.MediaPlayerBase -import ac.mdiq.podcini.playback.base.MediaPlayerBase.MediaPlayerInfo -import ac.mdiq.podcini.playback.base.MediaPlayerCallback -import ac.mdiq.podcini.playback.base.PlayerStatus -import ac.mdiq.podcini.playback.service.PlaybackService.LocalMediaPlayer -import ac.mdiq.podcini.storage.model.* -import ac.mdiq.podcini.storage.model.VolumeAdaptionSetting -import androidx.test.annotation.UiThreadTest -import androidx.test.filters.MediumTest -import androidx.test.platform.app.InstrumentationRegistry -import de.test.podcini.EspressoTestUtils -import de.test.podcini.util.service.download.HTTPBin -import junit.framework.AssertionFailedError -import org.apache.commons.io.IOUtils -import org.junit.After -import org.junit.Assert -import org.junit.Before -import org.junit.Test -import java.io.File -import java.io.FileOutputStream -import java.io.OutputStream -import java.util.* -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit -import kotlin.concurrent.Volatile - -/** - * Test class for LocalPSMP - */ -@MediumTest -class MediaPlayerBaseTest { - private var PLAYABLE_LOCAL_URL: String? = null - private var httpServer: HTTPBin? = null - private var playableFileUrl: String? = null - - @Volatile - private var assertionError: AssertionFailedError? = null - - @After - @UiThreadTest - @Throws(Exception::class) - fun tearDown() { -// deleteDatabase() - httpServer!!.stop() - } - - @Before - @UiThreadTest - @Throws(Exception::class) - fun setUp() { - assertionError = null - EspressoTestUtils.clearPreferences() - EspressoTestUtils.clearDatabase() - - val context = InstrumentationRegistry.getInstrumentation().targetContext - - httpServer = HTTPBin() - httpServer!!.start() - playableFileUrl = httpServer!!.baseUrl + "/files/0" - - var cacheDir = context.getExternalFilesDir("testFiles") - if (cacheDir == null) cacheDir = context.getExternalFilesDir("testFiles") - val dest = File(cacheDir, PLAYABLE_DEST_URL) - - Assert.assertNotNull(cacheDir) - Assert.assertTrue(cacheDir!!.canWrite()) - Assert.assertTrue(cacheDir.canRead()) - if (!dest.exists()) { - val i = InstrumentationRegistry.getInstrumentation().context.assets.open("3sec.mp3") - val o: OutputStream = FileOutputStream(File(cacheDir, PLAYABLE_DEST_URL)) - IOUtils.copy(i, o) - o.flush() - o.close() - i.close() - } - PLAYABLE_LOCAL_URL = dest.absolutePath - Assert.assertEquals(0, httpServer!!.serveFile(dest).toLong()) - } - - private fun checkPSMPInfo(info: MediaPlayerInfo?) { - try { - when (info!!.playerStatus) { - PlayerStatus.PLAYING, PlayerStatus.PAUSED, PlayerStatus.PREPARED, PlayerStatus.PREPARING, PlayerStatus.INITIALIZED, PlayerStatus.INITIALIZING, PlayerStatus.SEEKING -> Assert.assertNotNull( - info.playable) - PlayerStatus.STOPPED, PlayerStatus.ERROR -> Assert.assertNull(info.playable) - else -> {} - } - } catch (e: AssertionFailedError) { - if (assertionError == null) assertionError = e - } - } - - @Test - @UiThreadTest - fun testInit() { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val psmp: MediaPlayerBase = LocalMediaPlayer(c, DefaultMediaPlayerCallback()) - psmp.shutdown() - } - - private fun writeTestPlayable(downloadUrl: String?, fileUrl: String?): Playable { - val f = Feed(0, null, "f", "l", "d", null, null, null, null, "i", null, null, "l") - val prefs = FeedPreferences(f.id, false, FeedPreferences.AutoDeleteAction.NEVER, - VolumeAdaptionSetting.OFF, null, null) - f.preferences = prefs - f.episodes.clear() - val i = Episode(0, "t", "i", "l", Date(), PlayState.UNPLAYED.code, f) - f.episodes.add(i) - val media = EpisodeMedia(0, i, 0, 0, 0, "audio/wav", fileUrl, downloadUrl, fileUrl != null, null, 0, 0) - i.setMedia(media) -// val adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(f) -// Assert.assertTrue(media.id != 0L) -// adapter.close() - return media - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testPlayMediaObjectStreamNoStartNoPrepare() { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val countDownLatch = CountDownLatch(2) - val callback = CancelableMediaPlayerCallback(object : DefaultMediaPlayerCallback() { - override fun statusChanged(newInfo: MediaPlayerInfo?) { - try { - checkPSMPInfo(newInfo) - check(newInfo!!.playerStatus != PlayerStatus.ERROR) { "MediaPlayer error" } - when (countDownLatch.count) { - 0L -> { - Assert.fail() - } - 2L -> { - Assert.assertEquals(PlayerStatus.INITIALIZING, newInfo.playerStatus) - countDownLatch.countDown() - } - else -> { - Assert.assertEquals(PlayerStatus.INITIALIZED, newInfo.playerStatus) - countDownLatch.countDown() - } - } - } catch (e: AssertionFailedError) { - if (assertionError == null) assertionError = e - } - } - }) - val psmp: MediaPlayerBase = LocalMediaPlayer(c, callback) - val p = writeTestPlayable(playableFileUrl, null) - psmp.playMediaObject(p, true, false, false, false) - val res = countDownLatch.await(LATCH_TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS) - if (assertionError != null) throw assertionError!! - Assert.assertTrue(res) - - Assert.assertSame(PlayerStatus.INITIALIZED, psmp.playerInfo.playerStatus) - Assert.assertFalse(psmp.startWhenPrepared.get()) - callback.cancel() - psmp.shutdown() - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testPlayMediaObjectStreamStartNoPrepare() { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val countDownLatch = CountDownLatch(2) - val callback = CancelableMediaPlayerCallback(object : DefaultMediaPlayerCallback() { - override fun statusChanged(newInfo: MediaPlayerInfo?) { - try { - checkPSMPInfo(newInfo) - check(newInfo!!.playerStatus != PlayerStatus.ERROR) { "MediaPlayer error" } - when (countDownLatch.count) { - 0L -> { - Assert.fail() - } - 2L -> { - Assert.assertEquals(PlayerStatus.INITIALIZING, newInfo.playerStatus) - countDownLatch.countDown() - } - else -> { - Assert.assertEquals(PlayerStatus.INITIALIZED, newInfo.playerStatus) - countDownLatch.countDown() - } - } - } catch (e: AssertionFailedError) { - if (assertionError == null) assertionError = e - } - } - }) - val psmp: MediaPlayerBase = LocalMediaPlayer(c, callback) - val p = writeTestPlayable(playableFileUrl, null) - psmp.playMediaObject(p, true, true, false) - - val res = countDownLatch.await(LATCH_TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS) - if (assertionError != null) throw assertionError!! - Assert.assertTrue(res) - - Assert.assertSame(PlayerStatus.INITIALIZED, psmp.playerInfo.playerStatus) - Assert.assertTrue(psmp.startWhenPrepared.get()) - callback.cancel() - psmp.shutdown() - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testPlayMediaObjectStreamNoStartPrepare() { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val countDownLatch = CountDownLatch(4) - val callback = CancelableMediaPlayerCallback(object : DefaultMediaPlayerCallback() { - override fun statusChanged(newInfo: MediaPlayerInfo?) { - try { - checkPSMPInfo(newInfo) - check(newInfo!!.playerStatus != PlayerStatus.ERROR) { "MediaPlayer error" } - when (countDownLatch.count) { - 0L -> { - Assert.fail() - } - 4L -> { - Assert.assertEquals(PlayerStatus.INITIALIZING, newInfo.playerStatus) - } - 3L -> { - Assert.assertEquals(PlayerStatus.INITIALIZED, newInfo.playerStatus) - } - 2L -> { - Assert.assertEquals(PlayerStatus.PREPARING, newInfo.playerStatus) - } - 1L -> { - Assert.assertEquals(PlayerStatus.PREPARED, newInfo.playerStatus) - } - } - countDownLatch.countDown() - } catch (e: AssertionFailedError) { - if (assertionError == null) assertionError = e - } - } - }) - val psmp: MediaPlayerBase = LocalMediaPlayer(c, callback) - val p = writeTestPlayable(playableFileUrl, null) - psmp.playMediaObject(p, true, false, true) - val res = countDownLatch.await(LATCH_TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS) - if (assertionError != null) throw assertionError!! - Assert.assertTrue(res) - Assert.assertSame(PlayerStatus.PREPARED, psmp.playerInfo.playerStatus) - callback.cancel() - - psmp.shutdown() - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testPlayMediaObjectStreamStartPrepare() { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val countDownLatch = CountDownLatch(5) - val callback = CancelableMediaPlayerCallback(object : DefaultMediaPlayerCallback() { - override fun statusChanged(newInfo: MediaPlayerInfo?) { - try { - checkPSMPInfo(newInfo) - check(newInfo!!.playerStatus != PlayerStatus.ERROR) { "MediaPlayer error" } - when (countDownLatch.count) { - 0L -> { - Assert.fail() - } - 5L -> { - Assert.assertEquals(PlayerStatus.INITIALIZING, newInfo.playerStatus) - } - 4L -> { - Assert.assertEquals(PlayerStatus.INITIALIZED, newInfo.playerStatus) - } - 3L -> { - Assert.assertEquals(PlayerStatus.PREPARING, newInfo.playerStatus) - } - 2L -> { - Assert.assertEquals(PlayerStatus.PREPARED, newInfo.playerStatus) - } - 1L -> { - Assert.assertEquals(PlayerStatus.PLAYING, newInfo.playerStatus) - } - } - countDownLatch.countDown() - } catch (e: AssertionFailedError) { - if (assertionError == null) assertionError = e - } - } - }) - val psmp: MediaPlayerBase = LocalMediaPlayer(c, callback) - val p = writeTestPlayable(playableFileUrl, null) - psmp.playMediaObject(p, true, true, true) - val res = countDownLatch.await(LATCH_TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS) - if (assertionError != null) throw assertionError!! - Assert.assertTrue(res) - Assert.assertSame(PlayerStatus.PLAYING, psmp.playerInfo.playerStatus) - callback.cancel() - psmp.shutdown() - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testPlayMediaObjectLocalNoStartNoPrepare() { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val countDownLatch = CountDownLatch(2) - val callback = CancelableMediaPlayerCallback(object : DefaultMediaPlayerCallback() { - override fun statusChanged(newInfo: MediaPlayerInfo?) { - try { - checkPSMPInfo(newInfo) - check(newInfo!!.playerStatus != PlayerStatus.ERROR) { "MediaPlayer error" } - when (countDownLatch.count) { - 0L -> { - Assert.fail() - } - 2L -> { - Assert.assertEquals(PlayerStatus.INITIALIZING, newInfo.playerStatus) - countDownLatch.countDown() - } - else -> { - Assert.assertEquals(PlayerStatus.INITIALIZED, newInfo.playerStatus) - countDownLatch.countDown() - } - } - } catch (e: AssertionFailedError) { - if (assertionError == null) assertionError = e - } - } - }) - val psmp: MediaPlayerBase = LocalMediaPlayer(c, callback) - val p = writeTestPlayable(playableFileUrl, PLAYABLE_LOCAL_URL) - psmp.playMediaObject(p, false, false, false) - val res = countDownLatch.await(LATCH_TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS) - if (assertionError != null) throw assertionError!! - Assert.assertTrue(res) - Assert.assertSame(PlayerStatus.INITIALIZED, psmp.playerInfo.playerStatus) - Assert.assertFalse(psmp.startWhenPrepared.get()) - callback.cancel() - psmp.shutdown() - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testPlayMediaObjectLocalStartNoPrepare() { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val countDownLatch = CountDownLatch(2) - val callback = CancelableMediaPlayerCallback(object : DefaultMediaPlayerCallback() { - override fun statusChanged(newInfo: MediaPlayerInfo?) { - try { - checkPSMPInfo(newInfo) - check(newInfo!!.playerStatus != PlayerStatus.ERROR) { "MediaPlayer error" } - when (countDownLatch.count) { - 0L -> { - Assert.fail() - } - 2L -> { - Assert.assertEquals(PlayerStatus.INITIALIZING, newInfo.playerStatus) - countDownLatch.countDown() - } - else -> { - Assert.assertEquals(PlayerStatus.INITIALIZED, newInfo.playerStatus) - countDownLatch.countDown() - } - } - } catch (e: AssertionFailedError) { - if (assertionError == null) assertionError = e - } - } - }) - val psmp: MediaPlayerBase = LocalMediaPlayer(c, callback) - val p = writeTestPlayable(playableFileUrl, PLAYABLE_LOCAL_URL) - psmp.playMediaObject(p, false, true, false) - val res = countDownLatch.await(LATCH_TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS) - if (assertionError != null) throw assertionError!! - Assert.assertTrue(res) - Assert.assertSame(PlayerStatus.INITIALIZED, psmp.playerInfo.playerStatus) - Assert.assertTrue(psmp.startWhenPrepared.get()) - callback.cancel() - psmp.shutdown() - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testPlayMediaObjectLocalNoStartPrepare() { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val countDownLatch = CountDownLatch(4) - val callback = CancelableMediaPlayerCallback(object : DefaultMediaPlayerCallback() { - override fun statusChanged(newInfo: MediaPlayerInfo?) { - try { - checkPSMPInfo(newInfo) - check(newInfo!!.playerStatus != PlayerStatus.ERROR) { "MediaPlayer error" } - when (countDownLatch.count) { - 0L -> { - Assert.fail() - } - 4L -> { - Assert.assertEquals(PlayerStatus.INITIALIZING, newInfo.playerStatus) - } - 3L -> { - Assert.assertEquals(PlayerStatus.INITIALIZED, newInfo.playerStatus) - } - 2L -> { - Assert.assertEquals(PlayerStatus.PREPARING, newInfo.playerStatus) - } - 1L -> { - Assert.assertEquals(PlayerStatus.PREPARED, newInfo.playerStatus) - } - } - countDownLatch.countDown() - } catch (e: AssertionFailedError) { - if (assertionError == null) assertionError = e - } - } - }) - val psmp: MediaPlayerBase = LocalMediaPlayer(c, callback) - val p = writeTestPlayable(playableFileUrl, PLAYABLE_LOCAL_URL) - psmp.playMediaObject(p, false, false, true) - val res = countDownLatch.await(LATCH_TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS) - if (assertionError != null) throw assertionError!! - Assert.assertTrue(res) - Assert.assertSame(PlayerStatus.PREPARED, psmp.playerInfo.playerStatus) - callback.cancel() - psmp.shutdown() - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testPlayMediaObjectLocalStartPrepare() { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val countDownLatch = CountDownLatch(5) - val callback = CancelableMediaPlayerCallback(object : DefaultMediaPlayerCallback() { - override fun statusChanged(newInfo: MediaPlayerInfo?) { - try { - checkPSMPInfo(newInfo) - check(newInfo!!.playerStatus != PlayerStatus.ERROR) { "MediaPlayer error" } - when (countDownLatch.count) { - 0L -> { - Assert.fail() - } - 5L -> { - Assert.assertEquals(PlayerStatus.INITIALIZING, newInfo.playerStatus) - } - 4L -> { - Assert.assertEquals(PlayerStatus.INITIALIZED, newInfo.playerStatus) - } - 3L -> { - Assert.assertEquals(PlayerStatus.PREPARING, newInfo.playerStatus) - } - 2L -> { - Assert.assertEquals(PlayerStatus.PREPARED, newInfo.playerStatus) - } - 1L -> { - Assert.assertEquals(PlayerStatus.PLAYING, newInfo.playerStatus) - } - } - } catch (e: AssertionFailedError) { - if (assertionError == null) assertionError = e - } finally { - countDownLatch.countDown() - } - } - }) - val psmp: MediaPlayerBase = LocalMediaPlayer(c, callback) - val p = writeTestPlayable(playableFileUrl, PLAYABLE_LOCAL_URL) - psmp.playMediaObject(p, false, true, true) - val res = countDownLatch.await(LATCH_TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS) - if (assertionError != null) throw assertionError!! - Assert.assertTrue(res) - Assert.assertSame(PlayerStatus.PLAYING, psmp.playerInfo.playerStatus) - callback.cancel() - psmp.shutdown() - } - - @Throws(InterruptedException::class) - private fun pauseTestSkeleton(initialState: PlayerStatus, - stream: Boolean, - abandonAudioFocus: Boolean, - reinit: Boolean, - timeoutSeconds: Long - ) { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val latchCount = if ((stream && reinit)) 2 else 1 - val countDownLatch = CountDownLatch(latchCount) - - val callback = CancelableMediaPlayerCallback(object : DefaultMediaPlayerCallback() { - override fun statusChanged(newInfo: MediaPlayerInfo?) { - checkPSMPInfo(newInfo) - when { - newInfo!!.playerStatus == PlayerStatus.ERROR -> { - if (assertionError == null) assertionError = UnexpectedStateChange(newInfo.playerStatus) - } - initialState != PlayerStatus.PLAYING -> { - if (assertionError == null) assertionError = UnexpectedStateChange(newInfo.playerStatus) - } - else -> { - when (newInfo.playerStatus) { - PlayerStatus.PAUSED -> if (latchCount.toLong() == countDownLatch.count) countDownLatch.countDown() - else { - if (assertionError == null) assertionError = UnexpectedStateChange(newInfo.playerStatus) - } - PlayerStatus.INITIALIZED -> when { - stream && reinit && countDownLatch.count < latchCount -> { - countDownLatch.countDown() - } - countDownLatch.count < latchCount -> { - if (assertionError == null) assertionError = UnexpectedStateChange(newInfo.playerStatus) - } - } - else -> {} - } - } - } - } - - override fun shouldStop() { - if (assertionError == null) assertionError = AssertionFailedError("Unexpected call to shouldStop") - } - }) - val psmp: MediaPlayerBase = LocalMediaPlayer(c, callback) - val p = writeTestPlayable(playableFileUrl, PLAYABLE_LOCAL_URL) - if (initialState == PlayerStatus.PLAYING) { - psmp.playMediaObject(p, stream, true, true) - } - psmp.pause(abandonAudioFocus, reinit) - val res = countDownLatch.await(timeoutSeconds, TimeUnit.SECONDS) - if (assertionError != null) throw assertionError!! - Assert.assertTrue(res || initialState != PlayerStatus.PLAYING) - callback.cancel() - psmp.shutdown() - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testPauseDefaultState() { - pauseTestSkeleton(PlayerStatus.STOPPED, false, false, false, 1) - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testPausePlayingStateNoAbandonNoReinitNoStream() { - pauseTestSkeleton(PlayerStatus.PLAYING, false, false, false, LATCH_TIMEOUT_SECONDS.toLong()) - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testPausePlayingStateNoAbandonNoReinitStream() { - pauseTestSkeleton(PlayerStatus.PLAYING, true, false, false, LATCH_TIMEOUT_SECONDS.toLong()) - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testPausePlayingStateAbandonNoReinitNoStream() { - pauseTestSkeleton(PlayerStatus.PLAYING, false, true, false, LATCH_TIMEOUT_SECONDS.toLong()) - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testPausePlayingStateAbandonNoReinitStream() { - pauseTestSkeleton(PlayerStatus.PLAYING, true, true, false, LATCH_TIMEOUT_SECONDS.toLong()) - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testPausePlayingStateNoAbandonReinitNoStream() { - pauseTestSkeleton(PlayerStatus.PLAYING, false, false, true, LATCH_TIMEOUT_SECONDS.toLong()) - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testPausePlayingStateNoAbandonReinitStream() { - pauseTestSkeleton(PlayerStatus.PLAYING, true, false, true, LATCH_TIMEOUT_SECONDS.toLong()) - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testPausePlayingStateAbandonReinitNoStream() { - pauseTestSkeleton(PlayerStatus.PLAYING, false, true, true, LATCH_TIMEOUT_SECONDS.toLong()) - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testPausePlayingStateAbandonReinitStream() { - pauseTestSkeleton(PlayerStatus.PLAYING, true, true, true, LATCH_TIMEOUT_SECONDS.toLong()) - } - - @Throws(InterruptedException::class) - private fun resumeTestSkeleton(initialState: PlayerStatus, timeoutSeconds: Long) { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val latchCount = - when (initialState) { - PlayerStatus.PAUSED, PlayerStatus.PLAYING -> 2 - PlayerStatus.PREPARED -> 1 - else -> 0 - } - val countDownLatch = CountDownLatch(latchCount) - - val callback = CancelableMediaPlayerCallback(object : DefaultMediaPlayerCallback() { - override fun statusChanged(newInfo: MediaPlayerInfo?) { - checkPSMPInfo(newInfo) - when { - newInfo!!.playerStatus == PlayerStatus.ERROR -> { - if (assertionError == null) assertionError = UnexpectedStateChange(newInfo.playerStatus) - } - newInfo.playerStatus == PlayerStatus.PLAYING -> { - if (countDownLatch.count == 0L) { - if (assertionError == null) assertionError = UnexpectedStateChange(newInfo.playerStatus) - } else { - countDownLatch.countDown() - } - } - } - } - }) - val psmp: MediaPlayerBase = LocalMediaPlayer(c, callback) - if (initialState == PlayerStatus.PREPARED || initialState == PlayerStatus.PLAYING || initialState == PlayerStatus.PAUSED) { - val startWhenPrepared = (initialState != PlayerStatus.PREPARED) - psmp.playMediaObject(writeTestPlayable(playableFileUrl, PLAYABLE_LOCAL_URL), false, startWhenPrepared, true) - } - if (initialState == PlayerStatus.PAUSED) { - psmp.pause(false, false) - } - psmp.resume() - val res = countDownLatch.await(timeoutSeconds, TimeUnit.SECONDS) - if (assertionError != null) throw assertionError!! - Assert.assertTrue(res || (initialState != PlayerStatus.PAUSED && initialState != PlayerStatus.PREPARED)) - callback.cancel() - psmp.shutdown() - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testResumePausedState() { - resumeTestSkeleton(PlayerStatus.PAUSED, LATCH_TIMEOUT_SECONDS.toLong()) - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testResumePreparedState() { - resumeTestSkeleton(PlayerStatus.PREPARED, LATCH_TIMEOUT_SECONDS.toLong()) - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testResumePlayingState() { - resumeTestSkeleton(PlayerStatus.PLAYING, 1) - } - - @Throws(InterruptedException::class) - private fun prepareTestSkeleton(initialState: PlayerStatus, timeoutSeconds: Long) { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val latchCount = 1 - val countDownLatch = CountDownLatch(latchCount) - val callback = CancelableMediaPlayerCallback(object : DefaultMediaPlayerCallback() { - override fun statusChanged(newInfo: MediaPlayerInfo?) { - checkPSMPInfo(newInfo) - if (newInfo!!.playerStatus == PlayerStatus.ERROR) { - if (assertionError == null) assertionError = UnexpectedStateChange(newInfo.playerStatus) - } else { - when { - initialState == PlayerStatus.INITIALIZED && newInfo.playerStatus == PlayerStatus.PREPARED -> { - countDownLatch.countDown() - } - initialState != PlayerStatus.INITIALIZED && initialState == newInfo.playerStatus -> { - countDownLatch.countDown() - } - } - } - } - }) - val psmp: MediaPlayerBase = LocalMediaPlayer(c, callback) - val p = writeTestPlayable(playableFileUrl, PLAYABLE_LOCAL_URL) - if (initialState == PlayerStatus.INITIALIZED || initialState == PlayerStatus.PLAYING || initialState == PlayerStatus.PREPARED || initialState == PlayerStatus.PAUSED) { - val prepareImmediately = (initialState != PlayerStatus.INITIALIZED) - val startWhenPrepared = (initialState != PlayerStatus.PREPARED) - psmp.playMediaObject(p, false, startWhenPrepared, prepareImmediately) - if (initialState == PlayerStatus.PAUSED) { - psmp.pause(false, false) - } - psmp.prepare() - } - - val res = countDownLatch.await(timeoutSeconds, TimeUnit.SECONDS) - if (initialState != PlayerStatus.INITIALIZED) { - Assert.assertEquals(initialState, psmp.playerInfo.playerStatus) - } - - if (assertionError != null) throw assertionError!! - Assert.assertTrue(res) - callback.cancel() - psmp.shutdown() - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testPrepareInitializedState() { - prepareTestSkeleton(PlayerStatus.INITIALIZED, LATCH_TIMEOUT_SECONDS.toLong()) - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testPreparePlayingState() { - prepareTestSkeleton(PlayerStatus.PLAYING, 1) - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testPreparePausedState() { - prepareTestSkeleton(PlayerStatus.PAUSED, 1) - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testPreparePreparedState() { - prepareTestSkeleton(PlayerStatus.PREPARED, 1) - } - - @Throws(InterruptedException::class) - private fun reinitTestSkeleton(initialState: PlayerStatus, timeoutSeconds: Long) { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val latchCount = 2 - val countDownLatch = CountDownLatch(latchCount) - val callback = CancelableMediaPlayerCallback(object : DefaultMediaPlayerCallback() { - override fun statusChanged(newInfo: MediaPlayerInfo?) { - checkPSMPInfo(newInfo) - if (newInfo!!.playerStatus == PlayerStatus.ERROR) { - if (assertionError == null) assertionError = UnexpectedStateChange(newInfo.playerStatus) - } else { - when { - newInfo.playerStatus == initialState -> { - countDownLatch.countDown() - } - countDownLatch.count < latchCount && newInfo.playerStatus == PlayerStatus.INITIALIZED -> { - countDownLatch.countDown() - } - } - } - } - }) - val psmp: MediaPlayerBase = LocalMediaPlayer(c, callback) - val p = writeTestPlayable(playableFileUrl, PLAYABLE_LOCAL_URL) - val prepareImmediately = initialState != PlayerStatus.INITIALIZED - val startImmediately = initialState != PlayerStatus.PREPARED - psmp.playMediaObject(p, false, startImmediately, prepareImmediately) - if (initialState == PlayerStatus.PAUSED) { - psmp.pause(false, false) - } - psmp.reinit() - val res = countDownLatch.await(timeoutSeconds, TimeUnit.SECONDS) - if (assertionError != null) throw assertionError!! - Assert.assertTrue(res) - callback.cancel() - psmp.shutdown() - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testReinitPlayingState() { - reinitTestSkeleton(PlayerStatus.PLAYING, LATCH_TIMEOUT_SECONDS.toLong()) - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testReinitPausedState() { - reinitTestSkeleton(PlayerStatus.PAUSED, LATCH_TIMEOUT_SECONDS.toLong()) - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testPreparedPlayingState() { - reinitTestSkeleton(PlayerStatus.PREPARED, LATCH_TIMEOUT_SECONDS.toLong()) - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testReinitInitializedState() { - reinitTestSkeleton(PlayerStatus.INITIALIZED, LATCH_TIMEOUT_SECONDS.toLong()) - } - - private class UnexpectedStateChange(status: PlayerStatus) : AssertionFailedError("Unexpected state change: $status") - - open class DefaultMediaPlayerCallback : MediaPlayerCallback { - override fun statusChanged(newInfo: MediaPlayerInfo?) {} - override fun shouldStop() {} - override fun onMediaChanged(reloadUI: Boolean) {} - override fun onPostPlayback(media: Playable?, ended: Boolean, skipped: Boolean, playingNext: Boolean) {} - override fun onPlaybackStart(playable: Playable, position: Int) {} - override fun onPlaybackPause(playable: Playable?, position: Int) {} - override fun getNextInQueue(currentMedia: Playable?): Playable? { - return null - } - override fun findMedia(url: String): Playable? { - return null - } - override fun onPlaybackEnded(mediaType: MediaType?, stopPlaying: Boolean) {} - override fun ensureMediaInfoLoaded(media: Playable) {} - } - - class CancelableMediaPlayerCallback(private val originalCallback: MediaPlayerCallback) : MediaPlayerCallback { - private var isCancelled = false - - fun cancel() { - isCancelled = true - } - override fun statusChanged(newInfo: MediaPlayerInfo?) { - if (isCancelled) return - originalCallback.statusChanged(newInfo) - } - override fun shouldStop() { - if (isCancelled) return -// originalCallback.shouldStop() - } - override fun onMediaChanged(reloadUI: Boolean) { - if (isCancelled) return - originalCallback.onMediaChanged(reloadUI) - } - override fun onPostPlayback(media: Playable?, ended: Boolean, skipped: Boolean, playingNext: Boolean) { - if (isCancelled) return - originalCallback.onPostPlayback(media, ended, skipped, playingNext) - } - override fun onPlaybackStart(playable: Playable, position: Int) { - if (isCancelled) return - originalCallback.onPlaybackStart(playable, position) - } - override fun onPlaybackPause(playable: Playable?, position: Int) { - if (isCancelled) return - originalCallback.onPlaybackPause(playable, position) - } - override fun getNextInQueue(currentMedia: Playable?): Playable? { - if (isCancelled) return null - return originalCallback.getNextInQueue(currentMedia) - } - override fun findMedia(url: String): Playable? { - if (isCancelled) return null - return originalCallback.findMedia(url) - } - override fun onPlaybackEnded(mediaType: MediaType?, stopPlaying: Boolean) { - if (isCancelled) return - originalCallback.onPlaybackEnded(mediaType, stopPlaying) - } - override fun ensureMediaInfoLoaded(media: Playable) { - if (isCancelled) return - originalCallback.ensureMediaInfoLoaded(media) - } - } - - companion object { - private const val PLAYABLE_DEST_URL = "psmptestfile.mp3" - private const val LATCH_TIMEOUT_SECONDS = 3 - } -} diff --git a/app/src/androidTest/kotlin/ac/test/podcini/service/playback/SleepTimerPreferencesTest.kt b/app/src/androidTest/kotlin/ac/test/podcini/service/playback/SleepTimerPreferencesTest.kt deleted file mode 100644 index 8dd62b0f..00000000 --- a/app/src/androidTest/kotlin/ac/test/podcini/service/playback/SleepTimerPreferencesTest.kt +++ /dev/null @@ -1,19 +0,0 @@ -package de.test.podcini.service.playback - -import ac.mdiq.podcini.preferences.SleepTimerPreferences.isInTimeRange -import org.junit.Assert -import org.junit.Test - -class SleepTimerPreferencesTest { - @Test - fun testIsInTimeRange() { - Assert.assertTrue(isInTimeRange(0, 10, 8)) - Assert.assertTrue(isInTimeRange(1, 10, 8)) - Assert.assertTrue(isInTimeRange(1, 10, 1)) - Assert.assertTrue(isInTimeRange(20, 10, 8)) - Assert.assertTrue(isInTimeRange(20, 20, 8)) - Assert.assertFalse(isInTimeRange(1, 6, 8)) - Assert.assertFalse(isInTimeRange(1, 6, 6)) - Assert.assertFalse(isInTimeRange(20, 6, 8)) - } -} diff --git a/app/src/androidTest/kotlin/ac/test/podcini/service/playback/TaskManagerTest.kt b/app/src/androidTest/kotlin/ac/test/podcini/service/playback/TaskManagerTest.kt deleted file mode 100644 index b048fef6..00000000 --- a/app/src/androidTest/kotlin/ac/test/podcini/service/playback/TaskManagerTest.kt +++ /dev/null @@ -1,321 +0,0 @@ -package de.test.podcini.service.playback - -import ac.mdiq.podcini.playback.base.PlayerStatus -import ac.mdiq.podcini.playback.service.PlaybackService.TaskManager -import ac.mdiq.podcini.playback.service.PlaybackService.TaskManager.PSTMCallback -import ac.mdiq.podcini.preferences.SleepTimerPreferences.setShakeToReset -import ac.mdiq.podcini.preferences.SleepTimerPreferences.setVibrate -import ac.mdiq.podcini.storage.model.Feed -import ac.mdiq.podcini.storage.model.Episode -import ac.mdiq.podcini.storage.model.Playable -import ac.mdiq.podcini.ui.widget.WidgetUpdater.WidgetState -import ac.mdiq.podcini.util.EventFlow -import ac.mdiq.podcini.util.FlowEvent -import androidx.test.annotation.UiThreadTest -import androidx.test.filters.LargeTest -import androidx.test.platform.app.InstrumentationRegistry -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch -import org.junit.After -import org.junit.Assert -import org.junit.Before -import org.junit.Test -import java.util.* -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit - -/** - * Test class for PlaybackServiceTaskManager - */ -@LargeTest -class TaskManagerTest { - - val scope = CoroutineScope(Dispatchers.Main) - - @After - fun tearDown() { -// deleteDatabase() - } - - @Before - fun setUp() { - // create new database - val context = InstrumentationRegistry.getInstrumentation().targetContext -// init(context) -// deleteDatabase() -// val adapter = getInstance() -// adapter.open() -// adapter.close() - setShakeToReset(false) - setVibrate(false) - } - - @Test - fun testInit() { - val context = InstrumentationRegistry.getInstrumentation().targetContext - val pstm = TaskManager(context, defaultPSTM) - pstm.shutdown() - } - - private fun writeTestQueue(pref: String): List? { - val NUM_ITEMS = 10 - val f = Feed(0, null, "title", "link", "d", null, null, null, null, "id", null, "null", "url") - f.episodes.clear() - for (i in 0 until NUM_ITEMS) { - f.episodes.add(Episode(0, pref + i, pref + i, "link", Date(), PlayState.PLAYED.code, f)) - } -// val adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(f) -// adapter.setQueue(f.items) -// adapter.close() - - for (item in f.episodes) { - Assert.assertTrue(item.id != 0L) - } - return f.episodes - } - - @Test - @Throws(InterruptedException::class) - fun testStartPositionSaver() { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val NUM_COUNTDOWNS = 2 - val TIMEOUT = 3 * TaskManager.POSITION_SAVER_WAITING_INTERVAL - val countDownLatch = CountDownLatch(NUM_COUNTDOWNS) - val pstm = TaskManager(c, object : PSTMCallback { - override fun positionSaverTick() { - countDownLatch.countDown() - } - - override fun requestWidgetState(): WidgetState { - return WidgetState(PlayerStatus.PREPARING) - } - - override fun onChapterLoaded(media: Playable?) { - } - }) - pstm.startPositionSaver() - countDownLatch.await(TIMEOUT.toLong(), TimeUnit.MILLISECONDS) - pstm.shutdown() - } - - @Test - fun testIsPositionSaverActive() { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val pstm = TaskManager(c, defaultPSTM) - pstm.startPositionSaver() - Assert.assertTrue(pstm.isPositionSaverActive) - pstm.shutdown() - } - - @Test - fun testCancelPositionSaver() { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val pstm = TaskManager(c, defaultPSTM) - pstm.startPositionSaver() - pstm.cancelPositionSaver() - Assert.assertFalse(pstm.isPositionSaverActive) - pstm.shutdown() - } - - @Test - @Throws(InterruptedException::class) - fun testStartWidgetUpdater() { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val NUM_COUNTDOWNS = 2 - val TIMEOUT = 3 * TaskManager.WIDGET_UPDATER_NOTIFICATION_INTERVAL - val countDownLatch = CountDownLatch(NUM_COUNTDOWNS) - val pstm = TaskManager(c, object : PSTMCallback { - override fun positionSaverTick() { - } - - override fun requestWidgetState(): WidgetState { - countDownLatch.countDown() - return WidgetState(PlayerStatus.PREPARING) - } - - override fun onChapterLoaded(media: Playable?) { - } - }) - pstm.startWidgetUpdater() - countDownLatch.await(TIMEOUT.toLong(), TimeUnit.MILLISECONDS) - pstm.shutdown() - } - - @Test - fun testStartWidgetUpdaterAfterShutdown() { - // Should not throw. - val c = InstrumentationRegistry.getInstrumentation().targetContext - val pstm = TaskManager(c, defaultPSTM) - pstm.shutdown() - pstm.startWidgetUpdater() - } - - @Test - fun testIsWidgetUpdaterActive() { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val pstm = TaskManager(c, defaultPSTM) - pstm.startWidgetUpdater() - Assert.assertTrue(pstm.isWidgetUpdaterActive) - pstm.shutdown() - } - - @Test - fun testCancelWidgetUpdater() { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val pstm = TaskManager(c, defaultPSTM) - pstm.startWidgetUpdater() - pstm.cancelWidgetUpdater() - Assert.assertFalse(pstm.isWidgetUpdaterActive) - pstm.shutdown() - } - - @Test - fun testCancelAllTasksNoTasksStarted() { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val pstm = TaskManager(c, defaultPSTM) - pstm.cancelAllTasks() - Assert.assertFalse(pstm.isPositionSaverActive) - Assert.assertFalse(pstm.isWidgetUpdaterActive) - Assert.assertFalse(pstm.isSleepTimerActive) - pstm.shutdown() - } - - @Test - @UiThreadTest - fun testCancelAllTasksAllTasksStarted() { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val pstm = TaskManager(c, defaultPSTM) - pstm.startWidgetUpdater() - pstm.startPositionSaver() - pstm.setSleepTimer(100000) - pstm.cancelAllTasks() - Assert.assertFalse(pstm.isPositionSaverActive) - Assert.assertFalse(pstm.isWidgetUpdaterActive) - Assert.assertFalse(pstm.isSleepTimerActive) - pstm.shutdown() - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testSetSleepTimer() { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val TIME: Long = 2000 - val TIMEOUT = 2 * TIME - val countDownLatch = CountDownLatch(1) - val timerReceiver: Any = object : Any() { - private var eventSink: Job? = null - private fun cancelFlowEvents() { - eventSink?.cancel() - eventSink = null - } - private fun procFlowEvents() { - if (eventSink != null) return - eventSink = scope.launch { - EventFlow.events.collectLatest { event -> - when (event) { - is FlowEvent.SleepTimerUpdatedEvent -> sleepTimerUpdate(event) - else -> {} - } - } - } - } - - fun sleepTimerUpdate(event: FlowEvent.SleepTimerUpdatedEvent?) { - if (countDownLatch.count == 0L) { - Assert.fail() - } - countDownLatch.countDown() - } - } -// EventBus.getDefault().register(timerReceiver) - val pstm = TaskManager(c, defaultPSTM) - pstm.setSleepTimer(TIME) - countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS) -// EventBus.getDefault().unregister(timerReceiver) - pstm.shutdown() - } - - @Test - @UiThreadTest - @Throws(InterruptedException::class) - fun testDisableSleepTimer() { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val TIME: Long = 5000 - val TIMEOUT = 2 * TIME - val countDownLatch = CountDownLatch(1) - val timerReceiver: Any = object : Any() { - private var eventSink: Job? = null - private fun cancelFlowEvents() { - eventSink?.cancel() - eventSink = null - } - private fun procFlowEvents() { - if (eventSink != null) return - eventSink = scope.launch { - EventFlow.events.collectLatest { event -> - when (event) { - is FlowEvent.SleepTimerUpdatedEvent -> sleepTimerUpdate(event) - else -> {} - } - } - } - } - fun sleepTimerUpdate(event: FlowEvent.SleepTimerUpdatedEvent) { - when { - event.isOver -> { - countDownLatch.countDown() - } - event.getTimeLeft() == 1L -> { - Assert.fail("Arrived at 1 but should have been cancelled") - } - } - } - } - val pstm = TaskManager(c, defaultPSTM) -// EventBus.getDefault().register(timerReceiver) - pstm.setSleepTimer(TIME) - pstm.disableSleepTimer() - Assert.assertFalse(countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) - pstm.shutdown() -// EventBus.getDefault().unregister(timerReceiver) - } - - @Test - @UiThreadTest - fun testIsSleepTimerActivePositive() { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val pstm = TaskManager(c, defaultPSTM) - pstm.setSleepTimer(1000) - Assert.assertTrue(pstm.isSleepTimerActive) - pstm.shutdown() - } - - @Test - @UiThreadTest - fun testIsSleepTimerActiveNegative() { - val c = InstrumentationRegistry.getInstrumentation().targetContext - val pstm = TaskManager(c, defaultPSTM) - pstm.setSleepTimer(10000) - pstm.disableSleepTimer() - Assert.assertFalse(pstm.isSleepTimerActive) - pstm.shutdown() - } - - private val defaultPSTM: PSTMCallback = object : PSTMCallback { - override fun positionSaverTick() { - } - - override fun requestWidgetState(): WidgetState { - return WidgetState(PlayerStatus.PREPARING) - } - - override fun onChapterLoaded(media: Playable?) { - } - } -} diff --git a/app/src/androidTest/kotlin/ac/test/podcini/storage/AutoDownloadTest.kt b/app/src/androidTest/kotlin/ac/test/podcini/storage/AutoDownloadTest.kt deleted file mode 100644 index 82dd7733..00000000 --- a/app/src/androidTest/kotlin/ac/test/podcini/storage/AutoDownloadTest.kt +++ /dev/null @@ -1,118 +0,0 @@ -package de.test.podcini.storage - -import ac.mdiq.podcini.net.utils.NetworkUtils.isAllowMobileStreaming -import ac.mdiq.podcini.playback.PlaybackServiceStarter -import ac.mdiq.podcini.playback.base.InTheatre.curQueue -import ac.mdiq.podcini.playback.service.PlaybackService.Companion.isFollowQueue -import ac.mdiq.podcini.storage.algorithms.AutoDownloads -import ac.mdiq.podcini.storage.algorithms.AutoDownloads.downloadAlgorithm -import ac.mdiq.podcini.storage.model.Episode -import ac.mdiq.podcini.storage.model.Feed -import android.content.Context -import androidx.test.core.app.ApplicationProvider -import de.test.podcini.EspressoTestUtils -import de.test.podcini.ui.UITestUtils -import org.awaitility.Awaitility -import org.awaitility.core.ConditionTimeoutException -import org.junit.After -import org.junit.Assert -import org.junit.Before -import org.junit.Test -import java.util.concurrent.TimeUnit - -class AutoDownloadTest { - private var context: Context? = null - private var stubFeedsServer: UITestUtils? = null - private var stubDownloadAlgorithm: StubDownloadAlgorithm? = null - - @Before - @Throws(Exception::class) - fun setUp() { - context = ApplicationProvider.getApplicationContext() - - stubFeedsServer = UITestUtils(context!!) - stubFeedsServer!!.setup() - - EspressoTestUtils.clearPreferences() - EspressoTestUtils.clearDatabase() - isAllowMobileStreaming = true - - // Setup: enable automatic download - // it is not needed, as the actual automatic download is stubbed. - stubDownloadAlgorithm = StubDownloadAlgorithm() - downloadAlgorithm = stubDownloadAlgorithm!! - } - - @After - @Throws(Exception::class) - fun tearDown() { - downloadAlgorithm = AutoDownloads.AutoDownloadAlgorithm() - EspressoTestUtils.tryKillPlaybackService() - stubFeedsServer!!.tearDown() - } - - /** - * A cross-functional test, ensuring playback's behavior works with Auto Download in boundary condition. - * Scenario: - * - For setting enqueue location AFTER_CURRENTLY_PLAYING - * - when playback of an episode is complete and the app advances to the next episode (continuous playback on) - * - when automatic download kicks in, - * - ensure the next episode is the current playing one, needed for AFTER_CURRENTLY_PLAYING enqueue location. - */ - @Test - @Throws(Exception::class) - fun downloadsEnqueuedToAfterCurrent_CurrentAdvancedToNextOnPlaybackComplete() { - isFollowQueue = true // continuous playback - - // Setup: feeds and queue - // downloads 3 of them, leave some in new state (auto-downloadable) - stubFeedsServer!!.addLocalFeedData(false) - val queue = curQueue.episodes - Assert.assertTrue(queue.size > 1) - val item0 = queue[0] - val item1 = queue[1] - - // Actual test - // Play the first one in the queue - playEpisode(item0) - - try { - // when playback is complete, advances to the next one, and auto download kicks in, - // ensure that currently playing has been advanced to the next one by this point. - Awaitility.await("advanced to the next episode") - .atMost(6000, TimeUnit.MILLISECONDS) // the test mp3 media is 3-second long. twice should be enough - .until { item1.media!!.id == stubDownloadAlgorithm?.currentlyPlayingAtDownload } - } catch (cte: ConditionTimeoutException) { - val actual: Long = stubDownloadAlgorithm?.currentlyPlayingAtDownload?:0 - Assert.fail("when auto download is triggered, the next episode should be playing: (" - + item1.id + ", " + item1.title + ") . " - + "Actual playing: (" + actual + ")" - ) - } - } - - private fun playEpisode(item: Episode) { - val media = item.media - PlaybackServiceStarter(context!!, media!!) - .callEvenIfRunning(true) - .start() -// Awaitility.await("episode is playing") -// .atMost(2000, TimeUnit.MILLISECONDS) -// .until { item.media!!.id == currentlyPlayingFeedMediaId } - } - - private class StubDownloadAlgorithm : AutoDownloads.AutoDownloadAlgorithm() { - var currentlyPlayingAtDownload: Long = -1 - private set - - override fun autoDownloadEpisodeMedia(context: Context, feeds: List?): Runnable { - return Runnable { - if (currentlyPlayingAtDownload == -1L) { -// currentlyPlayingAtDownload = currentlyPlayingFeedMediaId - } else { - throw AssertionError("Stub automatic download should be invoked once and only once") - } - } - } - } -} diff --git a/app/src/androidTest/kotlin/ac/test/podcini/ui/FeedSettingsTest.kt b/app/src/androidTest/kotlin/ac/test/podcini/ui/FeedSettingsTest.kt deleted file mode 100644 index 864255ae..00000000 --- a/app/src/androidTest/kotlin/ac/test/podcini/ui/FeedSettingsTest.kt +++ /dev/null @@ -1,76 +0,0 @@ -package de.test.podcini.ui - -import android.content.Intent -import androidx.test.espresso.Espresso -import androidx.test.espresso.action.ViewActions -import androidx.test.espresso.intent.rule.IntentsTestRule -import androidx.test.espresso.matcher.ViewMatchers -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import ac.mdiq.podcini.R -import ac.mdiq.podcini.ui.activity.MainActivity -import ac.mdiq.podcini.storage.model.Feed -import de.test.podcini.EspressoTestUtils -import org.hamcrest.Matchers -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - - -@RunWith(AndroidJUnit4::class) -class FeedSettingsTest { - private var uiTestUtils: UITestUtils? = null - private var feed: Feed? = null - - @Rule - var activityRule: IntentsTestRule = IntentsTestRule(MainActivity::class.java, false, false) - - @Before - @Throws(Exception::class) - fun setUp() { - uiTestUtils = UITestUtils(InstrumentationRegistry.getInstrumentation().targetContext) - uiTestUtils!!.setup() - - EspressoTestUtils.clearPreferences() - EspressoTestUtils.clearDatabase() - - uiTestUtils!!.addLocalFeedData(false) - feed = uiTestUtils!!.hostedFeeds[0] - val intent = Intent(InstrumentationRegistry.getInstrumentation().targetContext, MainActivity::class.java) - intent.putExtra(MainActivity.Extras.fragment_feed_id.name, feed!!.id) - activityRule.launchActivity(intent) - } - - @After - @Throws(Exception::class) - fun tearDown() { - uiTestUtils!!.tearDown() - } - - @Test - fun testClickFeedSettings() { - Espresso.onView(ViewMatchers.isRoot()) - .perform(EspressoTestUtils.waitForView(Matchers.allOf(ViewMatchers.isDescendantOfA(ViewMatchers.withId(R.id.appBar)), - ViewMatchers.withText(feed!!.title), ViewMatchers.isDisplayed()), 1000)) - Espresso.onView(ViewMatchers.withId(R.id.butShowSettings)).perform(ViewActions.click()) - - EspressoTestUtils.clickPreference(R.string.keep_updated) - - EspressoTestUtils.clickPreference(R.string.authentication_label) - Espresso.onView(ViewMatchers.withText(R.string.cancel_label)).perform(ViewActions.click()) - - EspressoTestUtils.clickPreference(R.string.playback_speed) - Espresso.onView(ViewMatchers.withText(R.string.cancel_label)).perform(ViewActions.click()) - - EspressoTestUtils.clickPreference(R.string.pref_feed_skip) - Espresso.onView(ViewMatchers.withText(R.string.cancel_label)).perform(ViewActions.click()) - - EspressoTestUtils.clickPreference(R.string.auto_delete_label) - Espresso.onView(ViewMatchers.withText(R.string.cancel_label)).perform(ViewActions.click()) - - EspressoTestUtils.clickPreference(R.string.feed_volume_adapdation) - Espresso.onView(ViewMatchers.withText(R.string.cancel_label)).perform(ViewActions.click()) - } -} diff --git a/app/src/androidTest/kotlin/ac/test/podcini/ui/MainActivityTest.kt b/app/src/androidTest/kotlin/ac/test/podcini/ui/MainActivityTest.kt deleted file mode 100644 index f2cc4d46..00000000 --- a/app/src/androidTest/kotlin/ac/test/podcini/ui/MainActivityTest.kt +++ /dev/null @@ -1,70 +0,0 @@ -package de.test.podcini.ui - -import ac.mdiq.podcini.R -import ac.mdiq.podcini.ui.activity.MainActivity -import android.content.Intent -import androidx.test.espresso.Espresso -import androidx.test.espresso.action.ViewActions -import androidx.test.espresso.intent.rule.IntentsTestRule -import androidx.test.espresso.matcher.ViewMatchers -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import de.test.podcini.EspressoTestUtils -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import java.io.IOException - -/** - * User interface tests for MainActivity. - */ -@RunWith(AndroidJUnit4::class) -class MainActivityTest { - private var uiTestUtils: UITestUtils? = null - - @Rule - var activityRule: IntentsTestRule = IntentsTestRule(MainActivity::class.java, false, false) - - @Before - @Throws(IOException::class) - fun setUp() { - EspressoTestUtils.clearPreferences() - EspressoTestUtils.clearDatabase() - - activityRule.launchActivity(Intent()) - - uiTestUtils = UITestUtils(InstrumentationRegistry.getInstrumentation().targetContext) - uiTestUtils!!.setup() - } - - @After - @Throws(Exception::class) - fun tearDown() { - uiTestUtils!!.tearDown() -// deleteDatabase() - } - - @Test - @Throws(Exception::class) - fun testAddFeed() { - // connect to podcast feed - uiTestUtils!!.addHostedFeedData() - val feed = uiTestUtils!!.hostedFeeds[0] - EspressoTestUtils.openNavDrawer() - Espresso.onView(ViewMatchers.withText(R.string.add_feed_label)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withId(R.id.addViaUrlButton)).perform(ViewActions.scrollTo(), ViewActions.click()) - Espresso.onView(ViewMatchers.withId(R.id.editText)).perform(ViewActions.replaceText(feed.downloadUrl!!)) - Espresso.onView(ViewMatchers.withText(R.string.confirm_label)) - .perform(ViewActions.scrollTo(), ViewActions.click()) - - // subscribe podcast - Espresso.closeSoftKeyboard() - EspressoTestUtils.waitForViewGlobally(ViewMatchers.withText(R.string.subscribe_label), 15000) - Espresso.onView(ViewMatchers.withText(R.string.subscribe_label)).perform(ViewActions.click()) - - // wait for podcast feed item list - EspressoTestUtils.waitForViewGlobally(ViewMatchers.withId(R.id.butShowSettings), 15000) - } -} diff --git a/app/src/androidTest/kotlin/ac/test/podcini/ui/NavigationDrawerTest.kt b/app/src/androidTest/kotlin/ac/test/podcini/ui/NavigationDrawerTest.kt deleted file mode 100644 index 31567b5b..00000000 --- a/app/src/androidTest/kotlin/ac/test/podcini/ui/NavigationDrawerTest.kt +++ /dev/null @@ -1,219 +0,0 @@ -package de.test.podcini.ui - -import android.content.Intent -import androidx.test.espresso.Espresso -import androidx.test.espresso.action.ViewActions -import androidx.test.espresso.contrib.DrawerActions -import androidx.test.espresso.intent.Intents -import androidx.test.espresso.intent.matcher.IntentMatchers -import androidx.test.espresso.intent.rule.IntentsTestRule -import androidx.test.espresso.matcher.ViewMatchers -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import ac.mdiq.podcini.R -import ac.mdiq.podcini.ui.activity.MainActivity -import ac.mdiq.podcini.ui.activity.PreferenceActivity -import ac.mdiq.podcini.ui.fragment.* -import ac.mdiq.podcini.preferences.UserPreferences.hiddenDrawerItems -import ac.mdiq.podcini.ui.fragment.NavDrawerFragment.Companion.navMap -import de.test.podcini.EspressoTestUtils -import de.test.podcini.NthMatcher -import org.hamcrest.Matchers -import org.junit.* -import org.junit.runner.RunWith - -import java.io.IOException -import java.util.* - -/** - * User interface tests for MainActivity drawer. - */ -@RunWith(AndroidJUnit4::class) -class NavigationDrawerTest { - private var uiTestUtils: UITestUtils? = null - - @Rule - var activityRule: IntentsTestRule = IntentsTestRule(MainActivity::class.java, false, false) - - @Before - @Throws(IOException::class) - fun setUp() { - uiTestUtils = UITestUtils(InstrumentationRegistry.getInstrumentation().targetContext) - uiTestUtils!!.setup() - - EspressoTestUtils.clearPreferences() - EspressoTestUtils.clearDatabase() - } - - @After - @Throws(Exception::class) - fun tearDown() { - uiTestUtils!!.tearDown() - } - - private fun openNavDrawer() { - Espresso.onView(ViewMatchers.isRoot()) - .perform(EspressoTestUtils.waitForView(ViewMatchers.withId(R.id.main_layout), 1000)) - Espresso.onView(ViewMatchers.withId(R.id.main_layout)).perform(DrawerActions.open()) - } - - @Test - @Throws(Exception::class) - fun testClickNavDrawer() { - uiTestUtils!!.addLocalFeedData(false) - hiddenDrawerItems = ArrayList() - activityRule.launchActivity(Intent()) - - // queue - openNavDrawer() - EspressoTestUtils.onDrawerItem(ViewMatchers.withText(R.string.queue_label)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.isRoot()) - .perform(EspressoTestUtils.waitForView(Matchers.allOf(ViewMatchers.isDescendantOfA(ViewMatchers.withId(R.id.toolbar)), - ViewMatchers.withText(R.string.queue_label)), 1000)) - - // Inbox -// openNavDrawer() -// EspressoTestUtils.onDrawerItem(ViewMatchers.withText(R.string.inbox_label)).perform(ViewActions.click()) -// Espresso.onView(ViewMatchers.isRoot()) -// .perform(EspressoTestUtils.waitForView(Matchers.allOf(ViewMatchers.isDescendantOfA(ViewMatchers.withId(R.id.toolbar)), -// ViewMatchers.withText(R.string.inbox_label)), 1000)) - - // episodes - openNavDrawer() - EspressoTestUtils.onDrawerItem(ViewMatchers.withText(R.string.episodes_label)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.isRoot()) - .perform(EspressoTestUtils.waitForView(Matchers.allOf(ViewMatchers.isDescendantOfA(ViewMatchers.withId(R.id.toolbar)), - ViewMatchers.withText(R.string.episodes_label), ViewMatchers.isDisplayed()), 1000)) - - // Subscriptions - openNavDrawer() - EspressoTestUtils.onDrawerItem(ViewMatchers.withText(R.string.subscriptions_label)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.isRoot()) - .perform(EspressoTestUtils.waitForView(Matchers.allOf(ViewMatchers.isDescendantOfA(ViewMatchers.withId(R.id.toolbar)), - ViewMatchers.withText(R.string.subscriptions_label), ViewMatchers.isDisplayed()), 1000)) - - // downloads - openNavDrawer() - EspressoTestUtils.onDrawerItem(ViewMatchers.withText(R.string.downloads_label)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.isRoot()) - .perform(EspressoTestUtils.waitForView(Matchers.allOf(ViewMatchers.isDescendantOfA(ViewMatchers.withId(R.id.toolbar)), - ViewMatchers.withText(R.string.downloads_label), ViewMatchers.isDisplayed()), 1000)) - - // playback history - openNavDrawer() - EspressoTestUtils.onDrawerItem(ViewMatchers.withText(R.string.playback_history_label)) - .perform(ViewActions.click()) - Espresso.onView(ViewMatchers.isRoot()) - .perform(EspressoTestUtils.waitForView(Matchers.allOf(ViewMatchers.isDescendantOfA(ViewMatchers.withId(R.id.toolbar)), - ViewMatchers.withText(R.string.playback_history_label), ViewMatchers.isDisplayed()), 1000)) - - // add podcast - openNavDrawer() - Espresso.onView(ViewMatchers.withId(R.id.nav_list)).perform(ViewActions.swipeUp()) - EspressoTestUtils.onDrawerItem(ViewMatchers.withText(R.string.add_feed_label)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.isRoot()) - .perform(EspressoTestUtils.waitForView(Matchers.allOf(ViewMatchers.isDescendantOfA(ViewMatchers.withId(R.id.toolbar)), - ViewMatchers.withText(R.string.add_feed_label), ViewMatchers.isDisplayed()), 1000)) - - // podcasts - for (i in uiTestUtils!!.hostedFeeds.indices) { - val f = uiTestUtils!!.hostedFeeds[i] - openNavDrawer() - EspressoTestUtils.onDrawerItem(ViewMatchers.withText(f.title)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.isRoot()) - .perform(EspressoTestUtils.waitForView(Matchers.allOf(ViewMatchers.isDescendantOfA(ViewMatchers.withId(R.id.appBar)), - ViewMatchers.withText(f.title), ViewMatchers.isDisplayed()), 1000)) - } - } - - @Test - fun testGoToPreferences() { - activityRule.launchActivity(Intent()) - openNavDrawer() - Espresso.onView(ViewMatchers.withText(R.string.settings_label)).perform(ViewActions.click()) - Intents.intended(IntentMatchers.hasComponent(PreferenceActivity::class.java.name)) - } - - @Test - fun testDrawerPreferencesHideSomeElements() { - hiddenDrawerItems = ArrayList() - activityRule.launchActivity(Intent()) - openNavDrawer() - EspressoTestUtils.onDrawerItem(ViewMatchers.withText(R.string.queue_label)).perform(ViewActions.longClick()) - Espresso.onView(ViewMatchers.withText(R.string.episodes_label)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withId(androidx.appcompat.R.id.contentPanel)).perform(ViewActions.swipeUp()) - Espresso.onView(ViewMatchers.withText(R.string.playback_history_label)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withText(R.string.confirm_label)).perform(ViewActions.click()) - - val hidden = hiddenDrawerItems - Assert.assertEquals(2, hidden!!.size.toLong()) - Assert.assertTrue(hidden.contains(AllEpisodesFragment.TAG)) - Assert.assertTrue(hidden.contains(HistoryFragment.TAG)) - } - - @Test - fun testDrawerPreferencesUnhideSomeElements() { - var hidden = listOf(HistoryFragment.TAG, DownloadsFragment.TAG) - hiddenDrawerItems = hidden - activityRule.launchActivity(Intent()) - openNavDrawer() - Espresso.onView(NthMatcher.first(ViewMatchers.withText(R.string.queue_label))).perform(ViewActions.longClick()) - - Espresso.onView(ViewMatchers.withText(R.string.queue_label)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withId(androidx.appcompat.R.id.contentPanel)).perform(ViewActions.swipeUp()) - Espresso.onView(ViewMatchers.withText(R.string.downloads_label)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withText(R.string.confirm_label)).perform(ViewActions.click()) - - hidden = hiddenDrawerItems?.filterNotNull()?: listOf() - Assert.assertEquals(2, hidden.size.toLong()) - Assert.assertTrue(hidden.contains(QueuesFragment.TAG)) - Assert.assertTrue(hidden.contains(HistoryFragment.TAG)) - } - - - @Test - fun testDrawerPreferencesHideAllElements() { - hiddenDrawerItems = ArrayList() - activityRule.launchActivity(Intent()) -// val titles = activityRule.activity.resources.getStringArray(R.array.nav_drawer_titles) - val titles = navMap.keys.toTypedArray() - - openNavDrawer() - Espresso.onView(NthMatcher.first(ViewMatchers.withText(R.string.queue_label))).perform(ViewActions.longClick()) - for (i in titles.indices) { - val title = titles[i] - Espresso.onView(Matchers.allOf(ViewMatchers.withText(title), ViewMatchers.isDisplayed())) - .perform(ViewActions.click()) - - if (i == 3) { - Espresso.onView(ViewMatchers.withId(androidx.appcompat.R.id.contentPanel)).perform(ViewActions.swipeUp()) - } - } - - Espresso.onView(ViewMatchers.withText(R.string.confirm_label)).perform(ViewActions.click()) - - val hidden = hiddenDrawerItems - Assert.assertEquals(titles.size.toLong(), hidden!!.size.toLong()) - for (tag in NavDrawerFragment.navMap.keys) { - Assert.assertTrue(hidden.contains(tag)) - } - } - - @Test - fun testDrawerPreferencesHideCurrentElement() { - hiddenDrawerItems = ArrayList() - activityRule.launchActivity(Intent()) - openNavDrawer() - Espresso.onView(ViewMatchers.withText(R.string.downloads_label)).perform(ViewActions.click()) - openNavDrawer() - - Espresso.onView(NthMatcher.first(ViewMatchers.withText(R.string.queue_label))).perform(ViewActions.longClick()) - Espresso.onView(ViewMatchers.withId(androidx.appcompat.R.id.contentPanel)).perform(ViewActions.swipeUp()) - Espresso.onView(NthMatcher.first(ViewMatchers.withText(R.string.downloads_label))).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withText(R.string.confirm_label)).perform(ViewActions.click()) - - val hidden = hiddenDrawerItems - Assert.assertEquals(1, hidden!!.size.toLong()) - Assert.assertTrue(hidden.contains(DownloadsFragment.TAG)) - } -} diff --git a/app/src/androidTest/kotlin/ac/test/podcini/ui/PlayQueuesFragmentTest.kt b/app/src/androidTest/kotlin/ac/test/podcini/ui/PlayQueuesFragmentTest.kt deleted file mode 100644 index e693abd0..00000000 --- a/app/src/androidTest/kotlin/ac/test/podcini/ui/PlayQueuesFragmentTest.kt +++ /dev/null @@ -1,60 +0,0 @@ -package de.test.podcini.ui - -import android.content.Intent -import androidx.test.espresso.Espresso -import androidx.test.espresso.action.ViewActions -import androidx.test.espresso.intent.rule.IntentsTestRule -import androidx.test.espresso.matcher.ViewMatchers -import androidx.test.ext.junit.runners.AndroidJUnit4 -import ac.mdiq.podcini.R -import ac.mdiq.podcini.ui.activity.MainActivity -import ac.mdiq.podcini.ui.fragment.QueuesFragment -import de.test.podcini.EspressoTestUtils -import de.test.podcini.NthMatcher -import org.hamcrest.CoreMatchers -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - - -/** - * User interface tests for queue fragment. - */ -@RunWith(AndroidJUnit4::class) -class PlayQueuesFragmentTest { - @Rule - var activityRule: IntentsTestRule = IntentsTestRule(MainActivity::class.java, false, false) - - @Before - fun setUp() { - EspressoTestUtils.clearPreferences() - EspressoTestUtils.clearDatabase() - EspressoTestUtils.setLaunchScreen(QueuesFragment.TAG) - activityRule.launchActivity(Intent()) - } - - @Test - fun testLockEmptyQueue() { - Espresso.onView(NthMatcher.first(EspressoTestUtils.actionBarOverflow())).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withText(R.string.lock_queue)).perform(ViewActions.click()) - Espresso.onView(CoreMatchers.allOf(ViewMatchers.withClassName(CoreMatchers.endsWith("Button")), - ViewMatchers.withText(R.string.lock_queue))).perform(ViewActions.click()) - Espresso.onView(NthMatcher.first(EspressoTestUtils.actionBarOverflow())).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withText(R.string.lock_queue)).perform(ViewActions.click()) - } - - @Test - fun testSortEmptyQueue() { - Espresso.onView(NthMatcher.first(EspressoTestUtils.actionBarOverflow())).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withText(R.string.sort)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withText(R.string.random)).perform(ViewActions.click()) - } - - @Test - fun testKeepEmptyQueueSorted() { - Espresso.onView(NthMatcher.first(EspressoTestUtils.actionBarOverflow())).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withText(R.string.sort)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withText(R.string.keep_sorted)).perform(ViewActions.click()) - } -} diff --git a/app/src/androidTest/kotlin/ac/test/podcini/ui/PreferencesTest.kt b/app/src/androidTest/kotlin/ac/test/podcini/ui/PreferencesTest.kt deleted file mode 100644 index e0be914b..00000000 --- a/app/src/androidTest/kotlin/ac/test/podcini/ui/PreferencesTest.kt +++ /dev/null @@ -1,434 +0,0 @@ -package de.test.podcini.ui - -import android.content.Intent -import android.content.res.Resources -import androidx.annotation.StringRes -import androidx.preference.PreferenceManager -import androidx.test.espresso.Espresso -import androidx.test.espresso.action.ViewActions -import androidx.test.espresso.assertion.ViewAssertions -import androidx.test.espresso.matcher.ViewMatchers -import androidx.test.filters.LargeTest -import androidx.test.rule.ActivityTestRule -import ac.mdiq.podcini.R -import ac.mdiq.podcini.playback.service.PlaybackService -import ac.mdiq.podcini.playback.service.PlaybackService.Companion.isFollowQueue -import ac.mdiq.podcini.playback.service.PlaybackService.Companion.isPauseOnHeadsetDisconnect -import ac.mdiq.podcini.playback.service.PlaybackService.Companion.isPersistNotify -import ac.mdiq.podcini.playback.service.PlaybackService.Companion.isUnpauseOnBluetoothReconnect -import ac.mdiq.podcini.playback.service.PlaybackService.Companion.isUnpauseOnHeadsetReconnect -import ac.mdiq.podcini.ui.activity.PreferenceActivity -import ac.mdiq.podcini.storage.algorithms.AutoCleanups.APCleanupAlgorithm -import ac.mdiq.podcini.storage.algorithms.AutoCleanups.APNullCleanupAlgorithm -import ac.mdiq.podcini.storage.algorithms.AutoCleanups.APQueueCleanupAlgorithm -import ac.mdiq.podcini.storage.algorithms.AutoCleanups.build -import ac.mdiq.podcini.storage.algorithms.AutoCleanups.ExceptFavoriteCleanupAlgorithm -import ac.mdiq.podcini.preferences.UserPreferences -import ac.mdiq.podcini.preferences.UserPreferences.episodeCacheSize -import ac.mdiq.podcini.preferences.UserPreferences.fastForwardSecs -import ac.mdiq.podcini.preferences.UserPreferences.init -import ac.mdiq.podcini.preferences.UserPreferences.isAutoDelete -import ac.mdiq.podcini.preferences.UserPreferences.isAutoDeleteLocal -import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownload -import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownloadOnBattery -import ac.mdiq.podcini.preferences.UserPreferences.rewindSecs -import ac.mdiq.podcini.preferences.UserPreferences.shouldPauseForFocusLoss -import ac.mdiq.podcini.preferences.UserPreferences.showNextChapterOnFullNotification -import ac.mdiq.podcini.preferences.UserPreferences.showPlaybackSpeedOnFullNotification -import ac.mdiq.podcini.preferences.UserPreferences.showSkipOnFullNotification -import ac.mdiq.podcini.storage.database.Episodes.shouldDeleteRemoveFromQueue -import ac.mdiq.podcini.storage.database.Queues -import ac.mdiq.podcini.storage.database.Queues.enqueueLocation -import de.test.podcini.EspressoTestUtils -import org.awaitility.Awaitility -import org.junit.Assert -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import java.util.* -import java.util.concurrent.TimeUnit - -@LargeTest -class PreferencesTest { - private var res: Resources? = null - - @get:Rule - var activityTestRule: ActivityTestRule = ActivityTestRule( - PreferenceActivity::class.java, - false, - false) - - - @Before - fun setUp() { - EspressoTestUtils.clearDatabase() - EspressoTestUtils.clearPreferences() - activityTestRule.launchActivity(Intent()) - val prefs = PreferenceManager.getDefaultSharedPreferences(activityTestRule.activity) - prefs.edit().putBoolean(UserPreferences.Prefs.prefEnableAutoDl.name, true).commit() - - res = activityTestRule.activity.resources - init(activityTestRule.activity) - } - - @Test - fun testEnablePersistentPlaybackControls() { - val persistNotify = isPersistNotify - EspressoTestUtils.clickPreference(R.string.user_interface_label) - EspressoTestUtils.clickPreference(R.string.pref_persistNotify_title) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { persistNotify != isPersistNotify } - EspressoTestUtils.clickPreference(R.string.pref_persistNotify_title) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { persistNotify == isPersistNotify } - } - - @Test - fun testSetNotificationButtons() { - EspressoTestUtils.clickPreference(R.string.user_interface_label) - val buttons = res!!.getStringArray(R.array.full_notification_buttons_options) - EspressoTestUtils.clickPreference(R.string.pref_full_notification_buttons_title) - // First uncheck checkboxes - Espresso.onView(ViewMatchers.withText(buttons[1])).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withText(buttons[2])).perform(ViewActions.click()) - - Espresso.onView(ViewMatchers.withText(R.string.confirm_label)).perform(ViewActions.click()) - - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { showSkipOnFullNotification() } - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { showNextChapterOnFullNotification() } - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { !showPlaybackSpeedOnFullNotification() } - } - - @Test - fun testEnqueueLocation() { - EspressoTestUtils.clickPreference(R.string.playback_pref) - doTestEnqueueLocation(R.string.enqueue_location_after_current, Queues.EnqueueLocation.AFTER_CURRENTLY_PLAYING) - doTestEnqueueLocation(R.string.enqueue_location_front, Queues.EnqueueLocation.FRONT) - doTestEnqueueLocation(R.string.enqueue_location_back, Queues.EnqueueLocation.BACK) - doTestEnqueueLocation(R.string.enqueue_location_random, Queues.EnqueueLocation.RANDOM) - } - - private fun doTestEnqueueLocation(@StringRes optionResId: Int, expected: Queues.EnqueueLocation) { - EspressoTestUtils.clickPreference(R.string.pref_enqueue_location_title) - Espresso.onView(ViewMatchers.withText(optionResId)).perform(ViewActions.click()) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { expected == enqueueLocation } - } - - @Test - fun testHeadPhonesDisconnect() { - EspressoTestUtils.clickPreference(R.string.playback_pref) - val pauseOnHeadsetDisconnect = isPauseOnHeadsetDisconnect - Espresso.onView(ViewMatchers.withText(R.string.pref_pauseOnHeadsetDisconnect_title)) - .perform(ViewActions.click()) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { pauseOnHeadsetDisconnect != isPauseOnHeadsetDisconnect } - Espresso.onView(ViewMatchers.withText(R.string.pref_pauseOnHeadsetDisconnect_title)) - .perform(ViewActions.click()) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { pauseOnHeadsetDisconnect == isPauseOnHeadsetDisconnect } - } - - @Test - fun testHeadPhonesReconnect() { - EspressoTestUtils.clickPreference(R.string.playback_pref) - if (!isPauseOnHeadsetDisconnect) { - Espresso.onView(ViewMatchers.withText(R.string.pref_pauseOnHeadsetDisconnect_title)) - .perform(ViewActions.click()) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until(PlaybackService::isPauseOnHeadsetDisconnect) - } - val unpauseOnHeadsetReconnect = isUnpauseOnHeadsetReconnect - Espresso.onView(ViewMatchers.withText(R.string.pref_unpauseOnHeadsetReconnect_title)) - .perform(ViewActions.click()) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { unpauseOnHeadsetReconnect != isUnpauseOnHeadsetReconnect } - Espresso.onView(ViewMatchers.withText(R.string.pref_unpauseOnHeadsetReconnect_title)) - .perform(ViewActions.click()) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { unpauseOnHeadsetReconnect == isUnpauseOnHeadsetReconnect } - } - - @Test - fun testBluetoothReconnect() { - EspressoTestUtils.clickPreference(R.string.playback_pref) - if (!isPauseOnHeadsetDisconnect) { - Espresso.onView(ViewMatchers.withText(R.string.pref_pauseOnHeadsetDisconnect_title)) - .perform(ViewActions.click()) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until(PlaybackService::isPauseOnHeadsetDisconnect) - } - val unpauseOnBluetoothReconnect = isUnpauseOnBluetoothReconnect - Espresso.onView(ViewMatchers.withText(R.string.pref_unpauseOnBluetoothReconnect_title)) - .perform(ViewActions.click()) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { unpauseOnBluetoothReconnect != isUnpauseOnBluetoothReconnect } - Espresso.onView(ViewMatchers.withText(R.string.pref_unpauseOnBluetoothReconnect_title)) - .perform(ViewActions.click()) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { unpauseOnBluetoothReconnect == isUnpauseOnBluetoothReconnect } - } - - @Test - fun testContinuousPlayback() { - EspressoTestUtils.clickPreference(R.string.playback_pref) - val continuousPlayback = isFollowQueue - EspressoTestUtils.clickPreference(R.string.pref_followQueue_title) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { continuousPlayback != isFollowQueue } - EspressoTestUtils.clickPreference(R.string.pref_followQueue_title) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { continuousPlayback == isFollowQueue } - } - - @Test - fun testAutoDelete() { - EspressoTestUtils.clickPreference(R.string.downloads_pref) - val autoDelete = isAutoDelete - Espresso.onView(ViewMatchers.withText(R.string.pref_auto_delete_title)).perform(ViewActions.click()) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { autoDelete != isAutoDelete } - Espresso.onView(ViewMatchers.withText(R.string.pref_auto_delete_title)).perform(ViewActions.click()) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { autoDelete == isAutoDelete } - } - - @Test - fun testAutoDeleteLocal() { - EspressoTestUtils.clickPreference(R.string.downloads_pref) - val initialAutoDelete = isAutoDeleteLocal - Assert.assertFalse(initialAutoDelete) - - Espresso.onView(ViewMatchers.withText(R.string.pref_auto_local_delete_title)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withText(R.string.yes)).perform(ViewActions.click()) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { isAutoDeleteLocal } - - Espresso.onView(ViewMatchers.withText(R.string.pref_auto_local_delete_title)).perform(ViewActions.click()) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { !isAutoDeleteLocal } - } - - @Test - fun testPlaybackSpeeds() { - EspressoTestUtils.clickPreference(R.string.playback_pref) - EspressoTestUtils.clickPreference(R.string.playback_speed) - Espresso.onView(ViewMatchers.isRoot()) - .perform(EspressoTestUtils.waitForView(ViewMatchers.withText("1.25"), 1000)) - Espresso.onView(ViewMatchers.withText("1.25")).check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - } - - @Test - fun testPauseForInterruptions() { - EspressoTestUtils.clickPreference(R.string.playback_pref) - val pauseForFocusLoss = shouldPauseForFocusLoss() - EspressoTestUtils.clickPreference(R.string.pref_pausePlaybackForFocusLoss_title) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { pauseForFocusLoss != shouldPauseForFocusLoss() } - EspressoTestUtils.clickPreference(R.string.pref_pausePlaybackForFocusLoss_title) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { pauseForFocusLoss == shouldPauseForFocusLoss() } - } - - @Test - fun testSetEpisodeCache() { - val entries = res!!.getStringArray(R.array.episode_cache_size_entries) - val values = res!!.getStringArray(R.array.episode_cache_size_values) - val entry = entries[entries.size / 2] - val value = values[values.size / 2].toInt() - EspressoTestUtils.clickPreference(R.string.downloads_pref) - EspressoTestUtils.clickPreference(R.string.pref_automatic_download_title) - EspressoTestUtils.clickPreference(R.string.pref_episode_cache_title) - Espresso.onView(ViewMatchers.isRoot()) - .perform(EspressoTestUtils.waitForView(ViewMatchers.withText(entry), 1000)) - Espresso.onView(ViewMatchers.withText(entry)).perform(ViewActions.click()) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { episodeCacheSize == value } - } - - @Test - fun testSetEpisodeCacheMin() { - val entries = res!!.getStringArray(R.array.episode_cache_size_entries) - val values = res!!.getStringArray(R.array.episode_cache_size_values) - val minEntry = entries[0] - val minValue = values[0].toInt() - - EspressoTestUtils.clickPreference(R.string.downloads_pref) - EspressoTestUtils.clickPreference(R.string.pref_automatic_download_title) - EspressoTestUtils.clickPreference(R.string.pref_episode_cache_title) - Espresso.onView(ViewMatchers.withId(androidx.appcompat.R.id.select_dialog_listview)).perform(ViewActions.swipeDown()) - Espresso.onView(ViewMatchers.withText(minEntry)).perform(ViewActions.click()) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { episodeCacheSize == minValue } - } - - @Test - fun testSetEpisodeCacheMax() { - val entries = res!!.getStringArray(R.array.episode_cache_size_entries) - val values = res!!.getStringArray(R.array.episode_cache_size_values) - val maxEntry = entries[entries.size - 1] - val maxValue = values[values.size - 1].toInt() - Espresso.onView(ViewMatchers.withText(R.string.downloads_pref)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withText(R.string.pref_automatic_download_title)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withText(R.string.pref_episode_cache_title)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withId(androidx.appcompat.R.id.select_dialog_listview)).perform(ViewActions.swipeUp()) - Espresso.onView(ViewMatchers.withText(maxEntry)).perform(ViewActions.click()) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { episodeCacheSize == maxValue } - } - - @Test - fun testAutomaticDownload() { - val automaticDownload = isEnableAutodownload - EspressoTestUtils.clickPreference(R.string.downloads_pref) - EspressoTestUtils.clickPreference(R.string.pref_automatic_download_title) - EspressoTestUtils.clickPreference(R.string.pref_automatic_download_title) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { automaticDownload != isEnableAutodownload } - if (!isEnableAutodownload) { - EspressoTestUtils.clickPreference(R.string.pref_automatic_download_title) - } - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until(UserPreferences::isEnableAutodownload) - val enableAutodownloadOnBattery = isEnableAutodownloadOnBattery - EspressoTestUtils.clickPreference(R.string.pref_automatic_download_on_battery_title) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { enableAutodownloadOnBattery != isEnableAutodownloadOnBattery } - EspressoTestUtils.clickPreference(R.string.pref_automatic_download_on_battery_title) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { enableAutodownloadOnBattery == isEnableAutodownloadOnBattery } - } - - @Test - fun testEpisodeCleanupFavoriteOnly() { - EspressoTestUtils.clickPreference(R.string.downloads_pref) - Espresso.onView(ViewMatchers.withText(R.string.pref_automatic_download_title)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withText(R.string.pref_episode_cleanup_title)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withId(androidx.appcompat.R.id.select_dialog_listview)).perform(ViewActions.swipeDown()) - Espresso.onView(ViewMatchers.withText(R.string.episode_cleanup_except_favorite_removal)) - .perform(ViewActions.click()) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { build() is ExceptFavoriteCleanupAlgorithm } - } - - @Test - fun testEpisodeCleanupQueueOnly() { - EspressoTestUtils.clickPreference(R.string.downloads_pref) - Espresso.onView(ViewMatchers.withText(R.string.pref_automatic_download_title)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withText(R.string.pref_episode_cleanup_title)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withId(androidx.appcompat.R.id.select_dialog_listview)).perform(ViewActions.swipeDown()) - Espresso.onView(ViewMatchers.withText(R.string.episode_cleanup_queue_removal)).perform(ViewActions.click()) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { build() is APQueueCleanupAlgorithm } - } - - @Test - fun testEpisodeCleanupNeverAlg() { - EspressoTestUtils.clickPreference(R.string.downloads_pref) - Espresso.onView(ViewMatchers.withText(R.string.pref_automatic_download_title)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withText(R.string.pref_episode_cleanup_title)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withId(androidx.appcompat.R.id.select_dialog_listview)).perform(ViewActions.swipeUp()) - Espresso.onView(ViewMatchers.withText(R.string.episode_cleanup_never)).perform(ViewActions.click()) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { build() is APNullCleanupAlgorithm } - } - - @Test - fun testEpisodeCleanupClassic() { - EspressoTestUtils.clickPreference(R.string.downloads_pref) - Espresso.onView(ViewMatchers.withText(R.string.pref_automatic_download_title)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withText(R.string.pref_episode_cleanup_title)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withText(R.string.episode_cleanup_after_listening)).perform(ViewActions.click()) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { - val alg = build() - if (alg is APCleanupAlgorithm) { - return@until alg.numberOfHoursAfterPlayback == 0 - } - false - } - } - - @Test - fun testEpisodeCleanupNumDays() { - EspressoTestUtils.clickPreference(R.string.downloads_pref) - EspressoTestUtils.clickPreference(R.string.pref_automatic_download_title) - EspressoTestUtils.clickPreference(R.string.pref_episode_cleanup_title) - val search = res!!.getQuantityString(R.plurals.episode_cleanup_days_after_listening, 3, 3) - Espresso.onView(ViewMatchers.withText(search)).perform(ViewActions.scrollTo()) - Espresso.onView(ViewMatchers.withText(search)).perform(ViewActions.click()) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { - val alg = build() - if (alg is APCleanupAlgorithm) { - return@until alg.numberOfHoursAfterPlayback == 72 // 5 days - } - false - } - } - - @Test - fun testRewindChange() { - val seconds = rewindSecs - val deltas = res!!.getIntArray(R.array.seek_delta_values) - - EspressoTestUtils.clickPreference(R.string.playback_pref) - EspressoTestUtils.clickPreference(R.string.pref_rewind) - - val currentIndex = Arrays.binarySearch(deltas, seconds) - Assert.assertTrue(currentIndex >= 0 && currentIndex < deltas.size) // found? - - // Find next value (wrapping around to next) - val newIndex = (currentIndex + 1) % deltas.size - Espresso.onView(ViewMatchers.withText(deltas[newIndex].toString() + " seconds")).perform(ViewActions.click()) - - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { rewindSecs == deltas[newIndex] } - } - - @Test - fun testFastForwardChange() { - EspressoTestUtils.clickPreference(R.string.playback_pref) - for (i in 2 downTo 1) { // repeat twice to catch any error where fastforward is tracking rewind - val seconds = fastForwardSecs - val deltas = res!!.getIntArray(R.array.seek_delta_values) - - EspressoTestUtils.clickPreference(R.string.pref_fast_forward) - - val currentIndex = Arrays.binarySearch(deltas, seconds) - Assert.assertTrue(currentIndex >= 0 && currentIndex < deltas.size) // found? - - // Find next value (wrapping around to next) - val newIndex = (currentIndex + 1) % deltas.size - - Espresso.onView(ViewMatchers.withText(deltas[newIndex].toString() + " seconds")) - .perform(ViewActions.click()) - - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { fastForwardSecs == deltas[newIndex] } - } - } - - @Test - fun testDeleteRemovesFromQueue() { - EspressoTestUtils.clickPreference(R.string.downloads_pref) - if (!shouldDeleteRemoveFromQueue()) { - EspressoTestUtils.clickPreference(R.string.pref_delete_removes_from_queue_title) -// TODO: signature not correct, not sure what to do -// Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) -// .until(Callable { obj: UserPreferences -> obj.shouldDeleteRemoveFromQueue() }) - } - val deleteRemovesFromQueue = shouldDeleteRemoveFromQueue() - Espresso.onView(ViewMatchers.withText(R.string.pref_delete_removes_from_queue_title)) - .perform(ViewActions.click()) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { deleteRemovesFromQueue != shouldDeleteRemoveFromQueue() } - Espresso.onView(ViewMatchers.withText(R.string.pref_delete_removes_from_queue_title)) - .perform(ViewActions.click()) - Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS) - .until { deleteRemovesFromQueue == shouldDeleteRemoveFromQueue() } - } -} diff --git a/app/src/androidTest/kotlin/ac/test/podcini/ui/TextOnlyFeedsTest.kt b/app/src/androidTest/kotlin/ac/test/podcini/ui/TextOnlyFeedsTest.kt deleted file mode 100644 index 47fd5299..00000000 --- a/app/src/androidTest/kotlin/ac/test/podcini/ui/TextOnlyFeedsTest.kt +++ /dev/null @@ -1,67 +0,0 @@ -package de.test.podcini.ui - -import android.content.Intent -import androidx.test.espresso.Espresso -import androidx.test.espresso.action.ViewActions -import androidx.test.espresso.intent.rule.IntentsTestRule -import androidx.test.espresso.matcher.ViewMatchers -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import ac.mdiq.podcini.R -import ac.mdiq.podcini.ui.activity.MainActivity -import de.test.podcini.EspressoTestUtils -import org.hamcrest.CoreMatchers -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -import java.io.IOException - -/** - * Test UI for feeds that do not have media files - */ -@RunWith(AndroidJUnit4::class) -class TextOnlyFeedsTest { - private var uiTestUtils: UITestUtils? = null - - @Rule - var activityRule: IntentsTestRule = IntentsTestRule(MainActivity::class.java, false, false) - - @Before - @Throws(IOException::class) - fun setUp() { - EspressoTestUtils.clearPreferences() - EspressoTestUtils.clearDatabase() - - uiTestUtils = UITestUtils(InstrumentationRegistry.getInstrumentation().targetContext) - uiTestUtils!!.setHostTextOnlyFeeds(true) - uiTestUtils!!.setup() - - activityRule.launchActivity(Intent()) - } - - @After - @Throws(Exception::class) - fun tearDown() { - uiTestUtils!!.tearDown() - } - - @Test - @Throws(Exception::class) - fun testMarkAsPlayedList() { - uiTestUtils!!.addLocalFeedData(false) - val feed = uiTestUtils!!.hostedFeeds[0] - EspressoTestUtils.openNavDrawer() - Espresso.onView(ViewMatchers.withId(R.id.nav_list)).perform(ViewActions.swipeUp()) - EspressoTestUtils.onDrawerItem(ViewMatchers.withText(feed.title)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.withText(feed.episodes[0].title)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.isRoot()) - .perform(EspressoTestUtils.waitForView(ViewMatchers.withText(R.string.mark_read_no_media_label), 3000)) - Espresso.onView(ViewMatchers.withText(R.string.mark_read_no_media_label)).perform(ViewActions.click()) - Espresso.onView(ViewMatchers.isRoot()) - .perform(EspressoTestUtils.waitForView(CoreMatchers.allOf(ViewMatchers.withText(R.string.mark_read_no_media_label), - CoreMatchers.not(ViewMatchers.isDisplayed())), 3000)) - } -} diff --git a/app/src/androidTest/kotlin/ac/test/podcini/ui/UITestUtils.kt b/app/src/androidTest/kotlin/ac/test/podcini/ui/UITestUtils.kt deleted file mode 100644 index 9d776275..00000000 --- a/app/src/androidTest/kotlin/ac/test/podcini/ui/UITestUtils.kt +++ /dev/null @@ -1,207 +0,0 @@ -package de.test.podcini.ui - -import ac.mdiq.podcini.storage.model.Feed -import ac.mdiq.podcini.storage.model.Episode -import ac.mdiq.podcini.storage.model.EpisodeMedia -import ac.mdiq.podcini.storage.model.PlayState -import ac.mdiq.podcini.util.EventFlow -import ac.mdiq.podcini.util.FlowEvent -import android.content.Context -import android.util.Log -import androidx.test.platform.app.InstrumentationRegistry -import de.test.podcini.util.service.download.HTTPBin -import de.test.podcini.util.syndication.feedgenerator.Rss2Generator -import org.apache.commons.io.FileUtils -import org.apache.commons.io.IOUtils -import org.apache.commons.lang3.StringUtils -import org.junit.Assert -import java.io.File -import java.io.FileOutputStream -import java.io.IOException -import java.util.* - -/** - * Utility methods for UI tests. - * Starts a web server that hosts feeds, episodes and images. - */ -class UITestUtils(private val context: Context) { - private var testFileName = "3sec.mp3" - private var hostTextOnlyFeeds = false - private val server = HTTPBin() - private var destDir: File? = null - private var hostedFeedDir: File? = null - private var hostedMediaDir: File? = null - - val hostedFeeds: MutableList = ArrayList() - - @Throws(IOException::class) - fun setup() { - destDir = File(context.filesDir, "test/UITestUtils") - destDir!!.mkdirs() - hostedFeedDir = File(destDir, "hostedFeeds") - hostedFeedDir!!.mkdir() - hostedMediaDir = File(destDir, "hostedMediaDir") - hostedMediaDir!!.mkdir() - Assert.assertTrue(destDir!!.exists()) - Assert.assertTrue(hostedFeedDir!!.exists()) - Assert.assertTrue(hostedMediaDir!!.exists()) - server.start() - } - - @Throws(IOException::class) - fun tearDown() { - FileUtils.deleteDirectory(destDir) - FileUtils.deleteDirectory(hostedMediaDir) - FileUtils.deleteDirectory(hostedFeedDir) - server.stop() - - if (localFeedDataAdded) { -// deleteDatabase() - } - } - - @Throws(IOException::class) - fun hostFeed(feed: Feed): String { - val feedFile = File(hostedFeedDir, feed.title?:"") - val out = FileOutputStream(feedFile) - val generator = Rss2Generator() - generator.writeFeed(feed, out, "UTF-8", 0) - out.close() - val id = server.serveFile(feedFile) - Assert.assertTrue(id != -1) - return String.format(Locale.US, "%s/files/%d", server.baseUrl, id) - } - - private fun hostFile(file: File): String { - val id = server.serveFile(file) - Assert.assertTrue(id != -1) - return String.format(Locale.US, "%s/files/%d", server.baseUrl, id) - } - - @Throws(IOException::class) - private fun newMediaFile(name: String): File { - val mediaFile = File(hostedMediaDir, name) - if (mediaFile.exists()) { - mediaFile.delete() - } - Assert.assertFalse(mediaFile.exists()) - - val inVal = InstrumentationRegistry.getInstrumentation().context - .assets.open(testFileName) - Assert.assertNotNull(inVal) - - val out = FileOutputStream(mediaFile) - IOUtils.copy(inVal, out) - out.close() - - return mediaFile - } - - private var feedDataHosted = false - - /** - * Adds feeds, images and episodes to the webserver for testing purposes. - */ - @Throws(IOException::class) - fun addHostedFeedData() { - check(!feedDataHosted) { "addHostedFeedData was called twice on the same instance" } - for (i in 0 until NUM_FEEDS) { - val feed = Feed(0, null, "Title $i", "http://example.com/$i", "Description of feed $i", - "http://example.com/pay/feed$i", "author $i", "en", Feed.FeedType.RSS.name, "feed$i", null, null, - "http://example.com/feed/src/$i") - - // create items - val items: MutableList = ArrayList() - for (j in 0 until NUM_ITEMS_PER_FEED) { - val item = Episode(j.toLong(), "Feed " + (i + 1) + ": Item " + (j + 1), "item$j", - "http://example.com/feed$i/item/$j", Date(), PlayState.UNPLAYED.code, feed) - items.add(item) - - if (!hostTextOnlyFeeds) { - val mediaFile = newMediaFile("feed-$i-episode-$j.mp3") - item.setMedia(EpisodeMedia(j.toLong(), - item, - 0, - 0, - mediaFile.length(), - "audio/mp3", - null, - hostFile(mediaFile), - false, - null, - 0, - 0)) - } - } - feed.episodes.clear() - feed.episodes.addAll(items) - feed.downloadUrl = hostFeed(feed) - hostedFeeds.add(feed) - } - feedDataHosted = true - } - - - private var localFeedDataAdded = false - - - /** - * Adds feeds, images and episodes to the local database. This method will also call addHostedFeedData if it has not - * been called yet. - * Adds one item of each feed to the queue and to the playback history. - * This method should NOT be called if the testing class wants to download the hosted feed data. - * @param downloadEpisodes true if episodes should also be marked as downloaded. - */ - @Throws(Exception::class) - fun addLocalFeedData(downloadEpisodes: Boolean) { - if (localFeedDataAdded) { - Log.w(TAG, "addLocalFeedData was called twice on the same instance") - // might be a flaky test, this is actually not that severe - return - } - if (!feedDataHosted) addHostedFeedData() - - val queue: MutableList = ArrayList() - for (feed in hostedFeeds) { - if (downloadEpisodes) { - for (item in feed.episodes) { - if (item.media != null) { - val media = item.media - val fileId = StringUtils.substringAfter(media!!.downloadUrl, "files/").toInt() - media.fileUrl = (server.accessFile(fileId)?.absolutePath) - media.downloaded = (true) - } - } - } - - queue.add(feed.episodes[0]) - if (feed.episodes[1].media != null) { - feed.episodes[1].media!!.playbackCompletionDate = Date() - } - } - localFeedDataAdded = true - -// val adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(*hostedFeeds.toTypedArray()) -// adapter.setQueue(queue) -// adapter.close() -// EventFlow.postEvent(FlowEvent.FeedListEvent(FlowEvent.FeedListEvent.Action.UNKNOWN, hostedFeeds)) - EventFlow.postEvent(FlowEvent.QueueEvent.setQueue(queue)) - } - - fun setMediaFileName(filename: String) { - testFileName = filename - } - - fun setHostTextOnlyFeeds(hostTextOnlyFeeds: Boolean) { - this.hostTextOnlyFeeds = hostTextOnlyFeeds - } - - companion object { - private val TAG: String = UITestUtils::class.simpleName ?: "Anonymous" - - private const val NUM_FEEDS = 5 - private const val NUM_ITEMS_PER_FEED = 10 - } -} diff --git a/app/src/androidTest/kotlin/ac/test/podcini/ui/UITestUtilsTest.kt b/app/src/androidTest/kotlin/ac/test/podcini/ui/UITestUtilsTest.kt deleted file mode 100644 index f11a9608..00000000 --- a/app/src/androidTest/kotlin/ac/test/podcini/ui/UITestUtilsTest.kt +++ /dev/null @@ -1,97 +0,0 @@ -package de.test.podcini.ui - -import androidx.test.filters.MediumTest -import androidx.test.platform.app.InstrumentationRegistry -import ac.mdiq.podcini.storage.model.Feed -import org.junit.After -import org.junit.Assert -import org.junit.Before -import org.junit.Test -import java.io.File -import java.net.HttpURLConnection -import java.net.URL - -/** - * Test for the UITestUtils. Makes sure that all URLs are reachable and that the class does not cause any crashes. - */ -@MediumTest -class UITestUtilsTest { - private var uiTestUtils: UITestUtils? = null - - @Before - @Throws(Exception::class) - fun setUp() { - uiTestUtils = UITestUtils(InstrumentationRegistry.getInstrumentation().targetContext) - uiTestUtils!!.setup() - } - - @After - @Throws(Exception::class) - fun tearDown() { - uiTestUtils!!.tearDown() - } - - @Test - @Throws(Exception::class) - fun testAddHostedFeeds() { - uiTestUtils!!.addHostedFeedData() - val feeds: List = uiTestUtils!!.hostedFeeds - Assert.assertNotNull(feeds) - Assert.assertFalse(feeds.isEmpty()) - - for (feed in feeds) { - testUrlReachable(feed.downloadUrl) - for (item in feed.episodes) { - if (item.media != null) { - testUrlReachable(item.media!!.downloadUrl) - } - } - } - } - - @Throws(Exception::class) - fun testUrlReachable(strUtl: String?) { - val url = URL(strUtl) - val conn = url.openConnection() as HttpURLConnection - conn.requestMethod = "GET" - conn.connect() - val rc = conn.responseCode - Assert.assertEquals(HttpURLConnection.HTTP_OK.toLong(), rc.toLong()) - conn.disconnect() - } - - @Throws(Exception::class) - private fun addLocalFeedDataCheck(downloadEpisodes: Boolean) { - uiTestUtils!!.addLocalFeedData(downloadEpisodes) - Assert.assertNotNull(uiTestUtils!!.hostedFeeds) - Assert.assertFalse(uiTestUtils!!.hostedFeeds.isEmpty()) - - for (feed in uiTestUtils!!.hostedFeeds) { - Assert.assertTrue(feed.id != 0L) - for (item in feed.episodes) { - Assert.assertTrue(item.id != 0L) - if (item.media != null) { - Assert.assertTrue(item.media!!.id != 0L) - if (downloadEpisodes) { - Assert.assertTrue(item.media!!.downloaded) - Assert.assertNotNull(item.media!!.fileUrl) - val file = File(item.media!!.fileUrl!!) - Assert.assertTrue(file.exists()) - } - } - } - } - } - - @Test - @Throws(Exception::class) - fun testAddLocalFeedDataNoDownload() { - addLocalFeedDataCheck(false) - } - - @Test - @Throws(Exception::class) - fun testAddLocalFeedDataDownload() { - addLocalFeedDataCheck(true) - } -} diff --git a/app/src/androidTest/kotlin/ac/test/podcini/util/service/download/HTTPBin.kt b/app/src/androidTest/kotlin/ac/test/podcini/util/service/download/HTTPBin.kt deleted file mode 100644 index c802eef9..00000000 --- a/app/src/androidTest/kotlin/ac/test/podcini/util/service/download/HTTPBin.kt +++ /dev/null @@ -1,339 +0,0 @@ -package de.test.podcini.util.service.download - -import ac.mdiq.podcini.util.Logd -import android.util.Base64 -import android.util.Log -import fi.iki.elonen.NanoHTTPD -import fi.iki.elonen.NanoHTTPD.Response.IStatus -import org.apache.commons.io.IOUtils -import org.apache.commons.lang3.StringUtils -import java.io.* -import java.net.URLConnection -import java.util.* -import java.util.zip.GZIPOutputStream - -/** - * Http server for testing purposes - * - * - * Supported features: - * - * - * /status/code: Returns HTTP response with the given status code - * /redirect/n: Redirects n times - * /delay/n: Delay response for n seconds - * /basic-auth/username/password: Basic auth with username and password - * /gzip/n: Send gzipped data of size n bytes - * /files/id: Accesses the file with the specified ID (this has to be added first via serveFile). - */ -class HTTPBin : NanoHTTPD(0) { - private val servedFiles: MutableList = ArrayList() - - val baseUrl: String - get() = "http://127.0.0.1:$listeningPort" - - /** - * Adds the given file to the server. - * - * @return The ID of the file or -1 if the file could not be added to the server. - */ - @Synchronized - fun serveFile(file: File?): Int { - requireNotNull(file) { "file = null" } - if (!file.exists()) { - return -1 - } - for (i in servedFiles.indices) { - if (servedFiles[i].absolutePath == file.absolutePath) { - return i - } - } - servedFiles.add(file) - return servedFiles.size - 1 - } - - @Synchronized - fun accessFile(id: Int): File? { - return if (id < 0 || id >= servedFiles.size) { - null - } else { - servedFiles[id] - } - } - - override fun serve(session: IHTTPSession): Response { - Logd(TAG, "Requested url: " + session.uri) - - val segments = session.uri.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - if (segments.size < 3) { - Log.w(TAG, String.format(Locale.US, "Invalid number of URI segments: %d %s", - segments.size, segments.contentToString())) - get404Error() - } - - val func = segments[1] - val param = segments[2] - val headers = session.headers - - when { - func.equals("status", ignoreCase = true) -> { - try { - val code = param.toInt() - return Response(getStatus(code), MIME_HTML, "") - } catch (e: NumberFormatException) { - e.printStackTrace() - return internalError - } - } - func.equals("redirect", ignoreCase = true) -> { - try { - val times = param.toInt() - if (times < 0) { - throw NumberFormatException("times <= 0: $times") - } - - return getRedirectResponse(times - 1) - } catch (e: NumberFormatException) { - e.printStackTrace() - return internalError - } - } - func.equals("delay", ignoreCase = true) -> { - try { - val sec = param.toInt() - if (sec <= 0) { - throw NumberFormatException("sec <= 0: $sec") - } - - Thread.sleep(sec * 1000L) - return oKResponse - } catch (e: NumberFormatException) { - e.printStackTrace() - return internalError - } catch (e: InterruptedException) { - e.printStackTrace() - return internalError - } - } - func.equals("basic-auth", ignoreCase = true) -> { - if (!headers.containsKey("authorization")) { - Log.w(TAG, "No credentials provided") - return unauthorizedResponse - } - try { - val credentials = String(Base64.decode(headers["authorization"]!! - .split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[1], 0), charset("UTF-8")) - val credentialParts = credentials.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - if (credentialParts.size != 2) { - Log.w(TAG, "Unable to split credentials: " + credentialParts.contentToString()) - return internalError - } - if (credentialParts[0] == segments[2] && credentialParts[1] == segments[3]) { - Log.i(TAG, "Credentials accepted") - return oKResponse - } else { - Log.w(TAG, String.format("Invalid credentials. Expected %s, %s, but was %s, %s", - segments[2], segments[3], credentialParts[0], credentialParts[1])) - return unauthorizedResponse - } - } catch (e: UnsupportedEncodingException) { - e.printStackTrace() - return internalError - } - } - func.equals("gzip", ignoreCase = true) -> { - try { - val size = param.toInt() - if (size <= 0) { - Log.w(TAG, "Invalid size for gzipped data: $size") - throw NumberFormatException() - } - - return getGzippedResponse(size) - } catch (e: NumberFormatException) { - e.printStackTrace() - return internalError - } catch (e: IOException) { - e.printStackTrace() - return internalError - } - } - func.equals("files", ignoreCase = true) -> { - try { - val id = param.toInt() - if (id < 0) { - Log.w(TAG, "Invalid ID: $id") - throw NumberFormatException() - } - return getFileAccessResponse(id, headers) - } catch (e: NumberFormatException) { - e.printStackTrace() - return internalError - } - } - } - - return get404Error() - } - - @Synchronized - private fun getFileAccessResponse(id: Int, header: Map): Response { - val file = accessFile(id) - if (file == null || !file.exists()) { - Log.w(TAG, "File not found: $id") - return get404Error() - } - var inputStream: InputStream? = null - var contentRange: String? = null - val status: Response.Status - var successful = false - try { - inputStream = FileInputStream(file) - if (header.containsKey("range")) { - // read range header field - val value = header["range"] - val segments = value!!.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - if (segments.size != 2) { - Log.w(TAG, "Invalid segment length: " + segments.contentToString()) - return internalError - } - val type = StringUtils.substringBefore(value, "=") - if (!type.equals("bytes", ignoreCase = true)) { - Log.w(TAG, "Range is not specified in bytes: $value") - return internalError - } - try { - val start = StringUtils.substringBefore(segments[1], "-").toLong() - if (start >= file.length()) { - return rangeNotSatisfiable - } - - // skip 'start' bytes - IOUtils.skipFully(inputStream, start) - contentRange = "bytes " + start + (file.length() - 1) + "/" + file.length() - } catch (e: NumberFormatException) { - e.printStackTrace() - return internalError - } catch (e: IOException) { - e.printStackTrace() - return internalError - } - - status = Response.Status.PARTIAL_CONTENT - } else { - // request did not contain range header field - status = Response.Status.OK - } - successful = true - } catch (e: FileNotFoundException) { - e.printStackTrace() - - return internalError - } finally { - if (!successful && inputStream != null) { - IOUtils.closeQuietly(inputStream) - } - } - - val response = Response(status, URLConnection.guessContentTypeFromName(file.absolutePath), inputStream) - - response.addHeader("Accept-Ranges", "bytes") - if (contentRange != null) { - response.addHeader("Content-Range", contentRange) - } - response.addHeader("Content-Length", file.length().toString()) - return response - } - - @Throws(IOException::class) - private fun getGzippedResponse(size: Int): Response { - try { - Thread.sleep(200) - } catch (e: InterruptedException) { - e.printStackTrace() - } - val buffer = ByteArray(size) - val random = Random(System.currentTimeMillis()) - random.nextBytes(buffer) - - val compressed = ByteArrayOutputStream(buffer.size) - val gzipOutputStream = GZIPOutputStream(compressed) - gzipOutputStream.write(buffer) - gzipOutputStream.close() - - val inputStream: InputStream = ByteArrayInputStream(compressed.toByteArray()) - val response = Response(Response.Status.OK, MIME_PLAIN, inputStream) - response.addHeader("Content-Encoding", "gzip") - response.addHeader("Content-Length", compressed.size().toString()) - return response - } - - private fun getStatus(code: Int): IStatus { - when (code) { - 200 -> return Response.Status.OK - 201 -> return Response.Status.CREATED - 206 -> return Response.Status.PARTIAL_CONTENT - 301 -> return Response.Status.REDIRECT - 304 -> return Response.Status.NOT_MODIFIED - 400 -> return Response.Status.BAD_REQUEST - 401 -> return Response.Status.UNAUTHORIZED - 403 -> return Response.Status.FORBIDDEN - 404 -> return Response.Status.NOT_FOUND - 405 -> return Response.Status.METHOD_NOT_ALLOWED - 416 -> return Response.Status.RANGE_NOT_SATISFIABLE - 500 -> return Response.Status.INTERNAL_ERROR - else -> return object : IStatus { - override fun getRequestStatus(): Int { - return code - } - - override fun getDescription(): String { - return "Unknown" - } - } - } - } - - private fun getRedirectResponse(times: Int): Response { - when { - times > 0 -> { - val response = Response(Response.Status.REDIRECT, MIME_HTML, "This resource has been moved permanently") - response.addHeader("Location", "/redirect/$times") - return response - } - times == 0 -> { - return oKResponse - } - else -> { - return internalError - } - } - } - - private val unauthorizedResponse: Response - get() { - val response = Response(Response.Status.UNAUTHORIZED, MIME_HTML, "") - response.addHeader("WWW-Authenticate", "Basic realm=\"Test Realm\"") - return response - } - - private val oKResponse: Response - get() = Response(Response.Status.OK, MIME_HTML, "") - - private val internalError: Response - get() = Response(Response.Status.INTERNAL_ERROR, MIME_HTML, "The server encountered an internal error") - - private val rangeNotSatisfiable: Response - get() = Response(Response.Status.RANGE_NOT_SATISFIABLE, MIME_PLAIN, "") - - private fun get404Error(): Response { - return Response(Response.Status.NOT_FOUND, MIME_HTML, "The requested URL was not found on this server") - } - - companion object { - private val TAG: String = HTTPBin::class.simpleName ?: "Anonymous" - - private const val MIME_HTML = "text/html" - private const val MIME_PLAIN = "text/plain" - } -} diff --git a/app/src/androidTest/kotlin/ac/test/podcini/util/syndication/feedgenerator/FeedGenerator.kt b/app/src/androidTest/kotlin/ac/test/podcini/util/syndication/feedgenerator/FeedGenerator.kt deleted file mode 100644 index fcaa2329..00000000 --- a/app/src/androidTest/kotlin/ac/test/podcini/util/syndication/feedgenerator/FeedGenerator.kt +++ /dev/null @@ -1,28 +0,0 @@ -package de.test.podcini.util.syndication.feedgenerator - -import ac.mdiq.podcini.storage.model.Feed -import java.io.IOException -import java.io.OutputStream - -/** - * Generates a machine-readable, platform-independent representation of a Feed object. - */ -interface FeedGenerator { - /** - * Creates a machine-readable, platform-independent representation of a given - * Feed object and writes it to the given OutputStream. - * - * - * The representation might not be compliant with its specification if the feed - * is missing certain attribute values. This is intentional because the FeedGenerator is - * used for creating test data. - * - * @param feed The feed that should be written. Must not be null. - * @param outputStream The output target that the feed will be written to. The outputStream is not closed after - * the method's execution Must not be null. - * @param encoding The encoding to use. Must not be null. - * @param flags Optional argument for enabling implementation-dependent features. - */ - @Throws(IOException::class) - fun writeFeed(feed: Feed?, outputStream: OutputStream?, encoding: String?, flags: Long) -} diff --git a/app/src/androidTest/kotlin/ac/test/podcini/util/syndication/feedgenerator/GeneratorUtil.kt b/app/src/androidTest/kotlin/ac/test/podcini/util/syndication/feedgenerator/GeneratorUtil.kt deleted file mode 100644 index aa0362ca..00000000 --- a/app/src/androidTest/kotlin/ac/test/podcini/util/syndication/feedgenerator/GeneratorUtil.kt +++ /dev/null @@ -1,20 +0,0 @@ -package de.test.podcini.util.syndication.feedgenerator - -import org.xmlpull.v1.XmlSerializer -import java.io.IOException - -/** - * Utility methods for FeedGenerator - */ -internal object GeneratorUtil { - @JvmStatic - @Throws(IOException::class) - fun addPaymentLink(xml: XmlSerializer, paymentLink: String?, withNamespace: Boolean) { - val ns = if ((withNamespace)) "http://www.w3.org/2005/Atom" else null - xml.startTag(ns, "link") - xml.attribute(null, "rel", "payment") - xml.attribute(null, "href", paymentLink) - xml.attribute(null, "type", "text/html") - xml.endTag(ns, "link") - } -} diff --git a/app/src/androidTest/kotlin/ac/test/podcini/util/syndication/feedgenerator/Rss2Generator.kt b/app/src/androidTest/kotlin/ac/test/podcini/util/syndication/feedgenerator/Rss2Generator.kt deleted file mode 100644 index 41460595..00000000 --- a/app/src/androidTest/kotlin/ac/test/podcini/util/syndication/feedgenerator/Rss2Generator.kt +++ /dev/null @@ -1,125 +0,0 @@ -package de.test.podcini.util.syndication.feedgenerator - -import ac.mdiq.podcini.net.feed.parser.FeedHandler -import android.util.Xml -import ac.mdiq.podcini.util.MiscFormatter.formatRfc822Date -import ac.mdiq.podcini.storage.model.Feed -import de.test.podcini.util.syndication.feedgenerator.GeneratorUtil.addPaymentLink -import java.io.IOException -import java.io.OutputStream - -/** - * Creates RSS 2.0 feeds. See FeedGenerator for more information. - */ -class Rss2Generator : FeedGenerator { - @Throws(IOException::class) - override fun writeFeed(feed: Feed?, outputStream: OutputStream?, encoding: String?, flags: Long) { - requireNotNull(feed) { "feed = null" } - requireNotNull(outputStream) { "outputStream = null" } - - val xml = Xml.newSerializer() - xml.setOutput(outputStream, encoding) - xml.startDocument(encoding, null) - - xml.setPrefix("atom", "http://www.w3.org/2005/Atom") - xml.startTag(null, "rss") - xml.attribute(null, "version", "2.0") - xml.startTag(null, "channel") - - // Write Feed data - if (feed.title != null) { - xml.startTag(null, "title") - xml.text(feed.title) - xml.endTag(null, "title") - } - if (feed.description != null) { - xml.startTag(null, "description") - xml.text(feed.description) - xml.endTag(null, "description") - } - if (feed.link != null) { - xml.startTag(null, "link") - xml.text(feed.link) - xml.endTag(null, "link") - } - if (feed.language != null) { - xml.startTag(null, "language") - xml.text(feed.language) - xml.endTag(null, "language") - } - if (feed.imageUrl != null) { - xml.startTag(null, "image") - xml.startTag(null, "url") - xml.text(feed.imageUrl) - xml.endTag(null, "url") - xml.endTag(null, "image") - } - - val fundingList = feed.paymentLinks - if (fundingList.isNotEmpty()) { - for (funding in fundingList) { - addPaymentLink(xml, funding.url, true) - } - } - - // Write FeedItem data - if (feed.episodes.isNotEmpty()) { - for (item in feed.episodes) { - xml.startTag(null, "item") - - if (item.title != null) { - xml.startTag(null, "title") - xml.text(item.title) - xml.endTag(null, "title") - } - if (item.description != null) { - xml.startTag(null, "description") - xml.text(item.description) - xml.endTag(null, "description") - } - if (item.link != null) { - xml.startTag(null, "link") - xml.text(item.link) - xml.endTag(null, "link") - } - if (item.getPubDate() != null) { - xml.startTag(null, "pubDate") - xml.text(formatRfc822Date(item.getPubDate())) - xml.endTag(null, "pubDate") - } - if ((flags and FEATURE_WRITE_GUID) != 0L) { - xml.startTag(null, "guid") - xml.text(item.identifier) - xml.endTag(null, "guid") - } - if (item.media != null) { - xml.startTag(null, "enclosure") - xml.attribute(null, "url", item.media!!.downloadUrl) - xml.attribute(null, "length", item.media!!.size.toString()) - xml.attribute(null, "type", item.media!!.mimeType) - xml.endTag(null, "enclosure") - } - if (fundingList.isNotEmpty()) { - for (funding in fundingList) { - xml.startTag(FeedHandler.PodcastIndex.NSTAG, "funding") - xml.attribute(FeedHandler.PodcastIndex.NSTAG, "url", funding.url) - xml.text(funding.content) - addPaymentLink(xml, funding.url, true) - xml.endTag(FeedHandler.PodcastIndex.NSTAG, "funding") - } - } - - xml.endTag(null, "item") - } - } - - xml.endTag(null, "channel") - xml.endTag(null, "rss") - - xml.endDocument() - } - - companion object { - const val FEATURE_WRITE_GUID: Long = 1 - } -} diff --git a/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/DownloadRequest.kt b/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/DownloadRequest.kt index 608bff5d..4965f156 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/DownloadRequest.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/DownloadRequest.kt @@ -26,11 +26,6 @@ class DownloadRequest private constructor( var size: Long = 0 private var statusMsg = 0 - // only used in tests - constructor(destination: String, source: String, title: String, feedfileId: Long, - feedfileType: Int, username: String?, password: String?, arguments: Bundle?, initiatedByUser: Boolean) - : this(destination, source, title, feedfileId, feedfileType, null, username, password, false, arguments, initiatedByUser) - private constructor(builder: Builder) : this(builder.destination, builder.source, builder.title, diff --git a/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt b/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt index 14b1e1b1..27a2c0e9 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt @@ -452,7 +452,6 @@ class PlaybackService : MediaLibraryService() { // EventFlow.postEvent(FlowEvent.PlayEvent(nextItem)) return if (nextItem.media == null) null else unmanaged(nextItem.media!!) } - // only used in test override fun findMedia(url: String): Playable? { val item = getEpisodeByGuidOrUrl(null, url) return item?.media diff --git a/app/src/main/kotlin/ac/mdiq/podcini/preferences/UserPreferences.kt b/app/src/main/kotlin/ac/mdiq/podcini/preferences/UserPreferences.kt index 6265dd49..285f1253 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/preferences/UserPreferences.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/preferences/UserPreferences.kt @@ -227,21 +227,6 @@ object UserPreferences { return fullNotificationButtons.contains(buttonId) } -// only used in test - fun showSkipOnFullNotification(): Boolean { - return showButtonOnFullNotification(NOTIFICATION_BUTTON.SKIP.ordinal) - } - - // only used in test - fun showNextChapterOnFullNotification(): Boolean { - return showButtonOnFullNotification(NOTIFICATION_BUTTON.NEXT_CHAPTER.ordinal) - } - - // only used in test - fun showPlaybackSpeedOnFullNotification(): Boolean { - return showButtonOnFullNotification(NOTIFICATION_BUTTON.PLAYBACK_SPEED.ordinal) - } - /** * @return `true` if we should show remaining time or the duration */ @@ -258,11 +243,6 @@ object UserPreferences { appPrefs.edit().putBoolean(Prefs.showTimeLeft.name, showRemain!!).apply() } -// only used in test - fun shouldPauseForFocusLoss(): Boolean { - return appPrefs.getBoolean(Prefs.prefPauseForFocusLoss.name, true) - } - fun backButtonOpensDrawer(): Boolean { return appPrefs.getBoolean(Prefs.prefBackButtonOpensDrawer.name, false) } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/algorithms/AutoDownloads.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/algorithms/AutoDownloads.kt index 0c064325..f1db4d9a 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/algorithms/AutoDownloads.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/algorithms/AutoDownloads.kt @@ -110,7 +110,8 @@ object AutoDownloads { var episodes = mutableListOf() val dlFilter = if (f.preferences?.countingPlayed == true) EpisodeFilter(EpisodeFilter.States.downloaded.name) - else EpisodeFilter(EpisodeFilter.States.downloaded.name, EpisodeFilter.States.unplayed.name) + else EpisodeFilter(EpisodeFilter.States.downloaded.name, EpisodeFilter.States.unplayed.name, EpisodeFilter.States.inQueue.name, + EpisodeFilter.States.inProgress.name, EpisodeFilter.States.skipped.name) val downloadedCount = getEpisodesCount(dlFilter, f.id) val allowedDLCount = (f.preferences?.autoDLMaxEpisodes?:0) - downloadedCount Logd(TAG, "autoDownloadEpisodeMedia ${f.preferences?.autoDLMaxEpisodes} downloadedCount: $downloadedCount allowedDLCount: $allowedDLCount") @@ -118,15 +119,15 @@ object AutoDownloads { var queryString = "feedId == ${f.id} AND isAutoDownloadEnabled == true AND media != nil AND media.downloaded == false" when (f.preferences?.autoDLPolicy) { FeedPreferences.AutoDownloadPolicy.ONLY_NEW -> { - queryString += " AND playState == -1 SORT(pubDate DESC) LIMIT(${3*allowedDLCount})" + queryString += " AND playState == ${PlayState.NEW.code} SORT(pubDate DESC) LIMIT(${3*allowedDLCount})" episodes = realm.query(Episode::class).query(queryString).find().toMutableList() } FeedPreferences.AutoDownloadPolicy.NEWER -> { - queryString += " AND playState != 1 SORT(pubDate DESC) LIMIT(${3*allowedDLCount})" + queryString += " AND playState < ${PlayState.SKIPPED.code} SORT(pubDate DESC) LIMIT(${3*allowedDLCount})" episodes = realm.query(Episode::class).query(queryString).find().toMutableList() } FeedPreferences.AutoDownloadPolicy.OLDER -> { - queryString += " AND playState != 1 SORT(pubDate ASC) LIMIT(${3*allowedDLCount})" + queryString += " AND playState < ${PlayState.SKIPPED.code} SORT(pubDate ASC) LIMIT(${3*allowedDLCount})" episodes = realm.query(Episode::class).query(queryString).find().toMutableList() } else -> {} @@ -149,7 +150,7 @@ object AutoDownloads { runOnIOScope { realm.write { while (true) { - val episodesNew = query(Episode::class, "feedId == ${f.id} AND playState == -1 LIMIT(20)").find() + val episodesNew = query(Episode::class, "feedId == ${f.id} AND playState == ${PlayState.NEW.code} LIMIT(20)").find() if (episodesNew.isEmpty()) break Logd(TAG, "autoDownloadEpisodeMedia episodesNew: ${episodesNew.size}") episodesNew.map { e -> diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Episodes.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Episodes.kt index fd3bcf38..fb8f6438 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Episodes.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Episodes.kt @@ -206,28 +206,6 @@ object Episodes { } } -// only used in tests - fun persistEpisodeMedia(media: EpisodeMedia) : Job { - Logd(TAG, "persistEpisodeMedia called") - return runOnIOScope { - var episode = media.episodeOrFetch() - if (episode != null) { - episode = upsert(episode) { it.media = media } - EventFlow.postEvent(FlowEvent.EpisodeMediaEvent.updated(episode)) - } else Log.e(TAG, "persistEpisodeMedia media.episode is null") - } - } - - fun persistEpisode(episode: Episode?, isPositionChange: Boolean = false) : Job { - Logd(TAG, "persistEpisode called") - return runOnIOScope { - if (episode != null) { - upsert(episode) {} - if (!isPositionChange) EventFlow.postEvent(FlowEvent.EpisodeEvent.updated(episode)) - } - } - } - /** * This method will set the playback completion date to the current date regardless of the current value. * @param episode Episode that should be added to the playback history. diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/LogsAndStats.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/LogsAndStats.kt index e398969b..0eed36b1 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/LogsAndStats.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/LogsAndStats.kt @@ -56,7 +56,7 @@ object LogsAndStats { feedTotalTime += m.duration if (m.lastPlayedTime in timeFilterFrom.. 0 && m.playedDuration > 0) || m.episodeOrFetch()?.playState == PlayState.PLAYED.code || m.position > 0) { + if ((m.playbackCompletionTime > 0 && m.playedDuration > 0) || (m.episodeOrFetch()?.playState?:-10) > PlayState.SKIPPED.code || m.position > 0) { episodesStarted += 1 feedPlayedTime += m.duration } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Episode.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Episode.kt index 1f7c0d3e..6095cff2 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Episode.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Episode.kt @@ -211,7 +211,7 @@ class Episode : RealmObject { } fun isPlayed(): Boolean { - return playState == PlayState.PLAYED.code + return playState >= PlayState.SKIPPED.code } fun setPlayed(played: Boolean) { diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Feed.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Feed.kt index 4dbc9b91..1a81b9fa 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Feed.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Feed.kt @@ -285,7 +285,7 @@ class Feed : RealmObject { } fun getVirtualQueueItems(): List { - var qString = "feedId == $id AND playState != ${PlayState.PLAYED.code}" + var qString = "feedId == $id AND playState < ${PlayState.SKIPPED.code}" // TODO: perhaps need to set prefStreamOverDownload for youtube feeds if (type != FeedType.YOUTUBE.name && preferences?.prefStreamOverDownload != true) qString += " AND media.downloaded == true" val eList_ = realm.query(Episode::class, qString).find().toMutableList() diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/utils/EpisodeUtil.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/utils/EpisodeUtil.kt index 5d06a695..1022f683 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/utils/EpisodeUtil.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/utils/EpisodeUtil.kt @@ -34,16 +34,6 @@ object EpisodeUtil { return -1 } -// only used in tests - @JvmStatic - fun getIdList(items: List): List { - val result: MutableList = ArrayList() - for (item in items) { - result.add(item.id) - } - return result - } - @JvmStatic fun hasAlmostEnded(media: Playable): Boolean { return media.getDuration() > 0 && media.getPosition() >= media.getDuration() - smartMarkAsPlayedSecs * 1000 diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/EpisodesVM.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/EpisodesVM.kt index fff311d4..d909b4ed 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/EpisodesVM.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/EpisodesVM.kt @@ -722,8 +722,8 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: MutableList, feed: Icon(imageVector = ImageVector.vectorResource(ratingIconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "rating", modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer).width(18.dp).height(18.dp)) val playStateRes = PlayState.fromCode(vm.playedState).res Icon(imageVector = ImageVector.vectorResource(playStateRes), tint = textColor, contentDescription = "playState", modifier = Modifier.width(18.dp).height(18.dp)) - if (vm.inQueueState) - Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_playlist_play), tint = textColor, contentDescription = "ivInPlaylist", modifier = Modifier.width(18.dp).height(18.dp)) +// if (vm.inQueueState) +// Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_playlist_play), tint = textColor, contentDescription = "ivInPlaylist", modifier = Modifier.width(18.dp).height(18.dp)) if (vm.episode.media?.getMediaType() == MediaType.VIDEO) Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_videocam), tint = textColor, contentDescription = "isVideo", modifier = Modifier.width(18.dp).height(18.dp)) val curContext = LocalContext.current @@ -748,7 +748,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: MutableList, feed: // vm.actionRes = vm.actionButton!!.getDrawable() } } else { - LaunchedEffect(vm.actionButton) { + LaunchedEffect(Unit) { Logd(TAG, "LaunchedEffect init actionButton") vm.actionButton = actionButton_(vm.episode) // vm.actionRes = vm.actionButton!!.getDrawable() diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/dialog/RemoveFeedDialog.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/dialog/RemoveFeedDialog.kt deleted file mode 100644 index a41bf4b9..00000000 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/dialog/RemoveFeedDialog.kt +++ /dev/null @@ -1,77 +0,0 @@ -package ac.mdiq.podcini.ui.dialog - -import ac.mdiq.podcini.R -import ac.mdiq.podcini.storage.database.Feeds.deleteFeedSync -import ac.mdiq.podcini.storage.model.Feed -import ac.mdiq.podcini.util.Logd -import ac.mdiq.podcini.util.EventFlow -import ac.mdiq.podcini.util.FlowEvent -import android.app.ProgressDialog -import android.content.Context -import android.content.DialogInterface -import android.util.Log -import androidx.annotation.OptIn -import androidx.media3.common.util.UnstableApi -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext - -// only used in SearchFragment -object RemoveFeedDialog { - private val TAG: String = RemoveFeedDialog::class.simpleName ?: "Anonymous" - - fun show(context: Context, feed: Feed, callback: Runnable?) { - val feeds = listOf(feed) - val message = getMessageId(context, feeds) - showDialog(context, feeds, message, callback) - } - - fun show(context: Context, feeds: List) { - val message = getMessageId(context, feeds) - showDialog(context, feeds, message, null) - } - - private fun showDialog(context: Context, feeds: List, message: String, callback: Runnable?) { - val dialog: ConfirmationDialog = object : ConfirmationDialog(context, R.string.remove_feed_label, message) { - @OptIn(UnstableApi::class) override fun onConfirmButtonPressed(clickedDialog: DialogInterface) { - callback?.run() - clickedDialog.dismiss() - - val progressDialog = ProgressDialog(context) - progressDialog.setMessage(context.getString(R.string.feed_remover_msg)) - progressDialog.isIndeterminate = true - progressDialog.setCancelable(false) - progressDialog.show() - - val scope = CoroutineScope(Dispatchers.Main) - scope.launch { - try { - withContext(Dispatchers.IO) { - for (feed in feeds) { - deleteFeedSync(context, feed.id, false) - } - EventFlow.postEvent(FlowEvent.FeedListEvent(FlowEvent.FeedListEvent.Action.REMOVED, feeds.map { it.id })) - } - withContext(Dispatchers.Main) { - Logd(TAG, "Feed(s) deleted") - progressDialog.dismiss() - } - } catch (e: Throwable) { - Log.e(TAG, Log.getStackTraceString(e)) - withContext(Dispatchers.Main) { progressDialog.dismiss() } - } - } - } - } - dialog.createNewDialog().show() - } - - private fun getMessageId(context: Context, feeds: List): String { - return if (feeds.size == 1) { - if (feeds[0].isLocalFeed) context.getString(R.string.feed_delete_confirmation_local_msg) + feeds[0].title - else context.getString(R.string.feed_delete_confirmation_msg) + feeds[0].title - } else context.getString(R.string.feed_delete_confirmation_msg_batch) - - } -} diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AllEpisodesFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AllEpisodesFragment.kt index 90c7d198..1cf38573 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AllEpisodesFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AllEpisodesFragment.kt @@ -139,8 +139,9 @@ class AllEpisodesFragment : BaseEpisodesFragment() { var info = "${episodes.size} episodes" if (getFilter().properties.isNotEmpty()) { info += " - ${getString(R.string.filtered_label)}" - emptyView.setMessage(R.string.no_all_episodes_filtered_label) - } else emptyView.setMessage(R.string.no_all_episodes_label) +// emptyView.setMessage(R.string.no_all_episodes_filtered_label) + } +// else emptyView.setMessage(R.string.no_all_episodes_label) infoBarText.value = info // toolbar.menu?.findItem(R.id.action_favorites)?.setIcon(if (getFilter().showIsFavorite) R.drawable.ic_star else R.drawable.ic_star_border) } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/BaseEpisodesFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/BaseEpisodesFragment.kt index 85e88184..fe2d05b6 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/BaseEpisodesFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/BaseEpisodesFragment.kt @@ -11,7 +11,6 @@ import ac.mdiq.podcini.ui.actions.SwipeActions import ac.mdiq.podcini.ui.actions.SwipeActions.NoActionSwipeAction import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.compose.* -import ac.mdiq.podcini.ui.utils.EmptyViewHandler import ac.mdiq.podcini.util.EventFlow import ac.mdiq.podcini.util.FlowEvent import ac.mdiq.podcini.util.Logd @@ -51,7 +50,6 @@ abstract class BaseEpisodesFragment : Fragment(), Toolbar.OnMenuItemClickListene private var leftActionState = mutableStateOf(NoActionSwipeAction()) private var rightActionState = mutableStateOf(NoActionSwipeAction()) - lateinit var emptyView: EmptyViewHandler lateinit var toolbar: MaterialToolbar lateinit var swipeActions: SwipeActions @@ -106,15 +104,7 @@ abstract class BaseEpisodesFragment : Fragment(), Toolbar.OnMenuItemClickListene swipeActions.setFilter(getFilter()) refreshSwipeTelltale() - createListAdaptor() - - emptyView = EmptyViewHandler(requireContext()) - emptyView.setIcon(R.drawable.ic_feed) - emptyView.setTitle(R.string.no_all_episodes_head_label) - emptyView.setMessage(R.string.no_all_episodes_label) - emptyView.hide() - return binding.root } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/DownloadsFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/DownloadsFragment.kt index d12a4cf0..da1d37e9 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/DownloadsFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/DownloadsFragment.kt @@ -23,7 +23,6 @@ import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.compose.* import ac.mdiq.podcini.ui.dialog.EpisodeSortDialog import ac.mdiq.podcini.ui.dialog.SwitchQueueDialog -import ac.mdiq.podcini.ui.utils.EmptyViewHandler import ac.mdiq.podcini.util.EventFlow import ac.mdiq.podcini.util.FlowEvent import ac.mdiq.podcini.util.Logd @@ -72,8 +71,7 @@ import java.util.* private lateinit var toolbar: MaterialToolbar private lateinit var swipeActions: SwipeActions - private lateinit var emptyView: EmptyViewHandler - + private var displayUpArrow = false @UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { @@ -129,7 +127,7 @@ import java.util.* refreshSwipeTelltale() // if (arguments != null && requireArguments().getBoolean(ARG_SHOW_LOGS, false)) DownloadLogFragment().show(childFragmentManager, null) - addEmptyView() +// addEmptyView() return binding.root } @@ -296,13 +294,6 @@ import java.util.* // loadItems() // } - private fun addEmptyView() { - emptyView = EmptyViewHandler(requireContext()) - emptyView.setIcon(R.drawable.ic_download) - emptyView.setTitle(R.string.no_comp_downloads_head_label) - emptyView.setMessage(R.string.no_comp_downloads_label) - } - private fun onEpisodeEvent(event: FlowEvent.EpisodeEvent) { // Logd(TAG, "onEpisodeEvent() called with ${event.TAG}") var i = 0 @@ -354,7 +345,7 @@ import java.util.* private var loadItemsRunning = false private fun loadItems() { - emptyView.hide() +// emptyView.hide() Logd(TAG, "loadItems() called") if (!loadItemsRunning) { loadItemsRunning = true diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/HistoryFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/HistoryFragment.kt index 4fc5d2c0..ca01e382 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/HistoryFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/HistoryFragment.kt @@ -46,9 +46,9 @@ import kotlin.math.min toolbar.inflateMenu(R.menu.playback_history) toolbar.setTitle(R.string.playback_history_label) updateToolbar() - emptyView.setIcon(R.drawable.ic_history) - emptyView.setTitle(R.string.no_history_head_label) - emptyView.setMessage(R.string.no_history_label) +// emptyView.setIcon(R.drawable.ic_history) +// emptyView.setTitle(R.string.no_history_head_label) +// emptyView.setMessage(R.string.no_history_label) return root } @@ -125,8 +125,9 @@ import kotlin.math.min var info = "${episodes.size} episodes" if (getFilter().properties.isNotEmpty()) { info += " - ${getString(R.string.filtered_label)}" - emptyView.setMessage(R.string.no_all_episodes_filtered_label) - } else emptyView.setMessage(R.string.no_all_episodes_label) +// emptyView.setMessage(R.string.no_all_episodes_filtered_label) + } +// else emptyView.setMessage(R.string.no_all_episodes_label) infoBarText.value = info // toolbar.menu?.findItem(R.id.action_favorites)?.setIcon(if (getFilter().showIsFavorite) R.drawable.ic_star else R.drawable.ic_star_border) } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt index e81a41eb..c7a1ef04 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt @@ -88,7 +88,6 @@ import kotlin.math.max private var _binding: ComposeFragmentBinding? = null private val binding get() = _binding!! -// private lateinit var emptyViewHandler: EmptyViewHandler private lateinit var toolbar: MaterialToolbar private lateinit var swipeActions: SwipeActions private lateinit var swipeActionsBin: SwipeActions diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt index b9a3da26..c7f0e4f7 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt @@ -30,7 +30,6 @@ import ac.mdiq.podcini.ui.dialog.CustomFeedNameDialog import ac.mdiq.podcini.ui.dialog.FeedSortDialog import ac.mdiq.podcini.ui.dialog.TagSettingsDialog import ac.mdiq.podcini.ui.fragment.FeedSettingsFragment.Companion.queueSettingOptions -import ac.mdiq.podcini.ui.utils.EmptyViewHandler import ac.mdiq.podcini.util.EventFlow import ac.mdiq.podcini.util.FlowEvent import ac.mdiq.podcini.util.Logd @@ -108,7 +107,6 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener { private var _binding: FragmentSubscriptionsBinding? = null private val binding get() = _binding!! - private lateinit var emptyView: EmptyViewHandler private lateinit var toolbar: MaterialToolbar private val tags: MutableList = mutableListOf() @@ -132,6 +130,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener { // private var feedList: MutableList = mutableListOf() private var feedListFiltered = mutableStateListOf() var showFilterDialog by mutableStateOf(false) + var noSubscription by mutableStateOf(false) private var useGrid by mutableStateOf(null) private val useGridLayout by mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefFeedGridLayout.name, false)) @@ -171,10 +170,11 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener { binding.lazyColumn.setContent { CustomTheme(requireContext()) { if (showFilterDialog) FilterDialog(FeedFilter(feedsFilter)) { showFilterDialog = false } + if (noSubscription) Text(stringResource(R.string.no_subscriptions_label)) LazyList() } } - setupEmptyView() +// setupEmptyView() resetTags() val queues = realm.query(PlayQueue::class).find() @@ -211,11 +211,6 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener { return binding.root } -// override fun onResume() { -// Logd(TAG, "onResume() called") -// super.onResume() -// } - override fun onStart() { Logd(TAG, "onStart()") super.onStart() @@ -338,18 +333,10 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener { return true } - private fun setupEmptyView() { - emptyView = EmptyViewHandler(requireContext()) - emptyView.setIcon(R.drawable.ic_subscriptions) - emptyView.setTitle(R.string.no_subscriptions_head_label) - emptyView.setMessage(R.string.no_subscriptions_label) -// emptyView.attachToRecyclerView(recyclerView) - } - private var loadItemsRunning = false @OptIn(UnstableApi::class) private fun loadSubscriptions() { - emptyView.hide() +// emptyView.hide() if (!loadItemsRunning) { loadItemsRunning = true lifecycleScope.launch { @@ -363,13 +350,14 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener { // We have fewer items. This can result in items being selected that are no longer visible. // if (feedListFiltered.size > feedList.size) adapter.endSelectMode() // filterOnTag() + noSubscription = feedList.isEmpty() feedListFiltered.clear() feedListFiltered.addAll(feedList) feedCount = feedListFiltered.size.toString() + " / " + NavDrawerFragment.feedCount.toString() infoTextFiltered = " " if (feedsFilter.isNotEmpty()) infoTextFiltered = getString(R.string.filtered_label) txtvInformation = (infoTextFiltered + infoTextUpdate) - emptyView.updateVisibility() +// emptyView.updateVisibility() } } catch (e: Throwable) { Log.e(TAG, Log.getStackTraceString(e)) } finally { loadItemsRunning = false } @@ -389,7 +377,8 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener { val dir = 1 - 2*feedOrderDir // get from 0, 1 to 1, -1 val comparator: Comparator = when (feedOrder) { FeedSortOrder.UNPLAYED_NEW_OLD.index -> { - val queryString = "feedId == $0 AND (playState == ${PlayState.NEW.code} OR playState == ${PlayState.UNPLAYED.code})" +// val queryString = "feedId == $0 AND (playState == ${PlayState.NEW.code} OR playState == ${PlayState.UNPLAYED.code})" + val queryString = "feedId == $0 AND (playState < ${PlayState.SKIPPED.code})" val counterMap: MutableMap = mutableMapOf() for (f in feedList_) { val c = realm.query(Episode::class).query(queryString, f.id).count().find() @@ -411,7 +400,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener { } } FeedSortOrder.MOST_PLAYED.index -> { - val queryString = "feedId == $0 AND playState == ${PlayState.PLAYED.code}" + val queryString = "feedId == $0 AND playState >= ${PlayState.SKIPPED.code}" val counterMap: MutableMap = mutableMapOf() for (f in feedList_) { val c = realm.query(Episode::class).query(queryString, f.id).count().find() @@ -444,7 +433,8 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener { } FeedSortOrder.LAST_UPDATED_UNPLAYED_NEW_OLD.index -> { val queryString = - "feedId == $0 AND (playState == ${PlayState.NEW.code} OR playState == ${PlayState.UNPLAYED.code}) SORT(pubDate DESC)" +// "feedId == $0 AND (playState == ${PlayState.NEW.code} OR playState == ${PlayState.UNPLAYED.code}) SORT(pubDate DESC)" + "feedId == $0 AND (playState < ${PlayState.SKIPPED.code}) SORT(pubDate DESC)" val counterMap: MutableMap = mutableMapOf() for (f in feedList_) { val d = realm.query(Episode::class).query(queryString, f.id).first().find()?.pubDate ?: 0L @@ -466,7 +456,8 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener { } FeedSortOrder.MOST_DOWNLOADED_UNPLAYED.index -> { val queryString = - "feedId == $0 AND (playState == ${PlayState.NEW.code} OR playState == ${PlayState.UNPLAYED.code}) AND media.downloaded == true" +// "feedId == $0 AND (playState == ${PlayState.NEW.code} OR playState == ${PlayState.UNPLAYED.code}) AND media.downloaded == true" + "feedId == $0 AND (playState < ${PlayState.SKIPPED.code}) AND media.downloaded == true" val counterMap: MutableMap = mutableMapOf() for (f in feedList_) { val c = realm.query(Episode::class).query(queryString, f.id).count().find() diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/statistics/StatisticsFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/statistics/StatisticsFragment.kt index fb383644..b547b772 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/statistics/StatisticsFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/statistics/StatisticsFragment.kt @@ -447,7 +447,7 @@ class StatisticsFragment : Fragment() { else { // progress import does not include playedDuration if (includeMarkedAsPlayed) { - if (m.playbackCompletionTime > 0 || m.episodeOrFetch()?.playState == PlayState.PLAYED.code) + if (m.playbackCompletionTime > 0 || (m.episodeOrFetch()?.playState?:-10) >= PlayState.SKIPPED.code) dur += m.duration else if (m.position > 0) dur += m.position } else dur += m.position diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/utils/CoverLoader.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/utils/CoverLoader.kt deleted file mode 100644 index a848fcb2..00000000 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/utils/CoverLoader.kt +++ /dev/null @@ -1,117 +0,0 @@ -package ac.mdiq.podcini.ui.utils - -import ac.mdiq.podcini.R -import ac.mdiq.podcini.ui.activity.MainActivity -import ac.mdiq.podcini.util.Logd -import android.graphics.drawable.Drawable -import android.view.View -import android.widget.ImageView -import android.widget.TextView -import coil.ImageLoader -import coil.imageLoader -import coil.request.ErrorResult -import coil.request.ImageRequest -import coil.target.Target -import java.lang.ref.WeakReference - -class CoverLoader(private val activity: MainActivity) { - private var resource = 0 - private var uri: String? = null - private var fallbackUri: String? = null - private var imgvCover: ImageView? = null - private var textAndImageCombined = false - private var fallbackTitle: TextView? = null - - fun withUri(uri: String?): CoverLoader { - this.uri = uri - return this - } - - fun withResource(resource: Int): CoverLoader { - this.resource = resource - return this - } - - fun withFallbackUri(uri: String?): CoverLoader { - fallbackUri = uri - return this - } - - fun withCoverView(coverView: ImageView): CoverLoader { - imgvCover = coverView - return this - } - - fun withPlaceholderView(title: TextView): CoverLoader { - this.fallbackTitle = title - return this - } - - /** - * Set cover text and if it should be shown even if there is a cover image. - * @param fallbackTitle Fallback title text - * @param textAndImageCombined Show cover text even if there is a cover image? - */ - fun withPlaceholderView(fallbackTitle: TextView?, textAndImageCombined: Boolean): CoverLoader { - this.fallbackTitle = fallbackTitle - this.textAndImageCombined = textAndImageCombined - return this - } - - fun load() { - if (imgvCover == null) return - val coverTargetCoil = CoilCoverTarget(fallbackTitle, imgvCover!!, textAndImageCombined) - if (resource != 0) { - val imageLoader = ImageLoader.Builder(activity).build() - imageLoader.enqueue(ImageRequest.Builder(activity).data(null).target(coverTargetCoil).build()) - imgvCover!!.setImageResource(resource) - CoilCoverTarget.setTitleVisibility(fallbackTitle, textAndImageCombined) - return - } - - val request = ImageRequest.Builder(activity) - .data(uri) - .setHeader("User-Agent", "Mozilla/5.0") - .listener(object : ImageRequest.Listener { - override fun onError(request: ImageRequest, result: ErrorResult) { - Logd("CoverLoader", "Trying to get fallback image") - val fallbackImageRequest = ImageRequest.Builder(activity) - .data(fallbackUri) - .setHeader("User-Agent", "Mozilla/5.0") - .error(R.mipmap.ic_launcher) - .target(coverTargetCoil) - .build() - activity.imageLoader.enqueue(fallbackImageRequest) - } - }) - .target(coverTargetCoil) - .build() - activity.imageLoader.enqueue(request) - } - - internal class CoilCoverTarget(fallbackTitle: TextView?, coverImage: ImageView, private val textAndImageCombined: Boolean) : Target { - - private val fallbackTitle: WeakReference = WeakReference(fallbackTitle) - private val cover: WeakReference = WeakReference(coverImage) - - override fun onStart(placeholder: Drawable?) { - - } - override fun onError(error: Drawable?) { - setTitleVisibility(fallbackTitle.get(), true) - } - - override fun onSuccess(result: Drawable) { - val ivCover = cover.get() - ivCover!!.setImageDrawable(result) - setTitleVisibility(fallbackTitle.get(), textAndImageCombined) - } - - companion object { - fun setTitleVisibility(fallbackTitle: TextView?, textAndImageCombined: Boolean) { - fallbackTitle?.visibility = if (textAndImageCombined) View.VISIBLE else View.GONE - } - } - } - -} \ No newline at end of file diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/utils/EmptyViewHandler.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/utils/EmptyViewHandler.kt deleted file mode 100644 index 6ff1d0e5..00000000 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/utils/EmptyViewHandler.kt +++ /dev/null @@ -1,165 +0,0 @@ -package ac.mdiq.podcini.ui.utils - -import android.content.Context -import android.database.DataSetObserver -import android.view.Gravity -import android.view.View -import android.view.ViewGroup -import android.widget.* -import androidx.annotation.DrawableRes -import androidx.coordinatorlayout.widget.CoordinatorLayout -import androidx.recyclerview.widget.RecyclerView -import ac.mdiq.podcini.R -import ac.mdiq.podcini.databinding.EmptyViewLayoutBinding - -class EmptyViewHandler(context: Context) { - private var layoutAdded = false - private var listAdapter: ListAdapter? = null - private var recyclerAdapter: RecyclerView.Adapter<*>? = null - - private val emptyView: View - private val tvTitle: TextView - private val tvMessage: TextView - private val ivIcon: ImageView - - fun setTitle(title: Int) { - tvTitle.setText(title) - } - - fun setMessage(message: Int) { - tvMessage.setText(message) - } - - fun setMessage(message: String?) { - tvMessage.text = message - } - - fun setIcon(@DrawableRes icon: Int) { - ivIcon.setImageResource(icon) - ivIcon.visibility = View.VISIBLE - } - - fun hide() { - emptyView.visibility = View.GONE - } - - fun attachToListView(listView: AbsListView) { - check(!layoutAdded) { "Can not attach EmptyView multiple times" } - addToParentView(listView) - layoutAdded = true - listView.emptyView = emptyView - updateAdapter(listView.adapter) - } - - fun attachToRecyclerView(recyclerView: RecyclerView) { - check(!layoutAdded) { "Can not attach EmptyView multiple times" } - addToParentView(recyclerView) - layoutAdded = true - updateAdapter(recyclerView.adapter) - } - - private fun addToParentView(view: View) { - var parent = view.parent as? ViewGroup - while (parent != null) { - when (parent) { - is RelativeLayout -> { - parent.addView(emptyView) - val layoutParams = emptyView.layoutParams as RelativeLayout.LayoutParams - layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE) - emptyView.layoutParams = layoutParams - break - } - is FrameLayout -> { - parent.addView(emptyView) - val layoutParams = emptyView.layoutParams as FrameLayout.LayoutParams - layoutParams.gravity = Gravity.CENTER - emptyView.layoutParams = layoutParams - break - } - is CoordinatorLayout -> { - parent.addView(emptyView) - val layoutParams = emptyView.layoutParams as CoordinatorLayout.LayoutParams - layoutParams.gravity = Gravity.CENTER - emptyView.layoutParams = layoutParams - break - } - } - parent = parent.parent as? ViewGroup - } - } - - fun updateAdapter(adapter: RecyclerView.Adapter<*>?) { - recyclerAdapter?.unregisterAdapterDataObserver(adapterObserver) - - this.recyclerAdapter = adapter - adapter?.registerAdapterDataObserver(adapterObserver) - updateVisibility() - } - - private fun updateAdapter(adapter: ListAdapter?) { - listAdapter?.unregisterDataSetObserver(listAdapterObserver) - this.listAdapter = adapter - adapter?.registerDataSetObserver(listAdapterObserver) - updateVisibility() - } - - private val adapterObserver: SimpleAdapterDataObserver = object : SimpleAdapterDataObserver() { - override fun anythingChanged() { - updateVisibility() - } - } - - private val listAdapterObserver: DataSetObserver = object : DataSetObserver() { - override fun onChanged() { - updateVisibility() - } - } - - /** - * AdapterDataObserver that relays all events to the method anythingChanged(). - */ - abstract class SimpleAdapterDataObserver : RecyclerView.AdapterDataObserver() { - abstract fun anythingChanged() - - override fun onChanged() { - anythingChanged() - } - - override fun onItemRangeChanged(positionStart: Int, itemCount: Int) { - anythingChanged() - } - - override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) { - anythingChanged() - } - - override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { - anythingChanged() - } - - override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) { - anythingChanged() - } - - override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { - anythingChanged() - } - } - - init { - emptyView = View.inflate(context, R.layout.empty_view_layout, null) - val binding = EmptyViewLayoutBinding.bind(emptyView) - tvTitle = binding.emptyViewTitle - tvMessage = binding.emptyViewMessage - ivIcon = binding.emptyViewIcon - } - - fun updateVisibility() { - val empty = when { - recyclerAdapter != null -> recyclerAdapter!!.itemCount == 0 - listAdapter != null -> listAdapter!!.isEmpty - else -> true - } - emptyView.visibility = if (empty) View.VISIBLE else View.GONE - } -} diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/utils/LiftOnScrollListener.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/utils/LiftOnScrollListener.kt deleted file mode 100644 index 8537b6e3..00000000 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/utils/LiftOnScrollListener.kt +++ /dev/null @@ -1,47 +0,0 @@ -package ac.mdiq.podcini.ui.utils - -import android.animation.ValueAnimator -import android.view.View -import androidx.core.widget.NestedScrollView -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView - -/** - * Workaround for app:liftOnScroll flickering when in SwipeRefreshLayout - */ -class LiftOnScrollListener(appBar: View) : RecyclerView.OnScrollListener(), NestedScrollView.OnScrollChangeListener { - private val animator: ValueAnimator = ValueAnimator.ofFloat(0f, appBar.context.resources.displayMetrics.density * 8) - private var animatingToScrolled = false - - init { - animator.addUpdateListener { animation: ValueAnimator -> appBar.elevation = animation.animatedValue as Float } - } - - override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { - elevate(isScrolled(recyclerView)) - } - - private fun isScrolled(recyclerView: RecyclerView): Boolean { - val firstItem = (recyclerView.layoutManager as? LinearLayoutManager)?.findFirstVisibleItemPosition()?:-1 - when { - firstItem < 0 -> return false - firstItem > 0 -> return true - else -> { - val firstItemView = recyclerView.layoutManager?.findViewByPosition(firstItem) - return if (firstItemView == null) false else firstItemView.top < 0 - } - } - } - - override fun onScrollChange(v: NestedScrollView, scrollX: Int, scrollY: Int, oldScrollX: Int, oldScrollY: Int) { - elevate(scrollY != 0) - } - - private fun elevate(isScrolled: Boolean) { - if (isScrolled == animatingToScrolled) return - - animatingToScrolled = isScrolled - if (isScrolled) animator.start() - else animator.reverse() - } -} diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/utils/ToolbarIconTintManager.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/utils/ToolbarIconTintManager.kt deleted file mode 100644 index 397431f2..00000000 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/utils/ToolbarIconTintManager.kt +++ /dev/null @@ -1,52 +0,0 @@ -package ac.mdiq.podcini.ui.utils - -import android.content.Context -import android.graphics.PorterDuff -import android.graphics.PorterDuffColorFilter -import android.graphics.drawable.Drawable -import android.view.ContextThemeWrapper -import com.google.android.material.appbar.AppBarLayout -import com.google.android.material.appbar.AppBarLayout.OnOffsetChangedListener -import com.google.android.material.appbar.CollapsingToolbarLayout -import com.google.android.material.appbar.MaterialToolbar -import ac.mdiq.podcini.R - -abstract class ToolbarIconTintManager(private val context: Context, - private val toolbar: MaterialToolbar, - private val collapsingToolbar: CollapsingToolbarLayout) - : OnOffsetChangedListener { - - private var isTinted = false - - override fun onOffsetChanged(appBarLayout: AppBarLayout, offset: Int) { - val tint = (collapsingToolbar.height + offset) > (2 * collapsingToolbar.minimumHeight) - if (isTinted != tint) { - isTinted = tint - updateTint() - } - } - - fun updateTint() { - if (isTinted) { - doTint(ContextThemeWrapper(context, R.style.Theme_Podcini_Dark)) - safeSetColorFilter(toolbar.navigationIcon, PorterDuffColorFilter(-0x1, PorterDuff.Mode.SRC_ATOP)) - safeSetColorFilter(toolbar.overflowIcon, PorterDuffColorFilter(-0x1, PorterDuff.Mode.SRC_ATOP)) - safeSetColorFilter(toolbar.collapseIcon, PorterDuffColorFilter(-0x1, PorterDuff.Mode.SRC_ATOP)) - } else { - doTint(context) - safeSetColorFilter(toolbar.navigationIcon, null) - safeSetColorFilter(toolbar.overflowIcon, null) - safeSetColorFilter(toolbar.collapseIcon, null) - } - } - - private fun safeSetColorFilter(icon: Drawable?, filter: PorterDuffColorFilter?) { - icon?.colorFilter = filter - } - - /** - * View expansion was changed. Icons need to be tinted - * @param themedContext ContextThemeWrapper with dark theme while expanded - */ - protected abstract fun doTint(themedContext: Context) -} diff --git a/app/src/test/assets/local-feed1/track1.mp3 b/app/src/test/assets/local-feed1/track1.mp3 deleted file mode 100644 index b1f993c3..00000000 Binary files a/app/src/test/assets/local-feed1/track1.mp3 and /dev/null differ diff --git a/app/src/test/assets/local-feed2/folder.png b/app/src/test/assets/local-feed2/folder.png deleted file mode 100644 index 9e522a98..00000000 Binary files a/app/src/test/assets/local-feed2/folder.png and /dev/null differ diff --git a/app/src/test/assets/local-feed2/track1.mp3 b/app/src/test/assets/local-feed2/track1.mp3 deleted file mode 100644 index b1f993c3..00000000 Binary files a/app/src/test/assets/local-feed2/track1.mp3 and /dev/null differ diff --git a/app/src/test/assets/local-feed2/track2.mp3 b/app/src/test/assets/local-feed2/track2.mp3 deleted file mode 100644 index 310cddd6..00000000 Binary files a/app/src/test/assets/local-feed2/track2.mp3 and /dev/null differ diff --git a/app/src/test/kotlin/ac/mdiq/podcini/feed/EpisodeMediaTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/feed/EpisodeMediaTest.kt deleted file mode 100644 index cc6ac1a4..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/feed/EpisodeMediaTest.kt +++ /dev/null @@ -1,68 +0,0 @@ -package ac.mdiq.podcini.feed - -import ac.mdiq.podcini.feed.FeedMediaMother.anyFeedMedia -import ac.mdiq.podcini.storage.model.Episode -import ac.mdiq.podcini.storage.model.EpisodeMedia -import org.junit.Before -import org.junit.Test -import org.mockito.Mockito - -class EpisodeMediaTest { - private var media: EpisodeMedia? = null - - @Before - fun setUp() { - media = anyFeedMedia() - } - - /** - * Downloading a media from a not new and not played item should not change the item state. - */ - @Test - fun testDownloadMediaOfNotNewAndNotPlayedItem_unchangedItemState() { - val item = Mockito.mock(Episode::class.java) - Mockito.`when`(item.isNew).thenReturn(false) - Mockito.`when`(item.isPlayed()).thenReturn(false) - - media!!.episode = (item) - media!!.downloaded = (true) - - Mockito.verify(item, Mockito.never()).setNew() - Mockito.verify(item, Mockito.never()).setPlayed(true) - Mockito.verify(item, Mockito.never()).setPlayed(false) - } - - /** - * Downloading a media from a played item (thus not new) should not change the item state. - */ - @Test - fun testDownloadMediaOfPlayedItem_unchangedItemState() { - val item = Mockito.mock(Episode::class.java) - Mockito.`when`(item.isNew).thenReturn(false) - Mockito.`when`(item.isPlayed()).thenReturn(true) - - media!!.episode = (item) - media!!.downloaded = (true) - - Mockito.verify(item, Mockito.never()).setNew() - Mockito.verify(item, Mockito.never()).setPlayed(true) - Mockito.verify(item, Mockito.never()).setPlayed(false) - } - - /** - * Downloading a media from a new item (thus not played) should change the item to not played. - */ - @Test - fun testDownloadMediaOfNewItem_changedToNotPlayedItem() { - val item = Mockito.mock(Episode::class.java) - Mockito.`when`(item.isNew).thenReturn(true) - Mockito.`when`(item.isPlayed()).thenReturn(false) - - media!!.episode = (item) - media!!.downloaded = (true) - - Mockito.verify(item).setPlayed(false) - Mockito.verify(item, Mockito.never()).setNew() - Mockito.verify(item, Mockito.never()).setPlayed(true) - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/feed/EpisodeTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/feed/EpisodeTest.kt deleted file mode 100644 index ec2d8dd3..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/feed/EpisodeTest.kt +++ /dev/null @@ -1,138 +0,0 @@ -package ac.mdiq.podcini.feed - -import ac.mdiq.podcini.feed.FeedItemMother.anyFeedItemWithImage -import ac.mdiq.podcini.storage.model.Episode -import junit.framework.TestCase.assertEquals -import org.junit.Assert -import org.junit.Before -import org.junit.Test -import java.text.SimpleDateFormat - -class EpisodeTest { - private var original: Episode? = null - private var changedEpisode: Episode? = null - - @Before - fun setUp() { - original = anyFeedItemWithImage() - changedEpisode = anyFeedItemWithImage() - } - - @Test - fun testUpdateFromOther_feedItemImageDownloadUrlChanged() { - setNewFeedItemImageDownloadUrl() - original!!.updateFromOther(changedEpisode!!) - assertFeedItemImageWasUpdated() - } - - @Test - fun testUpdateFromOther_feedItemImageRemoved() { - feedItemImageRemoved() - original!!.updateFromOther(changedEpisode!!) - assertFeedItemImageWasNotUpdated() - } - - @Test - fun testUpdateFromOther_feedItemImageAdded() { - original!!.imageUrl = (null) - setNewFeedItemImageDownloadUrl() - original!!.updateFromOther(changedEpisode!!) - assertFeedItemImageWasUpdated() - } - - @Test - @Throws(Exception::class) - fun testUpdateFromOther_dateChanged() { - val originalDate = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("1952-03-11 00:00:00") - val changedDate = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("1952-03-11 00:42:42") - original!!.setPubDate(originalDate) - changedEpisode!!.setPubDate(changedDate) - original!!.updateFromOther(changedEpisode!!) - assertEquals(changedDate.time, original!!.getPubDate()!!.time) - } - - /** - * Test that a played item loses that state after being marked as new. - */ - @Test - fun testMarkPlayedItemAsNew_itemNotPlayed() { - original!!.setPlayed(true) - original!!.setNew() - - Assert.assertFalse(original!!.isPlayed()) - } - - /** - * Test that a new item loses that state after being marked as played. - */ - @Test - fun testMarkNewItemAsPlayed_itemNotNew() { - original!!.setNew() - original!!.setPlayed(true) - - Assert.assertFalse(original!!.isNew) - } - - /** - * Test that a new item loses that state after being marked as not played. - */ - @Test - fun testMarkNewItemAsNotPlayed_itemNotNew() { - original!!.setNew() - original!!.setPlayed(false) - - Assert.assertFalse(original!!.isNew) - } - - private fun setNewFeedItemImageDownloadUrl() { - changedEpisode!!.imageUrl = ("http://example.com/new_picture") - } - - private fun feedItemImageRemoved() { - changedEpisode!!.imageUrl = (null) - } - - private fun assertFeedItemImageWasUpdated() { - assertEquals(original!!.imageUrl, changedEpisode!!.imageUrl) - } - - private fun assertFeedItemImageWasNotUpdated() { - assertEquals(anyFeedItemWithImage().imageUrl, original!!.imageUrl) - } - - /** - * If one of `description` or `content:encoded` is null, use the other one. - */ - @Test - fun testShownotesNullValues() { - testShownotes(null, TEXT_LONG) - testShownotes(TEXT_LONG, null) - } - - /** - * If `description` is reasonably longer than `content:encoded`, use `description`. - */ - @Test - fun testShownotesLength() { - testShownotes(TEXT_SHORT, TEXT_LONG) - testShownotes(TEXT_LONG, TEXT_SHORT) - } - - /** - * Checks if the shownotes equal TEXT_LONG, using the given `description` and `content:encoded`. - * - * @param description Description of the feed item - * @param contentEncoded `content:encoded` of the feed item - */ - private fun testShownotes(description: String?, contentEncoded: String?) { - val item = Episode() - item.setDescriptionIfLonger(description) - item.setDescriptionIfLonger(contentEncoded) - Assert.assertEquals(TEXT_LONG, item.description) - } - - companion object { - private const val TEXT_LONG = "Lorem ipsum dolor sit amet, consectetur adipiscing elit." - private const val TEXT_SHORT = "Lorem ipsum" - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/feed/FeedAutoDownloadFilterTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/feed/FeedAutoDownloadFilterTest.kt deleted file mode 100644 index ae056024..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/feed/FeedAutoDownloadFilterTest.kt +++ /dev/null @@ -1,152 +0,0 @@ -package ac.mdiq.podcini.feed - -import ac.mdiq.podcini.storage.utils.DurationConverter.durationStringShortToMs -import ac.mdiq.podcini.storage.model.FeedAutoDownloadFilter -import ac.mdiq.podcini.storage.model.Episode -import org.junit.Assert -import org.junit.Test - -class FeedAutoDownloadFilterTest { - @Test - fun testNullFilter() { - val filter = FeedAutoDownloadFilter() - val item = Episode() - item.title = ("Hello world") - - Assert.assertFalse(filter.excludeOnly()) - Assert.assertFalse(filter.includeOnly()) - Assert.assertEquals("", filter.excludeFilterRaw) - Assert.assertEquals("", filter.includeFilterRaw) - Assert.assertTrue(filter.shouldAutoDownload(item)) - } - - @Test - fun testBasicIncludeFilter() { - val includeFilter = "Hello" - val filter = FeedAutoDownloadFilter(includeFilter, "") - val item = Episode() - item.title = ("Hello world") - - val item2 = Episode() - item2.title = ("Don't include me") - - Assert.assertFalse(filter.excludeOnly()) - Assert.assertTrue(filter.includeOnly()) - Assert.assertEquals("", filter.excludeFilterRaw) - Assert.assertEquals(includeFilter, filter.includeFilterRaw) - Assert.assertTrue(filter.shouldAutoDownload(item)) - Assert.assertFalse(filter.shouldAutoDownload(item2)) - } - - @Test - fun testBasicExcludeFilter() { - val excludeFilter = "Hello" - val filter = FeedAutoDownloadFilter("", excludeFilter) - val item = Episode() - item.title = ("Hello world") - - val item2 = Episode() - item2.title = ("Item2") - - Assert.assertTrue(filter.excludeOnly()) - Assert.assertFalse(filter.includeOnly()) - Assert.assertEquals(excludeFilter, filter.excludeFilterRaw) - Assert.assertEquals("", filter.includeFilterRaw) - Assert.assertFalse(filter.shouldAutoDownload(item)) - Assert.assertTrue(filter.shouldAutoDownload(item2)) - } - - @Test - fun testComplexIncludeFilter() { - val includeFilter = "Hello \n\"Two words\"" - val filter = FeedAutoDownloadFilter(includeFilter, "") - val item = Episode() - item.title = ("hello world") - - val item2 = Episode() - item2.title = ("Two three words") - - val item3 = Episode() - item3.title = ("One two words") - - Assert.assertFalse(filter.excludeOnly()) - Assert.assertTrue(filter.includeOnly()) - Assert.assertEquals("", filter.excludeFilterRaw) - Assert.assertEquals(includeFilter, filter.includeFilterRaw) - Assert.assertTrue(filter.shouldAutoDownload(item)) - Assert.assertFalse(filter.shouldAutoDownload(item2)) - Assert.assertTrue(filter.shouldAutoDownload(item3)) - } - - @Test - fun testComplexExcludeFilter() { - val excludeFilter = "Hello \"Two words\"" - val filter = FeedAutoDownloadFilter("", excludeFilter) - val item = Episode() - item.title = ("hello world") - - val item2 = Episode() - item2.title = ("One three words") - - val item3 = Episode() - item3.title = ("One two words") - - Assert.assertTrue(filter.excludeOnly()) - Assert.assertFalse(filter.includeOnly()) - Assert.assertEquals(excludeFilter, filter.excludeFilterRaw) - Assert.assertEquals("", filter.includeFilterRaw) - Assert.assertFalse(filter.shouldAutoDownload(item)) - Assert.assertTrue(filter.shouldAutoDownload(item2)) - Assert.assertFalse(filter.shouldAutoDownload(item3)) - } - - @Test - fun testComboFilter() { - val includeFilter = "Hello world" - val excludeFilter = "dislike" - val filter = FeedAutoDownloadFilter(includeFilter, excludeFilter) - - val download = Episode() - download.title = ("Hello everyone!") - // because, while it has words from the include filter it also has exclude words - val doNotDownload = Episode() - doNotDownload.title = ("I dislike the world") - // because it has no words from the include filter - val doNotDownload2 = Episode() - doNotDownload2.title = ("no words to include") - - Assert.assertTrue(filter.hasExcludeFilter()) - Assert.assertTrue(filter.hasIncludeFilter()) - Assert.assertTrue(filter.shouldAutoDownload(download)) - Assert.assertFalse(filter.shouldAutoDownload(doNotDownload)) - Assert.assertFalse(filter.shouldAutoDownload(doNotDownload2)) - } - - @Test - fun testMinimalDurationFilter() { - val download = Episode() - download.title = ("Hello friend!") - val downloadMedia = FeedMediaMother.anyFeedMedia() - downloadMedia.setDuration(durationStringShortToMs("05:00", false)) - download.setMedia(downloadMedia) - // because duration of the media in unknown - val download2 = Episode() - download2.title = ("Hello friend!") - val unknownDurationMedia = FeedMediaMother.anyFeedMedia() - download2.setMedia(unknownDurationMedia) - // because it is not long enough - val doNotDownload = Episode() - doNotDownload.title = ("Hello friend!") - val doNotDownloadMedia = FeedMediaMother.anyFeedMedia() - doNotDownloadMedia.setDuration(durationStringShortToMs("02:00", false)) - doNotDownload.setMedia(doNotDownloadMedia) - - val minimalDurationFilter = 3 * 60 - val filter = FeedAutoDownloadFilter("", "", minimalDurationFilter) - - Assert.assertTrue(filter.hasMinimalDurationFilter()) - Assert.assertTrue(filter.shouldAutoDownload(download)) - Assert.assertFalse(filter.shouldAutoDownload(doNotDownload)) - Assert.assertTrue(filter.shouldAutoDownload(download2)) - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/feed/FeedItemMother.kt b/app/src/test/kotlin/ac/mdiq/podcini/feed/FeedItemMother.kt deleted file mode 100644 index 7c712ba5..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/feed/FeedItemMother.kt +++ /dev/null @@ -1,15 +0,0 @@ -package ac.mdiq.podcini.feed - -import ac.mdiq.podcini.storage.model.Episode -import java.util.* - -internal object FeedItemMother { - private const val IMAGE_URL = "http://example.com/image" - - @JvmStatic - fun anyFeedItemWithImage(): Episode { - val item = Episode(0, "Item", "Item", "url", Date(), PlayState.PLAYED.code, FeedMother.anyFeed()) - item.imageUrl = (IMAGE_URL) - return item - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/feed/FeedMediaMother.kt b/app/src/test/kotlin/ac/mdiq/podcini/feed/FeedMediaMother.kt deleted file mode 100644 index d94159ba..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/feed/FeedMediaMother.kt +++ /dev/null @@ -1,14 +0,0 @@ -package ac.mdiq.podcini.feed - -import ac.mdiq.podcini.storage.model.EpisodeMedia - -internal object FeedMediaMother { - private const val EPISODE_URL = "http://example.com/episode" - private const val SIZE: Long = 42 - private const val MIME_TYPE = "audio/mp3" - - @JvmStatic - fun anyFeedMedia(): EpisodeMedia { - return EpisodeMedia(null, EPISODE_URL, SIZE, MIME_TYPE) - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/feed/FeedMother.kt b/app/src/test/kotlin/ac/mdiq/podcini/feed/FeedMother.kt deleted file mode 100644 index 8a0e4ea5..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/feed/FeedMother.kt +++ /dev/null @@ -1,14 +0,0 @@ -package ac.mdiq.podcini.feed - -import ac.mdiq.podcini.storage.model.Feed - -object FeedMother { - const val IMAGE_URL: String = "http://example.com/image" - - @JvmStatic - fun anyFeed(): Feed { - return Feed(0, null, "title", "http://example.com", "This is the description", - "http://example.com/payment", "Daniel", "en", null, "http://example.com/feed", IMAGE_URL, - null, "http://example.com/feed") - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/feed/FeedTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/feed/FeedTest.kt deleted file mode 100644 index 03dc906e..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/feed/FeedTest.kt +++ /dev/null @@ -1,117 +0,0 @@ -package ac.mdiq.podcini.feed - -import ac.mdiq.podcini.feed.FeedMother.anyFeed -import ac.mdiq.podcini.storage.model.Feed -import ac.mdiq.podcini.storage.model.EpisodeSortOrder -import junit.framework.TestCase.assertEquals -import org.junit.Assert -import org.junit.Before -import org.junit.Test - -class FeedTest { - private var original: Feed? = null - private var changedFeed: Feed? = null - - @Before - fun setUp() { - original = anyFeed() - changedFeed = anyFeed() - } - - @Test - @Throws(Exception::class) - fun testCompareWithOther_feedImageDownloadUrlChanged() { - setNewFeedImageDownloadUrl() - feedHasChanged() - } - - @Test - @Throws(Exception::class) - fun testCompareWithOther_sameFeedImage() { - changedFeed!!.imageUrl =(FeedMother.IMAGE_URL) - feedHasNotChanged() - } - - @Test - @Throws(Exception::class) - fun testCompareWithOther_feedImageRemoved() { - feedImageRemoved() - feedHasNotChanged() - } - - @Test - @Throws(Exception::class) - fun testUpdateFromOther_feedImageDownloadUrlChanged() { - setNewFeedImageDownloadUrl() - original!!.updateFromOther(changedFeed!!) - feedImageWasUpdated() - } - - @Test - @Throws(Exception::class) - fun testUpdateFromOther_feedImageRemoved() { - feedImageRemoved() - original!!.updateFromOther(changedFeed!!) - feedImageWasNotUpdated() - } - - @Test - @Throws(Exception::class) - fun testUpdateFromOther_feedImageAdded() { - feedHadNoImage() - setNewFeedImageDownloadUrl() - original!!.updateFromOther(changedFeed!!) - feedImageWasUpdated() - } - - @Test - @Throws(Exception::class) - fun testSetSortOrder_OnlyIntraFeedSortAllowed() { - for (sortOrder in EpisodeSortOrder.entries) { - if (sortOrder.scope == EpisodeSortOrder.Scope.INTRA_FEED) { - original!!.sortOrder = sortOrder // should be okay - } else { - try { - original!!.sortOrder = sortOrder - Assert.fail("SortOrder $sortOrder should not be allowed on a feed") - } catch (iae: IllegalArgumentException) { - // expected exception - } - } - } - } - - @Test - @Throws(Exception::class) - fun testSetSortOrder_NullAllowed() { - original!!.sortOrder = null // should be okay - } - - private fun feedHasNotChanged() { - Assert.assertFalse(original!!.compareWithOther(changedFeed!!)) - } - - private fun feedHadNoImage() { - original!!.imageUrl =(null) - } - - private fun setNewFeedImageDownloadUrl() { - changedFeed!!.imageUrl =("http://example.com/new_picture") - } - - private fun feedHasChanged() { - Assert.assertTrue(original!!.compareWithOther(changedFeed!!)) - } - - private fun feedImageRemoved() { - changedFeed!!.imageUrl =(null) - } - - private fun feedImageWasUpdated() { - assertEquals(original!!.imageUrl, changedFeed!!.imageUrl) - } - - private fun feedImageWasNotUpdated() { - assertEquals(anyFeed().imageUrl, original!!.imageUrl) - } -} \ No newline at end of file diff --git a/app/src/test/kotlin/ac/mdiq/podcini/feed/LocalFeedUpdaterTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/feed/LocalFeedUpdaterTest.kt deleted file mode 100644 index 5bf04dd3..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/feed/LocalFeedUpdaterTest.kt +++ /dev/null @@ -1,300 +0,0 @@ -package ac.mdiq.podcini.feed - -import ac.mdiq.podcini.net.feed.LocalFeedUpdater -import ac.mdiq.podcini.net.feed.LocalFeedUpdater.getImageUrl -import ac.mdiq.podcini.net.feed.LocalFeedUpdater.tryUpdateFeed -import ac.mdiq.podcini.net.download.service.DownloadServiceInterface -import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterfaceTestStub -import ac.mdiq.podcini.net.feed.LocalFeedUpdater.FastDocumentFile.Companion.list -import ac.mdiq.podcini.preferences.UserPreferences -import ac.mdiq.podcini.storage.database.Feeds.getFeedList -import ac.mdiq.podcini.storage.model.Feed -import ac.mdiq.podcini.util.config.ApplicationCallbacks -import ac.mdiq.podcini.util.config.ClientConfig -import android.app.Application -import android.content.Context -import android.media.MediaMetadataRetriever -import android.net.Uri -import android.webkit.MimeTypeMap -import androidx.test.platform.app.InstrumentationRegistry -import junit.framework.TestCase.assertEquals -import org.hamcrest.CoreMatchers -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers -import org.junit.After -import org.junit.Assert -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers -import org.mockito.Mockito -import org.robolectric.RobolectricTestRunner -import org.robolectric.shadows.ShadowMediaMetadataRetriever -import java.io.File -import java.io.IOException -import java.util.* - -/** - * Test local feeds handling in class LocalFeedUpdater. - */ -@RunWith(RobolectricTestRunner::class) -class LocalFeedUpdaterTest { - private lateinit var context: Context - - @Before - @Throws(Exception::class) - fun setUp() { - // Initialize environment - context = InstrumentationRegistry.getInstrumentation().context - UserPreferences.init(context!!) -// init(context) - - val app = context as Application? - ClientConfig.applicationCallbacks = Mockito.mock(ApplicationCallbacks::class.java) - Mockito.`when`(ClientConfig.applicationCallbacks?.getApplicationInstance()).thenReturn(app) - DownloadServiceInterface.setImpl(DownloadServiceInterfaceTestStub()) - - // Initialize database -// PodDBAdapter.init(context!!) -// deleteDatabase() -// val adapter = getInstance() -// adapter.open() -// adapter.close() - - mapDummyMetadata(LOCAL_FEED_DIR1) - mapDummyMetadata(LOCAL_FEED_DIR2) -// TODO: can't get 'addExtensionMimeTypMapping? -// Shadows.shadowOf(MimeTypeMap.getSingleton()).addExtensionMimeTypMapping("mp3", "audio/mp3") - } - - @After - fun tearDown() { -// DBWriter.tearDownTests() -// PodDBAdapter.tearDownTests() - } - - /** - * Test adding a new local feed. - */ - @Test - fun testUpdateFeed_AddNewFeed() { - // check for empty database - val feedListBefore = getFeedList() - assertThat(feedListBefore, CoreMatchers.`is`(Matchers.empty())) - - callUpdateFeed(LOCAL_FEED_DIR2) - - // verify new feed in database - verifySingleFeedInDatabaseAndItemCount(2) - val feedAfter = verifySingleFeedInDatabase() - assertEquals(FEED_URL, feedAfter.downloadUrl) - } - - /** - * Test adding further items to an existing local feed. - */ - @Test - fun testUpdateFeed_AddMoreItems() { - // add local feed with 1 item (localFeedDir1) - callUpdateFeed(LOCAL_FEED_DIR1) - - // now add another item (by changing to local feed folder localFeedDir2) - callUpdateFeed(LOCAL_FEED_DIR2) - - verifySingleFeedInDatabaseAndItemCount(2) - } - - /** - * Test removing items from an existing local feed without a corresponding media file. - */ - @Test - fun testUpdateFeed_RemoveItems() { - // add local feed with 2 items (localFeedDir1) - callUpdateFeed(LOCAL_FEED_DIR2) - - // now remove an item (by changing to local feed folder localFeedDir1) - callUpdateFeed(LOCAL_FEED_DIR1) - - verifySingleFeedInDatabaseAndItemCount(1) - } - - /** - * Test feed icon defined in the local feed media folder. - */ - @Test - fun testUpdateFeed_FeedIconFromFolder() { - callUpdateFeed(LOCAL_FEED_DIR2) - - val feedAfter = verifySingleFeedInDatabase() - assertThat(feedAfter.imageUrl, CoreMatchers.endsWith("local-feed2/folder.png")) - } - - /** - * Test default feed icon if there is no matching file in the local feed media folder. - */ - @Test - fun testUpdateFeed_FeedIconDefault() { - callUpdateFeed(LOCAL_FEED_DIR1) - - val feedAfter = verifySingleFeedInDatabase() - assertThat(feedAfter.imageUrl, Matchers.startsWith(Feed.PREFIX_GENERATIVE_COVER)) - } - - /** - * Test default feed metadata. - * - * @see .mapDummyMetadata Title and PubDate are dummy values. - */ - @Test - fun testUpdateFeed_FeedMetadata() { - callUpdateFeed(LOCAL_FEED_DIR1) - - val feed = verifySingleFeedInDatabase() - val feedItems = feed.episodes - assertEquals("track1.mp3", feedItems[0].title) - } - - @Test - fun testGetImageUrl_EmptyFolder() { - val imageUrl = getImageUrl(emptyList(), Uri.EMPTY) - assertThat(imageUrl, Matchers.startsWith(Feed.PREFIX_GENERATIVE_COVER)) - } - - @Test - fun testGetImageUrl_NoImageButAudioFiles() { - val folder = listOf(mockDocumentFile("audio.mp3", "audio/mp3")) - val imageUrl = getImageUrl(folder, Uri.EMPTY) - assertThat(imageUrl, Matchers.startsWith(Feed.PREFIX_GENERATIVE_COVER)) - } - - @Test - fun testGetImageUrl_PreferredImagesFilenames() { - for (filename in LocalFeedUpdater.PREFERRED_FEED_IMAGE_FILENAMES) { - val folder = listOf(mockDocumentFile("audio.mp3", "audio/mp3"), - mockDocumentFile(filename, "image/jpeg")) // image MIME type doesn't matter - val imageUrl = getImageUrl(folder, Uri.EMPTY) - assertThat(imageUrl, CoreMatchers.endsWith(filename)) - } - } - - @Test - fun testGetImageUrl_OtherImageFilenameJpg() { - val folder = listOf(mockDocumentFile("audio.mp3", "audio/mp3"), - mockDocumentFile("my-image.jpg", "image/jpeg")) - val imageUrl = getImageUrl(folder, Uri.EMPTY) - assertThat(imageUrl, CoreMatchers.endsWith("my-image.jpg")) - } - - @Test - fun testGetImageUrl_OtherImageFilenameJpeg() { - val folder = listOf(mockDocumentFile("audio.mp3", "audio/mp3"), - mockDocumentFile("my-image.jpeg", "image/jpeg")) - val imageUrl = getImageUrl(folder, Uri.EMPTY) - assertThat(imageUrl, CoreMatchers.endsWith("my-image.jpeg")) - } - - @Test - fun testGetImageUrl_OtherImageFilenamePng() { - val folder = listOf(mockDocumentFile("audio.mp3", "audio/mp3"), - mockDocumentFile("my-image.png", "image/png")) - val imageUrl = getImageUrl(folder, Uri.EMPTY) - assertThat(imageUrl, CoreMatchers.endsWith("my-image.png")) - } - - @Test - fun testGetImageUrl_OtherImageFilenameUnsupportedMimeType() { - val folder = listOf(mockDocumentFile("audio.mp3", "audio/mp3"), - mockDocumentFile("my-image.svg", "image/svg+xml")) - val imageUrl = getImageUrl(folder, Uri.EMPTY) - assertThat(imageUrl, Matchers.startsWith(Feed.PREFIX_GENERATIVE_COVER)) - } - - /** - * Fill ShadowMediaMetadataRetriever with dummy duration and title. - * - * @param localFeedDir assets local feed folder with media files - */ - private fun mapDummyMetadata(localFeedDir: String) { - for (fileName in Objects.requireNonNull(File(localFeedDir).list())) { - val path = "$localFeedDir/$fileName" - ShadowMediaMetadataRetriever.addMetadata(path, - MediaMetadataRetriever.METADATA_KEY_DURATION, "10") - ShadowMediaMetadataRetriever.addMetadata(path, - MediaMetadataRetriever.METADATA_KEY_TITLE, fileName) - ShadowMediaMetadataRetriever.addMetadata(path, - MediaMetadataRetriever.METADATA_KEY_DATE, "20200601T222324") - } - } - - /** - * Calls the method LocalFeedUpdater#tryUpdateFeed with the given local feed folder. - * - * @param localFeedDir assets local feed folder with media files - */ - private fun callUpdateFeed(localFeedDir: String) { - Mockito.mockStatic(LocalFeedUpdater.FastDocumentFile::class.java).use { dfMock -> - // mock external storage - dfMock.`when` { list(ArgumentMatchers.any(), ArgumentMatchers.any()) } - .thenReturn(mockLocalFolder(localFeedDir)) - - // call method to test - val feed = Feed(FEED_URL, null) - try { - tryUpdateFeed(feed, context!!, null, null) - } catch (e: IOException) { - throw RuntimeException(e) - } - } - } - - companion object { - /** - * URL to locate the local feed media files on the external storage (SD card). - * The exact URL doesn't matter here as access to external storage is mocked - * (seems not to be supported by Robolectric). - */ - private const val FEED_URL = - "content://com.android.externalstorage.documents/tree/primary%3ADownload%2Flocal-feed" - private const val LOCAL_FEED_DIR1 = "src/test/assets/local-feed1" - private const val LOCAL_FEED_DIR2 = "src/test/assets/local-feed2" - - /** - * Verify that the database contains exactly one feed and return that feed. - */ - private fun verifySingleFeedInDatabase(): Feed { - val feedListAfter = getFeedList() - Assert.assertEquals(1, feedListAfter.size.toLong()) - return feedListAfter[0] - } - - /** - * Verify that the database contains exactly one feed and the number of - * items in the feed. - * - * @param expectedItemCount expected number of items in the feed - */ - private fun verifySingleFeedInDatabaseAndItemCount(expectedItemCount: Int) { - val feed = verifySingleFeedInDatabase() - val feedItems = feed.episodes - Assert.assertEquals(expectedItemCount.toLong(), feedItems.size.toLong()) - } - - /** - * Create a DocumentFile mock object. - */ - private fun mockDocumentFile(fileName: String, mimeType: String): LocalFeedUpdater.FastDocumentFile { - return LocalFeedUpdater.FastDocumentFile(fileName, mimeType, Uri.parse("file:///path/$fileName"), 0, 0) - } - - private fun mockLocalFolder(folderName: String): List { - val files: MutableList = ArrayList() - for (f in Objects.requireNonNull>(File(folderName).listFiles())) { - val extension = MimeTypeMap.getFileExtensionFromUrl(f.path) - val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) - files.add(LocalFeedUpdater.FastDocumentFile(f.name, mimeType!!, - Uri.parse(f.toURI().toString()), f.length(), f.lastModified())) - } - return files - } - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/feed/VolumeAdaptionSettingTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/feed/VolumeAdaptionSettingTest.kt deleted file mode 100644 index fc78938c..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/feed/VolumeAdaptionSettingTest.kt +++ /dev/null @@ -1,115 +0,0 @@ -package ac.mdiq.podcini.feed - -import ac.mdiq.podcini.storage.model.VolumeAdaptionSetting -import ac.mdiq.podcini.storage.model.VolumeAdaptionSetting.Companion.fromInteger -import org.hamcrest.MatcherAssert -import org.hamcrest.Matchers -import org.junit.Assert -import org.junit.Test - -class VolumeAdaptionSettingTest { - @Test - fun mapOffToInteger() { - val setting = VolumeAdaptionSetting.OFF - MatcherAssert.assertThat(setting.toInteger(), Matchers.`is`(Matchers.equalTo(0))) - } - - @Test - fun mapLightReductionToInteger() { - val setting = VolumeAdaptionSetting.LIGHT_REDUCTION - - MatcherAssert.assertThat(setting.toInteger(), Matchers.`is`(Matchers.equalTo(1))) - } - - @Test - fun mapHeavyReductionToInteger() { - val setting = VolumeAdaptionSetting.HEAVY_REDUCTION - - MatcherAssert.assertThat(setting.toInteger(), Matchers.`is`(Matchers.equalTo(2))) - } - - @Test - fun mapLightBoostToInteger() { - val setting = VolumeAdaptionSetting.LIGHT_BOOST - - MatcherAssert.assertThat(setting.toInteger(), Matchers.`is`(Matchers.equalTo(3))) - } - - @Test - fun mapMediumBoostToInteger() { - val setting = VolumeAdaptionSetting.MEDIUM_BOOST - - MatcherAssert.assertThat(setting.toInteger(), Matchers.`is`(Matchers.equalTo(4))) - } - - @Test - fun mapHeavyBoostToInteger() { - val setting = VolumeAdaptionSetting.HEAVY_BOOST - - MatcherAssert.assertThat(setting.toInteger(), Matchers.`is`(Matchers.equalTo(5))) - } - - @Test - fun mapIntegerToVolumeAdaptionSetting() { - MatcherAssert.assertThat(fromInteger(0), Matchers.`is`(Matchers.equalTo(VolumeAdaptionSetting.OFF))) - MatcherAssert.assertThat(fromInteger(1), Matchers.`is`(Matchers.equalTo(VolumeAdaptionSetting.LIGHT_REDUCTION))) - MatcherAssert.assertThat(fromInteger(2), Matchers.`is`(Matchers.equalTo(VolumeAdaptionSetting.HEAVY_REDUCTION))) - MatcherAssert.assertThat(fromInteger(3), Matchers.`is`(Matchers.equalTo(VolumeAdaptionSetting.LIGHT_BOOST))) - MatcherAssert.assertThat(fromInteger(4), Matchers.`is`(Matchers.equalTo(VolumeAdaptionSetting.MEDIUM_BOOST))) - MatcherAssert.assertThat(fromInteger(5), Matchers.`is`(Matchers.equalTo(VolumeAdaptionSetting.HEAVY_BOOST))) - } - - @Test(expected = IllegalArgumentException::class) - fun cannotMapNegativeValues() { - fromInteger(-1) - } - - @Test(expected = IllegalArgumentException::class) - fun cannotMapValuesOutOfRange() { - fromInteger(6) - } - - @Test - fun noAdaptionIfTurnedOff() { - val adaptionFactor: Float = VolumeAdaptionSetting.OFF.adaptionFactor - Assert.assertEquals(1.0f, adaptionFactor, 0.01f) - } - - @Test - fun lightReductionYieldsHigherValueThanHeavyReduction() { - val lightReductionFactor: Float = VolumeAdaptionSetting.LIGHT_REDUCTION.adaptionFactor - - val heavyReductionFactor: Float = VolumeAdaptionSetting.HEAVY_REDUCTION.adaptionFactor - - Assert.assertTrue("Light reduction must have higher factor than heavy reduction", - lightReductionFactor > heavyReductionFactor) - } - - @Test - fun lightBoostYieldsHigherValueThanLightReduction() { - val lightReductionFactor: Float = VolumeAdaptionSetting.LIGHT_REDUCTION.adaptionFactor - - val lightBoostFactor: Float = VolumeAdaptionSetting.LIGHT_BOOST.adaptionFactor - - Assert.assertTrue("Light boost must have higher factor than light reduction", - lightBoostFactor > lightReductionFactor) - } - - @Test - fun mediumBoostYieldsHigherValueThanLightBoost() { - val lightBoostFactor: Float = VolumeAdaptionSetting.LIGHT_BOOST.adaptionFactor - - val mediumBoostFactor: Float = VolumeAdaptionSetting.MEDIUM_BOOST.adaptionFactor - - Assert.assertTrue("Medium boost must have higher factor than light boost", mediumBoostFactor > lightBoostFactor) - } - - @Test - fun heavyBoostYieldsHigherValueThanMediumBoost() { - val mediumBoostFactor: Float = VolumeAdaptionSetting.MEDIUM_BOOST.adaptionFactor - - val heavyBoostFactor: Float = VolumeAdaptionSetting.HEAVY_BOOST.adaptionFactor - - Assert.assertTrue("Heavy boost must have higher factor than medium boost", heavyBoostFactor > mediumBoostFactor) - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/net/download/serviceinterface/DownloadRequestTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/net/download/serviceinterface/DownloadRequestTest.kt deleted file mode 100644 index 93e2c6a1..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/net/download/serviceinterface/DownloadRequestTest.kt +++ /dev/null @@ -1,119 +0,0 @@ -package ac.mdiq.podcini.net.download.serviceinterface - -import ac.mdiq.podcini.net.download.service.DownloadRequest -import android.os.Bundle -import android.os.Parcel -import ac.mdiq.podcini.storage.model.EpisodeMedia -import org.junit.Assert -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner - -@RunWith(RobolectricTestRunner::class) -class DownloadRequestTest { - @Test - fun parcelInArrayListTest_WithAuth() { - doTestParcelInArrayList("case has authentication", - "usr1", "pass1", "usr2", "pass2") - } - - @Test - fun parcelInArrayListTest_NoAuth() { - doTestParcelInArrayList("case no authentication", - null, null, null, null) - } - - @Test - fun parcelInArrayListTest_MixAuth() { - doTestParcelInArrayList("case mixed authentication", - null, null, "usr2", "pass2") - } - - @Test - fun downloadRequestTestEquals() { - val destStr = "file://location/media.mp3" - val username = "testUser" - val password = "testPassword" - val item = createFeedItem(1) - val request1 = DownloadRequest.Builder(destStr, item) - .withAuthentication(username, password) - .build() - - val request2 = DownloadRequest.Builder(destStr, item) - .withAuthentication(username, password) - .build() - - val request3 = DownloadRequest.Builder(destStr, item) - .withAuthentication("diffUsername", "diffPassword") - .build() - - Assert.assertEquals(request1, request2) - Assert.assertNotEquals(request1, request3) - } - - // Test to ensure parcel using put/getParcelableArrayList() API work - // based on: https://stackoverflow.com/a/13507191 - private fun doTestParcelInArrayList(message: String, - username1: String?, password1: String?, - username2: String?, password2: String? - ) { - var toParcel: ArrayList - run { - // test DownloadRequests to parcel - val destStr = "file://location/media.mp3" - val item1 = createFeedItem(1) - val request1 = DownloadRequest.Builder(destStr, item1) - .withAuthentication(username1, password1) - .build() - - val item2 = createFeedItem(2) - val request2 = DownloadRequest.Builder(destStr, item2) - .withAuthentication(username2, password2) - .build() - - toParcel = ArrayList() - toParcel.add(request1) - toParcel.add(request2) - } - - // parcel the download requests - val bundleIn = Bundle() - bundleIn.putParcelableArrayList("r", toParcel) - - val parcel = Parcel.obtain() - bundleIn.writeToParcel(parcel, 0) - - val bundleOut = Bundle() - bundleOut.classLoader = DownloadRequest::class.java.classLoader - parcel.setDataPosition(0) // to read the parcel from the beginning. - bundleOut.readFromParcel(parcel) - - val fromParcel = bundleOut.getParcelableArrayList("r") - - // spot-check contents to ensure they are the same - // DownloadRequest.equals() implementation doesn't quite work - // for DownloadRequest.argument (a Bundle) - Assert.assertEquals( "$message - size", toParcel.size.toLong(), fromParcel!!.size.toLong()) - Assert.assertEquals("$message - source", toParcel[1].source, fromParcel[1].source) - Assert.assertEquals("$message - password", toParcel[0].password, fromParcel[0].password) - Assert.assertEquals("$message - argument", toString(toParcel[0].arguments), toString(fromParcel[0].arguments)) - } - - private fun createFeedItem(id: Int): EpisodeMedia { - // Use mockito would be less verbose, but it'll take extra 1 second for this tiny test - return EpisodeMedia(id.toLong(), null, 0, 0, 0, "", "", "http://example.com/episode$id", false, null, 0, 0) - } - - companion object { - private fun toString(b: Bundle?): String { - val sb = StringBuilder() - sb.append("{") - for (key in b!!.keySet()) { - val `val` = b[key] - sb.append("(").append(key).append(":").append(`val`).append(") ") - } - sb.append("}") - return sb.toString() - } - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/net/download/serviceinterface/DownloadServiceInterfaceTestStub.kt b/app/src/test/kotlin/ac/mdiq/podcini/net/download/serviceinterface/DownloadServiceInterfaceTestStub.kt deleted file mode 100644 index edfbd8a9..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/net/download/serviceinterface/DownloadServiceInterfaceTestStub.kt +++ /dev/null @@ -1,16 +0,0 @@ -package ac.mdiq.podcini.net.download.serviceinterface - -import ac.mdiq.podcini.net.download.service.DownloadServiceInterface -import ac.mdiq.podcini.storage.model.Episode -import ac.mdiq.podcini.storage.model.EpisodeMedia -import android.content.Context - -class DownloadServiceInterfaceTestStub : DownloadServiceInterface() { - override fun downloadNow(context: Context, item: Episode, ignoreConstraints: Boolean) {} - - override fun download(context: Context, item: Episode) {} - - override fun cancel(context: Context, media: EpisodeMedia) {} - - override fun cancelAll(context: Context) {} -} \ No newline at end of file diff --git a/app/src/test/kotlin/ac/mdiq/podcini/net/sync/HostnameParserTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/net/sync/HostnameParserTest.kt deleted file mode 100644 index a5952f1c..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/net/sync/HostnameParserTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -package ac.mdiq.podcini.net.sync - -import org.junit.Assert -import org.junit.Test - -class HostnameParserTest { - @Test - fun testHostOnly() { - assertHostname(HostnameParser("example.com"), "https", 443, "example.com", "") - assertHostname(HostnameParser("www.example.com"), "https", 443, "www.example.com", "") - } - - @Test - fun testHostAndPort() { - assertHostname(HostnameParser("example.com:443"), "https", 443, "example.com", "") - assertHostname(HostnameParser("example.com:80"), "http", 80, "example.com", "") - assertHostname(HostnameParser("example.com:123"), "https", 123, "example.com", "") - } - - @Test - fun testScheme() { - assertHostname(HostnameParser("https://example.com"), "https", 443, "example.com", "") - assertHostname(HostnameParser("https://example.com:80"), "https", 80, "example.com", "") - assertHostname(HostnameParser("http://example.com"), "http", 80, "example.com", "") - assertHostname(HostnameParser("http://example.com:443"), "http", 443, "example.com", "") - } - - @Test - fun testSubfolder() { - assertHostname(HostnameParser("https://example.com/"), "https", 443, "example.com", "") - assertHostname(HostnameParser("https://example.com/a"), "https", 443, "example.com", "/a") - assertHostname(HostnameParser("https://example.com/a/"), "https", 443, "example.com", "/a") - assertHostname(HostnameParser("https://example.com:42/a"), "https", 42, "example.com", "/a") - } - - private fun assertHostname(parser: HostnameParser, scheme: String, port: Int, host: String, subfolder: String) { - Assert.assertEquals(scheme, parser.scheme) - Assert.assertEquals(port.toLong(), parser.port.toLong()) - Assert.assertEquals(host, parser.host) - Assert.assertEquals(subfolder, parser.subfolder) - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/net/utils/UrlCheckerTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/net/utils/UrlCheckerTest.kt deleted file mode 100644 index 195d811e..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/net/utils/UrlCheckerTest.kt +++ /dev/null @@ -1,172 +0,0 @@ -package ac.mdiq.podcini.net.utils - -import ac.mdiq.podcini.net.utils.UrlChecker.prepareUrl -import ac.mdiq.podcini.net.utils.UrlChecker.urlEquals -import org.junit.Assert -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import java.io.UnsupportedEncodingException - -/** - * Test class for [UrlChecker] - */ -@RunWith(RobolectricTestRunner::class) -class UrlCheckerTest { - @Test - fun testCorrectURLHttp() { - val inVal = "http://example.com" - val out = prepareUrl(inVal) - Assert.assertEquals(inVal, out) - } - - @Test - fun testCorrectURLHttps() { - val inVal = "https://example.com" - val out = prepareUrl(inVal) - Assert.assertEquals(inVal, out) - } - - @Test - fun testMissingProtocol() { - val inVal = "example.com" - val out = prepareUrl(inVal) - Assert.assertEquals("http://example.com", out) - } - - @Test - fun testFeedProtocol() { - val inVal = "feed://example.com" - val out = prepareUrl(inVal) - Assert.assertEquals("http://example.com", out) - } - - @Test - fun testPcastProtocolNoScheme() { - val inVal = "pcast://example.com" - val out = prepareUrl(inVal) - Assert.assertEquals("http://example.com", out) - } - - @Test - fun testItpcProtocol() { - val inVal = "itpc://example.com" - val out = prepareUrl(inVal) - Assert.assertEquals("http://example.com", out) - } - - @Test - fun testItpcProtocolWithScheme() { - val inVal = "itpc://https://example.com" - val out = prepareUrl(inVal) - Assert.assertEquals("https://example.com", out) - } - - @Test - fun testWhiteSpaceUrlShouldNotAppend() { - val inVal = "\n http://example.com \t" - val out = prepareUrl(inVal) - Assert.assertEquals("http://example.com", out) - } - - @Test - fun testWhiteSpaceShouldAppend() { - val inVal = "\n example.com \t" - val out = prepareUrl(inVal) - Assert.assertEquals("http://example.com", out) - } - - @Test - fun testPodciniSubscribeProtocolNoScheme() { - val inVal = "podcini-subscribe://example.com" - val out = prepareUrl(inVal) - Assert.assertEquals("http://example.com", out) - } - - @Test - fun testPcastProtocolWithScheme() { - val inVal = "pcast://https://example.com" - val out = prepareUrl(inVal) - Assert.assertEquals("https://example.com", out) - } - - @Test - fun testPodciniSubscribeProtocolWithScheme() { - val inVal = "podcini-subscribe://https://example.com" - val out = prepareUrl(inVal) - Assert.assertEquals("https://example.com", out) - } - - @Test - @Throws(UnsupportedEncodingException::class) - fun testPodciniSubscribeDeeplink() { - val feed = "http://example.org/podcast.rss" -// Assert.assertEquals(feed, prepareUrl("https://podcini.org/deeplink/subscribe?url=$feed")) -// Assert.assertEquals(feed, prepareUrl("http://podcini.org/deeplink/subscribe?url=$feed")) -// Assert.assertEquals(feed, prepareUrl("http://podcini.org/deeplink/subscribe/?url=$feed")) -// Assert.assertEquals(feed, prepareUrl("https://www.podcini.org/deeplink/subscribe?url=$feed")) -// Assert.assertEquals(feed, prepareUrl("http://www.podcini.org/deeplink/subscribe?url=$feed")) -// Assert.assertEquals(feed, prepareUrl("http://www.podcini.org/deeplink/subscribe/?url=$feed")) -// Assert.assertEquals(feed, prepareUrl("http://www.podcini.org/deeplink/subscribe?url=" -// + URLEncoder.encode(feed, "UTF-8"))) -// Assert.assertEquals(feed, prepareUrl("http://www.podcini.org/deeplink/subscribe?url=" -// + "example.org/podcast.rss")) - } - - @Test - fun testProtocolRelativeUrlIsAbsolute() { - val inVal = "https://example.com" - val inBase = "http://examplebase.com" - val out = prepareUrl(inVal, inBase) - Assert.assertEquals(inVal, out) - } - - @Test - fun testProtocolRelativeUrlIsRelativeHttps() { - val inVal = "//example.com" - val inBase = "https://examplebase.com" - val out = prepareUrl(inVal, inBase) - Assert.assertEquals("https://example.com", out) - } - - @Test - fun testProtocolRelativeUrlIsHttpsWithApSubscribeProtocol() { - val inVal = "//example.com" - val inBase = "podcini-subscribe://https://examplebase.com" - val out = prepareUrl(inVal, inBase) - Assert.assertEquals("https://example.com", out) - } - - @Test - fun testProtocolRelativeUrlBaseUrlNull() { - val inVal = "example.com" - val out = prepareUrl(inVal, null) - Assert.assertEquals("http://example.com", out) - } - - @Test - fun testUrlEqualsSame() { - Assert.assertTrue(urlEquals("https://www.example.com/test", "https://www.example.com/test")) - Assert.assertTrue(urlEquals("https://www.example.com/test", "https://www.example.com/test/")) - Assert.assertTrue(urlEquals("https://www.example.com/test", "https://www.example.com//test")) - Assert.assertTrue(urlEquals("https://www.example.com", "https://www.example.com/")) - Assert.assertTrue(urlEquals("https://www.example.com", "http://www.example.com")) - Assert.assertTrue(urlEquals("http://www.example.com/", "https://www.example.com/")) - Assert.assertTrue(urlEquals("https://www.example.com/?id=42", "https://www.example.com/?id=42")) - Assert.assertTrue(urlEquals("https://example.com/podcast%20test", "https://example.com/podcast test")) - Assert.assertTrue(urlEquals("https://example.com/?a=podcast%20test", "https://example.com/?a=podcast test")) - Assert.assertTrue(urlEquals("https://example.com/?", "https://example.com/")) - Assert.assertTrue(urlEquals("https://example.com/?", "https://example.com")) - Assert.assertTrue(urlEquals("https://Example.com", "https://example.com")) - Assert.assertTrue(urlEquals("https://example.com/test", "https://example.com/Test")) - } - - @Test - fun testUrlEqualsDifferent() { - Assert.assertFalse(urlEquals("https://www.example.com/test", "https://www.example2.com/test")) - Assert.assertFalse(urlEquals("https://www.example.com/test", "https://www.example.de/test")) - Assert.assertFalse(urlEquals("https://example.com/", "https://otherpodcast.example.com/")) - Assert.assertFalse(urlEquals("https://www.example.com/?id=42&a=b", "https://www.example.com/?id=43&a=b")) - Assert.assertFalse(urlEquals("https://example.com/podcast%25test", "https://example.com/podcast test")) - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/element/AtomTextTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/element/AtomTextTest.kt deleted file mode 100644 index 0715da45..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/element/AtomTextTest.kt +++ /dev/null @@ -1,36 +0,0 @@ -package ac.mdiq.podcini.feed.parser.element.element - -import ac.mdiq.podcini.net.feed.parser.FeedHandler -import org.junit.Assert -import org.junit.Test -import org.junit.runner.RunWith - -import org.robolectric.RobolectricTestRunner - -/** - * Unit test for [AtomText]. - */ -@RunWith(RobolectricTestRunner::class) -class AtomTextTest { - @Test - fun testProcessingHtml() { - for (pair in TEST_DATA) { - val atomText = FeedHandler.AtomText("", FeedHandler.Atom(), FeedHandler.AtomText.TYPE_HTML) - atomText.setContent(pair[0]) - Assert.assertEquals(pair[1], atomText.processedContent) - } - } - - companion object { - private val TEST_DATA = arrayOf(arrayOf(">", ">"), - arrayOf(">", ">"), - arrayOf("<Français>", ""), - arrayOf("ßÄÖÜ", "ßÄÖÜ"), - arrayOf(""", "\""), - arrayOf("ß", "ß"), - arrayOf("’", "’"), - arrayOf("‰", "‰"), - arrayOf("€", "€") - ) - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/namespace/AtomParserTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/namespace/AtomParserTest.kt deleted file mode 100644 index 54adec05..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/namespace/AtomParserTest.kt +++ /dev/null @@ -1,92 +0,0 @@ -package ac.mdiq.podcini.feed.parser.element.namespace - -import ac.mdiq.podcini.storage.model.Feed -import junit.framework.TestCase.assertEquals -import org.junit.Assert -import org.junit.Test -import org.junit.runner.RunWith - -import org.robolectric.RobolectricTestRunner -import java.util.* - -/** - * Tests for Atom feeds in FeedHandler. - */ -@RunWith(RobolectricTestRunner::class) -class AtomParserTest { - @Test - @Throws(Exception::class) - fun testAtomBasic() { - val feedFile = FeedParserTestHelper.getFeedFile("feed-atom-testAtomBasic.xml") - val feed = FeedParserTestHelper.runFeedParser(feedFile) - Assert.assertEquals(Feed.FeedType.ATOM1.name, feed.type) - Assert.assertEquals("title", feed.title) - Assert.assertEquals("http://example.com/feed", feed.identifier) - Assert.assertEquals("http://example.com", feed.link) - Assert.assertEquals("This is the description", feed.description) - Assert.assertEquals("http://example.com/payment", feed.paymentLinks!![0].url) - Assert.assertEquals("http://example.com/picture", feed.imageUrl) - Assert.assertEquals(10, feed.episodes!!.size.toLong()) - for (i in feed.episodes!!.indices) { - val item = feed.episodes!![i] - Assert.assertEquals("http://example.com/item-$i", item.identifier) - Assert.assertEquals("item-$i", item.title) - Assert.assertNull(item.description) - Assert.assertEquals("http://example.com/items/$i", item.link) - assertEquals(Date((i * 60000).toLong()), item.getPubDate()) - Assert.assertNull(item.paymentLink) - Assert.assertEquals("http://example.com/picture", item.imageLocation) - // media - Assert.assertTrue(item.media != null) - val media = item.media - Assert.assertEquals("http://example.com/media-$i", media!!.downloadUrl) - Assert.assertEquals((1024 * 1024).toLong(), media.size) - Assert.assertEquals("audio/mp3", media.mimeType) - // chapters - Assert.assertNull(item.chapters) - } - } - - @Test - @Throws(Exception::class) - fun testEmptyRelLinks() { - val feedFile = FeedParserTestHelper.getFeedFile("feed-atom-testEmptyRelLinks.xml") - val feed = FeedParserTestHelper.runFeedParser(feedFile) - Assert.assertEquals(Feed.FeedType.ATOM1.name, feed.type) - Assert.assertEquals("title", feed.title) - Assert.assertEquals("http://example.com/feed", feed.identifier) - Assert.assertEquals("http://example.com", feed.link) - Assert.assertEquals("This is the description", feed.description) - Assert.assertNull(feed.paymentLinks) - Assert.assertEquals("http://example.com/picture", feed.imageUrl) - Assert.assertEquals(1, feed.episodes!!.size.toLong()) - - // feed entry - val item = feed.episodes!![0] - Assert.assertEquals("http://example.com/item-0", item.identifier) - Assert.assertEquals("item-0", item.title) - Assert.assertNull(item.description) - Assert.assertEquals("http://example.com/items/0", item.link) - assertEquals(Date(0), item.getPubDate()) - Assert.assertNull(item.paymentLink) - Assert.assertEquals("http://example.com/picture", item.imageLocation) - // media - Assert.assertFalse(item.media != null) - // chapters - Assert.assertNull(item.chapters) - } - - @Test - @Throws(Exception::class) - fun testLogoWithWhitespace() { - val feedFile = FeedParserTestHelper.getFeedFile("feed-atom-testLogoWithWhitespace.xml") - val feed = FeedParserTestHelper.runFeedParser(feedFile) - Assert.assertEquals("title", feed.title) - Assert.assertEquals("http://example.com/feed", feed.identifier) - Assert.assertEquals("http://example.com", feed.link) - Assert.assertEquals("This is the description", feed.description) - Assert.assertEquals("http://example.com/payment", feed.paymentLinks!![0].url) - Assert.assertEquals("https://example.com/image.png", feed.imageUrl) - Assert.assertEquals(0, feed.episodes!!.size.toLong()) - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/namespace/FeedParserTestHelper.kt b/app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/namespace/FeedParserTestHelper.kt deleted file mode 100644 index 7ca3c659..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/namespace/FeedParserTestHelper.kt +++ /dev/null @@ -1,31 +0,0 @@ -package ac.mdiq.podcini.feed.parser.element.namespace - -import ac.mdiq.podcini.net.feed.parser.FeedHandler -import ac.mdiq.podcini.storage.model.Feed -import java.io.File - -/** - * Tests for FeedHandler. - */ -object FeedParserTestHelper { - /** - * Returns the File object for a file in the resources folder. - */ - @JvmStatic - fun getFeedFile(fileName: String): File { - return File(FeedParserTestHelper::class.java.classLoader?.getResource(fileName)?.file?:"") - } - - /** - * Runs the feed parser on the given file. - */ - @JvmStatic - @Throws(Exception::class) - fun runFeedParser(feedFile: File): Feed { - val handler = FeedHandler() - val parsedFeed = Feed("http://example.com/feed", null) - parsedFeed.fileUrl = (feedFile.absolutePath) - handler.parseFeed(parsedFeed) - return parsedFeed - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/namespace/RssParserTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/namespace/RssParserTest.kt deleted file mode 100644 index 9e6f6761..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/namespace/RssParserTest.kt +++ /dev/null @@ -1,104 +0,0 @@ -package ac.mdiq.podcini.feed.parser.element.namespace - -import ac.mdiq.podcini.storage.model.Feed -import ac.mdiq.podcini.storage.model.MediaType -import ac.mdiq.podcini.feed.parser.element.namespace.FeedParserTestHelper.getFeedFile -import ac.mdiq.podcini.feed.parser.element.namespace.FeedParserTestHelper.runFeedParser -import junit.framework.TestCase.assertEquals -import org.junit.Assert -import org.junit.Test -import org.junit.runner.RunWith - -import org.robolectric.RobolectricTestRunner -import java.util.* - -/** - * Tests for RSS feeds in FeedHandler. - */ -@RunWith(RobolectricTestRunner::class) -class RssParserTest { - @Test - @Throws(Exception::class) - fun testRss2Basic() { - val feedFile = getFeedFile("feed-rss-testRss2Basic.xml") - val feed = runFeedParser(feedFile) - Assert.assertEquals(Feed.FeedType.RSS.name, feed.type) - Assert.assertEquals("title", feed.title) - Assert.assertEquals("en", feed.language) - Assert.assertEquals("http://example.com", feed.link) - Assert.assertEquals("This is the description", feed.description) - Assert.assertEquals("http://example.com/payment", feed.paymentLinks[0].url) - Assert.assertEquals("http://example.com/picture", feed.imageUrl) - Assert.assertEquals(10, feed.episodes.size.toLong()) - for (i in feed.episodes.indices) { - val item = feed.episodes[i] - Assert.assertEquals("http://example.com/item-$i", item.identifier) - Assert.assertEquals("item-$i", item.title) - Assert.assertNull(item.description) - Assert.assertEquals("http://example.com/items/$i", item.link) - assertEquals(Date((i * 60000).toLong()), item.getPubDate()) - Assert.assertNull(item.paymentLink) - Assert.assertEquals("http://example.com/picture", item.imageLocation) - // media - Assert.assertTrue(item.media != null) - val media = item.media - Assert.assertEquals("http://example.com/media-$i", media!!.downloadUrl) - Assert.assertEquals((1024 * 1024).toLong(), media.size) - Assert.assertEquals("audio/mp3", media.mimeType) - // chapters - Assert.assertNull(item.chapters) - } - } - - @Test - @Throws(Exception::class) - fun testImageWithWhitespace() { - val feedFile = getFeedFile("feed-rss-testImageWithWhitespace.xml") - val feed = runFeedParser(feedFile) - Assert.assertEquals("title", feed.title) - Assert.assertEquals("http://example.com", feed.link) - Assert.assertEquals("This is the description", feed.description) - Assert.assertEquals("http://example.com/payment", feed.paymentLinks[0].url) - Assert.assertEquals("https://example.com/image.png", feed.imageUrl) - Assert.assertEquals(0, feed.episodes.size.toLong()) - } - - @Test - @Throws(Exception::class) - fun testMediaContentMime() { - val feedFile = getFeedFile("feed-rss-testMediaContentMime.xml") - val feed = runFeedParser(feedFile) - Assert.assertEquals("title", feed.title) - Assert.assertEquals("http://example.com", feed.link) - Assert.assertEquals("This is the description", feed.description) - Assert.assertEquals("http://example.com/payment", feed.paymentLinks[0].url) - Assert.assertNull(feed.imageUrl) - Assert.assertEquals(1, feed.episodes.size.toLong()) - val feedItem = feed.episodes[0] - Assert.assertEquals(MediaType.VIDEO, feedItem.media!!.getMediaType()) - Assert.assertEquals("https://www.example.com/file.mp4", feedItem.media!!.downloadUrl) - } - - @Test - @Throws(Exception::class) - fun testMultipleFundingTags() { - val feedFile = getFeedFile("feed-rss-testMultipleFundingTags.xml") - val feed = runFeedParser(feedFile) - Assert.assertEquals(3, feed.paymentLinks.size.toLong()) - Assert.assertEquals("Text 1", feed.paymentLinks[0].content) - Assert.assertEquals("https://example.com/funding1", feed.paymentLinks[0].url) - Assert.assertEquals("Text 2", feed.paymentLinks[1].content) - Assert.assertEquals("https://example.com/funding2", feed.paymentLinks[1].url) - Assert.assertTrue(feed.paymentLinks[2].content.isNullOrBlank()) - Assert.assertEquals("https://example.com/funding3", feed.paymentLinks[2].url) - } - - @Test - @Throws(Exception::class) - fun testUnsupportedElements() { - val feedFile = getFeedFile("feed-rss-testUnsupportedElements.xml") - val feed = runFeedParser(feedFile) - Assert.assertEquals(1, feed.episodes.size.toLong()) - Assert.assertEquals("item-0", feed.episodes[0].title) - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/util/DateUtilsTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/util/DateUtilsTest.kt deleted file mode 100644 index 0cfbacbd..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/util/DateUtilsTest.kt +++ /dev/null @@ -1,169 +0,0 @@ -package ac.mdiq.podcini.feed.parser.element.util - -import ac.mdiq.podcini.net.feed.parser.utils.DateUtils.parse -import org.junit.Assert -import org.junit.Test -import java.util.* - -/** - * Unit test for [DateUtils]. - */ -class DateUtilsTest { - @Test - fun testParseDateWithMicroseconds() { - val exp = GregorianCalendar(2015, 2, 28, 13, 31, 4) - exp.timeZone = TimeZone.getTimeZone("UTC") - val expected = Date(exp.timeInMillis + 963) - val actual = parse("2015-03-28T13:31:04.963870") - Assert.assertEquals(expected, actual) - } - - @Test - fun testParseDateWithCentiseconds() { - val exp = GregorianCalendar(2015, 2, 28, 13, 31, 4) - exp.timeZone = TimeZone.getTimeZone("UTC") - val expected = Date(exp.timeInMillis + 960) - val actual = parse("2015-03-28T13:31:04.96") - Assert.assertEquals(expected, actual) - } - - @Test - fun testParseDateWithDeciseconds() { - val exp = GregorianCalendar(2015, 2, 28, 13, 31, 4) - exp.timeZone = TimeZone.getTimeZone("UTC") - val expected = Date(exp.timeInMillis + 900) - val actual = parse("2015-03-28T13:31:04.9") - Assert.assertEquals(expected.time / 1000, actual!!.time / 1000) - Assert.assertEquals(900, actual.time % 1000) - } - - @Test - fun testParseDateWithMicrosecondsAndTimezone() { - val exp = GregorianCalendar(2015, 2, 28, 6, 31, 4) - exp.timeZone = TimeZone.getTimeZone("UTC") - val expected = Date(exp.timeInMillis + 963) - val actual = parse("2015-03-28T13:31:04.963870 +0700") - Assert.assertEquals(expected, actual) - } - - @Test - fun testParseDateWithCentisecondsAndTimezone() { - val exp = GregorianCalendar(2015, 2, 28, 6, 31, 4) - exp.timeZone = TimeZone.getTimeZone("UTC") - val expected = Date(exp.timeInMillis + 960) - val actual = parse("2015-03-28T13:31:04.96 +0700") - Assert.assertEquals(expected, actual) - } - - @Test - fun testParseDateWithDecisecondsAndTimezone() { - val exp = GregorianCalendar(2015, 2, 28, 6, 31, 4) - exp.timeZone = TimeZone.getTimeZone("UTC") - val expected = Date(exp.timeInMillis + 900) - val actual = parse("2015-03-28T13:31:04.9 +0700") - Assert.assertEquals(expected.time / 1000, actual!!.time / 1000) - Assert.assertEquals(900, actual.time % 1000) - } - - @Test - fun testParseDateWithTimezoneName() { - val exp = GregorianCalendar(2015, 2, 28, 6, 31, 4) - exp.timeZone = TimeZone.getTimeZone("UTC") - val expected = Date(exp.timeInMillis) - val actual = parse("Sat, 28 Mar 2015 01:31:04 EST") - Assert.assertEquals(expected, actual) - } - - @Test - fun testParseDateWithTimezoneName2() { - val exp = GregorianCalendar(2015, 2, 28, 6, 31, 0) - exp.timeZone = TimeZone.getTimeZone("UTC") - val expected = Date(exp.timeInMillis) - val actual = parse("Sat, 28 Mar 2015 01:31 EST") - Assert.assertEquals(expected, actual) - } - - @Test - fun testParseDateWithTimeZoneOffset() { - val exp = GregorianCalendar(2015, 2, 28, 12, 16, 12) - exp.timeZone = TimeZone.getTimeZone("UTC") - val expected = Date(exp.timeInMillis) - val actual = parse("Sat, 28 March 2015 08:16:12 -0400") - Assert.assertEquals(expected, actual) - } - - @Test - fun testAsctime() { - val exp = GregorianCalendar(2011, 4, 25, 12, 33, 0) - exp.timeZone = TimeZone.getTimeZone("UTC") - val expected = Date(exp.timeInMillis) - val actual = parse("Wed, 25 May 2011 12:33:00") - Assert.assertEquals(expected, actual) - } - - @Test - fun testMultipleConsecutiveSpaces() { - val exp = GregorianCalendar(2010, 2, 23, 6, 6, 26) - exp.timeZone = TimeZone.getTimeZone("UTC") - val expected = Date(exp.timeInMillis) - val actual = parse("Tue, 23 Mar 2010 01:06:26 -0500") - Assert.assertEquals(expected, actual) - } - - @Test - fun testParseDateWithNoTimezonePadding() { - val exp = GregorianCalendar(2017, 1, 22, 22, 28, 0) - exp.timeZone = TimeZone.getTimeZone("UTC") - val expected = Date(exp.timeInMillis + 2) - val actual = parse("2017-02-22T14:28:00.002-08:00") - Assert.assertEquals(expected, actual) - } - - /** - * Requires Android platform. Root cause: [DateUtils] implementation makes - * use of ISO 8601 time zone, which does not work on standard JDK. - * - * @see .testParseDateWithNoTimezonePadding - */ - @Test - fun testParseDateWithForCest() { - val exp1 = GregorianCalendar(2017, 0, 28, 22, 0, 0) - exp1.timeZone = TimeZone.getTimeZone("UTC") - val expected1 = Date(exp1.timeInMillis) - val actual1 = parse("Sun, 29 Jan 2017 00:00:00 CEST") - Assert.assertEquals(expected1, actual1) - - val exp2 = GregorianCalendar(2017, 0, 28, 23, 0, 0) - exp2.timeZone = TimeZone.getTimeZone("UTC") - val expected2 = Date(exp2.timeInMillis) - val actual2 = parse("Sun, 29 Jan 2017 00:00:00 CET") - Assert.assertEquals(expected2, actual2) - } - - @Test - fun testParseDateWithIncorrectWeekday() { - val exp1 = GregorianCalendar(2014, 9, 8, 9, 0, 0) - exp1.timeZone = TimeZone.getTimeZone("GMT") - val expected = Date(exp1.timeInMillis) - val actual = parse("Thu, 8 Oct 2014 09:00:00 GMT") // actually a Wednesday - Assert.assertEquals(expected, actual) - } - - @Test - fun testParseDateWithBadAbbreviation() { - val exp1 = GregorianCalendar(2014, 8, 8, 0, 0, 0) - exp1.timeZone = TimeZone.getTimeZone("GMT") - val expected = Date(exp1.timeInMillis) - val actual = parse("Mon, 8 Sept 2014 00:00:00 GMT") // should be Sep - Assert.assertEquals(expected, actual) - } - - @Test - fun testParseDateWithTwoTimezones() { - val exp1 = GregorianCalendar(2015, Calendar.MARCH, 1, 1, 0, 0) - exp1.timeZone = TimeZone.getTimeZone("GMT-4") - val expected = Date(exp1.timeInMillis) - val actual = parse("Sun 01 Mar 2015 01:00:00 GMT-0400 (EDT)") - Assert.assertEquals(expected, actual) - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/util/DurationParserTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/util/DurationParserTest.kt deleted file mode 100644 index bded0838..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/util/DurationParserTest.kt +++ /dev/null @@ -1,43 +0,0 @@ -package ac.mdiq.podcini.feed.parser.element.util - -import ac.mdiq.podcini.net.feed.parser.utils.DurationParser.inMillis -import org.junit.Assert -import org.junit.Test - -class DurationParserTest { - private val milliseconds = 1 - private val seconds = 1000 * milliseconds - private val minutes = 60 * seconds - private val hours = 60 * minutes - - @Test - fun testSecondDurationInMillis() { - val duration = inMillis("00:45") - Assert.assertEquals((45 * seconds).toLong(), duration) - } - - @Test - fun testSingleNumberDurationInMillis() { - val twoHoursInSeconds = 2 * 60 * 60 - val duration = inMillis(twoHoursInSeconds.toString()) - Assert.assertEquals((2 * hours).toLong(), duration) - } - - @Test - fun testMinuteSecondDurationInMillis() { - val duration = inMillis("05:10") - Assert.assertEquals((5 * minutes + 10 * seconds).toLong(), duration) - } - - @Test - fun testHourMinuteSecondDurationInMillis() { - val duration = inMillis("02:15:45") - Assert.assertEquals((2 * hours + 15 * minutes + 45 * seconds).toLong(), duration) - } - - @Test - fun testSecondsWithMillisecondsInMillis() { - val duration = inMillis("00:00:00.123") - Assert.assertEquals(123, duration) - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/parser/media/id3/ChapterRReaderTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/parser/media/id3/ChapterRReaderTest.kt deleted file mode 100644 index 52fd301c..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/parser/media/id3/ChapterRReaderTest.kt +++ /dev/null @@ -1,214 +0,0 @@ -package ac.mdiq.podcini.feed.parser.media.id3 - -import ac.mdiq.podcini.net.feed.parser.media.id3.ChapterReader -import ac.mdiq.podcini.net.feed.parser.media.id3.ID3Reader -import ac.mdiq.podcini.net.feed.parser.media.id3.ID3ReaderException -import ac.mdiq.podcini.storage.model.Chapter -import ac.mdiq.podcini.storage.model.EmbeddedChapterImage.Companion.makeUrl -import ac.mdiq.podcini.net.feed.parser.media.id3.model.FrameHeader -import org.apache.commons.io.input.CountingInputStream -import org.junit.Assert -import org.junit.Test -import org.junit.runner.RunWith - -import org.robolectric.RobolectricTestRunner -import java.io.ByteArrayInputStream -import java.io.IOException - -@RunWith(RobolectricTestRunner::class) -class ChapterRReaderTest { - @Test - @Throws(IOException::class, ID3ReaderException::class) - fun testReadFullTagWithChapter() { - val chapter = Id3ReaderTest.concat( - Id3ReaderTest.generateFrameHeader(ChapterReader.FRAME_ID_CHAPTER, CHAPTER_WITHOUT_SUBFRAME.size), - CHAPTER_WITHOUT_SUBFRAME) - val data = Id3ReaderTest.concat( - Id3ReaderTest.generateId3Header(chapter.size), - chapter) - val inputStream = CountingInputStream(ByteArrayInputStream(data)) - val reader = ChapterReader(inputStream) - reader.readInputStream() - Assert.assertEquals(1, reader.getChapters().size.toLong()) - Assert.assertEquals(CHAPTER_WITHOUT_SUBFRAME_START_TIME.toLong(), reader.getChapters()[0].start) - } - - @Test - @Throws(IOException::class, ID3ReaderException::class) - fun testReadFullTagWithMultipleChapters() { - val chapter = Id3ReaderTest.concat( - Id3ReaderTest.generateFrameHeader(ChapterReader.FRAME_ID_CHAPTER, CHAPTER_WITHOUT_SUBFRAME.size), - CHAPTER_WITHOUT_SUBFRAME) - val data = Id3ReaderTest.concat( - Id3ReaderTest.generateId3Header(2 * chapter.size), - chapter, - chapter) - val inputStream = CountingInputStream(ByteArrayInputStream(data)) - val reader = ChapterReader(inputStream) - reader.readInputStream() - Assert.assertEquals(2, reader.getChapters().size.toLong()) - Assert.assertEquals(CHAPTER_WITHOUT_SUBFRAME_START_TIME.toLong(), reader.getChapters()[0].start) - Assert.assertEquals(CHAPTER_WITHOUT_SUBFRAME_START_TIME.toLong(), reader.getChapters()[1].start) - } - - @Test - @Throws(IOException::class, ID3ReaderException::class) - fun testReadChapterWithoutSubframes() { - val header = FrameHeader(ChapterReader.FRAME_ID_CHAPTER, - CHAPTER_WITHOUT_SUBFRAME.size, 0.toShort()) - val inputStream = CountingInputStream(ByteArrayInputStream(CHAPTER_WITHOUT_SUBFRAME)) - val chapter = ChapterReader(inputStream).readChapter(header) - Assert.assertEquals(CHAPTER_WITHOUT_SUBFRAME_START_TIME.toLong(), chapter.start) - } - - @Test - @Throws(IOException::class, ID3ReaderException::class) - fun testReadChapterWithTitle() { - val title = byteArrayOf(ID3Reader.ENCODING_ISO, - 'H'.code.toByte(), 'e'.code.toByte(), 'l'.code.toByte(), 'l'.code.toByte(), 'o'.code.toByte(), // Title - 0 // Null-terminated - ) - val chapterData = Id3ReaderTest.concat( - CHAPTER_WITHOUT_SUBFRAME, - Id3ReaderTest.generateFrameHeader(ChapterReader.FRAME_ID_TITLE, title.size), - title) - val header = FrameHeader(ChapterReader.FRAME_ID_CHAPTER, chapterData.size, 0.toShort()) - val inputStream = CountingInputStream(ByteArrayInputStream(chapterData)) - val reader = ChapterReader(inputStream) - val chapter = reader.readChapter(header) - Assert.assertEquals(CHAPTER_WITHOUT_SUBFRAME_START_TIME.toLong(), chapter.start) - Assert.assertEquals("Hello", chapter.title) - } - - @Test - @Throws(IOException::class, ID3ReaderException::class) - fun testReadTitleWithGarbage() { - val titleSubframeContent = byteArrayOf(ID3Reader.ENCODING_ISO, - 'A'.code.toByte(), // Title - 0, // Null-terminated - 42, 42, 42, 42 // Garbage, should be ignored - ) - val header = FrameHeader(ChapterReader.FRAME_ID_TITLE, titleSubframeContent.size, 0.toShort()) - val inputStream = CountingInputStream(ByteArrayInputStream(titleSubframeContent)) - val reader = ChapterReader(inputStream) - val chapter = Chapter() - reader.readChapterSubFrame(header, chapter) - Assert.assertEquals("A", chapter.title) - - // Should skip the garbage and point to the next frame - Assert.assertEquals(titleSubframeContent.size.toLong(), reader.position.toLong()) - } - - @Test - @Throws(IOException::class, ID3ReaderException::class) - fun testRealFileUltraschall() { - val inputStream = CountingInputStream(javaClass.classLoader?.getResource("ultraschall5.mp3")?.openStream()) - val reader = ChapterReader(inputStream) - reader.readInputStream() - val chapters = reader.getChapters() - - Assert.assertEquals(3, chapters.size.toLong()) - - Assert.assertEquals(0, chapters[0].start) - Assert.assertEquals(4004, chapters[1].start) - Assert.assertEquals(7999, chapters[2].start) - - Assert.assertEquals("Marke 1", chapters[0].title) - Assert.assertEquals("Marke 2", chapters[1].title) - Assert.assertEquals("Marke 3", chapters[2].title) - - Assert.assertEquals("https://example.com", chapters[0].link) - Assert.assertEquals("https://example.com", chapters[1].link) - Assert.assertEquals("https://example.com", chapters[2].link) - - Assert.assertEquals(makeUrl(16073, 2750569), chapters[0].imageUrl) - Assert.assertEquals(makeUrl(2766765, 15740), chapters[1].imageUrl) - Assert.assertEquals(makeUrl(2782628, 2750569), chapters[2].imageUrl) - } - - @Test - @Throws(IOException::class, ID3ReaderException::class) - fun testRealFileAuphonic() { - val inputStream = CountingInputStream(javaClass.classLoader?.getResource("auphonic.mp3")?.openStream()) - val reader = ChapterReader(inputStream) - reader.readInputStream() - val chapters = reader.getChapters() - - Assert.assertEquals(4, chapters.size.toLong()) - - Assert.assertEquals(0, chapters[0].start) - Assert.assertEquals(3000, chapters[1].start) - Assert.assertEquals(6000, chapters[2].start) - Assert.assertEquals(9000, chapters[3].start) - - Assert.assertEquals("Chapter 1 - ❤️😊", chapters[0].title) - Assert.assertEquals("Chapter 2 - ßöÄ", chapters[1].title) - Assert.assertEquals("Chapter 3 - 爱", chapters[2].title) - Assert.assertEquals("Chapter 4", chapters[3].title) - - Assert.assertEquals("https://example.com", chapters[0].link) - Assert.assertEquals("https://example.com", chapters[1].link) - Assert.assertEquals("https://example.com", chapters[2].link) - Assert.assertEquals("https://example.com", chapters[3].link) - - Assert.assertEquals(makeUrl(765, 308), chapters[0].imageUrl) - Assert.assertEquals(makeUrl(1271, 308), chapters[1].imageUrl) - Assert.assertEquals(makeUrl(1771, 308), chapters[2].imageUrl) - Assert.assertEquals(makeUrl(2259, 308), chapters[3].imageUrl) - } - - @Test - @Throws(IOException::class, ID3ReaderException::class) - fun testRealFileHindenburgJournalistPro() { - val inputStream = CountingInputStream(javaClass.classLoader?.getResource("hindenburg-journalist-pro.mp3")?.openStream()) - val reader = ChapterReader(inputStream) - reader.readInputStream() - val chapters = reader.getChapters() - - Assert.assertEquals(2, chapters.size.toLong()) - - Assert.assertEquals(0, chapters[0].start) - Assert.assertEquals(5006, chapters[1].start) - - Assert.assertEquals("Chapter Marker 1", chapters[0].title) - Assert.assertEquals("Chapter Marker 2", chapters[1].title) - - Assert.assertEquals("https://example.com/chapter1url", chapters[0].link) - Assert.assertEquals("https://example.com/chapter2url", chapters[1].link) - - Assert.assertEquals(makeUrl(5330, 4015), chapters[0].imageUrl) - Assert.assertEquals(makeUrl(9498, 4364), chapters[1].imageUrl) - } - - @Test - @Throws(IOException::class, ID3ReaderException::class) - fun testRealFileMp3chapsPy() { - val inputStream = CountingInputStream(javaClass.classLoader?.getResource("mp3chaps-py.mp3")?.openStream()) - val reader = ChapterReader(inputStream) - reader.readInputStream() - val chapters = reader.getChapters() - - Assert.assertEquals(4, chapters.size.toLong()) - - Assert.assertEquals(0, chapters[0].start) - Assert.assertEquals(7000, chapters[1].start) - Assert.assertEquals(9000, chapters[2].start) - Assert.assertEquals(11000, chapters[3].start) - - Assert.assertEquals("Start", chapters[0].title) - Assert.assertEquals("Chapter 1", chapters[1].title) - Assert.assertEquals("Chapter 2", chapters[2].title) - Assert.assertEquals("Chapter 3", chapters[3].title) - } - - companion object { - private const val CHAPTER_WITHOUT_SUBFRAME_START_TIME: Byte = 23 - private val CHAPTER_WITHOUT_SUBFRAME = - byteArrayOf('C'.code.toByte(), 'H'.code.toByte(), '1'.code.toByte(), 0, // String ID for mapping to CTOC - 0, 0, 0, CHAPTER_WITHOUT_SUBFRAME_START_TIME, // Start time - 0, 0, 0, 0, // End time - 0, 0, 0, 0, // Start offset - 0, 0, 0, 0 // End offset - ) - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/parser/media/id3/Id3ReaderTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/parser/media/id3/Id3ReaderTest.kt deleted file mode 100644 index 82951d4f..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/parser/media/id3/Id3ReaderTest.kt +++ /dev/null @@ -1,156 +0,0 @@ -package ac.mdiq.podcini.feed.parser.media.id3 - -import ac.mdiq.podcini.net.feed.parser.media.id3.ID3ReaderException -import ac.mdiq.podcini.net.feed.parser.media.id3.ID3Reader -import org.apache.commons.io.input.CountingInputStream -import org.junit.Assert -import org.junit.Test -import org.junit.runner.RunWith - -import org.robolectric.RobolectricTestRunner -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.io.IOException -import java.nio.charset.StandardCharsets - -@RunWith(RobolectricTestRunner::class) -class Id3ReaderTest { - @Test - @Throws(IOException::class) - fun testReadString() { - val data = byteArrayOf(ID3Reader.ENCODING_ISO, - 'T'.code.toByte(), 'e'.code.toByte(), 's'.code.toByte(), 't'.code.toByte(), - 0 // Null-terminated - ) - val inputStream = CountingInputStream(ByteArrayInputStream(data)) - val string = ID3Reader(inputStream).readEncodingAndString(1000) - Assert.assertEquals("Test", string) - } - - @Test - @Throws(IOException::class) - fun testReadMultipleStrings() { - val data = byteArrayOf(ID3Reader.ENCODING_ISO, - 'F'.code.toByte(), 'o'.code.toByte(), 'o'.code.toByte(), - 0, // Null-terminated - ID3Reader.ENCODING_ISO, - 'B'.code.toByte(), 'a'.code.toByte(), 'r'.code.toByte(), - 0 // Null-terminated - ) - val inputStream = CountingInputStream(ByteArrayInputStream(data)) - val reader = ID3Reader(inputStream) - Assert.assertEquals("Foo", reader.readEncodingAndString(1000)) - Assert.assertEquals("Bar", reader.readEncodingAndString(1000)) - } - - @Test - @Throws(IOException::class) - fun testReadingLimit() { - val data = byteArrayOf(ID3Reader.ENCODING_ISO, - 'A'.code.toByte(), 'B'.code.toByte(), 'C'.code.toByte(), 'D'.code.toByte() - ) - val inputStream = CountingInputStream(ByteArrayInputStream(data)) - val reader = ID3Reader(inputStream) - Assert.assertEquals("ABC", reader.readEncodingAndString(4)) // Includes encoding - Assert.assertEquals('D'.code.toLong(), reader.readByte().toLong()) - } - - @Test - @Throws(IOException::class) - fun testReadUtf16RespectsBom() { - val data = byteArrayOf( - ID3Reader.ENCODING_UTF16_WITH_BOM, - 0xff.toByte(), 0xfe.toByte(), // BOM: Little-endian - 'A'.code.toByte(), 0, 'B'.code.toByte(), 0, 'C'.code.toByte(), 0, - 0, 0, // Null-terminated - ID3Reader.ENCODING_UTF16_WITH_BOM, - 0xfe.toByte(), 0xff.toByte(), // BOM: Big-endian - 0, 'D'.code.toByte(), 0, 'E'.code.toByte(), 0, 'F'.code.toByte(), - 0, 0, // Null-terminated - ) - val inputStream = CountingInputStream(ByteArrayInputStream(data)) - val reader = ID3Reader(inputStream) - Assert.assertEquals("ABC", reader.readEncodingAndString(1000)) - Assert.assertEquals("DEF", reader.readEncodingAndString(1000)) - } - - @Test - @Throws(IOException::class) - fun testReadUtf16NullPrefix() { - val data = byteArrayOf( - ID3Reader.ENCODING_UTF16_WITH_BOM, - 0xff.toByte(), 0xfe.toByte(), // BOM - 0x00, 0x01, // Latin Capital Letter A with macron (Ā) - 0, 0, // Null-terminated - ) - val inputStream = CountingInputStream(ByteArrayInputStream(data)) - val string = ID3Reader(inputStream).readEncodingAndString(1000) - Assert.assertEquals("Ā", string) - } - - @Test - @Throws(IOException::class) - fun testReadingLimitUtf16() { - val data = byteArrayOf(ID3Reader.ENCODING_UTF16_WITHOUT_BOM, - 'A'.code.toByte(), 0, 'B'.code.toByte(), 0, 'C'.code.toByte(), 0, 'D'.code.toByte(), 0 - ) - val inputStream = CountingInputStream(ByteArrayInputStream(data)) - val reader = ID3Reader(inputStream) - reader.readEncodingAndString(6) // Includes encoding, produces broken string - Assert.assertTrue("Should respect limit even if it breaks a symbol", reader.position <= 6) - } - - @Test - @Throws(IOException::class, ID3ReaderException::class) - fun testReadTagHeader() { - val data = generateId3Header(23) - val inputStream = CountingInputStream(ByteArrayInputStream(data)) - val header = ID3Reader(inputStream).readTagHeader() - Assert.assertEquals("ID3", header.id) - Assert.assertEquals(42, header.version.toLong()) - Assert.assertEquals(23, header.size.toLong()) - } - - @Test - @Throws(IOException::class) - fun testReadFrameHeader() { - val data = generateFrameHeader("CHAP", 42) - val inputStream = CountingInputStream(ByteArrayInputStream(data)) - val header = ID3Reader(inputStream).readFrameHeader() - Assert.assertEquals("CHAP", header.id) - Assert.assertEquals(42, header.size.toLong()) - } - - companion object { - fun generateFrameHeader(id: String, size: Int): ByteArray { - return concat( - id.toByteArray(StandardCharsets.ISO_8859_1), // Frame ID - byteArrayOf((size shr 24).toByte(), (size shr 16).toByte(), - (size shr 8).toByte(), size.toByte(), // Size - 0, 0 // Flags - )) - } - - fun generateId3Header(size: Int): ByteArray { - return byteArrayOf( - 'I'.code.toByte(), 'D'.code.toByte(), '3'.code.toByte(), // Identifier - 0, 42, // Version - 0, // Flags - (size shr 24).toByte(), (size shr 16).toByte(), - (size shr 8).toByte(), size.toByte(), // Size - ) - } - - fun concat(vararg arrays: ByteArray?): ByteArray { - val outputStream = ByteArrayOutputStream() - try { - for (array in arrays) { - outputStream.write(array) - } - } catch (e: IOException) { - Assert.fail(e.message) - } - return outputStream.toByteArray() - } - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/parser/media/id3/MetadataReaderTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/parser/media/id3/MetadataReaderTest.kt deleted file mode 100644 index 61f8f79c..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/parser/media/id3/MetadataReaderTest.kt +++ /dev/null @@ -1,55 +0,0 @@ -package ac.mdiq.podcini.feed.parser.media.id3 - -import ac.mdiq.podcini.net.feed.parser.media.id3.ID3ReaderException -import ac.mdiq.podcini.net.feed.parser.media.id3.Id3MetadataReader -import org.apache.commons.io.input.CountingInputStream -import org.junit.Assert -import org.junit.Test -import org.junit.runner.RunWith - -import org.robolectric.RobolectricTestRunner -import java.io.IOException - -@RunWith(RobolectricTestRunner::class) -class MetadataReaderTest { - @Test - @Throws(IOException::class, ID3ReaderException::class) - fun testRealFileUltraschall() { - val inputStream = CountingInputStream(javaClass.classLoader - .getResource("ultraschall5.mp3").openStream()) - val reader = Id3MetadataReader(inputStream) - reader.readInputStream() - Assert.assertEquals("Description", reader.comment) - } - - @Test - @Throws(IOException::class, ID3ReaderException::class) - fun testRealFileAuphonic() { - val inputStream = CountingInputStream(javaClass.classLoader - .getResource("auphonic.mp3").openStream()) - val reader = Id3MetadataReader(inputStream) - reader.readInputStream() - Assert.assertEquals("Summary", reader.comment) - } - - @Test - @Throws(IOException::class, ID3ReaderException::class) - fun testRealFileHindenburgJournalistPro() { - val inputStream = CountingInputStream(javaClass.classLoader - .getResource("hindenburg-journalist-pro.mp3").openStream()) - val reader = Id3MetadataReader(inputStream) - reader.readInputStream() - Assert.assertEquals("This is the summary of this podcast episode. This file was made with" - + " Hindenburg Journalist Pro version 1.85, build number 2360.", reader.comment) - } - - @Test - @Throws(IOException::class, ID3ReaderException::class) - fun testRealFileMp3chapsPy() { - val inputStream = CountingInputStream(javaClass.classLoader - .getResource("mp3chaps-py.mp3").openStream()) - val reader = Id3MetadataReader(inputStream) - reader.readInputStream() - Assert.assertEquals("2021.08.13", reader.comment) - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/parser/media/vorbis/VorbisCommentChapterRReaderTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/parser/media/vorbis/VorbisCommentChapterRReaderTest.kt deleted file mode 100644 index 7df5b6c0..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/parser/media/vorbis/VorbisCommentChapterRReaderTest.kt +++ /dev/null @@ -1,46 +0,0 @@ -package ac.mdiq.podcini.feed.parser.media.vorbis - -import ac.mdiq.podcini.net.feed.parser.media.vorbis.VorbisCommentReaderException -import ac.mdiq.podcini.net.feed.parser.media.vorbis.VorbisCommentChapterReader -import org.junit.Assert -import org.junit.Test -import org.junit.runner.RunWith - -import org.robolectric.RobolectricTestRunner -import java.io.IOException - -@RunWith(RobolectricTestRunner::class) -class VorbisCommentChapterRReaderTest { - @Test - @Throws(IOException::class, VorbisCommentReaderException::class) - fun testRealFilesAuphonic() { - testRealFileAuphonic("auphonic.ogg") - testRealFileAuphonic("auphonic.opus") - } - - @Throws(IOException::class, VorbisCommentReaderException::class) - fun testRealFileAuphonic(filename: String?) { - val inputStream = javaClass.classLoader - .getResource(filename).openStream() - val reader = VorbisCommentChapterReader(inputStream) - reader.readInputStream() - val chapters = reader.getChapters() - - Assert.assertEquals(4, chapters.size.toLong()) - - Assert.assertEquals(0, chapters[0].start) - Assert.assertEquals(3000, chapters[1].start) - Assert.assertEquals(6000, chapters[2].start) - Assert.assertEquals(9000, chapters[3].start) - - Assert.assertEquals("Chapter 1 - ❤️😊", chapters[0].title) - Assert.assertEquals("Chapter 2 - ßöÄ", chapters[1].title) - Assert.assertEquals("Chapter 3 - 爱", chapters[2].title) - Assert.assertEquals("Chapter 4", chapters[3].title) - - Assert.assertEquals("https://example.com", chapters[0].link) - Assert.assertEquals("https://example.com", chapters[1].link) - Assert.assertEquals("https://example.com", chapters[2].link) - Assert.assertEquals("https://example.com", chapters[3].link) - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/parser/media/vorbis/VorbisCommentMetadataReaderTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/parser/media/vorbis/VorbisCommentMetadataReaderTest.kt deleted file mode 100644 index fe18ab45..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/parser/media/vorbis/VorbisCommentMetadataReaderTest.kt +++ /dev/null @@ -1,29 +0,0 @@ -package ac.mdiq.podcini.feed.parser.media.vorbis - -import ac.mdiq.podcini.net.feed.parser.media.vorbis.VorbisCommentReaderException -import ac.mdiq.podcini.net.feed.parser.media.vorbis.VorbisCommentMetadataReader -import org.junit.Assert -import org.junit.Test -import org.junit.runner.RunWith - -import org.robolectric.RobolectricTestRunner -import java.io.IOException - -@RunWith(RobolectricTestRunner::class) -class VorbisCommentMetadataReaderTest { - @Test - @Throws(IOException::class, VorbisCommentReaderException::class) - fun testRealFilesAuphonic() { - testRealFileAuphonic("auphonic.ogg") - testRealFileAuphonic("auphonic.opus") - } - - @Throws(IOException::class, VorbisCommentReaderException::class) - fun testRealFileAuphonic(filename: String?) { - val inputStream = javaClass.classLoader - .getResource(filename).openStream() - val reader = VorbisCommentMetadataReader(inputStream) - reader.readInputStream() - Assert.assertEquals("Summary", reader.description) - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/playback/base/RewindAfterPauseUtilTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/playback/base/RewindAfterPauseUtilTest.kt deleted file mode 100644 index 931190ef..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/playback/base/RewindAfterPauseUtilTest.kt +++ /dev/null @@ -1,53 +0,0 @@ -package ac.mdiq.podcini.playback.base - -import ac.mdiq.podcini.playback.base.MediaPlayerBase.Companion.calculatePositionWithRewind -import org.junit.Assert -import org.junit.Test - - -class RewindAfterPauseUtilTest { - @Test - fun testCalculatePositionWithRewindNoRewind() { - val ORIGINAL_POSITION = 10000 - val lastPlayed = System.currentTimeMillis() - val position = calculatePositionWithRewind(ORIGINAL_POSITION, lastPlayed) - - Assert.assertEquals(ORIGINAL_POSITION.toLong(), position.toLong()) - } - - @Test - fun testCalculatePositionWithRewindSmallRewind() { - val ORIGINAL_POSITION = 10000 - val lastPlayed = System.currentTimeMillis() - MediaPlayerBase.ELAPSED_TIME_FOR_SHORT_REWIND - 1000 - val position = calculatePositionWithRewind(ORIGINAL_POSITION, lastPlayed) - - Assert.assertEquals(ORIGINAL_POSITION - MediaPlayerBase.SHORT_REWIND, position.toLong()) - } - - @Test - fun testCalculatePositionWithRewindMediumRewind() { - val ORIGINAL_POSITION = 10000 - val lastPlayed = System.currentTimeMillis() - MediaPlayerBase.ELAPSED_TIME_FOR_MEDIUM_REWIND - 1000 - val position = calculatePositionWithRewind(ORIGINAL_POSITION, lastPlayed) - - Assert.assertEquals(ORIGINAL_POSITION - MediaPlayerBase.MEDIUM_REWIND, position.toLong()) - } - - @Test - fun testCalculatePositionWithRewindLongRewind() { - val ORIGINAL_POSITION = 30000 - val lastPlayed = System.currentTimeMillis() - MediaPlayerBase.ELAPSED_TIME_FOR_LONG_REWIND - 1000 - val position = calculatePositionWithRewind(ORIGINAL_POSITION, lastPlayed) - - Assert.assertEquals(ORIGINAL_POSITION - MediaPlayerBase.LONG_REWIND, position.toLong()) - } - - @Test - fun testCalculatePositionWithRewindNegativeNumber() { - val ORIGINAL_POSITION = 100 - val lastPlayed = System.currentTimeMillis() - MediaPlayerBase.ELAPSED_TIME_FOR_LONG_REWIND - 1000 - val position = calculatePositionWithRewind(ORIGINAL_POSITION, lastPlayed) - - Assert.assertEquals(0, position.toLong()) - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/service/playback/VolumeUpdaterTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/service/playback/VolumeUpdaterTest.kt deleted file mode 100644 index 9fda2a1b..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/service/playback/VolumeUpdaterTest.kt +++ /dev/null @@ -1,227 +0,0 @@ -package ac.mdiq.podcini.service.playback - -import ac.mdiq.podcini.playback.base.InTheatre.curMedia -import ac.mdiq.podcini.playback.base.MediaPlayerBase -import ac.mdiq.podcini.playback.base.PlayerStatus -import ac.mdiq.podcini.playback.service.PlaybackService -import ac.mdiq.podcini.storage.model.* -import ac.mdiq.podcini.storage.model.VolumeAdaptionSetting -import org.junit.Before -import org.junit.Test -import org.mockito.ArgumentMatchers -import org.mockito.Mockito - -class VolumeUpdaterTest { - private var mediaPlayer: MediaPlayerBase? = null - - @Before - fun setUp() { - mediaPlayer = Mockito.mock(MediaPlayerBase::class.java) - } - - @Test - fun noChangeIfNoFeedMediaPlaying() { -// val volumeUpdater = PlaybackService.VolumeUpdater() - - Mockito.`when`(MediaPlayerBase.status).thenReturn(PlayerStatus.PAUSED) - - val noFeedMedia = Mockito.mock(Playable::class.java) - Mockito.`when`(curMedia).thenReturn(noFeedMedia) - - PlaybackService.updateVolumeIfNecessary(mediaPlayer!!, FEED_ID, VolumeAdaptionSetting.OFF) - - Mockito.verify(mediaPlayer, Mockito.never())?.pause(ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyBoolean()) - Mockito.verify(mediaPlayer, Mockito.never())?.resume() - } - - @Test - fun noChangeIfPlayerStatusIsError() { -// val volumeUpdater = PlaybackService.VolumeUpdater() - - Mockito.`when`(MediaPlayerBase.status).thenReturn(PlayerStatus.ERROR) - - val feedMedia = mockFeedMedia() - Mockito.`when`(curMedia).thenReturn(feedMedia) - - PlaybackService.updateVolumeIfNecessary(mediaPlayer!!, FEED_ID, VolumeAdaptionSetting.OFF) - - Mockito.verify(mediaPlayer, Mockito.never())?.pause(ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyBoolean()) - Mockito.verify(mediaPlayer, Mockito.never())?.resume() - } - - @Test - fun noChangeIfPlayerStatusIsIndeterminate() { -// val volumeUpdater = PlaybackService.VolumeUpdater() - - Mockito.`when`(MediaPlayerBase.status).thenReturn(PlayerStatus.INDETERMINATE) - - val feedMedia = mockFeedMedia() - Mockito.`when`(curMedia).thenReturn(feedMedia) - - PlaybackService.updateVolumeIfNecessary(mediaPlayer!!, FEED_ID, VolumeAdaptionSetting.OFF) - - Mockito.verify(mediaPlayer, Mockito.never())?.pause(ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyBoolean()) - Mockito.verify(mediaPlayer, Mockito.never())?.resume() - } - - @Test - fun noChangeIfPlayerStatusIsStopped() { -// val volumeUpdater = PlaybackService.VolumeUpdater() - - Mockito.`when`(MediaPlayerBase.status).thenReturn(PlayerStatus.STOPPED) - - val feedMedia = mockFeedMedia() - Mockito.`when`(curMedia).thenReturn(feedMedia) - - PlaybackService.updateVolumeIfNecessary(mediaPlayer!!, FEED_ID, VolumeAdaptionSetting.OFF) - - Mockito.verify(mediaPlayer, Mockito.never())?.pause(ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyBoolean()) - Mockito.verify(mediaPlayer, Mockito.never())?.resume() - } - - @Test - fun noChangeIfPlayableIsNoItemOfAffectedFeed() { - Mockito.`when`(MediaPlayerBase.status).thenReturn(PlayerStatus.PLAYING) - - val feedMedia = mockFeedMedia() - Mockito.`when`(curMedia).thenReturn(feedMedia) - Mockito.`when`(feedMedia.episodeOrFetch()?.feed?.id).thenReturn(FEED_ID + 1) - -// val volumeUpdater = PlaybackService.VolumeUpdater() - PlaybackService.updateVolumeIfNecessary(mediaPlayer!!, FEED_ID, VolumeAdaptionSetting.OFF) - - Mockito.verify(mediaPlayer, Mockito.never())?.pause(ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyBoolean()) - Mockito.verify(mediaPlayer, Mockito.never())?.resume() - } - - @Test - fun updatesPreferencesForLoadedFeedMediaIfPlayerStatusIsPaused() { -// val volumeUpdater = PlaybackService.VolumeUpdater() - - Mockito.`when`(MediaPlayerBase.status).thenReturn(PlayerStatus.PAUSED) - - val feedMedia = mockFeedMedia() - Mockito.`when`(curMedia).thenReturn(feedMedia) - val feedPreferences: FeedPreferences = feedMedia.episodeOrFetch()!!.feed!!.preferences!! - - PlaybackService.updateVolumeIfNecessary(mediaPlayer!!, FEED_ID, VolumeAdaptionSetting.LIGHT_REDUCTION) - - Mockito.verify(feedPreferences, Mockito.times(1)) - .volumeAdaptionSetting = (VolumeAdaptionSetting.LIGHT_REDUCTION) - - Mockito.verify(mediaPlayer, Mockito.never())?.pause(ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyBoolean()) - Mockito.verify(mediaPlayer, Mockito.never())?.resume() - } - - @Test - fun updatesPreferencesForLoadedFeedMediaIfPlayerStatusIsPrepared() { -// val volumeUpdater = PlaybackService.VolumeUpdater() - - Mockito.`when`(MediaPlayerBase.status).thenReturn(PlayerStatus.PREPARED) - - val feedMedia = mockFeedMedia() - Mockito.`when`(curMedia).thenReturn(feedMedia) - val feedPreferences: FeedPreferences = feedMedia.episodeOrFetch()!!.feed!!.preferences!! - - PlaybackService.updateVolumeIfNecessary(mediaPlayer!!, FEED_ID, VolumeAdaptionSetting.LIGHT_REDUCTION) - - Mockito.verify(feedPreferences, Mockito.times(1)) - .volumeAdaptionSetting = (VolumeAdaptionSetting.LIGHT_REDUCTION) - - Mockito.verify(mediaPlayer, Mockito.never())?.pause(ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyBoolean()) - Mockito.verify(mediaPlayer, Mockito.never())?.resume() - } - - @Test - fun updatesPreferencesForLoadedFeedMediaIfPlayerStatusIsInitializing() { -// val volumeUpdater = PlaybackService.VolumeUpdater() - - Mockito.`when`(MediaPlayerBase.status).thenReturn(PlayerStatus.INITIALIZING) - - val feedMedia = mockFeedMedia() - Mockito.`when`(curMedia).thenReturn(feedMedia) - val feedPreferences: FeedPreferences = feedMedia.episodeOrFetch()!!.feed!!.preferences!! - - PlaybackService.updateVolumeIfNecessary(mediaPlayer!!, FEED_ID, VolumeAdaptionSetting.LIGHT_REDUCTION) - - Mockito.verify(feedPreferences, Mockito.times(1)) - .volumeAdaptionSetting = (VolumeAdaptionSetting.LIGHT_REDUCTION) - - Mockito.verify(mediaPlayer, Mockito.never())?.pause(ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyBoolean()) - Mockito.verify(mediaPlayer, Mockito.never())?.resume() - } - - @Test - fun updatesPreferencesForLoadedFeedMediaIfPlayerStatusIsPreparing() { -// val volumeUpdater = PlaybackService.VolumeUpdater() - - Mockito.`when`(MediaPlayerBase.status).thenReturn(PlayerStatus.PREPARING) - - val feedMedia = mockFeedMedia() - Mockito.`when`(curMedia).thenReturn(feedMedia) - val feedPreferences: FeedPreferences = feedMedia.episodeOrFetch()!!.feed!!.preferences!! - - PlaybackService.updateVolumeIfNecessary(mediaPlayer!!, FEED_ID, VolumeAdaptionSetting.LIGHT_REDUCTION) - - Mockito.verify(feedPreferences, Mockito.times(1)) - .volumeAdaptionSetting = (VolumeAdaptionSetting.LIGHT_REDUCTION) - - Mockito.verify(mediaPlayer, Mockito.never())?.pause(ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyBoolean()) - Mockito.verify(mediaPlayer, Mockito.never())?.resume() - } - - @Test - fun updatesPreferencesForLoadedFeedMediaIfPlayerStatusIsSeeking() { -// val volumeUpdater = PlaybackService.VolumeUpdater() - - Mockito.`when`(MediaPlayerBase.status).thenReturn(PlayerStatus.SEEKING) - - val feedMedia = mockFeedMedia() - Mockito.`when`(curMedia).thenReturn(feedMedia) - val feedPreferences: FeedPreferences = feedMedia.episodeOrFetch()!!.feed!!.preferences!! - - PlaybackService.updateVolumeIfNecessary(mediaPlayer!!, FEED_ID, VolumeAdaptionSetting.LIGHT_REDUCTION) - - Mockito.verify(feedPreferences, Mockito.times(1)) - .volumeAdaptionSetting = (VolumeAdaptionSetting.LIGHT_REDUCTION) - - Mockito.verify(mediaPlayer, Mockito.never())?.pause(ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyBoolean()) - Mockito.verify(mediaPlayer, Mockito.never())?.resume() - } - - @Test - fun updatesPreferencesAndForcesVolumeChangeForLoadedFeedMediaIfPlayerStatusIsPlaying() { -// val volumeUpdater = PlaybackService.VolumeUpdater() - - Mockito.`when`(MediaPlayerBase.status).thenReturn(PlayerStatus.PLAYING) - - val feedMedia = mockFeedMedia() - Mockito.`when`(curMedia).thenReturn(feedMedia) - val feedPreferences: FeedPreferences = feedMedia.episodeOrFetch()!!.feed!!.preferences!! - - PlaybackService.updateVolumeIfNecessary(mediaPlayer!!, FEED_ID, VolumeAdaptionSetting.HEAVY_REDUCTION) - - Mockito.verify(feedPreferences, Mockito.times(1)) - .volumeAdaptionSetting = (VolumeAdaptionSetting.HEAVY_REDUCTION) - - Mockito.verify(mediaPlayer, Mockito.times(1))?.pause(false, false) - Mockito.verify(mediaPlayer, Mockito.times(1))?.resume() - } - - private fun mockFeedMedia(): EpisodeMedia { - val episodeMedia = Mockito.mock(EpisodeMedia::class.java) - val episode = Mockito.mock(Episode::class.java) - val feed = Mockito.mock(Feed::class.java) - val feedPreferences = Mockito.mock(FeedPreferences::class.java) - - Mockito.`when`(episodeMedia.episodeOrFetch()).thenReturn(episode) - Mockito.`when`(episode.feed).thenReturn(feed) - Mockito.`when`(feed.id).thenReturn(FEED_ID) - Mockito.`when`(feed.preferences).thenReturn(feedPreferences) - return episodeMedia - } - - companion object { - private const val FEED_ID: Long = 42 - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/storage/APCleanupAlgorithmTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/storage/APCleanupAlgorithmTest.kt deleted file mode 100644 index c79eb22b..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/storage/APCleanupAlgorithmTest.kt +++ /dev/null @@ -1,18 +0,0 @@ -package ac.mdiq.podcini.storage - -import ac.mdiq.podcini.storage.algorithms.AutoCleanups.APCleanupAlgorithm -import org.junit.Assert -import org.junit.Test -import java.text.SimpleDateFormat - -class APCleanupAlgorithmTest { - @Test - @Throws(Exception::class) - fun testCalcMostRecentDateForDeletion() { - val algo = APCleanupAlgorithm(24) - val curDateForTest = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse("2018-11-13T14:08:56-0800") - val resExpected = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse("2018-11-12T14:08:56-0800") - val resActual = algo.calcMostRecentDateForDeletion(curDateForTest) - Assert.assertEquals("cutoff for retaining most recent 1 day", resExpected, resActual) - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbCleanupTests.kt b/app/src/test/kotlin/ac/mdiq/podcini/storage/DbCleanupTests.kt deleted file mode 100644 index b6e39254..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbCleanupTests.kt +++ /dev/null @@ -1,212 +0,0 @@ -package ac.mdiq.podcini.storage - -import ac.mdiq.podcini.preferences.UserPreferences -import ac.mdiq.podcini.storage.algorithms.AutoCleanups.performAutoCleanup -import ac.mdiq.podcini.storage.model.Feed -import ac.mdiq.podcini.storage.model.Episode -import ac.mdiq.podcini.storage.model.EpisodeMedia -import ac.mdiq.podcini.util.config.ApplicationCallbacks -import ac.mdiq.podcini.util.config.ClientConfig -import android.app.Application -import android.content.Context -import androidx.preference.PreferenceManager -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.After -import org.junit.Assert -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mockito -import org.robolectric.RobolectricTestRunner -import java.io.File -import java.io.IOException -import java.util.* - -/** - * Test class for DBTasks. - */ -@RunWith(RobolectricTestRunner::class) -open class DbCleanupTests { - private var cleanupAlgorithm = 0 - lateinit var context: Context - private lateinit var destFolder: File - - init { - setCleanupAlgorithm(UserPreferences.EPISODE_CLEANUP_DEFAULT) - } - - protected fun setCleanupAlgorithm(cleanupAlgorithm: Int) { - this.cleanupAlgorithm = cleanupAlgorithm - } - - @Before - fun setUp() { - context = InstrumentationRegistry.getInstrumentation().targetContext - destFolder = File(context.cacheDir, "DbCleanupTests") - destFolder.mkdir() - cleanupDestFolder(destFolder) - Assert.assertNotNull(destFolder) - Assert.assertTrue(destFolder.exists()) - Assert.assertTrue(destFolder.canWrite()) - - val prefEdit = PreferenceManager.getDefaultSharedPreferences(context.applicationContext).edit() - prefEdit.putString(UserPreferences.Prefs.prefEpisodeCacheSize.name, EPISODE_CACHE_SIZE.toString()) - prefEdit.putString(UserPreferences.Prefs.prefEpisodeCleanup.name, cleanupAlgorithm.toString()) - prefEdit.putBoolean(UserPreferences.Prefs.prefEnableAutoDl.name, true) - prefEdit.commit() - - UserPreferences.init(context) -// init(context) - - val app = context as Application? - ClientConfig.applicationCallbacks = Mockito.mock(ApplicationCallbacks::class.java) - Mockito.`when`(ClientConfig.applicationCallbacks?.getApplicationInstance()).thenReturn(app) - } - - @After - fun tearDown() { - cleanupDestFolder(destFolder) - Assert.assertTrue(destFolder.delete()) - -// DBWriter.tearDownTests() -// PodDBAdapter.tearDownTests() - } - - private fun cleanupDestFolder(destFolder: File?) { - for (f in destFolder!!.listFiles()!!) { - Assert.assertTrue(f.delete()) - } - } - - @Test - @Throws(IOException::class) - fun testPerformAutoCleanupShouldDelete() { - val numItems = EPISODE_CACHE_SIZE * 2 - - val feed = Feed("url", null, "title") - val items: MutableList = ArrayList() - feed.episodes.addAll(items) - val files: MutableList = ArrayList() - populateItems(numItems, feed, items, files, PlayState.PLAYED.code, false, false) - - performAutoCleanup(context) - for (i in files.indices) { - if (i < EPISODE_CACHE_SIZE) { - Assert.assertTrue(files[i].exists()) - } else { - Assert.assertFalse(files[i].exists()) - } - } - } - - @Throws(IOException::class) - fun populateItems(numItems: Int, feed: Feed, items: MutableList, files: MutableList, itemState: Int, addToQueue: Boolean, addToFavorites: Boolean) { - for (i in 0 until numItems) { - val itemDate = Date((numItems - i).toLong()) - var playbackCompletionDate: Date? = null - if (itemState == PlayState.PLAYED.code) { - playbackCompletionDate = itemDate - } - val item = Episode(0, "title", "id$i", "link", itemDate, itemState, feed) - - val f = File(destFolder, "file $i") - Assert.assertTrue(f.createNewFile()) - files.add(f) - item.setMedia(EpisodeMedia(0, item, 1, 0, 1L, "m", - f.absolutePath, "url", true, playbackCompletionDate, 0, 0)) - items.add(item) - } - -// val adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(feed) -// if (addToQueue) adapter.setQueue(items) -// if (addToFavorites) adapter.setFavorites(items) -// adapter.close() - - Assert.assertTrue(feed.id != 0L) - for (item in items) { - Assert.assertTrue(item.id != 0L) - Assert.assertTrue(item.media!!.id != 0L) - } - } - - @Test - @Throws(IOException::class) - open fun testPerformAutoCleanupHandleUnplayed() { - val numItems = EPISODE_CACHE_SIZE * 2 - - val feed = Feed("url", null, "title") - val items: MutableList = ArrayList() - feed.episodes.addAll(items) - val files: MutableList = ArrayList() - populateItems(numItems, feed, items, files, PlayState.UNPLAYED.code, false, false) - - performAutoCleanup(context) - for (file in files) { - Assert.assertTrue(file.exists()) - } - } - - @Test - @Throws(IOException::class) - open fun testPerformAutoCleanupShouldNotDeleteBecauseInQueue() { - val numItems = EPISODE_CACHE_SIZE * 2 - - val feed = Feed("url", null, "title") - val items: MutableList = ArrayList() - feed.episodes.addAll(items) - val files: MutableList = ArrayList() - populateItems(numItems, feed, items, files, PlayState.PLAYED.code, true, false) - - performAutoCleanup(context) - for (file in files) { - Assert.assertTrue(file.exists()) - } - } - - /** - * Reproduces a bug where DBTasks.performAutoCleanup(android.content.Context) would use the ID - * of the FeedItem in the call to DBWriter.deleteFeedMediaOfItem instead of the ID of the EpisodeMedia. - * This would cause the wrong item to be deleted. - */ - @Test - @Throws(IOException::class) - open fun testPerformAutoCleanupShouldNotDeleteBecauseInQueue_withFeedsWithNoMedia() { - // add feed with no enclosures so that item ID != media ID - DbTestUtils.saveFeedlist(1, 10, false) - - // add candidate for performAutoCleanup - val feeds = DbTestUtils.saveFeedlist(1, 1, true) - val m: EpisodeMedia = feeds[0].episodes[0].media!! - m.downloaded = (true) - m.fileUrl = ("file") -// val adapter = getInstance() -// adapter.open() -// adapter.setMedia(m) -// adapter.close() - - testPerformAutoCleanupShouldNotDeleteBecauseInQueue() - } - - @Test - @Throws(IOException::class) - fun testPerformAutoCleanupShouldNotDeleteBecauseFavorite() { - val numItems = EPISODE_CACHE_SIZE * 2 - - val feed = Feed("url", null, "title") - val items: MutableList = ArrayList() - feed.episodes.addAll(items) - val files: MutableList = ArrayList() - populateItems(numItems, feed, items, files, PlayState.PLAYED.code, false, true) - - performAutoCleanup(context) - for (file in files) { - Assert.assertTrue(file.exists()) - } - } - - companion object { - const val EPISODE_CACHE_SIZE: Int = 5 - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbNullCleanupAlgorithmTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/storage/DbNullCleanupAlgorithmTest.kt deleted file mode 100644 index 2d18f4d4..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbNullCleanupAlgorithmTest.kt +++ /dev/null @@ -1,113 +0,0 @@ -package ac.mdiq.podcini.storage - -import ac.mdiq.podcini.preferences.UserPreferences -import ac.mdiq.podcini.storage.algorithms.AutoCleanups.performAutoCleanup -import ac.mdiq.podcini.storage.model.Feed -import ac.mdiq.podcini.storage.model.Episode -import ac.mdiq.podcini.storage.model.EpisodeMedia -import android.content.Context -import androidx.preference.PreferenceManager -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.After -import org.junit.Assert -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import java.io.File -import java.io.IOException -import java.util.* - -/** - * Tests that the APNullCleanupAlgorithm is working correctly. - */ -@RunWith(RobolectricTestRunner::class) -class DbNullCleanupAlgorithmTest { - private lateinit var context: Context - - private var destFolder: File? = null - - @Before - fun setUp() { - context = InstrumentationRegistry.getInstrumentation().targetContext - destFolder = context!!.externalCacheDir - cleanupDestFolder(destFolder) - Assert.assertNotNull(destFolder) - Assert.assertTrue(destFolder!!.exists()) - Assert.assertTrue(destFolder!!.canWrite()) - - // create new database -// PodDBAdapter.init(context!!) -// deleteDatabase() -// val adapter = getInstance() -// adapter.open() -// adapter.close() - - val prefEdit = PreferenceManager.getDefaultSharedPreferences(context!!.applicationContext).edit() - prefEdit.putString(UserPreferences.Prefs.prefEpisodeCacheSize.name, EPISODE_CACHE_SIZE.toString()) - prefEdit.putString(UserPreferences.Prefs.prefEpisodeCleanup.name, UserPreferences.EPISODE_CLEANUP_NULL.toString()) - prefEdit.commit() - - UserPreferences.init(context!!) - } - - @After - fun tearDown() { -// DBWriter.tearDownTests() -// deleteDatabase() -// PodDBAdapter.tearDownTests() - - cleanupDestFolder(destFolder) - Assert.assertTrue(destFolder!!.delete()) - } - - private fun cleanupDestFolder(destFolder: File?) { - for (f in destFolder!!.listFiles()) { - Assert.assertTrue(f.delete()) - } - } - - /** - * A test with no items in the queue, but multiple items downloaded. - * The null algorithm should never delete any items, even if they're played and not in the queue. - */ - @Test - @Throws(IOException::class) - fun testPerformAutoCleanupShouldNotDelete() { - val numItems = EPISODE_CACHE_SIZE * 2 - - val feed = Feed("url", null, "title") - val items: MutableList = ArrayList() - feed.episodes.addAll(items) - val files: MutableList = ArrayList() - for (i in 0 until numItems) { - val item = Episode(0, "title", "id$i", "link", Date(), PlayState.PLAYED.code, feed) - - val f = File(destFolder, "file $i") - Assert.assertTrue(f.createNewFile()) - files.add(f) - item.setMedia(EpisodeMedia(0, item, 1, 0, 1L, "m", f.absolutePath, "url", true, - Date((numItems - i).toLong()), 0, 0)) - items.add(item) - } - -// val adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(feed) -// adapter.close() - - Assert.assertTrue(feed.id != 0L) - for (item in items) { - Assert.assertTrue(item.id != 0L) - Assert.assertTrue(item.media!!.id != 0L) - } - performAutoCleanup(context) - for (i in files.indices) { - Assert.assertTrue(files[i].exists()) - } - } - - companion object { - private const val EPISODE_CACHE_SIZE = 5 - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbPlayQueueCleanupAlgorithmTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/storage/DbPlayQueueCleanupAlgorithmTest.kt deleted file mode 100644 index ad7eab37..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbPlayQueueCleanupAlgorithmTest.kt +++ /dev/null @@ -1,47 +0,0 @@ -package ac.mdiq.podcini.storage - -import ac.mdiq.podcini.storage.model.Feed -import ac.mdiq.podcini.storage.model.Episode -import ac.mdiq.podcini.preferences.UserPreferences -import ac.mdiq.podcini.storage.algorithms.AutoCleanups.performAutoCleanup -import org.junit.Assert -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import java.io.File -import java.io.IOException - -/** - * Tests that the APQueueCleanupAlgorithm is working correctly. - */ -@RunWith(RobolectricTestRunner::class) -class DbPlayQueueCleanupAlgorithmTest : DbCleanupTests() { - init { - setCleanupAlgorithm(UserPreferences.EPISODE_CLEANUP_QUEUE) - } - - /** - * For APQueueCleanupAlgorithm we expect even unplayed episodes to be deleted if needed - * if they aren't in the queue. - */ - @Test - @Throws(IOException::class) - override fun testPerformAutoCleanupHandleUnplayed() { - val numItems = EPISODE_CACHE_SIZE * 2 - - val feed = Feed("url", null, "title") - val items: MutableList = ArrayList() - feed.episodes.addAll(items) - val files: MutableList = ArrayList() - populateItems(numItems, feed, items, files, PlayState.UNPLAYED.code, false, false) - - performAutoCleanup(context) - for (i in files.indices) { - if (i < EPISODE_CACHE_SIZE) { - Assert.assertTrue(files[i].exists()) - } else { - Assert.assertFalse(files[i].exists()) - } - } - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbReaderTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/storage/DbReaderTest.kt deleted file mode 100644 index 64ac45fe..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbReaderTest.kt +++ /dev/null @@ -1,478 +0,0 @@ -package ac.mdiq.podcini.storage - -import ac.mdiq.podcini.playback.base.InTheatre.curQueue -import ac.mdiq.podcini.preferences.UserPreferences -import ac.mdiq.podcini.storage.database.Episodes.getEpisode -import ac.mdiq.podcini.storage.database.Episodes.getEpisodeByGuidOrUrl -import ac.mdiq.podcini.storage.database.Episodes.getEpisodes -import ac.mdiq.podcini.storage.database.Feeds.getFeedList -import ac.mdiq.podcini.storage.database.Feeds.getFeedListDownloadUrls -import ac.mdiq.podcini.storage.database.Queues.getInQueueEpisodeIds -import ac.mdiq.podcini.storage.model.Episode -import ac.mdiq.podcini.storage.model.EpisodeFilter -import ac.mdiq.podcini.storage.model.EpisodeSortOrder -import ac.mdiq.podcini.ui.fragment.HistoryFragment.Companion.getHistory -import ac.mdiq.podcini.ui.fragment.HistoryFragment.Companion.getNumberOfCompleted -import androidx.test.platform.app.InstrumentationRegistry -import junit.framework.TestCase.assertEquals -import org.junit.* -import org.junit.experimental.runners.Enclosed -import org.junit.runner.RunWith -import org.robolectric.ParameterizedRobolectricTestRunner -import org.robolectric.RobolectricTestRunner -import java.util.* -import kotlin.math.max -import kotlin.math.min - -/** - * Test class for DBReader. - */ -@RunWith(Enclosed::class) -class DbReaderTest { - @Ignore("Not a test") - open class TestBase { - @Before - fun setUp() { - val context = InstrumentationRegistry.getInstrumentation().context - UserPreferences.init(context) - -// PodDBAdapter.init(context) -// deleteDatabase() -// val adapter = getInstance() -// adapter.open() -// adapter.close() - } - - @After - fun tearDown() { -// PodDBAdapter.tearDownTests() -// DBWriter.tearDownTests() - } - } - - @RunWith(RobolectricTestRunner::class) - class SingleTests : TestBase() { - @Test - fun testGetFeedList() { - val feeds = DbTestUtils.saveFeedlist(10, 0, false) - val savedFeeds = getFeedList() - Assert.assertNotNull(savedFeeds) - Assert.assertEquals(feeds.size.toLong(), savedFeeds.size.toLong()) - for (i in feeds.indices) { - Assert.assertEquals(feeds[i].id, savedFeeds[i].id) - } - } - - @Test - fun testGetFeedListSortOrder() { -// val adapter = getInstance() -// adapter.open() -// -// val feed1 = Feed(0, null, "A", "link", "d", null, null, null, "rss", "A", null, "", "") -// val feed2 = Feed(0, null, "b", "link", "d", null, null, null, "rss", "b", null, "", "") -// val feed3 = Feed(0, null, "C", "link", "d", null, null, null, "rss", "C", null, "", "") -// val feed4 = Feed(0, null, "d", "link", "d", null, null, null, "rss", "d", null, "", "") -// adapter.setCompleteFeed(feed1) -// adapter.setCompleteFeed(feed2) -// adapter.setCompleteFeed(feed3) -// adapter.setCompleteFeed(feed4) -// Assert.assertTrue(feed1.id != 0L) -// Assert.assertTrue(feed2.id != 0L) -// Assert.assertTrue(feed3.id != 0L) -// Assert.assertTrue(feed4.id != 0L) - -// adapter.close() - - val saved = getFeedList() - Assert.assertNotNull(saved) - Assert.assertEquals("Wrong size: ", 4, saved.size.toLong()) - -// Assert.assertEquals("Wrong id of feed 1: ", feed1.id, saved[0].id) -// Assert.assertEquals("Wrong id of feed 2: ", feed2.id, saved[1].id) -// Assert.assertEquals("Wrong id of feed 3: ", feed3.id, saved[2].id) -// Assert.assertEquals("Wrong id of feed 4: ", feed4.id, saved[3].id) - } - - @Test - fun testFeedListDownloadUrls() { - val feeds = DbTestUtils.saveFeedlist(10, 0, false) - val urls = getFeedListDownloadUrls() - Assert.assertNotNull(urls) - Assert.assertEquals(feeds.size.toLong(), urls.size.toLong()) - for (i in urls.indices) { - assertEquals(urls[i], feeds[i].downloadUrl) - } - } - - @Test - fun testLoadFeedDataOfFeedItemlist() { - val numFeeds = 10 - val numItems = 1 - val feeds = DbTestUtils.saveFeedlist(numFeeds, numItems, false) - val items: MutableList = mutableListOf() - for (f in feeds) { - for (item in f.episodes) { - item.feed = (null) - item.feedId = (f.id) - items.add(item) - } - } -// loadAdditionalFeedItemListData(items) - for (i in 0 until numFeeds) { - for (j in 0 until numItems) { - val item: Episode = feeds[i].episodes[j] - Assert.assertNotNull(item.feed) - assertEquals(feeds[i].id, item.feed!!.id) - assertEquals(item.feed!!.id, item.feedId) - } - } - } - - @Test - fun testGetFeedItemList() { - val numFeeds = 1 - val numItems = 10 - val feed = DbTestUtils.saveFeedlist(numFeeds, numItems, false)[0] - val items: List = feed.episodes.toList() - feed.episodes.clear() - val savedItems = feed.episodes - Assert.assertNotNull(savedItems) - Assert.assertEquals(items.size.toLong(), savedItems.size.toLong()) - for (i in savedItems.indices) { - Assert.assertEquals(savedItems[i].id, items[i].id) - } - } - - private fun saveQueue(numItems: Int): List { - require(numItems > 0) { "numItems<=0" } - val feeds = DbTestUtils.saveFeedlist(numItems, numItems, false) - val allItems: MutableList = ArrayList() - for (f in feeds) { - allItems.addAll(f.episodes) - } - // take random items from every feed - val random = Random() - val queue: MutableList = ArrayList() - while (queue.size < numItems) { - val index = random.nextInt(numItems) - if (!queue.contains(allItems[index])) { - queue.add(allItems[index]) - } - } -// val adapter = getInstance() -// adapter.open() -// adapter.setQueue(queue) -// adapter.close() - return queue - } - - @Test - fun testGetQueueIdList() { - val numItems = 10 - val queue = saveQueue(numItems) - val ids = getInQueueEpisodeIds().toList() - Assert.assertNotNull(ids) - Assert.assertEquals(ids.size.toLong(), queue.size.toLong()) - for (i in queue.indices) { - Assert.assertTrue(ids.get(i) != 0L) - Assert.assertEquals(ids.get(i), queue[i].id) - } - } - - @Test - fun testGetQueue() { - val numItems = 10 - val queue = saveQueue(numItems) - val savedQueue = curQueue.episodes - Assert.assertNotNull(savedQueue) - Assert.assertEquals(savedQueue.size.toLong(), queue.size.toLong()) - for (i in queue.indices) { - Assert.assertTrue(savedQueue[i].id != 0L) - Assert.assertEquals(savedQueue[i].id, queue[i].id) - } - } - - private fun saveDownloadedItems(numItems: Int): List { - require(numItems > 0) { "numItems<=0" } - val feeds = DbTestUtils.saveFeedlist(numItems, numItems, true) - val items: MutableList = ArrayList() - for (f in feeds) { - items.addAll(f.episodes) - } - val downloaded: MutableList = ArrayList() - val random = Random() - - while (downloaded.size < numItems) { - val i = random.nextInt(numItems) - if (!downloaded.contains(items[i])) { - val item = items[i] - item.media!!.downloaded = (true) - item.media!!.fileUrl = ("file$i") - downloaded.add(item) - } - } -// val adapter = getInstance() -// adapter.open() -// adapter.storeFeedItemlist(downloaded) -// adapter.close() - return downloaded - } - - @Test - fun testGetDownloadedItems() { - val numItems = 10 - val downloaded = saveDownloadedItems(numItems) - val downloadedSaved = getEpisodes(0, Int.MAX_VALUE, EpisodeFilter(EpisodeFilter.States.downloaded.name), EpisodeSortOrder.DATE_NEW_OLD) - Assert.assertNotNull(downloadedSaved) - Assert.assertEquals(downloaded.size.toLong(), downloadedSaved.size.toLong()) - for (item in downloadedSaved) { - Assert.assertNotNull(item.media) - Assert.assertTrue(item.media!!.downloaded) - Assert.assertNotNull(item.media!!.downloadUrl) - } - } - - private fun saveNewItems(numItems: Int): List { - val feeds = DbTestUtils.saveFeedlist(numItems, numItems, true) - val items: MutableList = ArrayList() - for (f in feeds) { - items.addAll(f.episodes) - } - val newItems: MutableList = ArrayList() - val random = Random() - - while (newItems.size < numItems) { - val i = random.nextInt(numItems) - if (!newItems.contains(items[i])) { - val item = items[i] - item.setNew() - newItems.add(item) - } - } -// val adapter = getInstance() -// adapter.open() -// adapter.storeFeedItemlist(newItems) -// adapter.close() - return newItems - } - - @Test - fun testGetNewItemIds() { - val numItems = 10 - - val newItems = saveNewItems(numItems) - val unreadIds = LongArray(newItems.size) - for (i in newItems.indices) { - unreadIds[i] = newItems[i].id - } - val newItemsSaved = getEpisodes(0, Int.MAX_VALUE, EpisodeFilter(EpisodeFilter.States.new.name), EpisodeSortOrder.DATE_NEW_OLD) - Assert.assertNotNull(newItemsSaved) - Assert.assertEquals(newItemsSaved.size.toLong(), newItems.size.toLong()) - for (feedItem in newItemsSaved) { - val savedId = feedItem.id - var found = false - for (id in unreadIds) { - if (id == savedId) { - found = true - break - } - } - Assert.assertTrue(found) - } - } - - @Test - fun testGetPlaybackHistoryLength() { - val totalItems = 100 - - val feed = DbTestUtils.saveFeedlist(1, totalItems, true)[0] - -// val adapter = getInstance() - for (playedItems in mutableListOf(0, 1, 20, 100)) { -// adapter.open() -// for (i in 0 until playedItems) { -// val m: EpisodeMedia = feed.items[i].media!! -// m.playbackCompletionDate = (Date((i + 1).toLong())) -// -// adapter.setFeedMediaPlaybackCompletionDate(m) -// } -// adapter.close() - - val len = getNumberOfCompleted() - Assert.assertEquals("Wrong size: ", len.toInt().toLong(), playedItems.toLong()) - } - } - - @Test - fun testGetNavDrawerDataQueueEmptyNoUnreadItems() { - val numFeeds = 10 - val numItems = 10 - DbTestUtils.saveFeedlist(numFeeds, numItems, true) -// val navDrawerData = getDatasetStats() -// Assert.assertEquals(numFeeds.toLong(), navDrawerData.items.size.toLong()) -// Assert.assertEquals(0, navDrawerData.numNewItems.toLong()) -// Assert.assertEquals(0, navDrawerData.queueSize.toLong()) - } - - @Test - fun testGetNavDrawerDataQueueNotEmptyWithUnreadItems() { - val numFeeds = 10 - val numItems = 10 - val numQueue = 1 - val numNew = 2 -// val feeds = DbTestUtils.saveFeedlist(numFeeds, numItems, true) -// val adapter = getInstance() -// adapter.open() -// for (i in 0 until numNew) { -// val item: FeedItem = feeds[0].items[i] -// item.setNew() -// adapter.setSingleFeedItem(item) -// } -// val queue: MutableList = ArrayList() -// for (i in 0 until numQueue) { -// val item: FeedItem = feeds[1].items[i] -// queue.add(item) -// } -// adapter.setQueue(queue) -// -// adapter.close() - -// val navDrawerData = getDatasetStats() -// Assert.assertEquals(numFeeds.toLong(), navDrawerData.items.size.toLong()) -// Assert.assertEquals(numNew.toLong(), navDrawerData.numNewItems.toLong()) -// Assert.assertEquals(numQueue.toLong(), navDrawerData.queueSize.toLong()) - } - - @Test - fun testGetFeedItemlistCheckChaptersFalse() { - val feeds = DbTestUtils.saveFeedlist(10, 10, false, false, 0) - for (feed in feeds) { - for (item in feed.episodes) { - Assert.assertFalse(item.chapters.isNotEmpty()) - } - } - } - - @Test - fun testGetFeedItemlistCheckChaptersTrue() { - val feeds = DbTestUtils.saveFeedlist(10, 10, false, true, 10) - for (feed in feeds) { - for (item in feed.episodes) { - Assert.assertTrue(item.chapters.isNotEmpty()) - } - } - } - - @Test - fun testLoadChaptersOfFeedItemNoChapters() { - val feeds = DbTestUtils.saveFeedlist(1, 3, false, false, 0) - DbTestUtils.saveFeedlist(1, 3, false, true, 3) - for (feed in feeds) { - for (item in feed.episodes) { - Assert.assertFalse(item.chapters.isNotEmpty()) - Assert.assertFalse(item.chapters.isNotEmpty()) - Assert.assertNull(item.chapters) - } - } - } - - @Test - fun testLoadChaptersOfFeedItemWithChapters() { - val numChapters = 3 - DbTestUtils.saveFeedlist(1, 3, false, false, 0) - val feeds = DbTestUtils.saveFeedlist(1, 3, false, true, numChapters) - for (feed in feeds) { - for (item in feed.episodes) { - Assert.assertTrue(item.chapters.isNotEmpty()) - Assert.assertTrue(item.chapters.isNotEmpty()) - Assert.assertNotNull(item.chapters) - assertEquals(numChapters, item.chapters!!.size) - } - } - } - - @Test - fun testGetItemWithChapters() { - val numChapters = 3 - val feeds = DbTestUtils.saveFeedlist(1, 1, false, true, numChapters) - val item1: Episode = feeds[0].episodes[0] - val item2 = getEpisode(item1.id) - Assert.assertTrue(item2?.chapters?.isNotEmpty() == true) - assertEquals(item1.chapters, item2?.chapters) - } - - @Test - fun testGetItemByEpisodeUrl() { - val feeds = DbTestUtils.saveFeedlist(1, 1, true) - val item1: Episode = feeds[0].episodes[0] - val feedItemByEpisodeUrl = getEpisodeByGuidOrUrl(null, item1.media!!.downloadUrl!!) - assertEquals(item1.identifier, feedItemByEpisodeUrl?.identifier) - } - - @Test - fun testGetItemByGuid() { - val feeds = DbTestUtils.saveFeedlist(1, 1, true) - val item1: Episode = feeds[0].episodes[0] - - val feedItemByGuid = getEpisodeByGuidOrUrl(item1.identifier, item1.media?.downloadUrl!!) - assertEquals(item1.identifier, feedItemByGuid?.identifier) - } - } - - @RunWith(ParameterizedRobolectricTestRunner::class) - class PlaybackHistoryTest(private val paramOffset: Int, private val paramLimit: Int) : TestBase() { - @Test - fun testGetPlaybackHistory() { - val numItems = (paramLimit + 1) * 2 - val playedItems = paramLimit + 1 - val numReturnedItems = min(max((playedItems - paramOffset).toDouble(), 0.0), paramLimit.toDouble()) - .toInt() - val numFeeds = 1 - - val feed = DbTestUtils.saveFeedlist(numFeeds, numItems, true)[0] - val ids = LongArray(playedItems) - -// val adapter = getInstance() -// adapter.open() -// for (i in 0 until playedItems) { -// val m: EpisodeMedia = feed.items[i].media!! -// m.playbackCompletionDate = (Date((i + 1).toLong())) -// adapter.setFeedMediaPlaybackCompletionDate(m) -// ids[ids.size - 1 - i] = m.item!!.id -// } -// adapter.close() - - val saved = getHistory(paramOffset, paramLimit) - Assert.assertNotNull(saved) - Assert.assertEquals(String.format("Wrong size with offset %d and limit %d: ", - paramOffset, paramLimit), - numReturnedItems.toLong(), saved.size.toLong()) - for (i in 0 until numReturnedItems) { - val item = saved[i] - Assert.assertNotNull(item.media!!.playbackCompletionDate) - Assert.assertEquals(String.format("Wrong sort order with offset %d and limit %d: ", - paramOffset, paramLimit), - item.id, ids[paramOffset + i]) - } - } - - companion object { - @ParameterizedRobolectricTestRunner.Parameters - fun data(): Collection> { - val limits: List = mutableListOf(1, 20, 100) - val offsets: List = mutableListOf(0, 10, 20) - val rv = Array(limits.size * offsets.size) { arrayOf(2) } - var i = 0 - for (offset in offsets) { - for (limit in limits) { - rv[i][0] = offset - rv[i][1] = limit - i++ - } - } - - return listOf(*rv) - } - } - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbTasksTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/storage/DbTasksTest.kt deleted file mode 100644 index 784e2f53..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbTasksTest.kt +++ /dev/null @@ -1,259 +0,0 @@ -package ac.mdiq.podcini.storage - -//import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.init -import ac.mdiq.podcini.preferences.UserPreferences -import ac.mdiq.podcini.storage.database.Feeds.getFeed -import ac.mdiq.podcini.storage.database.Feeds.updateFeed -import ac.mdiq.podcini.storage.model.Feed -import ac.mdiq.podcini.storage.model.Episode -import ac.mdiq.podcini.storage.model.EpisodeMedia -import ac.mdiq.podcini.util.config.ApplicationCallbacks -import ac.mdiq.podcini.util.config.ClientConfig -import android.app.Application -import android.content.Context -import androidx.test.platform.app.InstrumentationRegistry -import junit.framework.TestCase.assertEquals -import org.junit.After -import org.junit.Assert -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mockito -import org.robolectric.RobolectricTestRunner -import java.util.* - -/** - * Test class for [DBTasks]. - */ -@RunWith(RobolectricTestRunner::class) -class DbTasksTest { - private lateinit var context: Context - - @Before - fun setUp() { - context = InstrumentationRegistry.getInstrumentation().targetContext - UserPreferences.init(context) -// init(context) - - val app = context as Application? - ClientConfig.applicationCallbacks = Mockito.mock(ApplicationCallbacks::class.java) - Mockito.`when`(ClientConfig.applicationCallbacks?.getApplicationInstance()).thenReturn(app) - - // create new database -// PodDBAdapter.init(context) -// deleteDatabase() -// val adapter = getInstance() -// adapter.open() -// adapter.close() - } - - @After - fun tearDown() { -// DBWriter.tearDownTests() -// PodDBAdapter.tearDownTests() - } - - @Test - fun testUpdateFeedNewFeed() { - val numItems = 10 - - val feed = Feed("url", null, "title") - feed.episodes.clear() - for (i in 0 until numItems) { - feed.episodes.add(Episode(0, "item $i", "id $i", "link $i", - Date(), PlayState.UNPLAYED.code, feed)) - } - val newFeed = updateFeed(context, feed, false) - - Assert.assertEquals(feed.id, newFeed!!.id) - Assert.assertTrue(feed.id != 0L) - for (item in feed.episodes) { - Assert.assertFalse(item.isPlayed()) - Assert.assertTrue(item.id != 0L) - } - } - - /** Two feeds with the same title, but different download URLs should be treated as different feeds. */ - @Test - fun testUpdateFeedSameTitle() { - val feed1 = Feed("url1", null, "title") - val feed2 = Feed("url2", null, "title") - - feed1.episodes.clear() - feed2.episodes.clear() - - val savedFeed1 = updateFeed(context, feed1, false) - val savedFeed2 = updateFeed(context, feed2, false) - - Assert.assertTrue(savedFeed1!!.id != savedFeed2!!.id) - } - - @Test - fun testUpdateFeedUpdatedFeed() { - val numItemsOld = 10 - val numItemsNew = 10 - - val feed = Feed("url", null, "title") - feed.episodes.clear() - for (i in 0 until numItemsOld) { - feed.episodes.add(Episode(0, "item $i", "id $i", "link $i", - Date(i.toLong()), PlayState.PLAYED.code, feed)) - } -// val adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(feed) -// adapter.close() - - // ensure that objects have been saved in db, then reset - Assert.assertTrue(feed.id != 0L) - val feedID = feed.id - feed.id = 0 - val itemIDs: MutableList = ArrayList() - for (item in feed.episodes) { - Assert.assertTrue(item.id != 0L) - itemIDs.add(item.id) - item.id = 0 - } - - for (i in numItemsOld until numItemsNew + numItemsOld) { - feed.episodes.add(0, Episode(0, "item $i", "id $i", "link $i", - Date(i.toLong()), PlayState.UNPLAYED.code, feed)) - } - - val newFeed = updateFeed(context, feed, false) - Assert.assertNotSame(newFeed, feed) - - updatedFeedTest(newFeed, feedID, itemIDs, numItemsOld, numItemsNew) - - val feedFromDB = getFeed(newFeed!!.id) - Assert.assertNotNull(feedFromDB) - Assert.assertEquals(newFeed.id, feedFromDB!!.id) - updatedFeedTest(feedFromDB, feedID, itemIDs, numItemsOld, numItemsNew) - } - - @Test - fun testUpdateFeedMediaUrlResetState() { - val feed = Feed("url", null, "title") - val item = Episode(0, "item", "id", "link", Date(), PlayState.PLAYED.code, feed) - feed.episodes.add(item) - -// val adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(feed) -// adapter.close() - - // ensure that objects have been saved in db, then reset - Assert.assertTrue(feed.id != 0L) - Assert.assertTrue(item.id != 0L) - - val media = EpisodeMedia(item, "url", 1024, "mime/type") - item.setMedia(media) - val list: MutableList = ArrayList() - list.add(item) - feed.episodes.addAll(list) - - val newFeed = updateFeed(context, feed, false) - Assert.assertNotSame(newFeed, feed) - - val feedFromDB = getFeed(newFeed!!.id) - val episodeFromDB: Episode = feedFromDB!!.episodes[0] - Assert.assertTrue(episodeFromDB.isNew) - } - - @Test - fun testUpdateFeedRemoveUnlistedItems() { - val feed = Feed("url", null, "title") - feed.episodes.clear() - for (i in 0..9) { - feed.episodes.add( - Episode(0, "item $i", "id $i", "link $i", Date(i.toLong()), PlayState.PLAYED.code, feed)) - } -// val adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(feed) -// adapter.close() - - // delete some items - feed.episodes.subList(0, 2).clear() - val newFeed = updateFeed(context, feed, true) - assertEquals(8, newFeed?.episodes?.size) // 10 - 2 = 8 items - - val feedFromDB = getFeed(newFeed!!.id) - assertEquals(8, feedFromDB?.episodes?.size) // 10 - 2 = 8 items - } - - @Test - fun testUpdateFeedSetDuplicate() { - val feed = Feed("url", null, "title") - feed.episodes.clear() - for (i in 0..9) { - val item = - Episode(0, "item $i", "id $i", "link $i", Date(i.toLong()), PlayState.PLAYED.code, feed) - val media = EpisodeMedia(item, "download url $i", 123, "media/mp3") - item.setMedia(media) - feed.episodes.add(item) - } -// val adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(feed) -// adapter.close() - - // change the guid of the first item, but leave the download url the same - val item = feed.episodes[0] - item.identifier = ("id 0-duplicate") - item.title = ("item 0 duplicate") - val newFeed = updateFeed(context, feed, false) - assertEquals(10, newFeed?.episodes?.size) // id 1-duplicate replaces because the stream url is the same - - val feedFromDB = getFeed(newFeed!!.id) - assertEquals(10, feedFromDB?.episodes?.size) // id1-duplicate should override id 1 - - val updatedItem = feedFromDB!!.episodes[9] - assertEquals("item 0 duplicate", updatedItem.title) - assertEquals("id 0-duplicate", updatedItem.identifier) // Should use the new ID for sync etc - } - - - private fun updatedFeedTest(newFeed: Feed?, feedID: Long, itemIDs: List, numItemsOld: Int, numItemsNew: Int) { - Assert.assertEquals(feedID, newFeed!!.id) - assertEquals(numItemsNew + numItemsOld, newFeed.episodes.size) - newFeed.episodes.reverse() - var lastDate = Date(0) - for (i in 0 until numItemsOld) { - val item: Episode = newFeed.episodes[i] - Assert.assertSame(newFeed, item.feed) - Assert.assertEquals(itemIDs[i], item.id) - Assert.assertTrue(item.isPlayed()) - Assert.assertTrue(item.getPubDate()!!.time >= lastDate.time) - lastDate = Date(item.pubDate) - } - for (i in numItemsOld until numItemsNew + numItemsOld) { - val item: Episode = newFeed.episodes[i] - Assert.assertSame(newFeed, item.feed) - Assert.assertTrue(item.id != 0L) - Assert.assertFalse(item.isPlayed()) - Assert.assertTrue(item.getPubDate()!!.time >= lastDate.time) - lastDate = Date(item.pubDate) - } - } - - private fun createSavedFeed(title: String, numFeedItems: Int): Feed { - val feed = Feed("url", null, title) - - if (numFeedItems > 0) { - val items: MutableList = ArrayList(numFeedItems) - for (i in 1..numFeedItems) { - val item = Episode(0, "item $i of $title", "id$title$i", "link", - Date(), PlayState.UNPLAYED.code, feed) - items.add(item) - } - feed.episodes.addAll(items) - } - -// val adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(feed) -// adapter.close() - return feed - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbTestUtils.kt b/app/src/test/kotlin/ac/mdiq/podcini/storage/DbTestUtils.kt deleted file mode 100644 index 766214fb..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbTestUtils.kt +++ /dev/null @@ -1,58 +0,0 @@ -package ac.mdiq.podcini.storage - -import ac.mdiq.podcini.storage.model.Feed - -/** - * Utility methods for DB* tests. - */ -internal object DbTestUtils { - /** - * Use this method when tests involve chapters. - */ - /** - * Use this method when tests don't involve chapters. - */ - @JvmOverloads - fun saveFeedlist(numFeeds: Int, numItems: Int, withMedia: Boolean, - withChapters: Boolean = false, numChapters: Int = 0 - ): List { - require(numFeeds > 0) { "numFeeds<=0" } - require(numItems >= 0) { "numItems<0" } - - val feeds: MutableList = ArrayList() -// val adapter = getInstance() -// adapter.open() -// for (i in 0 until numFeeds) { -// val f = Feed(0, null, "feed $i", "link$i", "descr", null, null, -// null, null, "id$i", null, null, "url$i") -// f.items.clear() -// for (j in 0 until numItems) { -// val item = FeedItem(0, "item $j", "id$j", "link$j", Date(), -// FeedItem.PLAYED, f, withChapters) -// if (withMedia) { -// val media = EpisodeMedia(item, "url$j", 1, "audio/mp3") -// item.setMedia(media) -// } -// if (withChapters) { -// val chapters: MutableList = ArrayList() -// item.chapters = (chapters) -// for (k in 0 until numChapters) { -// chapters.add(Chapter(k.toLong(), "item $j chapter $k", -// "http://example.com", "http://example.com/image.png")) -// } -// } -// f.items.add(item) -// } -// f.items.sortWith(FeedItemPubdateComparator()) -// adapter.setCompleteFeed(f) -// Assert.assertTrue(f.id != 0L) -// for (item in f.items) { -// Assert.assertTrue(item.id != 0L) -// } -// feeds.add(f) -// } -// adapter.close() - - return feeds - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbWriterTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/storage/DbWriterTest.kt deleted file mode 100644 index 5b59b566..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/storage/DbWriterTest.kt +++ /dev/null @@ -1,893 +0,0 @@ -package ac.mdiq.podcini.storage - -import ac.mdiq.podcini.net.download.service.DownloadServiceInterface -import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterfaceTestStub -import ac.mdiq.podcini.playback.base.InTheatre.curQueue -import ac.mdiq.podcini.preferences.UserPreferences -import ac.mdiq.podcini.storage.database.Episodes.setCompletionDate -import ac.mdiq.podcini.storage.database.Episodes.deleteEpisodes -import ac.mdiq.podcini.storage.database.Episodes.deleteEpisodeMedia -import ac.mdiq.podcini.storage.database.Episodes.getEpisode -import ac.mdiq.podcini.storage.database.Episodes.getEpisodeMedia -import ac.mdiq.podcini.storage.database.Episodes.persistEpisode -import ac.mdiq.podcini.storage.database.Episodes.persistEpisodeMedia -import ac.mdiq.podcini.storage.database.Episodes.shouldDeleteRemoveFromQueue -import ac.mdiq.podcini.storage.database.Feeds.deleteFeedSync -import ac.mdiq.podcini.storage.database.Queues -import ac.mdiq.podcini.storage.database.Queues.addToQueue -import ac.mdiq.podcini.storage.database.Queues.clearQueue -import ac.mdiq.podcini.storage.database.Queues.enqueueLocation -import ac.mdiq.podcini.storage.database.Queues.moveInQueue -import ac.mdiq.podcini.storage.database.Queues.removeFromQueue -import ac.mdiq.podcini.storage.model.Episode -import ac.mdiq.podcini.storage.model.EpisodeMedia -import ac.mdiq.podcini.storage.model.Feed -import ac.mdiq.podcini.util.Logd -import ac.mdiq.podcini.util.config.ApplicationCallbacks -import ac.mdiq.podcini.util.config.ClientConfig -import android.app.Application -import android.content.Context -import androidx.preference.PreferenceManager -import androidx.test.platform.app.InstrumentationRegistry -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withTimeout -import org.awaitility.Awaitility -import org.junit.After -import org.junit.Assert -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mockito -import org.robolectric.RobolectricTestRunner -import java.io.File -import java.util.* -import java.util.concurrent.Future -import java.util.concurrent.TimeUnit - -/** - * Test class for [DBWriter]. - */ -@RunWith(RobolectricTestRunner::class) -class DbWriterTest { - private lateinit var context: Context - - @Before - fun setUp() { - context = InstrumentationRegistry.getInstrumentation().targetContext - UserPreferences.init(context) -// init(context) - - val app = context as Application? - ClientConfig.applicationCallbacks = Mockito.mock(ApplicationCallbacks::class.java) - Mockito.`when`(ClientConfig.applicationCallbacks?.getApplicationInstance()).thenReturn(app) - DownloadServiceInterface.setImpl(DownloadServiceInterfaceTestStub()) - - // create new database -// PodDBAdapter.init(context) -// deleteDatabase() -// val adapter = getInstance() -// adapter.open() -// adapter.close() - - val prefEdit = PreferenceManager.getDefaultSharedPreferences( - context.applicationContext).edit() - prefEdit.putBoolean(UserPreferences.Prefs.prefDeleteRemovesFromQueue.name, true).commit() - } - - @After - fun tearDown() { -// PodDBAdapter.tearDownTests() -// DBWriter.tearDownTests() - - val testDir = context.getExternalFilesDir(TEST_FOLDER) ?: return - val files = testDir.listFiles() ?: return - for (f in files) { - f.delete() - } - } - - @Test - @Throws(Exception::class) - fun testSetFeedMediaPlaybackInformation() { - val position = 50 - val lastPlayedTime: Long = 1000 - val playedDuration = 60 - val duration = 100 - - val feed = Feed("url", null, "title") - val items: MutableList = ArrayList() - feed.episodes.addAll(items) - val item = Episode(0, "Item", "Item", "url", Date(), PlayState.PLAYED.code, feed) - items.add(item) - val media = EpisodeMedia(0, item, duration, 1, 1, "mime_type", - "dummy path", "download_url", true, null, 0, 0) - item.setMedia(media) - - runBlocking { - val job = persistEpisode(item) - withTimeout(TIMEOUT*1000) { job.join() } - } - - media.setPosition(position) - media.setLastPlayedTime(lastPlayedTime) - media.playedDuration = playedDuration - - if (item.media != null) { - runBlocking { - val job = persistEpisodeMedia(item.media!!) - withTimeout(TIMEOUT * 1000) { job.join() } - } - } - - val itemFromDb = getEpisode(item.id) - val mediaFromDb = itemFromDb!!.media - - Assert.assertEquals(position.toLong(), mediaFromDb!!.getPosition().toLong()) - Assert.assertEquals(lastPlayedTime, mediaFromDb.getLastPlayedTime()) - Assert.assertEquals(playedDuration.toLong(), mediaFromDb.playedDuration.toLong()) - Assert.assertEquals(duration.toLong(), mediaFromDb.getDuration().toLong()) - } - - @Test - @Throws(Exception::class) - fun testDeleteFeedMediaOfItemFileExists() { - val dest = File(context.getExternalFilesDir(TEST_FOLDER), "testFile") - - Assert.assertTrue(dest.createNewFile()) - - val feed = Feed("url", null, "title") - val items: MutableList = ArrayList() - feed.episodes.addAll(items) - val item = Episode(0, "Item", "Item", "url", Date(), PlayState.PLAYED.code, feed) - - var media: EpisodeMedia? = EpisodeMedia(0, item, 1, 1, 1, "mime_type", - dest.absolutePath, "download_url", true, null, 0, 0) - item.setMedia(media) - - items.add(item) - -// val adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(feed) -// adapter.close() - Assert.assertTrue(media!!.id != 0L) - Assert.assertTrue(item.id != 0L) - - runBlocking { - val job = deleteEpisodeMedia(context, item) - withTimeout(TIMEOUT*1000) { job.join() } - } - media = getEpisodeMedia(media.id) - Assert.assertNotNull(media) - Assert.assertFalse(dest.exists()) - Assert.assertFalse(media!!.downloaded) - Assert.assertNull(media.fileUrl) - } - - @Test - @Throws(Exception::class) - fun testDeleteFeedMediaOfItemRemoveFromQueue() { - Assert.assertTrue(shouldDeleteRemoveFromQueue()) - - val dest = File(context.getExternalFilesDir(TEST_FOLDER), "testFile") - - Assert.assertTrue(dest.createNewFile()) - - val feed = Feed("url", null, "title") - val items: MutableList = ArrayList() - feed.episodes.addAll(items) - val item = Episode(0, "Item", "Item", "url", Date(), PlayState.UNPLAYED.code, feed) - - var media: EpisodeMedia? = EpisodeMedia(0, item, 1, 1, 1, "mime_type", - dest.absolutePath, "download_url", true, null, 0, 0) - item.setMedia(media) - - items.add(item) - var queue: MutableList = mutableListOf() - queue.add(item) - -// val adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(feed) -// adapter.setQueue(queue) -// adapter.close() - Assert.assertTrue(media!!.id != 0L) - Assert.assertTrue(item.id != 0L) - queue = curQueue.episodes - Assert.assertTrue(queue.size != 0) - - deleteEpisodeMedia(context, item) - Awaitility.await().timeout(2, TimeUnit.SECONDS).until { !dest.exists() } - media = getEpisodeMedia(media.id) - Assert.assertNotNull(media) - Assert.assertFalse(dest.exists()) - Assert.assertFalse(media!!.downloaded) - Assert.assertNull(media.fileUrl) - Awaitility.await().timeout(2, TimeUnit.SECONDS).until { curQueue.episodes.isEmpty() } - } - - @Test - @Throws(Exception::class) - fun testDeleteFeed() { - val destFolder = context.getExternalFilesDir(TEST_FOLDER) - Assert.assertNotNull(destFolder) - - val feed = Feed("url", null, "title") - feed.episodes.clear() - - val itemFiles: MutableList = ArrayList() - // create items with downloaded media files - for (i in 0..9) { - val item = Episode(0, "Item $i", "Item$i", "url", Date(), PlayState.PLAYED.code, feed) - feed.episodes.add(item) - - val enc = File(destFolder, "file $i") - Assert.assertTrue(enc.createNewFile()) - - itemFiles.add(enc) - val media = EpisodeMedia(0, item, 1, 1, 1, "mime_type", - enc.absolutePath, "download_url", true, null, 0, 0) - item.setMedia(media) - } - -// var adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(feed) -// adapter.close() - - Assert.assertTrue(feed.id != 0L) - for (item in feed.episodes) { - Assert.assertTrue(item.id != 0L) - Assert.assertTrue(item.media!!.id != 0L) - } - - runBlocking { - deleteFeedSync(context, feed.id) - } - - // check if files still exist - for (f in itemFiles) { - Assert.assertFalse(f.exists()) - } - -// adapter = getInstance() -// adapter.open() -// var c = adapter.getFeedCursor(feed.id) -// Assert.assertEquals(0, c.count.toLong()) -// c.close() -// for (item in feed.items) { -// c = adapter.getFeedItemCursor(item.id.toString()) -// Assert.assertEquals(0, c.count.toLong()) -// c.close() -// c = adapter.getSingleFeedMediaCursor(item.media!!.id) -// Assert.assertEquals(0, c.count.toLong()) -// c.close() -// } -// adapter.close() - } - - @Test - @Throws(Exception::class) - fun testDeleteFeedNoItems() { - val destFolder = context.getExternalFilesDir(TEST_FOLDER) - Assert.assertNotNull(destFolder) - - val feed = Feed("url", null, "title") - feed.episodes.clear() - feed.imageUrl = ("url") - -// var adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(feed) -// adapter.close() - - Assert.assertTrue(feed.id != 0L) - - runBlocking { - deleteFeedSync(context, feed.id) - } - -// adapter = getInstance() -// adapter.open() -// val c = adapter.getFeedCursor(feed.id) -// Assert.assertEquals(0, c.count.toLong()) -// c.close() -// adapter.close() - } - - @Test - @Throws(Exception::class) - fun testDeleteFeedNoFeedMedia() { - val destFolder = context.getExternalFilesDir(TEST_FOLDER) - Assert.assertNotNull(destFolder) - - val feed = Feed("url", null, "title") - feed.episodes.clear() - - feed.imageUrl = ("url") - - // create items - for (i in 0..9) { - val item = Episode(0, "Item $i", "Item$i", "url", Date(), PlayState.PLAYED.code, feed) - feed.episodes.add(item) - } - -// var adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(feed) -// adapter.close() - - Assert.assertTrue(feed.id != 0L) - for (item in feed.episodes) { - Assert.assertTrue(item.id != 0L) - } - - runBlocking { - deleteFeedSync(context, feed.id) - } - -// adapter = getInstance() -// adapter.open() -// var c = adapter.getFeedCursor(feed.id) -// Assert.assertEquals(0, c.count.toLong()) -// c.close() -// for (item in feed.items) { -// c = adapter.getFeedItemCursor(item.id.toString()) -// Assert.assertEquals(0, c.count.toLong()) -// c.close() -// } -// adapter.close() - } - - @Test - @Throws(Exception::class) - fun testDeleteFeedWithQueueItems() { - val destFolder = context.getExternalFilesDir(TEST_FOLDER) - Assert.assertNotNull(destFolder) - - val feed = Feed("url", null, "title") - feed.episodes.clear() - - feed.imageUrl = ("url") - - // create items with downloaded media files - for (i in 0..9) { - val item = Episode(0, "Item $i", "Item$i", "url", Date(), PlayState.PLAYED.code, feed) - feed.episodes.add(item) - val enc = File(destFolder, "file $i") - val media = EpisodeMedia(0, item, 1, 1, 1, "mime_type", - enc.absolutePath, "download_url", false, null, 0, 0) - item.setMedia(media) - } - -// val adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(feed) -// adapter.close() - - Assert.assertTrue(feed.id != 0L) - for (item in feed.episodes) { - Assert.assertTrue(item.id != 0L) - Assert.assertTrue(item.media!!.id != 0L) - } - - val queue: List = feed.episodes.toList() -// adapter.open() -// adapter.setQueue(queue) -// -// val queueCursor = adapter.queueIDCursor -// Assert.assertEquals(queue.size.toLong(), queueCursor.count.toLong()) -// queueCursor.close() -// -// adapter.close() - runBlocking { - deleteFeedSync(context, feed.id) - } -// adapter.open() -// -// var c = adapter.getFeedCursor(feed.id) -// Assert.assertEquals(0, c.count.toLong()) -// c.close() -// for (item in feed.items) { -// c = adapter.getFeedItemCursor(item.id.toString()) -// Assert.assertEquals(0, c.count.toLong()) -// c.close() -// c = adapter.getSingleFeedMediaCursor(item.media!!.id) -// Assert.assertEquals(0, c.count.toLong()) -// c.close() -// } -// c = adapter.queueCursor -// Assert.assertEquals(0, c.count.toLong()) -// c.close() -// adapter.close() - } - - @Test - @Throws(Exception::class) - fun testDeleteFeedNoDownloadedFiles() { - val destFolder = context.getExternalFilesDir(TEST_FOLDER) - Assert.assertNotNull(destFolder) - - val feed = Feed("url", null, "title") - feed.episodes.clear() - - feed.imageUrl = ("url") - - // create items with downloaded media files - for (i in 0..9) { - val item = Episode(0, "Item $i", "Item$i", "url", Date(), PlayState.PLAYED.code, feed) - feed.episodes.add(item) - val enc = File(destFolder, "file $i") - val media = EpisodeMedia(0, item, 1, 1, 1, "mime_type", - enc.absolutePath, "download_url", false, null, 0, 0) - item.setMedia(media) - } - -// var adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(feed) -// adapter.close() - - Assert.assertTrue(feed.id != 0L) - for (item in feed.episodes) { - Assert.assertTrue(item.id != 0L) - Assert.assertTrue(item.media!!.id != 0L) - } - - runBlocking { - deleteFeedSync(context, feed.id) - } - -// adapter = getInstance() -// adapter.open() -// var c = adapter.getFeedCursor(feed.id) -// Assert.assertEquals(0, c.count.toLong()) -// c.close() -// for (item in feed.items) { -// c = adapter.getFeedItemCursor(item.id.toString()) -// Assert.assertEquals(0, c.count.toLong()) -// c.close() -// c = adapter.getSingleFeedMediaCursor(item.media!!.id) -// Assert.assertEquals(0, c.count.toLong()) -// c.close() -// } -// adapter.close() - } - - @Test - @Throws(Exception::class) - fun testDeleteFeedItems() { - val feed = Feed("url", null, "title") - feed.episodes.clear() - feed.imageUrl = ("url") - - // create items - for (i in 0..9) { - val item = Episode(0, "Item $i", "Item$i", "url", Date(), PlayState.PLAYED.code, feed) - item.setMedia(EpisodeMedia(item, "", 0, "")) - feed.episodes.add(item) - } - -// var adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(feed) -// adapter.close() - - val itemsToDelete: List = feed.episodes.subList(0, 2) - runBlocking { - val job = deleteEpisodes(context, itemsToDelete) - withTimeout(TIMEOUT*1000) { job.join() } - } - -// adapter = getInstance() -// adapter.open() -// for (i in 0 until feed.items.size) { -// val feedItem: FeedItem = feed.items[i] -// val c = adapter.getFeedItemCursor(feedItem.id.toString()) -// if (i < 2) Assert.assertEquals(0, c.count.toLong()) -// else Assert.assertEquals(1, c.count.toLong()) -// c.close() -// } -// adapter.close() - } - - private fun playbackHistorySetup(playbackCompletionDate: Date?): EpisodeMedia { - val feed = Feed("url", null, "title") - feed.episodes.clear() - val item = Episode(0, "title", "id", "link", Date(), PlayState.PLAYED.code, feed) - val media = EpisodeMedia(0, item, 10, 0, 1, "mime", null, - "url", false, playbackCompletionDate, 0, 0) - feed.episodes.add(item) - item.setMedia(media) -// val adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(feed) -// adapter.close() - Assert.assertTrue(media.id != 0L) - return media - } - - @Test - @Throws(Exception::class) - fun testAddItemToPlaybackHistoryNotPlayedYet() { - val media: EpisodeMedia = playbackHistorySetup(null) - val item_ = media.episodeOrFetch() - if (item_ != null) { - runBlocking { - val job = setCompletionDate(item_) - withTimeout(TIMEOUT * 1000) { job.join() } - } - } - Assert.assertNotNull(media) - Assert.assertNotNull(media.playbackCompletionDate) - } - - @Test - @Throws(Exception::class) - fun testAddItemToPlaybackHistoryAlreadyPlayed() { - val oldDate: Long = 0 - - val media: EpisodeMedia = playbackHistorySetup(Date(oldDate)) - val item_ = media.episodeOrFetch() - if (item_ != null) { - runBlocking { - val job = setCompletionDate(item_) - withTimeout(TIMEOUT*1000) { job.join() } - } - } - - Assert.assertNotNull(media) - Assert.assertNotNull(media.playbackCompletionDate) - Assert.assertNotEquals(media.playbackCompletionDate!!.time, oldDate) - } - - @Throws(Exception::class) - private fun queueTestSetupMultipleItems(numItems: Int): Feed { - enqueueLocation = Queues.EnqueueLocation.BACK - val feed = Feed("url", null, "title") - feed.episodes.clear() - for (i in 0 until numItems) { - val item = Episode(0, "title $i", "id $i", "link $i", Date(), PlayState.PLAYED.code, feed) - item.setMedia(EpisodeMedia(item, "", 0, "")) - feed.episodes.add(item) - } - -// val adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(feed) -// adapter.close() - - for (item in feed.episodes) { - Assert.assertTrue(item.id != 0L) - } - val futures: MutableList> = ArrayList() - // TODO: -// for (item in feed.items) { -// futures.add(addQueueItem(context, item)) -// } -// for (f in futures) { -// f[TIMEOUT, TimeUnit.SECONDS] -// } - return feed - } - - @Test - @Throws(Exception::class) - fun testAddQueueItemSingleItem() { - val feed = Feed("url", null, "title") - feed.episodes.clear() - val item = Episode(0, "title", "id", "link", Date(), PlayState.PLAYED.code, feed) - item.setMedia(EpisodeMedia(item, "", 0, "")) - feed.episodes.add(item) - -// var adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(feed) -// adapter.close() - - Assert.assertTrue(item.id != 0L) - runBlocking { - val job = addToQueue(item) - withTimeout(TIMEOUT*1000) { job.join() } - } - -// adapter = getInstance() -// adapter.open() -// val cursor = adapter.queueIDCursor -// Assert.assertTrue(cursor.moveToFirst()) -// Assert.assertEquals(item.id, cursor.getLong(0)) -// cursor.close() -// adapter.close() - } - - @Test - @Throws(Exception::class) - fun testAddQueueItemSingleItemAlreadyInQueue() { - val feed = Feed("url", null, "title") - feed.episodes.clear() - val item = Episode(0, "title", "id", "link", Date(), PlayState.PLAYED.code, feed) - item.setMedia(EpisodeMedia(item, "", 0, "")) - feed.episodes.add(item) - -// var adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(feed) -// adapter.close() - - Assert.assertTrue(item.id != 0L) - runBlocking { - val job = addToQueue(item) - withTimeout(TIMEOUT*1000) { job.join() } - } - -// adapter = getInstance() -// adapter.open() -// var cursor = adapter.queueIDCursor -// Assert.assertTrue(cursor.moveToFirst()) -// Assert.assertEquals(item.id, cursor.getLong(0)) -// cursor.close() -// adapter.close() - - runBlocking { - val job = addToQueue(item) - withTimeout(TIMEOUT*1000) { job.join() } - } -// adapter = getInstance() -// adapter.open() -// cursor = adapter.queueIDCursor -// Assert.assertTrue(cursor.moveToFirst()) -// Assert.assertEquals(item.id, cursor.getLong(0)) -// Assert.assertEquals(1, cursor.count.toLong()) -// cursor.close() -// adapter.close() - } - - @Test - @Throws(Exception::class) - fun testAddQueueItemMultipleItems() { -// val numItems = 10 -// val feed = queueTestSetupMultipleItems(numItems) -// val adapter = getInstance() -// adapter.open() -// val cursor = adapter.queueIDCursor -// Assert.assertTrue(cursor.moveToFirst()) -// Assert.assertEquals(numItems.toLong(), cursor.count.toLong()) -// val expectedIds = getIdList(feed.items) -// val actualIds: MutableList = ArrayList() -// for (i in 0 until numItems) { -// Assert.assertTrue(cursor.moveToPosition(i)) -// actualIds.add(cursor.getLong(0)) -// } -// cursor.close() -// adapter.close() -// Assert.assertEquals("Bulk add to queue: result order should be the same as the order given", expectedIds, actualIds) - } - - @Test - @Throws(Exception::class) - fun testClearQueue() { - val numItems = 10 - - queueTestSetupMultipleItems(numItems) - runBlocking { - val job = clearQueue() - withTimeout(TIMEOUT*1000) { job.join() } - } -// val adapter = getInstance() -// adapter.open() -// val cursor = adapter.queueIDCursor -// Assert.assertFalse(cursor.moveToFirst()) -// cursor.close() -// adapter.close() - } - - @Test - @Throws(Exception::class) - fun testRemoveQueueItem() { - val numItems = 10 - val feed = createTestFeed(numItems) - - for (removeIndex in 0 until numItems) { - val item: Episode = feed.episodes[removeIndex] -// var adapter = getInstance() -// adapter.open() -// adapter.setQueue(feed.items.toList()) -// adapter.close() - - runBlocking { - val job = removeFromQueue(item) - withTimeout(TIMEOUT*1000) { job.join() } - } -// adapter = getInstance() -// adapter.open() -// val queue = adapter.queueIDCursor -// Assert.assertEquals((numItems - 1).toLong(), queue.count.toLong()) -// for (i in 0 until queue.count) { -// Assert.assertTrue(queue.moveToPosition(i)) -// val queueID = queue.getLong(0) -// Assert.assertTrue(queueID != item.id) // removed item is no longer in queue -// var idFound = false -// for (other in feed.items) { // items that were not removed are still in the queue -// idFound = idFound or (other.id == queueID) -// } -// Assert.assertTrue(idFound) -// } -// queue.close() -// adapter.close() - } - } - - @Test - @Throws(Exception::class) - fun testRemoveQueueItemMultipleItems() { - val numItems = 5 - val numInQueue = numItems - 1 // the last one not in queue for boundary condition - val feed = createTestFeed(numItems) - -// val itemsToAdd: List = feed.items.subList(0, numInQueue) -// withPodDB { adapter: PodDBAdapter? -> adapter!!.setQueue(itemsToAdd) } - - // Actual tests - // - - // Use array rather than List to make codes more succinct - val itemIds = toItemIds(feed.episodes).toTypedArray() - - runBlocking { - val job = removeFromQueue(feed.episodes[1], feed.episodes[3]) - withTimeout(TIMEOUT*1000) { job.join() } - } - assertQueueByItemIds("Average case - 2 items removed successfully", itemIds[0], itemIds[2]) - - runBlocking { - val job = removeFromQueue() - withTimeout(TIMEOUT*1000) { job.join() } - } - assertQueueByItemIds("Boundary case - no items supplied. queue should see no change", itemIds[0], itemIds[2]) - - runBlocking { - val job = removeFromQueue( feed.episodes[0], feed.episodes[4]) - withTimeout(TIMEOUT*1000) { job.join() } - } - assertQueueByItemIds("Boundary case - items not in queue ignored", itemIds[2]) - - runBlocking { - val job = removeFromQueue( feed.episodes[2]) - withTimeout(TIMEOUT*1000) { job.join() } - } - assertQueueByItemIds("Boundary case - invalid itemIds ignored") // the queue is empty - } - - @Test - @Throws(Exception::class) - fun testMoveQueueItem() { - val numItems = 10 - val feed = Feed("url", null, "title") - feed.episodes.clear() - for (i in 0 until numItems) { - val item = Episode(0, "title $i", "id $i", "link $i", - Date(), PlayState.PLAYED.code, feed) - item.setMedia(EpisodeMedia(item, "", 0, "")) - feed.episodes.add(item) - } - -// var adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(feed) -// adapter.close() - - for (item in feed.episodes) { - Assert.assertTrue(item.id != 0L) - } - for (from in 0 until numItems) { - for (to in 0 until numItems) { - if (from == to) continue - - Logd(TAG, String.format(Locale.US, "testMoveQueueItem: From=%d, To=%d", from, to)) - val fromID: Long = feed.episodes[from].id - -// adapter = getInstance() -// adapter.open() -// adapter.setQueue(feed.items) -// adapter.close() - - runBlocking { - val job = moveInQueue(from, to, false) - withTimeout(TIMEOUT*1000) { job.join() } - } -// adapter = getInstance() -// adapter.open() -// val queue = adapter.queueIDCursor -// Assert.assertEquals(numItems.toLong(), queue.count.toLong()) -// Assert.assertTrue(queue.moveToPosition(from)) -// Assert.assertNotEquals(fromID, queue.getLong(0)) -// Assert.assertTrue(queue.moveToPosition(to)) -// Assert.assertEquals(fromID, queue.getLong(0)) -// -// queue.close() -// adapter.close() - } - } - } - - @Test - @Throws(Exception::class) - fun testRemoveAllNewFlags() { - val numItems = 10 - val feed = Feed("url", null, "title") - feed.episodes.clear() - for (i in 0 until numItems) { - val item = Episode(0, "title $i", "id $i", "link $i", Date(), PlayState.NEW.code, feed) - item.setMedia(EpisodeMedia(item, "", 0, "")) - feed.episodes.add(item) - } - -// val adapter = getInstance() -// adapter.open() -// adapter.setCompleteFeed(feed) -// adapter.close() - - Assert.assertTrue(feed.id != 0L) - for (item in feed.episodes) { - Assert.assertTrue(item.id != 0L) - } - -// runBlocking { removeAllNewFlags().join() } - val loadedItems = feed.episodes - for (item in loadedItems) { - Assert.assertFalse(item.isNew) - } - } - - companion object { - private val TAG: String = DbWriterTest::class.simpleName ?: "Anonymous" - private const val TEST_FOLDER = "testDBWriter" - private const val TIMEOUT = 5L - - private fun createTestFeed(numItems: Int): Feed { - val feed = Feed("url", null, "title") - feed.episodes.clear() - for (i in 0 until numItems) { - val item = Episode(0, "title $i", "id $i", "link $i", Date(), PlayState.PLAYED.code, feed) - item.setMedia(EpisodeMedia(item, "", 0, "")) - feed.episodes.add(item) - } - -// withPodDB { adapter: PodDBAdapter? -> adapter!!.setCompleteFeed(feed) } - - for (item in feed.episodes) { - Assert.assertTrue(item.id != 0L) - } - return feed - } - -// private fun withPodDB(action: Consumer) { -//// val adapter = getInstance() -//// try { -//// adapter.open() -//// action.accept(adapter) -//// } finally { -//// adapter.close() -//// } -// } - - private fun assertQueueByItemIds(message: String, vararg itemIdsExpected: Long) { - val queue = curQueue.episodes - val itemIdsActualList = toItemIds(queue) - val itemIdsExpectedList: MutableList = ArrayList(itemIdsExpected.size) - for (id in itemIdsExpected) { - itemIdsExpectedList.add(id) - } - - Assert.assertEquals(message, itemIdsExpectedList, itemIdsActualList) - } - - private fun toItemIds(items: List): List { - val itemIds: MutableList = ArrayList(items.size) - for (item in items) { - itemIds.add(item.id) - } - return itemIds - } - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/storage/EpisodeDuplicateGuesserTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/storage/EpisodeDuplicateGuesserTest.kt deleted file mode 100644 index 673717a8..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/storage/EpisodeDuplicateGuesserTest.kt +++ /dev/null @@ -1,70 +0,0 @@ -package ac.mdiq.podcini.storage - -import ac.mdiq.podcini.storage.database.Feeds.EpisodeDuplicateGuesser.seemDuplicates -import ac.mdiq.podcini.storage.model.Episode -import ac.mdiq.podcini.storage.model.EpisodeMedia -import org.junit.Assert -import org.junit.Test -import java.util.* - -/** - * Test class for [EpisodeDuplicateGuesser]. - */ -class EpisodeDuplicateGuesserTest { - @Test - fun testSameId() { - Assert.assertTrue(seemDuplicates( - item("id", "Title1", "example.com/episode1", 0, 5 * MINUTES, "audio/*"), - item("id", "Title2", "example.com/episode2", 0, 20 * MINUTES, "video/*"))) - } - - @Test - fun testDuplicateDownloadUrl() { - Assert.assertTrue(seemDuplicates( - item("id1", "Title1", "example.com/episode", 0, 5 * MINUTES, "audio/*"), - item("id2", "Title2", "example.com/episode", 0, 5 * MINUTES, "audio/*"))) - Assert.assertFalse(seemDuplicates( - item("id1", "Title1", "example.com/episode1", 0, 5 * MINUTES, "audio/*"), - item("id2", "Title2", "example.com/episode2", 0, 5 * MINUTES, "audio/*"))) - } - - @Test - fun testOtherAttributes() { - Assert.assertTrue(seemDuplicates( - item("id1", "Title", "example.com/episode1", 10, 5 * MINUTES, "audio/*"), - item("id2", "Title", "example.com/episode2", 10, 5 * MINUTES, "audio/*"))) - Assert.assertTrue(seemDuplicates( - item("id1", "Title", "example.com/episode1", 10, 5 * MINUTES, "audio/*"), - item("id2", "Title", "example.com/episode2", 20, 6 * MINUTES, "audio/*"))) - Assert.assertFalse(seemDuplicates( - item("id1", "Title", "example.com/episode1", 10, 5 * MINUTES, "audio/*"), - item("id2", "Title", "example.com/episode2", 10, 5 * MINUTES, "video/*"))) - Assert.assertTrue(seemDuplicates( - item("id1", "Title", "example.com/episode1", 10, 5 * MINUTES, "audio/mpeg"), - item("id2", "Title", "example.com/episode2", 10, 5 * MINUTES, "audio/mp3"))) - Assert.assertFalse(seemDuplicates( - item("id1", "Title", "example.com/episode1", 5 * DAYS, 5 * MINUTES, "audio/*"), - item("id2", "Title", "example.com/episode2", 2 * DAYS, 5 * MINUTES, "audio/*"))) - } - - @Test - fun testNoMediaType() { - Assert.assertTrue(seemDuplicates( - item("id1", "Title", "example.com/episode1", 2 * DAYS, 5 * MINUTES, ""), - item("id2", "Title", "example.com/episode2", 2 * DAYS, 5 * MINUTES, ""))) - } - - private fun item(guid: String, title: String, downloadUrl: String, - date: Long, duration: Long, mime: String - ): Episode { - val item = Episode(0, title, guid, "link", Date(date), PlayState.PLAYED.code, null) - val media = EpisodeMedia(item, downloadUrl, duration, mime) - item.setMedia(media) - return item - } - - companion object { - private const val MINUTES = (1000 * 60).toLong() - private const val DAYS = 24 * 60 * MINUTES - } -} \ No newline at end of file diff --git a/app/src/test/kotlin/ac/mdiq/podcini/storage/ExceptFavoriteCleanupAlgorithmTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/storage/ExceptFavoriteCleanupAlgorithmTest.kt deleted file mode 100644 index 3db6cdce..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/storage/ExceptFavoriteCleanupAlgorithmTest.kt +++ /dev/null @@ -1,87 +0,0 @@ -package ac.mdiq.podcini.storage - -import ac.mdiq.podcini.storage.model.Feed -import ac.mdiq.podcini.storage.model.Episode -import ac.mdiq.podcini.preferences.UserPreferences -import ac.mdiq.podcini.storage.algorithms.AutoCleanups.performAutoCleanup -import org.junit.Assert -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import java.io.File -import java.io.IOException - -/** - * Tests that the APFavoriteCleanupAlgorithm is working correctly. - */ -@RunWith(RobolectricTestRunner::class) -class ExceptFavoriteCleanupAlgorithmTest : DbCleanupTests() { - private val numberOfItems = EPISODE_CACHE_SIZE * 2 - - init { - setCleanupAlgorithm(UserPreferences.EPISODE_CLEANUP_EXCEPT_FAVORITE) - } - - @Test - @Throws(IOException::class) - override fun testPerformAutoCleanupHandleUnplayed() { - val feed = Feed("url", null, "title") - val items: MutableList = ArrayList() - feed.episodes.addAll(items) - val files: MutableList = ArrayList() - populateItems(numberOfItems, feed, items, files, PlayState.UNPLAYED.code, false, false) - - performAutoCleanup(context) - for (i in files.indices) { - if (i < EPISODE_CACHE_SIZE) { - Assert.assertTrue("Only enough items should be deleted", files[i].exists()) - } else { - Assert.assertFalse("Expected episode to be deleted", files[i].exists()) - } - } - } - - @Test - @Throws(IOException::class) - fun testPerformAutoCleanupDeletesQueued() { - val feed = Feed("url", null, "title") - val items: MutableList = ArrayList() - feed.episodes.addAll(items) - val files: MutableList = ArrayList() - populateItems(numberOfItems, feed, items, files, PlayState.UNPLAYED.code, true, false) - - performAutoCleanup(context) - for (i in files.indices) { - if (i < EPISODE_CACHE_SIZE) { - Assert.assertTrue("Only enough items should be deleted", files[i].exists()) - } else { - Assert.assertFalse("Queued episodes should be deleted", files[i].exists()) - } - } - } - - @Test - @Throws(IOException::class) - fun testPerformAutoCleanupSavesFavorited() { - val feed = Feed("url", null, "title") - val items: MutableList = ArrayList() - feed.episodes.addAll(items) - val files: MutableList = ArrayList() - populateItems(numberOfItems, feed, items, files, PlayState.UNPLAYED.code, false, true) - - performAutoCleanup(context) - for (i in files.indices) { - Assert.assertTrue("Favorite episodes should should not be deleted", files[i].exists()) - } - } - - @Throws(IOException::class) - override fun testPerformAutoCleanupShouldNotDeleteBecauseInQueue() { - // Yes it should - } - - @Throws(IOException::class) - override fun testPerformAutoCleanupShouldNotDeleteBecauseInQueue_withFeedsWithNoMedia() { - // Yes it should - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/storage/ItemEnqueuePositionCalculatorTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/storage/ItemEnqueuePositionCalculatorTest.kt deleted file mode 100644 index ee0feb53..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/storage/ItemEnqueuePositionCalculatorTest.kt +++ /dev/null @@ -1,151 +0,0 @@ -package ac.mdiq.podcini.storage - -import ac.mdiq.podcini.feed.FeedMother.anyFeed -import ac.mdiq.podcini.net.download.service.DownloadServiceInterface -import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterfaceTestStub -import ac.mdiq.podcini.storage.database.Queues -import ac.mdiq.podcini.storage.database.Queues.EnqueueLocation -import ac.mdiq.podcini.storage.model.Episode -import ac.mdiq.podcini.storage.model.EpisodeMedia -import ac.mdiq.podcini.storage.model.Playable -import ac.mdiq.podcini.storage.model.RemoteMedia -import ac.mdiq.podcini.util.CollectionTestUtil -import ac.mdiq.podcini.storage.utils.EpisodeUtil.getIdList -import org.junit.Assert -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import java.util.* -import java.util.stream.Collectors - -object ItemEnqueuePositionCalculatorTest { - fun doAddToQueueAndAssertResult(message: String?, - calculator: Queues.EnqueuePositionPolicy, - itemToAdd: Episode, - queue: MutableList, - currentlyPlaying: Playable?, - idsExpected: List? - ) { - val posActual = calculator.calcPosition(queue, currentlyPlaying) - queue.add(posActual, itemToAdd) - Assert.assertEquals(message, idsExpected, getIdList(queue)) - } - - val QUEUE_EMPTY: List = Collections.unmodifiableList(emptyList()) - - val QUEUE_DEFAULT: List = Collections.unmodifiableList(listOf( - createFeedItem(11), createFeedItem(12), createFeedItem(13), createFeedItem(14))) - val QUEUE_DEFAULT_IDS: List = QUEUE_DEFAULT.stream().map(Episode::id).collect(Collectors.toList()) - - fun getCurrentlyPlaying(idCurrentlyPlaying: Long): Playable? { - if (ID_CURRENTLY_PLAYING_NOT_FEEDMEDIA == idCurrentlyPlaying) { - return externalMedia() - } - if (ID_CURRENTLY_PLAYING_NULL == idCurrentlyPlaying) { - return null - } - return createFeedItem(idCurrentlyPlaying).media - } - - fun externalMedia(): Playable { - return RemoteMedia(createFeedItem(0)) - } - - const val ID_CURRENTLY_PLAYING_NULL: Long = -1L - const val ID_CURRENTLY_PLAYING_NOT_FEEDMEDIA: Long = -9999L - - - fun createFeedItem(id: Long): Episode { - val item = Episode(id, "Item$id", "ItemId$id", "url", - Date(), PlayState.PLAYED.code, anyFeed()) - val media = EpisodeMedia(item, "http://download.url.net/$id", 1234567, "audio/mpeg") - media.id = item.id - item.setMedia(media) - return item - } -} - -@RunWith(Parameterized::class) -open class BasicTest { - @Parameterized.Parameter - var message: String? = null - - @Parameterized.Parameter(1) - var idsExpected: List? = null - - @Parameterized.Parameter(2) - var options: Queues.EnqueueLocation? = null - - @Parameterized.Parameter(3) - var curQueue: List? = null - - /** - * Add a FeedItem with ID [.TFI_ID] with the setup - */ - @Test - fun test() { - DownloadServiceInterface.setImpl(DownloadServiceInterfaceTestStub()) - val calculator = Queues.EnqueuePositionPolicy(options!!) - - // shallow copy to which the test will add items - val queue: MutableList = ArrayList(curQueue) - val tFI = ItemEnqueuePositionCalculatorTest.createFeedItem(TFI_ID) - ItemEnqueuePositionCalculatorTest.doAddToQueueAndAssertResult(message, - calculator, tFI, queue, currentlyPlaying, - idsExpected) - } - - open val currentlyPlaying: Playable? - get() = null - - companion object { - @Parameterized.Parameters(name = "{index}: case<{0}>, expected:{1}") - fun data(): Iterable> { - return listOf(arrayOf("case default, i.e., add to the end", - CollectionTestUtil.concat(ItemEnqueuePositionCalculatorTest.QUEUE_DEFAULT_IDS, TFI_ID), - EnqueueLocation.BACK, ItemEnqueuePositionCalculatorTest.QUEUE_DEFAULT), arrayOf("case option enqueue at front", - CollectionTestUtil.concat(TFI_ID, ItemEnqueuePositionCalculatorTest.QUEUE_DEFAULT_IDS), - EnqueueLocation.FRONT, ItemEnqueuePositionCalculatorTest.QUEUE_DEFAULT), arrayOf("case empty queue, option default", - CollectionTestUtil.list(TFI_ID), - EnqueueLocation.BACK, ItemEnqueuePositionCalculatorTest.QUEUE_EMPTY), arrayOf("case empty queue, option enqueue at front", - CollectionTestUtil.list(TFI_ID), - EnqueueLocation.FRONT, ItemEnqueuePositionCalculatorTest.QUEUE_EMPTY)) - } - - const val TFI_ID: Long = 101 - } -} - -@RunWith(Parameterized::class) -class AfterCurrentlyPlayingTest : BasicTest() { - @Parameterized.Parameter(4) - var idCurrentlyPlaying: Long = 0 - - override val currentlyPlaying: Playable? - get() = ItemEnqueuePositionCalculatorTest.getCurrentlyPlaying(idCurrentlyPlaying) - - companion object { - @Parameterized.Parameters(name = "{index}: case<{0}>, expected:{1}") - fun data(): Iterable> { - return listOf(arrayOf("case option after currently playing", - CollectionTestUtil.list(11L, TFI_ID, 12L, 13L, 14L), - EnqueueLocation.AFTER_CURRENTLY_PLAYING, ItemEnqueuePositionCalculatorTest.QUEUE_DEFAULT, 11L), arrayOf("case option after currently playing, currently playing in the middle of the queue", - CollectionTestUtil.list(11L, 12L, 13L, TFI_ID, 14L), - EnqueueLocation.AFTER_CURRENTLY_PLAYING, ItemEnqueuePositionCalculatorTest.QUEUE_DEFAULT, 13L), arrayOf("case option after currently playing, currently playing is not in queue", - CollectionTestUtil.concat(TFI_ID, ItemEnqueuePositionCalculatorTest.QUEUE_DEFAULT_IDS), - EnqueueLocation.AFTER_CURRENTLY_PLAYING, ItemEnqueuePositionCalculatorTest.QUEUE_DEFAULT, 99L), arrayOf("case option after currently playing, no currentlyPlaying is null", - CollectionTestUtil.concat(TFI_ID, ItemEnqueuePositionCalculatorTest.QUEUE_DEFAULT_IDS), - EnqueueLocation.AFTER_CURRENTLY_PLAYING, - ItemEnqueuePositionCalculatorTest.QUEUE_DEFAULT, ID_CURRENTLY_PLAYING_NULL), arrayOf("case option after currently playing, currentlyPlaying is not a feedMedia", - CollectionTestUtil.concat(TFI_ID, ItemEnqueuePositionCalculatorTest.QUEUE_DEFAULT_IDS), - EnqueueLocation.AFTER_CURRENTLY_PLAYING, - ItemEnqueuePositionCalculatorTest.QUEUE_DEFAULT, ID_CURRENTLY_PLAYING_NOT_FEEDMEDIA), arrayOf("case empty queue, option after currently playing", - CollectionTestUtil.list(TFI_ID), - EnqueueLocation.AFTER_CURRENTLY_PLAYING, - ItemEnqueuePositionCalculatorTest.QUEUE_EMPTY, ID_CURRENTLY_PLAYING_NULL)) - } - - private const val ID_CURRENTLY_PLAYING_NULL = -1L - private const val ID_CURRENTLY_PLAYING_NOT_FEEDMEDIA = -9999L - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/storage/mapper/FeedCursorMapperTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/storage/mapper/FeedCursorMapperTest.kt deleted file mode 100644 index f37d8ec6..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/storage/mapper/FeedCursorMapperTest.kt +++ /dev/null @@ -1,81 +0,0 @@ -package ac.mdiq.podcini.storage.mapper - -//@RunWith(RobolectricTestRunner::class) -//class FeedCursorMapperTest { -// private var adapter: PodDBAdapter? = null -// -// @Before -// fun setUp() { -// val context = InstrumentationRegistry.getInstrumentation().context -// -//// init(context) -//// adapter = getInstance() -// -// writeFeedToDatabase() -// } -// -// @After -// fun tearDown() { -//// tearDownTests() -// } -// -// @Test -// fun testFromCursor() { -//// adapter!!.allFeedsCursor.use { cursor -> -//// cursor.moveToNext() -//// val feed = convert(cursor) -//// Assert.assertTrue(feed.id >= 0) -//// Assert.assertEquals("feed custom title", feed.title) -//// Assert.assertEquals("feed custom title", feed.getCustomTitle()) -//// assertEquals("feed link", feed.link) -//// assertEquals("feed description", feed.description) -//// Assert.assertEquals("feed payment link", feed.paymentLinks!![0].url) -//// assertEquals("feed author", feed.author) -//// assertEquals("feed language", feed.language) -//// assertEquals("feed image url", feed.imageUrl) -//// Assert.assertEquals("feed file url", feed.getFile_url()) -//// assertEquals("feed download url", feed.download_url) -//// Assert.assertTrue(feed.downloaded) -//// assertEquals("feed last update", feed.lastUpdate) -//// assertEquals("feed type", feed.type) -//// assertEquals("feed identifier", feed.feedIdentifier) -//// Assert.assertTrue(feed.isPaged) -//// assertEquals("feed next page link", feed.nextPageLink) -//// Assert.assertTrue(feed.itemFilter!!.showUnplayed) -//// Assert.assertEquals(1, feed.sortOrder!!.code.toLong()) -//// Assert.assertTrue(feed.hasLastUpdateFailed()) -//// } -// } -// -// /** -// * Insert test data to the database. -// * Uses raw database insert instead of adapter.setCompleteFeed() to avoid testing the Feed class -// * against itself. -// */ -// private fun writeFeedToDatabase() { -// val values = ContentValues() -// values.put(PodDBAdapter.KEY_TITLE, "feed title") -// values.put(PodDBAdapter.KEY_CUSTOM_TITLE, "feed custom title") -// values.put(PodDBAdapter.KEY_LINK, "feed link") -// values.put(PodDBAdapter.KEY_DESCRIPTION, "feed description") -// values.put(PodDBAdapter.KEY_PAYMENT_LINK, "feed payment link") -// values.put(PodDBAdapter.KEY_AUTHOR, "feed author") -// values.put(PodDBAdapter.KEY_LANGUAGE, "feed language") -// values.put(PodDBAdapter.KEY_IMAGE_URL, "feed image url") -// -// values.put(PodDBAdapter.KEY_FILE_URL, "feed file url") -// values.put(PodDBAdapter.KEY_DOWNLOAD_URL, "feed download url") -// values.put(PodDBAdapter.KEY_DOWNLOADED, true) -// values.put(PodDBAdapter.KEY_LASTUPDATE, "feed last update") -// values.put(PodDBAdapter.KEY_TYPE, "feed type") -// values.put(PodDBAdapter.KEY_FEED_IDENTIFIER, "feed identifier") -// -// values.put(PodDBAdapter.KEY_IS_PAGED, true) -// values.put(PodDBAdapter.KEY_NEXT_PAGE_LINK, "feed next page link") -// values.put(PodDBAdapter.KEY_HIDE, "unplayed") -// values.put(PodDBAdapter.KEY_SORT_ORDER, "1") -// values.put(PodDBAdapter.KEY_LAST_UPDATE_FAILED, true) -// -//// adapter!!.insertTestData(PodDBAdapter.TABLE_NAME_FEEDS, values) -// } -//} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/sync/EpisodeActionFilterTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/sync/EpisodeActionFilterTest.kt deleted file mode 100644 index d9948308..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/sync/EpisodeActionFilterTest.kt +++ /dev/null @@ -1,182 +0,0 @@ -package ac.mdiq.podcini.sync - -import ac.mdiq.podcini.net.sync.SyncService.Companion.getRemoteActionsOverridingLocalActions -import ac.mdiq.podcini.net.sync.model.EpisodeAction -import junit.framework.TestCase -import java.text.ParseException -import java.text.SimpleDateFormat - - -class EpisodeActionFilterTest : TestCase() { -// var episodeActionFilter: EpisodeActionFilter = EpisodeActionFilter - - @Throws(ParseException::class) - fun testGetRemoteActionsHappeningAfterLocalActions() { - val format = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") - val morning = format.parse("2021-01-01 08:00:00") - val lateMorning = format.parse("2021-01-01 09:00:00") - - val episodeActions: MutableList = ArrayList() - episodeActions.add(EpisodeAction.Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY) - .timestamp(morning) - .position(10) - .build() - ) - episodeActions.add(EpisodeAction.Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY) - .timestamp(lateMorning) - .position(20) - .build() - ) - episodeActions.add(EpisodeAction.Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY) - .timestamp(morning) - .position(5) - .build() - ) - - val morningFiveMinutesLater = format.parse("2021-01-01 08:05:00") - val remoteActions: MutableList = ArrayList() - remoteActions.add(EpisodeAction.Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY) - .timestamp(morningFiveMinutesLater) - .position(10) - .build() - ) - remoteActions.add(EpisodeAction.Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY) - .timestamp(morningFiveMinutesLater) - .position(5) - .build() - ) - - val uniqueList =getRemoteActionsOverridingLocalActions(remoteActions, episodeActions) - assertSame(1, uniqueList.size) - } - - @Throws(ParseException::class) - fun testGetRemoteActionsHappeningBeforeLocalActions() { - val format = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") - val morning = format.parse("2021-01-01 08:00:00") - val lateMorning = format.parse("2021-01-01 09:00:00") - - val episodeActions: MutableList = ArrayList() - episodeActions.add(EpisodeAction.Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY) - .timestamp(morning) - .position(10) - .build() - ) - episodeActions.add(EpisodeAction.Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY) - .timestamp(lateMorning) - .position(20) - .build() - ) - episodeActions.add(EpisodeAction.Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY) - .timestamp(morning) - .position(5) - .build() - ) - - val morningFiveMinutesEarlier = format.parse("2021-01-01 07:55:00") - val remoteActions: MutableList = ArrayList() - remoteActions.add(EpisodeAction.Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY) - .timestamp(morningFiveMinutesEarlier) - .position(10) - .build() - ) - remoteActions.add(EpisodeAction.Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY) - .timestamp(morningFiveMinutesEarlier) - .position(5) - .build() - ) - - val uniqueList = getRemoteActionsOverridingLocalActions(remoteActions, episodeActions) - assertSame(0, uniqueList.size) - } - - @Throws(ParseException::class) - fun testGetMultipleRemoteActionsHappeningAfterLocalActions() { - val format = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") - val morning = format.parse("2021-01-01 08:00:00") - - val episodeActions: MutableList = ArrayList() - episodeActions.add(EpisodeAction.Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY) - .timestamp(morning) - .position(10) - .build() - ) - episodeActions.add(EpisodeAction.Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY) - .timestamp(morning) - .position(5) - .build() - ) - - val morningFiveMinutesLater = format.parse("2021-01-01 08:05:00") - val remoteActions: MutableList = ArrayList() - remoteActions.add(EpisodeAction.Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY) - .timestamp(morningFiveMinutesLater) - .position(10) - .build() - ) - remoteActions.add(EpisodeAction.Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY) - .timestamp(morningFiveMinutesLater) - .position(5) - .build() - ) - - val uniqueList = getRemoteActionsOverridingLocalActions(remoteActions, episodeActions) - assertEquals(2, uniqueList.size) - } - - @Throws(ParseException::class) - fun testGetMultipleRemoteActionsHappeningBeforeLocalActions() { - val format = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") - val morning = format.parse("2021-01-01 08:00:00") - - val episodeActions: MutableList = ArrayList() - episodeActions.add(EpisodeAction.Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY) - .timestamp(morning) - .position(10) - .build() - ) - episodeActions.add(EpisodeAction.Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY) - .timestamp(morning) - .position(5) - .build() - ) - - val morningFiveMinutesEarlier = format.parse("2021-01-01 07:55:00") - val remoteActions: MutableList = ArrayList() - remoteActions.add(EpisodeAction.Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY) - .timestamp(morningFiveMinutesEarlier) - .position(10) - .build() - ) - remoteActions.add(EpisodeAction.Builder("podcast.a", "episode.2", EpisodeAction.Action.PLAY) - .timestamp(morningFiveMinutesEarlier) - .position(5) - .build() - ) - - val uniqueList = getRemoteActionsOverridingLocalActions(remoteActions, episodeActions) - assertEquals(0, uniqueList.size) - } - - @Throws(ParseException::class) - fun testPresentRemoteTimestampOverridesMissingLocalTimestamp() { - val format = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") - val arbitraryTime = format.parse("2021-01-01 08:00:00") - - val episodeActions: MutableList = ArrayList() - episodeActions.add(EpisodeAction.Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY) // no timestamp - .position(10) - .build() - ) - - val remoteActions: MutableList = ArrayList() - remoteActions.add(EpisodeAction.Builder("podcast.a", "episode.1", EpisodeAction.Action.PLAY) - .timestamp(arbitraryTime) - .position(10) - .build() - ) - - val uniqueList = getRemoteActionsOverridingLocalActions(remoteActions, episodeActions) - assertSame(1, uniqueList.size) - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/sync/GuidValidatorTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/sync/GuidValidatorTest.kt deleted file mode 100644 index c4430c6c..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/sync/GuidValidatorTest.kt +++ /dev/null @@ -1,19 +0,0 @@ -package ac.mdiq.podcini.sync - -import ac.mdiq.podcini.net.sync.SyncService.Companion.isValidGuid -import junit.framework.TestCase - -class GuidValidatorTest : TestCase() { - fun testIsValidGuid() { - assertTrue(isValidGuid("skfjsdvgsd")) - } - - fun testIsInvalidGuid() { - assertFalse(isValidGuid("")) - assertFalse(isValidGuid(" ")) - assertFalse(isValidGuid("\n")) - assertFalse(isValidGuid(" \n")) - assertFalse(isValidGuid(null)) - assertFalse(isValidGuid("null")) - } -} \ No newline at end of file diff --git a/app/src/test/kotlin/ac/mdiq/podcini/util/CollectionTestUtil.kt b/app/src/test/kotlin/ac/mdiq/podcini/util/CollectionTestUtil.kt deleted file mode 100644 index 81129c02..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/util/CollectionTestUtil.kt +++ /dev/null @@ -1,27 +0,0 @@ -package ac.mdiq.podcini.util - -import java.util.* - -object CollectionTestUtil { - fun concat(item: T, list: List?): List { - val res: MutableList = ArrayList(list) - res.add(0, item) - return res - } - - fun concat(list: List?, item: T): List { - val res: MutableList = ArrayList(list) - res.add(item) - return res - } - - fun concat(list1: List?, list2: List?): List { - val res: MutableList = ArrayList(list1) - res.addAll(list2!!) - return res - } - - fun list(vararg a: T): List { - return listOf(*a) - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/util/DurationConverterTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/util/DurationConverterTest.kt deleted file mode 100644 index f23426db..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/util/DurationConverterTest.kt +++ /dev/null @@ -1,41 +0,0 @@ -package ac.mdiq.podcini.util - -import ac.mdiq.podcini.storage.utils.DurationConverter.durationStringLongToMs -import ac.mdiq.podcini.storage.utils.DurationConverter.durationStringShortToMs -import ac.mdiq.podcini.storage.utils.DurationConverter.getDurationStringLong -import ac.mdiq.podcini.storage.utils.DurationConverter.getDurationStringShort -import org.junit.Assert -import org.junit.Test - -/** - * Test class for converter - */ -class DurationConverterTest { - @Test - fun testGetDurationStringLong() { - val expected = "13:05:10" - val input = 47110000 - Assert.assertEquals(expected, getDurationStringLong(input)) - } - - @Test - fun testGetDurationStringShort() { - val expected = "13:05" - Assert.assertEquals(expected, getDurationStringShort(47110000, true)) - Assert.assertEquals(expected, getDurationStringShort(785000, false)) - } - - @Test - fun testDurationStringLongToMs() { - val input = "01:20:30" - val expected: Long = 4830000 - Assert.assertEquals(expected, durationStringLongToMs(input).toLong()) - } - - @Test - fun testDurationStringShortToMs() { - val input = "8:30" - Assert.assertEquals(30600000, durationStringShortToMs(input, true).toLong()) - Assert.assertEquals(510000, durationStringShortToMs(input, false).toLong()) - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/util/EpisodePermutorsTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/util/EpisodePermutorsTest.kt deleted file mode 100644 index 89ea6867..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/util/EpisodePermutorsTest.kt +++ /dev/null @@ -1,222 +0,0 @@ -package ac.mdiq.podcini.util - -import ac.mdiq.podcini.storage.utils.EpisodesPermutors.getPermutor -import ac.mdiq.podcini.storage.model.Feed -import ac.mdiq.podcini.storage.model.Episode -import ac.mdiq.podcini.storage.model.EpisodeMedia -import ac.mdiq.podcini.storage.model.EpisodeSortOrder -import org.junit.Assert -import org.junit.Test -import java.util.* - -/** - * Test class for FeedItemPermutors. - */ -class EpisodePermutorsTest { - @Test - fun testEnsureNonNullPermutors() { - for (sortOrder in EpisodeSortOrder.entries) { - Assert.assertNotNull("The permutor for SortOrder $sortOrder is unexpectedly null", - getPermutor(sortOrder)) - } - } - - @Test - fun testPermutorForRule_EPISODE_TITLE_ASC() { - val permutor = getPermutor(EpisodeSortOrder.EPISODE_TITLE_A_Z) - - val itemList = testList - Assert.assertTrue(checkIdOrder(itemList, 1, 3, 2)) // before sorting - permutor.reorder(itemList) - Assert.assertTrue(checkIdOrder(itemList, 1, 2, 3)) // after sorting - } - - @Test - fun testPermutorForRule_EPISODE_TITLE_ASC_NullTitle() { - val permutor = getPermutor(EpisodeSortOrder.EPISODE_TITLE_A_Z) - - val itemList = testList.toMutableList() - itemList[2].title = (null) - Assert.assertTrue(checkIdOrder(itemList, 1, 3, 2)) // before sorting - permutor.reorder(itemList) - Assert.assertTrue(checkIdOrder(itemList, 2, 1, 3)) // after sorting - } - - - @Test - fun testPermutorForRule_EPISODE_TITLE_DESC() { - val permutor = getPermutor(EpisodeSortOrder.EPISODE_TITLE_Z_A) - - val itemList = testList - Assert.assertTrue(checkIdOrder(itemList, 1, 3, 2)) // before sorting - permutor.reorder(itemList) - Assert.assertTrue(checkIdOrder(itemList, 3, 2, 1)) // after sorting - } - - @Test - fun testPermutorForRule_DATE_ASC() { - val permutor = getPermutor(EpisodeSortOrder.DATE_OLD_NEW) - - val itemList = testList - Assert.assertTrue(checkIdOrder(itemList, 1, 3, 2)) // before sorting - permutor.reorder(itemList) - Assert.assertTrue(checkIdOrder(itemList, 1, 2, 3)) // after sorting - } - - @Test - fun testPermutorForRule_DATE_ASC_NulPubDatel() { - val permutor = getPermutor(EpisodeSortOrder.DATE_OLD_NEW) - - val itemList = testList - itemList[2] // itemId 2 - .setPubDate(null) - Assert.assertTrue(checkIdOrder(itemList, 1, 3, 2)) // before sorting - permutor.reorder(itemList) - Assert.assertTrue(checkIdOrder(itemList, 2, 1, 3)) // after sorting - } - - @Test - fun testPermutorForRule_DATE_DESC() { - val permutor = getPermutor(EpisodeSortOrder.DATE_NEW_OLD) - - val itemList = testList - Assert.assertTrue(checkIdOrder(itemList, 1, 3, 2)) // before sorting - permutor.reorder(itemList) - Assert.assertTrue(checkIdOrder(itemList, 3, 2, 1)) // after sorting - } - - @Test - fun testPermutorForRule_DURATION_ASC() { - val permutor = getPermutor(EpisodeSortOrder.DURATION_SHORT_LONG) - - val itemList = testList - Assert.assertTrue(checkIdOrder(itemList, 1, 3, 2)) // before sorting - permutor.reorder(itemList) - Assert.assertTrue(checkIdOrder(itemList, 1, 2, 3)) // after sorting - } - - @Test - fun testPermutorForRule_DURATION_DESC() { - val permutor = getPermutor(EpisodeSortOrder.DURATION_LONG_SHORT) - - val itemList = testList - Assert.assertTrue(checkIdOrder(itemList, 1, 3, 2)) // before sorting - permutor.reorder(itemList) - Assert.assertTrue(checkIdOrder(itemList, 3, 2, 1)) // after sorting - } - - @Test - fun testPermutorForRule_size_asc() { - val permutor = getPermutor(EpisodeSortOrder.SIZE_SMALL_LARGE) - - val itemList = testList - Assert.assertTrue(checkIdOrder(itemList, 1, 3, 2)) // before sorting - permutor.reorder(itemList) - Assert.assertTrue(checkIdOrder(itemList, 1, 2, 3)) // after sorting - } - - @Test - fun testPermutorForRule_size_desc() { - val permutor = getPermutor(EpisodeSortOrder.SIZE_LARGE_SMALL) - - val itemList = testList - Assert.assertTrue(checkIdOrder(itemList, 1, 3, 2)) // before sorting - permutor.reorder(itemList) - Assert.assertTrue(checkIdOrder(itemList, 3, 2, 1)) // after sorting - } - - @Test - fun testPermutorForRule_DURATION_DESC_NullMedia() { - val permutor = getPermutor(EpisodeSortOrder.DURATION_LONG_SHORT) - - val itemList = testList - itemList[1] // itemId 3 - .setMedia(null) - Assert.assertTrue(checkIdOrder(itemList, 1, 3, 2)) // before sorting - permutor.reorder(itemList) - Assert.assertTrue(checkIdOrder(itemList, 2, 1, 3)) // after sorting - } - - @Test - fun testPermutorForRule_FEED_TITLE_ASC() { - val permutor = getPermutor(EpisodeSortOrder.FEED_TITLE_A_Z) - - val itemList = testList - Assert.assertTrue(checkIdOrder(itemList, 1, 3, 2)) // before sorting - permutor.reorder(itemList) - Assert.assertTrue(checkIdOrder(itemList, 1, 2, 3)) // after sorting - } - - @Test - fun testPermutorForRule_FEED_TITLE_DESC() { - val permutor = getPermutor(EpisodeSortOrder.FEED_TITLE_Z_A) - - val itemList = testList - Assert.assertTrue(checkIdOrder(itemList, 1, 3, 2)) // before sorting - permutor.reorder(itemList) - Assert.assertTrue(checkIdOrder(itemList, 3, 2, 1)) // after sorting - } - - @Test - fun testPermutorForRule_FEED_TITLE_DESC_NullTitle() { - val permutor = getPermutor(EpisodeSortOrder.FEED_TITLE_Z_A) - - val itemList = testList - itemList[1].feed!!.title = (null) - Assert.assertTrue(checkIdOrder(itemList, 1, 3, 2)) // before sorting - permutor.reorder(itemList) - Assert.assertTrue(checkIdOrder(itemList, 2, 1, 3)) // after sorting - } - - private val testList: MutableList - /** - * Generates a list with test data. - */ - get() { - val itemList: MutableList = ArrayList() - - val calendar = Calendar.getInstance() - calendar[2019, 0] = 1 // January 1st - val feed1 = Feed(null, null, "Feed title 1") - val episode1 = Episode(1, "Title 1", null, null, calendar.time, 0, feed1) - val episodeMedia1 = EpisodeMedia(0, episode1, 1000, 0, 100, null, null, null, true, null, 0, 0) - episode1.setMedia(episodeMedia1) - itemList.add(episode1) - - calendar[2019, 2] = 1 // March 1st - val feed2 = Feed(null, null, "Feed title 3") - val episode2 = Episode(3, "Title 3", null, null, calendar.time, 0, feed2) - val episodeMedia2 = EpisodeMedia(0, episode2, 3000, 0, 300, null, null, null, true, null, 0, 0) - episode2.setMedia(episodeMedia2) - itemList.add(episode2) - - calendar[2019, 1] = 1 // February 1st - val feed3 = Feed(null, null, "Feed title 2") - val episode3 = Episode(2, "Title 2", null, null, calendar.time, 0, feed3) - val episodeMedia3 = EpisodeMedia(0, episode3, 2000, 0, 200, null, null, null, true, null, 0, 0) - episode3.setMedia(episodeMedia3) - itemList.add(episode3) - - return itemList - } - - /** - * Checks if both lists have the same size and the same ID order. - * - * @param itemList Item list. - * @param ids List of IDs. - * @return `true` if both lists have the same size and the same ID order. - */ - private fun checkIdOrder(itemList: List, vararg ids: Long): Boolean { - if (itemList.size != ids.size) { - return false - } - - for (i in ids.indices) { - if (itemList[i].id != ids[i]) { - return false - } - } - return true - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/util/EpisodeUtilTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/util/EpisodeUtilTest.kt deleted file mode 100644 index 2f1e2955..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/util/EpisodeUtilTest.kt +++ /dev/null @@ -1,86 +0,0 @@ -package ac.mdiq.podcini.util - -import ac.mdiq.podcini.storage.utils.EpisodeUtil.getIdList -import ac.mdiq.podcini.storage.model.Episode -import ac.mdiq.podcini.storage.model.Feed -import org.junit.Assert -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized - -@RunWith(Parameterized::class) -class EpisodeUtilTest(private val msg: String, - private val feedLink: String, - private val itemLink: String, - private val expected: String -) { - // Test the getIds() method - @Test - fun testGetIds() { - val feedItemsList: MutableList = ArrayList(5) - val idList: MutableList = ArrayList() - - idList.add(980) - idList.add(324) - idList.add(226) - idList.add(164) - idList.add(854) - - for (i in 0..4) { - val item = createFeedItem(feedLink, itemLink) - item.id = idList[i].toLong() - feedItemsList.add(item) - } - - val actual = getIdList(feedItemsList) - - // covers edge case for getIds() method - val emptyList: List = ArrayList() - val testEmptyList = getIdList(emptyList) - Assert.assertEquals(msg, 0, testEmptyList.size.toLong()) - Assert.assertEquals(msg, 980, actual[0]) - Assert.assertEquals(msg, 324, actual[1]) - Assert.assertEquals(msg, 226, actual[2]) - Assert.assertEquals(msg, 164, actual[3]) - Assert.assertEquals(msg, 854, actual[4]) - } - - // Tests the Null value for getLinkWithFallback() method - @Test - fun testLinkWithFallbackNullValue() { - val actual = null - Assert.assertEquals(msg, null, actual) - } - - @Test - fun testLinkWithFallback() { - val actual = createFeedItem(feedLink, itemLink).getLinkWithFallback() - Assert.assertEquals(msg, expected, actual) - } - - companion object { - private const val FEED_LINK = "http://example.com" - private const val ITEM_LINK = "http://example.com/feedItem1" - - @Parameterized.Parameters - fun data(): Collection> { - return listOf(arrayOf("average", FEED_LINK, ITEM_LINK, ITEM_LINK), - arrayOf("null item link - fallback to feed", FEED_LINK, null, FEED_LINK), - arrayOf("empty item link - same as null", FEED_LINK, "", FEED_LINK), - arrayOf("blank item link - same as null", FEED_LINK, " ", FEED_LINK), - arrayOf("fallback, but feed link is null too", null, null, null), - arrayOf("fallback - but empty feed link - same as null", "", null, null), - arrayOf("fallback - but blank feed link - same as null", " ", null, null)) - } - - private fun createFeedItem(feedLink: String, itemLink: String): Episode { - val feed = Feed() - feed.link = (feedLink) - val episode = Episode() - episode.link = (itemLink) - episode.feed = (feed) - feed.episodes.add(episode) - return episode - } - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/util/FilenameGeneratorTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/util/FilenameGeneratorTest.kt deleted file mode 100644 index c088408f..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/util/FilenameGeneratorTest.kt +++ /dev/null @@ -1,93 +0,0 @@ -package ac.mdiq.podcini.util - -import ac.mdiq.podcini.storage.utils.FileNameGenerator -import androidx.test.platform.app.InstrumentationRegistry -import ac.mdiq.podcini.storage.utils.FileNameGenerator.generateFileName -import org.apache.commons.lang3.StringUtils -import org.junit.Assert -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import java.io.File - -@RunWith(RobolectricTestRunner::class) -class FilenameGeneratorTest { - @Test - @Throws(Exception::class) - fun testGenerateFileName() { - val result = generateFileName("abc abc") - Assert.assertEquals(result, "abc abc") - createFiles(result) - } - - @Test - @Throws(Exception::class) - fun testGenerateFileName1() { - val result = generateFileName("ab/c: ("10:12", "1:10:12") - - val shownotes = ("

Some test text with a timecode " + timeStrings[0] - + " here. Hey look another one " + timeStrings[1] + " here!

") - val t = ShownotesCleaner(context) - val res = t.processShownotes(shownotes, 2 * 60 * 60 * 1000) - checkLinkCorrect(res, longArrayOf((10 * 60 * 1000 + 12 * 1000).toLong(), - (60 * 60 * 1000 + 10 * 60 * 1000 + 12 * 1000).toLong()), timeStrings) - } - - @Test - fun testProcessShownotesAddTimecodeMultipleShortFormatNoChapters() { - // One of these timecodes fits as HH:MM and one does not so both should be parsed as MM:SS. - - val timeStrings = arrayOf("10:12", "2:12") - - val shownotes = ("

Some test text with a timecode " + timeStrings[0] - + " here. Hey look another one " + timeStrings[1] + " here!

") - val t = ShownotesCleaner(context) - val res = t.processShownotes(shownotes, 3 * 60 * 60 * 1000) - checkLinkCorrect(res, - longArrayOf((10 * 60 * 1000 + 12 * 1000).toLong(), (2 * 60 * 1000 + 12 * 1000).toLong()), - timeStrings) - } - - @Test - fun testProcessShownotesAddTimecodeParentheses() { - val timeStr = "10:11" - val time = (3600 * 1000 * 10 + 60 * 1000 * 11).toLong() - - val shownotes = "

Some test text with a timecode ($timeStr) here.

" - val t = ShownotesCleaner(context) - val res = t.processShownotes(shownotes, Int.MAX_VALUE) - checkLinkCorrect(res, longArrayOf(time), arrayOf(timeStr)) - } - - @Test - fun testProcessShownotesAddTimecodeBrackets() { - val timeStr = "10:11" - val time = (3600 * 1000 * 10 + 60 * 1000 * 11).toLong() - - val shownotes = "

Some test text with a timecode [$timeStr] here.

" - val t = ShownotesCleaner(context) - val res = t.processShownotes(shownotes, Int.MAX_VALUE) - checkLinkCorrect(res, longArrayOf(time), arrayOf(timeStr)) - } - - @Test - fun testProcessShownotesAddTimecodeAngleBrackets() { - val timeStr = "10:11" - val time = (3600 * 1000 * 10 + 60 * 1000 * 11).toLong() - - val shownotes = "

Some test text with a timecode <$timeStr> here.

" - val t = ShownotesCleaner(context) - val res = t.processShownotes(shownotes, Int.MAX_VALUE) - checkLinkCorrect(res, longArrayOf(time), arrayOf(timeStr)) - } - - @Test - fun testProcessShownotesAndInvalidTimecode() { - val timeStrs = arrayOf("2:1", "0:0", "000", "00", "00:000") - - val shownotes = StringBuilder("

Some test text with timecodes ") - for (timeStr in timeStrs) { - shownotes.append(timeStr).append(" ") - } - shownotes.append("here.

") - - val t = ShownotesCleaner(context) - val res = t.processShownotes(shownotes.toString(), Int.MAX_VALUE) - checkLinkCorrect(res, LongArray(0), arrayOfNulls(0)) - } - - private fun checkLinkCorrect(res: String, timecodes: LongArray, timecodeStr: Array) { - Assert.assertNotNull(res) - val d = Jsoup.parse(res) - val links = d.body().getElementsByTag("a") - var countedLinks = 0 - for (link in links) { - val href = link.attributes()["href"] - val text = link.text() - if (href.startsWith("podcini://")) { - Assert.assertTrue(href.endsWith(timecodes[countedLinks].toString())) - Assert.assertEquals(timecodeStr[countedLinks], text) - countedLinks++ - Assert.assertTrue("Contains too many links: " + countedLinks + " > " - + timecodes.size, countedLinks <= timecodes.size) - } - } - Assert.assertEquals(timecodes.size.toLong(), countedLinks.toLong()) - } - - @Test - fun testIsTimecodeLink() { - Assert.assertFalse(isTimecodeLink(null)) - Assert.assertFalse(isTimecodeLink("http://podcini/timecode/123123")) - Assert.assertFalse(isTimecodeLink("podcini://timecode/")) - Assert.assertFalse(isTimecodeLink("podcini://123123")) - Assert.assertFalse(isTimecodeLink("podcini://timecode/123123a")) - Assert.assertTrue(isTimecodeLink("podcini://timecode/123")) - Assert.assertTrue(isTimecodeLink("podcini://timecode/1")) - } - - @Test - fun testGetTimecodeLinkTime() { - Assert.assertEquals(-1, getTimecodeLinkTime(null).toLong()) - Assert.assertEquals(-1, getTimecodeLinkTime("http://timecode/123").toLong()) - Assert.assertEquals(123, getTimecodeLinkTime("podcini://timecode/123").toLong()) - } - - @Test - fun testCleanupColors() { - val input = ("/* /* */ .foo { text-decoration: underline;color:#f00;font-weight:bold;}" - + "#bar { text-decoration: underline;color:#f00;font-weight:bold; }" - + "div {text-decoration: underline; color /* */ : /* */ #f00 /* */; font-weight:bold; }" - + "#foobar { /* color: */ text-decoration: underline; /* color: */font-weight:bold /* ; */; }" - + "baz { background-color:#f00;border: solid 2px;border-color:#0f0;text-decoration: underline; }") - val expected = (" .foo { text-decoration: underline;font-weight:bold;}" - + "#bar { text-decoration: underline;font-weight:bold; }" - + "div {text-decoration: underline; font-weight:bold; }" - + "#foobar { text-decoration: underline; font-weight:bold ; }" - + "baz { background-color:#f00;border: solid 2px;border-color:#0f0;text-decoration: underline; }") - Assert.assertEquals(expected, cleanStyleTag(input)) - } -} diff --git a/app/src/test/kotlin/ac/mdiq/podcini/util/syndication/FeedDiscovererTest.kt b/app/src/test/kotlin/ac/mdiq/podcini/util/syndication/FeedDiscovererTest.kt deleted file mode 100644 index 66423b65..00000000 --- a/app/src/test/kotlin/ac/mdiq/podcini/util/syndication/FeedDiscovererTest.kt +++ /dev/null @@ -1,134 +0,0 @@ -package ac.mdiq.podcini.util.syndication - -import ac.mdiq.podcini.ui.fragment.OnlineFeedFragment -import androidx.test.platform.app.InstrumentationRegistry -import org.apache.commons.io.FileUtils -import org.apache.commons.io.IOUtils -import org.junit.After -import org.junit.Assert -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import java.io.File -import java.io.FileOutputStream -import java.nio.charset.StandardCharsets - -/** - * Test class for [FeedDiscoverer] - */ -@RunWith(RobolectricTestRunner::class) -class FeedDiscovererTest { - private var fd: OnlineFeedFragment.FeedDiscoverer? = null - - private var testDir: File? = null - - @Before - fun setUp() { - fd = OnlineFeedFragment.FeedDiscoverer() - testDir = File(InstrumentationRegistry - .getInstrumentation().targetContext.filesDir, "FeedDiscovererTest") - testDir!!.mkdir() - Assert.assertTrue(testDir!!.exists()) - } - - @After - @Throws(Exception::class) - fun tearDown() { - FileUtils.deleteDirectory(testDir) - } - - private fun createTestHtmlString(rel: String, type: String, href: String, title: String): String { - return String.format("Test", - rel, type, href, title) - } - - private fun createTestHtmlString(rel: String, type: String, href: String): String { - return String.format("Test", - rel, type, href) - } - - @Throws(Exception::class) - private fun checkFindUrls(isAlternate: Boolean, - isRss: Boolean, - withTitle: Boolean, - isAbsolute: Boolean, - fromString: Boolean - ) { - val title = "Test title" - val hrefAbs = "http://example.com/feed" - val hrefRel = "/feed" - val base = "http://example.com" - - val rel = if (isAlternate) "alternate" else "feed" - val type = if (isRss) "application/rss+xml" else "application/atom+xml" - val href = if (isAbsolute) hrefAbs else hrefRel - - val res: Map - val html = if (withTitle) createTestHtmlString(rel, type, href, title) - else createTestHtmlString(rel, type, href) - if (fromString) { - res = fd!!.findLinks(html, base) - } else { - val testFile = File(testDir, "feed") - val out = FileOutputStream(testFile) - IOUtils.write(html, out, StandardCharsets.UTF_8) - out.close() - res = fd!!.findLinks(testFile, base) - } - - Assert.assertNotNull(res) - Assert.assertEquals(1, res.size.toLong()) - for (key in res.keys) { - Assert.assertEquals(hrefAbs, key) - } - Assert.assertTrue(res.containsKey(hrefAbs)) - if (withTitle) { - Assert.assertEquals(title, res[hrefAbs]) - } else { - Assert.assertEquals(href, res[hrefAbs]) - } - } - - @Test - @Throws(Exception::class) - fun testAlternateRSSWithTitleAbsolute() { - checkFindUrls(true, true, true, true, true) - } - - @Test - @Throws(Exception::class) - fun testAlternateRSSWithTitleRelative() { - checkFindUrls(true, true, true, false, true) - } - - @Test - @Throws(Exception::class) - fun testAlternateRSSNoTitleAbsolute() { - checkFindUrls(true, true, false, true, true) - } - - @Test - @Throws(Exception::class) - fun testAlternateRSSNoTitleRelative() { - checkFindUrls(true, true, false, false, true) - } - - @Test - @Throws(Exception::class) - fun testAlternateAtomWithTitleAbsolute() { - checkFindUrls(true, false, true, true, true) - } - - @Test - @Throws(Exception::class) - fun testFeedAtomWithTitleAbsolute() { - checkFindUrls(false, false, true, true, true) - } - - @Test - @Throws(Exception::class) - fun testAlternateRSSWithTitleAbsoluteFromFile() { - checkFindUrls(true, true, true, true, false) - } -} diff --git a/app/src/test/kotlin/android/util/Log.kt b/app/src/test/kotlin/android/util/Log.kt deleted file mode 100644 index 2f529a80..00000000 --- a/app/src/test/kotlin/android/util/Log.kt +++ /dev/null @@ -1,243 +0,0 @@ -package android.util - -import java.io.PrintWriter -import java.io.StringWriter - -/** - * A stub for [android.util.Log] to be used in unit tests. - * - * It outputs the log statements to standard error. - */ -object Log { - /** - * Priority constant for the println method; use Log.v. - */ - const val VERBOSE: Int = 2 - - /** - * Priority constant for the println method; use Log.d. - */ - const val DEBUG: Int = 3 - - /** - * Priority constant for the println method; use Log.i. - */ - const val INFO: Int = 4 - - /** - * Priority constant for the println method; use Log.w. - */ - const val WARN: Int = 5 - - /** - * Priority constant for the println method; use Log.e. - */ - const val ERROR: Int = 6 - - /** - * Priority constant for the println method. - */ - const val ASSERT: Int = 7 - - /** - * Send a [.VERBOSE] log message. - * @param tag Used to identify the source of a log message. It usually identifies - * the class or activity where the log call occurs. - * @param msg The message you would like logged. - */ - fun v(tag: String, msg: String): Int { - return println_native(LOG_ID_MAIN, VERBOSE, tag, msg) - } - - /** - * Send a [.VERBOSE] log message and log the exception. - * @param tag Used to identify the source of a log message. It usually identifies - * the class or activity where the log call occurs. - * @param msg The message you would like logged. - * @param tr An exception to log - */ - fun v(tag: String, msg: String, tr: Throwable?): Int { - return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr) - } - - /** - * Send a [.DEBUG] log message. - * @param tag Used to identify the source of a log message. It usually identifies - * the class or activity where the log call occurs. - * @param msg The message you would like logged. - */ - fun d(tag: String, msg: String): Int { - return println_native(LOG_ID_MAIN, DEBUG, tag, msg) - } - - /** - * Send a [.DEBUG] log message and log the exception. - * @param tag Used to identify the source of a log message. It usually identifies - * the class or activity where the log call occurs. - * @param msg The message you would like logged. - * @param tr An exception to log - */ - fun d(tag: String, msg: String, tr: Throwable?): Int { - return printlns(LOG_ID_MAIN, DEBUG, tag, msg, tr) - } - - /** - * Send an [.INFO] log message. - * @param tag Used to identify the source of a log message. It usually identifies - * the class or activity where the log call occurs. - * @param msg The message you would like logged. - */ - fun i(tag: String, msg: String): Int { - return println_native(LOG_ID_MAIN, INFO, tag, msg) - } - - /** - * Send a [.INFO] log message and log the exception. - * @param tag Used to identify the source of a log message. It usually identifies - * the class or activity where the log call occurs. - * @param msg The message you would like logged. - * @param tr An exception to log - */ - fun i(tag: String, msg: String, tr: Throwable?): Int { - return printlns(LOG_ID_MAIN, INFO, tag, msg, tr) - } - - /** - * Send a [.WARN] log message. - * @param tag Used to identify the source of a log message. It usually identifies - * the class or activity where the log call occurs. - * @param msg The message you would like logged. - */ - fun w(tag: String, msg: String): Int { - return println_native(LOG_ID_MAIN, WARN, tag, msg) - } - - /** - * Send a [.WARN] log message and log the exception. - * @param tag Used to identify the source of a log message. It usually identifies - * the class or activity where the log call occurs. - * @param msg The message you would like logged. - * @param tr An exception to log - */ - fun w(tag: String, msg: String, tr: Throwable?): Int { - return printlns(LOG_ID_MAIN, WARN, tag, msg, tr) - } - - /** - * Checks to see whether or not a log for the specified tag is loggable at the specified level. - * - * @return true in all cases (for unit test environment) - */ - fun isLoggable(tag: String?, level: Int): Boolean { - return true - } - - /* - * Send a {@link #WARN} log message and log the exception. - * @param tag Used to identify the source of a log message. It usually identifies - * the class or activity where the log call occurs. - * @param tr An exception to log - */ - fun w(tag: String, tr: Throwable?): Int { - return printlns(LOG_ID_MAIN, WARN, tag, "", tr) - } - - /** - * Send an [.ERROR] log message. - * @param tag Used to identify the source of a log message. It usually identifies - * the class or activity where the log call occurs. - * @param msg The message you would like logged. - */ - fun e(tag: String, msg: String): Int { - return println_native(LOG_ID_MAIN, ERROR, tag, msg) - } - - /** - * Send a [.ERROR] log message and log the exception. - * @param tag Used to identify the source of a log message. It usually identifies - * the class or activity where the log call occurs. - * @param msg The message you would like logged. - * @param tr An exception to log - */ - fun e(tag: String, msg: String, tr: Throwable?): Int { - return printlns(LOG_ID_MAIN, ERROR, tag, msg, tr) - } - - /** - * What a Terrible Failure: Report a condition that should never happen. - * The error will always be logged at level ASSERT with the call stack. - * Depending on system configuration, a report may be added to the - * [android.os.DropBoxManager] and/or the process may be terminated - * immediately with an error dialog. - * @param tag Used to identify the source of a log message. - * @param msg The message you would like logged. - */ - fun wtf(tag: String, msg: String): Int { - return wtf(LOG_ID_MAIN, tag, msg, null, false, false) - } - - /** - * Like [.wtf], but also writes to the log the full - * call stack. - * @hide - */ - fun wtfStack(tag: String, msg: String): Int { - return wtf(LOG_ID_MAIN, tag, msg, null, true, false) - } - - /** - * What a Terrible Failure: Report an exception that should never happen. - * Similar to [.wtf], with an exception to log. - * @param tag Used to identify the source of a log message. - * @param tr An exception to log. - */ - fun wtf(tag: String, tr: Throwable): Int { - return wtf(LOG_ID_MAIN, tag, tr.message?:"", tr, false, false) - } - - /** - * What a Terrible Failure: Report an exception that should never happen. - * Similar to [.wtf], with a message as well. - * @param tag Used to identify the source of a log message. - * @param msg The message you would like logged. - * @param tr An exception to log. May be null. - */ - fun wtf(tag: String, msg: String, tr: Throwable?): Int { - return wtf(LOG_ID_MAIN, tag, msg, tr, false, false) - } - - /** - * Priority Constant for wtf. - * Added for this custom Log implementation, not in android sources. - */ - private const val WTF = 8 - - fun wtf(logId: Int, tag: String, msg: String, tr: Throwable?, localStack: Boolean, - system: Boolean - ): Int { - return printlns(LOG_ID_MAIN, WTF, tag, msg, tr) - } - - private const val LOG_ID_MAIN = 0 - - private val PRIORITY_ABBREV = arrayOf("0", "1", "V", "D", "I", "W", "E", "A", "WTF") - - private fun println_native(bufID: Int, priority: Int, tag: String, msg: String): Int { - val res: String = PRIORITY_ABBREV[priority] + "/" + tag + " " + msg + System.lineSeparator() - System.err.print(res) - return res.length - } - - private fun printlns(bufID: Int, priority: Int, tag: String, msg: String, - tr: Throwable? - ): Int { - val trSW = StringWriter() - if (tr != null) { - trSW.append(" , Exception: ") - val trPW = PrintWriter(trSW) - tr.printStackTrace(trPW) - trPW.flush() - } - return println_native(bufID, priority, tag, msg + trSW.toString()) - } -} diff --git a/app/src/test/resources/auphonic.m4a b/app/src/test/resources/auphonic.m4a deleted file mode 100644 index ca59a80f..00000000 Binary files a/app/src/test/resources/auphonic.m4a and /dev/null differ diff --git a/app/src/test/resources/auphonic.mp3 b/app/src/test/resources/auphonic.mp3 deleted file mode 100644 index ca2a7ed4..00000000 Binary files a/app/src/test/resources/auphonic.mp3 and /dev/null differ diff --git a/app/src/test/resources/auphonic.ogg b/app/src/test/resources/auphonic.ogg deleted file mode 100644 index de326517..00000000 Binary files a/app/src/test/resources/auphonic.ogg and /dev/null differ diff --git a/app/src/test/resources/auphonic.opus b/app/src/test/resources/auphonic.opus deleted file mode 100644 index 08538ecb..00000000 Binary files a/app/src/test/resources/auphonic.opus and /dev/null differ diff --git a/app/src/test/resources/feed-atom-testAtomBasic.xml b/app/src/test/resources/feed-atom-testAtomBasic.xml deleted file mode 100644 index cefc4f97..00000000 --- a/app/src/test/resources/feed-atom-testAtomBasic.xml +++ /dev/null @@ -1 +0,0 @@ -http://example.com/feedtitleThis is the descriptionhttp://example.com/picturehttp://example.com/item-0item-01970-01-01T00:00:00Zhttp://example.com/item-1item-11970-01-01T00:01:00Zhttp://example.com/item-2item-21970-01-01T00:02:00Zhttp://example.com/item-3item-31970-01-01T00:03:00Zhttp://example.com/item-4item-41970-01-01T00:04:00Zhttp://example.com/item-5item-51970-01-01T00:05:00Zhttp://example.com/item-6item-61970-01-01T00:06:00Zhttp://example.com/item-7item-71970-01-01T00:07:00Zhttp://example.com/item-8item-81970-01-01T00:08:00Zhttp://example.com/item-9item-91970-01-01T00:09:00Z \ No newline at end of file diff --git a/app/src/test/resources/feed-atom-testEmptyRelLinks.xml b/app/src/test/resources/feed-atom-testEmptyRelLinks.xml deleted file mode 100644 index 04c28ef6..00000000 --- a/app/src/test/resources/feed-atom-testEmptyRelLinks.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - http://example.com/feed - title - - This is the description - http://example.com/picture - - http://example.com/item-0 - item-0 - - 1970-01-01T00:00:00Z - - diff --git a/app/src/test/resources/feed-atom-testLogoWithWhitespace.xml b/app/src/test/resources/feed-atom-testLogoWithWhitespace.xml deleted file mode 100644 index f4886d56..00000000 --- a/app/src/test/resources/feed-atom-testLogoWithWhitespace.xml +++ /dev/null @@ -1,2 +0,0 @@ -http://example.com/feedtitleThis is the description https://example.com/image.png - \ No newline at end of file diff --git a/app/src/test/resources/feed-rss-testImageWithWhitespace.xml b/app/src/test/resources/feed-rss-testImageWithWhitespace.xml deleted file mode 100644 index 2be9401d..00000000 --- a/app/src/test/resources/feed-rss-testImageWithWhitespace.xml +++ /dev/null @@ -1,2 +0,0 @@ -titleThis is the descriptionhttp://example.comen https://example.com/image.png - \ No newline at end of file diff --git a/app/src/test/resources/feed-rss-testMediaContentMime.xml b/app/src/test/resources/feed-rss-testMediaContentMime.xml deleted file mode 100644 index a715abb3..00000000 --- a/app/src/test/resources/feed-rss-testMediaContentMime.xml +++ /dev/null @@ -1 +0,0 @@ -titleThis is the descriptionhttp://example.comen \ No newline at end of file diff --git a/app/src/test/resources/feed-rss-testMultipleFundingTags.xml b/app/src/test/resources/feed-rss-testMultipleFundingTags.xml deleted file mode 100644 index 2535bda3..00000000 --- a/app/src/test/resources/feed-rss-testMultipleFundingTags.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - title - - Text 1 - Text 2 - - diff --git a/app/src/test/resources/feed-rss-testRss2Basic.xml b/app/src/test/resources/feed-rss-testRss2Basic.xml deleted file mode 100644 index dd771b61..00000000 --- a/app/src/test/resources/feed-rss-testRss2Basic.xml +++ /dev/null @@ -1 +0,0 @@ -titleThis is the descriptionhttp://example.comenhttp://example.com/pictureitem-0http://example.com/items/001 Jan 70 01:00:00 +0100http://example.com/item-0item-1http://example.com/items/101 Jan 70 01:01:00 +0100http://example.com/item-1item-2http://example.com/items/201 Jan 70 01:02:00 +0100http://example.com/item-2item-3http://example.com/items/301 Jan 70 01:03:00 +0100http://example.com/item-3item-4http://example.com/items/401 Jan 70 01:04:00 +0100http://example.com/item-4item-5http://example.com/items/501 Jan 70 01:05:00 +0100http://example.com/item-5item-6http://example.com/items/601 Jan 70 01:06:00 +0100http://example.com/item-6item-7http://example.com/items/701 Jan 70 01:07:00 +0100http://example.com/item-7item-8http://example.com/items/801 Jan 70 01:08:00 +0100http://example.com/item-8item-9http://example.com/items/901 Jan 70 01:09:00 +0100http://example.com/item-9 \ No newline at end of file diff --git a/app/src/test/resources/feed-rss-testUnsupportedElements.xml b/app/src/test/resources/feed-rss-testUnsupportedElements.xml deleted file mode 100644 index f21ca7eb..00000000 --- a/app/src/test/resources/feed-rss-testUnsupportedElements.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - title - - item-0 - - - - item-1 - - - - diff --git a/app/src/test/resources/hindenburg-journalist-pro.m4a b/app/src/test/resources/hindenburg-journalist-pro.m4a deleted file mode 100644 index bd64dd9d..00000000 Binary files a/app/src/test/resources/hindenburg-journalist-pro.m4a and /dev/null differ diff --git a/app/src/test/resources/hindenburg-journalist-pro.mp3 b/app/src/test/resources/hindenburg-journalist-pro.mp3 deleted file mode 100644 index d341b604..00000000 Binary files a/app/src/test/resources/hindenburg-journalist-pro.mp3 and /dev/null differ diff --git a/app/src/test/resources/mp3chaps-py.mp3 b/app/src/test/resources/mp3chaps-py.mp3 deleted file mode 100644 index 05d519fb..00000000 Binary files a/app/src/test/resources/mp3chaps-py.mp3 and /dev/null differ diff --git a/app/src/test/resources/ultraschall5.mp3 b/app/src/test/resources/ultraschall5.mp3 deleted file mode 100644 index a73029a5..00000000 Binary files a/app/src/test/resources/ultraschall5.mp3 and /dev/null differ diff --git a/changelog.md b/changelog.md index f3c8aa55..da6eac1e 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,11 @@ +# 6.12.7 + +* fixed issue in auto-download where number of played is not correctly counted +* in AutoDownloadPolicy, Newer or Older now means include episodes with playState not being Skipped, Played, and Ignored +* in Subscriptions sorting, Played means Skipped, Played, or Ignored, Unplayed means any state lower than Skipped +* fixed high CPU usage in Download view +* removed a few useless classes and the useless test modules + # 6.12.6 * in SearchFragment, made search criteria options are togglable, and added back search online option diff --git a/fastlane/metadata/android/en-US/changelogs/3020285.txt b/fastlane/metadata/android/en-US/changelogs/3020285.txt new file mode 100644 index 00000000..bf69071f --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/3020285.txt @@ -0,0 +1,7 @@ + Version 6.12.7 + +* fixed issue in auto-download where number of played is not correctly counted +* in AutoDownloadPolicy, Newer or Older now means include episodes with playState not being Skipped, Played, and Ignored +* in Subscriptions sorting, Played means Skipped, Played, or Ignored, Unplayed means any state lower than Skipped +* fixed high CPU usage in Download view +* removed a few useless classes and the useless test modules diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6e049b6c..7c392a6b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ activityCompose = "1.9.3" annotation = "1.9.0" appcompat = "1.7.0" -awaitility = "4.2.1" +#awaitility = "4.2.1" balloon = "1.6.6" coil = "2.7.0" commonsLang3 = "3.15.0" @@ -17,7 +17,7 @@ coreKtxVersion = "1.8.1" coreSplashscreen = "1.0.1" desugar_jdk_libs_nio = "2.1.2" documentfile = "1.0.1" -espressoCore = "3.6.1" +#espressoCore = "3.6.1" fontawesomeTypeface = "5.13.3.0-kotlin" fyydlin = "v0.5.0" googleMaterialTypeface = "4.0.0.3-kotlin" @@ -27,10 +27,10 @@ gridlayout = "1.0.0" groovyXml = "3.0.19" iconicsCore = "5.5.0-b01" iconicsViews = "5.5.0-b01" -javaxInject = "1" +#javaxInject = "1" jsoup = "1.18.1" -junit = "1.2.1" -junitVersion = "4.13.2" +#junit = "1.2.1" +#junitVersion = "4.13.2" kotlin = "2.0.20" kotlinxCoroutinesAndroid = "1.8.1" libraryBase = "2.1.0" @@ -45,8 +45,8 @@ media3Session = "1.4.1" media3Ui = "1.4.1" media3Exoplayer = "1.4.1" mediarouter = "1.7.0" -mockitoInline = "5.2.0" -nanohttpd = "2.1.1" +#mockitoInline = "5.2.0" +#nanohttpd = "2.1.1" okhttp = "4.12.0" okhttpUrlconnection = "4.12.0" okio = "3.9.0" @@ -57,9 +57,9 @@ preferenceKtx = "1.2.1" readability4j = "1.0.8" recyclerview = "1.3.2" recyclerviewswipedecorator = "1.3" -robolectric = "4.13" -rules = "1.6.1" -runner = "1.6.2" +#robolectric = "4.13" +#rules = "1.6.1" +#runner = "1.6.2" rxandroid = "3.0.2" rxjava = "2.2.21" rxjavaVersion = "3.1.8" @@ -76,7 +76,7 @@ window = "1.3.0" workRuntime = "2.9.1" #uiViewbinding = "1.7.2" fragmentCompose = "1.8.4" -fragment-ktx = "1.8.4" +#fragment-ktx = "1.8.4" [libraries] androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" } androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" } @@ -84,19 +84,19 @@ androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "a androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" } androidx-constraintlayout-compose = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "constraintlayoutCompose" } androidx-coordinatorlayout = { module = "androidx.coordinatorlayout:coordinatorlayout", version.ref = "coordinatorlayout" } -androidx-core = { module = "androidx.test:core", version.ref = "rules" } +#androidx-core = { module = "androidx.test:core", version.ref = "rules" } androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" } androidx-documentfile = { module = "androidx.documentfile:documentfile", version.ref = "documentfile" } androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" } -androidx-espresso-contrib = { module = "androidx.test.espresso:espresso-contrib", version.ref = "espressoCore" } -androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" } -androidx-espresso-intents = { module = "androidx.test.espresso:espresso-intents", version.ref = "espressoCore" } +#androidx-espresso-contrib = { module = "androidx.test.espresso:espresso-contrib", version.ref = "espressoCore" } +#androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" } +#androidx-espresso-intents = { module = "androidx.test.espresso:espresso-intents", version.ref = "espressoCore" } androidx-gridlayout = { module = "androidx.gridlayout:gridlayout", version.ref = "gridlayout" } -androidx-junit = { module = "androidx.test.ext:junit", version.ref = "junit" } +#androidx-junit = { module = "androidx.test.ext:junit", version.ref = "junit" } androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } #androidx-material = { module = "androidx.compose.material:material", version.ref = "material" } #androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "materialIconsExtended" } -androidx-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" } +#androidx-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" } androidx-material3-android = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" } androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3Exoplayer" } androidx-media3-media3-datasource-okhttp = { module = "androidx.media3:media3-datasource-okhttp", version.ref = "media3Ui" } @@ -105,15 +105,15 @@ androidx-mediarouter = { module = "androidx.mediarouter:mediarouter", version.re androidx-palette-ktx = { module = "androidx.palette:palette-ktx", version.ref = "paletteKtx" } androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" } androidx-preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "preferenceKtx" } -androidx-runner = { module = "androidx.test:runner", version.ref = "runner" } -androidx-rules = { module = "androidx.test:rules", version.ref = "rules" } +#androidx-runner = { module = "androidx.test:runner", version.ref = "runner" } +#androidx-rules = { module = "androidx.test:rules", version.ref = "rules" } androidx-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "uiTooling" } androidx-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "uiToolingPreview" } androidx-viewpager2 = { module = "androidx.viewpager2:viewpager2", version.ref = "viewpager2" } androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" } androidx-window = { module = "androidx.window:window", version.ref = "window" } androidx-work-runtime = { module = "androidx.work:work-runtime", version.ref = "workRuntime" } -awaitility = { module = "org.awaitility:awaitility", version.ref = "awaitility" } +#awaitility = { module = "org.awaitility:awaitility", version.ref = "awaitility" } balloon = { module = "com.github.skydoves:balloon", version.ref = "balloon" } coil = { module = "io.coil-kt:coil", version.ref = "coil" } coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" } @@ -129,16 +129,16 @@ gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" } groovy-xml = { module = "org.codehaus.groovy:groovy-xml", version.ref = "groovyXml" } iconics-views = { module = "com.mikepenz:iconics-views", version.ref = "iconicsViews" } iconics-core = { module = "com.mikepenz:iconics-core", version.ref = "iconicsCore" } -javax-inject = { module = "javax.inject:javax.inject", version.ref = "javaxInject" } +#javax-inject = { module = "javax.inject:javax.inject", version.ref = "javaxInject" } jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } -junit = { module = "junit:junit", version.ref = "junitVersion" } +#junit = { module = "junit:junit", version.ref = "junitVersion" } kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesAndroid" } library-base = { module = "io.realm.kotlin:library-base", version.ref = "libraryBase" } material = { module = "com.google.android.material:material", version.ref = "materialVersion" } media3-common = { module = "androidx.media3:media3-common", version.ref = "media3Common" } media3-session = { module = "androidx.media3:media3-session", version.ref = "media3Session" } -mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockitoInline" } -nanohttpd = { module = "com.nanohttpd:nanohttpd", version.ref = "nanohttpd" } +#mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockitoInline" } +#nanohttpd = { module = "com.nanohttpd:nanohttpd", version.ref = "nanohttpd" } okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } okio = { module = "com.squareup.okio:okio", version.ref = "okio" } okhttp3-okhttp-urlconnection = { module = "com.squareup.okhttp3:okhttp-urlconnection", version.ref = "okhttpUrlconnection" } @@ -146,7 +146,7 @@ play-services-base = { module = "com.google.android.gms:play-services-base", ver play-services-cast-framework = { module = "com.google.android.gms:play-services-cast-framework", version.ref = "playServicesCastFramework" } readability4j = { module = "net.dankito.readability4j:readability4j", version.ref = "readability4j" } recyclerviewswipedecorator = { module = "com.github.xabaras:RecyclerViewSwipeDecorator", version.ref = "recyclerviewswipedecorator" } -robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } +#robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } rxandroid = { module = "io.reactivex.rxjava3:rxandroid", version.ref = "rxandroid" } rxjava3-rxjava = { module = "io.reactivex.rxjava3:rxjava", version.ref = "rxjavaVersion" } rxjava = { module = "io.reactivex.rxjava2:rxjava", version.ref = "rxjava" }