Skip to content
Open
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 @@ -33,6 +33,19 @@ class FakeGetHorizonCourseManager(): HorizonGetCoursesManager {
return DataResult.Success(getCourses())
}

override suspend fun getCourseWithProgressById(
courseId: Long,
userId: Long,
forceNetwork: Boolean
): DataResult<CourseWithProgress> {
val course = getCourses().find { it.courseId == courseId }
return if (course != null) {
DataResult.Success(course)
} else {
DataResult.Fail()
}
}

override suspend fun getEnrollments(
userId: Long,
forceNetwork: Boolean
Expand Down Expand Up @@ -84,6 +97,9 @@ class FakeGetHorizonCourseManager(): HorizonGetCoursesManager {
CourseWithModuleItemDurations(
courseId = courseId,
courseName = "Program Course",
moduleItemsDuration = emptyList(),
startDate = null,
endDate = null
)
)
}
Expand All @@ -94,6 +110,7 @@ class FakeGetHorizonCourseManager(): HorizonGetCoursesManager {
CourseWithProgress(
courseId = courses[0].id,
courseName = courses[0].name,
courseImageUrl = null,
courseSyllabus = "Syllabus for Course 1",
progress = 0.25
)
Expand All @@ -102,6 +119,7 @@ class FakeGetHorizonCourseManager(): HorizonGetCoursesManager {
CourseWithProgress(
courseId = courses[1].id,
courseName = courses[1].name,
courseImageUrl = null,
courseSyllabus = "Syllabus for Course 2",
progress = 1.0
)
Expand All @@ -110,6 +128,7 @@ class FakeGetHorizonCourseManager(): HorizonGetCoursesManager {
CourseWithProgress(
courseId = courses[2].id,
courseName = courses[2].name,
courseImageUrl = null,
courseSyllabus = null,
progress = 0.0
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
query HorizonGetCourseById($courseId: ID!, $userId: ID!) {
legacyNode(_id: $courseId, type: Course) {
... on Course {
id: _id
name
image_download_url: imageUrl
syllabus_body: syllabusBody
usersConnection(filter: {userIds: [$userId]}) {
nodes {
courseProgression {
requirements {
completionPercentage
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.instructure.canvasapi2.managers.graphql.horizon
import com.apollographql.apollo.ApolloClient
import com.apollographql.apollo.api.Optional
import com.instructure.canvasapi2.GetCoursesQuery
import com.instructure.canvasapi2.HorizonGetCourseByIdQuery
import com.instructure.canvasapi2.HorizonGetProgramCourseByIdQuery
import com.instructure.canvasapi2.QLClientConfig
import com.instructure.canvasapi2.enqueueQuery
Expand All @@ -29,6 +30,8 @@ import java.util.Date
interface HorizonGetCoursesManager {
suspend fun getCoursesWithProgress(userId: Long, forceNetwork: Boolean = false): DataResult<List<CourseWithProgress>>

suspend fun getCourseWithProgressById(courseId: Long, userId: Long, forceNetwork: Boolean = false): DataResult<CourseWithProgress>

suspend fun getEnrollments(userId: Long, forceNetwork: Boolean = false): DataResult<List<GetCoursesQuery.Enrollment>>

suspend fun getProgramCourses(courseId: Long, forceNetwork: Boolean = false): DataResult<CourseWithModuleItemDurations>
Expand All @@ -50,14 +53,38 @@ class HorizonGetCoursesManagerImpl(private val apolloClient: ApolloClient): Hori
}
}

override suspend fun getCourseWithProgressById(courseId: Long, userId: Long, forceNetwork: Boolean): DataResult<CourseWithProgress> {
return try {
val query = HorizonGetCourseByIdQuery(courseId.toString(), userId.toString())
val result = apolloClient.enqueueQuery(query, forceNetwork).dataAssertNoErrors

val progress = result.legacyNode?.onCourse?.usersConnection?.nodes?.firstOrNull()?.courseProgression?.requirements?.completionPercentage ?: 0.0
val courseId = result.legacyNode?.onCourse?.id?.toLongOrNull() ?: -1L
val courseName = result.legacyNode?.onCourse?.name ?: ""
val courseSyllabus = result.legacyNode?.onCourse?.syllabus_body ?: ""
val imageUrl = result.legacyNode?.onCourse?.image_download_url ?: ""
val course = CourseWithProgress(courseId, courseName, imageUrl, courseSyllabus, progress)

return DataResult.Success(course)
} catch (e: Exception) {
DataResult.Fail(Failure.Exception(e))
}
}

private fun mapCourse(course: GetCoursesQuery.Course?): CourseWithProgress? {
val progress = course?.usersConnection?.nodes?.firstOrNull()?.courseProgression?.requirements?.completionPercentage ?: 0.0
val courseId = course?.id?.toLong()
val courseName = course?.name
val courseSyllabus = course?.syllabus_body
val imageUrl = course?.image_download_url

return if (courseId != null && courseName != null) {
CourseWithProgress(courseId, courseName, courseSyllabus, progress)
CourseWithProgress(
courseId,
courseName,
imageUrl,
courseSyllabus,
progress)
} else {
null
}
Expand Down Expand Up @@ -113,7 +140,8 @@ class HorizonGetCoursesManagerImpl(private val apolloClient: ApolloClient): Hori
data class CourseWithProgress(
val courseId: Long,
val courseName: String,
val courseSyllabus: String? = null,
val courseImageUrl: String?,
val courseSyllabus: String?,
val progress: Double,
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright (C) 2025 - present Instructure, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.instructure.horizon.ui.features.learn

import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.navigation.compose.ComposeNavigator
import androidx.navigation.compose.rememberNavController
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.instructure.horizon.features.learn.course.details.CourseDetailsScreen
import com.instructure.horizon.features.learn.course.details.CourseDetailsUiState
import com.instructure.horizon.horizonui.platform.LoadingState
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class CourseDetailsUiTest {
@get:Rule
val composeTestRule = createComposeRule()

@Test
fun testLoadingStateDisplaysSpinner() {
val state = CourseDetailsUiState(
loadingState = LoadingState(isLoading = true),
courseName = "",
courseProgress = 0.0,
courseId = 1L
)

composeTestRule.setContent {
val navController = rememberNavController()
navController.navigatorProvider.addNavigator(ComposeNavigator())

CourseDetailsScreen(
state = state,
navController = navController
)
}

composeTestRule.onNodeWithTag("LoadingSpinner")
.assertIsDisplayed()
}

@Test
fun testCourseDetailsDisplaysCourseName() {
val state = CourseDetailsUiState(
loadingState = LoadingState(isLoading = false),
courseName = "Test Course Name",
courseProgress = 75.0,
courseId = 1L
)

composeTestRule.setContent {
val navController = rememberNavController()
navController.navigatorProvider.addNavigator(ComposeNavigator())

CourseDetailsScreen(
state = state,
navController = navController
)
}

composeTestRule.onNodeWithText("Test Course Name")
.assertIsDisplayed()
}

@Test
fun testErrorStateDisplayed() {
val state = CourseDetailsUiState(
loadingState = LoadingState(isLoading = false, isError = true, errorMessage = "Failed to load course"),
courseName = "",
courseProgress = 0.0,
courseId = 1L
)

composeTestRule.setContent {
val navController = rememberNavController()
navController.navigatorProvider.addNavigator(ComposeNavigator())

CourseDetailsScreen(
state = state,
navController = navController
)
}

composeTestRule.onNodeWithText("Failed to load course", substring = true)
.assertIsDisplayed()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.instructure.horizon.horizonui.molecules.Spinner
import org.junit.Rule
Expand All @@ -42,14 +41,4 @@ class HorizonLearnUiTest {
composeTestRule.onNodeWithTag("LoadingSpinner")
.assertIsDisplayed()
}

@Test
fun testLearnTitleDisplays() {
composeTestRule.setContent {
androidx.compose.material3.Text("Learn")
}

composeTestRule.onNodeWithText("Learn")
.assertIsDisplayed()
}
}
Loading