Skip to content

Commit 190dd87

Browse files
Code refactoring and junit tests (#391)
* refactor: minor code style changes * feat: CalendarViewModelTest * feat: LearnViewModelTest
1 parent 0ff62f0 commit 190dd87

File tree

9 files changed

+258
-21
lines changed

9 files changed

+258
-21
lines changed

core/src/main/java/org/openedx/core/adapter/NavigationFragmentAdapter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ class NavigationFragmentAdapter(fragment: Fragment) : FragmentStateAdapter(fragm
1414
fun addFragment(fragment: Fragment) {
1515
fragments.add(fragment)
1616
}
17-
}
17+
}

core/src/main/java/org/openedx/core/config/Config.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ class Config(context: Context) {
1212

1313
private var configProperties: JsonObject = try {
1414
val inputStream = context.assets.open("config/config.json")
15-
val parser = JsonParser()
16-
val config = parser.parse(InputStreamReader(inputStream))
15+
val config = JsonParser.parseReader(InputStreamReader(inputStream))
1716
config.asJsonObject
1817
} catch (e: Exception) {
1918
JsonObject()

core/src/main/java/org/openedx/core/ui/ComposeCommon.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ import androidx.compose.runtime.rememberCoroutineScope
6161
import androidx.compose.runtime.saveable.rememberSaveable
6262
import androidx.compose.runtime.setValue
6363
import androidx.compose.ui.Alignment
64-
import androidx.compose.ui.ExperimentalComposeUiApi
6564
import androidx.compose.ui.Modifier
6665
import androidx.compose.ui.draw.clip
6766
import androidx.compose.ui.draw.drawWithContent
@@ -210,7 +209,6 @@ fun Toolbar(
210209
}
211210
}
212211

213-
@OptIn(ExperimentalComposeUiApi::class)
214212
@Composable
215213
fun SearchBar(
216214
modifier: Modifier,
@@ -310,7 +308,6 @@ fun SearchBar(
310308
)
311309
}
312310

313-
@OptIn(ExperimentalComposeUiApi::class)
314311
@Composable
315312
fun SearchBarStateless(
316313
modifier: Modifier,

dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryView.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ private fun CourseListItem(
434434
Column {
435435
AsyncImage(
436436
model = ImageRequest.Builder(LocalContext.current)
437-
.data(course.course.courseImage.toImageLink(apiHostUrl) ?: "")
437+
.data(course.course.courseImage.toImageLink(apiHostUrl))
438438
.error(CoreR.drawable.core_no_image_course)
439439
.placeholder(CoreR.drawable.core_no_image_course)
440440
.build(),

dashboard/src/test/java/org/openedx/dashboard/presentation/DashboardViewModelTest.kt renamed to dashboard/src/test/java/org/openedx/dashboard/presentation/DashboardListViewModelTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import org.openedx.dashboard.domain.interactor.DashboardInteractor
3838
import java.net.UnknownHostException
3939

4040
@OptIn(ExperimentalCoroutinesApi::class)
41-
class DashboardViewModelTest {
41+
class DashboardListViewModelTest {
4242

4343
@get:Rule
4444
val testInstantTaskExecutorRule: TestRule = InstantTaskExecutorRule()
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package org.openedx.dashboard.presentation
2+
3+
import androidx.fragment.app.FragmentManager
4+
import io.mockk.every
5+
import io.mockk.mockk
6+
import io.mockk.verify
7+
import junit.framework.TestCase.assertEquals
8+
import junit.framework.TestCase.assertTrue
9+
import kotlinx.coroutines.test.runTest
10+
import org.junit.Test
11+
import org.openedx.DashboardNavigator
12+
import org.openedx.core.config.Config
13+
import org.openedx.core.config.DashboardConfig
14+
import org.openedx.learn.presentation.LearnViewModel
15+
16+
class LearnViewModelTest {
17+
18+
private val config = mockk<Config>()
19+
private val dashboardRouter = mockk<DashboardRouter>(relaxed = true)
20+
private val analytics = mockk<DashboardAnalytics>(relaxed = true)
21+
private val fragmentManager = mockk<FragmentManager>()
22+
23+
private val viewModel = LearnViewModel(config, dashboardRouter, analytics)
24+
25+
@Test
26+
fun `onSettingsClick calls navigateToSettings`() = runTest {
27+
viewModel.onSettingsClick(fragmentManager)
28+
verify { dashboardRouter.navigateToSettings(fragmentManager) }
29+
}
30+
31+
@Test
32+
fun `getDashboardFragment returns correct fragment based on dashboardType`() = runTest {
33+
DashboardConfig.DashboardType.entries.forEach { type ->
34+
every { config.getDashboardConfig().getType() } returns type
35+
val dashboardFragment = viewModel.getDashboardFragment
36+
assertEquals(DashboardNavigator(type).getDashboardFragment()::class, dashboardFragment::class)
37+
}
38+
}
39+
40+
41+
@Test
42+
fun `getProgramFragment returns correct program fragment`() = runTest {
43+
viewModel.getProgramFragment
44+
verify { dashboardRouter.getProgramFragment() }
45+
}
46+
47+
@Test
48+
fun `isProgramTypeWebView returns correct view type`() = runTest {
49+
every { config.getProgramConfig().isViewTypeWebView() } returns true
50+
assertTrue(viewModel.isProgramTypeWebView)
51+
}
52+
53+
@Test
54+
fun `logMyCoursesTabClickedEvent logs correct analytics event`() = runTest {
55+
viewModel.logMyCoursesTabClickedEvent()
56+
57+
verify {
58+
analytics.logScreenEvent(
59+
screenName = DashboardAnalyticsEvent.MY_COURSES.eventName,
60+
params = match {
61+
it[DashboardAnalyticsKey.NAME.key] == DashboardAnalyticsEvent.MY_COURSES.biValue
62+
}
63+
)
64+
}
65+
}
66+
67+
@Test
68+
fun `logMyProgramsTabClickedEvent logs correct analytics event`() = runTest {
69+
viewModel.logMyProgramsTabClickedEvent()
70+
71+
verify {
72+
analytics.logScreenEvent(
73+
screenName = DashboardAnalyticsEvent.MY_PROGRAMS.eventName,
74+
params = match {
75+
it[DashboardAnalyticsKey.NAME.key] == DashboardAnalyticsEvent.MY_PROGRAMS.biValue
76+
}
77+
)
78+
}
79+
}
80+
}

discussion/src/main/java/org/openedx/discussion/data/model/response/CommentsResponse.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@ data class CommentResult(
7676
authorLabel ?: "",
7777
createdAt,
7878
updatedAt,
79-
rawBody ?: "",
80-
renderedBody ?: "",
81-
TextConverter.textToLinkedImageText(renderedBody ?: ""),
79+
rawBody,
80+
renderedBody,
81+
TextConverter.textToLinkedImageText(renderedBody),
8282
abuseFlagged,
8383
voted,
8484
voteCount,

profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileFragment.kt

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -675,25 +675,29 @@ private fun EditProfileScreen(
675675
openWarningMessageDialog = true
676676
}
677677
},
678-
text = stringResource(if (uiState.isLimited) R.string.profile_switch_to_full else R.string.profile_switch_to_limited),
678+
text = stringResource(
679+
if (uiState.isLimited) {
680+
R.string.profile_switch_to_full
681+
} else {
682+
R.string.profile_switch_to_limited
683+
}
684+
),
679685
color = MaterialTheme.appColors.textAccent,
680686
style = MaterialTheme.appTypography.labelLarge
681687
)
682688
Spacer(modifier = Modifier.height(20.dp))
683689
ProfileFields(
684690
disabled = uiState.isLimited,
685-
onFieldClick = { it, title ->
686-
when (it) {
691+
onFieldClick = { field, title ->
692+
when (field) {
687693
YEAR_OF_BIRTH -> {
688694
serverFieldName.value = YEAR_OF_BIRTH
689-
expandedList =
690-
LocaleUtils.getBirthYearsRange()
695+
expandedList = LocaleUtils.getBirthYearsRange()
691696
}
692697

693698
COUNTRY -> {
694699
serverFieldName.value = COUNTRY
695-
expandedList =
696-
LocaleUtils.getCountries()
700+
expandedList = LocaleUtils.getCountries()
697701
}
698702

699703
LANGUAGE -> {
@@ -706,9 +710,8 @@ private fun EditProfileScreen(
706710
coroutine.launch {
707711
val index = expandedList.indexOfFirst { option ->
708712
if (serverFieldName.value == LANGUAGE) {
709-
option.value == (mapFields[serverFieldName.value] as List<LanguageProficiency>).getOrNull(
710-
0
711-
)?.code
713+
option.value == (mapFields[serverFieldName.value] as List<LanguageProficiency>)
714+
.getOrNull(0)?.code
712715
} else {
713716
option.value == mapFields[serverFieldName.value]
714717
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package org.openedx.profile.presentation.profile
2+
3+
import androidx.activity.result.ActivityResultLauncher
4+
import androidx.fragment.app.FragmentManager
5+
import io.mockk.coVerify
6+
import io.mockk.every
7+
import io.mockk.mockk
8+
import junit.framework.TestCase.assertEquals
9+
import junit.framework.TestCase.assertTrue
10+
import kotlinx.coroutines.Dispatchers
11+
import kotlinx.coroutines.ExperimentalCoroutinesApi
12+
import kotlinx.coroutines.flow.flowOf
13+
import kotlinx.coroutines.test.StandardTestDispatcher
14+
import kotlinx.coroutines.test.resetMain
15+
import kotlinx.coroutines.test.runTest
16+
import kotlinx.coroutines.test.setMain
17+
import org.junit.After
18+
import org.junit.Before
19+
import org.junit.Test
20+
import org.openedx.core.data.storage.CalendarPreferences
21+
import org.openedx.core.data.storage.CorePreferences
22+
import org.openedx.core.domain.interactor.CalendarInteractor
23+
import org.openedx.core.presentation.settings.calendarsync.CalendarSyncState
24+
import org.openedx.core.system.CalendarManager
25+
import org.openedx.core.system.connection.NetworkConnection
26+
import org.openedx.core.system.notifier.calendar.CalendarCreated
27+
import org.openedx.core.system.notifier.calendar.CalendarNotifier
28+
import org.openedx.core.system.notifier.calendar.CalendarSynced
29+
import org.openedx.core.worker.CalendarSyncScheduler
30+
import org.openedx.profile.presentation.ProfileRouter
31+
import org.openedx.profile.presentation.calendar.CalendarViewModel
32+
33+
@OptIn(ExperimentalCoroutinesApi::class)
34+
class CalendarViewModelTest {
35+
36+
private val dispatcher = StandardTestDispatcher()
37+
private lateinit var viewModel: CalendarViewModel
38+
39+
private val calendarSyncScheduler = mockk<CalendarSyncScheduler>(relaxed = true)
40+
private val calendarManager = mockk<CalendarManager>(relaxed = true)
41+
private val calendarPreferences = mockk<CalendarPreferences>(relaxed = true)
42+
private val calendarNotifier = mockk<CalendarNotifier>(relaxed = true)
43+
private val calendarInteractor = mockk<CalendarInteractor>(relaxed = true)
44+
private val corePreferences = mockk<CorePreferences>(relaxed = true)
45+
private val profileRouter = mockk<ProfileRouter>()
46+
private val networkConnection = mockk<NetworkConnection>()
47+
private val permissionLauncher = mockk<ActivityResultLauncher<Array<String>>>()
48+
private val fragmentManager = mockk<FragmentManager>()
49+
50+
@Before
51+
fun setup() {
52+
Dispatchers.setMain(dispatcher)
53+
every { networkConnection.isOnline() } returns true
54+
viewModel = CalendarViewModel(
55+
calendarSyncScheduler = calendarSyncScheduler,
56+
calendarManager = calendarManager,
57+
calendarPreferences = calendarPreferences,
58+
calendarNotifier = calendarNotifier,
59+
calendarInteractor = calendarInteractor,
60+
corePreferences = corePreferences,
61+
profileRouter = profileRouter,
62+
networkConnection = networkConnection
63+
)
64+
}
65+
66+
@After
67+
fun tearDown() {
68+
Dispatchers.resetMain()
69+
}
70+
71+
@Test
72+
fun `init triggers immediate sync and loads calendar data`() = runTest(dispatcher) {
73+
coVerify { calendarSyncScheduler.requestImmediateSync() }
74+
coVerify { calendarInteractor.getAllCourseCalendarStateFromCache() }
75+
}
76+
77+
@Test
78+
fun `setUpCalendarSync launches permission request`() = runTest(dispatcher) {
79+
every { permissionLauncher.launch(calendarManager.permissions) } returns Unit
80+
viewModel.setUpCalendarSync(permissionLauncher)
81+
coVerify { permissionLauncher.launch(calendarManager.permissions) }
82+
}
83+
84+
@Test
85+
fun `setCalendarSyncEnabled enables sync and triggers sync when isEnabled is true`() = runTest(dispatcher) {
86+
viewModel.setCalendarSyncEnabled(isEnabled = true, fragmentManager = fragmentManager)
87+
88+
coVerify {
89+
calendarPreferences.isCalendarSyncEnabled = true
90+
calendarSyncScheduler.requestImmediateSync()
91+
}
92+
assertTrue(viewModel.uiState.value.isCalendarSyncEnabled)
93+
}
94+
95+
@Test
96+
fun `setRelativeDateEnabled updates preference and UI state`() = runTest(dispatcher) {
97+
viewModel.setRelativeDateEnabled(true)
98+
99+
coVerify { corePreferences.isRelativeDatesEnabled = true }
100+
assertTrue(viewModel.uiState.value.isRelativeDateEnabled)
101+
}
102+
103+
@Test
104+
fun `network disconnection changes sync state to offline`() = runTest(dispatcher) {
105+
every { networkConnection.isOnline() } returns false
106+
viewModel = CalendarViewModel(
107+
calendarSyncScheduler,
108+
calendarManager,
109+
calendarPreferences,
110+
calendarNotifier,
111+
calendarInteractor,
112+
corePreferences,
113+
profileRouter,
114+
networkConnection
115+
)
116+
117+
assertEquals(CalendarSyncState.OFFLINE, viewModel.uiState.value.calendarSyncState)
118+
}
119+
120+
@Test
121+
fun `successful calendar sync updates sync state to SYNCED`() = runTest(dispatcher) {
122+
viewModel = CalendarViewModel(
123+
calendarSyncScheduler,
124+
calendarManager,
125+
calendarPreferences,
126+
calendarNotifier.apply {
127+
every { notifier } returns flowOf(CalendarSynced)
128+
},
129+
calendarInteractor,
130+
corePreferences,
131+
profileRouter,
132+
networkConnection
133+
)
134+
135+
assertEquals(CalendarSyncState.SYNCED, viewModel.uiState.value.calendarSyncState)
136+
}
137+
138+
@Test
139+
fun `calendar creation updates calendar existence state`() = runTest(dispatcher) {
140+
every { calendarPreferences.calendarId } returns 1
141+
every { calendarManager.isCalendarExist(1) } returns true
142+
143+
viewModel = CalendarViewModel(
144+
calendarSyncScheduler,
145+
calendarManager,
146+
calendarPreferences,
147+
calendarNotifier.apply {
148+
every { notifier } returns flowOf(CalendarCreated)
149+
},
150+
calendarInteractor,
151+
corePreferences,
152+
profileRouter,
153+
networkConnection
154+
)
155+
156+
assertTrue(viewModel.uiState.value.isCalendarExist)
157+
}
158+
}

0 commit comments

Comments
 (0)