diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SubmissionDetailsInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SubmissionDetailsInteractionTest.kt index e06b5fa056..73a58aae79 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SubmissionDetailsInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SubmissionDetailsInteractionTest.kt @@ -25,7 +25,6 @@ import com.instructure.canvas.espresso.FeatureCategory import com.instructure.canvas.espresso.Priority import com.instructure.canvas.espresso.TestCategory import com.instructure.canvas.espresso.TestMetaData -import com.instructure.canvas.espresso.annotations.Stub import com.instructure.canvas.espresso.mockcanvas.MockCanvas import com.instructure.canvas.espresso.mockcanvas.addAssignment import com.instructure.canvas.espresso.mockcanvas.addFileToCourse @@ -246,18 +245,86 @@ class SubmissionDetailsInteractionTest : StudentComposeTest() { ) } - @Stub @Test @TestMetaData(Priority.COMMON, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION) fun testComments_videoCommentPlayback() { - // After recording a video comment, user should be able to view a replay + val data = getToCourse() + val user = data.users.values.first() + val assignment = data.addAssignment( + courseId = course.id, + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY) + ) + + val videoAttachment = createVideoAttachment(data, "video_comment.mp4") + + val commentText = "Here's a video comment" + + val submissionComment = SubmissionComment( + id = data.newItemId(), + authorId = user.id, + authorName = user.name, + comment = commentText, + createdAt = Date(), + attachments = arrayListOf(videoAttachment), + author = Author(id = user.id, displayName = user.shortName), + attempt = 1 + ) + + data.addSubmissionForAssignment( + assignmentId = assignment.id, + userId = user.id, + type = Assignment.SubmissionType.ONLINE_TEXT_ENTRY.apiString, + body = "Some Text!", + comment = submissionComment + ) + + courseBrowserPage.selectAssignments() + assignmentListPage.clickAssignment(assignment) + assignmentDetailsPage.goToSubmissionDetails() + submissionDetailsPage.openComments() + submissionDetailsPage.assertCommentDisplayed(commentText, user) + submissionDetailsPage.assertVideoCommentDisplayed() } - @Stub @Test @TestMetaData(Priority.COMMON, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION) fun testComments_audioCommentPlayback() { - // After recording an audio comment, user should be able to hear an audio playback + val data = getToCourse() + val user = data.users.values.first() + val assignment = data.addAssignment( + courseId = course.id, + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY) + ) + + val audioAttachment = createAudioAttachment(data, "audio_comment.mp3") + + val commentText = "Here's an audio comment" + + val submissionComment = SubmissionComment( + id = data.newItemId(), + authorId = user.id, + authorName = user.name, + comment = commentText, + createdAt = Date(), + attachments = arrayListOf(audioAttachment), + author = Author(id = user.id, displayName = user.shortName), + attempt = 1 + ) + + data.addSubmissionForAssignment( + assignmentId = assignment.id, + userId = user.id, + type = Assignment.SubmissionType.ONLINE_TEXT_ENTRY.apiString, + body = "Some Text!", + comment = submissionComment + ) + + courseBrowserPage.selectAssignments() + assignmentListPage.clickAssignment(assignment) + assignmentDetailsPage.goToSubmissionDetails() + submissionDetailsPage.openComments() + submissionDetailsPage.assertCommentDisplayed(commentText, user) + submissionDetailsPage.assertAudioCommentDisplayed() } @@ -286,6 +353,54 @@ class SubmissionDetailsInteractionTest : StudentComposeTest() { return attachment } + private fun createAudioAttachment(data: MockCanvas, name: String): Attachment { + val course1 = data.courses.values.first() + val fileId = data.addFileToCourse( + courseId = course1.id, + displayName = name, + contentType = "audio/mp3", + fileContent = "fake audio content" + ) + + val mockUrl = "https://mock-data.instructure.com/files/$fileId/preview" + val attachment = Attachment( + id = data.newItemId(), + contentType = "audio/mp3", + displayName = name, + filename = name, + url = mockUrl, + previewUrl = mockUrl, + createdAt = Date(), + size = 1024L + ) + + return attachment + } + + private fun createVideoAttachment(data: MockCanvas, name: String): Attachment { + val course1 = data.courses.values.first() + val fileId = data.addFileToCourse( + courseId = course1.id, + displayName = name, + contentType = "video/mp4", + fileContent = "fake video content" + ) + + val mockUrl = "https://mock-data.instructure.com/files/$fileId/preview" + val attachment = Attachment( + id = data.newItemId(), + contentType = "video/mp4", + displayName = name, + filename = name, + url = mockUrl, + previewUrl = mockUrl, + createdAt = Date(), + size = 2048L + ) + + return attachment + } + // Mock a specified number of students and courses, sign in, then navigate to course browser page for // first course. private fun getToCourse( diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/classic/CommentLibraryE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/classic/CommentLibraryE2ETest.kt index f9f6832391..29fb0951f8 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/classic/CommentLibraryE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/classic/CommentLibraryE2ETest.kt @@ -101,7 +101,7 @@ class CommentLibraryE2ETest : TeacherComposeTest() { val testText = "another" Log.d(STEP_TAG, "Type '$testText' word.") - speedGraderPage.typeComment(testText) + speedGraderPage.typeCommentInCommentLibraryInputField(testText) Log.d(ASSERTION_TAG, "Assert that there is only 1 comment displayed in the comment library, which matches the filter.") speedGraderPage.assertCommentLibraryItemCount(1) @@ -126,7 +126,7 @@ class CommentLibraryE2ETest : TeacherComposeTest() { val nonExistingCommentText = "csakafradi" Log.d(STEP_TAG, "Type a non-existing comment text, '$nonExistingCommentText' into the comment input field.") - speedGraderPage.typeComment(nonExistingCommentText) + speedGraderPage.typeCommentInCommentLibraryInputField(nonExistingCommentText) Log.d(ASSERTION_TAG, "Assert that there is no comment displayed in the comment library as there's no matching comment with the entered (filter) text.") speedGraderPage.assertCommentLibraryItemCount(0) @@ -148,7 +148,7 @@ class CommentLibraryE2ETest : TeacherComposeTest() { val testText2 = "test" Log.d(STEP_TAG, "Type '$testText2' word.") - speedGraderPage.typeComment(testText2) + speedGraderPage.typeCommentInCommentLibraryInputField(testText2) Log.d(ASSERTION_TAG, "Assert that there are 2 comments displayed in the comment library, which matches the filter.") speedGraderPage.assertCommentLibraryItemCount(2) @@ -162,9 +162,9 @@ class CommentLibraryE2ETest : TeacherComposeTest() { Log.d(ASSERTION_TAG, "Assert that the 'Comments' label is displayed with the corresponding number of comments, which is 2 at the moment.") speedGraderPage.assertCommentsLabelDisplayed(2) - Log.d(ASSERTION_TAG, "Assert assert both the '$testComment' and '$testComment2' (whole) comments are displayed in the comments section.") - speedGraderPage.assertCommentDisplayed(testComment) - speedGraderPage.assertCommentDisplayed(testComment2) + Log.d(ASSERTION_TAG, "Assert that both the '$testComment' and '$testComment2' (whole) comments are displayed in the comments section.") + speedGraderPage.assertCommentDisplayed(testComment, author = null) + speedGraderPage.assertCommentDisplayed(testComment2, author = null) } private fun prepareSettingsAndMakeAssignmentWithSubmission( diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/interaction/CommentLibraryInteractionTest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/interaction/CommentLibraryInteractionTest.kt index 3a8f7d2957..c5d89e100b 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/interaction/CommentLibraryInteractionTest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/interaction/CommentLibraryInteractionTest.kt @@ -16,18 +16,18 @@ */ package com.instructure.teacher.ui.interaction -import androidx.test.espresso.Espresso import com.instructure.canvas.espresso.FeatureCategory import com.instructure.canvas.espresso.Priority import com.instructure.canvas.espresso.TestCategory import com.instructure.canvas.espresso.TestMetaData -import com.instructure.canvas.espresso.annotations.Stub import com.instructure.canvas.espresso.mockcanvas.MockCanvas import com.instructure.canvas.espresso.mockcanvas.addAssignment import com.instructure.canvas.espresso.mockcanvas.addCoursePermissions import com.instructure.canvas.espresso.mockcanvas.addSubmissionForAssignment import com.instructure.canvas.espresso.mockcanvas.fakes.FakeAssignmentDetailsManager import com.instructure.canvas.espresso.mockcanvas.fakes.FakeCommentLibraryManager +import com.instructure.canvas.espresso.mockcanvas.fakes.FakeCustomGradeStatusesManager +import com.instructure.canvas.espresso.mockcanvas.fakes.FakeDifferentiationTagsManager import com.instructure.canvas.espresso.mockcanvas.fakes.FakeInboxSettingsManager import com.instructure.canvas.espresso.mockcanvas.fakes.FakePostPolicyManager import com.instructure.canvas.espresso.mockcanvas.fakes.FakeStudentContextManager @@ -35,24 +35,25 @@ import com.instructure.canvas.espresso.mockcanvas.fakes.FakeSubmissionCommentsMa import com.instructure.canvas.espresso.mockcanvas.fakes.FakeSubmissionContentManager import com.instructure.canvas.espresso.mockcanvas.fakes.FakeSubmissionDetailsManager import com.instructure.canvas.espresso.mockcanvas.fakes.FakeSubmissionGradeManager -import com.instructure.canvas.espresso.mockcanvas.fakes.FakeDifferentiationTagsManager import com.instructure.canvas.espresso.mockcanvas.fakes.FakeSubmissionRubricManager import com.instructure.canvas.espresso.mockcanvas.init import com.instructure.canvasapi2.di.GraphQlApiModule +import com.instructure.canvasapi2.di.graphql.CustomGradeStatusModule import com.instructure.canvasapi2.managers.CommentLibraryManager import com.instructure.canvasapi2.managers.InboxSettingsManager import com.instructure.canvasapi2.managers.PostPolicyManager import com.instructure.canvasapi2.managers.StudentContextManager import com.instructure.canvasapi2.managers.SubmissionRubricManager import com.instructure.canvasapi2.managers.graphql.AssignmentDetailsManager +import com.instructure.canvasapi2.managers.graphql.CustomGradeStatusesManager import com.instructure.canvasapi2.managers.graphql.DifferentiationTagsManager import com.instructure.canvasapi2.managers.graphql.SubmissionCommentsManager import com.instructure.canvasapi2.managers.graphql.SubmissionContentManager import com.instructure.canvasapi2.managers.graphql.SubmissionDetailsManager import com.instructure.canvasapi2.managers.graphql.SubmissionGradeManager -import com.instructure.pandautils.di.DifferentiationTagsModule import com.instructure.canvasapi2.models.Assignment import com.instructure.canvasapi2.models.CanvasContextPermission +import com.instructure.pandautils.di.DifferentiationTagsModule import com.instructure.teacher.ui.utils.TeacherComposeTest import com.instructure.teacher.ui.utils.extensions.tokenLogin import dagger.hilt.android.testing.BindValue @@ -60,7 +61,7 @@ import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.UninstallModules import org.junit.Test -@UninstallModules(GraphQlApiModule::class, DifferentiationTagsModule::class) +@UninstallModules(GraphQlApiModule::class, DifferentiationTagsModule::class, CustomGradeStatusModule::class) @HiltAndroidTest class CommentLibraryInteractionTest : TeacherComposeTest() { @@ -110,187 +111,149 @@ class CommentLibraryInteractionTest : TeacherComposeTest() { @JvmField val submissionCommentsManager: SubmissionCommentsManager = FakeSubmissionCommentsManager() - @Stub + @BindValue + @JvmField + val customGradeStatusesManager: CustomGradeStatusesManager = FakeCustomGradeStatusesManager() + @Test @TestMetaData(Priority.IMPORTANT, FeatureCategory.SPEED_GRADER, TestCategory.INTERACTION) fun showAllItemsWhenCommentFieldIsClicked() { val commentLibraryItems = createCommentLibraryMockData() goToSpeedGraderCommentsPage() - speedGraderCommentsPage.clickCommentField() - commentLibraryPage.assertPageObjects() - commentLibraryPage.assertSuggestionsCount(commentLibraryItems.size) - commentLibraryItems.forEach { - commentLibraryPage.assertSuggestionVisible(it) - } + speedGraderPage.clickCommentLibraryButton() + + speedGraderPage.assertCommentLibraryTitle() + speedGraderPage.assertCommentLibraryItemCount(commentLibraryItems.size) } - @Stub @Test @TestMetaData(Priority.IMPORTANT, FeatureCategory.SPEED_GRADER, TestCategory.INTERACTION) - fun showAndSelectFilteredCommentCloseCommentLibrary() { + fun selectCommentLibrarySuggestionComment() { createCommentLibraryMockData() - val isTablet = isTabletDevice() - val isLandScape = isLandscapeDevice() goToSpeedGraderCommentsPage() - speedGraderCommentsPage.typeComment("great work") - if(isTablet || isLandScape) Espresso.pressBack() - commentLibraryPage.assertPageObjects() - commentLibraryPage.assertSuggestionsCount(1) + speedGraderPage.clickCommentLibraryButton() + speedGraderPage.assertCommentLibraryTitle() - val filteredSuggestion = "Great work! But it seems that you may have submitted the wrong file. Please double-check, attach the correct file, and resubmit." - commentLibraryPage.assertSuggestionVisible(filteredSuggestion) - commentLibraryPage.selectSuggestion(filteredSuggestion) + speedGraderPage.typeInCommentLibraryFilter("Great work") + speedGraderPage.assertCommentLibraryItemCount(1) + speedGraderPage.selectCommentLibraryResultItem(0) - speedGraderCommentsPage.assertCommentFieldHasText(filteredSuggestion) - speedGraderPage.assertCommentLibraryNotVisible() + val filteredSuggestion = "Great work! But it seems that you may have submitted the wrong file. Please double-check, attach the correct file, and resubmit." + speedGraderPage.assertCommentLibraryFilterContains(filteredSuggestion) } - @Stub @Test @TestMetaData(Priority.IMPORTANT, FeatureCategory.SPEED_GRADER, TestCategory.INTERACTION) fun selectCommentLibrarySuggestionAndSendComment() { createCommentLibraryMockData() - val isTablet = isTabletDevice() - val isLandScape = isLandscapeDevice() goToSpeedGraderCommentsPage() - speedGraderCommentsPage.typeComment("great work") - if(isTablet || isLandScape) Espresso.pressBack() - commentLibraryPage.assertPageObjects() - commentLibraryPage.assertSuggestionsCount(1) + speedGraderPage.clickCommentLibraryButton() + speedGraderPage.assertCommentLibraryTitle() - val filteredSuggestion = "Great work! But it seems that you may have submitted the wrong file. Please double-check, attach the correct file, and resubmit." - commentLibraryPage.assertSuggestionVisible(filteredSuggestion) - commentLibraryPage.selectSuggestion(filteredSuggestion) + speedGraderPage.typeInCommentLibraryFilter("Great work") + speedGraderPage.assertCommentLibraryItemCount(1) + speedGraderPage.selectCommentLibraryResultItem(0) - // Check that the input field was populated with the selected comment - speedGraderCommentsPage.assertCommentFieldHasText(filteredSuggestion) - speedGraderPage.assertCommentLibraryNotVisible() + val filteredSuggestion = "Great work! But it seems that you may have submitted the wrong file. Please double-check, attach the correct file, and resubmit." + speedGraderPage.assertCommentLibraryFilterContains(filteredSuggestion) - // Check sending selected comment - speedGraderCommentsPage.sendComment() - speedGraderCommentsPage.assertDisplaysCommentText(filteredSuggestion) - speedGraderPage.assertCommentLibraryNotVisible() + speedGraderPage.clickSendCommentButton(commentLibraryOpened = true) + speedGraderPage.assertCommentDisplayed(filteredSuggestion, author = null) } - @Stub @Test @TestMetaData(Priority.IMPORTANT, FeatureCategory.SPEED_GRADER, TestCategory.INTERACTION) fun sendCommentFromCommentLibraryWithoutSelectingSuggestion() { createCommentLibraryMockData() - val isTablet = isTabletDevice() - val isLandScape = isLandscapeDevice() goToSpeedGraderCommentsPage() - val comment = "Great work" - speedGraderCommentsPage.typeComment(comment) - if(isTablet || isLandScape) Espresso.pressBack() - commentLibraryPage.assertPageObjects() - commentLibraryPage.assertSuggestionsCount(1) + speedGraderPage.clickCommentLibraryButton() + speedGraderPage.assertCommentLibraryTitle() - val filteredSuggestion = "Great work! But it seems that you may have submitted the wrong file. Please double-check, attach the correct file, and resubmit." - commentLibraryPage.assertSuggestionVisible(filteredSuggestion) - if(isTablet || isLandScape) Espresso.pressBack() - speedGraderCommentsPage.sendComment() + speedGraderPage.typeInCommentLibraryFilter("Great work") + speedGraderPage.assertCommentLibraryItemCount(1) + + speedGraderPage.clickCloseCommentLibraryButton() + + speedGraderPage.clickSendCommentButton() - speedGraderCommentsPage.assertDisplaysCommentText(comment) - speedGraderPage.assertCommentLibraryNotVisible() + speedGraderPage.assertCommentDisplayed("Great work", author = null) } - @Stub @Test @TestMetaData(Priority.IMPORTANT, FeatureCategory.SPEED_GRADER, TestCategory.INTERACTION) fun reopenCommentLibraryWhenTextIsModified() { createCommentLibraryMockData() - val isTablet = isTabletDevice() - val isLandScape = isLandscapeDevice() goToSpeedGraderCommentsPage() - speedGraderCommentsPage.typeComment("great ") - if(isTablet || isLandScape) Espresso.pressBack() - commentLibraryPage.assertPageObjects() - commentLibraryPage.assertSuggestionsCount(3) + speedGraderPage.clickCommentLibraryButton() + speedGraderPage.assertCommentLibraryTitle() + speedGraderPage.typeInCommentLibraryFilter("great ") + speedGraderPage.assertCommentLibraryItemCount(3) - commentLibraryPage.closeCommentLibrary() - speedGraderPage.assertCommentLibraryNotVisible() + speedGraderPage.clickCloseCommentLibraryButton() - speedGraderCommentsPage.typeComment("start") - if(isTablet || isLandScape) Espresso.pressBack() - commentLibraryPage.assertPageObjects() - commentLibraryPage.assertSuggestionsCount(1) + speedGraderPage.clickCommentLibraryButton() + speedGraderPage.assertCommentLibraryTitle() - val filteredSuggestion = "You are off to a great start. Please add more detail to justify your reasoning." - commentLibraryPage.assertSuggestionVisible(filteredSuggestion) + speedGraderPage.typeInCommentLibraryFilter("start") + speedGraderPage.assertCommentLibraryItemCount(1) } - @Stub @Test @TestMetaData(Priority.COMMON, FeatureCategory.SPEED_GRADER, TestCategory.INTERACTION) fun showEmptyViewWhenFilteringHasNoSuggestion() { createCommentLibraryMockData() goToSpeedGraderCommentsPage() - speedGraderCommentsPage.typeComment("great work, bro") // Type something that is not present in the comment library - commentLibraryPage.assertSuggestionListNotVisible() - commentLibraryPage.assertEmptyViewVisible() + speedGraderPage.clickCommentLibraryButton() + speedGraderPage.assertCommentLibraryTitle() + + speedGraderPage.typeInCommentLibraryFilter("Great work! bro") + speedGraderPage.assertCommentLibraryItemCount(0) } - @Stub @Test @TestMetaData(Priority.COMMON, FeatureCategory.SPEED_GRADER, TestCategory.INTERACTION) fun selectCommentLibrarySuggestionFromMultipleItemResult() { createCommentLibraryMockData() - val isTablet = isTabletDevice() - val isLandScape = isLandscapeDevice() goToSpeedGraderCommentsPage() - speedGraderCommentsPage.typeComment("great") - if(isTablet || isLandScape) Espresso.pressBack() - commentLibraryPage.assertPageObjects() - commentLibraryPage.assertSuggestionsCountGreaterThan(1) //Make sure that we have more than 1 filter result - val filteredSuggestion = "Great work! But it seems that you may have submitted the wrong file. Please double-check, attach the correct file, and resubmit." - commentLibraryPage.selectSuggestion(filteredSuggestion) - // Check that the input field was populated with the selected comment - speedGraderCommentsPage.assertCommentFieldHasText(filteredSuggestion) - speedGraderPage.assertCommentLibraryNotVisible() + speedGraderPage.clickCommentLibraryButton() + speedGraderPage.assertCommentLibraryTitle() + + speedGraderPage.typeInCommentLibraryFilter("great") + speedGraderPage.assertCommentLibraryItemCount(3) + speedGraderPage.selectCommentLibraryResultItem(1) - // Check sending selected comment - speedGraderCommentsPage.sendComment() - speedGraderCommentsPage.assertDisplaysCommentText(filteredSuggestion) - speedGraderPage.assertCommentLibraryNotVisible() + val filteredSuggestion = "Great work! But it seems that you may have submitted the wrong file. Please double-check, attach the correct file, and resubmit." + speedGraderPage.assertCommentLibraryFilterContains(filteredSuggestion) + + speedGraderPage.clickSendCommentButton(commentLibraryOpened = true) + speedGraderPage.assertCommentDisplayed(filteredSuggestion, author = null) } - @Stub @Test @TestMetaData(Priority.COMMON, FeatureCategory.SPEED_GRADER, TestCategory.INTERACTION) fun showAllCommentLibraryItemsAfterClearingCommentFieldFilter() { val commentLibraryItems = createCommentLibraryMockData() - val isTablet = isTabletDevice() - val isLandScape = isLandscapeDevice() - goToSpeedGraderCommentsPage() - speedGraderCommentsPage.typeComment("Great work!") - if(isTablet || isLandScape) Espresso.pressBack() - commentLibraryPage.assertPageObjects() - commentLibraryPage.assertSuggestionsCount(1) + speedGraderPage.clickCommentLibraryButton() + speedGraderPage.assertCommentLibraryTitle() - //Select filtered suggestion and verify if it's displayed within the comment text field. - val filteredSuggestion = "Great work! But it seems that you may have submitted the wrong file. Please double-check, attach the correct file, and resubmit." - commentLibraryPage.assertSuggestionVisible(filteredSuggestion) - commentLibraryPage.selectSuggestion(filteredSuggestion) - - speedGraderCommentsPage.assertCommentFieldHasText(filteredSuggestion) - speedGraderPage.assertCommentLibraryNotVisible() + speedGraderPage.typeInCommentLibraryFilter("Great work!") + speedGraderPage.assertCommentLibraryItemCount(1) + speedGraderPage.selectCommentLibraryResultItem(0) - //Clearing the comment text field. - speedGraderCommentsPage.clearComment() + val filteredSuggestion = "Great work! But it seems that you may have submitted the wrong file. Please double-check, attach the correct file, and resubmit." + speedGraderPage.assertCommentLibraryFilterContains(filteredSuggestion) - //Verify that after clearing the comment text field, all of the comment library suggestions are visible. - commentLibraryPage.assertPageObjects() - commentLibraryPage.assertSuggestionsCount(commentLibraryItems.size) + speedGraderPage.clearComment() + speedGraderPage.assertCommentLibraryItemCount(commentLibraryItems.size) } private fun createCommentLibraryMockData(): List { @@ -305,7 +268,7 @@ class CommentLibraryInteractionTest : TeacherComposeTest() { data.addCoursePermissions( course.id, - CanvasContextPermission() // Just need to have some sort of permissions object registered + CanvasContextPermission() ) val settings = data.userSettings[teacher.id]!!.copy(commentLibrarySuggestions = true) @@ -346,6 +309,9 @@ class CommentLibraryInteractionTest : TeacherComposeTest() { assignmentListPage.clickAssignment(assignment) assignmentDetailsPage.clickAllSubmissions() assignmentSubmissionListPage.clickSubmission(student) - speedGraderPage.selectCommentsTab() + composeTestRule.waitForIdle() + if (isCompactDevice()) speedGraderPage.clickExpandPanelButton() + speedGraderPage.selectTab("Grade & Rubric") + composeTestRule.waitForIdle() } } \ No newline at end of file diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/interaction/SpeedGraderCommentsInteractionTest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/interaction/SpeedGraderCommentsInteractionTest.kt index 953cf96d5b..5d2f2726f9 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/interaction/SpeedGraderCommentsInteractionTest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/interaction/SpeedGraderCommentsInteractionTest.kt @@ -15,14 +15,37 @@ */ package com.instructure.teacher.ui.interaction -import com.instructure.canvas.espresso.annotations.Stub import com.instructure.canvas.espresso.mockcanvas.MockCanvas import com.instructure.canvas.espresso.mockcanvas.addAssignment import com.instructure.canvas.espresso.mockcanvas.addCoursePermissions -import com.instructure.canvas.espresso.mockcanvas.addSubmissionsForAssignment +import com.instructure.canvas.espresso.mockcanvas.addSubmissionForAssignment +import com.instructure.canvas.espresso.mockcanvas.fakes.FakeAssignmentDetailsManager +import com.instructure.canvas.espresso.mockcanvas.fakes.FakeCommentLibraryManager +import com.instructure.canvas.espresso.mockcanvas.fakes.FakeCustomGradeStatusesManager import com.instructure.canvas.espresso.mockcanvas.fakes.FakeDifferentiationTagsManager +import com.instructure.canvas.espresso.mockcanvas.fakes.FakeInboxSettingsManager +import com.instructure.canvas.espresso.mockcanvas.fakes.FakePostPolicyManager +import com.instructure.canvas.espresso.mockcanvas.fakes.FakeStudentContextManager +import com.instructure.canvas.espresso.mockcanvas.fakes.FakeSubmissionCommentsManager +import com.instructure.canvas.espresso.mockcanvas.fakes.FakeSubmissionContentManager +import com.instructure.canvas.espresso.mockcanvas.fakes.FakeSubmissionDetailsManager +import com.instructure.canvas.espresso.mockcanvas.fakes.FakeSubmissionGradeManager +import com.instructure.canvas.espresso.mockcanvas.fakes.FakeSubmissionRubricManager import com.instructure.canvas.espresso.mockcanvas.init +import com.instructure.canvasapi2.di.GraphQlApiModule +import com.instructure.canvasapi2.di.graphql.CustomGradeStatusModule +import com.instructure.canvasapi2.managers.CommentLibraryManager +import com.instructure.canvasapi2.managers.InboxSettingsManager +import com.instructure.canvasapi2.managers.PostPolicyManager +import com.instructure.canvasapi2.managers.StudentContextManager +import com.instructure.canvasapi2.managers.SubmissionRubricManager +import com.instructure.canvasapi2.managers.graphql.AssignmentDetailsManager +import com.instructure.canvasapi2.managers.graphql.CustomGradeStatusesManager import com.instructure.canvasapi2.managers.graphql.DifferentiationTagsManager +import com.instructure.canvasapi2.managers.graphql.SubmissionCommentsManager +import com.instructure.canvasapi2.managers.graphql.SubmissionContentManager +import com.instructure.canvasapi2.managers.graphql.SubmissionDetailsManager +import com.instructure.canvasapi2.managers.graphql.SubmissionGradeManager import com.instructure.canvasapi2.models.Assignment import com.instructure.canvasapi2.models.Attachment import com.instructure.canvasapi2.models.CanvasContextPermission @@ -38,9 +61,59 @@ import dagger.hilt.android.testing.UninstallModules import org.junit.Test @HiltAndroidTest -@UninstallModules(DifferentiationTagsModule::class) +@UninstallModules( + GraphQlApiModule::class, + DifferentiationTagsModule::class, + CustomGradeStatusModule::class +) class SpeedGraderCommentsInteractionTest : TeacherComposeTest() { + override fun displaysPageObjects() = Unit + + @BindValue + @JvmField + val commentLibraryManager: CommentLibraryManager = FakeCommentLibraryManager() + + @BindValue + @JvmField + val postPolicyManager: PostPolicyManager = FakePostPolicyManager() + + @BindValue + @JvmField + val inboxSettingsManager: InboxSettingsManager = FakeInboxSettingsManager() + + @BindValue + @JvmField + val personContextManager: StudentContextManager = FakeStudentContextManager() + + @BindValue + @JvmField + val assignmentDetailsManager: AssignmentDetailsManager = FakeAssignmentDetailsManager() + + @BindValue + @JvmField + val submissionContentManager: SubmissionContentManager = FakeSubmissionContentManager() + + @BindValue + @JvmField + val submissionGradeManager: SubmissionGradeManager = FakeSubmissionGradeManager() + + @BindValue + @JvmField + val submissionDetailsManager: SubmissionDetailsManager = FakeSubmissionDetailsManager() + + @BindValue + @JvmField + val submissionRubricManager: SubmissionRubricManager = FakeSubmissionRubricManager() + + @BindValue + @JvmField + val submissionCommentsManager: SubmissionCommentsManager = FakeSubmissionCommentsManager() + + @BindValue + @JvmField + val customGradeStatusesManager: CustomGradeStatusesManager = FakeCustomGradeStatusesManager() + @BindValue @JvmField val differentiationTagsManager: DifferentiationTagsManager = FakeDifferentiationTagsManager() @@ -54,153 +127,200 @@ class SpeedGraderCommentsInteractionTest : TeacherComposeTest() { url = "http://fake.blah/somePath" // Code/Test will crash w/o a non-null url ) - @Stub @Test - override fun displaysPageObjects() { - goToSpeedGraderCommentsPage( - submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY) - ) + fun assertCommentWithAuthorAndTextDisplayed() { + val submission = goToSpeedGraderCommentsPage(commentText = "Important comment") - speedGraderCommentsPage.assertPageObjects() + val authorName = submission.submissionComments[0].authorName!! + val commentText = submission.submissionComments[0].comment!! + speedGraderPage.assertCommentsLabelDisplayed(1) + speedGraderPage.assertCommentDisplayed(commentText,authorName) } - @Stub @Test - fun displaysAuthorName() { - val submissionList = goToSpeedGraderCommentsPage( - submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), - withComment = true - ) + fun assertCommentsLabelDisplayed() { + val submission = goToSpeedGraderCommentsPage(commentText = "Important comment") - val authorName = submissionList?.get(0)!!.submissionComments[0].authorName!! - speedGraderCommentsPage.assertDisplaysAuthorName(authorName) + val commentCount = submission.submissionComments.size + speedGraderPage.assertCommentsLabelDisplayed(commentCount) } - @Stub @Test - fun displaysCommentText() { - val submissionList = goToSpeedGraderCommentsPage( - submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), - withComment = true - ) + fun addsNewTextComment() { + goToSpeedGraderCommentsPage() + + val newComment = randomString(32) - val commentText = submissionList?.get(0)!!.submissionComments[0].comment!! - speedGraderCommentsPage.assertDisplaysCommentText(commentText) + speedGraderPage.typeCommentInInputField(newComment) + speedGraderPage.clickSendCommentButton() + speedGraderPage.assertCommentDisplayed(newComment, author = null) } - @Stub @Test - fun displaysCommentAttachment() { - val submissionList = goToSpeedGraderCommentsPage( - submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), - withComment = true, - attachment = attachment - ) + fun showsNoComments() { + goToSpeedGraderCommentsPage() - val attachment = submissionList?.get(0)!!.submissionComments[0].attachments.get(0) - speedGraderCommentsPage.assertDisplaysCommentAttachment(attachment) + speedGraderPage.assertCommentsLabelDisplayed(0) } - @Stub @Test - fun displaysSubmissionHistory() { - goToSpeedGraderCommentsPage( - submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY) - ) + fun displaysOwnCommentText() { + val submission = goToSpeedGraderCommentsPage(commentText = "teacher's comment", isTeacherComment = true) - speedGraderCommentsPage.assertDisplaysSubmission() + val commentText = submission.submissionComments[0].comment!! + speedGraderPage.assertCommentsLabelDisplayed(1) + speedGraderPage.assertCommentDisplayed(commentText, author = null) + speedGraderPage.assertCommentAuthorNameNotDisplayed() } - @Stub @Test - fun displaysSubmissionFile() { - val submissionList = goToSpeedGraderCommentsPage( - submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_UPLOAD), - attachment = attachment - ) + fun sendsCommentFromCommentLibrary() { + goToSpeedGraderCommentsPage(seedCommentLibrary = true) + + speedGraderPage.clickCommentLibraryButton() + speedGraderPage.assertCommentLibraryTitle() + speedGraderPage.assertCommentLibraryItemCount(3) - val fileAttachments = submissionList?.get(0)!!.attachments[0] - speedGraderCommentsPage.assertDisplaysSubmissionFile(fileAttachments) + speedGraderPage.selectCommentLibraryResultItem(0) + speedGraderPage.clickSendCommentButton(commentLibraryOpened = true) + speedGraderPage.assertCommentDisplayed("Great work!", author = null) } - @Stub @Test - fun addsNewTextComment() { - goToSpeedGraderCommentsPage( - submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY) - ) + fun displaysMultipleCommentsInOrder() { + val submission = goToSpeedGraderCommentsPage(commentCount = 4) + + val commentText1 = submission.submissionComments[0].comment!! + val commentText2 = submission.submissionComments[1].comment!! + val commentText3 = submission.submissionComments[2].comment!! + val commentText4 = submission.submissionComments[3].comment!! + + speedGraderPage.assertCommentsLabelDisplayed(4) + + speedGraderPage.assertCommentTextDisplayed(commentText1, isOwnComment = false) + speedGraderPage.assertCommentTextDisplayed(commentText2, isOwnComment = true) + speedGraderPage.assertCommentTextDisplayed(commentText3, isOwnComment = false) + speedGraderPage.assertCommentTextDisplayed(commentText4, isOwnComment = true) - val newComment = randomString(32) - speedGraderCommentsPage.addComment(newComment) - speedGraderCommentsPage.assertDisplaysCommentText(newComment) } - @Stub @Test - fun showsNoCommentsMessage() { - goToSpeedGraderCommentsPage( - submissionCount = 0, - submissionTypeList = listOf(Assignment.SubmissionType.ON_PAPER) + fun displaysCommentWithAttachment() { + val submission = goToSpeedGraderCommentsPage( + commentText = "Please check this file", + isTeacherComment = false, + attachment = attachment ) - speedGraderCommentsPage.assertDisplaysEmptyState() + val commentText = submission.submissionComments[0].comment!! + val authorName = submission.submissionComments[0].authorName!! + val attachmentName = submission.submissionComments[0].attachments[0].displayName + + speedGraderPage.assertCommentsLabelDisplayed(1) + speedGraderPage.assertCommentDisplayed(commentText, authorName) + speedGraderPage.assertCommentAttachmentDisplayed(attachmentName.toString()) } /** - * Common setup routine - * - * [submissionCount] is the number of submissions for the created assignment. Typically 0 or 1. - * [submissionTypeList] is the submission type for the assignment. - * [withComment] if true, include a (student) comment with the submission. - * [attachment] if non-null, is either a comment attachment (if withComment is true) or a submission - * attachment (if withComment is false). + * Common setup routine for SpeedGrader comments tests. + * Always creates an assignment with ONLINE_TEXT_ENTRY submission type. * + * @param commentText if not null, include a single comment with the submission containing this text (ignored if commentCount > 0) + * @param isTeacherComment if true, the comment will be authored by the teacher (own comment), otherwise by the student + * @param attachment if non-null, is either a comment attachment (if commentText is not null) or a submission attachment (if commentText is null) + * @param seedCommentLibrary if true, seeds the comment library with predefined comments + * @param commentCount if > 0, creates multiple alternating student/teacher comments (overrides commentText parameter) + * @return the created submission */ private fun goToSpeedGraderCommentsPage( - submissionCount: Int = 1, - submissionTypeList: List = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), - withComment: Boolean = false, - attachment: Attachment? = null): MutableList? { + commentText: String? = null, + isTeacherComment: Boolean = false, + attachment: Attachment? = null, + seedCommentLibrary: Boolean = false, + commentCount: Int = 0 + ): Submission { val data = MockCanvas.init(teacherCount = 1, studentCount = 1, courseCount = 1, favoriteCourseCount = 1) val teacher = data.teachers[0] val course = data.courses.values.first() val student = data.students[0] + if (seedCommentLibrary) { + data.commentLibraryItems[teacher.id] = listOf( + "Great work!", + "Please review the instructions", + "Well done on this assignment" + ) + } + data.addCoursePermissions( course.id, - CanvasContextPermission() // Just need to have some sort of permissions object registered + CanvasContextPermission() ) val assignment = data.addAssignment( courseId = course.id, - submissionTypeList = submissionTypeList + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY) ) - var submissionComment : SubmissionComment? = null - if(withComment) { - submissionComment = SubmissionComment( - id = data.newItemId(), - authorId = student.id, - // Allows Espresso to distinguish between this and the full name, which is elsewhere on the page - authorName = student.shortName, - authorPronouns = student.pronouns, - attempt = 1L, - comment = "a comment", - attachments = if(attachment == null) arrayListOf() else arrayListOf(attachment) - ) + val allComments = when { + commentCount > 0 -> { + List(commentCount) { i -> + val isStudentComment = i % 2 == 0 + val author = if (isStudentComment) student else teacher + val commentNumber = if (isStudentComment) (i / 2) + 1 else (i / 2) + 1 + + SubmissionComment( + id = data.newItemId(), + authorId = author.id, + authorName = author.shortName, + authorPronouns = author.pronouns, + attempt = 1L, + comment = if (isStudentComment) { + when (commentNumber) { + 1 -> "First student comment" + 2 -> "Second student comment" + else -> "Student comment #$commentNumber" + } + } else { + when (commentNumber) { + 1 -> "Teacher reply" + 2 -> "Another teacher reply" + else -> "Teacher reply #$commentNumber" + } + }, + attachments = arrayListOf() + ) + } + } + commentText != null -> { + val author = if (isTeacherComment) teacher else student + listOf( + SubmissionComment( + id = data.newItemId(), + authorId = author.id, + authorName = author.shortName, + authorPronouns = author.pronouns, + attempt = 1L, + comment = commentText, + attachments = if (attachment == null) arrayListOf() else arrayListOf(attachment) + ) + ) + } + else -> emptyList() } - var submissionList = mutableListOf() - repeat(submissionCount) { - val submissionTypesRaw = submissionTypeList.map { it.apiString } - submissionList = data.addSubmissionsForAssignment( - assignmentId = assignment.id, - userId = student.id, - types = submissionTypesRaw, - comment = submissionComment, - attachment = if (withComment) null else attachment - ) + val submission = data.addSubmissionForAssignment( + assignmentId = assignment.id, + userId = student.id, + type = Assignment.SubmissionType.ONLINE_TEXT_ENTRY.apiString, + body = "This is a test submission", + attachment = if (commentText == null && commentCount == 0) attachment else null, + comment = allComments.firstOrNull() + ) + + if (allComments.isNotEmpty()) { + submission.submissionComments = allComments } val token = data.tokenFor(teacher)!! @@ -211,9 +331,11 @@ class SpeedGraderCommentsInteractionTest : TeacherComposeTest() { assignmentListPage.clickAssignment(assignment) assignmentDetailsPage.clickAllSubmissions() assignmentSubmissionListPage.clickSubmission(student) - speedGraderPage.selectCommentsTab() - speedGraderPage.swipeUpCommentsTab() + composeTestRule.waitForIdle() + if (isCompactDevice()) speedGraderPage.clickExpandPanelButton() + speedGraderPage.selectTab("Grade & Rubric") + composeTestRule.waitForIdle() - return submissionList + return submission } } diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/classic/SpeedGraderCommentsPage.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/classic/SpeedGraderCommentsPage.kt index 4dba8ed42a..062e39ee4e 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/classic/SpeedGraderCommentsPage.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/classic/SpeedGraderCommentsPage.kt @@ -15,32 +15,19 @@ */ package com.instructure.teacher.ui.pages.classic -import androidx.test.espresso.Espresso -import androidx.test.espresso.matcher.ViewMatchers.Visibility import androidx.test.espresso.matcher.ViewMatchers.hasDescendant -import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility import com.instructure.canvas.espresso.containsTextCaseInsensitive -import com.instructure.canvasapi2.models.Attachment import com.instructure.espresso.OnViewWithId import com.instructure.espresso.WaitForViewWithId import com.instructure.espresso.assertDisplayed -import com.instructure.espresso.assertHasText -import com.instructure.espresso.assertVisible -import com.instructure.espresso.clearText import com.instructure.espresso.click import com.instructure.espresso.page.BasePage -import com.instructure.espresso.page.callOnClick import com.instructure.espresso.page.onView -import com.instructure.espresso.page.onViewWithId -import com.instructure.espresso.page.onViewWithText -import com.instructure.espresso.page.plus import com.instructure.espresso.page.waitForView -import com.instructure.espresso.page.withAncestor import com.instructure.espresso.page.withId import com.instructure.espresso.page.withParent import com.instructure.espresso.page.withText import com.instructure.espresso.scrollTo -import com.instructure.espresso.typeText import com.instructure.teacher.R import org.hamcrest.Matchers.allOf @@ -53,116 +40,12 @@ import org.hamcrest.Matchers.allOf * adding a comment, sending a video comment, sending an audio comment, and asserting the display of video and audio comments. * This page extends the BasePage class. */ -class SpeedGraderCommentsPage : BasePage() { +class SpeedGraderCommentsPage : BasePage() { // TODO: We’re still using these tests elsewhere, but that test is stubbed as well, so once we un-stub it, we can regroup these and delete this class. private val commentEditText by OnViewWithId(R.id.commentEditText) private val sendCommentButton by WaitForViewWithId(R.id.sendCommentButton) private val addAttachmentButton by OnViewWithId(R.id.addAttachment) - /** - * Asserts the display of the author name. - * - * @param name The name of the author. - */ - fun assertDisplaysAuthorName(name: String) { - onView(withText(name)).assertVisible() - } - - /** - * Asserts the display of the comment text. - * - * @param comment The comment text. - */ - fun assertDisplaysCommentText(comment: String) { - waitForView(allOf(withId(R.id.commentTextView), withEffectiveVisibility(Visibility.VISIBLE))) - .assertHasText(comment) - } - - /** - * Asserts the display of the comment attachment. - * - * @param attachment The attachment representing the comment attachment. - */ - fun assertDisplaysCommentAttachment(attachment: Attachment) { - onViewWithId(R.id.attachmentNameTextView).assertHasText(attachment.displayName!!) - } - - /** - * Asserts the display of the submission. - */ - fun assertDisplaysSubmission() { - onViewWithId(R.id.commentSubmissionAttachmentView).assertDisplayed() - } - - /** - * Asserts the display of the submission file. - * - * @param attachment The attachment representing the submission file. - */ - fun assertDisplaysSubmissionFile(attachment: Attachment) { - val parentMatcher = withParent(withId(R.id.commentSubmissionAttachmentView)) - val match = onView(allOf(parentMatcher, withId(R.id.titleTextView))) - match.assertHasText(attachment.displayName!!) - } - - /** - * Asserts that the comment field has the specified text. - * - * @param text The expected text in the comment field. - */ - fun assertCommentFieldHasText(text: String) { - commentEditText.assertHasText(text) - } - - /** - * Types the specified comment in the comment field. - * - * @param comment The comment to type. - */ - fun typeComment(comment: String) { - onView(withId(R.id.commentEditText) + withAncestor(R.id.commentInputContainer)).typeText(comment) - } - - /** - * Clears the comment field. - */ - fun clearComment() { - onView(withId(R.id.commentEditText) + withAncestor(R.id.commentInputContainer)).clearText() - } - - /** - * Sends the comment. - */ - fun sendComment() { - onView(withId(R.id.sendCommentButton) + withEffectiveVisibility(Visibility.VISIBLE)) - .click() - } - - /** - * Clicks on the comment field. - */ - fun clickCommentField() { - commentEditText.click() - } - - /** - * Asserts the display of the empty state. - */ - fun assertDisplaysEmptyState() { - onViewWithText(R.string.no_submission_comments).assertDisplayed() - } - - /** - * Adds a comment with the specified text. - * - * @param comment The comment text. - */ - fun addComment(comment: String) { - commentEditText.typeText(comment) - Espresso.closeSoftKeyboard() - callOnClick(withId(R.id.sendCommentButton)) - } - /** * Sends a video comment. */ diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/compose/SpeedGraderPage.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/compose/SpeedGraderPage.kt index 7ba1eec724..65645c8998 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/compose/SpeedGraderPage.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/compose/SpeedGraderPage.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performScrollTo +import androidx.compose.ui.test.performTextClearance import androidx.compose.ui.test.performTextInput import androidx.compose.ui.test.performTextReplacement import androidx.test.espresso.Espresso @@ -115,29 +116,6 @@ class SpeedGraderPage(private val composeTestRule: ComposeTestRule) : BasePage() composeTestRule.waitForIdle() } - /** - * Enters a new grade in the Compose grade input field. - * - * @param grade The grade value to input. - */ - fun enterNewGrade(grade: String) { - composeTestRule - .onNodeWithTag("gradeInputField") - .performTextInput(grade) - } - - /** - * Asserts that the final grade is displayed in the Compose UI. - * - * @param grade The expected grade value to be displayed. - */ - fun assertFinalGradeIsDisplayed(grade: String) { - composeTestRule - .onNodeWithTag("finalGradeValue") - .assertTextContains(grade, substring = true) - .assertIsDisplayed() - } - /** * Asserts that the empty view (No Submission) is displayed on the SpeedGrader page. */ @@ -195,10 +173,15 @@ class SpeedGraderPage(private val composeTestRule: ComposeTestRule) : BasePage() * * @param commentCount The expected comment count to be displayed in the label. */ + @OptIn(ExperimentalTestApi::class) fun assertCommentsLabelDisplayed(commentCount: Int) { - composeTestRule.onNode( - hasTestTag("commentsLabel") and hasText("Comments ($commentCount)"), useUnmergedTree = true - ).performScrollTo().assertIsDisplayed() + composeTestRule.onNodeWithTag("commentsLabel").performScrollTo().assertIsDisplayed() + composeTestRule.waitUntilExactlyOneExists( + hasTestTag("commentsLabel") and hasText("Comments ($commentCount)"), timeoutMillis = 5000) + composeTestRule + .onNodeWithTag("commentsLabel") + .assertTextContains("Comments ($commentCount)", substring = true) + .assertIsDisplayed() } /** @@ -225,11 +208,24 @@ class SpeedGraderPage(private val composeTestRule: ComposeTestRule) : BasePage() } /** - * Types the specified comment in the comment input field. + * Types a comment in the main SpeedGrader comment input field. + * + * @param comment The comment text to type. + */ + fun typeCommentInInputField(comment: String) { + composeTestRule + .onNodeWithTag("speedGraderCommentInputField") + .performTextInput(comment) + closeSoftKeyboard() + composeTestRule.waitForIdle() + } + + /** + * Types the specified comment in the comment library filter input field. * * @param comment The comment to type. */ - fun typeComment(comment: String) { + fun typeCommentInCommentLibraryInputField(comment: String) { composeTestRule .onNodeWithTag("commentLibraryFilterInputField") .performTextInput(comment) @@ -241,10 +237,7 @@ class SpeedGraderPage(private val composeTestRule: ComposeTestRule) : BasePage() * Clears the comment input field. */ fun clearComment() { - composeTestRule - .onNodeWithTag("commentLibraryFilterInputField") - .performTextReplacement("") // There's no clearText() in compose testing yet - closeSoftKeyboard() + composeTestRule.onNodeWithTag("commentLibraryFilterInputField").performTextClearance() composeTestRule.waitForIdle() } @@ -282,6 +275,7 @@ class SpeedGraderPage(private val composeTestRule: ComposeTestRule) : BasePage() } else { composeTestRule .onNodeWithTag("sendCommentButton") + .performScrollTo() .performClick() } composeTestRule.waitForIdle() @@ -307,10 +301,45 @@ class SpeedGraderPage(private val composeTestRule: ComposeTestRule) : BasePage() */ fun assertCommentDisplayed(comment: String, author: String? = null) { // if author is null, that means it's an own comment because in that case we don't display the author name. if (author != null) { - composeTestRule.onNode(hasTestTag("commentAuthorName") and hasText(author), useUnmergedTree = true).assertIsDisplayed() - composeTestRule.onNode(hasTestTag("commentCreatedAtDate") and hasAnySibling(hasTestTag("commentAuthorName") and hasText(author)), useUnmergedTree = true).assertIsDisplayed() + composeTestRule.onNode(hasTestTag("commentAuthorName") and hasText(author), useUnmergedTree = true).performScrollTo().assertIsDisplayed() + composeTestRule.onNode(hasTestTag("commentCreatedAtDate") and hasAnySibling(hasTestTag("commentAuthorName") and hasText(author)), useUnmergedTree = true).performScrollTo().assertIsDisplayed() + composeTestRule.onNode(hasTestTag("commentText") and hasAnySibling(hasTestTag("commentAuthorName") and hasText(author)), useUnmergedTree = true).performScrollTo().assertIsDisplayed() } - else composeTestRule.onNode(hasTestTag("ownCommentText") and hasText(comment), useUnmergedTree = true).assertIsDisplayed() + else composeTestRule.onNode(hasTestTag("ownCommentText") and hasText(comment), useUnmergedTree = true).performScrollTo().assertIsDisplayed() + } + + /** + * Asserts that a comment text is displayed without checking for author. + * This is useful when you have multiple comments from the same author, + * where sibling matching becomes unreliable. + * + * @param comment The comment text to assert. + * @param isOwnComment If true, looks for "ownCommentText" tag, otherwise "commentText" tag. + */ + fun assertCommentTextDisplayed(comment: String, isOwnComment: Boolean = false) { + val tag = if (isOwnComment) "ownCommentText" else "commentText" + composeTestRule.onNode(hasTestTag(tag) and hasText(comment), useUnmergedTree = true) + .performScrollTo() + .assertIsDisplayed() + } + + /** + * Asserts that the comment author name is not displayed. + * This is useful for verifying that own comments (teacher comments) don't show the author name. + */ + fun assertCommentAuthorNameNotDisplayed() { + composeTestRule.onNodeWithTag("commentAuthorName").assertDoesNotExist() + } + + /** + * Asserts that a comment attachment with the specified name is displayed. + * + * @param attachmentName The name of the attachment to assert. + */ + fun assertCommentAttachmentDisplayed(attachmentName: String) { + composeTestRule.onNode(hasText(attachmentName), useUnmergedTree = true) + .performScrollTo() + .assertIsDisplayed() } /** @@ -332,6 +361,30 @@ class SpeedGraderPage(private val composeTestRule: ComposeTestRule) : BasePage() composeTestRule.onNodeWithText("Comment Library").assertIsDisplayed() } + /** + * Types text in the comment library filter input field and closes the keyboard. + * + * @param text The text to type in the filter field. + */ + fun typeInCommentLibraryFilter(text: String) { + composeTestRule.onNodeWithTag("commentLibraryFilterInputField").performClick().performTextReplacement(text) + try { + closeSoftKeyboard() + } catch (_: Exception) { + // Ignore if keyboard is already closed + } + composeTestRule.waitForIdle() + } + + /** + * Asserts that the comment library filter input field contains the specified text. + * + * @param text The text that should be contained in the filter field. + */ + fun assertCommentLibraryFilterContains(text: String) { + composeTestRule.onNodeWithTag("commentLibraryFilterInputField").assertTextContains(text) + } + /** * Selects the "Comments" tab. */ diff --git a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockcanvas/fakes/FakeSubmissionCommentsManager.kt b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockcanvas/fakes/FakeSubmissionCommentsManager.kt index 1b18558644..fc4c72b73b 100644 --- a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockcanvas/fakes/FakeSubmissionCommentsManager.kt +++ b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockcanvas/fakes/FakeSubmissionCommentsManager.kt @@ -15,10 +15,13 @@ */ package com.instructure.canvas.espresso.mockcanvas.fakes +import com.instructure.canvas.espresso.mockcanvas.MockCanvas import com.instructure.canvasapi2.CreateSubmissionCommentMutation import com.instructure.canvasapi2.SubmissionCommentsQuery import com.instructure.canvasapi2.managers.graphql.SubmissionCommentsManager +import com.instructure.canvasapi2.models.SubmissionComment import com.instructure.canvasapi2.models.SubmissionCommentsResponseWrapper +import java.util.Date class FakeSubmissionCommentsManager : SubmissionCommentsManager { override suspend fun getSubmissionComments( @@ -26,8 +29,45 @@ class FakeSubmissionCommentsManager : SubmissionCommentsManager { assignmentId: Long, forceNetwork: Boolean ): SubmissionCommentsResponseWrapper { + val data = MockCanvas.data + + val submission = data.submissions[assignmentId]?.find { it.userId == userId } + + val graphqlComments = submission?.submissionComments?.map { comment -> + SubmissionCommentsQuery.Node1( + author = SubmissionCommentsQuery.Author( + _id = comment.authorId.toString(), + name = comment.authorName, + email = null, + avatarUrl = null, + pronouns = comment.authorPronouns + ), + mediaObject = null, + comment = comment.comment, + mediaCommentId = null, + createdAt = comment.createdAt ?: Date(), + canReply = true, + draft = false, + attempt = comment.attempt?.toInt() ?: 1, + read = true, + attachments = comment.attachments.map { attachment -> + SubmissionCommentsQuery.Attachment( + _id = attachment.id.toString(), + id = attachment.id.toString(), + contentType = attachment.contentType, + createdAt = attachment.createdAt, + displayName = attachment.displayName, + title = attachment.filename, + size = attachment.size.toString(), + thumbnailUrl = attachment.thumbnailUrl, + url = attachment.url + ) + } + ) + } ?: emptyList() + return SubmissionCommentsResponseWrapper( - comments = emptyList(), + comments = graphqlComments, data = SubmissionCommentsQuery.Data( submission = null ) @@ -40,6 +80,25 @@ class FakeSubmissionCommentsManager : SubmissionCommentsManager { attempt: Int?, isGroupComment: Boolean ): CreateSubmissionCommentMutation.Data { + val data = MockCanvas.data + + val submission = data.submissions.values.flatten().find { it.id == submissionId } + + if (submission != null) { + val newComment = SubmissionComment( + id = data.newItemId(), + authorId = data.currentUser?.id ?: 0L, + authorName = data.currentUser?.shortName ?: "Teacher", + authorPronouns = data.currentUser?.pronouns, + attempt = attempt?.toLong() ?: submission.attempt, + comment = comment, + createdAt = Date(), + attachments = arrayListOf() + ) + + submission.submissionComments = submission.submissionComments + newComment + } + return CreateSubmissionCommentMutation.Data(null) } } \ No newline at end of file diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/speedgrader/grade/comments/SpeedGraderCommentsSection.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/speedgrader/grade/comments/SpeedGraderCommentsSection.kt index 4c8f40b2ef..1cca5e0aeb 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/speedgrader/grade/comments/SpeedGraderCommentsSection.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/speedgrader/grade/comments/SpeedGraderCommentsSection.kt @@ -94,7 +94,6 @@ import com.instructure.pandautils.views.FloatingRecordingView import com.instructure.pandautils.views.RecordingMediaType import dagger.hilt.android.EarlyEntryPoints import java.util.Date -import java.util.UUID import kotlin.math.roundToInt @@ -715,6 +714,7 @@ fun SpeedGraderUserCommentItem( if (comment.content.isNotEmpty()) { Text( text = comment.content, + modifier = Modifier.testTag("commentText"), fontSize = 14.sp, lineHeight = 19.sp, color = colorResource(id = R.color.textDarkest)