diff --git a/core/src/main/java/org/openedx/core/data/model/CourseProgressResponse.kt b/core/src/main/java/org/openedx/core/data/model/CourseProgressResponse.kt index 00d55a9b5..6c191ee3a 100644 --- a/core/src/main/java/org/openedx/core/data/model/CourseProgressResponse.kt +++ b/core/src/main/java/org/openedx/core/data/model/CourseProgressResponse.kt @@ -93,22 +93,24 @@ data class CourseProgressResponse( @SerializedName("assignment_colors") val assignmentColors: List? ) { // TODO Temporary solution. Backend will returns color list later - val defaultColors = listOf( - "#D24242", - "#7B9645", - "#5A5AD8", - "#B0842C", - "#2E90C2", - "#D13F88", - "#36A17D", - "#AE5AD8", - "#3BA03B" - ) + companion object { + val DEFAULT_COLORS = listOf( + "#D24242", + "#7B9645", + "#5A5AD8", + "#B0842C", + "#2E90C2", + "#D13F88", + "#36A17D", + "#AE5AD8", + "#3BA03B" + ) + } fun mapToRoomEntity() = GradingPolicyDb( assignmentPolicies = assignmentPolicies?.map { it.mapToRoomEntity() } ?: emptyList(), gradeRange = gradeRange ?: emptyMap(), - assignmentColors = assignmentColors ?: defaultColors + assignmentColors = assignmentColors ?: DEFAULT_COLORS ) fun mapToDomain() = CourseProgress.GradingPolicy( @@ -116,7 +118,7 @@ data class CourseProgressResponse( gradeRange = gradeRange ?: emptyMap(), assignmentColors = assignmentColors?.map { colorString -> Color(colorString.toColorInt()) - } ?: defaultColors.map { Color(it.toColorInt()) } + } ?: DEFAULT_COLORS.map { Color(it.toColorInt()) } ) data class AssignmentPolicy( diff --git a/course/src/main/java/org/openedx/course/data/repository/CourseRepository.kt b/course/src/main/java/org/openedx/course/data/repository/CourseRepository.kt index a6db3df5a..b57698f61 100644 --- a/course/src/main/java/org/openedx/course/data/repository/CourseRepository.kt +++ b/course/src/main/java/org/openedx/course/data/repository/CourseRepository.kt @@ -2,6 +2,8 @@ package org.openedx.course.data.repository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import okhttp3.MultipartBody import org.openedx.core.ApiConstants import org.openedx.core.data.api.CourseApi @@ -24,6 +26,8 @@ import org.openedx.core.module.db.DownloadDao import org.openedx.core.system.connection.NetworkConnection import java.net.URLDecoder import java.nio.charset.StandardCharsets +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicInteger @Suppress("TooManyFunctions") class CourseRepository( @@ -37,6 +41,28 @@ class CourseRepository( private val courseStatusMap = mutableMapOf() private val courseDatesMap = mutableMapOf() + private val courseSessionIds = ConcurrentHashMap() + private val courseStructureFetchedSession = ConcurrentHashMap() + private val courseStatusFetchedSession = ConcurrentHashMap() + private val courseDatesFetchedSession = ConcurrentHashMap() + private val courseProgressFetchedSession = ConcurrentHashMap() + private val courseStructureLocks = ConcurrentHashMap() + private val courseStatusLocks = ConcurrentHashMap() + private val courseDatesLocks = ConcurrentHashMap() + private val courseProgressLocks = ConcurrentHashMap() + private val enrollmentLocks = ConcurrentHashMap() + + private fun lockFor(map: ConcurrentHashMap, key: String): Mutex { + return map.computeIfAbsent(key) { Mutex() } + } + + fun startCourseSession(courseId: String) { + courseSessionIds.computeIfAbsent(courseId) { AtomicInteger(0) }.incrementAndGet() + } + + private fun currentSessionId(courseId: String): Int { + return courseSessionIds[courseId]?.get() ?: 0 + } suspend fun removeDownloadModel(id: String) { downloadDao.removeDownloadModel(id) @@ -50,30 +76,49 @@ class CourseRepository( suspend fun getCourseStructureFlow( courseId: String, - forceRefresh: Boolean = true + forceRefresh: Boolean = false ): Flow = channelFlowWithAwait { var hasCourseStructure = false - val cachedCourseStructure = courseStructure[courseId] ?: ( - courseDao.getCourseStructureById(courseId)?.mapToDomain() - ) + val sessionId = currentSessionId(courseId) + val cachedCourseStructure = courseStructure[courseId] + ?: courseDao.getCourseStructureById(courseId)?.mapToDomain() + ?.also { courseStructure[courseId] = it } if (cachedCourseStructure != null) { hasCourseStructure = true trySend(cachedCourseStructure) } - val fetchRemoteCourse = !hasCourseStructure || forceRefresh + val shouldFetchForSession = courseStructureFetchedSession[courseId] != sessionId + val fetchRemoteCourse = !hasCourseStructure || shouldFetchForSession || forceRefresh if (networkConnection.isOnline() && fetchRemoteCourse) { - val response = api.getCourseStructure( - "stale-if-error=0", - "v4", - preferencesManager.user?.username, - courseId - ) - courseDao.insertCourseStructureEntity(response.mapToRoomEntity()) - val courseDomainModel = response.mapToDomain() - courseStructure[courseId] = courseDomainModel - trySend(courseDomainModel) - hasCourseStructure = true + val lock = lockFor(courseStructureLocks, courseId) + lock.withLock { + val lockedSessionId = currentSessionId(courseId) + val cachedAfterLock = courseStructure[courseId] + ?: courseDao.getCourseStructureById(courseId)?.mapToDomain() + ?.also { courseStructure[courseId] = it } + val shouldFetchForSessionLocked = + courseStructureFetchedSession[courseId] != lockedSessionId + if (!shouldFetchForSessionLocked && cachedAfterLock != null) { + if (!hasCourseStructure) { + trySend(cachedAfterLock) + hasCourseStructure = true + } + return@withLock + } + val response = api.getCourseStructure( + "stale-if-error=0", + "v4", + preferencesManager.user?.username, + courseId + ) + courseDao.insertCourseStructureEntity(response.mapToRoomEntity()) + val courseDomainModel = response.mapToDomain() + courseStructure[courseId] = courseDomainModel + courseStructureFetchedSession[courseId] = lockedSessionId + trySend(courseDomainModel) + hasCourseStructure = true + } } if (!hasCourseStructure) { throw NoCachedDataException() @@ -90,37 +135,62 @@ class CourseRepository( } suspend fun getCourseStructure(courseId: String, isNeedRefresh: Boolean): CourseStructure { - if (!isNeedRefresh) courseStructure[courseId]?.let { return it } - - if (networkConnection.isOnline()) { - val response = api.getCourseStructure( - "stale-if-error=0", - "v4", - preferencesManager.user?.username, - courseId - ) - courseDao.insertCourseStructureEntity(response.mapToRoomEntity()) - courseStructure[courseId] = response.mapToDomain() - } else { - val cachedCourseStructure = courseDao.getCourseStructureById(courseId) - if (cachedCourseStructure != null) { - courseStructure[courseId] = cachedCourseStructure.mapToDomain() + val lock = lockFor(courseStructureLocks, courseId) + return lock.withLock { + if (!isNeedRefresh) { + courseStructure[courseId]?.let { return@withLock it } + courseDao.getCourseStructureById(courseId)?.mapToDomain()?.let { + courseStructure[courseId] = it + return@withLock it + } + } + if (networkConnection.isOnline()) { + val response = api.getCourseStructure( + "stale-if-error=0", + "v4", + preferencesManager.user?.username, + courseId + ) + courseDao.insertCourseStructureEntity(response.mapToRoomEntity()) + courseStructure[courseId] = response.mapToDomain() + courseStructureFetchedSession[courseId] = currentSessionId(courseId) } else { - throw NoCachedDataException() + val cachedCourseStructure = courseDao.getCourseStructureById(courseId) + if (cachedCourseStructure != null) { + courseStructure[courseId] = cachedCourseStructure.mapToDomain() + } else { + throw NoCachedDataException() + } } - } - return courseStructure[courseId]!! + return@withLock courseStructure[courseId]!! + } } - suspend fun getEnrollmentDetailsFlow(courseId: String): Flow = + suspend fun getEnrollmentDetailsFlow( + courseId: String, + forceRefresh: Boolean = false + ): Flow = channelFlowWithAwait { - getCourseEnrollmentDetailsFromCache(courseId)?.let { - trySend(it) + val cachedDetails = getCourseEnrollmentDetailsFromCache(courseId) + cachedDetails?.let { trySend(it) } + if (networkConnection.isOnline() && (forceRefresh || cachedDetails == null)) { + val lock = lockFor(enrollmentLocks, courseId) + lock.withLock { + val cachedAfterLock = getCourseEnrollmentDetailsFromCache(courseId) + if (!forceRefresh && cachedAfterLock != null) { + if (cachedDetails == null) { + trySend(cachedAfterLock) + } + return@withLock + } + val details = getEnrollmentDetails(courseId) + courseDao.insertCourseEnrollmentDetailsEntity(details.mapToEntity()) + trySend(details) + } + } else if (cachedDetails == null) { + throw NoCachedDataException() } - val details = getEnrollmentDetails(courseId) - courseDao.insertCourseEnrollmentDetailsEntity(details.mapToEntity()) - trySend(details) } private suspend fun getCourseEnrollmentDetailsFromCache(courseId: String): CourseEnrollmentDetails? { @@ -132,25 +202,50 @@ class CourseRepository( return api.getEnrollmentDetails(courseId = courseId).mapToDomain() } - suspend fun getCourseStatusFlow(courseId: String): Flow = + suspend fun getCourseStatusFlow( + courseId: String, + forceRefresh: Boolean = false + ): Flow = channelFlowWithAwait { - val localStatus = courseStatusMap[courseId] - localStatus?.let { trySend(it) } - - if (networkConnection.isOnline()) { - val username = preferencesManager.user?.username ?: "" - val status = api.getCourseStatus(username, courseId).mapToDomain() - courseStatusMap[courseId] = status - trySend(status) - } else { - val status = localStatus ?: CourseComponentStatus("") + val sessionId = currentSessionId(courseId) + val cachedStatus = courseStatusMap[courseId] + cachedStatus?.let { trySend(it) } + + val shouldFetchForSession = courseStatusFetchedSession[courseId] != sessionId + if (networkConnection.isOnline() && (forceRefresh || shouldFetchForSession || cachedStatus == null)) { + val lock = lockFor(courseStatusLocks, courseId) + lock.withLock { + val lockedSessionId = currentSessionId(courseId) + val cachedAfterLock = courseStatusMap[courseId] + val shouldFetchForSessionLocked = + courseStatusFetchedSession[courseId] != lockedSessionId + if (!shouldFetchForSessionLocked && cachedAfterLock != null) { + if (cachedStatus == null) { + trySend(cachedAfterLock) + } + return@withLock + } + val username = preferencesManager.user?.username ?: "" + val status = api.getCourseStatus(username, courseId).mapToDomain() + courseStatusMap[courseId] = status + courseStatusFetchedSession[courseId] = lockedSessionId + trySend(status) + } + } else if (cachedStatus == null) { + val status = CourseComponentStatus("") trySend(status) } } suspend fun getCourseStatus(courseId: String): CourseComponentStatus { - val username = preferencesManager.user?.username ?: "" - return api.getCourseStatus(username, courseId).mapToDomain() + val lock = lockFor(courseStatusLocks, courseId) + return lock.withLock { + val username = preferencesManager.user?.username ?: "" + val status = api.getCourseStatus(username, courseId).mapToDomain() + courseStatusMap[courseId] = status + courseStatusFetchedSession[courseId] = currentSessionId(courseId) + return@withLock status + } } suspend fun markBlocksCompletion(courseId: String, blocksId: List) { @@ -163,17 +258,36 @@ class CourseRepository( return api.markBlocksCompletion(blocksCompletionBody) } - suspend fun getCourseDatesFlow(courseId: String): Flow = + suspend fun getCourseDatesFlow( + courseId: String, + forceRefresh: Boolean = false + ): Flow = channelFlowWithAwait { - val localDates = courseDatesMap[courseId] - localDates?.let { trySend(it) } - - if (networkConnection.isOnline()) { - val datesResult = api.getCourseDates(courseId).getCourseDatesResult() - courseDatesMap[courseId] = datesResult - trySend(datesResult) - } else { - val datesResult = localDates ?: CourseDatesResult( + val sessionId = currentSessionId(courseId) + val cachedDates = courseDatesMap[courseId] + cachedDates?.let { trySend(it) } + + val shouldFetchForSession = courseDatesFetchedSession[courseId] != sessionId + if (networkConnection.isOnline() && (forceRefresh || shouldFetchForSession || cachedDates == null)) { + val lock = lockFor(courseDatesLocks, courseId) + lock.withLock { + val lockedSessionId = currentSessionId(courseId) + val cachedAfterLock = courseDatesMap[courseId] + val shouldFetchForSessionLocked = + courseDatesFetchedSession[courseId] != lockedSessionId + if (!shouldFetchForSessionLocked && cachedAfterLock != null) { + if (cachedDates == null) { + trySend(cachedAfterLock) + } + return@withLock + } + val datesResult = api.getCourseDates(courseId).getCourseDatesResult() + courseDatesMap[courseId] = datesResult + courseDatesFetchedSession[courseId] = lockedSessionId + trySend(datesResult) + } + } else if (cachedDates == null) { + val datesResult = CourseDatesResult( datesSection = linkedMapOf(), courseBanner = CourseDatesBannerInfo( missedDeadlines = false, @@ -187,8 +301,28 @@ class CourseRepository( } } - suspend fun getCourseDates(courseId: String) = - api.getCourseDates(courseId).getCourseDatesResult() + suspend fun getCourseDates( + courseId: String, + forceRefresh: Boolean = false + ): CourseDatesResult { + if (!forceRefresh) { + courseDatesMap[courseId]?.let { return it } + } + val lock = lockFor(courseDatesLocks, courseId) + return lock.withLock { + if (!forceRefresh) { + courseDatesMap[courseId]?.let { return@withLock it } + } + if (networkConnection.isOnline()) { + val datesResult = api.getCourseDates(courseId).getCourseDatesResult() + courseDatesMap[courseId] = datesResult + courseDatesFetchedSession[courseId] = currentSessionId(courseId) + return@withLock datesResult + } + courseDatesMap[courseId]?.let { return@withLock it } + throw NoCachedDataException() + } + } suspend fun resetCourseDates(courseId: String) = api.resetCourseDates(mapOf(ApiConstants.COURSE_KEY to courseId)).mapToDomain() @@ -264,16 +398,38 @@ class CourseRepository( ): Flow = channelFlowWithAwait { var courseProgress: CourseProgressEntity? = null + val sessionId = currentSessionId(courseId) if (!isRefresh) { courseProgress = courseDao.getCourseProgressById(courseId) if (courseProgress != null) { trySend(courseProgress.mapToDomain()) } } - if (networkConnection.isOnline() && (!getOnlyCacheIfExist || courseProgress == null)) { - val response = api.getCourseProgress(courseId) - courseDao.insertCourseProgressEntity(response.mapToRoomEntity(courseId)) - trySend(response.mapToDomain()) + val shouldFetchForSession = courseProgressFetchedSession[courseId] != sessionId + val shouldFetch = + isRefresh || courseProgress == null || shouldFetchForSession || !getOnlyCacheIfExist + if (networkConnection.isOnline() && shouldFetch) { + val lock = lockFor(courseProgressLocks, courseId) + lock.withLock { + val lockedSessionId = currentSessionId(courseId) + val cachedAfterLock = if (!isRefresh) { + courseDao.getCourseProgressById(courseId) + } else { + null + } + val shouldFetchForSessionLocked = + courseProgressFetchedSession[courseId] != lockedSessionId + if (!isRefresh && cachedAfterLock != null) { + trySend(cachedAfterLock.mapToDomain()) + if (!shouldFetchForSessionLocked) { + return@withLock + } + } + val response = api.getCourseProgress(courseId) + courseDao.insertCourseProgressEntity(response.mapToRoomEntity(courseId)) + courseProgressFetchedSession[courseId] = lockedSessionId + trySend(response.mapToDomain()) + } } } } diff --git a/course/src/main/java/org/openedx/course/domain/interactor/CourseInteractor.kt b/course/src/main/java/org/openedx/course/domain/interactor/CourseInteractor.kt index 86788f34c..4fe42409e 100644 --- a/course/src/main/java/org/openedx/course/domain/interactor/CourseInteractor.kt +++ b/course/src/main/java/org/openedx/course/domain/interactor/CourseInteractor.kt @@ -13,9 +13,13 @@ class CourseInteractor( private val repository: CourseRepository ) : CourseInteractor { + fun startCourseSession(courseId: String) { + repository.startCourseSession(courseId) + } + suspend fun getCourseStructureFlow( courseId: String, - forceRefresh: Boolean = true + forceRefresh: Boolean = false ): Flow { return repository.getCourseStructureFlow(courseId, forceRefresh) } @@ -31,8 +35,11 @@ class CourseInteractor( return repository.getCourseStructureFromCache(courseId) } - suspend fun getEnrollmentDetailsFlow(courseId: String): Flow { - return repository.getEnrollmentDetailsFlow(courseId) + suspend fun getEnrollmentDetailsFlow( + courseId: String, + forceRefresh: Boolean = false + ): Flow { + return repository.getEnrollmentDetailsFlow(courseId, forceRefresh) } suspend fun getEnrollmentDetails(courseId: String): CourseEnrollmentDetails { @@ -87,13 +94,22 @@ class CourseInteractor( } } - suspend fun getCourseStatusFlow(courseId: String) = repository.getCourseStatusFlow(courseId) + suspend fun getCourseStatusFlow( + courseId: String, + forceRefresh: Boolean = false + ) = repository.getCourseStatusFlow(courseId, forceRefresh) suspend fun getCourseStatus(courseId: String) = repository.getCourseStatus(courseId) - suspend fun getCourseDatesFlow(courseId: String) = repository.getCourseDatesFlow(courseId) + suspend fun getCourseDatesFlow( + courseId: String, + forceRefresh: Boolean = false + ) = repository.getCourseDatesFlow(courseId, forceRefresh) - suspend fun getCourseDates(courseId: String) = repository.getCourseDates(courseId) + suspend fun getCourseDates( + courseId: String, + forceRefresh: Boolean = false + ) = repository.getCourseDates(courseId, forceRefresh) suspend fun resetCourseDates(courseId: String) = repository.resetCourseDates(courseId) diff --git a/course/src/main/java/org/openedx/course/presentation/assignments/CourseAssignmentViewModel.kt b/course/src/main/java/org/openedx/course/presentation/assignments/CourseAssignmentViewModel.kt index 11da8d792..7bbc574ac 100644 --- a/course/src/main/java/org/openedx/course/presentation/assignments/CourseAssignmentViewModel.kt +++ b/course/src/main/java/org/openedx/course/presentation/assignments/CourseAssignmentViewModel.kt @@ -39,7 +39,7 @@ class CourseAssignmentViewModel( private fun collectData() { viewModelScope.launch { val courseProgressFlow = interactor.getCourseProgress(courseId, false, true) - val courseStructureFlow = interactor.getCourseStructureFlow(courseId) + val courseStructureFlow = interactor.getCourseStructureFlow(courseId, false) combine( courseProgressFlow, diff --git a/course/src/main/java/org/openedx/course/presentation/container/CourseContainerFragment.kt b/course/src/main/java/org/openedx/course/presentation/container/CourseContainerFragment.kt index 57e0a3be4..d9fba0523 100644 --- a/course/src/main/java/org/openedx/course/presentation/container/CourseContainerFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/container/CourseContainerFragment.kt @@ -143,13 +143,6 @@ class CourseContainerFragment : Fragment(R.layout.fragment_course_container) { observe() } - override fun onResume() { - super.onResume() - if (viewModel.courseAccessStatus.value == CourseAccessError.NONE) { - viewModel.updateData() - } - } - override fun onDestroyView() { snackBar?.dismiss() super.onDestroyView() diff --git a/course/src/main/java/org/openedx/course/presentation/container/CourseContainerViewModel.kt b/course/src/main/java/org/openedx/course/presentation/container/CourseContainerViewModel.kt index ff9643bd4..b75cd21b4 100644 --- a/course/src/main/java/org/openedx/course/presentation/container/CourseContainerViewModel.kt +++ b/course/src/main/java/org/openedx/course/presentation/container/CourseContainerViewModel.kt @@ -172,13 +172,18 @@ class CourseContainerViewModel( _showProgress.value = true + interactor.startCourseSession(courseId) + viewModelScope.launch { val courseStructureFlow = interactor.getCourseStructureFlow(courseId) .catch { e -> handleFetchError(e) emit(null) } - val courseDetailsFlow = interactor.getEnrollmentDetailsFlow(courseId) + val courseDetailsFlow = interactor.getEnrollmentDetailsFlow( + courseId, + forceRefresh = true + ) .catch { emit(null) } courseStructureFlow.combine(courseDetailsFlow) { courseStructure, courseEnrollmentDetails -> courseStructure to courseEnrollmentDetails diff --git a/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesViewModel.kt b/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesViewModel.kt index 91b5c6ee5..4fa37242f 100644 --- a/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesViewModel.kt +++ b/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesViewModel.kt @@ -18,6 +18,7 @@ import org.openedx.core.domain.model.Block import org.openedx.core.domain.model.CourseBannerType import org.openedx.core.domain.model.CourseDateBlock import org.openedx.core.domain.model.CourseStructure +import org.openedx.core.exception.NoCachedDataException import org.openedx.core.extension.getSequentialBlocks import org.openedx.core.extension.getVerticalBlocks import org.openedx.core.presentation.settings.calendarsync.CalendarSyncDialogType @@ -75,7 +76,7 @@ class CourseDatesViewModel( courseNotifier.notifier.collect { event -> when (event) { is RefreshDates -> { - loadingCourseDatesInternal() + loadingCourseDatesInternal(forceRefresh = true) } } } @@ -91,15 +92,23 @@ class CourseDatesViewModel( } } - loadingCourseDatesInternal() + loadingCourseDatesInternal(forceRefresh = false) } - private fun loadingCourseDatesInternal() { + private fun loadingCourseDatesInternal(forceRefresh: Boolean) { viewModelScope.launch { try { - courseStructure = interactor.getCourseStructure(courseId = courseId) + courseStructure = if (forceRefresh) { + interactor.getCourseStructure(courseId = courseId, isNeedRefresh = true) + } else { + interactor.getCourseStructure(courseId = courseId) + } isSelfPaced = courseStructure?.isSelfPaced ?: false - val datesResponse = interactor.getCourseDates(courseId = courseId) + val datesResponse = if (forceRefresh) { + interactor.getCourseDates(courseId = courseId, forceRefresh = true) + } else { + interactor.getCourseDates(courseId = courseId) + } if (datesResponse.datesSection.isEmpty()) { _uiState.value = CourseDatesUIState.Error } else { @@ -111,7 +120,7 @@ class CourseDatesViewModel( } } catch (e: Exception) { _uiState.value = CourseDatesUIState.Error - if (e.isInternetError()) { + if (e.isInternetError() || e is NoCachedDataException) { _uiMessage.emit( UIMessage.SnackBarMessage(resourceManager.getString(CoreR.string.core_error_no_connection)) ) @@ -126,7 +135,7 @@ class CourseDatesViewModel( viewModelScope.launch { try { interactor.resetCourseDates(courseId = courseId) - loadingCourseDatesInternal() + loadingCourseDatesInternal(forceRefresh = true) courseNotifier.send(CourseDatesShifted) onResetDates(true) } catch (e: Exception) { diff --git a/course/src/main/java/org/openedx/course/presentation/progress/CourseProgressViewModel.kt b/course/src/main/java/org/openedx/course/presentation/progress/CourseProgressViewModel.kt index 805f486d1..4b8bc42e1 100644 --- a/course/src/main/java/org/openedx/course/presentation/progress/CourseProgressViewModel.kt +++ b/course/src/main/java/org/openedx/course/presentation/progress/CourseProgressViewModel.kt @@ -41,7 +41,7 @@ class CourseProgressViewModel( private fun collectData(isRefresh: Boolean) { viewModelScope.launch { val courseProgressFlow = interactor.getCourseProgress(courseId, isRefresh, false) - val courseStructureFlow = interactor.getCourseStructureFlow(courseId) + val courseStructureFlow = interactor.getCourseStructureFlow(courseId, false) combine( courseProgressFlow, diff --git a/course/src/test/java/org/openedx/course/presentation/container/CourseContainerViewModelTest.kt b/course/src/test/java/org/openedx/course/presentation/container/CourseContainerViewModelTest.kt index c64ce59a3..f7ff70cbd 100644 --- a/course/src/test/java/org/openedx/course/presentation/container/CourseContainerViewModelTest.kt +++ b/course/src/test/java/org/openedx/course/presentation/container/CourseContainerViewModelTest.kt @@ -5,6 +5,7 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every +import io.mockk.justRun import io.mockk.mockk import io.mockk.spyk import io.mockk.verify @@ -76,6 +77,7 @@ class CourseContainerViewModelTest { every { corePreferences.appConfig } returns CoreMocks.mockAppConfig every { courseNotifier.notifier } returns emptyFlow() every { config.getApiHostURL() } returns "baseUrl" + justRun { interactor.startCourseSession(any()) } coEvery { interactor.getEnrollmentDetails(any()) } returns CoreMocks.mockCourseEnrollmentDetails every { imageProcessor.loadImage(any(), any(), any()) } returns Unit every { imageProcessor.applyBlur(any(), any()) } returns mockBitmap @@ -109,7 +111,7 @@ class CourseContainerViewModelTest { interactor.getCourseStructureFlow(any(), any()) } returns flowOf(null) coEvery { - interactor.getEnrollmentDetailsFlow(any()) + interactor.getEnrollmentDetailsFlow(any(), any()) } returns flow { throw Exception() } every { analytics.logScreenEvent( @@ -126,7 +128,7 @@ class CourseContainerViewModelTest { viewModel.fetchCourseDetails() advanceUntilIdle() - coVerify(exactly = 1) { interactor.getEnrollmentDetailsFlow(any()) } + coVerify(exactly = 1) { interactor.getEnrollmentDetailsFlow(any(), any()) } verify(exactly = 1) { analytics.logScreenEvent( CourseAnalyticsEvent.DASHBOARD.eventName, @@ -164,7 +166,7 @@ class CourseContainerViewModelTest { coEvery { interactor.getCourseStructureFlow(any(), any()) } returns flowOf( CoreMocks.mockCourseStructure ) - coEvery { interactor.getEnrollmentDetailsFlow(any()) } returns flowOf( + coEvery { interactor.getEnrollmentDetailsFlow(any(), any()) } returns flowOf( CoreMocks.mockCourseEnrollmentDetails ) every { @@ -182,7 +184,7 @@ class CourseContainerViewModelTest { viewModel.fetchCourseDetails() advanceUntilIdle() - coVerify(exactly = 1) { interactor.getEnrollmentDetailsFlow(any()) } + coVerify(exactly = 1) { interactor.getEnrollmentDetailsFlow(any(), any()) } verify(exactly = 1) { analytics.logScreenEvent( CourseAnalyticsEvent.DASHBOARD.eventName, @@ -221,7 +223,7 @@ class CourseContainerViewModelTest { coEvery { interactor.getCourseStructureFlow(any(), any()) } returns flowOf( CoreMocks.mockCourseStructure ) - coEvery { interactor.getEnrollmentDetailsFlow(any()) } returns flowOf( + coEvery { interactor.getEnrollmentDetailsFlow(any(), any()) } returns flowOf( CoreMocks.mockCourseEnrollmentDetails ) every { diff --git a/course/src/test/java/org/openedx/course/presentation/dates/CourseDatesViewModelTest.kt b/course/src/test/java/org/openedx/course/presentation/dates/CourseDatesViewModelTest.kt index ca9b996a3..69371cc5b 100644 --- a/course/src/test/java/org/openedx/course/presentation/dates/CourseDatesViewModelTest.kt +++ b/course/src/test/java/org/openedx/course/presentation/dates/CourseDatesViewModelTest.kt @@ -111,7 +111,7 @@ class CourseDatesViewModelTest { courseRouter, calendarRouter, ) - coEvery { interactor.getCourseDates(any()) } throws UnknownHostException() + coEvery { interactor.getCourseDates(any(), any()) } throws UnknownHostException() val message = async { withTimeoutOrNull(5000) { viewModel.uiMessage.first() as? UIMessage.SnackBarMessage @@ -119,7 +119,7 @@ class CourseDatesViewModelTest { } advanceUntilIdle() - coVerify(exactly = 1) { interactor.getCourseDates(any()) } + coVerify(exactly = 1) { interactor.getCourseDates(any(), any()) } Assert.assertEquals(noInternet, message.await()?.message) assert(viewModel.uiState.value is CourseDatesUIState.Error) @@ -141,7 +141,7 @@ class CourseDatesViewModelTest { courseRouter, calendarRouter, ) - coEvery { interactor.getCourseDates(any()) } throws Exception() + coEvery { interactor.getCourseDates(any(), any()) } throws Exception() val message = async { withTimeoutOrNull(5000) { viewModel.uiMessage.first() as? UIMessage.SnackBarMessage @@ -149,7 +149,7 @@ class CourseDatesViewModelTest { } advanceUntilIdle() - coVerify(exactly = 1) { interactor.getCourseDates(any()) } + coVerify(exactly = 1) { interactor.getCourseDates(any(), any()) } assert(message.await()?.message.isNullOrEmpty()) assert(viewModel.uiState.value is CourseDatesUIState.Error) @@ -171,7 +171,12 @@ class CourseDatesViewModelTest { courseRouter, calendarRouter, ) - coEvery { interactor.getCourseDates(any()) } returns CourseMocks.courseDatesResultWithData + coEvery { + interactor.getCourseDates( + any(), + any() + ) + } returns CourseMocks.courseDatesResultWithData val message = async { withTimeoutOrNull(5000) { viewModel.uiMessage.first() as? UIMessage.SnackBarMessage @@ -179,7 +184,7 @@ class CourseDatesViewModelTest { } advanceUntilIdle() - coVerify(exactly = 1) { interactor.getCourseDates(any()) } + coVerify(exactly = 1) { interactor.getCourseDates(any(), any()) } assert(message.await()?.message.isNullOrEmpty()) assert(viewModel.uiState.value is CourseDatesUIState.CourseDates) @@ -201,7 +206,7 @@ class CourseDatesViewModelTest { courseRouter, calendarRouter, ) - coEvery { interactor.getCourseDates(any()) } returns CourseDatesResult( + coEvery { interactor.getCourseDates(any(), any()) } returns CourseDatesResult( datesSection = linkedMapOf(), courseBanner = CoreMocks.mockCourseDatesBannerInfo, ) @@ -212,7 +217,7 @@ class CourseDatesViewModelTest { } advanceUntilIdle() - coVerify(exactly = 1) { interactor.getCourseDates(any()) } + coVerify(exactly = 1) { interactor.getCourseDates(any(), any()) } assert(message.await()?.message.isNullOrEmpty()) assert(viewModel.uiState.value is CourseDatesUIState.Error) diff --git a/course/src/test/java/org/openedx/course/presentation/home/CourseHomeViewModelTest.kt b/course/src/test/java/org/openedx/course/presentation/home/CourseHomeViewModelTest.kt index 5387d7965..0f4bc9b0e 100644 --- a/course/src/test/java/org/openedx/course/presentation/home/CourseHomeViewModelTest.kt +++ b/course/src/test/java/org/openedx/course/presentation/home/CourseHomeViewModelTest.kt @@ -139,12 +139,17 @@ class CourseHomeViewModelTest { ) ) } - coEvery { interactor.getCourseStatusFlow(courseId) } returns flow { + coEvery { interactor.getCourseStatusFlow(courseId, any()) } returns flow { emit( CoreMocks.mockCourseComponentStatus ) } - coEvery { interactor.getCourseDatesFlow(courseId) } returns flow { emit(CoreMocks.mockCourseDatesResult) } + coEvery { + interactor.getCourseDatesFlow( + courseId, + any() + ) + } returns flow { emit(CoreMocks.mockCourseDatesResult) } coEvery { interactor.getCourseProgress( courseId, @@ -177,8 +182,8 @@ class CourseHomeViewModelTest { advanceUntilIdle() coVerify { interactor.getCourseStructureFlow(courseId, false) } - coVerify { interactor.getCourseStatusFlow(courseId) } - coVerify { interactor.getCourseDatesFlow(courseId) } + coVerify { interactor.getCourseStatusFlow(courseId, any()) } + coVerify { interactor.getCourseDatesFlow(courseId, any()) } coVerify { interactor.getCourseProgress(courseId, false, true) } assertTrue(viewModel.uiState.value is CourseHomeUIState.CourseData) @@ -196,12 +201,17 @@ class CourseHomeViewModelTest { false ) } returns flow { throw UnknownHostException() } - coEvery { interactor.getCourseStatusFlow(courseId) } returns flow { + coEvery { interactor.getCourseStatusFlow(courseId, any()) } returns flow { emit( CoreMocks.mockCourseComponentStatus ) } - coEvery { interactor.getCourseDatesFlow(courseId) } returns flow { emit(CoreMocks.mockCourseDatesResult) } + coEvery { + interactor.getCourseDatesFlow( + courseId, + any() + ) + } returns flow { emit(CoreMocks.mockCourseDatesResult) } coEvery { interactor.getCourseProgress( courseId, @@ -244,12 +254,17 @@ class CourseHomeViewModelTest { false ) } returns flow { throw Exception() } - coEvery { interactor.getCourseStatusFlow(courseId) } returns flow { + coEvery { interactor.getCourseStatusFlow(courseId, any()) } returns flow { emit( CoreMocks.mockCourseComponentStatus ) } - coEvery { interactor.getCourseDatesFlow(courseId) } returns flow { emit(CoreMocks.mockCourseDatesResult) } + coEvery { + interactor.getCourseDatesFlow( + courseId, + any() + ) + } returns flow { emit(CoreMocks.mockCourseDatesResult) } coEvery { interactor.getCourseProgress( courseId, @@ -293,12 +308,17 @@ class CourseHomeViewModelTest { CoreMocks.mockCourseStructure ) } - coEvery { interactor.getCourseStatusFlow(courseId) } returns flow { + coEvery { interactor.getCourseStatusFlow(courseId, any()) } returns flow { emit( CoreMocks.mockCourseComponentStatus ) } - coEvery { interactor.getCourseDatesFlow(courseId) } returns flow { emit(CoreMocks.mockCourseDatesResult) } + coEvery { + interactor.getCourseDatesFlow( + courseId, + any() + ) + } returns flow { emit(CoreMocks.mockCourseDatesResult) } coEvery { interactor.getCourseProgress( courseId, @@ -342,12 +362,17 @@ class CourseHomeViewModelTest { CoreMocks.mockCourseStructure ) } - coEvery { interactor.getCourseStatusFlow(courseId) } returns flow { + coEvery { interactor.getCourseStatusFlow(courseId, any()) } returns flow { emit( CoreMocks.mockCourseComponentStatus ) } - coEvery { interactor.getCourseDatesFlow(courseId) } returns flow { emit(CoreMocks.mockCourseDatesResult) } + coEvery { + interactor.getCourseDatesFlow( + courseId, + any() + ) + } returns flow { emit(CoreMocks.mockCourseDatesResult) } coEvery { interactor.getCourseProgress( courseId, @@ -399,12 +424,17 @@ class CourseHomeViewModelTest { CoreMocks.mockCourseStructure ) } - coEvery { interactor.getCourseStatusFlow(courseId) } returns flow { + coEvery { interactor.getCourseStatusFlow(courseId, any()) } returns flow { emit( CoreMocks.mockCourseComponentStatus ) } - coEvery { interactor.getCourseDatesFlow(courseId) } returns flow { emit(CoreMocks.mockCourseDatesResult) } + coEvery { + interactor.getCourseDatesFlow( + courseId, + any() + ) + } returns flow { emit(CoreMocks.mockCourseDatesResult) } coEvery { interactor.getCourseProgress( courseId, @@ -450,47 +480,8 @@ class CourseHomeViewModelTest { @Test fun `logVideoClick analytics event`() = runTest { - coEvery { interactor.getCourseStructureFlow(courseId, false) } returns flow { - emit( - CoreMocks.mockCourseStructure.copy( - id = courseId, - name = courseTitle - ) - ) - } - coEvery { interactor.getCourseStatusFlow(courseId) } returns flow { - emit( - CoreMocks.mockCourseComponentStatus - ) - } - coEvery { interactor.getCourseDatesFlow(courseId) } returns flow { emit(CoreMocks.mockCourseDatesResult) } - coEvery { - interactor.getCourseProgress( - courseId, - false, - true - ) - } returns flow { emit(CoreMocks.mockCourseProgress) } - - val viewModel = CourseHomeViewModel( - courseId = courseId, - courseTitle = courseTitle, - config = config, - interactor = interactor, - resourceManager = resourceManager, - courseNotifier = courseNotifier, - networkConnection = networkConnection, - preferencesManager = preferencesManager, - analytics = analytics, - downloadDialogManager = downloadDialogManager, - fileUtil = fileUtil, - courseRouter = courseRouter, - videoPreviewHelper = videoPreviewHelper, - coreAnalytics = coreAnalytics, - downloadDao = downloadDao, - workerController = workerController, - downloadHelper = downloadHelper - ) + stubCourseDataFlows() + val viewModel = createViewModel() advanceUntilIdle() @@ -511,47 +502,8 @@ class CourseHomeViewModelTest { @Test fun `logAssignmentClick analytics event`() = runTest { - coEvery { interactor.getCourseStructureFlow(courseId, false) } returns flow { - emit( - CoreMocks.mockCourseStructure.copy( - id = courseId, - name = courseTitle - ) - ) - } - coEvery { interactor.getCourseStatusFlow(courseId) } returns flow { - emit( - CoreMocks.mockCourseComponentStatus - ) - } - coEvery { interactor.getCourseDatesFlow(courseId) } returns flow { emit(CoreMocks.mockCourseDatesResult) } - coEvery { - interactor.getCourseProgress( - courseId, - false, - true - ) - } returns flow { emit(CoreMocks.mockCourseProgress) } - - val viewModel = CourseHomeViewModel( - courseId = courseId, - courseTitle = courseTitle, - config = config, - interactor = interactor, - resourceManager = resourceManager, - courseNotifier = courseNotifier, - networkConnection = networkConnection, - preferencesManager = preferencesManager, - analytics = analytics, - downloadDialogManager = downloadDialogManager, - fileUtil = fileUtil, - courseRouter = courseRouter, - videoPreviewHelper = videoPreviewHelper, - coreAnalytics = coreAnalytics, - downloadDao = downloadDao, - workerController = workerController, - downloadHelper = downloadHelper - ) + stubCourseDataFlows() + val viewModel = createViewModel() advanceUntilIdle() @@ -577,12 +529,17 @@ class CourseHomeViewModelTest { CoreMocks.mockCourseStructure ) } - coEvery { interactor.getCourseStatusFlow(courseId) } returns flow { + coEvery { interactor.getCourseStatusFlow(courseId, any()) } returns flow { emit( CoreMocks.mockCourseComponentStatus ) } - coEvery { interactor.getCourseDatesFlow(courseId) } returns flow { emit(CoreMocks.mockCourseDatesResult) } + coEvery { + interactor.getCourseDatesFlow( + courseId, + any() + ) + } returns flow { emit(CoreMocks.mockCourseDatesResult) } coEvery { interactor.getCourseProgress( courseId, @@ -633,12 +590,17 @@ class CourseHomeViewModelTest { CoreMocks.mockCourseStructure ) } - coEvery { interactor.getCourseStatusFlow(courseId) } returns flow { + coEvery { interactor.getCourseStatusFlow(courseId, any()) } returns flow { emit( CoreMocks.mockCourseComponentStatus ) } - coEvery { interactor.getCourseDatesFlow(courseId) } returns flow { emit(CoreMocks.mockCourseDatesResult) } + coEvery { + interactor.getCourseDatesFlow( + courseId, + any() + ) + } returns flow { emit(CoreMocks.mockCourseDatesResult) } coEvery { interactor.getCourseProgress( courseId, @@ -681,12 +643,17 @@ class CourseHomeViewModelTest { CoreMocks.mockCourseStructure ) } - coEvery { interactor.getCourseStatusFlow(courseId) } returns flow { + coEvery { interactor.getCourseStatusFlow(courseId, any()) } returns flow { emit( CoreMocks.mockCourseComponentStatus ) } - coEvery { interactor.getCourseDatesFlow(courseId) } returns flow { emit(CoreMocks.mockCourseDatesResult) } + coEvery { + interactor.getCourseDatesFlow( + courseId, + any() + ) + } returns flow { emit(CoreMocks.mockCourseDatesResult) } coEvery { interactor.getCourseProgress( courseId, @@ -729,12 +696,17 @@ class CourseHomeViewModelTest { CoreMocks.mockCourseStructure ) } - coEvery { interactor.getCourseStatusFlow(courseId) } returns flow { + coEvery { interactor.getCourseStatusFlow(courseId, any()) } returns flow { emit( CoreMocks.mockCourseComponentStatus ) } - coEvery { interactor.getCourseDatesFlow(courseId) } returns flow { emit(CoreMocks.mockCourseDatesResult) } + coEvery { + interactor.getCourseDatesFlow( + courseId, + any() + ) + } returns flow { emit(CoreMocks.mockCourseDatesResult) } coEvery { interactor.getCourseProgress( courseId, @@ -775,12 +747,17 @@ class CourseHomeViewModelTest { CoreMocks.mockCourseStructure ) } - coEvery { interactor.getCourseStatusFlow(courseId) } returns flow { + coEvery { interactor.getCourseStatusFlow(courseId, any()) } returns flow { emit( CoreMocks.mockCourseComponentStatus ) } - coEvery { interactor.getCourseDatesFlow(courseId) } returns flow { emit(CoreMocks.mockCourseDatesResult) } + coEvery { + interactor.getCourseDatesFlow( + courseId, + any() + ) + } returns flow { emit(CoreMocks.mockCourseDatesResult) } coEvery { interactor.getCourseProgress( courseId, @@ -825,12 +802,17 @@ class CourseHomeViewModelTest { CoreMocks.mockCourseStructure ) } - coEvery { interactor.getCourseStatusFlow(courseId) } returns flow { + coEvery { interactor.getCourseStatusFlow(courseId, any()) } returns flow { emit( CoreMocks.mockCourseComponentStatus ) } - coEvery { interactor.getCourseDatesFlow(courseId) } returns flow { emit(CoreMocks.mockCourseDatesResult) } + coEvery { + interactor.getCourseDatesFlow( + courseId, + any() + ) + } returns flow { emit(CoreMocks.mockCourseDatesResult) } coEvery { interactor.getCourseProgress( courseId, @@ -861,4 +843,55 @@ class CourseHomeViewModelTest { assertTrue(viewModel.isCourseDropdownNavigationEnabled) } + + private fun stubCourseDataFlows() { + coEvery { interactor.getCourseStructureFlow(courseId, false) } returns flow { + emit( + CoreMocks.mockCourseStructure.copy( + id = courseId, + name = courseTitle + ) + ) + } + coEvery { interactor.getCourseStatusFlow(courseId, any()) } returns flow { + emit( + CoreMocks.mockCourseComponentStatus + ) + } + coEvery { + interactor.getCourseDatesFlow( + courseId, + any() + ) + } returns flow { emit(CoreMocks.mockCourseDatesResult) } + coEvery { + interactor.getCourseProgress( + courseId, + false, + true + ) + } returns flow { emit(CoreMocks.mockCourseProgress) } + } + + private fun createViewModel(): CourseHomeViewModel { + return CourseHomeViewModel( + courseId = courseId, + courseTitle = courseTitle, + config = config, + interactor = interactor, + resourceManager = resourceManager, + courseNotifier = courseNotifier, + networkConnection = networkConnection, + preferencesManager = preferencesManager, + analytics = analytics, + downloadDialogManager = downloadDialogManager, + fileUtil = fileUtil, + courseRouter = courseRouter, + videoPreviewHelper = videoPreviewHelper, + coreAnalytics = coreAnalytics, + downloadDao = downloadDao, + workerController = workerController, + downloadHelper = downloadHelper + ) + } } diff --git a/course/src/test/java/org/openedx/course/presentation/outline/CourseOutlineViewModelTest.kt b/course/src/test/java/org/openedx/course/presentation/outline/CourseOutlineViewModelTest.kt index 381e09948..88b41bd09 100644 --- a/course/src/test/java/org/openedx/course/presentation/outline/CourseOutlineViewModelTest.kt +++ b/course/src/test/java/org/openedx/course/presentation/outline/CourseOutlineViewModelTest.kt @@ -90,8 +90,13 @@ class CourseOutlineViewModelTest { every { downloadDialogManager.showDownloadFailedPopup(any(), any()) } returns Unit every { preferencesManager.isRelativeDatesEnabled } returns true - coEvery { interactor.getCourseDates(any()) } returns CoreMocks.mockCourseDatesResult - coEvery { interactor.getCourseDatesFlow(any()) } returns flowOf(CoreMocks.mockCourseDatesResult) + coEvery { interactor.getCourseDates(any(), any()) } returns CoreMocks.mockCourseDatesResult + coEvery { + interactor.getCourseDatesFlow( + any(), + any() + ) + } returns flowOf(CoreMocks.mockCourseDatesResult) } @After @@ -120,7 +125,12 @@ class CourseOutlineViewModelTest { any(), ) } returns Unit - coEvery { interactor.getCourseStatusFlow(any()) } returns flow { throw UnknownHostException() } + coEvery { + interactor.getCourseStatusFlow( + any(), + any() + ) + } returns flow { throw UnknownHostException() } val viewModel = CourseContentAllViewModel( "", @@ -148,7 +158,7 @@ class CourseOutlineViewModelTest { advanceUntilIdle() coVerify(exactly = 2) { interactor.getCourseStructureFlow(any(), any()) } - coVerify(exactly = 2) { interactor.getCourseStatusFlow(any()) } + coVerify(exactly = 2) { interactor.getCourseStatusFlow(any(), any()) } assertEquals(noInternet, message.await()?.message) assert(viewModel.uiState.value is CourseContentAllUIState.Error) @@ -162,7 +172,7 @@ class CourseOutlineViewModelTest { ) every { networkConnection.isOnline() } returns true every { downloadDao.getAllDataFlow() } returns flow { emit(emptyList()) } - coEvery { interactor.getCourseStatusFlow(any()) } returns flow { throw Exception() } + coEvery { interactor.getCourseStatusFlow(any(), any()) } returns flow { throw Exception() } val viewModel = CourseContentAllViewModel( "", "", @@ -189,7 +199,7 @@ class CourseOutlineViewModelTest { advanceUntilIdle() coVerify(exactly = 2) { interactor.getCourseStructureFlow(any(), any()) } - coVerify(exactly = 2) { interactor.getCourseStatusFlow(any()) } + coVerify(exactly = 2) { interactor.getCourseStatusFlow(any(), any()) } assertEquals(somethingWrong, message.await()?.message) assert(viewModel.uiState.value is CourseContentAllUIState.Error) @@ -211,7 +221,9 @@ class CourseOutlineViewModelTest { ) ) } - coEvery { interactor.getCourseStatusFlow(any()) } returns flowOf(CourseComponentStatus("id")) + coEvery { interactor.getCourseStatusFlow(any(), any()) } returns flowOf( + CourseComponentStatus("id") + ) every { config.getCourseUIConfig().isCourseDropdownNavigationEnabled } returns false val viewModel = CourseContentAllViewModel( @@ -243,7 +255,7 @@ class CourseOutlineViewModelTest { advanceUntilIdle() coVerify(exactly = 2) { interactor.getCourseStructureFlow(any(), any()) } - coVerify(exactly = 2) { interactor.getCourseStatusFlow(any()) } + coVerify(exactly = 2) { interactor.getCourseStatusFlow(any(), any()) } assert(message.await() == null) assert(viewModel.uiState.value is CourseContentAllUIState.CourseData) @@ -265,7 +277,9 @@ class CourseOutlineViewModelTest { ) ) } - coEvery { interactor.getCourseStatusFlow(any()) } returns flowOf(CourseComponentStatus("id")) + coEvery { interactor.getCourseStatusFlow(any(), any()) } returns flowOf( + CourseComponentStatus("id") + ) every { config.getCourseUIConfig().isCourseDropdownNavigationEnabled } returns false val viewModel = CourseContentAllViewModel( @@ -296,7 +310,7 @@ class CourseOutlineViewModelTest { advanceUntilIdle() coVerify(exactly = 2) { interactor.getCourseStructureFlow(any(), any()) } - coVerify(exactly = 2) { interactor.getCourseStatusFlow(any()) } + coVerify(exactly = 2) { interactor.getCourseStatusFlow(any(), any()) } assert(message.await() == null) assert(viewModel.uiState.value is CourseContentAllUIState.CourseData) @@ -318,7 +332,9 @@ class CourseOutlineViewModelTest { ) ) } - coEvery { interactor.getCourseStatusFlow(any()) } returns flowOf(CourseComponentStatus("id")) + coEvery { interactor.getCourseStatusFlow(any(), any()) } returns flowOf( + CourseComponentStatus("id") + ) every { config.getCourseUIConfig().isCourseDropdownNavigationEnabled } returns false val viewModel = CourseContentAllViewModel( @@ -349,7 +365,7 @@ class CourseOutlineViewModelTest { advanceUntilIdle() coVerify(exactly = 2) { interactor.getCourseStructureFlow(any(), any()) } - coVerify(exactly = 2) { interactor.getCourseStatusFlow(any()) } + coVerify(exactly = 2) { interactor.getCourseStatusFlow(any(), any()) } assert(message.await() == null) assert(viewModel.uiState.value is CourseContentAllUIState.CourseData) @@ -363,7 +379,9 @@ class CourseOutlineViewModelTest { ) coEvery { notifier.notifier } returns flow { emit(CourseStructureUpdated("")) } every { networkConnection.isOnline() } returns true - coEvery { interactor.getCourseStatusFlow(any()) } returns flowOf(CourseComponentStatus("id")) + coEvery { interactor.getCourseStatusFlow(any(), any()) } returns flowOf( + CourseComponentStatus("id") + ) val viewModel = CourseContentAllViewModel( "", @@ -393,7 +411,7 @@ class CourseOutlineViewModelTest { advanceUntilIdle() coVerify(exactly = 3) { interactor.getCourseStructureFlow(any(), any()) } - coVerify(exactly = 3) { interactor.getCourseStatusFlow(any()) } + coVerify(exactly = 3) { interactor.getCourseStatusFlow(any(), any()) } } @Test @@ -413,7 +431,9 @@ class CourseOutlineViewModelTest { } returns Unit coEvery { workerController.saveModels(any()) } returns Unit coEvery { interactor.getCourseStatus(any()) } returns CourseComponentStatus("id") - coEvery { interactor.getCourseStatusFlow(any()) } returns flowOf(CourseComponentStatus("id")) + coEvery { interactor.getCourseStatusFlow(any(), any()) } returns flowOf( + CourseComponentStatus("id") + ) coEvery { downloadDao.getAllDataFlow() } returns flow { emit(emptyList()) } every { config.getCourseUIConfig().isCourseDropdownNavigationEnabled } returns false @@ -460,7 +480,9 @@ class CourseOutlineViewModelTest { CoreMocks.mockCourseStructure ) coEvery { interactor.getCourseStatus(any()) } returns CourseComponentStatus("id") - coEvery { interactor.getCourseStatusFlow(any()) } returns flowOf(CourseComponentStatus("id")) + coEvery { interactor.getCourseStatusFlow(any(), any()) } returns flowOf( + CourseComponentStatus("id") + ) every { preferencesManager.videoSettings.wifiDownloadOnly } returns true every { networkConnection.isWifiConnected() } returns true every { networkConnection.isOnline() } returns true