From 01d39b960b91d7af8add1aba9b3646c072441b7b Mon Sep 17 00:00:00 2001 From: PavloNetrebchuk Date: Wed, 23 Oct 2024 12:53:45 +0300 Subject: [PATCH 1/3] refactor: minor code style changes --- .../core/adapter/NavigationFragmentAdapter.kt | 2 +- .../java/org/openedx/core/config/Config.kt | 3 +-- .../java/org/openedx/core/ui/ComposeCommon.kt | 3 --- .../presentation/DashboardGalleryView.kt | 2 +- .../data/model/response/CommentsResponse.kt | 6 ++--- .../presentation/edit/EditProfileFragment.kt | 23 +++++++++++-------- 6 files changed, 19 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/org/openedx/core/adapter/NavigationFragmentAdapter.kt b/core/src/main/java/org/openedx/core/adapter/NavigationFragmentAdapter.kt index 273c53427..708b43829 100644 --- a/core/src/main/java/org/openedx/core/adapter/NavigationFragmentAdapter.kt +++ b/core/src/main/java/org/openedx/core/adapter/NavigationFragmentAdapter.kt @@ -14,4 +14,4 @@ class NavigationFragmentAdapter(fragment: Fragment) : FragmentStateAdapter(fragm fun addFragment(fragment: Fragment) { fragments.add(fragment) } -} \ No newline at end of file +} diff --git a/core/src/main/java/org/openedx/core/config/Config.kt b/core/src/main/java/org/openedx/core/config/Config.kt index 528ff4cc8..4a378266e 100644 --- a/core/src/main/java/org/openedx/core/config/Config.kt +++ b/core/src/main/java/org/openedx/core/config/Config.kt @@ -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() diff --git a/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt b/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt index eb9f92800..736eb545c 100644 --- a/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt +++ b/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt @@ -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 @@ -210,7 +209,6 @@ fun Toolbar( } } -@OptIn(ExperimentalComposeUiApi::class) @Composable fun SearchBar( modifier: Modifier, @@ -310,7 +308,6 @@ fun SearchBar( ) } -@OptIn(ExperimentalComposeUiApi::class) @Composable fun SearchBarStateless( modifier: Modifier, diff --git a/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryView.kt b/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryView.kt index 0fd0e2ccd..1eb0cb5cf 100644 --- a/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryView.kt +++ b/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryView.kt @@ -424,7 +424,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(), diff --git a/discussion/src/main/java/org/openedx/discussion/data/model/response/CommentsResponse.kt b/discussion/src/main/java/org/openedx/discussion/data/model/response/CommentsResponse.kt index 711bab32c..a2248b036 100644 --- a/discussion/src/main/java/org/openedx/discussion/data/model/response/CommentsResponse.kt +++ b/discussion/src/main/java/org/openedx/discussion/data/model/response/CommentsResponse.kt @@ -76,9 +76,9 @@ data class CommentResult( authorLabel ?: "", createdAt, updatedAt, - rawBody ?: "", - renderedBody ?: "", - TextConverter.textToLinkedImageText(renderedBody ?: ""), + rawBody, + renderedBody, + TextConverter.textToLinkedImageText(renderedBody), abuseFlagged, voted, voteCount, diff --git a/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileFragment.kt b/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileFragment.kt index 3800d23ac..21317c3fe 100644 --- a/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileFragment.kt +++ b/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileFragment.kt @@ -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 -> { @@ -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).getOrNull( - 0 - )?.code + option.value == (mapFields[serverFieldName.value] as List) + .getOrNull(0)?.code } else { option.value == mapFields[serverFieldName.value] } From 6071d20c0d0388c0361608c5e45a3645cdd8dda4 Mon Sep 17 00:00:00 2001 From: PavloNetrebchuk Date: Mon, 28 Oct 2024 15:05:58 +0200 Subject: [PATCH 2/3] feat: CalendarViewModelTest --- .../profile/CalendarViewModelTest.kt | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 profile/src/test/java/org/openedx/profile/presentation/profile/CalendarViewModelTest.kt diff --git a/profile/src/test/java/org/openedx/profile/presentation/profile/CalendarViewModelTest.kt b/profile/src/test/java/org/openedx/profile/presentation/profile/CalendarViewModelTest.kt new file mode 100644 index 000000000..3cc8f1da2 --- /dev/null +++ b/profile/src/test/java/org/openedx/profile/presentation/profile/CalendarViewModelTest.kt @@ -0,0 +1,157 @@ +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(relaxed = true) + private val calendarManager = mockk(relaxed = true) + private val calendarPreferences = mockk(relaxed = true) + private val calendarNotifier = mockk(relaxed = true) + private val calendarInteractor = mockk(relaxed = true) + private val corePreferences = mockk(relaxed = true) + private val profileRouter = mockk(relaxed = true) + private val networkConnection = mockk(relaxed = true) + private val permissionLauncher = mockk>>(relaxed = true) + private val fragmentManager = mockk(relaxed = true) + + @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) { + 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) + } +} From 41d65f21003bb4aa014d9341c99f268b9ea4d563 Mon Sep 17 00:00:00 2001 From: PavloNetrebchuk Date: Tue, 29 Oct 2024 16:09:08 +0200 Subject: [PATCH 3/3] feat: LearnViewModelTest --- ...lTest.kt => DashboardListViewModelTest.kt} | 2 +- .../presentation/LearnViewModelTest.kt | 80 +++++++++++++++++++ .../profile/CalendarViewModelTest.kt | 9 ++- 3 files changed, 86 insertions(+), 5 deletions(-) rename dashboard/src/test/java/org/openedx/dashboard/presentation/{DashboardViewModelTest.kt => DashboardListViewModelTest.kt} (99%) create mode 100644 dashboard/src/test/java/org/openedx/dashboard/presentation/LearnViewModelTest.kt diff --git a/dashboard/src/test/java/org/openedx/dashboard/presentation/DashboardViewModelTest.kt b/dashboard/src/test/java/org/openedx/dashboard/presentation/DashboardListViewModelTest.kt similarity index 99% rename from dashboard/src/test/java/org/openedx/dashboard/presentation/DashboardViewModelTest.kt rename to dashboard/src/test/java/org/openedx/dashboard/presentation/DashboardListViewModelTest.kt index 2a1131392..216d8ecbf 100644 --- a/dashboard/src/test/java/org/openedx/dashboard/presentation/DashboardViewModelTest.kt +++ b/dashboard/src/test/java/org/openedx/dashboard/presentation/DashboardListViewModelTest.kt @@ -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() diff --git a/dashboard/src/test/java/org/openedx/dashboard/presentation/LearnViewModelTest.kt b/dashboard/src/test/java/org/openedx/dashboard/presentation/LearnViewModelTest.kt new file mode 100644 index 000000000..090dc7987 --- /dev/null +++ b/dashboard/src/test/java/org/openedx/dashboard/presentation/LearnViewModelTest.kt @@ -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() + private val dashboardRouter = mockk(relaxed = true) + private val analytics = mockk(relaxed = true) + private val fragmentManager = mockk() + + 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 + } + ) + } + } +} diff --git a/profile/src/test/java/org/openedx/profile/presentation/profile/CalendarViewModelTest.kt b/profile/src/test/java/org/openedx/profile/presentation/profile/CalendarViewModelTest.kt index 3cc8f1da2..7fd8977a1 100644 --- a/profile/src/test/java/org/openedx/profile/presentation/profile/CalendarViewModelTest.kt +++ b/profile/src/test/java/org/openedx/profile/presentation/profile/CalendarViewModelTest.kt @@ -42,10 +42,10 @@ class CalendarViewModelTest { private val calendarNotifier = mockk(relaxed = true) private val calendarInteractor = mockk(relaxed = true) private val corePreferences = mockk(relaxed = true) - private val profileRouter = mockk(relaxed = true) - private val networkConnection = mockk(relaxed = true) - private val permissionLauncher = mockk>>(relaxed = true) - private val fragmentManager = mockk(relaxed = true) + private val profileRouter = mockk() + private val networkConnection = mockk() + private val permissionLauncher = mockk>>() + private val fragmentManager = mockk() @Before fun setup() { @@ -76,6 +76,7 @@ class CalendarViewModelTest { @Test fun `setUpCalendarSync launches permission request`() = runTest(dispatcher) { + every { permissionLauncher.launch(calendarManager.permissions) } returns Unit viewModel.setUpCalendarSync(permissionLauncher) coVerify { permissionLauncher.launch(calendarManager.permissions) } }