diff --git a/app/src/main/java/com/daedan/festabook/presentation/news/NewsViewModel.kt b/app/src/main/java/com/daedan/festabook/presentation/news/NewsViewModel.kt index dc47a55a..c9b1b766 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/news/NewsViewModel.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/news/NewsViewModel.kt @@ -35,7 +35,7 @@ class NewsViewModel( private val lostItemRepository: LostItemRepository, ) : ViewModel() { private val _noticeUiState: MutableStateFlow = - MutableStateFlow(NoticeUiState.InitialLoading) + MutableStateFlow(NoticeUiState(content = NoticeUiState.Content.InitialLoading)) val noticeUiState: StateFlow = _noticeUiState.asStateFlow() private val _faqUiState: MutableStateFlow = @@ -43,15 +43,15 @@ class NewsViewModel( val faqUiState: StateFlow = _faqUiState.asStateFlow() private val _lostUiState: MutableStateFlow = - MutableStateFlow(LostUiState.InitialLoading) + MutableStateFlow(LostUiState(content = LostUiState.Content.InitialLoading)) val lostUiState: StateFlow = _lostUiState.asStateFlow() private var noticeIdToExpand: Long? = null init { - loadAllNotices(NoticeUiState.InitialLoading) + loadAllNotices(NoticeUiState(content = NoticeUiState.Content.InitialLoading)) loadAllFAQs(FAQUiState.InitialLoading) - loadAllLostItems(LostUiState.InitialLoading) + loadAllLostItems(LostUiState(content = LostUiState.Content.InitialLoading)) } fun loadAllNotices(state: NoticeUiState) { @@ -71,10 +71,13 @@ class NewsViewModel( if (it == -1) DEFAULT_POSITION else it } _noticeUiState.value = - NoticeUiState.Success(updatedNotices, expandPosition) + NoticeUiState( + content = + NoticeUiState.Content.Success(updatedNotices, expandPosition), + ) noticeIdToExpand = null }.onFailure { - _noticeUiState.value = NoticeUiState.Error(it) + _noticeUiState.value = NoticeUiState(content = NoticeUiState.Content.Error(it)) } } } @@ -93,14 +96,9 @@ class NewsViewModel( fun expandNotice(noticeIdToExpand: Long) { this.noticeIdToExpand = noticeIdToExpand - val notices = - when (val currentState = _noticeUiState.value) { - is NoticeUiState.Refreshing -> currentState.oldNotices - is NoticeUiState.Success -> currentState.notices - else -> return - } - - loadAllNotices(NoticeUiState.Refreshing(notices)) + if (_noticeUiState.value.content !is NoticeUiState.Content.InitialLoading) { + loadAllNotices(_noticeUiState.value) + } } fun toggleFAQ(faqItem: FAQItemUiModel) { @@ -140,7 +138,7 @@ class NewsViewModel( null -> LostUiModel.Guide() } } - _lostUiState.value = LostUiState.Success(lostUiModels) + _lostUiState.value = LostUiState(content = LostUiState.Content.Success(lostUiModels)) } } @@ -160,14 +158,19 @@ class NewsViewModel( } private fun updateNoticeUiState(onUpdate: (List) -> List) { + val currentState = _noticeUiState.value _noticeUiState.value = - when (val currentState = _noticeUiState.value) { - is NoticeUiState.Success -> + when (val currentContent = currentState.content) { + is NoticeUiState.Content.Success -> { currentState.copy( - notices = onUpdate(currentState.notices), + content = + currentContent.copy(notices = onUpdate(currentContent.notices)), ) + } - else -> currentState + else -> { + return + } } } @@ -182,10 +185,18 @@ class NewsViewModel( private fun updateLostUiState(onUpdate: (List) -> List) { val currentState = _lostUiState.value + val currentContent = currentState.content _lostUiState.value = - when (currentState) { - is LostUiState.Success -> currentState.copy(lostItems = onUpdate(currentState.lostItems)) - else -> currentState + when (currentContent) { + is LostUiState.Content.Success -> { + _lostUiState.value.copy( + content = currentContent.copy(lostItems = onUpdate(currentContent.lostItems)), + ) + } + + else -> { + currentState + } } } } diff --git a/app/src/main/java/com/daedan/festabook/presentation/news/component/NewsScreen.kt b/app/src/main/java/com/daedan/festabook/presentation/news/component/NewsScreen.kt index fe382d64..ed46fc32 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/news/component/NewsScreen.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/news/component/NewsScreen.kt @@ -35,33 +35,26 @@ fun NewsScreen( val faqUiState by newsViewModel.faqUiState.collectAsStateWithLifecycle() val currentOnShowErrorSnackbar by rememberUpdatedState(onShowErrorSnackbar) - val isNoticeRefreshing = noticeUiState is NoticeUiState.Refreshing - val isLostItemRefreshing = lostUiState is LostUiState.Refreshing - LaunchedEffect(noticeUiState) { - when (val uiState = noticeUiState) { - is NoticeUiState.Success -> { + when (val content = noticeUiState.content) { + is NoticeUiState.Content.Success -> { pageState.animateScrollToPage(NewsTab.NOTICE.ordinal) } - is NoticeUiState.Error -> { - currentOnShowErrorSnackbar(uiState.throwable) + is NoticeUiState.Content.Error -> { + currentOnShowErrorSnackbar(content.throwable) } - else -> { - Unit - } + else -> {} } } LaunchedEffect(lostUiState) { - when (val uiState = lostUiState) { - is LostUiState.Error -> { - currentOnShowErrorSnackbar(uiState.throwable) + when (val content = lostUiState.content) { + is LostUiState.Content.Error -> { + currentOnShowErrorSnackbar(content.throwable) } - else -> { - Unit - } + else -> {} } } @@ -71,9 +64,7 @@ fun NewsScreen( currentOnShowErrorSnackbar(uiState.throwable) } - else -> { - Unit - } + else -> {} } } @@ -90,17 +81,13 @@ fun NewsScreen( noticeUiState = noticeUiState, faqUiState = faqUiState, lostUiState = lostUiState, - isNoticeRefreshing = isNoticeRefreshing, - isLostItemRefreshing = isLostItemRefreshing, onNoticeRefresh = { - val oldNotices = - (noticeUiState as? NoticeUiState.Success)?.notices ?: emptyList() - newsViewModel.loadAllNotices(NoticeUiState.Refreshing(oldNotices)) + val currentUiState = noticeUiState.copy(isRefreshing = true) + newsViewModel.loadAllNotices(currentUiState) }, onLostItemRefresh = { - val oldLostItems = - (lostUiState as? LostUiState.Success)?.lostItems ?: emptyList() - newsViewModel.loadAllLostItems(LostUiState.Refreshing(oldLostItems)) + val currentUiState = lostUiState.copy(isRefreshing = true) + newsViewModel.loadAllLostItems(currentUiState) }, onNoticeClick = { newsViewModel.toggleNotice(it) }, onFaqClick = { newsViewModel.toggleFAQ(it) }, diff --git a/app/src/main/java/com/daedan/festabook/presentation/news/component/NewsTabPage.kt b/app/src/main/java/com/daedan/festabook/presentation/news/component/NewsTabPage.kt index 1fbf10a1..7bdaac3c 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/news/component/NewsTabPage.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/news/component/NewsTabPage.kt @@ -27,8 +27,6 @@ fun NewsTabPage( lostUiState: LostUiState, onNoticeRefresh: () -> Unit, onLostItemRefresh: () -> Unit, - isNoticeRefreshing: Boolean, - isLostItemRefreshing: Boolean, onNoticeClick: (NoticeUiModel) -> Unit, onFaqClick: (FAQItemUiModel) -> Unit, onLostGuideClick: () -> Unit, @@ -41,30 +39,31 @@ fun NewsTabPage( ) { index -> val tab = NewsTab.entries[index] when (tab) { - NewsTab.NOTICE -> + NewsTab.NOTICE -> { NoticeScreen( uiState = noticeUiState, onNoticeClick = onNoticeClick, - isRefreshing = isNoticeRefreshing, onRefresh = onNoticeRefresh, modifier = Modifier.padding(horizontal = festabookSpacing.paddingScreenGutter), ) + } - NewsTab.FAQ -> + NewsTab.FAQ -> { FAQScreen( uiState = faqUiState, onFaqClick = onFaqClick, modifier = Modifier.padding(horizontal = festabookSpacing.paddingScreenGutter), ) + } - NewsTab.LOST_ITEM -> + NewsTab.LOST_ITEM -> { LostItemScreen( lostUiState = lostUiState, onLostGuideClick = onLostGuideClick, - isRefreshing = isLostItemRefreshing, onRefresh = onLostItemRefresh, modifier = Modifier.padding(horizontal = festabookSpacing.paddingScreenGutter), ) + } } } } @@ -74,13 +73,11 @@ fun NewsTabPage( private fun NewsTabPagePreview() { NewsTabPage( pageState = rememberPagerState { 3 }, - noticeUiState = NoticeUiState.Success(emptyList(), 0), + noticeUiState = NoticeUiState(content = NoticeUiState.Content.Success(emptyList(), 0)), faqUiState = FAQUiState.Success(emptyList()), - lostUiState = LostUiState.Success(emptyList()), + lostUiState = LostUiState(content = LostUiState.Content.Success(emptyList())), onNoticeRefresh = {}, onLostItemRefresh = {}, - isNoticeRefreshing = false, - isLostItemRefreshing = false, onNoticeClick = {}, onFaqClick = {}, onLostGuideClick = {}, diff --git a/app/src/main/java/com/daedan/festabook/presentation/news/faq/component/FAQScreen.kt b/app/src/main/java/com/daedan/festabook/presentation/news/faq/component/FAQScreen.kt index 3f9908d3..95fd81b6 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/news/faq/component/FAQScreen.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/news/faq/component/FAQScreen.kt @@ -2,16 +2,16 @@ package com.daedan.festabook.presentation.news.faq.component import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.daedan.festabook.R import com.daedan.festabook.presentation.common.component.EmptyStateScreen +import com.daedan.festabook.presentation.common.component.ErrorStateScreen import com.daedan.festabook.presentation.common.component.LoadingStateScreen import com.daedan.festabook.presentation.news.component.NewsItem import com.daedan.festabook.presentation.news.faq.FAQUiState @@ -27,12 +27,13 @@ fun FAQScreen( ) { when (uiState) { is FAQUiState.Error -> { - LaunchedEffect(uiState) { - Timber.w(uiState.throwable.stackTraceToString()) - } + Timber.w(uiState.throwable.stackTraceToString()) + ErrorStateScreen(modifier = modifier.fillMaxSize()) } - is FAQUiState.InitialLoading -> LoadingStateScreen() + is FAQUiState.InitialLoading -> { + LoadingStateScreen() + } is FAQUiState.Success -> { if (uiState.faqs.isEmpty()) { diff --git a/app/src/main/java/com/daedan/festabook/presentation/news/lost/LostUiState.kt b/app/src/main/java/com/daedan/festabook/presentation/news/lost/LostUiState.kt index 57b49393..9d6a11d9 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/news/lost/LostUiState.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/news/lost/LostUiState.kt @@ -2,18 +2,19 @@ package com.daedan.festabook.presentation.news.lost import com.daedan.festabook.presentation.news.lost.model.LostUiModel -sealed interface LostUiState { - data object InitialLoading : LostUiState +data class LostUiState( + val content: Content, + val isRefreshing: Boolean = false, +) { + sealed interface Content { + data object InitialLoading : Content - data class Refreshing( - val oldLostItems: List, - ) : LostUiState + data class Success( + val lostItems: List, + ) : Content - data class Success( - val lostItems: List, - ) : LostUiState - - data class Error( - val throwable: Throwable, - ) : LostUiState + data class Error( + val throwable: Throwable, + ) : Content + } } diff --git a/app/src/main/java/com/daedan/festabook/presentation/news/lost/component/LostItemScreen.kt b/app/src/main/java/com/daedan/festabook/presentation/news/lost/component/LostItemScreen.kt index cb214696..32be691d 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/news/lost/component/LostItemScreen.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/news/lost/component/LostItemScreen.kt @@ -1,16 +1,18 @@ package com.daedan.festabook.presentation.news.lost.component import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -21,6 +23,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.daedan.festabook.R import com.daedan.festabook.presentation.common.component.EmptyStateScreen +import com.daedan.festabook.presentation.common.component.ErrorStateScreen import com.daedan.festabook.presentation.common.component.LoadingStateScreen import com.daedan.festabook.presentation.common.component.PullToRefreshContainer import com.daedan.festabook.presentation.news.component.NewsItem @@ -37,11 +40,11 @@ private const val SPAN_COUNT: Int = 2 fun LostItemScreen( lostUiState: LostUiState, onLostGuideClick: () -> Unit, - isRefreshing: Boolean, onRefresh: () -> Unit, modifier: Modifier = Modifier, ) { var clickedLostItem by remember { mutableStateOf(null) } + val scrollState = rememberScrollState() clickedLostItem?.let { LostItemModalDialog( @@ -51,39 +54,33 @@ fun LostItemScreen( } PullToRefreshContainer( - isRefreshing = isRefreshing, + isRefreshing = lostUiState.isRefreshing, onRefresh = onRefresh, + modifier = modifier, ) { graphicsLayer -> - when (lostUiState) { - LostUiState.InitialLoading -> { + when (val content = lostUiState.content) { + LostUiState.Content.InitialLoading -> { LoadingStateScreen() } - is LostUiState.Error -> { - LaunchedEffect(lostUiState) { - Timber.w(lostUiState.throwable.stackTraceToString()) - } - } - - is LostUiState.Refreshing -> { - LostItemContent( - lostItems = lostUiState.oldLostItems, - onLostGuideClick = onLostGuideClick, - onLostItemClick = { }, + is LostUiState.Content.Error -> { + Timber.w(content.throwable.stackTraceToString()) + ErrorStateScreen( modifier = - modifier + Modifier .fillMaxSize() - .then(graphicsLayer), + .then(graphicsLayer) + .verticalScroll(scrollState), ) } - is LostUiState.Success -> { + is LostUiState.Content.Success -> { LostItemContent( - lostItems = lostUiState.lostItems, + lostItems = content.lostItems, onLostGuideClick = onLostGuideClick, onLostItemClick = { clickedLostItem = it }, modifier = - modifier + Modifier .fillMaxSize() .then(graphicsLayer), ) @@ -99,49 +96,50 @@ private fun LostItemContent( onLostItemClick: (LostUiModel.Item) -> Unit, modifier: Modifier = Modifier, ) { - val isLostItemEmpty = lostItems.none { it is LostUiModel.Item } - if (isLostItemEmpty) { - EmptyStateScreen() - } + Box(modifier = modifier) { + val isLostItemEmpty = lostItems.none { it is LostUiModel.Item } + if (isLostItemEmpty) { + EmptyStateScreen() + } - LazyVerticalGrid( - modifier = modifier, - columns = GridCells.Fixed(SPAN_COUNT), - contentPadding = - PaddingValues( - top = festabookSpacing.paddingBody2, - bottom = festabookSpacing.paddingBody2, - ), - verticalArrangement = Arrangement.spacedBy(festabookSpacing.paddingBody2), - horizontalArrangement = Arrangement.spacedBy(festabookSpacing.paddingBody2), - ) { - item(span = { GridItemSpan(SPAN_COUNT) }) { - val guide = lostItems.firstOrNull() as? LostUiModel.Guide - guide?.let { - NewsItem( - title = stringResource(R.string.lost_item_guide), - description = it.description, - isExpanded = it.isExpanded, - onclick = onLostGuideClick, - icon = - { - Icon( - painter = painterResource(R.drawable.ic_info), - contentDescription = stringResource(R.string.info), - ) - }, + LazyVerticalGrid( + columns = GridCells.Fixed(SPAN_COUNT), + contentPadding = + PaddingValues( + top = festabookSpacing.paddingBody2, + bottom = festabookSpacing.paddingBody2, + ), + verticalArrangement = Arrangement.spacedBy(festabookSpacing.paddingBody2), + horizontalArrangement = Arrangement.spacedBy(festabookSpacing.paddingBody2), + ) { + item(span = { GridItemSpan(SPAN_COUNT) }) { + val guide = lostItems.firstOrNull() as? LostUiModel.Guide + guide?.let { + NewsItem( + title = stringResource(R.string.lost_item_guide), + description = it.description, + isExpanded = it.isExpanded, + onclick = onLostGuideClick, + icon = + { + Icon( + painter = painterResource(R.drawable.ic_info), + contentDescription = stringResource(R.string.info), + ) + }, + ) + } + } + items( + items = lostItems.drop(1).filterIsInstance(), + key = { lostItem -> lostItem.lostItemId }, + ) { lostItem -> + LostItem( + url = lostItem.imageUrl, + onLostItemClick = { onLostItemClick(lostItem) }, ) } } - items( - items = lostItems.drop(1).filterIsInstance(), - key = { lostItem -> lostItem.lostItemId }, - ) { lostItem -> - LostItem( - url = lostItem.imageUrl, - onLostItemClick = { onLostItemClick(lostItem) }, - ) - } } } diff --git a/app/src/main/java/com/daedan/festabook/presentation/news/notice/NoticeUiState.kt b/app/src/main/java/com/daedan/festabook/presentation/news/notice/NoticeUiState.kt index 78cd73ed..4cd6607d 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/news/notice/NoticeUiState.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/news/notice/NoticeUiState.kt @@ -2,21 +2,22 @@ package com.daedan.festabook.presentation.news.notice import com.daedan.festabook.presentation.news.notice.model.NoticeUiModel -sealed interface NoticeUiState { - data class Refreshing( - val oldNotices: List, - ) : NoticeUiState +data class NoticeUiState( + val content: Content, + val isRefreshing: Boolean = false, +) { + sealed interface Content { + data object InitialLoading : Content - data object InitialLoading : NoticeUiState + data class Success( + val notices: List, + val expandPosition: Int, + ) : Content - data class Success( - val notices: List, - val expandPosition: Int, - ) : NoticeUiState - - data class Error( - val throwable: Throwable, - ) : NoticeUiState + data class Error( + val throwable: Throwable, + ) : Content + } companion object { const val DEFAULT_POSITION: Int = 0 diff --git a/app/src/main/java/com/daedan/festabook/presentation/news/notice/component/NoticeScreen.kt b/app/src/main/java/com/daedan/festabook/presentation/news/notice/component/NoticeScreen.kt index fc18391e..2f63e3b6 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/news/notice/component/NoticeScreen.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/news/notice/component/NoticeScreen.kt @@ -1,10 +1,14 @@ package com.daedan.festabook.presentation.news.notice.component +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.runtime.Composable @@ -15,6 +19,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.daedan.festabook.R import com.daedan.festabook.presentation.common.component.EmptyStateScreen +import com.daedan.festabook.presentation.common.component.ErrorStateScreen import com.daedan.festabook.presentation.common.component.LoadingStateScreen import com.daedan.festabook.presentation.common.component.PullToRefreshContainer import com.daedan.festabook.presentation.news.component.NewsItem @@ -29,39 +34,39 @@ import timber.log.Timber fun NoticeScreen( uiState: NoticeUiState, onNoticeClick: (NoticeUiModel) -> Unit, - isRefreshing: Boolean, onRefresh: () -> Unit, modifier: Modifier = Modifier, ) { + val scrollState = rememberScrollState() + PullToRefreshContainer( - isRefreshing = isRefreshing, + isRefreshing = uiState.isRefreshing, onRefresh = onRefresh, + modifier = modifier, ) { graphicsLayer -> - when (uiState) { - NoticeUiState.InitialLoading -> { + when (val content = uiState.content) { + NoticeUiState.Content.InitialLoading -> { LoadingStateScreen() } - is NoticeUiState.Error -> { - LaunchedEffect(uiState) { - Timber.w(uiState.throwable.stackTraceToString()) - } - } - - is NoticeUiState.Refreshing -> { - NoticeContent( - notices = uiState.oldNotices, - onNoticeClick = onNoticeClick, - modifier = modifier.then(graphicsLayer), + is NoticeUiState.Content.Error -> { + Timber.e(content.throwable.stackTraceToString()) + ErrorStateScreen( + modifier = + Modifier + .fillMaxSize() + .then(graphicsLayer) + .verticalScroll(scrollState), ) } - is NoticeUiState.Success -> { + is NoticeUiState.Content.Success -> { NoticeContent( - notices = uiState.notices, - expandPosition = uiState.expandPosition, + scrollState = scrollState, + notices = content.notices, + expandPosition = content.expandPosition, onNoticeClick = onNoticeClick, - modifier = modifier.then(graphicsLayer), + modifier = Modifier.then(graphicsLayer), ) } } @@ -70,6 +75,7 @@ fun NoticeScreen( @Composable private fun NoticeContent( + scrollState: ScrollState, notices: List, onNoticeClick: (NoticeUiModel) -> Unit, modifier: Modifier = Modifier, @@ -81,7 +87,12 @@ private fun NoticeContent( listState.animateScrollToItem(expandPosition) } if (notices.isEmpty()) { - EmptyStateScreen(modifier = modifier) + EmptyStateScreen( + modifier = + modifier + .fillMaxSize() + .verticalScroll(scrollState), + ) } else { LazyColumn( modifier = modifier, @@ -127,12 +138,14 @@ private fun NoticeContent( private fun NoticeScreenPreview() { NoticeScreen( uiState = - NoticeUiState.Success( - notices = emptyList(), - expandPosition = 0, + NoticeUiState( + content = + NoticeUiState.Content.Success( + notices = emptyList(), + expandPosition = 0, + ), ), onNoticeClick = { }, - isRefreshing = false, onRefresh = {}, ) } diff --git a/app/src/test/java/com/daedan/festabook/news/NewsViewModelTest.kt b/app/src/test/java/com/daedan/festabook/news/NewsViewModelTest.kt index 70cc1faa..1b4bbead 100644 --- a/app/src/test/java/com/daedan/festabook/news/NewsViewModelTest.kt +++ b/app/src/test/java/com/daedan/festabook/news/NewsViewModelTest.kt @@ -21,8 +21,7 @@ import io.mockk.coVerify import io.mockk.mockk import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain @@ -36,7 +35,7 @@ import org.junit.Test class NewsViewModelTest { @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule() - private val testDispatcher = StandardTestDispatcher() + private val testDispatcher = UnconfinedTestDispatcher() private lateinit var noticeRepository: NoticeRepository private lateinit var faqRepository: FAQRepository private lateinit var lostItemRepository: LostItemRepository @@ -72,15 +71,14 @@ class NewsViewModelTest { coEvery { noticeRepository.fetchNotices() } returns Result.success(FAKE_NOTICES) // when - newsViewModel.loadAllNotices(NoticeUiState.InitialLoading) - advanceUntilIdle() + newsViewModel.loadAllNotices(NoticeUiState(content = NoticeUiState.Content.InitialLoading)) // then val expected = FAKE_NOTICES.map { it.toUiModel() } val actual = newsViewModel.noticeUiState.value coVerify { noticeRepository.fetchNotices() } assertThat(actual).isEqualTo( - NoticeUiState.Success(expected, DEFAULT_POSITION), + NoticeUiState(content = NoticeUiState.Content.Success(expected, DEFAULT_POSITION)), ) } @@ -89,16 +87,18 @@ class NewsViewModelTest { runTest { // given val expected = - LostUiState.Success( - listOf( - (FAKE_LOST_ITEM[0] as Lost.Guide).toLostGuideItemUiModel(), - (FAKE_LOST_ITEM[1] as Lost.Item).toLostItemUiModel(), - ), + LostUiState( + content = + LostUiState.Content.Success( + listOf( + (FAKE_LOST_ITEM[0] as Lost.Guide).toLostGuideItemUiModel(), + (FAKE_LOST_ITEM[1] as Lost.Item).toLostItemUiModel(), + ), + ), ) // when - newsViewModel.loadAllLostItems(LostUiState.InitialLoading) - advanceUntilIdle() + newsViewModel.loadAllLostItems(LostUiState(content = LostUiState.Content.InitialLoading)) // then val actual = newsViewModel.lostUiState.value @@ -114,11 +114,10 @@ class NewsViewModelTest { coEvery { noticeRepository.fetchNotices() } returns Result.failure(exception) // when - newsViewModel.loadAllNotices(NoticeUiState.InitialLoading) - advanceUntilIdle() + newsViewModel.loadAllNotices(NoticeUiState(content = NoticeUiState.Content.InitialLoading)) // then - val expected = NoticeUiState.Error(exception) + val expected = NoticeUiState(content = NoticeUiState.Content.Error(exception)) val actual = newsViewModel.noticeUiState.value coVerify { noticeRepository.fetchNotices() } assertThat(actual).isEqualTo(expected) @@ -132,7 +131,6 @@ class NewsViewModelTest { // when newsViewModel = NewsViewModel(noticeRepository, faqRepository, lostItemRepository) - advanceUntilIdle() // then val expected = FAKE_FAQS.map { it.toUiModel() } @@ -150,7 +148,6 @@ class NewsViewModelTest { // when newsViewModel = NewsViewModel(noticeRepository, faqRepository, lostItemRepository) - advanceUntilIdle() // then val expected = FAQUiState.Error(exception) @@ -167,10 +164,9 @@ class NewsViewModelTest { // when newsViewModel.toggleNotice(notice) - advanceUntilIdle() // then - val expected = + val notices = listOf( notice.copy(isExpanded = true), NoticeUiModel( @@ -182,7 +178,9 @@ class NewsViewModelTest { ), ) val actual = newsViewModel.noticeUiState.value - assertThat(actual).isEqualTo(NoticeUiState.Success(expected, DEFAULT_POSITION)) + val expected = + NoticeUiState(content = NoticeUiState.Content.Success(notices, DEFAULT_POSITION)) + assertThat(actual).isEqualTo(expected) } @Test @@ -193,7 +191,6 @@ class NewsViewModelTest { // when newsViewModel.toggleFAQ(faq) - advanceUntilIdle() // then val expected = listOf(faq.copy(isExpanded = true)) @@ -206,7 +203,7 @@ class NewsViewModelTest { runTest { // given coEvery { noticeRepository.fetchNotices() } returns Result.success(FAKE_NOTICES) - val expected = + val notices = listOf( FAKE_NOTICES.first().toUiModel(), FAKE_NOTICES[1].toUiModel().copy(isExpanded = true), @@ -214,12 +211,12 @@ class NewsViewModelTest { // when newsViewModel.expandNotice(2) - advanceUntilIdle() // then val actual = newsViewModel.noticeUiState.value + val expected = NoticeUiState(content = NoticeUiState.Content.Success(notices, 1)) coVerify { noticeRepository.fetchNotices() } - assertThat(actual).isEqualTo(NoticeUiState.Success(expected, 1)) + assertThat(actual).isEqualTo(expected) } @Test @@ -227,13 +224,16 @@ class NewsViewModelTest { runTest { // given val expected = - LostUiState.Success( - FAKE_LOST_ITEM_UI_MODEL.map { - when (it) { - is LostUiModel.Guide -> it.copy(isExpanded = true) - else -> it - } - }, + LostUiState( + content = + LostUiState.Content.Success( + FAKE_LOST_ITEM_UI_MODEL.map { + when (it) { + is LostUiModel.Guide -> it.copy(isExpanded = true) + else -> it + } + }, + ), ) // when