version updates

This commit is contained in:
Gergely Hegedus 2022-04-21 12:36:39 +03:00
parent 12e6f01d29
commit b256cb9bf2
28 changed files with 302 additions and 234 deletions

View file

@ -6,13 +6,12 @@ plugins {
apply from: 'signing.config.gradle'
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
compileSdk 31
defaultConfig {
applicationId "org.fnives.tiktokdownloader"
minSdkVersion 23
targetSdkVersion 30
minSdk 23
targetSdk 31
versionCode 1
versionName "1.0.0"
@ -51,41 +50,65 @@ android {
lintOptions {
abortOnError true
}
testOptions.unitTests {
includeAndroidResources = true
all {
useJUnitPlatform()
testLogging {
events 'started', 'passed', 'skipped', 'failed'
exceptionFormat "full"
showStandardStreams true
}
}
}
}
tasks.configureEach { task ->
if (task.taskIdentity.type.toString() == "class org.jetbrains.kotlin.gradle.tasks.KotlinCompile") {
task.kotlinOptions {
freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
}
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "androidx.core:core-ktx:1.3.2"
implementation "androidx.appcompat:appcompat:1.2.0"
implementation "androidx.activity:activity-ktx:1.2.0-beta01"
implementation "androidx.fragment:fragment-ktx:1.3.0-beta01"
implementation "com.google.android.material:material:1.2.1"
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
implementation "androidx.core:core-ktx:1.7.0"
implementation "androidx.appcompat:appcompat:1.4.1"
implementation "androidx.activity:activity-ktx:1.4.0"
implementation "androidx.fragment:fragment-ktx:1.4.1"
implementation "com.google.android.material:material:1.5.0"
implementation "androidx.constraintlayout:constraintlayout:2.1.3"
// Coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.0-M1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
implementation "androidx.fragment:fragment-ktx:1.2.5"
def coroutine_version = "1.6.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
implementation "androidx.fragment:fragment-ktx:1.4.1"
implementation "com.github.bumptech.glide:glide:4.11.0"
kapt "com.github.bumptech.glide:compiler:4.11.0"
def glide_version = "4.11.0"
implementation "com.github.bumptech.glide:glide:$glide_version"
kapt "com.github.bumptech.glide:compiler:$glide_version"
def okhttp_version = "4.9.3"
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.7.2"
implementation 'com.pierfrancescosoffritti.androidyoutubeplayer:core:10.0.5'
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
implementation 'com.pierfrancescosoffritti.androidyoutubeplayer:core:11.0.1'
testImplementation "org.junit.jupiter:junit-jupiter-engine:5.7.0"
testImplementation "org.junit.jupiter:junit-jupiter-params:5.7.0"
testImplementation 'com.jraska.livedata:testing-ktx:1.1.2'
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
testImplementation "com.squareup.okhttp3:mockwebserver:4.2.1"
def junit_version = "5.7.0"
testImplementation "org.junit.jupiter:junit-jupiter-engine:$junit_version"
testImplementation "org.junit.jupiter:junit-jupiter-params:$junit_version"
testImplementation 'com.jraska.livedata:testing-ktx:1.2.0'
testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0"
testImplementation "com.squareup.okhttp3:mockwebserver:$okhttp_version"
testImplementation "commons-io:commons-io:2.8.0"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.0-M1"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutine_version"
testImplementation "androidx.arch.core:core-testing:2.1.0"
androidTestImplementation "androidx.test.ext:junit:1.1.2"
androidTestImplementation "androidx.test.espresso:espresso-core:3.3.0"
androidTestImplementation "androidx.test.ext:junit:1.1.3"
androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
}

View file

@ -13,19 +13,23 @@
<application
android:name=".App"
android:allowBackup="true"
android:fullBackupContent="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:fullBackupContent="false"
android:supportsRtl="true"
android:theme="@style/Theme.TikTokDownloader">
<activity android:name=".ui.main.MainActivity">
<activity
android:name=".ui.main.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ui.service.DownloadIntentReceiverActivity"
<activity
android:name=".ui.service.DownloadIntentReceiverActivity"
android:exported="true"
android:theme="@style/NoDisplayTheme">
<intent-filter>
<action android:name="android.intent.action.SEND" />
@ -35,6 +39,7 @@
<data android:mimeType="message/*" />
</intent-filter>
</activity>
<service android:name=".ui.service.QueueService" />
</application>

View file

@ -39,10 +39,10 @@ class SharedPreferencesManagerImpl private constructor(private val sharedPrefere
override fun getValue(thisRef: SharedPreferencesManagerImpl, property: KProperty<*>): Flow<Set<String>> =
callbackFlow {
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
offer(thisRef.getValues())
trySend(thisRef.getValues())
}
thisRef.sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
offer(thisRef.getValues())
trySend(thisRef.getValues())
awaitClose {
thisRef.sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener)

View file

@ -5,6 +5,6 @@ import org.fnives.tiktokdownloader.data.network.parsing.response.VideoResponse
class VideoResponseConverter : ParsingExceptionThrowingConverter<VideoResponse>() {
override fun convertSafely(value: ResponseBody): VideoResponse? =
VideoResponse(value.contentType(), value.byteStream())
override fun convertSafely(responseBody: ResponseBody): VideoResponse? =
VideoResponse(responseBody.contentType(), responseBody.byteStream())
}

View file

@ -1,7 +1,7 @@
package org.fnives.tiktokdownloader.data.usecase
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
@ -16,6 +16,7 @@ import org.fnives.tiktokdownloader.data.model.VideoInPending
import org.fnives.tiktokdownloader.data.model.VideoInProgress
import org.fnives.tiktokdownloader.data.model.VideoState
@OptIn(FlowPreview::class)
class StateOfVideosObservableUseCase(
videoInProgressLocalSource: VideoInProgressLocalSource,
videoInPendingLocalSource: VideoInPendingLocalSource,

View file

@ -2,6 +2,7 @@ package org.fnives.tiktokdownloader.data.usecase
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@ -30,6 +31,7 @@ import org.fnives.tiktokdownloader.data.network.exceptions.CaptchaRequiredExcept
import org.fnives.tiktokdownloader.data.network.exceptions.NetworkException
import org.fnives.tiktokdownloader.data.network.exceptions.ParsingException
@OptIn(FlowPreview::class)
class VideoDownloadingProcessorUseCase(
private val tikTokDownloadRemoteSource: TikTokDownloadRemoteSource,
private val videoInProgressLocalSource: VideoInProgressLocalSource,

View file

@ -15,6 +15,7 @@ class ViewModelFactory(
private val viewModelModule: ViewModelModule,
) : AbstractSavedStateViewModelFactory(savedStateRegistryOwner, defaultArgs) {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(key: String, modelClass: Class<T>, handle: SavedStateHandle): T {
val viewModel = when (modelClass) {
MainViewModel::class.java -> viewModelModule.mainViewModel(handle)

View file

@ -6,7 +6,6 @@ import android.os.Bundle
import android.view.MenuItem
import android.view.animation.AccelerateDecelerateInterpolator
import android.view.animation.OvershootInterpolator
import androidx.activity.viewModels
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.coordinatorlayout.widget.CoordinatorLayout
@ -43,28 +42,28 @@ class MainActivity : AppCompatActivity() {
animateFabClicked(downloadFab)
viewModel.onFetchDownloadClicked()
}
viewModel.refreshActionVisibility.observe(this, {
viewModel.refreshActionVisibility.observe(this) {
animateFabVisibility(downloadFab, it == true)
})
viewModel.errorMessage.observe(this, {
}
viewModel.errorMessage.observe(this) {
val stringRes = it?.item?.stringRes ?: return@observe
Snackbar.make(snackBarAnchor, stringRes, Snackbar.LENGTH_SHORT).show()
})
}
}
private fun setupBottomNavigationView(bottomNavigationView: BottomNavigationView, savedInstanceState: Bundle?) {
bottomNavigationView.setOnNavigationItemSelectedListener(BottomNavigationView.OnNavigationItemSelectedListener { item ->
bottomNavigationView.setOnItemSelectedListener { item ->
val fragment = when (item.itemId) {
R.id.help_menu_item -> HelpFragment.newInstance()
R.id.queue_menu_item -> QueueFragment.newInstance()
else -> return@OnNavigationItemSelectedListener false
else -> return@setOnItemSelectedListener false
}
item.toScreen()?.let(viewModel::onScreenSelected)
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_holder, fragment)
.commit()
return@OnNavigationItemSelectedListener true
})
return@setOnItemSelectedListener true
}
if (savedInstanceState == null) {
bottomNavigationView.selectedItemId = R.id.queue_menu_item
}

View file

@ -75,7 +75,7 @@ class QueueService : Service() {
.setSmallIcon(R.drawable.ic_download)
.setContentIntent(buildMainPendingIntent(this))
.setAutoCancel(true)
.setNotificationSilent()
.setSilent(true)
.build()
NotificationState.Finish -> {
stopSelf()

View file

@ -43,7 +43,6 @@
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/default_padding"
app:autoPlay="false"
app:showFullScreenButton="false"
app:enableAutomaticInitialization="true"
app:handleNetworkEvents="true"
app:videoId="NXv3JpmwA8Y" />
@ -82,7 +81,6 @@
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/default_padding"
app:autoPlay="false"
app:showFullScreenButton="false"
app:enableAutomaticInitialization="true"
app:handleNetworkEvents="true"
app:videoId="jxaxffE8c4c" />

View file

@ -1,13 +1,15 @@
package org.fnives.tiktokdownloader.data.local
import com.nhaarman.mockitokotlin2.spy
import org.fnives.tiktokdownloader.data.local.persistent.SharedPreferencesManager
import org.fnives.tiktokdownloader.helper.mock.InMemorySharedPreferencesManager
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
import org.mockito.kotlin.spy
@Suppress("TestFunctionName")
@Timeout(value = 2)
class CaptchaTimeoutLocalSourceTest {
private lateinit var mockSharedPreferencesManager: SharedPreferencesManager

View file

@ -1,15 +1,9 @@
package org.fnives.tiktokdownloader.data.local
import com.nhaarman.mockitokotlin2.anyOrNull
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verifyZeroInteractions
import com.nhaarman.mockitokotlin2.whenever
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList
@ -26,9 +20,16 @@ import org.fnives.tiktokdownloader.helper.mock.InMemorySharedPreferencesManager
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.verifyNoInteractions
import org.mockito.kotlin.whenever
import java.io.InputStream
@Suppress("TestFunctionName")
@Timeout(value = 2)
class VideoDownloadedLocalSourceTest {
private lateinit var sut: VideoDownloadedLocalSource
@ -52,8 +53,8 @@ class VideoDownloadedLocalSourceTest {
@Test
fun GIVEN_observing_saved_videos_WHEN_initialized_THEN_emptylist_is_emitted() = runBlocking<Unit> {
Assertions.assertEquals(emptyList<VideoDownloaded>(), sut.savedVideos.first())
verifyZeroInteractions(mockSaveVideoFile)
verifyZeroInteractions(mockVerifyFileForUriExists)
verifyNoInteractions(mockSaveVideoFile)
verifyNoInteractions(mockVerifyFileForUriExists)
}
@Test

View file

@ -15,8 +15,10 @@ import org.fnives.tiktokdownloader.data.model.VideoInPending
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
@Suppress("TestFunctionName")
@Timeout(value = 2)
class VideoInPendingLocalSourceTest {
private lateinit var sut: VideoInPendingLocalSource

View file

@ -13,8 +13,10 @@ import org.fnives.tiktokdownloader.data.model.VideoInProgress
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
@Suppress("TestFunctionName")
@Timeout(value = 2)
class VideoInProgressLocalSourceTest {
private lateinit var sut: VideoInProgressLocalSource

View file

@ -14,12 +14,14 @@ import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
import java.util.stream.Stream
@Suppress("TestFunctionName")
@Timeout(value = 2)
class TikTokDownloadRemoteSourceTest {
private lateinit var mockWebServer: MockWebServer

View file

@ -9,6 +9,7 @@ import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
import java.io.File
/**
@ -16,7 +17,8 @@ import java.io.File
* Since the website may change anytime without any notice, this test verifies with actual request going out.
* However this makes the test shaky, because if the device has no proper connection it may fail.
*/
@Suppress("TestFunctionName")
@Timeout(value = 2)
class TikTokDownloadRemoteSourceUpToDateTest {
private lateinit var sut: TikTokDownloadRemoteSource

View file

@ -1,20 +1,22 @@
package org.fnives.tiktokdownloader.data.usecase
import com.nhaarman.mockitokotlin2.anyOrNull
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
import com.nhaarman.mockitokotlin2.verifyZeroInteractions
import com.nhaarman.mockitokotlin2.whenever
import org.fnives.tiktokdownloader.data.local.VideoInPendingLocalSource
import org.fnives.tiktokdownloader.data.model.VideoInPending
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
@Suppress("TestFunctionName")
@Timeout(value = 2)
class AddVideoToQueueUseCaseTest {
private lateinit var sut: AddVideoToQueueUseCase
@ -30,8 +32,8 @@ class AddVideoToQueueUseCaseTest {
@Test
fun GIVEN_no_action_THEN_the_local_source_and_verifier_is_not_touched() {
verifyZeroInteractions(mockUrlVerificationUseCase)
verifyZeroInteractions(mockVideoInPendingLocalSource)
verifyNoInteractions(mockUrlVerificationUseCase)
verifyNoInteractions(mockVideoInPendingLocalSource)
}
@Test
@ -83,6 +85,6 @@ class AddVideoToQueueUseCaseTest {
Assertions.assertFalse(actual, "Url is Saved while it should NOT be")
verify(mockUrlVerificationUseCase, times(1)).invoke(expectedUrl)
verifyNoMoreInteractions(mockUrlVerificationUseCase)
verifyZeroInteractions(mockVideoInPendingLocalSource)
verifyNoInteractions(mockVideoInPendingLocalSource)
}
}

View file

@ -1,19 +1,15 @@
package org.fnives.tiktokdownloader.data.usecase
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
import com.nhaarman.mockitokotlin2.verifyZeroInteractions
import com.nhaarman.mockitokotlin2.whenever
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher
import org.fnives.tiktokdownloader.data.local.VideoDownloadedLocalSource
import org.fnives.tiktokdownloader.data.local.VideoInPendingLocalSource
import org.fnives.tiktokdownloader.data.local.VideoInProgressLocalSource
@ -21,14 +17,26 @@ import org.fnives.tiktokdownloader.data.model.VideoDownloaded
import org.fnives.tiktokdownloader.data.model.VideoInPending
import org.fnives.tiktokdownloader.data.model.VideoInProgress
import org.fnives.tiktokdownloader.data.model.VideoState
import org.fnives.tiktokdownloader.helper.advanceTimeBy
import org.fnives.tiktokdownloader.helper.advanceUntilIdle
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
@Suppress("TestFunctionName")
@OptIn(ExperimentalCoroutinesApi::class)
@Timeout(value = 2)
class StateOfVideosObservableUseCaseTest {
private lateinit var testDispatcher: TestCoroutineDispatcher
private lateinit var testDispatcher: TestDispatcher
private lateinit var mockVideoInProgressLocalSource: VideoInProgressLocalSource
private lateinit var mockVideoInPendingLocalSource: VideoInPendingLocalSource
private lateinit var mockVideoDownloadedLocalSource: VideoDownloadedLocalSource
@ -48,7 +56,7 @@ class StateOfVideosObservableUseCaseTest {
whenever(mockVideoInProgressLocalSource.videoInProcessFlow).doReturn(videoInProgressMutableFlow)
whenever(mockVideoInPendingLocalSource.pendingVideos).doReturn(videoInPendingMutableFlow)
whenever(mockVideoDownloadedLocalSource.savedVideos).doReturn(videoDownloadedMutableFlow)
testDispatcher = TestCoroutineDispatcher()
testDispatcher = StandardTestDispatcher()
sut = StateOfVideosObservableUseCase(
videoInProgressLocalSource = mockVideoInProgressLocalSource,
videoInPendingLocalSource = mockVideoInPendingLocalSource,
@ -59,13 +67,13 @@ class StateOfVideosObservableUseCaseTest {
@Test
fun WHEN_no_invoke_is_called_THEN_no_dependency_is_called() {
verifyZeroInteractions(mockVideoDownloadedLocalSource)
verifyZeroInteractions(mockVideoInPendingLocalSource)
verifyZeroInteractions(mockVideoInProgressLocalSource)
verifyNoInteractions(mockVideoDownloadedLocalSource)
verifyNoInteractions(mockVideoInPendingLocalSource)
verifyNoInteractions(mockVideoInProgressLocalSource)
}
@Test
fun GIVEN_no_inProgress_AND_empty_pending_AND_empty_saved_THEN_emptyList_is_emitted() = runBlocking(testDispatcher) {
fun GIVEN_no_inProgress_AND_empty_pending_AND_empty_saved_THEN_emptyList_is_emitted() = runBlocking {
videoInProgressMutableFlow.value = null
videoInPendingMutableFlow.value = emptyList()
videoDownloadedMutableFlow.value = emptyList()
@ -83,7 +91,7 @@ class StateOfVideosObservableUseCaseTest {
}
@Test
fun GIVEN_inProgress_AND_empty_pending_AND_empty_saved_THEN_inProgress_is_emitted() = runBlocking(testDispatcher) {
fun GIVEN_inProgress_AND_empty_pending_AND_empty_saved_THEN_inProgress_is_emitted() = runBlocking {
val videoInProgress = VideoInProgress("alma", "url")
val expected = listOf<VideoState>(VideoState.InProcess(videoInProgress))
val expectedList = listOf(emptyList(), expected)
@ -109,7 +117,7 @@ class StateOfVideosObservableUseCaseTest {
}
@Test
fun GIVEN_inProgress_AND_pendingWithSameId_AND_empty_saved_THEN_inProgress_is_emitted() = runBlocking(testDispatcher) {
fun GIVEN_inProgress_AND_pendingWithSameId_AND_empty_saved_THEN_inProgress_is_emitted() = runBlocking {
val videoInProgress = VideoInProgress("alma", "url")
val videoInPending = VideoInPending(id = videoInProgress.id, url = videoInProgress.url)
val expected = listOf<VideoState>(VideoState.InProcess(videoInProgress))
@ -130,7 +138,7 @@ class StateOfVideosObservableUseCaseTest {
}
@Test
fun GIVEN_in_pending_AND_nothing_inprogress_AND_empty_saved_THEN_inPending_is_emitted() = runBlocking(testDispatcher) {
fun GIVEN_in_pending_AND_nothing_inprogress_AND_empty_saved_THEN_inPending_is_emitted() = runBlocking {
val videoInPending = VideoInPending(id = "alma", url = "url")
val expected = listOf<VideoState>(VideoState.InPending(videoInPending))
val expectedList = listOf(emptyList(), expected)
@ -151,7 +159,7 @@ class StateOfVideosObservableUseCaseTest {
@Test
fun GIVEN_inProgress_AND_pendingWithSameId_AND_savedWithSameId_THEN_inProgress_And_saved_is_emitted() =
runBlocking(testDispatcher) {
runBlocking {
val videoInProgress = VideoInProgress("alma", "url")
val videoInPending = VideoInPending(id = videoInProgress.id, url = videoInProgress.url)
val videoDownloaded = VideoDownloaded(id = videoInProgress.id, url = videoInProgress.url, uri = "uri")
@ -173,7 +181,7 @@ class StateOfVideosObservableUseCaseTest {
}
@Test
fun GIVEN_new_item_faster_than_debounce_THEN_only_the_last_items_are_emitted() = runBlocking(testDispatcher) {
fun GIVEN_new_item_faster_than_debounce_THEN_only_the_last_items_are_emitted() = runBlocking {
val videoInProgress = VideoInProgress("alma", "url")
val expected = listOf(VideoState.InProcess(videoInProgress))
val expectedList = listOf(expected)
@ -194,7 +202,7 @@ class StateOfVideosObservableUseCaseTest {
}
@Test
fun GIVEN_new_item_slower_than_debounce_THEN_both_list_is_emitted() = runBlocking(testDispatcher) {
fun GIVEN_new_item_slower_than_debounce_THEN_both_list_is_emitted() = runBlocking {
val videoInProgress = VideoInProgress("alma", "url")
val expected = listOf(VideoState.InProcess(videoInProgress))
val expectedList = listOf(emptyList(), expected)
@ -215,7 +223,7 @@ class StateOfVideosObservableUseCaseTest {
}
@Test
fun GIVEN_processing_LATER_pendingWithSameId_THEN_no_new_emition_happens() = runBlocking(testDispatcher) {
fun GIVEN_processing_LATER_pendingWithSameId_THEN_no_new_emition_happens() = runBlocking {
val videoInProgress = VideoInProgress("alma", "url")
val videoInPendingSameAsProgress = VideoInPending(id = videoInProgress.id, url = videoInProgress.url)
val videoInPendingOther = VideoInPending(id = "alma2", url = "url2")

View file

@ -3,8 +3,10 @@ package org.fnives.tiktokdownloader.data.usecase
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
@Suppress("TestFunctionName")
@Timeout(value = 2)
class UrlVerificationUseCaseTest {
private lateinit var sut: UrlVerificationUseCase

View file

@ -1,21 +1,15 @@
package org.fnives.tiktokdownloader.data.usecase
import com.nhaarman.mockitokotlin2.anyOrNull
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
import com.nhaarman.mockitokotlin2.verifyZeroInteractions
import com.nhaarman.mockitokotlin2.whenever
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.runBlockingTest
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.runTest
import org.fnives.tiktokdownloader.data.local.CaptchaTimeoutLocalSource
import org.fnives.tiktokdownloader.data.local.VideoDownloadedLocalSource
import org.fnives.tiktokdownloader.data.local.VideoInPendingLocalSource
@ -30,15 +24,27 @@ import org.fnives.tiktokdownloader.data.network.TikTokDownloadRemoteSource
import org.fnives.tiktokdownloader.data.network.exceptions.CaptchaRequiredException
import org.fnives.tiktokdownloader.data.network.exceptions.NetworkException
import org.fnives.tiktokdownloader.data.network.exceptions.ParsingException
import org.fnives.tiktokdownloader.helper.advanceTimeBy
import org.fnives.tiktokdownloader.helper.advanceUntilIdle
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
import java.io.InputStream
@Suppress("TestFunctionName")
@OptIn(ExperimentalCoroutinesApi::class)
//@Timeout(value = 2)
class VideoDownloadingProcessorUseCaseTest {
private lateinit var testDispatcher: TestCoroutineDispatcher
private lateinit var testDispatcher: TestDispatcher
private lateinit var mockVideoInProgressLocalSource: VideoInProgressLocalSource
private lateinit var mockVideoInPendingLocalSource: VideoInPendingLocalSource
private lateinit var mockVideoDownloadedLocalSource: VideoDownloadedLocalSource
@ -62,7 +68,7 @@ class VideoDownloadingProcessorUseCaseTest {
whenever(mockVideoInProgressLocalSource.videoInProcessFlow).doReturn(videoInProgressMutableFlow)
whenever(mockVideoInPendingLocalSource.pendingVideos).doReturn(videoInPendingMutableFlow)
whenever(mockVideoDownloadedLocalSource.savedVideos).doReturn(videoDownloadedMutableFlow)
testDispatcher = TestCoroutineDispatcher()
testDispatcher = StandardTestDispatcher()
sut = VideoDownloadingProcessorUseCase(
videoInProgressLocalSource = mockVideoInProgressLocalSource,
videoInPendingLocalSource = mockVideoInPendingLocalSource,
@ -75,24 +81,24 @@ class VideoDownloadingProcessorUseCaseTest {
@Test
fun WHEN_no_method_invoked_THEN_no_interaction_with_dependencies() {
verifyZeroInteractions(mockVideoInProgressLocalSource)
verifyZeroInteractions(mockVideoInPendingLocalSource)
verifyZeroInteractions(mockVideoDownloadedLocalSource)
verifyZeroInteractions(mockTikTokDownloadRemoteSource)
verifyNoInteractions(mockVideoInProgressLocalSource)
verifyNoInteractions(mockVideoInPendingLocalSource)
verifyNoInteractions(mockVideoDownloadedLocalSource)
verifyNoInteractions(mockTikTokDownloadRemoteSource)
}
@Test
fun GIVEN_not_observing_WHEN_fetching_THEN_nothing_happens() {
sut.fetchVideoInState()
verifyZeroInteractions(mockVideoInProgressLocalSource)
verifyZeroInteractions(mockVideoInPendingLocalSource)
verifyZeroInteractions(mockVideoDownloadedLocalSource)
verifyZeroInteractions(mockTikTokDownloadRemoteSource)
verifyNoInteractions(mockVideoInProgressLocalSource)
verifyNoInteractions(mockVideoInPendingLocalSource)
verifyNoInteractions(mockVideoDownloadedLocalSource)
verifyNoInteractions(mockTikTokDownloadRemoteSource)
}
@Test
fun GIVEN_empty_pendingVideos_WHEN_observing_THEN_error_is_emited() = runBlocking(testDispatcher) {
fun GIVEN_empty_pendingVideos_WHEN_observing_THEN_error_is_emited() = runBlocking {
videoInPendingMutableFlow.value = emptyList()
val expected = ProcessState.Finished
val expectedList = listOf(expected)
@ -103,13 +109,12 @@ class VideoDownloadingProcessorUseCaseTest {
Assertions.assertEquals(expectedList, resultList.await())
verify(mockVideoInPendingLocalSource, times(1)).pendingVideos
verifyNoMoreInteractions(mockVideoInPendingLocalSource)
verifyZeroInteractions(mockVideoInPendingLocalSource)
verifyZeroInteractions(mockVideoDownloadedLocalSource)
verifyZeroInteractions(mockTikTokDownloadRemoteSource)
verifyNoInteractions(mockVideoDownloadedLocalSource)
verifyNoInteractions(mockTikTokDownloadRemoteSource)
}
@Test
fun GIVEN_one_pending_video_AND_network_error_WHEN_observing_THEN_error_is_emited() = runBlocking(testDispatcher) {
fun GIVEN_one_pending_video_AND_network_error_WHEN_observing_THEN_error_is_emited() = runBlocking {
val videoInPending = VideoInPending("alma", "banan")
videoInPendingMutableFlow.value = listOf(videoInPending)
whenever(mockTikTokDownloadRemoteSource.getVideo(videoInPending)).then { throw NetworkException() }
@ -126,11 +131,10 @@ class VideoDownloadingProcessorUseCaseTest {
verify(mockVideoDownloadedLocalSource, times(1)).savedVideos
verifyNoMoreInteractions(mockTikTokDownloadRemoteSource)
verifyNoMoreInteractions(mockVideoDownloadedLocalSource)
verifyZeroInteractions(mockVideoInPendingLocalSource)
}
@Test
fun GIVEN_one_pending_video_AND_parsing_error_WHEN_observing_THEN_parsingError_is_emited() = runBlocking(testDispatcher) {
fun GIVEN_one_pending_video_AND_parsing_error_WHEN_observing_THEN_parsingError_is_emited() = runBlocking {
val videoInPending = VideoInPending("alma", "banan")
videoInPendingMutableFlow.value = listOf(videoInPending)
whenever(mockTikTokDownloadRemoteSource.getVideo(videoInPending)).then { throw ParsingException() }
@ -144,7 +148,7 @@ class VideoDownloadingProcessorUseCaseTest {
}
@Test
fun GIVEN_one_pending_video_AND_unexpected_error_WHEN_observing_THEN_unknown_error_is_emitted() = runBlockingTest(testDispatcher) {
fun GIVEN_one_pending_video_AND_unexpected_error_WHEN_observing_THEN_unknown_error_is_emitted() = runTest(testDispatcher) {
val videoInPending = VideoInPending("alma", "banan")
videoInPendingMutableFlow.value = listOf(videoInPending)
whenever(mockTikTokDownloadRemoteSource.getVideo(videoInPending)).then { throw Throwable() }
@ -158,7 +162,7 @@ class VideoDownloadingProcessorUseCaseTest {
}
@Test
fun GIVEN_one_pending_video_AND_network_errors_WHILE_observing_WHEN_fetching_THEN_it_retries() = runBlocking<Unit>(testDispatcher) {
fun GIVEN_one_pending_video_AND_network_errors_WHILE_observing_WHEN_fetching_THEN_it_retries() = runBlocking<Unit> {
val videoInPending = VideoInPending("alma", "banan")
videoInPendingMutableFlow.value = listOf(videoInPending)
var specificException = true
@ -177,7 +181,7 @@ class VideoDownloadingProcessorUseCaseTest {
}
@Test
fun GIVEN_one_pending_video_AND_parsing_errors_WHILE_observing_WHEN_fetching_THEN_it_retries() = runBlocking<Unit>(testDispatcher) {
fun GIVEN_one_pending_video_AND_parsing_errors_WHILE_observing_WHEN_fetching_THEN_it_retries() = runBlocking<Unit> {
val videoInPending = VideoInPending("alma", "banan")
videoInPendingMutableFlow.value = listOf(videoInPending)
var specificException = true
@ -196,7 +200,7 @@ class VideoDownloadingProcessorUseCaseTest {
}
@Test
fun GIVEN_one_pending_video_AND_unknown_errors_WHILE_observing_WHEN_fetching_THEN_it_retries() = runBlocking<Unit>(testDispatcher) {
fun GIVEN_one_pending_video_AND_unknown_errors_WHILE_observing_WHEN_fetching_THEN_it_retries() = runBlocking<Unit> {
val videoInPending = VideoInPending("alma", "banan")
videoInPendingMutableFlow.value = listOf(videoInPending)
var specificException = true
@ -216,7 +220,7 @@ class VideoDownloadingProcessorUseCaseTest {
// verify that fetching even while request is running doesn't matter, only after error is emitted
@Test
fun GIVEN_one_pending_video_AND_delaying_until_fetch_WHILE_observing_WHEN_fetching_THEN_emition_happens_only_once() = runBlocking<Unit>(testDispatcher) {
fun GIVEN_one_pending_video_AND_delaying_until_fetch_WHILE_observing_WHEN_fetching_THEN_emition_happens_only_once() = runBlocking<Unit> {
val videoInPending = VideoInPending("alma", "banan")
videoInPendingMutableFlow.value = listOf(videoInPending)
var specificException = true
@ -242,7 +246,7 @@ class VideoDownloadingProcessorUseCaseTest {
}
@Test
fun GIVEN_one_pending_video_AND_failing_request_WHEN_observing_THEN_video_is_marked_processing_then_unprocessing() = runBlocking<Unit>(testDispatcher) {
fun GIVEN_one_pending_video_AND_failing_request_WHEN_observing_THEN_video_is_marked_processing_then_unprocessing() = runBlocking<Unit> {
val videoInPending = VideoInPending("alma", "banan")
videoInPendingMutableFlow.value = listOf(videoInPending)
whenever(mockTikTokDownloadRemoteSource.getVideo(videoInPending)).then {
@ -261,7 +265,7 @@ class VideoDownloadingProcessorUseCaseTest {
}
@Test
fun GIVEN_one_pending_video_AND_successful_request_AND_storage_error_WHEN_observing_THEN_video_is_saved_called_and_error_is_propogated() = runBlocking(testDispatcher) {
fun GIVEN_one_pending_video_AND_successful_request_AND_storage_error_WHEN_observing_THEN_video_is_saved_called_and_error_is_propogated() = runBlocking {
val videoInPending = VideoInPending("alma", "banan")
videoInPendingMutableFlow.value = listOf(videoInPending)
val videoInSavingIntoFile = VideoInSavingIntoFile("x", "u", VideoInSavingIntoFile.ContentType("a", "b"), FalseInputStream())
@ -282,7 +286,7 @@ class VideoDownloadingProcessorUseCaseTest {
}
@Test
fun GIVEN_one_pending_video_AND_successful_request_AND_unexpected_error_WHEN_observing_THEN_video_is_saved_called_and_error_is_propogated() = runBlocking(testDispatcher) {
fun GIVEN_one_pending_video_AND_successful_request_AND_unexpected_error_WHEN_observing_THEN_video_is_saved_called_and_error_is_propogated() = runBlocking {
val videoInPending = VideoInPending("alma", "banan")
videoInPendingMutableFlow.value = listOf(videoInPending)
val videoInSavingIntoFile = VideoInSavingIntoFile("x", "u", VideoInSavingIntoFile.ContentType("a", "b"), FalseInputStream())
@ -303,7 +307,7 @@ class VideoDownloadingProcessorUseCaseTest {
}
@Test
fun GIVEN_one_pending_video_AND_successful_request_AND_successful_file_save_WHEN_observing_THEN_pending_is_removed() = runBlocking(testDispatcher) {
fun GIVEN_one_pending_video_AND_successful_request_AND_successful_file_save_WHEN_observing_THEN_pending_is_removed() = runBlocking {
val videoInPending = VideoInPending("alma", "banan")
val videoDownloaded = VideoDownloaded("zz", "yy", "xx")
videoInPendingMutableFlow.value = listOf(videoInPending)
@ -325,7 +329,7 @@ class VideoDownloadingProcessorUseCaseTest {
}
@Test
fun GIVEN_one_pending_video_AND_captcha_timeout_WHEN_observing_THEN_captcha_timeout_is_emited() = runBlocking(testDispatcher) {
fun GIVEN_one_pending_video_AND_captcha_timeout_WHEN_observing_THEN_captcha_timeout_is_emited() = runBlocking {
val videoInPending = VideoInPending("alma", "banan")
videoInPendingMutableFlow.value = listOf(videoInPending)
whenever(mockCaptchaTimeoutLocalSource.isInCaptchaTimeout()).doReturn(true)
@ -344,7 +348,8 @@ class VideoDownloadingProcessorUseCaseTest {
}
@Test
fun GIVEN_one_pending_video_AND_successful_request_AND_successful_file_save_WHEN_observing_with_2_THEN_pending_is_removed_AND_only_once_executed() = runBlocking(testDispatcher) {
fun GIVEN_one_pending_video_AND_successful_request_AND_successful_file_save_WHEN_observing_with_2_THEN_pending_is_removed_AND_only_once_executed() =
runBlocking {
val videoInPending = VideoInPending("alma", "banan")
val videoDownloaded = VideoDownloaded("zz", "yy", "xx")
videoInPendingMutableFlow.value = listOf(videoInPending)
@ -368,7 +373,7 @@ class VideoDownloadingProcessorUseCaseTest {
}
@Test
fun GIVEN_one_pending_video_BUT_already_downloaded_WHEN_observing_THEN_processed_is_emitted_but_no_request_call() = runBlocking(testDispatcher) {
fun GIVEN_one_pending_video_BUT_already_downloaded_WHEN_observing_THEN_processed_is_emitted_but_no_request_call() = runBlocking {
val videoInPending = VideoInPending("alma", "banan")
val videoDownloaded = VideoDownloaded("alma", "banan", "xx")
videoInPendingMutableFlow.value = listOf(videoInPending)
@ -389,11 +394,11 @@ class VideoDownloadingProcessorUseCaseTest {
verifyNoMoreInteractions(mockVideoInPendingLocalSource)
verifyNoMoreInteractions(mockVideoDownloadedLocalSource)
verifyNoMoreInteractions(mockVideoInProgressLocalSource)
verifyZeroInteractions(mockTikTokDownloadRemoteSource)
verifyNoInteractions(mockTikTokDownloadRemoteSource)
}
@Test
fun GIVEN_one_pending_video_BUT_already_downloaded_AND_captcha_timeout_WHEN_observing_THEN_processed_is_emitted_but_no_request_call() = runBlocking(testDispatcher) {
fun GIVEN_one_pending_video_BUT_already_downloaded_AND_captcha_timeout_WHEN_observing_THEN_processed_is_emitted_but_no_request_call() = runBlocking {
val videoInPending = VideoInPending("alma", "banan")
val videoDownloaded = VideoDownloaded("alma", "banan", "xx")
videoInPendingMutableFlow.value = listOf(videoInPending)
@ -415,11 +420,11 @@ class VideoDownloadingProcessorUseCaseTest {
verifyNoMoreInteractions(mockVideoInPendingLocalSource)
verifyNoMoreInteractions(mockVideoDownloadedLocalSource)
verifyNoMoreInteractions(mockVideoInProgressLocalSource)
verifyZeroInteractions(mockTikTokDownloadRemoteSource)
verifyNoInteractions(mockTikTokDownloadRemoteSource)
}
@Test
fun GIVEN_one_pending_video_BUT_CaptchaTimeoutException_WHEN_observing_THEN_its_saved_and_captchaError_emitted() = runBlocking(testDispatcher) {
fun GIVEN_one_pending_video_BUT_CaptchaTimeoutException_WHEN_observing_THEN_its_saved_and_captchaError_emitted() = runBlocking {
val videoInPending = VideoInPending("alma", "banan")
videoInPendingMutableFlow.value = listOf(videoInPending)
videoDownloadedMutableFlow.value = listOf()
@ -451,7 +456,7 @@ class VideoDownloadingProcessorUseCaseTest {
}
@Test
fun GIVEN_one_pending_video_AND_not_advancing_enough_WHILE_observing_WHEN_fetching_THEN_nothing_is_called() = runBlocking<Unit>(testDispatcher) {
fun GIVEN_one_pending_video_AND_not_advancing_enough_WHILE_observing_WHEN_fetching_THEN_nothing_is_called() = runBlocking<Unit> {
val videoInPending = VideoInPending("alma", "banan")
videoInPendingMutableFlow.value = listOf(videoInPending)
whenever(mockTikTokDownloadRemoteSource.getVideo(videoInPending)).then { throw NetworkException() }
@ -459,21 +464,23 @@ class VideoDownloadingProcessorUseCaseTest {
val resultList = async(testDispatcher) { sut.processState.take(2).toList() }
testDispatcher.advanceTimeBy(199)
verifyZeroInteractions(mockTikTokDownloadRemoteSource)
verifyNoInteractions(mockTikTokDownloadRemoteSource)
testDispatcher.advanceUntilIdle()
resultList.cancelAndJoin()
}
@Test
fun GIVEN_one_pending_video_AND_but_advancing_enough_WHILE_observing_WHEN_fetching_THEN_nothing_is_called() = runBlocking<Unit>(testDispatcher) {
fun GIVEN_one_pending_video_AND_but_advancing_enough_WHILE_observing_WHEN_fetching_THEN_nothing_is_called() = runBlocking<Unit> {
val videoInPending = VideoInPending("alma", "banan")
videoInPendingMutableFlow.value = listOf(videoInPending)
whenever(mockTikTokDownloadRemoteSource.getVideo(videoInPending)).then { throw NetworkException() }
val resultList = async(testDispatcher) { sut.processState.take(2).toList() }
testDispatcher.advanceTimeBy(200)
testDispatcher.advanceTimeBy(201)
verify(mockTikTokDownloadRemoteSource, times(1)).getVideo(videoInPending)
verifyNoMoreInteractions(mockTikTokDownloadRemoteSource)
testDispatcher.advanceUntilIdle()
resultList.cancelAndJoin()
}

View file

@ -1,10 +1,6 @@
package org.fnives.tiktokdownloader.di
import android.content.Context
import com.nhaarman.mockitokotlin2.anyOrNull
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
@ -14,7 +10,13 @@ import org.fnives.tiktokdownloader.ui.main.queue.QueueViewModel
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@Timeout(value = 2)
class ServiceLocatorTest {
private lateinit var mockContext: Context

View file

@ -0,0 +1,10 @@
package org.fnives.tiktokdownloader.helper
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestDispatcher
@OptIn(ExperimentalCoroutinesApi::class)
fun TestDispatcher.advanceUntilIdle() = scheduler.advanceUntilIdle()
@OptIn(ExperimentalCoroutinesApi::class)
fun TestDispatcher.advanceTimeBy(delayTimeMillis: Long) = scheduler.advanceTimeBy(delayTimeMillis)

View file

@ -3,7 +3,7 @@ package org.fnives.tiktokdownloader.helper.mock
import androidx.lifecycle.Lifecycle
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryOwner
import com.nhaarman.mockitokotlin2.mock
import org.mockito.kotlin.mock
class MockSavedStateRegistryOwner(
private val lifecycle: Lifecycle = MockLifecycle(),

View file

@ -2,14 +2,7 @@ package org.fnives.tiktokdownloader.ui.main
import androidx.lifecycle.SavedStateHandle
import com.jraska.livedata.test
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
import com.nhaarman.mockitokotlin2.whenever
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.MutableSharedFlow
import org.fnives.tiktokdownloader.data.model.ProcessState
import org.fnives.tiktokdownloader.data.model.VideoDownloaded
import org.fnives.tiktokdownloader.data.model.VideoInPending
@ -24,23 +17,29 @@ import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
import java.util.stream.Stream
@Suppress("TestFunctionName")
@ExtendWith(InstantExecutorExtension::class, MainDispatcherExtension::class)
class MainViewModelTest {
private lateinit var conflatedBroadcastChannel: ConflatedBroadcastChannel<ProcessState>
private lateinit var processStateFlow: MutableSharedFlow<ProcessState>
private lateinit var mockVideoDownloadingProcessorUseCase: VideoDownloadingProcessorUseCase
private lateinit var mockAddVideoToQueueUseCase: AddVideoToQueueUseCase
private lateinit var sut: MainViewModel
@BeforeEach
fun setup() {
conflatedBroadcastChannel = ConflatedBroadcastChannel()
processStateFlow = MutableSharedFlow(replay = 1)
mockVideoDownloadingProcessorUseCase = mock()
mockAddVideoToQueueUseCase = mock()
whenever(mockVideoDownloadingProcessorUseCase.processState).doReturn(conflatedBroadcastChannel.asFlow())
whenever(mockVideoDownloadingProcessorUseCase.processState).doReturn(processStateFlow)
sut = MainViewModel(mockVideoDownloadingProcessorUseCase, mockAddVideoToQueueUseCase, SavedStateHandle())
}
@ -95,7 +94,7 @@ class MainViewModelTest {
val testObserver = sut.refreshActionVisibility.test()
conflatedBroadcastChannel.offer(processState)
processStateFlow.tryEmit(processState)
testObserver.assertHistorySize(2).assertValueHistory(false, expected)
verify(mockVideoDownloadingProcessorUseCase, times(1)).processState
@ -114,7 +113,7 @@ class MainViewModelTest {
val testObserver = sut.errorMessage.test()
conflatedBroadcastChannel.offer(processState)
processStateFlow.tryEmit(processState)
testObserver.assertHistorySize(2).assertValueHistory(null, expected?.let(::Event))
verify(mockVideoDownloadingProcessorUseCase, times(1)).processState

View file

@ -1,15 +1,7 @@
package org.fnives.tiktokdownloader.ui.main.queue
import com.jraska.livedata.test
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
import com.nhaarman.mockitokotlin2.verifyZeroInteractions
import com.nhaarman.mockitokotlin2.whenever
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.MutableSharedFlow
import org.fnives.tiktokdownloader.data.model.VideoInPending
import org.fnives.tiktokdownloader.data.model.VideoState
import org.fnives.tiktokdownloader.data.usecase.AddVideoToQueueUseCase
@ -21,13 +13,19 @@ import org.fnives.tiktokdownloader.ui.shared.Event
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import kotlin.math.exp
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
@Suppress("TestFunctionName")
@ExtendWith(InstantExecutorExtension::class, MainDispatcherExtension::class)
class QueueViewModelTest {
private lateinit var stateOfVideosConflatedBroadcastChannel: ConflatedBroadcastChannel<List<VideoState>>
private lateinit var stateOfVideosFlow: MutableSharedFlow<List<VideoState>>
private lateinit var mockStateOfVideosObservableUseCase: StateOfVideosObservableUseCase
private lateinit var mockAddVideoToQueueUseCase: AddVideoToQueueUseCase
private lateinit var mockVideoDownloadingProcessorUseCase: VideoDownloadingProcessorUseCase
@ -35,9 +33,9 @@ class QueueViewModelTest {
@BeforeEach
fun setup() {
stateOfVideosConflatedBroadcastChannel = ConflatedBroadcastChannel()
stateOfVideosFlow = MutableSharedFlow(replay = 1)
mockStateOfVideosObservableUseCase = mock()
whenever(mockStateOfVideosObservableUseCase.invoke()).doReturn(stateOfVideosConflatedBroadcastChannel.asFlow())
whenever(mockStateOfVideosObservableUseCase.invoke()).doReturn(stateOfVideosFlow)
mockAddVideoToQueueUseCase = mock()
mockVideoDownloadingProcessorUseCase = mock()
sut = QueueViewModel(mockStateOfVideosObservableUseCase, mockAddVideoToQueueUseCase, mockVideoDownloadingProcessorUseCase)
@ -48,20 +46,20 @@ class QueueViewModelTest {
sut.downloads.test().assertNoValue()
verify(mockStateOfVideosObservableUseCase, times(1)).invoke()
verifyNoMoreInteractions(mockStateOfVideosObservableUseCase)
verifyZeroInteractions(mockAddVideoToQueueUseCase)
verifyZeroInteractions(mockVideoDownloadingProcessorUseCase)
verifyNoInteractions(mockAddVideoToQueueUseCase)
verifyNoInteractions(mockVideoDownloadingProcessorUseCase)
}
@Test
fun GIVEN_initialized_AND_observing_WHEN_emitting_a_emptyList_THEN_it_is_sent_out() {
val expected = listOf<VideoState>()
stateOfVideosConflatedBroadcastChannel.offer(expected)
stateOfVideosFlow.tryEmit(expected)
sut.downloads.test().assertValue(expected)
verify(mockStateOfVideosObservableUseCase, times(1)).invoke()
verifyNoMoreInteractions(mockStateOfVideosObservableUseCase)
verifyZeroInteractions(mockAddVideoToQueueUseCase)
verifyZeroInteractions(mockVideoDownloadingProcessorUseCase)
verifyNoInteractions(mockAddVideoToQueueUseCase)
verifyNoInteractions(mockVideoDownloadingProcessorUseCase)
}
@Test
@ -70,15 +68,15 @@ class QueueViewModelTest {
val expected2 = listOf(VideoState.InPending(VideoInPending("a2", "b2")))
val testObserver = sut.downloads.test()
stateOfVideosConflatedBroadcastChannel.offer(expected1)
stateOfVideosConflatedBroadcastChannel.offer(expected2)
stateOfVideosFlow.tryEmit(expected1)
stateOfVideosFlow.tryEmit(expected2)
testObserver.assertHistorySize(2).assertHasValue()
.assertValueHistory(expected1, expected2)
verify(mockStateOfVideosObservableUseCase, times(1)).invoke()
verifyNoMoreInteractions(mockStateOfVideosObservableUseCase)
verifyZeroInteractions(mockAddVideoToQueueUseCase)
verifyZeroInteractions(mockVideoDownloadingProcessorUseCase)
verifyNoInteractions(mockAddVideoToQueueUseCase)
verifyNoInteractions(mockVideoDownloadingProcessorUseCase)
}
@Test

View file

@ -1,16 +1,7 @@
package org.fnives.tiktokdownloader.ui.service
import com.jraska.livedata.test
import com.nhaarman.mockitokotlin2.anyOrNull
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
import com.nhaarman.mockitokotlin2.verifyZeroInteractions
import com.nhaarman.mockitokotlin2.whenever
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.MutableSharedFlow
import org.fnives.tiktokdownloader.R
import org.fnives.tiktokdownloader.data.model.ProcessState
import org.fnives.tiktokdownloader.data.model.VideoDownloaded
@ -25,22 +16,30 @@ import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
import java.util.stream.Stream
@Suppress("TestFunctionName")
@ExtendWith(InstantExecutorExtension::class, MainDispatcherExtension::class)
class QueueServiceViewModelTest {
private lateinit var videoDownloadingProcessorChannel: ConflatedBroadcastChannel<ProcessState>
private lateinit var videoDownloadingProcessorFlow: MutableSharedFlow<ProcessState>
private lateinit var mockVideoDownloadingProcessorUseCase: VideoDownloadingProcessorUseCase
private lateinit var mockAddVideoToQueueUseCase: AddVideoToQueueUseCase
private lateinit var sut: QueueServiceViewModel
@BeforeEach
fun setup() {
videoDownloadingProcessorChannel = ConflatedBroadcastChannel()
videoDownloadingProcessorFlow = MutableSharedFlow(replay = 1)
mockVideoDownloadingProcessorUseCase = mock()
whenever(mockVideoDownloadingProcessorUseCase.processState).doReturn(videoDownloadingProcessorChannel.asFlow())
whenever(mockVideoDownloadingProcessorUseCase.processState).doReturn(videoDownloadingProcessorFlow)
mockAddVideoToQueueUseCase = mock()
sut = QueueServiceViewModel(mockAddVideoToQueueUseCase, mockVideoDownloadingProcessorUseCase)
}
@ -64,7 +63,7 @@ class QueueServiceViewModelTest {
verify(mockAddVideoToQueueUseCase, times(1)).invoke("url.com")
verifyNoMoreInteractions(mockAddVideoToQueueUseCase)
verifyZeroInteractions(mockVideoDownloadingProcessorUseCase)
verifyNoInteractions(mockVideoDownloadingProcessorUseCase)
}
@Test
@ -89,7 +88,7 @@ class QueueServiceViewModelTest {
) {
whenever(mockAddVideoToQueueUseCase.invoke(anyOrNull())).doReturn(true)
sut.onUrlReceived("")
videoDownloadingProcessorChannel.offer(processState)
videoDownloadingProcessorFlow.tryEmit(processState)
sut.notificationState.test()
.assertHistorySize(1)
@ -101,7 +100,7 @@ class QueueServiceViewModelTest {
sut.onClear()
sut.onUrlReceived("alma")
videoDownloadingProcessorChannel.offer(ProcessState.UnknownError)
videoDownloadingProcessorFlow.tryEmit(ProcessState.UnknownError)
sut.notificationState.test().assertNoValue()
}

View file

@ -1,12 +1,13 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = "1.4.10"
ext.kotlin_version = "1.6.20"
repositories {
mavenCentral()
google()
jcenter()
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath "com.android.tools.build:gradle:4.1.0"
classpath 'com.android.tools.build:gradle:7.1.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
@ -16,8 +17,8 @@ buildscript {
allprojects {
repositories {
mavenCentral()
google()
jcenter()
}
}

View file

@ -1,6 +1,6 @@
#Sat Oct 31 22:42:23 EET 2020
#Thu Jan 27 21:44:07 EET 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
zipStoreBase=GRADLE_USER_HOME