mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-09-12 08:58:51 -04:00
Merge pull request #1182 from kiwix/macgills/#1174-introduce-unit-testing
Macgills/#1174 introduce unit testing
This commit is contained in:
commit
fac411fe6f
10
README.md
10
README.md
@ -24,12 +24,12 @@ We utilize different build variants (flavours) to build various different versio
|
|||||||
- [Retrofit](http://square.github.io/retrofit/) - Retrofit turns your REST API into a Java interface
|
- [Retrofit](http://square.github.io/retrofit/) - Retrofit turns your REST API into a Java interface
|
||||||
- [OkHttp](https://github.com/square/okhttp) - An HTTP+SPDY client for Android and Java applications
|
- [OkHttp](https://github.com/square/okhttp) - An HTTP+SPDY client for Android and Java applications
|
||||||
- [Butterknife](http://jakewharton.github.io/butterknife/) - View "injection" library for Android
|
- [Butterknife](http://jakewharton.github.io/butterknife/) - View "injection" library for Android
|
||||||
- [Mockito](https://github.com/mockito/mockito) - Most popular Mocking framework for unit tests written in Java
|
- [Mockito](https://github.com/mockito/mockito) - Most popular Mocking framework for unit tests written in Java
|
||||||
- [Guava](https://github.com/google/guava) - Collections, caching, primitives support, concurrency libraries, common annotations, string processing, I/O, and so forth.
|
|
||||||
- [Apache](https://github.com/apache/commons-io) - The Apache Commons IO library contains utility classes, stream implementations, file filters, file comparators, endian transformation classes, and much more.
|
|
||||||
- [RxJava](https://github.com/ReactiveX/RxJava) - Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM.
|
- [RxJava](https://github.com/ReactiveX/RxJava) - Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM.
|
||||||
|
- [ObjectBox] (https://github.com/objectbox/objectbox-java) - Reactive NoSQL Databse to replace SquiDb
|
||||||
|
- [MockK] (https://github.com/mockk/mockk) - Kotlin mocking library that allows mocking of final classes by default.
|
||||||
|
- [JUnit5] (https://github.com/junit-team/junit5/) - The next generation of JUnit
|
||||||
|
- [AssertJ] (https://github.com/joel-costigliola/assertj-core) - Fluent assertions for test code
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
@ -110,11 +110,9 @@ dependencies {
|
|||||||
implementation "io.reactivex.rxjava2:rxjava:$rxJavaVersion"
|
implementation "io.reactivex.rxjava2:rxjava:$rxJavaVersion"
|
||||||
|
|
||||||
// JUnit
|
// JUnit
|
||||||
testImplementation 'junit:junit:4.12'
|
|
||||||
androidTestImplementation 'junit:junit:4.12'
|
androidTestImplementation 'junit:junit:4.12'
|
||||||
|
|
||||||
// Mockito
|
// Mockito
|
||||||
testImplementation "org.mockito:mockito-core:2.24.5"
|
|
||||||
androidTestImplementation "org.mockito:mockito-android:2.24.5"
|
androidTestImplementation "org.mockito:mockito-android:2.24.5"
|
||||||
|
|
||||||
// Leak canary
|
// Leak canary
|
||||||
@ -136,6 +134,14 @@ dependencies {
|
|||||||
implementation "android.arch.lifecycle:extensions:1.1.1"
|
implementation "android.arch.lifecycle:extensions:1.1.1"
|
||||||
implementation "io.objectbox:objectbox-kotlin:$objectboxVersion"
|
implementation "io.objectbox:objectbox-kotlin:$objectboxVersion"
|
||||||
implementation "io.objectbox:objectbox-rxjava:$objectboxVersion"
|
implementation "io.objectbox:objectbox-rxjava:$objectboxVersion"
|
||||||
|
|
||||||
|
testImplementation "org.junit.jupiter:junit-jupiter:5.4.2"
|
||||||
|
testImplementation "io.mockk:mockk:1.9"
|
||||||
|
testImplementation "org.assertj:assertj-core:3.11.1"
|
||||||
|
//update this with androidx
|
||||||
|
testImplementation 'com.jraska.livedata:testing-ktx:0.2.1'
|
||||||
|
testImplementation 'android.arch.core:core-testing:1.1.1'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set custom app import directory
|
// Set custom app import directory
|
||||||
@ -364,6 +370,9 @@ android {
|
|||||||
androidExtensions {
|
androidExtensions {
|
||||||
experimental = true
|
experimental = true
|
||||||
}
|
}
|
||||||
|
testOptions {
|
||||||
|
unitTests.returnDefaultValues = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Testdroid deployment configuration
|
// Testdroid deployment configuration
|
||||||
|
@ -25,6 +25,7 @@ import dagger.Module;
|
|||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import dagger.android.AndroidInjectionModule;
|
import dagger.android.AndroidInjectionModule;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
import org.kiwix.kiwixmobile.downloader.model.UriToFileConverter;
|
||||||
import org.kiwix.kiwixmobile.utils.BookUtils;
|
import org.kiwix.kiwixmobile.utils.BookUtils;
|
||||||
|
|
||||||
@Module(includes = {
|
@Module(includes = {
|
||||||
@ -52,4 +53,9 @@ public class ApplicationModule {
|
|||||||
BookUtils provideBookUtils() {
|
BookUtils provideBookUtils() {
|
||||||
return new BookUtils();
|
return new BookUtils();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides @Singleton
|
||||||
|
UriToFileConverter provideUriToFIleCOnverter() {
|
||||||
|
return new UriToFileConverter.Impl();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,8 @@ import java.io.File
|
|||||||
|
|
||||||
data class BookOnDisk(
|
data class BookOnDisk(
|
||||||
val databaseId: Long? = null,
|
val databaseId: Long? = null,
|
||||||
val book: Book,
|
val book: Book = Book().apply { id = "" },
|
||||||
val file: File
|
val file: File = File("")
|
||||||
) {
|
) {
|
||||||
constructor(bookOnDiskEntity: BookOnDiskEntity) : this(
|
constructor(bookOnDiskEntity: BookOnDiskEntity) : this(
|
||||||
bookOnDiskEntity.id,
|
bookOnDiskEntity.id,
|
||||||
|
@ -17,12 +17,11 @@
|
|||||||
*/
|
*/
|
||||||
package org.kiwix.kiwixmobile.downloader.model
|
package org.kiwix.kiwixmobile.downloader.model
|
||||||
|
|
||||||
import android.view.WindowId
|
|
||||||
import org.kiwix.kiwixmobile.database.newdb.entities.DownloadEntity
|
|
||||||
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity
|
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity
|
||||||
|
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
|
||||||
|
|
||||||
data class DownloadModel(
|
data class DownloadModel(
|
||||||
val databaseId: Long? = null,
|
val databaseId: Long? = null,
|
||||||
val downloadId: Long,
|
val downloadId: Long = 0,
|
||||||
val book: LibraryNetworkEntity.Book
|
val book: LibraryNetworkEntity.Book = Book().apply { id = "" }
|
||||||
)
|
)
|
||||||
|
@ -55,21 +55,22 @@ import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class DownloadStatus(
|
class DownloadStatus(
|
||||||
val downloadId: Long,
|
val downloadId: Long = 0L,
|
||||||
val title: String,
|
val title: String = "",
|
||||||
val description: String,
|
val description: String = "",
|
||||||
val state: DownloadState,
|
val state: DownloadState = DownloadState.Pending,
|
||||||
val bytesDownloadedSoFar: Long,
|
val bytesDownloadedSoFar: Long = 0,
|
||||||
val totalSizeBytes: Long,
|
val totalSizeBytes: Long = 0,
|
||||||
val lastModified: String,
|
val lastModified: String = "",
|
||||||
val localUri: String?,
|
val localUri: String? = null,
|
||||||
val mediaProviderUri: String?,
|
val mediaProviderUri: String? = null,
|
||||||
val mediaType: String?,
|
val mediaType: String? = null,
|
||||||
val uri: String?,
|
val uri: String? = null,
|
||||||
val book: Book
|
val book: Book = Book().apply { id = "" }
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun toBookOnDisk() = BookOnDisk(book = book, file = File(Uri.parse(localUri).path))
|
fun toBookOnDisk(uriToFileConverter:UriToFileConverter) =
|
||||||
|
BookOnDisk(book = book, file = uriToFileConverter.convert(localUri))
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
cursor: Cursor,
|
cursor: Cursor,
|
||||||
@ -90,6 +91,11 @@ class DownloadStatus(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface UriToFileConverter {
|
||||||
|
fun convert(uriString: String?) = File(Uri.parse(uriString).path)
|
||||||
|
class Impl:UriToFileConverter{}
|
||||||
|
}
|
||||||
|
|
||||||
sealed class DownloadState(val stringId: Int) {
|
sealed class DownloadState(val stringId: Int) {
|
||||||
companion object {
|
companion object {
|
||||||
fun from(
|
fun from(
|
||||||
|
@ -3,8 +3,8 @@ package org.kiwix.kiwixmobile.zim_manager
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
|
import io.reactivex.Flowable
|
||||||
import io.reactivex.processors.BehaviorProcessor
|
import io.reactivex.processors.BehaviorProcessor
|
||||||
import io.reactivex.processors.PublishProcessor
|
|
||||||
import org.kiwix.kiwixmobile.extensions.networkState
|
import org.kiwix.kiwixmobile.extensions.networkState
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -13,14 +13,14 @@ class ConnectivityBroadcastReceiver @Inject constructor(private val connectivity
|
|||||||
|
|
||||||
override val action: String = ConnectivityManager.CONNECTIVITY_ACTION
|
override val action: String = ConnectivityManager.CONNECTIVITY_ACTION
|
||||||
|
|
||||||
val networkStates =
|
private val _networkStates = BehaviorProcessor.createDefault(connectivityManager.networkState)
|
||||||
BehaviorProcessor.createDefault(connectivityManager.networkState)
|
val networkStates: Flowable<NetworkState> = _networkStates
|
||||||
|
|
||||||
override fun onIntentWithActionReceived(
|
override fun onIntentWithActionReceived(
|
||||||
context: Context,
|
context: Context,
|
||||||
intent: Intent
|
intent: Intent
|
||||||
) {
|
) {
|
||||||
networkStates.onNext(connectivityManager.networkState)
|
_networkStates.onNext(connectivityManager.networkState)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package org.kiwix.kiwixmobile.zim_manager
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.Language
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class DefaultLanguageProvider @Inject constructor(private val context: Context) {
|
||||||
|
fun provide() = Language(
|
||||||
|
context.resources.configuration.locale.isO3Language,
|
||||||
|
true,
|
||||||
|
1
|
||||||
|
)
|
||||||
|
}
|
@ -21,6 +21,7 @@ package org.kiwix.kiwixmobile.zim_manager
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.arch.lifecycle.MutableLiveData
|
import android.arch.lifecycle.MutableLiveData
|
||||||
import android.arch.lifecycle.ViewModel
|
import android.arch.lifecycle.ViewModel
|
||||||
|
import android.support.annotation.VisibleForTesting
|
||||||
import io.reactivex.Flowable
|
import io.reactivex.Flowable
|
||||||
import io.reactivex.disposables.CompositeDisposable
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
@ -39,6 +40,7 @@ import org.kiwix.kiwixmobile.downloader.model.DownloadItem
|
|||||||
import org.kiwix.kiwixmobile.downloader.model.DownloadModel
|
import org.kiwix.kiwixmobile.downloader.model.DownloadModel
|
||||||
import org.kiwix.kiwixmobile.downloader.model.DownloadState.Successful
|
import org.kiwix.kiwixmobile.downloader.model.DownloadState.Successful
|
||||||
import org.kiwix.kiwixmobile.downloader.model.DownloadStatus
|
import org.kiwix.kiwixmobile.downloader.model.DownloadStatus
|
||||||
|
import org.kiwix.kiwixmobile.downloader.model.UriToFileConverter
|
||||||
import org.kiwix.kiwixmobile.extensions.calculateSearchMatches
|
import org.kiwix.kiwixmobile.extensions.calculateSearchMatches
|
||||||
import org.kiwix.kiwixmobile.extensions.registerReceiver
|
import org.kiwix.kiwixmobile.extensions.registerReceiver
|
||||||
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity
|
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity
|
||||||
@ -71,7 +73,9 @@ class ZimManageViewModel @Inject constructor(
|
|||||||
private val context: Application,
|
private val context: Application,
|
||||||
private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver,
|
private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver,
|
||||||
private val bookUtils: BookUtils,
|
private val bookUtils: BookUtils,
|
||||||
private val fat32Checker: Fat32Checker
|
private val fat32Checker: Fat32Checker,
|
||||||
|
private val uriToFileConverter: UriToFileConverter,
|
||||||
|
private val defaultLanguageProvider: DefaultLanguageProvider
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
val libraryItems: MutableLiveData<List<LibraryListItem>> = MutableLiveData()
|
val libraryItems: MutableLiveData<List<LibraryListItem>> = MutableLiveData()
|
||||||
@ -94,6 +98,11 @@ class ZimManageViewModel @Inject constructor(
|
|||||||
context.registerReceiver(connectivityBroadcastReceiver)
|
context.registerReceiver(connectivityBroadcastReceiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
fun onClearedExposed() {
|
||||||
|
onCleared()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
compositeDisposable.clear()
|
compositeDisposable.clear()
|
||||||
context.unregisterReceiver(connectivityBroadcastReceiver)
|
context.unregisterReceiver(connectivityBroadcastReceiver)
|
||||||
@ -105,16 +114,17 @@ class ZimManageViewModel @Inject constructor(
|
|||||||
val downloadStatuses = downloadStatuses(downloads)
|
val downloadStatuses = downloadStatuses(downloads)
|
||||||
val booksFromDao = books()
|
val booksFromDao = books()
|
||||||
val networkLibrary = PublishProcessor.create<LibraryNetworkEntity>()
|
val networkLibrary = PublishProcessor.create<LibraryNetworkEntity>()
|
||||||
|
val languages = languageDao.languages()
|
||||||
return arrayOf(
|
return arrayOf(
|
||||||
updateDownloadItems(downloadStatuses),
|
updateDownloadItems(downloadStatuses),
|
||||||
removeCompletedDownloadsFromDb(downloadStatuses),
|
removeCompletedDownloadsFromDb(downloadStatuses),
|
||||||
removeNonExistingDownloadsFromDb(downloadStatuses, downloads),
|
removeNonExistingDownloadsFromDb(downloadStatuses, downloads),
|
||||||
updateBookItems(booksFromDao),
|
updateBookItems(booksFromDao),
|
||||||
checkFileSystemForBooksOnRequest(booksFromDao),
|
checkFileSystemForBooksOnRequest(booksFromDao),
|
||||||
updateLibraryItems(booksFromDao, downloads, networkLibrary),
|
updateLibraryItems(booksFromDao, downloads, networkLibrary, languages),
|
||||||
updateLanguagesInDao(networkLibrary),
|
updateLanguagesInDao(networkLibrary, languages),
|
||||||
updateNetworkStates(),
|
updateNetworkStates(),
|
||||||
updateLanguageItemsForDialog(),
|
updateLanguageItemsForDialog(languages),
|
||||||
requestsAndConnectivtyChangesToLibraryRequests(networkLibrary)
|
requestsAndConnectivtyChangesToLibraryRequests(networkLibrary)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -155,6 +165,7 @@ class ZimManageViewModel @Inject constructor(
|
|||||||
)
|
)
|
||||||
.buffer(3, SECONDS)
|
.buffer(3, SECONDS)
|
||||||
.map(this::downloadIdsWithNoStatusesOverBufferPeriod)
|
.map(this::downloadIdsWithNoStatusesOverBufferPeriod)
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
.subscribe(
|
.subscribe(
|
||||||
{
|
{
|
||||||
downloadDao.delete(*it.toLongArray())
|
downloadDao.delete(*it.toLongArray())
|
||||||
@ -184,13 +195,15 @@ class ZimManageViewModel @Inject constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateLanguageItemsForDialog() = requestLanguagesDialog
|
private fun updateLanguageItemsForDialog(languages: Flowable<List<Language>>) =
|
||||||
.withLatestFrom(languageDao.languages(),
|
requestLanguagesDialog
|
||||||
BiFunction<Unit, List<Language>, List<Language>> { _, languages -> languages })
|
.withLatestFrom(
|
||||||
.subscribe(
|
languages,
|
||||||
languageItems::postValue,
|
BiFunction<Unit, List<Language>, List<Language>> { _, languages -> languages })
|
||||||
Throwable::printStackTrace
|
.subscribe(
|
||||||
)
|
languageItems::postValue,
|
||||||
|
Throwable::printStackTrace
|
||||||
|
)
|
||||||
|
|
||||||
private fun updateNetworkStates() =
|
private fun updateNetworkStates() =
|
||||||
connectivityBroadcastReceiver.networkStates.subscribe(
|
connectivityBroadcastReceiver.networkStates.subscribe(
|
||||||
@ -200,11 +213,12 @@ class ZimManageViewModel @Inject constructor(
|
|||||||
private fun updateLibraryItems(
|
private fun updateLibraryItems(
|
||||||
booksFromDao: Flowable<List<BookOnDisk>>,
|
booksFromDao: Flowable<List<BookOnDisk>>,
|
||||||
downloads: Flowable<List<DownloadModel>>,
|
downloads: Flowable<List<DownloadModel>>,
|
||||||
library: Flowable<LibraryNetworkEntity>
|
library: Flowable<LibraryNetworkEntity>,
|
||||||
|
languages: Flowable<List<Language>>
|
||||||
) = Flowable.combineLatest(
|
) = Flowable.combineLatest(
|
||||||
booksFromDao,
|
booksFromDao,
|
||||||
downloads,
|
downloads,
|
||||||
languageDao.languages().filter { it.isNotEmpty() },
|
languages.filter { it.isNotEmpty() },
|
||||||
library,
|
library,
|
||||||
requestFiltering
|
requestFiltering
|
||||||
.doOnNext { libraryListIsRefreshing.postValue(true) }
|
.doOnNext { libraryListIsRefreshing.postValue(true) }
|
||||||
@ -221,15 +235,17 @@ class ZimManageViewModel @Inject constructor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
private fun updateLanguagesInDao(
|
private fun updateLanguagesInDao(
|
||||||
library: Flowable<LibraryNetworkEntity>
|
library: Flowable<LibraryNetworkEntity>,
|
||||||
|
languages: Flowable<List<Language>>
|
||||||
) = library
|
) = library
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.map { it.books }
|
.map { it.books }
|
||||||
.withLatestFrom(
|
.withLatestFrom(
|
||||||
languageDao.languages(),
|
languages,
|
||||||
BiFunction(this::combineToLanguageList)
|
BiFunction(this::combineToLanguageList)
|
||||||
)
|
)
|
||||||
.map { it.sortedBy(Language::language) }
|
.map { it.sortedBy(Language::language) }
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
.subscribe(
|
.subscribe(
|
||||||
languageDao::insert,
|
languageDao::insert,
|
||||||
Throwable::printStackTrace
|
Throwable::printStackTrace
|
||||||
@ -278,11 +294,7 @@ class ZimManageViewModel @Inject constructor(
|
|||||||
|
|
||||||
private fun defaultLanguage() =
|
private fun defaultLanguage() =
|
||||||
listOf(
|
listOf(
|
||||||
Language(
|
defaultLanguageProvider.provide()
|
||||||
context.resources.configuration.locale.isO3Language,
|
|
||||||
true,
|
|
||||||
1
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun languageIsActive(
|
private fun languageIsActive(
|
||||||
@ -411,7 +423,8 @@ class ZimManageViewModel @Inject constructor(
|
|||||||
.filter { it.isNotEmpty() }
|
.filter { it.isNotEmpty() }
|
||||||
.subscribe(
|
.subscribe(
|
||||||
{
|
{
|
||||||
bookDao.insert(it.map { downloadStatus -> downloadStatus.toBookOnDisk() })
|
bookDao.insert(
|
||||||
|
it.map { downloadStatus -> downloadStatus.toBookOnDisk(uriToFileConverter) })
|
||||||
downloadDao.delete(
|
downloadDao.delete(
|
||||||
*it.map { status -> status.downloadId }.toLongArray()
|
*it.map { status -> status.downloadId }.toLongArray()
|
||||||
)
|
)
|
||||||
|
@ -18,7 +18,7 @@ class StorageObserver @Inject constructor(
|
|||||||
private val downloadDao: NewDownloadDao
|
private val downloadDao: NewDownloadDao
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val _booksOnFileSystem = PublishProcessor.create<Collection<BookOnDisk>>()
|
private val _booksOnFileSystem = PublishProcessor.create<List<BookOnDisk>>()
|
||||||
val booksOnFileSystem = _booksOnFileSystem.distinctUntilChanged()
|
val booksOnFileSystem = _booksOnFileSystem.distinctUntilChanged()
|
||||||
.doOnSubscribe {
|
.doOnSubscribe {
|
||||||
downloadDao.downloads()
|
downloadDao.downloads()
|
||||||
@ -37,7 +37,7 @@ class StorageObserver @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onScanCompleted() {
|
override fun onScanCompleted() {
|
||||||
_booksOnFileSystem.onNext(foundBooks)
|
_booksOnFileSystem.onNext(foundBooks.toList())
|
||||||
|
|
||||||
}
|
}
|
||||||
}).scan(sharedPreferenceUtil.prefStorage)
|
}).scan(sharedPreferenceUtil.prefStorage)
|
||||||
|
@ -2,22 +2,32 @@ package org.kiwix.kiwixmobile.zim_manager.library_view.adapter
|
|||||||
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class Language constructor(
|
data class Language constructor(
|
||||||
locale: Locale,
|
|
||||||
var active: Boolean,
|
var active: Boolean,
|
||||||
var occurencesOfLanguage: Int,
|
var occurencesOfLanguage: Int,
|
||||||
var language: String = locale.displayLanguage,
|
var language: String,
|
||||||
var languageLocalized: String = locale.getDisplayLanguage(locale),
|
var languageLocalized: String,
|
||||||
var languageCode: String = locale.isO3Language,
|
var languageCode: String,
|
||||||
var languageCodeISO2: String = locale.language
|
var languageCodeISO2: String
|
||||||
) {
|
) {
|
||||||
|
constructor(
|
||||||
|
locale: Locale,
|
||||||
|
active: Boolean,
|
||||||
|
occurrencesOfLanguage: Int
|
||||||
|
) : this(
|
||||||
|
active,
|
||||||
|
occurrencesOfLanguage,
|
||||||
|
locale.displayLanguage,
|
||||||
|
locale.getDisplayLanguage(locale),
|
||||||
|
locale.isO3Language,
|
||||||
|
locale.language
|
||||||
|
)
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
languageCode: String,
|
languageCode: String,
|
||||||
active: Boolean,
|
active: Boolean,
|
||||||
occurrencesOfLanguage: Int
|
occurrencesOfLanguage: Int
|
||||||
) : this(Locale(languageCode), active, occurrencesOfLanguage) {
|
) : this(Locale(languageCode), active, occurrencesOfLanguage)
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
return (other as Language).language == language && other.active == active
|
return (other as Language).language == language && other.active == active
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Kiwix Android
|
||||||
|
* Copyright (C) 2018 Kiwix <android.kiwix.org>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kiwix.kiwixmobile
|
||||||
|
|
||||||
|
import android.arch.core.executor.ArchTaskExecutor
|
||||||
|
import android.arch.core.executor.TaskExecutor
|
||||||
|
import org.junit.jupiter.api.extension.AfterEachCallback
|
||||||
|
import org.junit.jupiter.api.extension.BeforeEachCallback
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext
|
||||||
|
|
||||||
|
class InstantExecutorExtension : BeforeEachCallback, AfterEachCallback {
|
||||||
|
|
||||||
|
override fun beforeEach(context: ExtensionContext?) {
|
||||||
|
ArchTaskExecutor.getInstance()
|
||||||
|
.setDelegate(object : TaskExecutor() {
|
||||||
|
override fun executeOnDiskIO(runnable: Runnable) = runnable.run()
|
||||||
|
|
||||||
|
override fun postToMainThread(runnable: Runnable) = runnable.run()
|
||||||
|
|
||||||
|
override fun isMainThread(): Boolean = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun afterEach(context: ExtensionContext?) {
|
||||||
|
ArchTaskExecutor.getInstance().setDelegate(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,503 @@
|
|||||||
|
/*
|
||||||
|
* Kiwix Android
|
||||||
|
* Copyright (C) 2018 Kiwix <android.kiwix.org>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kiwix.kiwixmobile.zim_manager
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import com.jraska.livedata.test
|
||||||
|
import io.mockk.clearMocks
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import io.reactivex.Scheduler
|
||||||
|
import io.reactivex.Single
|
||||||
|
import io.reactivex.android.plugins.RxAndroidPlugins
|
||||||
|
import io.reactivex.plugins.RxJavaPlugins
|
||||||
|
import io.reactivex.processors.PublishProcessor
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import io.reactivex.schedulers.TestScheduler
|
||||||
|
import org.junit.jupiter.api.AfterAll
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.Nested
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
|
import org.kiwix.kiwixmobile.InstantExecutorExtension
|
||||||
|
import org.kiwix.kiwixmobile.R
|
||||||
|
import org.kiwix.kiwixmobile.database.newdb.dao.NewBookDao
|
||||||
|
import org.kiwix.kiwixmobile.database.newdb.dao.NewDownloadDao
|
||||||
|
import org.kiwix.kiwixmobile.database.newdb.dao.NewLanguagesDao
|
||||||
|
import org.kiwix.kiwixmobile.downloader.Downloader
|
||||||
|
import org.kiwix.kiwixmobile.downloader.model.BookOnDisk
|
||||||
|
import org.kiwix.kiwixmobile.downloader.model.DownloadItem
|
||||||
|
import org.kiwix.kiwixmobile.downloader.model.DownloadModel
|
||||||
|
import org.kiwix.kiwixmobile.downloader.model.DownloadState
|
||||||
|
import org.kiwix.kiwixmobile.downloader.model.DownloadStatus
|
||||||
|
import org.kiwix.kiwixmobile.downloader.model.UriToFileConverter
|
||||||
|
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity
|
||||||
|
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
|
||||||
|
import org.kiwix.kiwixmobile.network.KiwixService
|
||||||
|
import org.kiwix.kiwixmobile.utils.BookUtils
|
||||||
|
import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState
|
||||||
|
import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.CanWrite4GbFile
|
||||||
|
import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.CannotWrite4GbFile
|
||||||
|
import org.kiwix.kiwixmobile.zim_manager.NetworkState.CONNECTED
|
||||||
|
import org.kiwix.kiwixmobile.zim_manager.NetworkState.NOT_CONNECTED
|
||||||
|
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.StorageObserver
|
||||||
|
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.Language
|
||||||
|
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem
|
||||||
|
import java.io.File
|
||||||
|
import java.util.LinkedList
|
||||||
|
import java.util.concurrent.TimeUnit.MILLISECONDS
|
||||||
|
import java.util.concurrent.TimeUnit.SECONDS
|
||||||
|
|
||||||
|
@ExtendWith(InstantExecutorExtension::class)
|
||||||
|
class ZimManageViewModelTest {
|
||||||
|
|
||||||
|
private val newDownloadDao: NewDownloadDao = mockk()
|
||||||
|
private val newBookDao: NewBookDao = mockk()
|
||||||
|
private val newLanguagesDao: NewLanguagesDao = mockk()
|
||||||
|
private val downloader: Downloader = mockk()
|
||||||
|
private val storageObserver: StorageObserver = mockk()
|
||||||
|
private val kiwixService: KiwixService = mockk()
|
||||||
|
private val application: Application = mockk()
|
||||||
|
private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver = mockk()
|
||||||
|
private val bookUtils: BookUtils = mockk()
|
||||||
|
private val fat32Checker: Fat32Checker = mockk()
|
||||||
|
private val uriToFileConverter: UriToFileConverter = mockk()
|
||||||
|
lateinit var viewModel: ZimManageViewModel
|
||||||
|
|
||||||
|
private val downloads: PublishProcessor<List<DownloadModel>> = PublishProcessor.create()
|
||||||
|
private val booksOnFileSystem: PublishProcessor<List<BookOnDisk>> = PublishProcessor.create()
|
||||||
|
private val books: PublishProcessor<List<BookOnDisk>> = PublishProcessor.create()
|
||||||
|
private val languages: PublishProcessor<List<Language>> = PublishProcessor.create()
|
||||||
|
private val fileSystemStates: PublishProcessor<FileSystemState> = PublishProcessor.create()
|
||||||
|
private val networkStates: PublishProcessor<NetworkState> = PublishProcessor.create()
|
||||||
|
|
||||||
|
private val testScheduler = TestScheduler()
|
||||||
|
|
||||||
|
init {
|
||||||
|
setScheduler(testScheduler)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setScheduler(replacementScheduler: Scheduler) {
|
||||||
|
RxJavaPlugins.setIoSchedulerHandler { scheduler -> replacementScheduler }
|
||||||
|
RxJavaPlugins.setComputationSchedulerHandler { scheduler -> replacementScheduler }
|
||||||
|
RxJavaPlugins.setNewThreadSchedulerHandler { scheduler -> replacementScheduler }
|
||||||
|
RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler -> Schedulers.trampoline() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
fun teardown() {
|
||||||
|
RxJavaPlugins.reset()
|
||||||
|
RxAndroidPlugins.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private val defaultLanguageProvider: DefaultLanguageProvider = mockk()
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun init() {
|
||||||
|
clearMocks(
|
||||||
|
newDownloadDao, newBookDao, newLanguagesDao, downloader,
|
||||||
|
storageObserver, kiwixService, application, connectivityBroadcastReceiver, bookUtils,
|
||||||
|
fat32Checker, uriToFileConverter, defaultLanguageProvider
|
||||||
|
)
|
||||||
|
every { connectivityBroadcastReceiver.action } returns "test"
|
||||||
|
every { newDownloadDao.downloads() } returns downloads
|
||||||
|
every { newBookDao.books() } returns books
|
||||||
|
every { storageObserver.booksOnFileSystem } returns booksOnFileSystem
|
||||||
|
every { newLanguagesDao.languages() } returns languages
|
||||||
|
every { fat32Checker.fileSystemStates } returns fileSystemStates
|
||||||
|
every { connectivityBroadcastReceiver.networkStates } returns networkStates
|
||||||
|
every { application.registerReceiver(any(), any()) } returns mockk()
|
||||||
|
viewModel = ZimManageViewModel(
|
||||||
|
newDownloadDao, newBookDao, newLanguagesDao, downloader,
|
||||||
|
storageObserver, kiwixService, application, connectivityBroadcastReceiver, bookUtils,
|
||||||
|
fat32Checker, uriToFileConverter, defaultLanguageProvider
|
||||||
|
)
|
||||||
|
testScheduler.triggerActions()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class Context {
|
||||||
|
@Test
|
||||||
|
fun `registers broadcastReceiver in init`() {
|
||||||
|
verify {
|
||||||
|
application.registerReceiver(connectivityBroadcastReceiver, any())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `unregisters broadcastReceiver in onCleared`() {
|
||||||
|
every { application.unregisterReceiver(any()) } returns mockk()
|
||||||
|
viewModel.onClearedExposed()
|
||||||
|
verify {
|
||||||
|
application.unregisterReceiver(connectivityBroadcastReceiver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class Downloads {
|
||||||
|
@Test
|
||||||
|
fun `on emission from database query and render downloads`() {
|
||||||
|
val expectedStatus = DownloadStatus()
|
||||||
|
expectStatusWith(listOf(expectedStatus))
|
||||||
|
viewModel.downloadItems
|
||||||
|
.test()
|
||||||
|
.assertValue(listOf(DownloadItem(expectedStatus)))
|
||||||
|
.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `on emission of successful status create a book and delete the download`() {
|
||||||
|
every { uriToFileConverter.convert(any()) } returns File("test")
|
||||||
|
val expectedStatus = DownloadStatus(
|
||||||
|
downloadId = 10L,
|
||||||
|
state = DownloadState.Successful
|
||||||
|
)
|
||||||
|
expectStatusWith(listOf(expectedStatus))
|
||||||
|
val element = expectedStatus.toBookOnDisk(uriToFileConverter)
|
||||||
|
verify {
|
||||||
|
newBookDao.insert(listOf(element))
|
||||||
|
newDownloadDao.delete(10L)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `if statuses don't have a matching Id for download in db over 3 secs then delete`() {
|
||||||
|
expectStatusWith(
|
||||||
|
listOf(DownloadStatus(downloadId = 1)),
|
||||||
|
listOf(DownloadModel(downloadId = 1), DownloadModel(downloadId = 3))
|
||||||
|
)
|
||||||
|
testScheduler.advanceTimeBy(3, SECONDS)
|
||||||
|
testScheduler.triggerActions()
|
||||||
|
verify {
|
||||||
|
newDownloadDao.delete(3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `if statuses do have a matching Id for download in db over 3 secs then don't delete`() {
|
||||||
|
expectStatusWith(
|
||||||
|
listOf(DownloadStatus(downloadId = 1)),
|
||||||
|
listOf(DownloadModel(downloadId = 1))
|
||||||
|
)
|
||||||
|
testScheduler.advanceTimeBy(3, SECONDS)
|
||||||
|
testScheduler.triggerActions()
|
||||||
|
verify(exactly = 0) {
|
||||||
|
newDownloadDao.delete(any())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun expectStatusWith(
|
||||||
|
expectedStatuses: List<DownloadStatus>,
|
||||||
|
expectedDownloads: List<DownloadModel> = listOf(
|
||||||
|
DownloadModel()
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
val downloadList = expectedDownloads
|
||||||
|
every { downloader.queryStatus(downloadList) } returns expectedStatuses
|
||||||
|
downloads.offer(downloadList)
|
||||||
|
testScheduler.triggerActions()
|
||||||
|
testScheduler.advanceTimeBy(1, SECONDS)
|
||||||
|
testScheduler.triggerActions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class Books {
|
||||||
|
@Test
|
||||||
|
fun `emissions from DB sorted by title and observed`() {
|
||||||
|
books.onNext(
|
||||||
|
listOf(
|
||||||
|
BookOnDisk().apply { book.title = "z" },
|
||||||
|
BookOnDisk().apply { book.title = "a" }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
testScheduler.triggerActions()
|
||||||
|
viewModel.bookItems.test()
|
||||||
|
.assertValue(
|
||||||
|
listOf(
|
||||||
|
BookOnDisk().apply { book.title = "a" },
|
||||||
|
BookOnDisk().apply { book.title = "z" }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
// @Disabled("WithLatestFrom is not calling BiFunction removeBooksAlreadyInDao - revisit")
|
||||||
|
fun `books found on filesystem are filtered by books already in db`() {
|
||||||
|
val expectedBook = BookOnDisk().apply { book.id = "1" }
|
||||||
|
val bookToRemove = BookOnDisk().apply { book.id = "2" }
|
||||||
|
testScheduler.triggerActions()
|
||||||
|
viewModel.requestFileSystemCheck.onNext(Unit)
|
||||||
|
testScheduler.triggerActions()
|
||||||
|
books.onNext(listOf(bookToRemove))
|
||||||
|
testScheduler.triggerActions()
|
||||||
|
booksOnFileSystem.onNext(
|
||||||
|
listOf(
|
||||||
|
expectedBook,
|
||||||
|
expectedBook,
|
||||||
|
bookToRemove
|
||||||
|
)
|
||||||
|
)
|
||||||
|
testScheduler.triggerActions()
|
||||||
|
verify {
|
||||||
|
newBookDao.insert(listOf(expectedBook))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class Lanuages {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `a network request with no result and an empty language db triggers an activation of the default locale`() {
|
||||||
|
val expectedLanguage = Language(
|
||||||
|
active = true,
|
||||||
|
occurencesOfLanguage = 1,
|
||||||
|
language = "eng",
|
||||||
|
languageLocalized = "englocal",
|
||||||
|
languageCode = "ENG",
|
||||||
|
languageCodeISO2 = "en"
|
||||||
|
)
|
||||||
|
expectNetworkDbAndDefault(
|
||||||
|
listOf(),
|
||||||
|
listOf(),
|
||||||
|
expectedLanguage
|
||||||
|
)
|
||||||
|
verify { newLanguagesDao.insert(listOf(expectedLanguage)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `a network request with no result and a non empty language db does not trigger anything`() {
|
||||||
|
expectNetworkDbAndDefault(
|
||||||
|
listOf(),
|
||||||
|
listOf(
|
||||||
|
Language(
|
||||||
|
active = true,
|
||||||
|
occurencesOfLanguage = 1,
|
||||||
|
language = "eng",
|
||||||
|
languageLocalized = "englocal",
|
||||||
|
languageCode = "ENG",
|
||||||
|
languageCodeISO2 = "en"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Language(true, 1, "", "", "", "")
|
||||||
|
)
|
||||||
|
verify(exactly = 0) { newLanguagesDao.insert(any()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `a network request with a result and an empty language db triggers an activation of the default locale with the result of the web request`() {
|
||||||
|
val defaultLanguage = Language(
|
||||||
|
active = true,
|
||||||
|
occurencesOfLanguage = 1,
|
||||||
|
language = "English",
|
||||||
|
languageLocalized = "English",
|
||||||
|
languageCode = "eng",
|
||||||
|
languageCodeISO2 = "eng"
|
||||||
|
)
|
||||||
|
expectNetworkDbAndDefault(
|
||||||
|
listOf(
|
||||||
|
Book().apply { language = "eng" },
|
||||||
|
Book().apply { language = "eng" },
|
||||||
|
Book().apply { language = "fra" }
|
||||||
|
),
|
||||||
|
listOf(),
|
||||||
|
defaultLanguage)
|
||||||
|
verify {
|
||||||
|
newLanguagesDao.insert(
|
||||||
|
listOf(
|
||||||
|
defaultLanguage.copy(occurencesOfLanguage = 2),
|
||||||
|
Language(
|
||||||
|
active = false,
|
||||||
|
occurencesOfLanguage = 1,
|
||||||
|
language = "fra",
|
||||||
|
languageLocalized = "",
|
||||||
|
languageCode = "",
|
||||||
|
languageCodeISO2 = ""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `a network request with a result and a non empty language db triggers an activation of the result of the web request with the db`() {
|
||||||
|
val dbLanguage = Language(
|
||||||
|
active = true,
|
||||||
|
occurencesOfLanguage = 1,
|
||||||
|
language = "English",
|
||||||
|
languageLocalized = "English",
|
||||||
|
languageCode = "eng",
|
||||||
|
languageCodeISO2 = "eng"
|
||||||
|
)
|
||||||
|
expectNetworkDbAndDefault(
|
||||||
|
listOf(
|
||||||
|
Book().apply { language = "eng" },
|
||||||
|
Book().apply { language = "eng" },
|
||||||
|
Book().apply { language = "fra" }
|
||||||
|
),
|
||||||
|
listOf(dbLanguage),
|
||||||
|
Language(true, 1, "", "", "", "")
|
||||||
|
)
|
||||||
|
verify {
|
||||||
|
newLanguagesDao.insert(
|
||||||
|
listOf(
|
||||||
|
dbLanguage.copy(occurencesOfLanguage = 2),
|
||||||
|
Language(
|
||||||
|
active = false,
|
||||||
|
occurencesOfLanguage = 1,
|
||||||
|
language = "fra",
|
||||||
|
languageLocalized = "",
|
||||||
|
languageCode = "",
|
||||||
|
languageCodeISO2 = ""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun expectNetworkDbAndDefault(
|
||||||
|
networkBooks: List<Book>,
|
||||||
|
dbBooks: List<Language>,
|
||||||
|
defaultLanguage: Language
|
||||||
|
) {
|
||||||
|
every { kiwixService.library } returns Single.just(
|
||||||
|
LibraryNetworkEntity().apply {
|
||||||
|
book = LinkedList(networkBooks)
|
||||||
|
})
|
||||||
|
val defaultLanguage = defaultLanguage
|
||||||
|
every { defaultLanguageProvider.provide() } returns defaultLanguage
|
||||||
|
languages.onNext(dbBooks)
|
||||||
|
testScheduler.triggerActions()
|
||||||
|
networkStates.onNext(CONNECTED)
|
||||||
|
testScheduler.triggerActions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `network states observed`() {
|
||||||
|
networkStates.offer(NOT_CONNECTED)
|
||||||
|
viewModel.networkStates.test()
|
||||||
|
.assertValue(NOT_CONNECTED)
|
||||||
|
.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `language items for dialog observed`() {
|
||||||
|
val expectedValue = listOf(Language(true, 1, "e", "e", "e", "e"))
|
||||||
|
testScheduler.triggerActions()
|
||||||
|
languages.onNext(expectedValue)
|
||||||
|
testScheduler.triggerActions()
|
||||||
|
viewModel.requestLanguagesDialog.onNext(Unit)
|
||||||
|
testScheduler.triggerActions()
|
||||||
|
viewModel.languageItems.test()
|
||||||
|
.assertValue(expectedValue)
|
||||||
|
.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `library update removes from sources`() {
|
||||||
|
every { application.getString(R.string.your_languages) } returns "1"
|
||||||
|
every { application.getString(R.string.other_languages) } returns "2"
|
||||||
|
val bookAlreadyOnDisk = Book().apply {
|
||||||
|
id = "0"
|
||||||
|
url = ""
|
||||||
|
}
|
||||||
|
val bookDownloading = Book().apply {
|
||||||
|
id = "1"
|
||||||
|
url = ""
|
||||||
|
}
|
||||||
|
val bookWithStackExchange = Book().apply {
|
||||||
|
id = "2"
|
||||||
|
url = "blahblah/stack_exchange/"
|
||||||
|
}
|
||||||
|
val bookWithActiveLanguage = Book().apply {
|
||||||
|
id = "3"
|
||||||
|
language = "activeLanguage"
|
||||||
|
url = ""
|
||||||
|
}
|
||||||
|
val bookWithInactiveLanguage = Book().apply {
|
||||||
|
id = "3"
|
||||||
|
language = "inactiveLanguage"
|
||||||
|
url = ""
|
||||||
|
}
|
||||||
|
every { kiwixService.library } returns Single.just(
|
||||||
|
LibraryNetworkEntity().apply {
|
||||||
|
book = LinkedList(
|
||||||
|
listOf(
|
||||||
|
bookAlreadyOnDisk,
|
||||||
|
bookDownloading,
|
||||||
|
bookWithStackExchange,
|
||||||
|
bookWithActiveLanguage,
|
||||||
|
bookWithInactiveLanguage
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
networkStates.onNext(CONNECTED)
|
||||||
|
downloads.onNext(listOf(DownloadModel(book = bookDownloading)))
|
||||||
|
books.onNext(listOf(BookOnDisk(book = bookAlreadyOnDisk)))
|
||||||
|
languages.onNext(
|
||||||
|
listOf(
|
||||||
|
Language(true, 1, "", "", "activeLanguage", ""),
|
||||||
|
Language(false, 1, "", "", "inactiveLanguage", "")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
fileSystemStates.onNext(CanWrite4GbFile)
|
||||||
|
testScheduler.advanceTimeBy(500, MILLISECONDS)
|
||||||
|
testScheduler.triggerActions()
|
||||||
|
viewModel.libraryItems.test()
|
||||||
|
.assertValue(
|
||||||
|
listOf(
|
||||||
|
LibraryListItem.DividerItem(Long.MAX_VALUE, "1"),
|
||||||
|
LibraryListItem.BookItem(bookWithActiveLanguage),
|
||||||
|
LibraryListItem.DividerItem(Long.MIN_VALUE, "2"),
|
||||||
|
LibraryListItem.BookItem(bookWithInactiveLanguage)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `library filters out files over 4GB if file system state says to`() {
|
||||||
|
val bookOver4Gb = Book().apply {
|
||||||
|
id = "0"
|
||||||
|
url = ""
|
||||||
|
size = "${Fat32Checker.FOUR_GIGABYTES_IN_KILOBYTES + 1}"
|
||||||
|
}
|
||||||
|
every { kiwixService.library } returns Single.just(
|
||||||
|
LibraryNetworkEntity().apply {
|
||||||
|
book = LinkedList(listOf(bookOver4Gb))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
networkStates.onNext(CONNECTED)
|
||||||
|
downloads.onNext(listOf())
|
||||||
|
books.onNext(listOf())
|
||||||
|
languages.onNext(listOf(Language(true, 1, "", "", "activeLanguage", "")))
|
||||||
|
fileSystemStates.onNext(CannotWrite4GbFile)
|
||||||
|
testScheduler.advanceTimeBy(500, MILLISECONDS)
|
||||||
|
testScheduler.triggerActions()
|
||||||
|
viewModel.libraryItems.test()
|
||||||
|
.assertValue(listOf())
|
||||||
|
.dispose()
|
||||||
|
}
|
||||||
|
}
|
1
app/src/test/resources/io/mockk/settings.properties
Normal file
1
app/src/test/resources/io/mockk/settings.properties
Normal file
@ -0,0 +1 @@
|
|||||||
|
relaxUnitFun=true
|
1
app/src/test/resources/junit-platform.properties
Normal file
1
app/src/test/resources/junit-platform.properties
Normal file
@ -0,0 +1 @@
|
|||||||
|
junit.jupiter.testinstance.lifecycle.default = per_class
|
Loading…
x
Reference in New Issue
Block a user