Skip to content

Commit

Permalink
Code refactoring and junit tests (openedx#391)
Browse files Browse the repository at this point in the history
* refactor: minor code style changes

* feat: CalendarViewModelTest

* feat: LearnViewModelTest
  • Loading branch information
PavloNetrebchuk authored Oct 30, 2024
1 parent 0ff62f0 commit 190dd87
Show file tree
Hide file tree
Showing 9 changed files with 258 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ class NavigationFragmentAdapter(fragment: Fragment) : FragmentStateAdapter(fragm
fun addFragment(fragment: Fragment) {
fragments.add(fragment)
}
}
}
3 changes: 1 addition & 2 deletions core/src/main/java/org/openedx/core/config/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ class Config(context: Context) {

private var configProperties: JsonObject = try {
val inputStream = context.assets.open("config/config.json")
val parser = JsonParser()
val config = parser.parse(InputStreamReader(inputStream))
val config = JsonParser.parseReader(InputStreamReader(inputStream))
config.asJsonObject
} catch (e: Exception) {
JsonObject()
Expand Down
3 changes: 0 additions & 3 deletions core/src/main/java/org/openedx/core/ui/ComposeCommon.kt
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithContent
Expand Down Expand Up @@ -210,7 +209,6 @@ fun Toolbar(
}
}

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun SearchBar(
modifier: Modifier,
Expand Down Expand Up @@ -310,7 +308,6 @@ fun SearchBar(
)
}

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun SearchBarStateless(
modifier: Modifier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ private fun CourseListItem(
Column {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(course.course.courseImage.toImageLink(apiHostUrl) ?: "")
.data(course.course.courseImage.toImageLink(apiHostUrl))
.error(CoreR.drawable.core_no_image_course)
.placeholder(CoreR.drawable.core_no_image_course)
.build(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import org.openedx.dashboard.domain.interactor.DashboardInteractor
import java.net.UnknownHostException

@OptIn(ExperimentalCoroutinesApi::class)
class DashboardViewModelTest {
class DashboardListViewModelTest {

@get:Rule
val testInstantTaskExecutorRule: TestRule = InstantTaskExecutorRule()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package org.openedx.dashboard.presentation

import androidx.fragment.app.FragmentManager
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertTrue
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.openedx.DashboardNavigator
import org.openedx.core.config.Config
import org.openedx.core.config.DashboardConfig
import org.openedx.learn.presentation.LearnViewModel

class LearnViewModelTest {

private val config = mockk<Config>()
private val dashboardRouter = mockk<DashboardRouter>(relaxed = true)
private val analytics = mockk<DashboardAnalytics>(relaxed = true)
private val fragmentManager = mockk<FragmentManager>()

private val viewModel = LearnViewModel(config, dashboardRouter, analytics)

@Test
fun `onSettingsClick calls navigateToSettings`() = runTest {
viewModel.onSettingsClick(fragmentManager)
verify { dashboardRouter.navigateToSettings(fragmentManager) }
}

@Test
fun `getDashboardFragment returns correct fragment based on dashboardType`() = runTest {
DashboardConfig.DashboardType.entries.forEach { type ->
every { config.getDashboardConfig().getType() } returns type
val dashboardFragment = viewModel.getDashboardFragment
assertEquals(DashboardNavigator(type).getDashboardFragment()::class, dashboardFragment::class)
}
}


@Test
fun `getProgramFragment returns correct program fragment`() = runTest {
viewModel.getProgramFragment
verify { dashboardRouter.getProgramFragment() }
}

@Test
fun `isProgramTypeWebView returns correct view type`() = runTest {
every { config.getProgramConfig().isViewTypeWebView() } returns true
assertTrue(viewModel.isProgramTypeWebView)
}

@Test
fun `logMyCoursesTabClickedEvent logs correct analytics event`() = runTest {
viewModel.logMyCoursesTabClickedEvent()

verify {
analytics.logScreenEvent(
screenName = DashboardAnalyticsEvent.MY_COURSES.eventName,
params = match {
it[DashboardAnalyticsKey.NAME.key] == DashboardAnalyticsEvent.MY_COURSES.biValue
}
)
}
}

@Test
fun `logMyProgramsTabClickedEvent logs correct analytics event`() = runTest {
viewModel.logMyProgramsTabClickedEvent()

verify {
analytics.logScreenEvent(
screenName = DashboardAnalyticsEvent.MY_PROGRAMS.eventName,
params = match {
it[DashboardAnalyticsKey.NAME.key] == DashboardAnalyticsEvent.MY_PROGRAMS.biValue
}
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ data class CommentResult(
authorLabel ?: "",
createdAt,
updatedAt,
rawBody ?: "",
renderedBody ?: "",
TextConverter.textToLinkedImageText(renderedBody ?: ""),
rawBody,
renderedBody,
TextConverter.textToLinkedImageText(renderedBody),
abuseFlagged,
voted,
voteCount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -675,25 +675,29 @@ private fun EditProfileScreen(
openWarningMessageDialog = true
}
},
text = stringResource(if (uiState.isLimited) R.string.profile_switch_to_full else R.string.profile_switch_to_limited),
text = stringResource(
if (uiState.isLimited) {
R.string.profile_switch_to_full
} else {
R.string.profile_switch_to_limited
}
),
color = MaterialTheme.appColors.textAccent,
style = MaterialTheme.appTypography.labelLarge
)
Spacer(modifier = Modifier.height(20.dp))
ProfileFields(
disabled = uiState.isLimited,
onFieldClick = { it, title ->
when (it) {
onFieldClick = { field, title ->
when (field) {
YEAR_OF_BIRTH -> {
serverFieldName.value = YEAR_OF_BIRTH
expandedList =
LocaleUtils.getBirthYearsRange()
expandedList = LocaleUtils.getBirthYearsRange()
}

COUNTRY -> {
serverFieldName.value = COUNTRY
expandedList =
LocaleUtils.getCountries()
expandedList = LocaleUtils.getCountries()
}

LANGUAGE -> {
Expand All @@ -706,9 +710,8 @@ private fun EditProfileScreen(
coroutine.launch {
val index = expandedList.indexOfFirst { option ->
if (serverFieldName.value == LANGUAGE) {
option.value == (mapFields[serverFieldName.value] as List<LanguageProficiency>).getOrNull(
0
)?.code
option.value == (mapFields[serverFieldName.value] as List<LanguageProficiency>)
.getOrNull(0)?.code
} else {
option.value == mapFields[serverFieldName.value]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package org.openedx.profile.presentation.profile

import androidx.activity.result.ActivityResultLauncher
import androidx.fragment.app.FragmentManager
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertTrue
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.openedx.core.data.storage.CalendarPreferences
import org.openedx.core.data.storage.CorePreferences
import org.openedx.core.domain.interactor.CalendarInteractor
import org.openedx.core.presentation.settings.calendarsync.CalendarSyncState
import org.openedx.core.system.CalendarManager
import org.openedx.core.system.connection.NetworkConnection
import org.openedx.core.system.notifier.calendar.CalendarCreated
import org.openedx.core.system.notifier.calendar.CalendarNotifier
import org.openedx.core.system.notifier.calendar.CalendarSynced
import org.openedx.core.worker.CalendarSyncScheduler
import org.openedx.profile.presentation.ProfileRouter
import org.openedx.profile.presentation.calendar.CalendarViewModel

@OptIn(ExperimentalCoroutinesApi::class)
class CalendarViewModelTest {

private val dispatcher = StandardTestDispatcher()
private lateinit var viewModel: CalendarViewModel

private val calendarSyncScheduler = mockk<CalendarSyncScheduler>(relaxed = true)
private val calendarManager = mockk<CalendarManager>(relaxed = true)
private val calendarPreferences = mockk<CalendarPreferences>(relaxed = true)
private val calendarNotifier = mockk<CalendarNotifier>(relaxed = true)
private val calendarInteractor = mockk<CalendarInteractor>(relaxed = true)
private val corePreferences = mockk<CorePreferences>(relaxed = true)
private val profileRouter = mockk<ProfileRouter>()
private val networkConnection = mockk<NetworkConnection>()
private val permissionLauncher = mockk<ActivityResultLauncher<Array<String>>>()
private val fragmentManager = mockk<FragmentManager>()

@Before
fun setup() {
Dispatchers.setMain(dispatcher)
every { networkConnection.isOnline() } returns true
viewModel = CalendarViewModel(
calendarSyncScheduler = calendarSyncScheduler,
calendarManager = calendarManager,
calendarPreferences = calendarPreferences,
calendarNotifier = calendarNotifier,
calendarInteractor = calendarInteractor,
corePreferences = corePreferences,
profileRouter = profileRouter,
networkConnection = networkConnection
)
}

@After
fun tearDown() {
Dispatchers.resetMain()
}

@Test
fun `init triggers immediate sync and loads calendar data`() = runTest(dispatcher) {
coVerify { calendarSyncScheduler.requestImmediateSync() }
coVerify { calendarInteractor.getAllCourseCalendarStateFromCache() }
}

@Test
fun `setUpCalendarSync launches permission request`() = runTest(dispatcher) {
every { permissionLauncher.launch(calendarManager.permissions) } returns Unit
viewModel.setUpCalendarSync(permissionLauncher)
coVerify { permissionLauncher.launch(calendarManager.permissions) }
}

@Test
fun `setCalendarSyncEnabled enables sync and triggers sync when isEnabled is true`() = runTest(dispatcher) {
viewModel.setCalendarSyncEnabled(isEnabled = true, fragmentManager = fragmentManager)

coVerify {
calendarPreferences.isCalendarSyncEnabled = true
calendarSyncScheduler.requestImmediateSync()
}
assertTrue(viewModel.uiState.value.isCalendarSyncEnabled)
}

@Test
fun `setRelativeDateEnabled updates preference and UI state`() = runTest(dispatcher) {
viewModel.setRelativeDateEnabled(true)

coVerify { corePreferences.isRelativeDatesEnabled = true }
assertTrue(viewModel.uiState.value.isRelativeDateEnabled)
}

@Test
fun `network disconnection changes sync state to offline`() = runTest(dispatcher) {
every { networkConnection.isOnline() } returns false
viewModel = CalendarViewModel(
calendarSyncScheduler,
calendarManager,
calendarPreferences,
calendarNotifier,
calendarInteractor,
corePreferences,
profileRouter,
networkConnection
)

assertEquals(CalendarSyncState.OFFLINE, viewModel.uiState.value.calendarSyncState)
}

@Test
fun `successful calendar sync updates sync state to SYNCED`() = runTest(dispatcher) {
viewModel = CalendarViewModel(
calendarSyncScheduler,
calendarManager,
calendarPreferences,
calendarNotifier.apply {
every { notifier } returns flowOf(CalendarSynced)
},
calendarInteractor,
corePreferences,
profileRouter,
networkConnection
)

assertEquals(CalendarSyncState.SYNCED, viewModel.uiState.value.calendarSyncState)
}

@Test
fun `calendar creation updates calendar existence state`() = runTest(dispatcher) {
every { calendarPreferences.calendarId } returns 1
every { calendarManager.isCalendarExist(1) } returns true

viewModel = CalendarViewModel(
calendarSyncScheduler,
calendarManager,
calendarPreferences,
calendarNotifier.apply {
every { notifier } returns flowOf(CalendarCreated)
},
calendarInteractor,
corePreferences,
profileRouter,
networkConnection
)

assertTrue(viewModel.uiState.value.isCalendarExist)
}
}

0 comments on commit 190dd87

Please sign in to comment.