From d87bc718d34133b79332880a3b9c60476255bacb Mon Sep 17 00:00:00 2001 From: Xilin Jia <6257601+XilinJia@users.noreply.github.com> Date: Sun, 27 Oct 2024 11:14:47 +0100 Subject: [PATCH] 6.12.7 commit --- app/build.gradle | 38 +- app/src/androidTest/assets/30sec.mp3 | Bin 120017 -> 0 bytes app/src/androidTest/assets/3sec.mp3 | Bin 49043 -> 0 bytes .../ac/test/podcini/EspressoTestUtils.kt | 210 ---- .../kotlin/ac/test/podcini/IgnoreOnCi.kt | 9 - .../kotlin/ac/test/podcini/NthMatcher.kt | 30 - .../test/podcini/dialogs/ShareDialogTest.kt | 66 -- .../ac/test/podcini/playback/PlaybackTest.kt | 307 ------ .../service/download/HttpDownloaderTest.kt | 291 ------ .../service/playback/MediaPlayerBaseTest.kt | 872 ----------------- .../playback/SleepTimerPreferencesTest.kt | 19 - .../service/playback/TaskManagerTest.kt | 321 ------- .../test/podcini/storage/AutoDownloadTest.kt | 118 --- .../ac/test/podcini/ui/FeedSettingsTest.kt | 76 -- .../ac/test/podcini/ui/MainActivityTest.kt | 70 -- .../test/podcini/ui/NavigationDrawerTest.kt | 219 ----- .../test/podcini/ui/PlayQueuesFragmentTest.kt | 60 -- .../ac/test/podcini/ui/PreferencesTest.kt | 434 --------- .../ac/test/podcini/ui/TextOnlyFeedsTest.kt | 67 -- .../kotlin/ac/test/podcini/ui/UITestUtils.kt | 207 ---- .../ac/test/podcini/ui/UITestUtilsTest.kt | 97 -- .../podcini/util/service/download/HTTPBin.kt | 339 ------- .../feedgenerator/FeedGenerator.kt | 28 - .../feedgenerator/GeneratorUtil.kt | 20 - .../feedgenerator/Rss2Generator.kt | 125 --- .../net/download/service/DownloadRequest.kt | 5 - .../playback/service/PlaybackService.kt | 1 - .../podcini/preferences/UserPreferences.kt | 20 - .../storage/algorithms/AutoDownloads.kt | 11 +- .../mdiq/podcini/storage/database/Episodes.kt | 22 - .../podcini/storage/database/LogsAndStats.kt | 2 +- .../ac/mdiq/podcini/storage/model/Episode.kt | 2 +- .../ac/mdiq/podcini/storage/model/Feed.kt | 2 +- .../mdiq/podcini/storage/utils/EpisodeUtil.kt | 10 - .../ac/mdiq/podcini/ui/compose/EpisodesVM.kt | 6 +- .../podcini/ui/dialog/RemoveFeedDialog.kt | 77 -- .../ui/fragment/AllEpisodesFragment.kt | 5 +- .../ui/fragment/BaseEpisodesFragment.kt | 10 - .../podcini/ui/fragment/DownloadsFragment.kt | 15 +- .../podcini/ui/fragment/HistoryFragment.kt | 11 +- .../podcini/ui/fragment/QueuesFragment.kt | 1 - .../ui/fragment/SubscriptionsFragment.kt | 35 +- .../ui/statistics/StatisticsFragment.kt | 2 +- .../ac/mdiq/podcini/ui/utils/CoverLoader.kt | 117 --- .../mdiq/podcini/ui/utils/EmptyViewHandler.kt | 165 ---- .../podcini/ui/utils/LiftOnScrollListener.kt | 47 - .../ui/utils/ToolbarIconTintManager.kt | 52 - app/src/test/assets/local-feed1/track1.mp3 | Bin 43341 -> 0 bytes app/src/test/assets/local-feed2/folder.png | Bin 1589 -> 0 bytes app/src/test/assets/local-feed2/track1.mp3 | Bin 43341 -> 0 bytes app/src/test/assets/local-feed2/track2.mp3 | Bin 43497 -> 0 bytes .../ac/mdiq/podcini/feed/EpisodeMediaTest.kt | 68 -- .../ac/mdiq/podcini/feed/EpisodeTest.kt | 138 --- .../feed/FeedAutoDownloadFilterTest.kt | 152 --- .../ac/mdiq/podcini/feed/FeedItemMother.kt | 15 - .../ac/mdiq/podcini/feed/FeedMediaMother.kt | 14 - .../kotlin/ac/mdiq/podcini/feed/FeedMother.kt | 14 - .../kotlin/ac/mdiq/podcini/feed/FeedTest.kt | 117 --- .../mdiq/podcini/feed/LocalFeedUpdaterTest.kt | 300 ------ .../podcini/feed/VolumeAdaptionSettingTest.kt | 115 --- .../serviceinterface/DownloadRequestTest.kt | 119 --- .../DownloadServiceInterfaceTestStub.kt | 16 - .../podcini/net/sync/HostnameParserTest.kt | 42 - .../mdiq/podcini/net/utils/UrlCheckerTest.kt | 172 ---- .../feed/element/element/AtomTextTest.kt | 36 - .../feed/element/namespace/AtomParserTest.kt | 92 -- .../element/namespace/FeedParserTestHelper.kt | 31 - .../feed/element/namespace/RssParserTest.kt | 104 -- .../parser/feed/element/util/DateUtilsTest.kt | 169 ---- .../feed/element/util/DurationParserTest.kt | 43 - .../parser/media/id3/ChapterRReaderTest.kt | 214 ----- .../podcini/parser/media/id3/Id3ReaderTest.kt | 156 --- .../parser/media/id3/MetadataReaderTest.kt | 55 -- .../vorbis/VorbisCommentChapterRReaderTest.kt | 46 - .../vorbis/VorbisCommentMetadataReaderTest.kt | 29 - .../playback/base/RewindAfterPauseUtilTest.kt | 53 -- .../service/playback/VolumeUpdaterTest.kt | 227 ----- .../podcini/storage/APCleanupAlgorithmTest.kt | 18 - .../ac/mdiq/podcini/storage/DbCleanupTests.kt | 212 ----- .../storage/DbNullCleanupAlgorithmTest.kt | 113 --- .../DbPlayQueueCleanupAlgorithmTest.kt | 47 - .../ac/mdiq/podcini/storage/DbReaderTest.kt | 478 ---------- .../ac/mdiq/podcini/storage/DbTasksTest.kt | 259 ----- .../ac/mdiq/podcini/storage/DbTestUtils.kt | 58 -- .../ac/mdiq/podcini/storage/DbWriterTest.kt | 893 ------------------ .../storage/EpisodeDuplicateGuesserTest.kt | 70 -- .../ExceptFavoriteCleanupAlgorithmTest.kt | 87 -- .../ItemEnqueuePositionCalculatorTest.kt | 151 --- .../storage/mapper/FeedCursorMapperTest.kt | 81 -- .../podcini/sync/EpisodeActionFilterTest.kt | 182 ---- .../ac/mdiq/podcini/sync/GuidValidatorTest.kt | 19 - .../mdiq/podcini/util/CollectionTestUtil.kt | 27 - .../podcini/util/DurationConverterTest.kt | 41 - .../mdiq/podcini/util/EpisodePermutorsTest.kt | 222 ----- .../ac/mdiq/podcini/util/EpisodeUtilTest.kt | 86 -- .../podcini/util/FilenameGeneratorTest.kt | 93 -- .../ac/mdiq/podcini/util/URIUtilTest.kt | 23 - .../podcini/util/gui/ShownotesCleanerTest.kt | 234 ----- .../util/syndication/FeedDiscovererTest.kt | 134 --- app/src/test/kotlin/android/util/Log.kt | 243 ----- app/src/test/resources/auphonic.m4a | Bin 114657 -> 0 bytes app/src/test/resources/auphonic.mp3 | Bin 143695 -> 0 bytes app/src/test/resources/auphonic.ogg | Bin 6565 -> 0 bytes app/src/test/resources/auphonic.opus | Bin 4189 -> 0 bytes .../resources/feed-atom-testAtomBasic.xml | 1 - .../resources/feed-atom-testEmptyRelLinks.xml | 14 - .../feed-atom-testLogoWithWhitespace.xml | 2 - .../feed-rss-testImageWithWhitespace.xml | 2 - .../feed-rss-testMediaContentMime.xml | 1 - .../feed-rss-testMultipleFundingTags.xml | 9 - .../test/resources/feed-rss-testRss2Basic.xml | 1 - .../feed-rss-testUnsupportedElements.xml | 14 - .../resources/hindenburg-journalist-pro.m4a | Bin 23315 -> 0 bytes .../resources/hindenburg-journalist-pro.mp3 | Bin 206098 -> 0 bytes app/src/test/resources/mp3chaps-py.mp3 | Bin 123247 -> 0 bytes app/src/test/resources/ultraschall5.mp3 | Bin 5903309 -> 0 bytes changelog.md | 8 + .../android/en-US/changelogs/3020285.txt | 7 + gradle/libs.versions.toml | 50 +- 119 files changed, 97 insertions(+), 10961 deletions(-) delete mode 100644 app/src/androidTest/assets/30sec.mp3 delete mode 100644 app/src/androidTest/assets/3sec.mp3 delete mode 100644 app/src/androidTest/kotlin/ac/test/podcini/EspressoTestUtils.kt delete mode 100644 app/src/androidTest/kotlin/ac/test/podcini/IgnoreOnCi.kt delete mode 100644 app/src/androidTest/kotlin/ac/test/podcini/NthMatcher.kt delete mode 100644 app/src/androidTest/kotlin/ac/test/podcini/dialogs/ShareDialogTest.kt delete mode 100644 app/src/androidTest/kotlin/ac/test/podcini/playback/PlaybackTest.kt delete mode 100644 app/src/androidTest/kotlin/ac/test/podcini/service/download/HttpDownloaderTest.kt delete mode 100644 app/src/androidTest/kotlin/ac/test/podcini/service/playback/MediaPlayerBaseTest.kt delete mode 100644 app/src/androidTest/kotlin/ac/test/podcini/service/playback/SleepTimerPreferencesTest.kt delete mode 100644 app/src/androidTest/kotlin/ac/test/podcini/service/playback/TaskManagerTest.kt delete mode 100644 app/src/androidTest/kotlin/ac/test/podcini/storage/AutoDownloadTest.kt delete mode 100644 app/src/androidTest/kotlin/ac/test/podcini/ui/FeedSettingsTest.kt delete mode 100644 app/src/androidTest/kotlin/ac/test/podcini/ui/MainActivityTest.kt delete mode 100644 app/src/androidTest/kotlin/ac/test/podcini/ui/NavigationDrawerTest.kt delete mode 100644 app/src/androidTest/kotlin/ac/test/podcini/ui/PlayQueuesFragmentTest.kt delete mode 100644 app/src/androidTest/kotlin/ac/test/podcini/ui/PreferencesTest.kt delete mode 100644 app/src/androidTest/kotlin/ac/test/podcini/ui/TextOnlyFeedsTest.kt delete mode 100644 app/src/androidTest/kotlin/ac/test/podcini/ui/UITestUtils.kt delete mode 100644 app/src/androidTest/kotlin/ac/test/podcini/ui/UITestUtilsTest.kt delete mode 100644 app/src/androidTest/kotlin/ac/test/podcini/util/service/download/HTTPBin.kt delete mode 100644 app/src/androidTest/kotlin/ac/test/podcini/util/syndication/feedgenerator/FeedGenerator.kt delete mode 100644 app/src/androidTest/kotlin/ac/test/podcini/util/syndication/feedgenerator/GeneratorUtil.kt delete mode 100644 app/src/androidTest/kotlin/ac/test/podcini/util/syndication/feedgenerator/Rss2Generator.kt delete mode 100644 app/src/main/kotlin/ac/mdiq/podcini/ui/dialog/RemoveFeedDialog.kt delete mode 100644 app/src/main/kotlin/ac/mdiq/podcini/ui/utils/CoverLoader.kt delete mode 100644 app/src/main/kotlin/ac/mdiq/podcini/ui/utils/EmptyViewHandler.kt delete mode 100644 app/src/main/kotlin/ac/mdiq/podcini/ui/utils/LiftOnScrollListener.kt delete mode 100644 app/src/main/kotlin/ac/mdiq/podcini/ui/utils/ToolbarIconTintManager.kt delete mode 100644 app/src/test/assets/local-feed1/track1.mp3 delete mode 100644 app/src/test/assets/local-feed2/folder.png delete mode 100644 app/src/test/assets/local-feed2/track1.mp3 delete mode 100644 app/src/test/assets/local-feed2/track2.mp3 delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/feed/EpisodeMediaTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/feed/EpisodeTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/feed/FeedAutoDownloadFilterTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/feed/FeedItemMother.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/feed/FeedMediaMother.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/feed/FeedMother.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/feed/FeedTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/feed/LocalFeedUpdaterTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/feed/VolumeAdaptionSettingTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/net/download/serviceinterface/DownloadRequestTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/net/download/serviceinterface/DownloadServiceInterfaceTestStub.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/net/sync/HostnameParserTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/net/utils/UrlCheckerTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/element/AtomTextTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/namespace/AtomParserTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/namespace/FeedParserTestHelper.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/namespace/RssParserTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/util/DateUtilsTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/parser/feed/element/util/DurationParserTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/parser/media/id3/ChapterRReaderTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/parser/media/id3/Id3ReaderTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/parser/media/id3/MetadataReaderTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/parser/media/vorbis/VorbisCommentChapterRReaderTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/parser/media/vorbis/VorbisCommentMetadataReaderTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/playback/base/RewindAfterPauseUtilTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/service/playback/VolumeUpdaterTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/storage/APCleanupAlgorithmTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/storage/DbCleanupTests.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/storage/DbNullCleanupAlgorithmTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/storage/DbPlayQueueCleanupAlgorithmTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/storage/DbReaderTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/storage/DbTasksTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/storage/DbTestUtils.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/storage/DbWriterTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/storage/EpisodeDuplicateGuesserTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/storage/ExceptFavoriteCleanupAlgorithmTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/storage/ItemEnqueuePositionCalculatorTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/storage/mapper/FeedCursorMapperTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/sync/EpisodeActionFilterTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/sync/GuidValidatorTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/util/CollectionTestUtil.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/util/DurationConverterTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/util/EpisodePermutorsTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/util/EpisodeUtilTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/util/FilenameGeneratorTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/util/URIUtilTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/util/gui/ShownotesCleanerTest.kt delete mode 100644 app/src/test/kotlin/ac/mdiq/podcini/util/syndication/FeedDiscovererTest.kt delete mode 100644 app/src/test/kotlin/android/util/Log.kt delete mode 100644 app/src/test/resources/auphonic.m4a delete mode 100644 app/src/test/resources/auphonic.mp3 delete mode 100644 app/src/test/resources/auphonic.ogg delete mode 100644 app/src/test/resources/auphonic.opus delete mode 100644 app/src/test/resources/feed-atom-testAtomBasic.xml delete mode 100644 app/src/test/resources/feed-atom-testEmptyRelLinks.xml delete mode 100644 app/src/test/resources/feed-atom-testLogoWithWhitespace.xml delete mode 100644 app/src/test/resources/feed-rss-testImageWithWhitespace.xml delete mode 100644 app/src/test/resources/feed-rss-testMediaContentMime.xml delete mode 100644 app/src/test/resources/feed-rss-testMultipleFundingTags.xml delete mode 100644 app/src/test/resources/feed-rss-testRss2Basic.xml delete mode 100644 app/src/test/resources/feed-rss-testUnsupportedElements.xml delete mode 100644 app/src/test/resources/hindenburg-journalist-pro.m4a delete mode 100644 app/src/test/resources/hindenburg-journalist-pro.mp3 delete mode 100644 app/src/test/resources/mp3chaps-py.mp3 delete mode 100644 app/src/test/resources/ultraschall5.mp3 create mode 100644 fastlane/metadata/android/en-US/changelogs/3020285.txt 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 48e3984349331e5780e9aa2c3586c982947d52bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120017 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 T{SXZe(_lFF@qcBeZQAt`ZhDQD zJ%k&(wsB%OKX7%wi?49;a?gl|^Wo0wzwu&lr)htd?v6G_PuiOY?X~@Jd@!B$hFgQ) zptpUne>i=AGTK^y+(!4?>+I}o<@4U;^kBTQe>hp&91h!ukJ@W@ceiWqnH~q9cIU1bwM{Ap}+dsJ1UK`Vs+41S5{fB$wtIMw!Z-2HAo24HQuYWbG z=U)7v^4rCq@4xO`v_SuSn$^~)nQP_k%>rEsUH-m0SLnte z-zk*0VI&GM9C8bV@-~b_A%;V4p-|q2ktoD)$SoAg+b|M^7!J9GLU|iTq7cI&w@@f= z!$=fjIOG-znQtdxffX2$@0zg{p%>)jEVsA%a5HL7{3L zLZ%Qwq3WPewGJUuh@en)P^emmkSRn^s5&TAtwYEZA}CZH6sp!CWC{@!styWO>ku-9 z2ntmPg{pN3nL-4Gs)It+I)qFifW`K72_Zxg%Auy35AMr5RpO% zhN6T*#W;kh3m^|P-bTI)ArCYvbejd}pY0_tSAfB*mh 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 b1f993c3f7d171a10769218ef1c3052870f335cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43341 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 z 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 - // 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 ca59a80f6731eb526919731b7a4f1d2e2f2d83ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 114657 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 Sil*~cRXDA^b!W_<$Nmqt1uPi= diff --git a/app/src/test/resources/auphonic.mp3 b/app/src/test/resources/auphonic.mp3 deleted file mode 100644 index ca2a7ed4f5d6119d3f42f45810ed1cda2aac08f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 143695 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 l5TFm+L87hwM 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 OmxurUc=7kQk^cwp@_eHJ diff --git a/app/src/test/resources/auphonic.opus b/app/src/test/resources/auphonic.opus deleted file mode 100644 index 08538ecb75e259cd196748105cf95c4473040784..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4189 zcmeH~O^n+_6vrL5K!F8mRUr^a$VhEZP3$<oOamtAIBiBkRvih#1MgkCFu0x!~2=!pld zudAzyHy#B2&2}@_?DzIHW&hT6gpx8_vy9p36?@$4dBe-U;dIrQ1E>eS z_OfR9%WC9TCMV)0qhZRI3MN+2>OQVX33g z0?{haaF`{Ec|{aY&5oQXipLU7D^O#K7I=fuAM$xV%l~rq{TrXZar4^SuRWg#bgilq zgyCRxN)Y^s*Bf;Got3yNB<%~agvXBrd zpvbSUev(iq+>3&|qp(B@^#8F-C?^!;L~i*mxv~F!yFUZ}@eITd%;{ILEIlykVSD`Q znT|B~kL{1`TmQr3_B%n8nK2UL9Nx9JzrUF>#Hkd&C=uczMEvOJch9B_k^6lQ@j*mv zeY0>PWr))cXnArR{fZCK&AVsMr3{htX?ezo4`aFT@sHo83~|cS@{AD=WBGMST1*)t z_wybieWScQzOtG!#OYhJ9XQ*Ik0RnTLD)zcB4^VMj29|>jUM~+hj-G2c&O;?A N^)Eh~&&=VQ%3sClaiRbK 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 bd64dd9daa1fa0adde9a13c03693aa82dc13bba1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23315 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 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 d341b6045168c0b2fb2d1406e121725053910612..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 206098 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 Spl1M&+hOXb)aTsY7XJmRS~Ix- diff --git a/app/src/test/resources/mp3chaps-py.mp3 b/app/src/test/resources/mp3chaps-py.mp3 deleted file mode 100644 index 05d519fb011f3a482ac7638dcc12bd97175ff44b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 123247 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�C(OygP`)3V@vl;)5tYsEd&7rD(|VW3 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
Nj<`-aAP>*VC}m_%Fr=1X-MweR95c#zwu6$ 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