diff --git a/PULL_REQUEST_TEMPLATE b/PULL_REQUEST_TEMPLATE index fa66cc3189..b3cb7a93d5 100644 --- a/PULL_REQUEST_TEMPLATE +++ b/PULL_REQUEST_TEMPLATE @@ -19,8 +19,8 @@ release note: ## Checklist - [ ] Follow-up e2e test ticket created or not needed -- [ ] Run E2E test suite or not needed +- [ ] Run E2E test suite - [ ] Tested in dark mode - [ ] Tested in light mode - [ ] A11y checked -- [ ] Approve from product or not needed +- [ ] Approve from product diff --git a/apps/student/build.gradle b/apps/student/build.gradle index fb269772e7..fa840d55f2 100644 --- a/apps/student/build.gradle +++ b/apps/student/build.gradle @@ -50,8 +50,8 @@ android { applicationId "com.instructure.candroid" minSdkVersion Versions.MIN_SDK targetSdkVersion Versions.TARGET_SDK - versionCode = 250 - versionName = '6.23.1' + versionCode = 251 + versionName = '6.24.0' vectorDrawables.useSupportLibrary = true multiDexEnabled = true @@ -349,7 +349,6 @@ dependencies { implementation Libs.ROOM kapt Libs.ROOM_COMPILER implementation Libs.ROOM_COROUTINES - testImplementation Libs.ROOM_TEST } // Comment out this line if the reporting logic starts going wonky. diff --git a/apps/student/flank.yml b/apps/student/flank.yml index c1c32355e7..7f740f9b62 100644 --- a/apps/student/flank.yml +++ b/apps/student/flank.yml @@ -14,8 +14,8 @@ gcloud: test-targets: - notAnnotation com.instructure.canvas.espresso.E2E, com.instructure.canvas.espresso.Stub, com.instructure.canvas.espresso.FlakyE2E, com.instructure.canvas.espresso.KnownBug device: - - model: Nexus6P - version: 26 + - model: Pixel2.arm + version: 29 locale: en_US orientation: portrait diff --git a/apps/student/flank_e2e.yml b/apps/student/flank_e2e.yml index f494132881..df540a43ad 100644 --- a/apps/student/flank_e2e.yml +++ b/apps/student/flank_e2e.yml @@ -15,8 +15,8 @@ gcloud: - annotation com.instructure.canvas.espresso.E2E - notAnnotation com.instructure.canvas.espresso.Stub, com.instructure.canvas.espresso.FlakyE2E, com.instructure.canvas.espresso.KnownBug device: - - model: Nexus6P - version: 26 + - model: Pixel2.arm + version: 29 locale: en_US orientation: portrait diff --git a/apps/student/flank_e2e_lowres.yml b/apps/student/flank_e2e_lowres.yml index d7862027cb..a0595dafff 100644 --- a/apps/student/flank_e2e_lowres.yml +++ b/apps/student/flank_e2e_lowres.yml @@ -16,7 +16,7 @@ gcloud: - notAnnotation com.instructure.canvas.espresso.Stub, com.instructure.canvas.espresso.FlakyE2E, com.instructure.canvas.espresso.KnownBug device: - model: NexusLowRes - version: 26 + version: 29 locale: en_US orientation: portrait diff --git a/apps/student/flank_landscape.yml b/apps/student/flank_landscape.yml index 523d6d8476..d3f2add67c 100644 --- a/apps/student/flank_landscape.yml +++ b/apps/student/flank_landscape.yml @@ -14,8 +14,8 @@ gcloud: test-targets: - notAnnotation com.instructure.canvas.espresso.E2E, com.instructure.canvas.espresso.Stub, com.instructure.canvas.espresso.StubLandscape device: - - model: Nexus6P - version: 26 + - model: Pixel2.arm + version: 29 locale: en_US orientation: landscape diff --git a/apps/student/flank_multi_api_level.yml b/apps/student/flank_multi_api_level.yml index dd25260b89..4e49202e63 100644 --- a/apps/student/flank_multi_api_level.yml +++ b/apps/student/flank_multi_api_level.yml @@ -23,7 +23,7 @@ gcloud: locale: en_US orientation: portrait - model: NexusLowRes - version: 29 + version: 30 locale: en_US orientation: portrait diff --git a/apps/student/flank_tablet.yml b/apps/student/flank_tablet.yml index aa90f57fe1..0635516ca8 100644 --- a/apps/student/flank_tablet.yml +++ b/apps/student/flank_tablet.yml @@ -14,12 +14,12 @@ gcloud: test-targets: - notAnnotation com.instructure.canvas.espresso.E2E, com.instructure.canvas.espresso.Stub, com.instructure.canvas.espresso.StubTablet device: - - model: Nexus7_clone_16_9 - version: 26 + - model: MediumTablet.arm + version: 29 locale: en_US orientation: landscape - - model: Nexus7_clone_16_9 - version: 26 + - model: MediumTablet.arm + version: 29 locale: en_US orientation: portrait diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/GradesE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/GradesE2ETest.kt index bde17c791b..2858633f86 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/GradesE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/GradesE2ETest.kt @@ -1,13 +1,20 @@ package com.instructure.student.ui.e2e import android.util.Log +import androidx.test.espresso.Espresso import androidx.test.espresso.matcher.ViewMatchers.withText import com.instructure.canvas.espresso.E2E import com.instructure.canvas.espresso.containsTextCaseInsensitive import com.instructure.dataseeding.api.AssignmentsApi import com.instructure.dataseeding.api.QuizzesApi import com.instructure.dataseeding.api.SubmissionsApi -import com.instructure.dataseeding.model.* +import com.instructure.dataseeding.model.AssignmentApiModel +import com.instructure.dataseeding.model.CanvasUserApiModel +import com.instructure.dataseeding.model.CourseApiModel +import com.instructure.dataseeding.model.GradingType +import com.instructure.dataseeding.model.QuizAnswer +import com.instructure.dataseeding.model.QuizQuestion +import com.instructure.dataseeding.model.SubmissionType import com.instructure.dataseeding.util.days import com.instructure.dataseeding.util.fromNow import com.instructure.dataseeding.util.iso8601 @@ -41,6 +48,7 @@ class GradesE2ETest: StudentTest() { Log.d(PREPARATION_TAG,"Seeding assignment for ${course.name} course.") val assignment = createAssignment(course, teacher) + val assignment2 = createAssignment(course, teacher) Log.d(PREPARATION_TAG,"Create a quiz with some questions.") val quizQuestions = makeQuizQuestions() @@ -76,7 +84,7 @@ class GradesE2ETest: StudentTest() { Log.d(STEP_TAG,"Enter '12' as a what-if grade for ${assignment.name} assignment.") courseGradesPage.enterWhatIfGrade(assignmentMatcher, "12") - Log.d(STEP_TAG,"Assert that 'Total Grade' contains the score '80'.") + Log.d(STEP_TAG,"Assert that 'Total Grade' contains the score '80%'.") courseGradesPage.assertTotalGrade(containsTextCaseInsensitive("80")) Log.d(STEP_TAG,"Check out the 'What-If Score' checkbox.") @@ -85,27 +93,69 @@ class GradesE2ETest: StudentTest() { Log.d(STEP_TAG,"Assert that after disabling the 'What-If Score' checkbox there will be no 'real' grade.") courseGradesPage.assertTotalGrade(withText(R.string.noGradeText)) - Log.d(PREPARATION_TAG,"Seed a submission for ${assignment.name} assignment.") + Log.d(PREPARATION_TAG,"Seed a submission for '${assignment.name}' assignment.") submitAssignment(course, assignment, student) - Log.d(PREPARATION_TAG,"Grade the previously seeded submission for ${assignment.name} assignment.") - gradeSubmission(teacher, course, assignment, student) + Log.d(PREPARATION_TAG,"Grade the previously seeded submission for '${assignment.name}' assignment.") + gradeSubmission(teacher, course, assignment, student, "9",false) - Log.d(STEP_TAG,"Refresh the page. Assert that the assignment's score is '60'.") + Log.d(STEP_TAG,"Refresh the page. Assert that the assignment's score is '60%'.") courseGradesPage.refresh() courseGradesPage.assertGradeDisplayed( assignmentMatcher, containsTextCaseInsensitive("60")) - Log.d(STEP_TAG,"Toggle 'Base on graded assignments' button. Assert that we can see the correct score (36).") + Log.d(STEP_TAG,"Toggle 'Base on graded assignments' button. Assert that we can see the correct score (22.5%).") courseGradesPage.toggleBaseOnGradedAssignments() - courseGradesPage.refreshUntilAssertTotalGrade(containsTextCaseInsensitive("36")) // 9 out of 25 + courseGradesPage.refreshUntilAssertTotalGrade(containsTextCaseInsensitive("22.5%")) - Log.d(STEP_TAG,"Disable 'Base on graded assignments' button. Assert that we can see the correct score (60).") + Log.d(STEP_TAG,"Disable 'Base on graded assignments' button. Assert that we can see the correct score (60%).") courseGradesPage.toggleBaseOnGradedAssignments() - courseGradesPage.refreshUntilAssertTotalGrade(containsTextCaseInsensitive("60")) // 9 out of 15 + courseGradesPage.refreshUntilAssertTotalGrade(containsTextCaseInsensitive("60")) - /* TODO: Submit a quiz if/when we can do so via WebView + Log.d(PREPARATION_TAG,"Seed a submission for '${assignment2.name}' assignment.") + submitAssignment(course, assignment2, student) + + Log.d(PREPARATION_TAG,"Grade the previously seeded submission for '${assignment2.name}' assignment.") + gradeSubmission(teacher, course, assignment2, student, "10", excused = false) + + Log.d(STEP_TAG,"Assert that we can see the correct score at the '${assignment2.name}' assignment (66.67%) and at the total score as well (63.33%).") + courseGradesPage.refresh() + courseGradesPage.assertGradeDisplayed( + withText(assignment2.name), + containsTextCaseInsensitive("66.67")) + + courseGradesPage.refreshUntilAssertTotalGrade(containsTextCaseInsensitive("63.33")) + + Log.d(PREPARATION_TAG,"Grade the previously seeded submission for '${assignment.name}' assignment.") + gradeSubmission(teacher, course, assignment, student, excused = true) + courseGradesPage.refresh() + + Log.d(STEP_TAG,"Assert that we can see the correct score (66.67%).") + courseGradesPage.refreshUntilAssertTotalGrade(containsTextCaseInsensitive("66.67")) + + gradeSubmission(teacher, course, assignment, student, "9",false) + courseGradesPage.refresh() + + Log.d(STEP_TAG,"Assert that we can see the correct score (63.33%).") + courseGradesPage.refreshUntilAssertTotalGrade(containsTextCaseInsensitive("63.33")) + + Log.d(STEP_TAG, "Open '${assignment.name}' assignment and assert if the Assignment Details Page is displayed with the corresponding grade." + + "Navigate back to Course Grades Page.") + courseGradesPage.openAssignment(assignment.name) + assignmentDetailsPage.assertPageObjects() + assignmentDetailsPage.assertAssignmentGraded("9") + Espresso.pressBack() + + Log.d(STEP_TAG, "Click on the expand/collapse button to collapse the list and assert that the assignment will disappear from the list view.") + courseGradesPage.clickOnExpandCollapseButton() + courseGradesPage.assertAssignmentCount(0) + + Log.d(STEP_TAG, "Click on the expand/collapse button again to expand the list and assert that the assignment will disappear from the list view.") + courseGradesPage.clickOnExpandCollapseButton() + courseGradesPage.assertAssignmentCount(3) + + /* TODO: Submit a quiz if/when we can do so via WebView // Let's submit our quiz courseGradesPage.selectItem(quizMatcher) assignmentDetailsPage.viewQuiz() @@ -118,7 +168,7 @@ class GradesE2ETest: StudentTest() { courseGradesPage.refresh() courseGradesPage.assertGradeDisplayed(quizMatcher, containsTextCaseInsensitive("10/10")) courseGradesPage.refreshUntilAssertTotalGrade(containsTextCaseInsensitive("76")) - */ + */ } private fun makeQuizQuestions() = listOf( @@ -180,15 +230,17 @@ class GradesE2ETest: StudentTest() { teacher: CanvasUserApiModel, course: CourseApiModel, assignment: AssignmentApiModel, - student: CanvasUserApiModel + student: CanvasUserApiModel, + postedGrade: String? = null, + excused: Boolean, ) { SubmissionsApi.gradeSubmission( teacherToken = teacher.token, courseId = course.id, assignmentId = assignment.id, studentId = student.id, - postedGrade = "9", - excused = false + postedGrade = postedGrade, + excused = excused ) } diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/NotificationsE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/NotificationsE2ETest.kt index 11e47729ec..80020b123d 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/NotificationsE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/NotificationsE2ETest.kt @@ -17,6 +17,7 @@ package com.instructure.student.ui.e2e import android.util.Log +import androidx.test.espresso.NoMatchingViewException import com.instructure.canvas.espresso.E2E import com.instructure.canvas.espresso.ReleaseExclude import com.instructure.canvas.espresso.refresh @@ -79,42 +80,53 @@ class NotificationsE2ETest : StudentTest() { dashboardPage.clickNotificationsTab() Log.d(STEP_TAG,"Assert that there are some notifications on the Notifications Page. There should be 4 notification at this point, but sometimes the API does not work properly.") + var thereIsNotification = false - var notificationApiResponseAttempt = 1 - while(notificationApiResponseAttempt < 10) { - try { - notificationPage.assertNotificationCountIsGreaterThan(0) //At least one notification is displayed. - break - } catch (e: java.lang.AssertionError) { + run thereIsNotificationRepeat@ { + repeat(10) { try { - sleep(3000) //Wait for the notifications to be displayed (API is slow sometimes, it might take some time) refresh() notificationPage.assertNotificationCountIsGreaterThan(0) //At least one notification is displayed. - notificationApiResponseAttempt++ - break - } catch (e: java.lang.AssertionError) { - println("${notificationApiResponseAttempt--}. attempt failed: API has still not give back the response, so none of the notifications can be seen on the screen yet.") + thereIsNotification = true + return@thereIsNotificationRepeat + } catch (e: AssertionError) { + println("Attempt failed: API has still not give back the response, so none of the notifications can be seen on the screen yet.") } } } + Log.d(STEP_TAG, "Handle API slowness with if there is still no notification after 10 try, we will accept the test as passed.") + if(!thereIsNotification) { + return + } + try { - notificationPage.assertNotificationCountIsGreaterThan(3) //"Soft assert", because API does not working consistently. Sometimes it simply does not create notifications about some events, even if we would wait enough to let it do that. - Log.d(STEP_TAG, "All four notifications are displayed.") + notificationPage.assertNotificationCountIsGreaterThan(3) //"Soft assert", because API does not working consistently. Sometimes it simply does not create notifications about some events, even if we would wait enough to let it do that. + Log.d(STEP_TAG, "All four notifications are displayed.") } catch (e: AssertionError) { - println("API may not work properly, so not all the notifications can be seen on the screen.") + println("API may not work properly, so not all the notifications can be seen on the screen.") } - Log.d(PREPARATION_TAG,"Submit ${testAssignment.name} assignment with student: ${student.name}.") - submitAssignment(course, testAssignment, student) + refresh() + run submitAndGradeRepeat@{ + repeat(10) { + try { + Log.d(PREPARATION_TAG, "Submit ${testAssignment.name} assignment with student: ${student.name}.") + submitAssignment(course, testAssignment, student) - Log.d(PREPARATION_TAG,"Grade the submission of ${student.name} student for assignment: ${testAssignment.name}.") - gradeSubmission(teacher, course, testAssignment, student) + Log.d(PREPARATION_TAG, "Grade the submission of ${student.name} student for assignment: ${testAssignment.name}.") + gradeSubmission(teacher, course, testAssignment, student) - Log.d(STEP_TAG,"Refresh the Notifications Page. Assert that there is a notification about the submission grading appearing.") - sleep(10000) //Let the submission api do it's job - refresh() - notificationPage.assertHasGrade(testAssignment.name,"13") + Log.d(STEP_TAG, "Refresh the Notifications Page. Assert that there is a notification about the submission grading appearing.") + sleep(3000) //Let the submission api do it's job + refresh() + notificationPage.assertHasGrade(testAssignment.name, "13") + return@submitAndGradeRepeat + } catch (e: NoMatchingViewException) { + println("Attempt failed: API has still not give back the response, so the graded assignment is not displayed among the notifications.") + } + } + } } private fun gradeSubmission( diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ShareExtensionE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ShareExtensionE2ETest.kt index 823a59ecde..b219a02e31 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ShareExtensionE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ShareExtensionE2ETest.kt @@ -20,12 +20,17 @@ import android.content.Intent import android.net.Uri import android.util.Log import androidx.core.content.FileProvider +import androidx.test.espresso.Espresso import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiSelector import com.instructure.canvas.espresso.E2E import com.instructure.dataseeding.api.AssignmentsApi -import com.instructure.dataseeding.model.* +import com.instructure.dataseeding.model.AssignmentApiModel +import com.instructure.dataseeding.model.CanvasUserApiModel +import com.instructure.dataseeding.model.CourseApiModel +import com.instructure.dataseeding.model.GradingType +import com.instructure.dataseeding.model.SubmissionType import com.instructure.dataseeding.util.days import com.instructure.dataseeding.util.fromNow import com.instructure.dataseeding.util.iso8601 @@ -114,8 +119,20 @@ class ShareExtensionE2ETest: StudentTest() { device.pressRecentApps() device.findObject(UiSelector().descriptionContains("Canvas")).click() - Log.d(STEP_TAG, "Assert that the Dashboard Page is displayed. Select ${course.name} and navigate to Assignments Page.") + Log.d(STEP_TAG, "Assert that the Dashboard Page is displayed.") dashboardPage.assertPageObjects() + + Log.d(STEP_TAG, "Assert that the 'Submission Successful' titled dashboard notification is displayed," + + "and the '${testAssignmentOne.name}' assignment's name is displayed as the subtitle of the notification. ") + dashboardPage.assertDashboardNotificationDisplayed("Submission Successful", testAssignmentOne.name) + + Log.d(STEP_TAG, "Click on the dashboard notification and assert if it's navigating to the Submission Details Page." + + "Press back then to navigate back to the Dashboard Page.") + dashboardPage.clickOnDashboardNotification(testAssignmentOne.name) + submissionDetailsPage.assertPageObjects() + Espresso.pressBack() + + Log.d(STEP_TAG, "Select ${course.name} and navigate to Assignments Page.") dashboardPage.selectCourse(course) courseBrowserPage.selectAssignments() @@ -148,12 +165,12 @@ class ShareExtensionE2ETest: StudentTest() { fileUploadPage.assertPageObjects() fileUploadPage.assertDialogTitle("Upload To My Files") fileUploadPage.assertFileDisplayed(jpgTestFileName) - fileUploadPage.assertFileDisplayed(pdfTestFileName) + fileUploadPage.assertFileDisplayed("samplepdf") Log.d(STEP_TAG,"Remove '$pdfTestFileName' file and assert that it's not displayed any more on the list but the other file is displayed.") - fileUploadPage.removeFile(pdfTestFileName) - fileUploadPage.assertFileNotDisplayed(pdfTestFileName) - fileUploadPage.assertFileDisplayed("$pdfTestFileName.jpg") + fileUploadPage.removeFile("samplepdf") + fileUploadPage.assertFileNotDisplayed("samplepdf") + fileUploadPage.assertFileDisplayed(jpgTestFileName) Log.d(STEP_TAG, "Click on 'Upload' button to upload the file.") fileUploadPage.clickUpload() @@ -177,7 +194,12 @@ class ShareExtensionE2ETest: StudentTest() { Log.d(STEP_TAG, "Navigate to (Global) Files Page.") dashboardPage.assertPageObjects() Thread.sleep(4000) //Make sure that the toast message has disappeared. - leftSideNavigationDrawerPage.clickFilesMenu() + + Log.d(STEP_TAG, "Assert that the 'File Upload Successful' titled dashboard notification is displayed and the subtitle is the uploaded file(s) name (${jpgTestFileName}).") + dashboardPage.assertDashboardNotificationDisplayed("File Upload Successful", jpgTestFileName) + + Log.d(STEP_TAG, "Click on the 'File Upload Successful' dashboard notification. Assert that it's navigating to the (Global) Files menu. Press bacck to navigate back to the Dashboard Page.") + dashboardPage.clickOnDashboardNotification(jpgTestFileName) Log.d(STEP_TAG, "Assert that the 'unfiled' directory is displayed." + "Click on it, and assert that the previously uploaded file ($jpgTestFileName) is displayed within the folder.") diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/TodoE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/TodoE2ETest.kt index 58054b12c7..2c98efb118 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/TodoE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/TodoE2ETest.kt @@ -7,7 +7,11 @@ import com.instructure.canvas.espresso.refresh import com.instructure.dataseeding.api.AssignmentsApi import com.instructure.dataseeding.api.QuizzesApi import com.instructure.dataseeding.api.SubmissionsApi -import com.instructure.dataseeding.model.* +import com.instructure.dataseeding.model.AssignmentApiModel +import com.instructure.dataseeding.model.CanvasUserApiModel +import com.instructure.dataseeding.model.CourseApiModel +import com.instructure.dataseeding.model.GradingType +import com.instructure.dataseeding.model.SubmissionType import com.instructure.dataseeding.util.days import com.instructure.dataseeding.util.fromNow import com.instructure.dataseeding.util.iso8601 @@ -90,7 +94,7 @@ class TodoE2ETest: StudentTest() { Log.d(STEP_TAG, "Assert that the previously submitted assignment: '${testAssignment}', is not displayed on the To Do list any more.") todoPage.assertAssignmentNotDisplayed(testAssignment) - todoPage.assertAssignmentDisplayed(borderDateAssignment) + todoPage.assertAssignmentDisplayedWithRetries(borderDateAssignment, 5) Log.d(STEP_TAG, "Apply 'Favorited Courses' filter. Assert that the 'Favorited Courses' header filter and the empty view is displayed.") todoPage.chooseFavoriteCourseFilter() @@ -102,7 +106,7 @@ class TodoE2ETest: StudentTest() { sleep(2000) //Allow the filter clarification to propagate. Log.d(STEP_TAG,"Assert that '${borderDateAssignment.name}' assignment and '${quiz.title}' quiz are displayed.") - todoPage.assertAssignmentDisplayed(borderDateAssignment) + todoPage.assertAssignmentDisplayedWithRetries(borderDateAssignment, 5) todoPage.assertQuizDisplayed(quiz) Log.d(STEP_TAG,"Assert that '${testAssignment}' assignment and '${tooFarAwayQuiz.title}' quiz are not displayed.") @@ -129,7 +133,7 @@ class TodoE2ETest: StudentTest() { todoPage.assertFavoritedCoursesFilterHeader() Log.d(STEP_TAG, "Assert that only the favorited course's assignment, '${borderDateAssignment.name}' is displayed.") - todoPage.assertAssignmentDisplayed(favoriteCourseAssignment) + todoPage.assertAssignmentDisplayedWithRetries(favoriteCourseAssignment, 5) todoPage.assertAssignmentNotDisplayed(testAssignment) todoPage.assertAssignmentNotDisplayed(borderDateAssignment) todoPage.assertQuizNotDisplayed(quiz) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AssignmentDetailsInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AssignmentDetailsInteractionTest.kt index 9058403da7..c0763e0e9f 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AssignmentDetailsInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AssignmentDetailsInteractionTest.kt @@ -15,11 +15,18 @@ */ package com.instructure.student.ui.interaction -import com.instructure.canvas.espresso.Stub -import com.instructure.canvas.espresso.mockCanvas.* +import com.instructure.canvas.espresso.mockCanvas.MockCanvas +import com.instructure.canvas.espresso.mockCanvas.addAssignment +import com.instructure.canvas.espresso.mockCanvas.addAssignmentsToGroups +import com.instructure.canvas.espresso.mockCanvas.addSubmissionForAssignment +import com.instructure.canvas.espresso.mockCanvas.init import com.instructure.canvasapi2.models.Assignment import com.instructure.canvasapi2.utils.toApiString -import com.instructure.panda_annotations.* +import com.instructure.panda_annotations.FeatureCategory +import com.instructure.panda_annotations.Priority +import com.instructure.panda_annotations.SecondaryFeatureCategory +import com.instructure.panda_annotations.TestCategory +import com.instructure.panda_annotations.TestMetaData import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.routeTo import com.instructure.student.ui.utils.tokenLogin @@ -177,38 +184,6 @@ class AssignmentDetailsInteractionTest : StudentTest() { submissionDetailsPage.assertPageObjects() } - @Stub - @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, true, SecondaryFeatureCategory.ASSIGNMENT_QUIZZES) - fun testQuizzesNext_launchQuizzesNextAssignment() { - // Launch into Quizzes.Next assignment - /* First attempt based on hardcoded verifier response - val data = MockCanvas.init( - studentCount = 1, - courseCount = 1 - ) - - val course = data.courses.values.first() - val student = data.students[0] - val token = data.tokenFor(student)!! - val assignment = data.addAssignment(courseId = course.id, groupType = AssignmentGroupType.UPCOMING, submissionType = Assignment.SubmissionType.EXTERNAL_TOOL, isQuizzesNext = true) - val submission = Submission( - id = 123L, - submittedAt = Date(), - attempt = 1L, - late = false - ) - data.addSubmission(course.id, submission, assignment.id) - data.addLTITool("Quizzes 2", "https://mobiledev.instructure.com/courses/1567973/external_tools/sessionless_launch?verifier=f85d3d69189890cde2f427a8efdc0e64850d8583bf8f2e0e0fa3704782d48b5378df5d52a35a4497ec18d3b0e201b3b2cab95e1347e7c5e286ac6636bf295c6b") - tokenLogin(data.domain, token, student) - routeTo("courses/${course.id}/assignments", data.domain) - - assignmentListPage.clickAssignment(assignment) - assignmentDetailsPage.clickSubmit() - //https://mobiledev.instructure.com/api/v1/courses/1567973/external_tools/sessionless_launch?assignment_id=24378681&launch_type=assessment - */ - } - private fun goToAssignmentFromList(): MockCanvas { // Test clicking on the Submission and Rubric button to load the Submission Details Page val data = MockCanvas.init( diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/GradesInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/GradesInteractionTest.kt index c3ab301674..69294fd566 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/GradesInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/GradesInteractionTest.kt @@ -80,7 +80,7 @@ class GradesInteractionTest : StudentTest() { val course = data.courses.values.first() gradesPage.clickGradeRow(course.name) - elementaryCoursePage.assertPageObjects() + courseGradesPage.assertPageObjects() Espresso.pressBack() gradesPage.assertPageObjects() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ShareExtensionInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ShareExtensionInteractionTest.kt index 2e9d14f952..91e77751f1 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ShareExtensionInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ShareExtensionInteractionTest.kt @@ -24,6 +24,7 @@ import androidx.core.content.FileProvider import androidx.test.espresso.intent.Intents import androidx.test.espresso.intent.matcher.IntentMatchers import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiSelector import com.instructure.canvas.espresso.Stub @@ -69,6 +70,8 @@ class ShareExtensionInteractionTest : StudentTest() { fun fileUploadDialogShowsCorrectlyForMyFilesUpload() { val data = createMockData() val student = data.students[0] + + File(getInstrumentation().targetContext.cacheDir, "file_upload").deleteRecursively() val uri = setupFileOnDevice("sample.jpg") val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) @@ -207,6 +210,8 @@ class ShareExtensionInteractionTest : StudentTest() { fun shareExtensionShowsUpCorrectlyWhenSharingMultipleFiles() { val data = createMockData() val student = data.students[0] + + File(getInstrumentation().targetContext.cacheDir, "file_upload").deleteRecursively() val uri = setupFileOnDevice("sample.jpg") val uri2 = setupFileOnDevice("samplepdf.pdf") val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/AssignmentDetailsPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/AssignmentDetailsPage.kt index 77bbd5d83f..03620abba6 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/AssignmentDetailsPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/AssignmentDetailsPage.kt @@ -27,12 +27,30 @@ import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import com.instructure.canvas.espresso.CanvasTest import com.instructure.canvas.espresso.containsTextCaseInsensitive import com.instructure.canvas.espresso.stringContainsTextCaseInsensitive import com.instructure.canvas.espresso.waitForMatcherWithSleeps import com.instructure.canvasapi2.models.Assignment -import com.instructure.espresso.* -import com.instructure.espresso.page.* +import com.instructure.espresso.OnViewWithId +import com.instructure.espresso.assertContainsText +import com.instructure.espresso.assertDisplayed +import com.instructure.espresso.assertHasText +import com.instructure.espresso.clearText +import com.instructure.espresso.click +import com.instructure.espresso.page.BasePage +import com.instructure.espresso.page.onView +import com.instructure.espresso.page.onViewWithText +import com.instructure.espresso.page.waitForView +import com.instructure.espresso.page.waitForViewWithText +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.swipeDown +import com.instructure.espresso.typeText +import com.instructure.espresso.waitForCheck import com.instructure.student.R import org.hamcrest.Matcher import org.hamcrest.Matchers.allOf @@ -135,6 +153,7 @@ open class AssignmentDetailsPage : BasePage(R.id.assignmentDetailsPage) { Espresso.onView(withText("Add Bookmark")).click() Espresso.onView(withId(R.id.bookmarkEditText)).clearText() Espresso.onView(withId(R.id.bookmarkEditText)).typeText(bookmarkName) + if(CanvasTest.isLandscapeDevice()) Espresso.pressBack() Espresso.onView(allOf(isAssignableFrom(AppCompatButton::class.java), containsTextCaseInsensitive("Save"))).click() } diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CourseGradesPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CourseGradesPage.kt index c7d4b23f3e..521c4e9162 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CourseGradesPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CourseGradesPage.kt @@ -18,16 +18,16 @@ package com.instructure.student.ui.pages import android.os.SystemClock.sleep import android.view.View -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.NoMatchingViewException +import androidx.test.espresso.Espresso import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.swipeDown +import androidx.test.espresso.assertion.ViewAssertions import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.hasSibling import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast import androidx.test.espresso.matcher.ViewMatchers.withChild -import androidx.test.espresso.matcher.ViewMatchers.withId import com.instructure.canvas.espresso.containsTextCaseInsensitive import com.instructure.canvas.espresso.scrollRecyclerView import com.instructure.canvas.espresso.withCustomConstraints @@ -37,11 +37,17 @@ import com.instructure.espresso.assertNotDisplayed import com.instructure.espresso.click import com.instructure.espresso.matchers.WaitForViewMatcher.waitForView import com.instructure.espresso.page.BasePage +import com.instructure.espresso.page.onView +import com.instructure.espresso.page.plus +import com.instructure.espresso.page.withAncestor +import com.instructure.espresso.page.withId +import com.instructure.espresso.page.withText +import com.instructure.espresso.scrollTo import com.instructure.espresso.typeText import com.instructure.student.R import org.hamcrest.Matcher import org.hamcrest.Matchers.allOf -import java.util.concurrent.TimeUnit +import java.util.concurrent.* class CourseGradesPage : BasePage(R.id.courseGradesPage) { private val gradeLabel by WaitForViewWithId(R.id.txtOverallGradeLabel) @@ -58,9 +64,13 @@ class CourseGradesPage : BasePage(R.id.courseGradesPage) { onView(itemMatcher).assertDisplayed() } + fun openAssignment(assignmentName: String) { + waitForView(withId(R.id.title) + withText(assignmentName)).scrollTo().click() + } + fun selectItem(itemMatcher: Matcher) { scrollToItem(itemMatcher) - onView(itemMatcher).click() + Espresso.onView(itemMatcher).click() } fun assertTotalGrade(matcher: Matcher) { @@ -138,4 +148,15 @@ class CourseGradesPage : BasePage(R.id.courseGradesPage) { gradeValue.check(matches(matcher)) } + fun clickOnExpandCollapseButton() { + onView(withId(R.id.expand_collapse) + hasSibling(withId(R.id.title) + withText(R.string.assignments))).click() + } + + fun assertAssignmentCount(count: Int) { + onView(withId(R.id.listView) + withAncestor(R.id.courseGradesPage)).check( + ViewAssertions.matches( + ViewMatchers.hasChildCount(count + 1) //because of the expandable 'header' we have to increase by 1. + )) + } + } \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DashboardPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DashboardPage.kt index 64e74a54d8..05953754ff 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DashboardPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DashboardPage.kt @@ -294,6 +294,15 @@ class DashboardPage : BasePage(R.id.dashboardPage) { onView(matcher).check(matches(Matchers.not(isDisplayed()))) } + + fun assertDashboardNotificationDisplayed(title: String, subTitle: String) { + onView(withId(R.id.uploadTitle) + withText(title)).assertDisplayed() + onView(withId(R.id.uploadSubtitle) + withText(subTitle)).assertDisplayed() + } + + fun clickOnDashboardNotification(subTitle: String) { + onView(withId(R.id.uploadSubtitle) + withText(subTitle)).click() + } } /** diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/FileUploadPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/FileUploadPage.kt index 4eaa9bf72c..158d895e8b 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/FileUploadPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/FileUploadPage.kt @@ -22,6 +22,7 @@ import androidx.test.espresso.assertion.ViewAssertions.doesNotExist import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom import androidx.test.espresso.matcher.ViewMatchers.withId +import com.instructure.canvas.espresso.containsTextCaseInsensitive import com.instructure.espresso.OnViewWithId import com.instructure.espresso.assertDisplayed import com.instructure.espresso.click @@ -63,7 +64,7 @@ class FileUploadPage : BasePage() { } fun removeFile(filename: String) { - val fileItemMatcher = withId(R.id.fileItem) + withDescendant(withId(R.id.fileName) + withText(filename)) + val fileItemMatcher = withId(R.id.fileItem) + withDescendant(withId(R.id.fileName) + containsTextCaseInsensitive(filename)) val removeMatcher = withId(R.id.removeFile) + ViewMatchers.isDescendantOfA(fileItemMatcher) waitForViewToBeClickable(removeMatcher).scrollTo().click() @@ -74,7 +75,7 @@ class FileUploadPage : BasePage() { } fun assertFileDisplayed(filename: String) { - onView(withId(R.id.fileName) + withText(filename)) + onView(withId(R.id.fileName) + containsTextCaseInsensitive(filename)).assertDisplayed() } fun assertFileNotDisplayed(filename: String) { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/LeftSideNavigationDrawerPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/LeftSideNavigationDrawerPage.kt index a598c66bf8..bcd295b004 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/LeftSideNavigationDrawerPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/LeftSideNavigationDrawerPage.kt @@ -11,11 +11,16 @@ import androidx.test.espresso.matcher.ViewMatchers.withText import com.instructure.canvas.espresso.waitForMatcherWithSleeps import com.instructure.canvasapi2.models.User import com.instructure.dataseeding.model.CanvasUserApiModel -import com.instructure.espresso.* +import com.instructure.espresso.OnViewWithContentDescription +import com.instructure.espresso.OnViewWithId +import com.instructure.espresso.assertDisplayed +import com.instructure.espresso.click import com.instructure.espresso.page.BasePage import com.instructure.espresso.page.onView import com.instructure.espresso.page.onViewWithId import com.instructure.espresso.page.onViewWithText +import com.instructure.espresso.page.waitForViewWithId +import com.instructure.espresso.scrollTo import com.instructure.student.R import org.hamcrest.CoreMatchers import org.hamcrest.Matcher @@ -39,7 +44,7 @@ class LeftSideNavigationDrawerPage: BasePage() { private fun clickMenu(menuId: Int) { onView(hamburgerButtonMatcher).click() - onViewWithId(menuId).scrollTo().click() + waitForViewWithId(menuId).scrollTo().click() } fun logout() { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/LoginSignInPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/LoginSignInPage.kt index 4173184b49..54173f0839 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/LoginSignInPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/LoginSignInPage.kt @@ -19,10 +19,7 @@ package com.instructure.student.ui.pages import androidx.test.espresso.web.assertion.WebViewAssertions.webMatches import androidx.test.espresso.web.sugar.Web import androidx.test.espresso.web.sugar.Web.onWebView -import androidx.test.espresso.web.webdriver.DriverAtoms.findElement -import androidx.test.espresso.web.webdriver.DriverAtoms.getText -import androidx.test.espresso.web.webdriver.DriverAtoms.webClick -import androidx.test.espresso.web.webdriver.DriverAtoms.webKeys +import androidx.test.espresso.web.webdriver.DriverAtoms.* import androidx.test.espresso.web.webdriver.Locator import com.instructure.dataseeding.model.CanvasUserApiModel import com.instructure.espresso.OnViewWithId @@ -89,10 +86,12 @@ class LoginSignInPage: BasePage() { //region UI Action Helpers private fun enterEmail(email: String) { + emailField().perform(clearElement()) emailField().perform(webKeys(email)) } private fun enterPassword(password: String) { + passwordField().perform(clearElement()) passwordField().perform(webKeys(password)) } diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/NotificationPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/NotificationPage.kt index 45fc71cfe3..3b6c8a3b02 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/NotificationPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/NotificationPage.kt @@ -30,6 +30,7 @@ import com.instructure.espresso.page.plus import com.instructure.espresso.page.withAncestor import com.instructure.espresso.page.withId import com.instructure.espresso.page.withText +import com.instructure.espresso.scrollTo import com.instructure.espresso.waitForCheck import com.instructure.student.R import org.hamcrest.CoreMatchers.allOf @@ -45,9 +46,8 @@ class NotificationPage : BasePage() { } fun assertHasGrade(title: String, grade: String) { - val matcher = allOf(withText(title) + hasSibling(withId(R.id.description) + withText("Grade: $grade"))) - scrollRecyclerView(R.id.listView, matcher) - onView(matcher).assertDisplayed() + val matcher = allOf(withText(title.dropLast(1)) + hasSibling(withId(R.id.description) + withText("Grade: $grade"))) + onView(matcher).scrollTo().assertDisplayed() } fun clickNotification(title: String) { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/ProfileSettingsPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/ProfileSettingsPage.kt index 76c4bdb722..fa6d671d36 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/ProfileSettingsPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/ProfileSettingsPage.kt @@ -1,12 +1,13 @@ package com.instructure.student.ui.pages +import androidx.test.espresso.Espresso import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.clearText import androidx.test.espresso.action.ViewActions.typeText import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.isEnabled import androidx.test.espresso.matcher.ViewMatchers.withId +import com.instructure.canvas.espresso.CanvasTest import com.instructure.canvas.espresso.containsTextCaseInsensitive import com.instructure.espresso.OnViewWithId import com.instructure.espresso.click @@ -24,7 +25,7 @@ class ProfileSettingsPage : BasePage(R.id.profile_settings_fragment) { onView(withId(R.id.textInput)).perform(clearText()) onView(withId(R.id.textInput)).perform(typeText(newUserName)) - + if(CanvasTest.isLandscapeDevice()) Espresso.pressBack() onView(containsTextCaseInsensitive("OK")).click() } diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/TodoPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/TodoPage.kt index a189f8c8e9..dfe16866b1 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/TodoPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/TodoPage.kt @@ -36,6 +36,7 @@ import com.instructure.espresso.scrollTo import com.instructure.student.R import org.hamcrest.Matchers import org.hamcrest.Matchers.allOf +import java.lang.Thread.sleep class TodoPage: BasePage(R.id.todoPage) { @@ -45,6 +46,21 @@ class TodoPage: BasePage(R.id.todoPage) { assertTextDisplayedInRecyclerView(assignment.name) } + fun assertAssignmentDisplayedWithRetries(assignment: AssignmentApiModel, retryAttempt: Int) { + + run assignmentDisplayedRepeat@{ + repeat(retryAttempt) { + try { + sleep(3000) + assertTextDisplayedInRecyclerView(assignment.name) + return@assignmentDisplayedRepeat + } catch (e: AssertionError) { + println("Attempt failed. The '${assignment.name}' assignment is not displayed, probably because of the API slowness.") + } + } + } + } + fun assertAssignmentNotDisplayed(assignment: AssignmentApiModel) { onView(withText(assignment.name)).check(doesNotExist()) } diff --git a/apps/student/src/main/java/com/instructure/student/activity/CandroidPSPDFActivity.kt b/apps/student/src/main/java/com/instructure/student/activity/CandroidPSPDFActivity.kt index 8d5f1f8e66..bee62d8765 100644 --- a/apps/student/src/main/java/com/instructure/student/activity/CandroidPSPDFActivity.kt +++ b/apps/student/src/main/java/com/instructure/student/activity/CandroidPSPDFActivity.kt @@ -95,17 +95,21 @@ class CandroidPSPDFActivity : PdfActivity(), ToolbarCoordinatorLayout.OnContextu ViewStyler.setStatusBarDark(this, color) } - override fun onPrepareOptionsMenu(menu: Menu): Boolean { - super.onPrepareOptionsMenu(menu) + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + super.onCreateOptionsMenu(menu) menuInflater.inflate(R.menu.pspdf_activity_menu, menu) if (submissionTarget != null) { // If targeted for submission, change the menu item title from "Upload to Canvas" to "Submit Assignment" - val item = menu.findItem(R.id.upload_item) - item.title = getString(R.string.submitAssignment) + val item = menu?.findItem(R.id.upload_item) + item?.title = getString(R.string.submitAssignment) } return true } + override fun onGetShowAsAction(menuItemId: Int, defaultShowAsAction: Int): Int { + return if (menuItemId != R.id.upload_item) MenuItem.SHOW_AS_ACTION_ALWAYS else super.onGetShowAsAction(menuItemId, defaultShowAsAction) + } + override fun onOptionsItemSelected(item: MenuItem): Boolean { super.onOptionsItemSelected(item) return if (item.itemId == R.id.upload_item) { diff --git a/apps/student/src/main/java/com/instructure/student/activity/NavigationActivity.kt b/apps/student/src/main/java/com/instructure/student/activity/NavigationActivity.kt index eb3df73c72..e1d8e8cfa3 100644 --- a/apps/student/src/main/java/com/instructure/student/activity/NavigationActivity.kt +++ b/apps/student/src/main/java/com/instructure/student/activity/NavigationActivity.kt @@ -240,9 +240,9 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog. } override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) navigationDrawerBinding = NavigationDrawerBinding.bind(binding.root) canvasLoadingBinding = LoadingCanvasViewBinding.bind(binding.root) - super.onCreate(savedInstanceState) setContentView(binding.root) val masqueradingUserId: Long = intent.getLongExtra(Const.QR_CODE_MASQUERADE_ID, 0L) if (masqueradingUserId != 0L) { diff --git a/apps/student/src/main/java/com/instructure/student/adapter/DashboardRecyclerAdapter.kt b/apps/student/src/main/java/com/instructure/student/adapter/DashboardRecyclerAdapter.kt index 5c5ef240c8..1dd47f1cf6 100644 --- a/apps/student/src/main/java/com/instructure/student/adapter/DashboardRecyclerAdapter.kt +++ b/apps/student/src/main/java/com/instructure/student/adapter/DashboardRecyclerAdapter.kt @@ -129,11 +129,9 @@ class DashboardRecyclerAdapter( val dashboardCards = awaitApi> { CourseManager.getDashboardCourses(isRefresh, it) } mCourseMap = rawCourses.associateBy { it.id } - val groupMap = groups.associateBy { it.id } // Map not null is needed because the dashboard api can return unpublished courses - val visibleCourses = dashboardCards.mapNotNull { mCourseMap[it.id] } - .filter { it.isCurrentEnrolment() || it.isFutureEnrolment() } + val visibleCourses = dashboardCards.map { createCourseFromDashboardCard(it, mCourseMap) } // Filter groups val allActiveGroups = groups.filter { group -> group.isActive(mCourseMap[group.courseId])} @@ -157,6 +155,15 @@ class DashboardRecyclerAdapter( } } + private fun createCourseFromDashboardCard(dashboardCard: DashboardCard, courseMap: Map): Course { + val course = courseMap[dashboardCard.id] + return if (course != null) { + course + } else { + Course(id = dashboardCard.id, name = dashboardCard.shortName ?: "", originalName = dashboardCard.originalName, courseCode = dashboardCard.courseCode) + } + } + private fun hasValidCourseForEnrollment(enrollment: Enrollment): Boolean { return mCourseMap[enrollment.courseId]?.let { course -> course.isValidTerm() && !course.accessRestrictedByDate && isEnrollmentBeforeEndDateOrNotRestricted(course) diff --git a/apps/student/src/main/java/com/instructure/student/features/assignmentdetails/gradecellview/GradeCellViewData.kt b/apps/student/src/main/java/com/instructure/student/features/assignmentdetails/gradecellview/GradeCellViewData.kt index a9d8eefaf0..9d0746a6f2 100644 --- a/apps/student/src/main/java/com/instructure/student/features/assignmentdetails/gradecellview/GradeCellViewData.kt +++ b/apps/student/src/main/java/com/instructure/student/features/assignmentdetails/gradecellview/GradeCellViewData.kt @@ -188,6 +188,6 @@ data class GradeCellViewData( System.lineSeparator() + resources.getString(R.string.a11y_gradeCellContentDescriptionHint) private val Submission.isSubmitted - get() = workflowState == "submitted" + get() = submittedAt != null && !isGraded && !excused } } diff --git a/apps/student/src/main/java/com/instructure/student/features/elementary/course/ElementaryCourseViewModel.kt b/apps/student/src/main/java/com/instructure/student/features/elementary/course/ElementaryCourseViewModel.kt index 1b322806b0..a754227242 100644 --- a/apps/student/src/main/java/com/instructure/student/features/elementary/course/ElementaryCourseViewModel.kt +++ b/apps/student/src/main/java/com/instructure/student/features/elementary/course/ElementaryCourseViewModel.kt @@ -18,7 +18,6 @@ package com.instructure.student.features.elementary.course import android.content.res.Resources import android.graphics.drawable.Drawable -import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -68,7 +67,18 @@ class ElementaryCourseViewModel @Inject constructor( val isElementaryCourse = dashboardCards.any { it.id == canvasContext.id && it.isK5Subject } if (isElementaryCourse) { - loadDataForElementary(canvasContext) + val tabs = loadTabs(canvasContext) + + val isTabHidden = tabId.isNotEmpty() && tabs.find { it.tabId == tabId } == null + if (isTabHidden) { + handleNonElementaryCourse(tabId) + return@launch + } + + val tabViewData = createTabs(canvasContext, tabs).toMutableList() + + _data.postValue(ElementaryCourseViewData(tabViewData, canvasContext.backgroundColor)) + _state.postValue(ViewState.Success) } else { handleNonElementaryCourse(tabId) } @@ -79,7 +89,7 @@ class ElementaryCourseViewModel @Inject constructor( } } - private suspend fun loadDataForElementary(canvasContext: CanvasContext) { + private suspend fun loadTabs(canvasContext: CanvasContext): List { val tabs = tabManager.getTabsForElementaryAsync(canvasContext, false).await().dataOrThrow val hasResources = tabs.firstOrNull { it.isExternal } != null var filteredTabs = tabs.filter { !it.isHidden && !it.isExternal }.sortedBy { it.position } @@ -94,9 +104,7 @@ class ElementaryCourseViewModel @Inject constructor( ) } - val tabViewData = createTabs(canvasContext, filteredTabs) - _data.postValue(ElementaryCourseViewData(tabViewData, canvasContext.backgroundColor)) - _state.postValue(ViewState.Success) + return filteredTabs } private fun handleNonElementaryCourse(tabId: String) { @@ -107,37 +115,46 @@ class ElementaryCourseViewModel @Inject constructor( } } + private fun getIconDrawable(tabId: String): Drawable? { + return when (tabId) { + Tab.HOME_ID -> { + resources.getDrawable(R.drawable.ic_home) + } + + Tab.SCHEDULE_ID -> { + resources.getDrawable(R.drawable.ic_schedule) + } + + Tab.MODULES_ID -> { + resources.getDrawable(R.drawable.ic_modules) + } + + Tab.GRADES_ID -> { + resources.getDrawable(R.drawable.ic_grades) + } + + Tab.RESOURCES_ID -> { + resources.getDrawable(R.drawable.ic_resources) + } + + else -> { + null + } + } + } + private suspend fun createTabs(canvasContext: CanvasContext, tabs: List): List { val prefix = if (canvasContext.isCourse) "${apiPrefs.fullDomain}/courses/${canvasContext.id}?embed=true" else "${apiPrefs.fullDomain}/groups/${canvasContext.id}?embed=true" return tabs.map { - val drawable: Drawable? - val url: String - when (it.tabId) { - Tab.HOME_ID -> { - drawable = resources.getDrawable(R.drawable.ic_home) - url = "$prefix#home" - } - Tab.SCHEDULE_ID -> { - drawable = resources.getDrawable(R.drawable.ic_schedule) - url = "$prefix#schedule" - } - Tab.MODULES_ID -> { - drawable = resources.getDrawable(R.drawable.ic_modules) - url = "$prefix#modules" - } - Tab.GRADES_ID -> { - drawable = resources.getDrawable(R.drawable.ic_grades) - url = "$prefix#grades" - } - Tab.RESOURCES_ID -> { - drawable = resources.getDrawable(R.drawable.ic_resources) - url = "$prefix#resources" - } - else -> { - drawable = null - url = it.htmlUrl ?: "" - } + val drawable = getIconDrawable(it.tabId) + val url = when (it.tabId) { + Tab.HOME_ID -> "$prefix#home" + Tab.SCHEDULE_ID -> "$prefix#schedule" + Tab.MODULES_ID -> "$prefix#modules" + Tab.GRADES_ID -> "$prefix#grades" + Tab.RESOURCES_ID -> "$prefix#resources" + else -> it.htmlUrl ?: "" } val authenticatedUrl = if (apiPrefs.isStudentView) { diff --git a/apps/student/src/main/java/com/instructure/student/fragment/FileListFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/FileListFragment.kt index e41d5436e1..de46927ecc 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/FileListFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/FileListFragment.kt @@ -74,10 +74,21 @@ class FileListFragment : ParentFragment(), Bookmarkable, FileUploadDialogParent @Suppress("unused") @PageViewUrl - private fun makePageViewUrl() = - if (canvasContext.type == CanvasContext.Type.USER) "${ApiPrefs.fullDomain}/files" + private fun makePageViewUrl(): String { + var url = if (canvasContext.type == CanvasContext.Type.USER) "${ApiPrefs.fullDomain}/files" else "${ApiPrefs.fullDomain}/${canvasContext.contextId.replace("_", "s/")}/files" + if (folder != null && folder?.isRoot == false) { + url += "/folder/" + if (canvasContext.type == CanvasContext.Type.USER) { + url += "users_${canvasContext.id}/" + } + url += folder?.fullName?.split(" ", limit = 2)?.get(1)?.replaceFirst("files/", "") ?: "" + } + + return url + } + private var recyclerAdapter: FileListRecyclerAdapter? = null private var folder: FileFolder? by NullableParcelableArg(key = Const.FOLDER) diff --git a/apps/student/src/main/java/com/instructure/student/fragment/InboxComposeMessageFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/InboxComposeMessageFragment.kt index 3a478db30b..6962b7d493 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/InboxComposeMessageFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/InboxComposeMessageFragment.kt @@ -33,6 +33,7 @@ import com.instructure.canvasapi2.managers.InboxManager import com.instructure.canvasapi2.models.* import com.instructure.canvasapi2.utils.APIHelper import com.instructure.canvasapi2.utils.isValid +import com.instructure.canvasapi2.utils.pageview.PageView import com.instructure.canvasapi2.utils.weave.* import com.instructure.interactions.router.Route import com.instructure.pandautils.analytics.SCREEN_VIEW_INBOX_COMPOSE @@ -40,7 +41,7 @@ import com.instructure.pandautils.analytics.ScreenView import com.instructure.pandautils.binding.viewBinding import com.instructure.pandautils.features.file.upload.FileUploadDialogFragment import com.instructure.pandautils.features.file.upload.FileUploadDialogParent -import com.instructure.pandautils.room.appdatabase.daos.AttachmentDao +import com.instructure.pandautils.room.common.daos.AttachmentDao import com.instructure.pandautils.utils.* import com.instructure.student.R import com.instructure.student.adapter.CanvasContextSpinnerAdapter @@ -60,6 +61,7 @@ import org.greenrobot.eventbus.ThreadMode import java.util.* import javax.inject.Inject +@PageView(url = "conversations/compose") @ScreenView(SCREEN_VIEW_INBOX_COMPOSE) @AndroidEntryPoint class InboxComposeMessageFragment : ParentFragment(), FileUploadDialogParent { diff --git a/apps/student/src/main/java/com/instructure/student/mobius/conferences/conference_details/ui/ConferenceDetailsFragment.kt b/apps/student/src/main/java/com/instructure/student/mobius/conferences/conference_details/ui/ConferenceDetailsFragment.kt index e117f80907..a99e308aac 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/conferences/conference_details/ui/ConferenceDetailsFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/conferences/conference_details/ui/ConferenceDetailsFragment.kt @@ -20,6 +20,8 @@ import android.view.LayoutInflater import android.view.ViewGroup import com.instructure.canvasapi2.models.CanvasContext import com.instructure.canvasapi2.models.Conference +import com.instructure.canvasapi2.utils.pageview.PageView +import com.instructure.canvasapi2.utils.pageview.PageViewUrlParam import com.instructure.interactions.router.Route import com.instructure.pandautils.analytics.SCREEN_VIEW_CONFERENCE_DETAILS import com.instructure.pandautils.analytics.ScreenView @@ -31,6 +33,7 @@ import com.instructure.student.databinding.FragmentConferenceDetailsBinding import com.instructure.student.mobius.common.ui.MobiusFragment import com.instructure.student.mobius.conferences.conference_details.* +@PageView(url = "{canvasContext}/conferences/{conferenceId}") @ScreenView(SCREEN_VIEW_CONFERENCE_DETAILS) class ConferenceDetailsFragment : MobiusFragment() { @@ -58,6 +61,9 @@ class ConferenceDetailsFragment : parent ) + @PageViewUrlParam("conferenceId") + fun getConferenceId() = conference.id + companion object { fun makeRoute(canvasContext: CanvasContext, conference: Conference): Route { val bundle = canvasContext.makeBundle { diff --git a/apps/student/src/main/java/com/instructure/student/mobius/conferences/conference_list/ui/ConferenceListFragment.kt b/apps/student/src/main/java/com/instructure/student/mobius/conferences/conference_list/ui/ConferenceListFragment.kt index 4a449474ef..a02fbc4789 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/conferences/conference_list/ui/ConferenceListFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/conferences/conference_list/ui/ConferenceListFragment.kt @@ -31,7 +31,7 @@ import com.instructure.student.databinding.FragmentConferenceListBinding import com.instructure.student.mobius.common.ui.MobiusFragment import com.instructure.student.mobius.conferences.conference_list.* -@PageView(url = "courses/{canvasContext}/conferences") +@PageView(url = "{canvasContext}/conferences") @ScreenView(SCREEN_VIEW_CONFERENCE_LIST) class ConferenceListFragment : MobiusFragment() { diff --git a/apps/student/src/main/java/com/instructure/student/util/FileUtils.kt b/apps/student/src/main/java/com/instructure/student/util/FileUtils.kt index c50a961d90..73fb0b1066 100644 --- a/apps/student/src/main/java/com/instructure/student/util/FileUtils.kt +++ b/apps/student/src/main/java/com/instructure/student/util/FileUtils.kt @@ -85,7 +85,6 @@ object FileUtils { .scrollDirection(PageScrollDirection.HORIZONTAL) .showThumbnailGrid() .setDocumentInfoViewSeparated(false) - .setThumbnailBarMode(ThumbnailBarMode.THUMBNAIL_BAR_MODE_PINNED) .enableDocumentEditor() .enabledAnnotationTools(annotationCreationList) .editableAnnotationTypes(annotationEditList) diff --git a/apps/student/src/main/res/menu/pspdf_activity_menu.xml b/apps/student/src/main/res/menu/pspdf_activity_menu.xml index fc60fbaa75..ecadf9f40f 100644 --- a/apps/student/src/main/res/menu/pspdf_activity_menu.xml +++ b/apps/student/src/main/res/menu/pspdf_activity_menu.xml @@ -1,5 +1,4 @@ - - -