V1.0.46 oss (#253)
* add a UI trigger for demo annotations * Implement internal link navigation * Simplify bottom padding logic in `EpubReaderScreen` for vertical scroll mode * Add support for Calibre metadata * Implement advanced library management with Room-backed collections, tags, and smart rules. * Display book series information in book details and remove tags from home screen * Implement zoom reset functionality in PDF viewer * Add a "Pages" thumbnail view to the PDF navigation drawer * Implement jump-back navigation in PDF viewer * MainViewModel: disable FolderSyncWorker, add log export, and enhance PDF position logging * Replace Gson with Kotlin Serialization in SmartCollectionEngine * Implement ONNX-based speech bubble detection * Implement speech bubble detection in PDF viewer * Implement "Smart Comic Zoom" feature for PDF manga/comic reading * Implement reader session persistence and restoration in `MainViewModel` * Add tap-to-turn page feature to PDF viewer * Improve book cache management and recovery * Refactor top overlay padding logic in `PdfViewerScreen` * Implement stylus eraser support in PDF reader * Refactor ReaderTextFormatPanel to use ModalBottomSheet and add new formatting controls * Introduce a customizable horizontal margin setting for the EPUB reader * Refine folder sync and metadata handling for better conflict resolution and stability. * Introduce user-adjustable image scaling for the EPUB reader * Use maxWidthPx as default width fallback for blocks * Improve cross-page text selection and header styling in the paginated reader * Refactor speech bubble detection and UI to support segmentation masks and interactive scaling * Improve speech bubble detection masking and rendering quality * Update ONNX speech bubble detector to use `.ort` model and optimize inference * Improve pagination accuracy * Implement hierarchical folder navigation for the library. * Refactor library item layout for improved space efficiency * Add support for browsing and downloading Google Fonts * Persist library landing state and add shelf search functionality * Fix base tts sample. * Move SpeechBubbleDetector to main source set and update ONNX dependency * Implement a "locate" feature and improve synchronization for TTS (Text-to-Speech) playback across EPUB and PDF readers. * Refine TTS synchronization and voice management in the EPUB reader * Adjust OCR checks for OSS flavor and optimize external dictionary intent flags * Add "Locate" button to PDF drawer's pages tab and move the page number to bottom right of thumbnail * fix various crashes * Refactor SpeechBubbleDetector into product flavors * Implement speech bubble detection caching and background prefetching * Implement on-demand download for Bubble Zoom ML model * Add smooth animation for PDF speech bubble expansion * fixes #252 * hide Google Fonts option, in FontsScreen, for offline variant * Bump version to 1.0.46(46)
This commit is contained in:
parent
579b6f25bf
commit
d16bdd70d4
67 changed files with 9724 additions and 1890 deletions
169
app/src/test/java/com/aryan/reader/MainViewModelTest.kt
Normal file
169
app/src/test/java/com/aryan/reader/MainViewModelTest.kt
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
package com.aryan.reader
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Resources
|
||||
import android.util.Log
|
||||
import androidx.work.WorkManager
|
||||
import com.aryan.reader.data.*
|
||||
import com.tom_roush.pdfbox.android.PDFBoxResourceLoader
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.*
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class MainViewModelTest {
|
||||
|
||||
private val testDispatcher = StandardTestDispatcher()
|
||||
|
||||
private lateinit var viewModel: MainViewModel
|
||||
private lateinit var mockApplication: Application
|
||||
private lateinit var mockPrefs: SharedPreferences
|
||||
private lateinit var mockEditor: SharedPreferences.Editor
|
||||
|
||||
private val billingStateFlow = MutableStateFlow(ProUpgradeState())
|
||||
private val customFontsFlow = MutableStateFlow<List<CustomFontEntity>>(emptyList())
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mockkStatic(Log::class)
|
||||
every { Log.isLoggable(any(), any()) } returns false
|
||||
every { Log.d(any(), any()) } returns 0
|
||||
every { Log.i(any(), any()) } returns 0
|
||||
every { Log.e(any(), any(), any()) } returns 0
|
||||
every { Log.w(any(), any<String>()) } returns 0
|
||||
|
||||
Dispatchers.setMain(testDispatcher)
|
||||
|
||||
mockApplication = mockk()
|
||||
mockPrefs = mockk(relaxed = true)
|
||||
mockEditor = mockk(relaxed = true)
|
||||
val mockResources = mockk<Resources>(relaxed = true)
|
||||
|
||||
every { mockApplication.applicationContext } returns mockApplication
|
||||
every { mockApplication.getSharedPreferences(any(), any()) } returns mockPrefs
|
||||
every { mockApplication.resources } returns mockResources
|
||||
every { mockPrefs.edit() } returns mockEditor
|
||||
|
||||
every { mockPrefs.getString(any(), any()) } answers { secondArg() as String? }
|
||||
every { mockPrefs.getBoolean(any(), any()) } answers { secondArg() as Boolean }
|
||||
every { mockPrefs.getInt(any(), any()) } answers { secondArg() as Int }
|
||||
every { mockPrefs.getFloat(any(), any()) } answers { secondArg() as Float }
|
||||
|
||||
mockkStatic(AppDatabase::class)
|
||||
val mockDb = mockk<AppDatabase>(relaxed = true)
|
||||
every { AppDatabase.getDatabase(any()) } returns mockDb
|
||||
|
||||
mockkStatic(WorkManager::class)
|
||||
every { WorkManager.getInstance(any()) } returns mockk(relaxed = true)
|
||||
mockkStatic(PDFBoxResourceLoader::class)
|
||||
every { PDFBoxResourceLoader.init(any()) } just Runs
|
||||
|
||||
mockkConstructor(AuthRepository::class)
|
||||
mockkConstructor(RecentFilesRepository::class)
|
||||
mockkConstructor(BillingClientWrapper::class)
|
||||
mockkConstructor(RemoteConfigRepository::class)
|
||||
mockkConstructor(FirestoreRepository::class)
|
||||
mockkConstructor(FeedbackRepository::class)
|
||||
mockkConstructor(FontsRepository::class)
|
||||
|
||||
every { anyConstructed<BillingClientWrapper>().proUpgradeState } returns billingStateFlow
|
||||
every { anyConstructed<AuthRepository>().getSignedInUser() } returns null
|
||||
every { anyConstructed<AuthRepository>().observeAuthState() } returns flowOf(null)
|
||||
every { anyConstructed<RecentFilesRepository>().getRecentFilesFlow() } returns flowOf(emptyList())
|
||||
every { anyConstructed<RecentFilesRepository>().activeShelvesFlow } returns flowOf(emptyList())
|
||||
every { anyConstructed<RecentFilesRepository>().shelfCrossRefsFlow } returns flowOf(emptyList())
|
||||
every { anyConstructed<RecentFilesRepository>().tagsFlow } returns flowOf(emptyList())
|
||||
every { anyConstructed<RecentFilesRepository>().tagCrossRefsFlow } returns flowOf(emptyList())
|
||||
|
||||
coEvery { anyConstructed<RecentFilesRepository>().migrateLegacyShelvesToRoom() } just Runs
|
||||
coEvery { anyConstructed<RecentFilesRepository>().seedTagsIfEmpty(any()) } just Runs
|
||||
|
||||
every { anyConstructed<FontsRepository>().getAllFonts() } returns customFontsFlow
|
||||
|
||||
viewModel = MainViewModel(mockApplication)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
Dispatchers.resetMain()
|
||||
unmockkAll()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `search query updates uiState when search is active`() = runTest {
|
||||
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
|
||||
viewModel.uiState.collect {}
|
||||
}
|
||||
|
||||
viewModel.setSearchActive(true)
|
||||
viewModel.onSearchQueryChange("Moby Dick")
|
||||
|
||||
assertEquals("Moby Dick", viewModel.uiState.value.searchQuery)
|
||||
assertTrue(viewModel.uiState.value.isSearchActive)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setSearchActive false clears the search query`() = runTest {
|
||||
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
|
||||
viewModel.uiState.collect {}
|
||||
}
|
||||
|
||||
viewModel.setSearchActive(true)
|
||||
viewModel.onSearchQueryChange("Android")
|
||||
viewModel.setSearchActive(false)
|
||||
|
||||
assertEquals("", viewModel.uiState.value.searchQuery)
|
||||
assertFalse(viewModel.uiState.value.isSearchActive)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `switching theme updates internal state and preferences`() = runTest {
|
||||
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
|
||||
viewModel.uiState.collect {}
|
||||
}
|
||||
|
||||
viewModel.setAppThemeMode(AppThemeMode.DARK)
|
||||
|
||||
assertEquals(AppThemeMode.DARK, viewModel.uiState.value.appThemeMode)
|
||||
verify { mockEditor.putString("app_theme_mode", AppThemeMode.DARK.name) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setTabsEnabled persists to shared preferences`() = runTest {
|
||||
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
|
||||
viewModel.uiState.collect {}
|
||||
}
|
||||
|
||||
viewModel.setTabsEnabled(true)
|
||||
|
||||
assertTrue(viewModel.uiState.value.isTabsEnabled)
|
||||
verify { mockEditor.putBoolean("tabs_enabled", true) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `banner message logic works correctly`() = runTest {
|
||||
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
|
||||
viewModel.uiState.collect {}
|
||||
}
|
||||
|
||||
viewModel.showBanner("Test Message", isError = true)
|
||||
|
||||
val currentBanner = viewModel.uiState.value.bannerMessage
|
||||
assertEquals("Test Message", currentBanner?.message)
|
||||
assertTrue(currentBanner?.isError == true)
|
||||
|
||||
viewModel.bannerMessageShown()
|
||||
assertEquals(null, viewModel.uiState.value.bannerMessage)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue