Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code refactoring and junit tests #391

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -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(),
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)
}
}
Loading