From 869c53f6d8aaa048c91959072379f34f3b592ad0 Mon Sep 17 00:00:00 2001 From: SkyD666 Date: Wed, 11 Dec 2024 23:01:11 +0800 Subject: [PATCH] [fix|test] Fix crash caused by unsaveable PictureInPictureParams.Builder; add test code --- app/build.gradle.kts | 6 +- .../java/com/skyd/anivu/RssModule.kt | 126 ++++++++++++++++++ .../anivu/base/mvi/AbstractMviViewModel.kt | 6 +- .../com/skyd/anivu/model/db/dao/ArticleDao.kt | 4 +- .../com/skyd/anivu/model/db/dao/FeedDao.kt | 4 +- .../model/repository/feed/FeedRepository.kt | 5 - .../java/com/skyd/anivu/ui/mpv/pip/PipUtil.kt | 3 +- .../anivu/ui/screen/feed/FeedViewModel.kt | 1 + gradle/libs.versions.toml | 4 + 9 files changed, 145 insertions(+), 14 deletions(-) create mode 100644 app/src/androidTest/java/com/skyd/anivu/RssModule.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 954d2f4a..1d61ba3b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -22,7 +22,7 @@ android { minSdk = 24 targetSdk = 35 versionCode = 24 - versionName = "2.1-beta16" + versionName = "2.1-beta17" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" @@ -183,6 +183,7 @@ dependencies { implementation(libs.androidx.room.runtime) implementation(libs.androidx.room.ktx) implementation(libs.androidx.room.paging) + implementation(libs.androidx.junit.ktx) ksp(libs.androidx.room.compiler) implementation(libs.androidx.work.runtime.ktx) implementation(libs.androidx.datastore.preferences) @@ -237,6 +238,9 @@ dependencies { // debugImplementation("com.squareup.leakcanary:leakcanary-android:2.13") testImplementation(libs.junit) + testImplementation(libs.mockito.core) + testImplementation(libs.mockito.kotlin) + androidTestImplementation(libs.kotlinx.coroutines.test) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) } \ No newline at end of file diff --git a/app/src/androidTest/java/com/skyd/anivu/RssModule.kt b/app/src/androidTest/java/com/skyd/anivu/RssModule.kt new file mode 100644 index 00000000..1f738d94 --- /dev/null +++ b/app/src/androidTest/java/com/skyd/anivu/RssModule.kt @@ -0,0 +1,126 @@ +package com.skyd.anivu + +import android.content.Context +import androidx.paging.PagingConfig +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import com.skyd.anivu.base.mvi.mviViewModelNeedMainThread +import com.skyd.anivu.config.Const +import com.skyd.anivu.model.bean.group.GroupVo +import com.skyd.anivu.model.db.AppDatabase +import com.skyd.anivu.model.db.dao.ArticleDao +import com.skyd.anivu.model.db.dao.FeedDao +import com.skyd.anivu.model.db.dao.GroupDao +import com.skyd.anivu.model.repository.ArticleRepository +import com.skyd.anivu.model.repository.ReadRepository +import com.skyd.anivu.model.repository.RssHelper +import com.skyd.anivu.model.repository.SearchRepository +import com.skyd.anivu.model.repository.feed.FeedRepository +import com.skyd.anivu.model.repository.feed.ReorderGroupRepository +import com.skyd.anivu.model.repository.feed.RequestHeadersRepository +import com.skyd.anivu.util.favicon.FaviconExtractor +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import org.junit.After +import org.junit.Assert.* +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import retrofit2.Retrofit +import java.io.IOException + + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RunWith(AndroidJUnit4::class) +class RssModule { + private val json = Json { + ignoreUnknownKeys = true + explicitNulls = false + } + + private val okHttpClient: OkHttpClient = OkHttpClient.Builder() + .addInterceptor(HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + }) + .build() + + private val retrofit = Retrofit + .Builder() + .baseUrl(Const.BASE_URL) + .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) + .client(okHttpClient) + .build() + + private val faviconExtractor = FaviconExtractor(retrofit) + private val pagingConfig = PagingConfig(pageSize = 20, enablePlaceholders = false) + + private lateinit var db: AppDatabase + private lateinit var groupDao: GroupDao + private lateinit var feedDao: FeedDao + private lateinit var articleDao: ArticleDao + private var rssHelper: RssHelper = RssHelper(okHttpClient, faviconExtractor) + private lateinit var reorderGroupRepository: ReorderGroupRepository + private lateinit var feedRepository: FeedRepository + private lateinit var articleRepository: ArticleRepository + private lateinit var readRepository: ReadRepository + private lateinit var searchRepository: SearchRepository + private lateinit var requestHeadersRepository: RequestHeadersRepository + + + @Test + fun test1RequestGroupAnyList() = runTest { + feedRepository.requestGroupAnyList().first().also { + assertTrue(it[0] is GroupVo.DefaultGroup) + } + } + + @Test + fun test2SetFeed() = runTest { + feedRepository.setFeed( + url = "https://blogs.nvidia.cn/feed/", + groupId = null, + nickname = "Nvidia", + ).first().also { + assertTrue( + it.url == "https://blogs.nvidia.cn/feed/" && + it.groupId == null && it.nickname == "Nvidia" + ) + } + } + + @Before + fun init() { + mviViewModelNeedMainThread = false + + val context = ApplicationProvider.getApplicationContext() + db = AppDatabase.getInstance(context) + groupDao = db.groupDao() + feedDao = db.feedDao() + articleDao = db.articleDao() + + reorderGroupRepository = ReorderGroupRepository(groupDao) + feedRepository = + FeedRepository(groupDao, feedDao, articleDao, reorderGroupRepository, rssHelper) + articleRepository = ArticleRepository(feedDao, articleDao, rssHelper, pagingConfig) + searchRepository = SearchRepository(feedDao, articleDao, pagingConfig) + requestHeadersRepository = RequestHeadersRepository(feedDao) + } + + @After + @Throws(IOException::class) + fun destroy() { +// db.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/skyd/anivu/base/mvi/AbstractMviViewModel.kt b/app/src/main/java/com/skyd/anivu/base/mvi/AbstractMviViewModel.kt index eb38dca8..5fa0083d 100644 --- a/app/src/main/java/com/skyd/anivu/base/mvi/AbstractMviViewModel.kt +++ b/app/src/main/java/com/skyd/anivu/base/mvi/AbstractMviViewModel.kt @@ -28,8 +28,10 @@ import java.util.concurrent.atomic.AtomicInteger import kotlin.LazyThreadSafetyMode.PUBLICATION import kotlin.coroutines.ContinuationInterceptor +var mviViewModelNeedMainThread: Boolean = BuildConfig.DEBUG + private fun debugCheckMainThread() { - if (BuildConfig.DEBUG) { + if (mviViewModelNeedMainThread) { check(Looper.getMainLooper() === Looper.myLooper()) { "Expected to be called on the main thread but was " + Thread.currentThread().name } @@ -37,7 +39,7 @@ private fun debugCheckMainThread() { } suspend fun debugCheckImmediateMainDispatcher() { - if (BuildConfig.DEBUG) { + if (mviViewModelNeedMainThread) { val interceptor = currentCoroutineContext()[ContinuationInterceptor] Log.d( "###", diff --git a/app/src/main/java/com/skyd/anivu/model/db/dao/ArticleDao.kt b/app/src/main/java/com/skyd/anivu/model/db/dao/ArticleDao.kt index d936ea71..e695a7d4 100644 --- a/app/src/main/java/com/skyd/anivu/model/db/dao/ArticleDao.kt +++ b/app/src/main/java/com/skyd/anivu/model/db/dao/ArticleDao.kt @@ -11,13 +11,13 @@ import androidx.room.RewriteQueriesToDropUnusedColumns import androidx.room.Transaction import androidx.sqlite.db.SupportSQLiteQuery import com.skyd.anivu.appContext -import com.skyd.anivu.model.bean.feed.FEED_TABLE_NAME -import com.skyd.anivu.model.bean.feed.FeedBean import com.skyd.anivu.model.bean.article.ARTICLE_TABLE_NAME import com.skyd.anivu.model.bean.article.ArticleBean import com.skyd.anivu.model.bean.article.ArticleWithEnclosureBean import com.skyd.anivu.model.bean.article.ArticleWithFeed import com.skyd.anivu.model.bean.article.EnclosureBean +import com.skyd.anivu.model.bean.feed.FEED_TABLE_NAME +import com.skyd.anivu.model.bean.feed.FeedBean import com.skyd.anivu.ui.notification.ArticleNotificationManager import dagger.hilt.EntryPoint import dagger.hilt.InstallIn diff --git a/app/src/main/java/com/skyd/anivu/model/db/dao/FeedDao.kt b/app/src/main/java/com/skyd/anivu/model/db/dao/FeedDao.kt index 7c2c12c2..753a9b27 100644 --- a/app/src/main/java/com/skyd/anivu/model/db/dao/FeedDao.kt +++ b/app/src/main/java/com/skyd/anivu/model/db/dao/FeedDao.kt @@ -11,6 +11,7 @@ import androidx.room.Transaction import androidx.room.Update import androidx.sqlite.db.SupportSQLiteQuery import com.skyd.anivu.appContext +import com.skyd.anivu.model.bean.article.ArticleBean import com.skyd.anivu.model.bean.feed.FEED_TABLE_NAME import com.skyd.anivu.model.bean.feed.FEED_VIEW_NAME import com.skyd.anivu.model.bean.feed.FeedBean @@ -18,7 +19,6 @@ import com.skyd.anivu.model.bean.feed.FeedViewBean import com.skyd.anivu.model.bean.feed.FeedWithArticleBean import com.skyd.anivu.model.bean.group.GROUP_TABLE_NAME import com.skyd.anivu.model.bean.group.GroupBean -import com.skyd.anivu.model.bean.article.ArticleBean import dagger.hilt.EntryPoint import dagger.hilt.InstallIn import dagger.hilt.android.EntryPointAccessors @@ -56,7 +56,7 @@ interface FeedDao { val articleId = articleWithEnclosure.article.articleId // Add ArticleWithEnclosure - if (articleWithEnclosure.article.feedUrl != feedUrl) { + return@map if (articleWithEnclosure.article.feedUrl != feedUrl) { articleWithEnclosure.copy( article = articleWithEnclosure.article.copy(feedUrl = feedUrl), enclosures = articleWithEnclosure.enclosures.map { diff --git a/app/src/main/java/com/skyd/anivu/model/repository/feed/FeedRepository.kt b/app/src/main/java/com/skyd/anivu/model/repository/feed/FeedRepository.kt index 75145a5b..f75d9a23 100644 --- a/app/src/main/java/com/skyd/anivu/model/repository/feed/FeedRepository.kt +++ b/app/src/main/java/com/skyd/anivu/model/repository/feed/FeedRepository.kt @@ -82,11 +82,6 @@ class FeedRepository @Inject constructor( .flowOn(Dispatchers.IO) } - suspend fun setFeed(feedBean: FeedBean): Flow { - return flowOf(feedDao.setFeed(feedBean)) - .flowOn(Dispatchers.IO) - } - suspend fun setFeed( url: String, groupId: String?, diff --git a/app/src/main/java/com/skyd/anivu/ui/mpv/pip/PipUtil.kt b/app/src/main/java/com/skyd/anivu/ui/mpv/pip/PipUtil.kt index c35eec75..f448c766 100644 --- a/app/src/main/java/com/skyd/anivu/ui/mpv/pip/PipUtil.kt +++ b/app/src/main/java/com/skyd/anivu/ui/mpv/pip/PipUtil.kt @@ -16,7 +16,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.toAndroidRectF @@ -60,7 +59,7 @@ internal fun Modifier.pipParams( shouldEnterPipMode: Boolean, playState: PlayState, ): Modifier = run { - var builder by rememberSaveable { mutableStateOf(null) } + var builder by remember { mutableStateOf(null) } val currentPlayState by rememberUpdatedState(playState) val setActionsAndApplyBuilder: (PictureInPictureParams.Builder) -> Unit = remember { { builder -> diff --git a/app/src/main/java/com/skyd/anivu/ui/screen/feed/FeedViewModel.kt b/app/src/main/java/com/skyd/anivu/ui/screen/feed/FeedViewModel.kt index b68e478d..d65c9819 100644 --- a/app/src/main/java/com/skyd/anivu/ui/screen/feed/FeedViewModel.kt +++ b/app/src/main/java/com/skyd/anivu/ui/screen/feed/FeedViewModel.kt @@ -1,5 +1,6 @@ package com.skyd.anivu.ui.screen.feed +import android.util.Log import com.skyd.anivu.base.mvi.AbstractMviViewModel import com.skyd.anivu.ext.catchMap import com.skyd.anivu.ext.startWith diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index de0bc4ed..c7ac8b91 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -80,9 +80,13 @@ libtorrent4j-x8664 = { module = "org.libtorrent4j:libtorrent4j-android-x86_64", junit = { module = "junit:junit", version = "4.13.2" } androidx-junit = { module = "androidx.test.ext:junit", version = "1.2.1" } +androidx-junit-ktx = { group = "androidx.test.ext", name = "junit-ktx", version = "1.2.1" } +mockito-core = { module = "org.mockito:mockito-core", version = "5.14.2" } +mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version = "5.4.0" } androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version = "3.6.1" } androidx-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version = "2.3.0" } androidx-benchmark-macro-junit4 = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version = "1.3.3" } +kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version = "1.9.0" } [plugins] android-application = { id = "com.android.application", version = "8.7.3" }