1package com.parseable.android
3import com.parseable.android.data.local.FavoriteStreamDao
4import com.parseable.android.data.model.*
5import com.parseable.android.data.repository.ParseableRepository
6import com.parseable.android.data.repository.SettingsRepository
7import com.parseable.android.ui.screens.streams.StreamsViewModel
9import kotlinx.coroutines.Dispatchers
10import kotlinx.coroutines.ExperimentalCoroutinesApi
11import kotlinx.coroutines.flow.flowOf
12import kotlinx.coroutines.test.*
14import org.junit.Assert.*
15import org.junit.Before
18@OptIn(ExperimentalCoroutinesApi::class)
19class StreamsViewModelTest {
21 private lateinit var repository: ParseableRepository
22 private lateinit var settingsRepository: SettingsRepository
23 private lateinit var favoriteDao: FavoriteStreamDao
24 private lateinit var viewModel: StreamsViewModel
25 private val testDispatcher = UnconfinedTestDispatcher()
29 Dispatchers.setMain(testDispatcher)
30 repository = mockk(relaxed = true)
31 settingsRepository = mockk(relaxed = true)
32 favoriteDao = mockk(relaxed = true)
33 every { favoriteDao.getAllNames() } returns flowOf(emptyList())
38 Dispatchers.resetMain()
42 fun `refresh loads streams and about info`() = runTest {
43 val streams = listOf(LogStream("stream1"), LogStream("stream2"))
44 val about = AboutInfo(version = "1.0.0")
45 coEvery { repository.listStreams(forceRefresh = true) } returns ApiResult.Success(streams)
46 coEvery { repository.getAbout() } returns ApiResult.Success(about)
47 coEvery { repository.getStreamStats(any()) } returns ApiResult.Error("skip", 0)
49 viewModel = StreamsViewModel(repository, settingsRepository, favoriteDao)
52 val state = viewModel.state.value
53 assertEquals(2, state.streams.size)
54 assertEquals("stream1", state.streams[0].name)
55 assertEquals("1.0.0", state.aboutInfo?.version)
56 assertFalse(state.isLoading)
57 assertNull(state.error)
61 fun `refresh sets error when streams fail`() = runTest {
62 coEvery { repository.listStreams(forceRefresh = true) } returns ApiResult.Error("Network error", 0)
63 coEvery { repository.getAbout() } returns ApiResult.Error("fail", 0)
65 viewModel = StreamsViewModel(repository, settingsRepository, favoriteDao)
68 val state = viewModel.state.value
69 assertTrue(state.streams.isEmpty())
70 assertNotNull(state.error)
71 assertFalse(state.isLoading)
75 fun `refresh loads stats for each stream`() = runTest {
76 val streams = listOf(LogStream("s1"), LogStream("s2"))
77 coEvery { repository.listStreams(forceRefresh = true) } returns ApiResult.Success(streams)
78 coEvery { repository.getAbout() } returns ApiResult.Success(AboutInfo())
79 coEvery { repository.getStreamStats("s1") } returns ApiResult.Success(
81 ingestion = IngestionStats(count = 100, size = "1MB"),
82 storage = StorageStats(size = "500KB"),
85 coEvery { repository.getStreamStats("s2") } returns ApiResult.Error("fail", 500)
87 viewModel = StreamsViewModel(repository, settingsRepository, favoriteDao)
90 val state = viewModel.state.value
91 assertEquals(1, state.streamStats.size)
92 assertNotNull(state.streamStats["s1"])
93 assertEquals(100L, state.streamStats["s1"]?.eventCount)
94 assertTrue(state.failedStats.contains("s2"))
98 fun `logout calls clearConfig`() = runTest {
99 coEvery { repository.listStreams(forceRefresh = true) } returns ApiResult.Success(emptyList())
100 coEvery { repository.getAbout() } returns ApiResult.Success(AboutInfo())
102 viewModel = StreamsViewModel(repository, settingsRepository, favoriteDao)
105 coVerify { settingsRepository.clearConfig() }
109 fun `favorites are loaded from DAO on init`() = runTest {
110 every { favoriteDao.getAllNames() } returns flowOf(listOf("stream1", "stream2"))
112 viewModel = StreamsViewModel(repository, settingsRepository, favoriteDao)
114 val state = viewModel.state.value
115 assertTrue(state.favoriteNames.contains("stream1"))
116 assertTrue(state.favoriteNames.contains("stream2"))
120 fun `toggleFavorite adds when not favorited`() = runTest {
121 every { favoriteDao.getAllNames() } returns flowOf(emptyList())
123 viewModel = StreamsViewModel(repository, settingsRepository, favoriteDao)
124 viewModel.toggleFavorite("new_stream")
126 coVerify { favoriteDao.insert(match { it.streamName == "new_stream" }) }
130 fun `refresh resets isLoading on unexpected exception`() = runTest {
131 coEvery { repository.getAbout() } throws RuntimeException("boom")
132 coEvery { repository.listStreams(forceRefresh = true) } returns ApiResult.Success(emptyList())
134 viewModel = StreamsViewModel(repository, settingsRepository, favoriteDao)
137 val state = viewModel.state.value
138 assertFalse(state.isLoading)
139 assertNotNull(state.error)
140 assertTrue(state.error!!.contains("boom"))
144 fun `toggleFavorite removes when already favorited`() = runTest {
145 every { favoriteDao.getAllNames() } returns flowOf(listOf("existing"))
146 // toggleFavorite decides insert-vs-delete from the committed DB state (isFavoriteNow),
147 // not the async-mirrored favoriteNames flow, so stub that as the source of truth.
148 coEvery { favoriteDao.isFavoriteNow("existing") } returns true
150 viewModel = StreamsViewModel(repository, settingsRepository, favoriteDao)
151 viewModel.toggleFavorite("existing")
153 coVerify { favoriteDao.deleteByName("existing") }
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list