diff --git a/android-vault b/android-vault index ddd561a8ef..15225ede6c 160000 --- a/android-vault +++ b/android-vault @@ -1 +1 @@ -Subproject commit ddd561a8ef8289f2ed248925679827b2da5ab016 +Subproject commit 15225ede6c44da8265e5cdaea34d49dbc47cb8f5 diff --git a/apps/flutter_parent/android/app/build.gradle b/apps/flutter_parent/android/app/build.gradle index 823d43f00f..5fd86f30e5 100644 --- a/apps/flutter_parent/android/app/build.gradle +++ b/apps/flutter_parent/android/app/build.gradle @@ -104,7 +104,7 @@ dependencies { testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' implementation "com.squareup.okhttp3:okhttp:4.9.1" implementation 'org.jsoup:jsoup:1.11.3' implementation 'com.google.gms:google-services:4.3.14' diff --git a/apps/flutter_parent/lib/l10n/app_localizations.dart b/apps/flutter_parent/lib/l10n/app_localizations.dart index 53eda6d918..86768767b3 100644 --- a/apps/flutter_parent/lib/l10n/app_localizations.dart +++ b/apps/flutter_parent/lib/l10n/app_localizations.dart @@ -989,11 +989,18 @@ class AppLocalizations { desc: 'Screen reader-friendly replacement for the "-" character in letter grades like "A-"', ); - String latePenalty(String pointsLost) => Intl.message( - 'Late penalty (-$pointsLost)', + String yourGrade(String pointsAchieved) => Intl.message( + 'Your grade: $pointsAchieved', + desc: 'Text displayed when a late penalty has been applied to the assignment, this is the achieved score without the penalty', + args: [pointsAchieved], + name: 'yourGrade', + ); + + String latePenaltyUpdated(String pointsLost) => Intl.message( + 'Late Penalty: -$pointsLost pts', desc: 'Text displayed when a late penalty has been applied to the assignment', args: [pointsLost], - name: 'latePenalty', + name: 'latePenaltyUpdated', ); String finalGrade(String grade) => Intl.message( diff --git a/apps/flutter_parent/lib/models/grade_cell_data.dart b/apps/flutter_parent/lib/models/grade_cell_data.dart index 642c29700e..f30b60241f 100644 --- a/apps/flutter_parent/lib/models/grade_cell_data.dart +++ b/apps/flutter_parent/lib/models/grade_cell_data.dart @@ -40,6 +40,7 @@ abstract class GradeCellData implements Built 0.0) { grade = ''; // Grade will be shown in the 'final grade' text var pointsDeducted = NumberFormat.decimalPattern().format(submission.pointsDeducted ?? 0.0); - latePenalty = l10n.latePenalty(pointsDeducted); + var pointsAchieved = NumberFormat.decimalPattern().format(submission.enteredScore); + yourGrade = l10n.yourGrade(pointsAchieved); + latePenalty = l10n.latePenaltyUpdated(pointsDeducted); finalGrade = l10n.finalGrade(submission.grade ?? grade); } @@ -166,6 +171,7 @@ abstract class GradeCellData implements Built _$this._outOf; set outOf(String? outOf) => _$this._outOf = outOf; + String? _yourGrade; + String? get yourGrade => _$this._yourGrade; + set yourGrade(String? yourGrade) => _$this._yourGrade = yourGrade; + String? _latePenalty; String? get latePenalty => _$this._latePenalty; set latePenalty(String? latePenalty) => _$this._latePenalty = latePenalty; @@ -221,6 +233,7 @@ class GradeCellDataBuilder _grade = $v.grade; _gradeContentDescription = $v.gradeContentDescription; _outOf = $v.outOf; + _yourGrade = $v.yourGrade; _latePenalty = $v.latePenalty; _finalGrade = $v.finalGrade; _$v = null; @@ -264,6 +277,7 @@ class GradeCellDataBuilder grade: BuiltValueNullFieldError.checkNotNull(grade, r'GradeCellData', 'grade'), gradeContentDescription: BuiltValueNullFieldError.checkNotNull(gradeContentDescription, r'GradeCellData', 'gradeContentDescription'), outOf: BuiltValueNullFieldError.checkNotNull(outOf, r'GradeCellData', 'outOf'), + yourGrade: BuiltValueNullFieldError.checkNotNull(yourGrade, r'GradeCellData', 'yourGrade'), latePenalty: BuiltValueNullFieldError.checkNotNull(latePenalty, r'GradeCellData', 'latePenalty'), finalGrade: BuiltValueNullFieldError.checkNotNull(finalGrade, r'GradeCellData', 'finalGrade')); replace(_$result); diff --git a/apps/flutter_parent/lib/screens/account_creation/account_creation_interactor.dart b/apps/flutter_parent/lib/screens/account_creation/account_creation_interactor.dart index 584172d22a..e042eeba79 100644 --- a/apps/flutter_parent/lib/screens/account_creation/account_creation_interactor.dart +++ b/apps/flutter_parent/lib/screens/account_creation/account_creation_interactor.dart @@ -35,6 +35,6 @@ class AccountCreationInteractor { } launchPrivacyPolicy() { - locator().launch('https://www.instructure.com/canvas/privacy'); + locator().launch('https://www.instructure.com/policies/product-privacy-policy'); } } diff --git a/apps/flutter_parent/lib/screens/assignments/grade_cell.dart b/apps/flutter_parent/lib/screens/assignments/grade_cell.dart index 9d8bb559bf..d8bac77e11 100644 --- a/apps/flutter_parent/lib/screens/assignments/grade_cell.dart +++ b/apps/flutter_parent/lib/screens/assignments/grade_cell.dart @@ -18,6 +18,7 @@ import 'package:flutter_parent/models/assignment.dart'; import 'package:flutter_parent/models/course.dart'; import 'package:flutter_parent/models/grade_cell_data.dart'; import 'package:flutter_parent/models/submission.dart'; +import 'package:flutter_parent/utils/design/parent_colors.dart'; import 'package:flutter_parent/utils/design/parent_theme.dart'; import 'package:percent_indicator/circular_percent_indicator.dart'; @@ -134,37 +135,49 @@ class GradeCell extends StatelessWidget { ], ), if (!_isGradedRestrictQuantitativeData) SizedBox(width: 16), - if (!_isGradedRestrictQuantitativeData) Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (data.grade.isNotEmpty) - Text( - data.grade, - key: Key('grade-cell-grade'), - style: Theme.of(context).textTheme.headlineMedium, - semanticsLabel: data.gradeContentDescription, - ), - if (data.outOf.isNotEmpty) Text(data.outOf, key: Key('grade-cell-out-of')), - if (data.latePenalty.isNotEmpty) - Text( - data.latePenalty, - style: TextStyle(color: data.accentColor), - key: Key('grade-cell-late-penalty'), - ), - if (data.finalGrade.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 8), - child: Text( - data.finalGrade, - key: Key('grade-cell-final-grade'), - style: Theme.of(context).textTheme.titleMedium, + if (!_isGradedRestrictQuantitativeData) + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (data.grade.isNotEmpty) + Text( + data.grade, + key: Key('grade-cell-grade'), + style: Theme.of(context).textTheme.headlineMedium, + semanticsLabel: data.gradeContentDescription, + ), + if (data.outOf.isNotEmpty) Text(data.outOf, key: Key('grade-cell-out-of')), + if (data.yourGrade.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 4), + child: Text( + data.yourGrade, + key: Key('grade-cell-your-grade'), + ), + ), + if (data.latePenalty.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 4), + child: Text( + data.latePenalty, + style: TextStyle(color: ParentColors.failure), + key: Key('grade-cell-late-penalty'), + ), + ), + if (data.finalGrade.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 4), + child: Text( + data.finalGrade, + key: Key('grade-cell-final-grade'), + style: Theme.of(context).textTheme.titleMedium, + ), ), - ), - ], + ], + ), ), - ), ], ); } diff --git a/apps/flutter_parent/lib/screens/settings/legal_screen.dart b/apps/flutter_parent/lib/screens/settings/legal_screen.dart index c6b56ff3cd..90323f5401 100644 --- a/apps/flutter_parent/lib/screens/settings/legal_screen.dart +++ b/apps/flutter_parent/lib/screens/settings/legal_screen.dart @@ -34,7 +34,7 @@ class LegalScreen extends StatelessWidget { children: [ _LegalRow( label: l10n.privacyPolicy, - onTap: () => locator().launch('https://www.instructure.com/canvas/privacy'), + onTap: () => locator().launch('https://www.instructure.com/policies/product-privacy-policy'), icon: CanvasIcons.admin, ), _LegalRow( diff --git a/apps/flutter_parent/test/screens/account_creation/account_creation_interactor_test.dart b/apps/flutter_parent/test/screens/account_creation/account_creation_interactor_test.dart index 600529acca..0569a4a601 100644 --- a/apps/flutter_parent/test/screens/account_creation/account_creation_interactor_test.dart +++ b/apps/flutter_parent/test/screens/account_creation/account_creation_interactor_test.dart @@ -57,7 +57,7 @@ void main() { test('launchPrivacyPolicy calls the url launcher', () { AccountCreationInteractor().launchPrivacyPolicy(); verify( - launcher.launch('https://www.instructure.com/canvas/privacy'), + launcher.launch('https://www.instructure.com/policies/product-privacy-policy'), ).called(1); }); } diff --git a/apps/flutter_parent/test/screens/assignments/grade_cell_data_test.dart b/apps/flutter_parent/test/screens/assignments/grade_cell_data_test.dart index 134975c7df..2fe2782299 100644 --- a/apps/flutter_parent/test/screens/assignments/grade_cell_data_test.dart +++ b/apps/flutter_parent/test/screens/assignments/grade_cell_data_test.dart @@ -250,10 +250,11 @@ void main() { ..grade = '79' ..score = 79.0); var expected = baseGradedState.rebuild((b) => b - ..graphPercent = 0.85 - ..score = '85' + ..graphPercent = 0.79 + ..score = '79' ..showPointsLabel = true - ..latePenalty = 'Late penalty (-6)' + ..yourGrade = 'Your grade: 85' + ..latePenalty = 'Late Penalty: -6 pts' ..finalGrade = 'Final Grade: 79'); var actual = GradeCellData.forSubmission(baseCourse, baseAssignment, submission, theme, l10n); expect(actual, expected); diff --git a/apps/flutter_parent/test/screens/assignments/grade_cell_widget_test.dart b/apps/flutter_parent/test/screens/assignments/grade_cell_widget_test.dart index 37bd688ec3..3cc7c10885 100644 --- a/apps/flutter_parent/test/screens/assignments/grade_cell_widget_test.dart +++ b/apps/flutter_parent/test/screens/assignments/grade_cell_widget_test.dart @@ -16,6 +16,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_parent/l10n/app_localizations.dart'; import 'package:flutter_parent/models/grade_cell_data.dart'; import 'package:flutter_parent/screens/assignments/grade_cell.dart'; +import 'package:flutter_parent/utils/design/parent_colors.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:percent_indicator/circular_percent_indicator.dart'; @@ -158,11 +159,13 @@ void main() { GradeCellData data = GradeCellData((b) => b ..state = GradeCellState.graded ..accentColor = Colors.pinkAccent - ..latePenalty = 'Late penalty: (-25)'); + ..yourGrade = 'Your grade: 85' + ..latePenalty = 'Late penalty: -25'); await setupWithData(tester, data); expect(find.byKey(Key('grade-cell-late-penalty')).evaluate(), find.text(data.latePenalty).evaluate()); - expect(tester.widget(find.byKey(Key('grade-cell-late-penalty'))).style!.color, data.accentColor); + expect(find.byKey(Key('grade-cell-your-grade')).evaluate(), find.text(data.yourGrade).evaluate()); + expect(tester.widget(find.byKey(Key('grade-cell-late-penalty'))).style!.color, ParentColors.failure); }); testWidgetsWithAccessibilityChecks('Displays final grade text', (tester) async { diff --git a/apps/flutter_parent/test/screens/settings/legal_screen_test.dart b/apps/flutter_parent/test/screens/settings/legal_screen_test.dart index 6055ed0335..bd6ce8d482 100644 --- a/apps/flutter_parent/test/screens/settings/legal_screen_test.dart +++ b/apps/flutter_parent/test/screens/settings/legal_screen_test.dart @@ -47,7 +47,7 @@ void main() { await tester.tap(find.text(l10n.privacyPolicy)); await tester.pumpAndSettle(); - verify(mockLauncher.launch('https://www.instructure.com/canvas/privacy')).called(1); + verify(mockLauncher.launch('https://www.instructure.com/policies/product-privacy-policy')).called(1); }); testWidgetsWithAccessibilityChecks('tapping github launches url', (tester) async { diff --git a/apps/settings.gradle b/apps/settings.gradle index 011f248a08..9fff40bf18 100644 --- a/apps/settings.gradle +++ b/apps/settings.gradle @@ -22,8 +22,6 @@ include ':pandautils' include ':rceditor' include ':recyclerview' include ':pandares' -include ':panda_annotations' -include ':panda_processor' include ':DocumentScanner' project(':annotations').projectDir = new File(rootProject.projectDir, '/../libs/annotations') @@ -38,6 +36,4 @@ project(':pandautils').projectDir = new File(rootProject.projectDir, '/../libs/p project(':rceditor').projectDir = new File(rootProject.projectDir, '/../libs/rceditor') project(':recyclerview').projectDir = new File(rootProject.projectDir, '/../libs/recyclerview') project(':pandares').projectDir = new File(rootProject.projectDir, '/../libs/pandares') -project(':panda_annotations').projectDir = new File(rootProject.projectDir, '/../libs/panda_annotations') -project(':panda_processor').projectDir = new File(rootProject.projectDir, '/../libs/panda_processor') project(':DocumentScanner').projectDir = new File(rootProject.projectDir, '/../libs/DocumentScanner') diff --git a/apps/student/build.gradle b/apps/student/build.gradle index eb671160b8..f27f2dd8e4 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 = 255 - versionName = '6.26.1' + versionCode = 258 + versionName = '7.0.2' vectorDrawables.useSupportLibrary = true multiDexEnabled = true @@ -244,8 +244,6 @@ android { } dependencies { - implementation project(path: ':panda_annotations') - kaptAndroidTestQa project(path: ':panda_processor') implementation fileTree(dir: 'libs', include: ['*.jar']) @@ -320,7 +318,8 @@ dependencies { implementation Libs.SQLDELIGHT /* Qr Code */ - implementation Libs.JOURNEY_ZXING + implementation (Libs.JOURNEY_ZXING) { transitive = false } + implementation Libs.JOURNEY_ZXING_CORE /* AAC */ implementation Libs.VIEW_MODEL @@ -349,6 +348,8 @@ dependencies { implementation Libs.ROOM kapt Libs.ROOM_COMPILER implementation Libs.ROOM_COROUTINES + + testImplementation Libs.HAMCREST } // Comment out this line if the reporting logic starts going wonky. diff --git a/apps/student/src/androidTest/java/com/instructure/student/espresso/TestAppManager.kt b/apps/student/src/androidTest/java/com/instructure/student/espresso/TestAppManager.kt index c99b8cf92d..fc00cc637e 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/espresso/TestAppManager.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/espresso/TestAppManager.kt @@ -16,7 +16,6 @@ */ package com.instructure.student.espresso -import androidx.work.Configuration import androidx.work.WorkerFactory import com.instructure.student.util.BaseAppManager @@ -25,8 +24,6 @@ open class TestAppManager : BaseAppManager() { var workerFactory: WorkerFactory? = null override fun getWorkManagerFactory(): WorkerFactory { - return workerFactory?.let { - it - } ?: WorkerFactory.getDefaultWorkerFactory() + return workerFactory ?: WorkerFactory.getDefaultWorkerFactory() } } \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/AnnouncementsE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/AnnouncementsE2ETest.kt index 755499d291..29646fe3f9 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/AnnouncementsE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/AnnouncementsE2ETest.kt @@ -19,13 +19,13 @@ package com.instructure.student.ui.e2e import android.util.Log import androidx.test.espresso.Espresso import com.instructure.canvas.espresso.E2E +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.refresh import com.instructure.canvasapi2.models.DiscussionEntry import com.instructure.dataseeding.api.DiscussionTopicsApi -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -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.seedData import com.instructure.student.ui.utils.tokenLogin diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/AssignmentsE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/AssignmentsE2ETest.kt index f546bc83ea..3793f2be87 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/AssignmentsE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/AssignmentsE2ETest.kt @@ -22,6 +22,10 @@ import androidx.test.espresso.Espresso import androidx.test.espresso.matcher.ViewMatchers import androidx.test.rule.GrantPermissionRule import com.instructure.canvas.espresso.E2E +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.dataseeding.api.AssignmentGroupsApi import com.instructure.dataseeding.api.AssignmentsApi import com.instructure.dataseeding.api.CoursesApi @@ -38,10 +42,6 @@ import com.instructure.dataseeding.util.ago import com.instructure.dataseeding.util.days import com.instructure.dataseeding.util.fromNow import com.instructure.dataseeding.util.iso8601 -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData import com.instructure.student.ui.pages.AssignmentListPage import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.ViewUtils @@ -131,7 +131,7 @@ class AssignmentsE2ETest: StudentTest() { fun testLetterGradeTextAssignmentE2E() { Log.d(PREPARATION_TAG,"Seeding data.") - val data = seedData(students = 1, teachers = 1, courses = 1) + val data = seedData(teachers = 1, courses = 1, students = 1) val student = data.studentsList[0] val teacher = data.teachersList[0] val course = data.coursesList[0] @@ -163,7 +163,7 @@ class AssignmentsE2ETest: StudentTest() { fun testPercentageFileAssignmentWithCommentE2E() { Log.d(PREPARATION_TAG,"Seeding data.") - val data = seedData(students = 1, teachers = 1, courses = 1) + val data = seedData(teachers = 1, courses = 1, students = 1) val student = data.studentsList[0] val teacher = data.teachersList[0] val course = data.coursesList[0] @@ -186,7 +186,12 @@ class AssignmentsE2ETest: StudentTest() { assignmentListPage.clickAssignment(percentageFileAssignment) Log.d(PREPARATION_TAG, "Seed a text file.") - val uploadInfo = uploadTextFile(courseId = course.id, assignmentId = percentageFileAssignment.id, token = student.token, fileUploadType = FileUploadType.ASSIGNMENT_SUBMISSION) + val uploadInfo = uploadTextFile( + courseId = course.id, + assignmentId = percentageFileAssignment.id, + token = student.token, + fileUploadType = FileUploadType.ASSIGNMENT_SUBMISSION + ) Log.d(PREPARATION_TAG,"Submit ${percentageFileAssignment.name} assignment for ${student.name} student.") submitCourseAssignment(course, percentageFileAssignment, uploadInfo, student) @@ -245,7 +250,7 @@ class AssignmentsE2ETest: StudentTest() { fun testMultipleAssignmentsE2E() { Log.d(PREPARATION_TAG,"Seeding data.") - val data = seedData(students = 1, teachers = 1, courses = 1) + val data = seedData(teachers = 1, courses = 1, students = 1) val student = data.studentsList[0] val teacher = data.teachersList[0] val course = data.coursesList[0] @@ -288,7 +293,7 @@ class AssignmentsE2ETest: StudentTest() { @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.E2E) fun testFilterAndSortAssignmentsE2E() { Log.d(PREPARATION_TAG,"Seeding data.") - val data = seedData(students = 1, teachers = 1, courses = 1) + val data = seedData(teachers = 1, courses = 1, students = 1) val student = data.studentsList[0] val teacher = data.teachersList[0] val course = data.coursesList[0] @@ -420,7 +425,7 @@ class AssignmentsE2ETest: StudentTest() { fun testMediaCommentsE2E() { Log.d(PREPARATION_TAG,"Seeding data.") - val data = seedData(students = 1, teachers = 1, courses = 1) + val data = seedData(teachers = 1, courses = 1, students = 1) val student = data.studentsList[0] val teacher = data.teachersList[0] val course = data.coursesList[0] @@ -464,7 +469,7 @@ class AssignmentsE2ETest: StudentTest() { fun testAddFileCommentE2E() { Log.d(PREPARATION_TAG,"Seeding data.") - val data = seedData(students = 1, teachers = 1, courses = 1) + val data = seedData(teachers = 1, courses = 1, students = 1) val student = data.studentsList[0] val teacher = data.teachersList[0] val course = data.coursesList[0] @@ -481,8 +486,8 @@ class AssignmentsE2ETest: StudentTest() { Log.d(STEP_TAG,"Seed a comment attachment upload.") val commentUploadInfo = uploadTextFile( - assignmentId = assignment.id, courseId = course.id, + assignmentId = assignment.id, token = student.token, fileUploadType = FileUploadType.COMMENT_ATTACHMENT ) @@ -510,7 +515,7 @@ class AssignmentsE2ETest: StudentTest() { fun testSubmissionAttemptSelection() { Log.d(PREPARATION_TAG, "Seeding data.") - val data = seedData(students = 1, teachers = 1, courses = 1) + val data = seedData(teachers = 1, courses = 1, students = 1) val student = data.studentsList[0] val teacher = data.teachersList[0] val course = data.coursesList[0] @@ -572,7 +577,7 @@ class AssignmentsE2ETest: StudentTest() { fun testCommentsBelongToSubmissionAttempts() { Log.d(PREPARATION_TAG,"Seeding data.") - val data = seedData(students = 1, teachers = 1, courses = 1) + val data = seedData(teachers = 1, courses = 1, students = 1) val student = data.studentsList[0] val teacher = data.teachersList[0] val course = data.coursesList[0] @@ -665,7 +670,7 @@ class AssignmentsE2ETest: StudentTest() { @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.E2E) fun showOnlyLetterGradeOnDashboardAndAssignmentListPageE2E() { Log.d(PREPARATION_TAG,"Seeding data.") - val data = seedData(students = 1, teachers = 1, courses = 1) + val data = seedData(teachers = 1, courses = 1, students = 1) val student = data.studentsList[0] val teacher = data.teachersList[0] val course = data.coursesList[0] @@ -821,7 +826,7 @@ class AssignmentsE2ETest: StudentTest() { @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.E2E) fun showOnlyLetterGradeOnGradesPageE2E() { Log.d(PREPARATION_TAG, "Seeding data.") - val data = seedData(students = 1, teachers = 1, courses = 1) + val data = seedData(teachers = 1, courses = 1, students = 1) val student = data.studentsList[0] val teacher = data.teachersList[0] val course = data.coursesList[0] diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/BookmarksE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/BookmarksE2ETest.kt index 2e0ae5bba7..5a70977edd 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/BookmarksE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/BookmarksE2ETest.kt @@ -19,16 +19,20 @@ package com.instructure.student.ui.e2e import android.util.Log import androidx.test.espresso.Espresso import com.instructure.canvas.espresso.E2E +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.refresh 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 -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -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.ViewUtils import com.instructure.student.ui.utils.seedData diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/CollaborationsE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/CollaborationsE2ETest.kt index c1f0c7e521..69fd95887e 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/CollaborationsE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/CollaborationsE2ETest.kt @@ -2,11 +2,11 @@ package com.instructure.student.ui.e2e import android.util.Log import com.instructure.canvas.espresso.E2E +import com.instructure.canvas.espresso.FeatureCategory import com.instructure.canvas.espresso.KnownBug -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData +import com.instructure.canvas.espresso.Priority +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData import com.instructure.student.ui.pages.CollaborationsPage import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedData diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ConferencesE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ConferencesE2ETest.kt index 90a7e6e01a..dcc641e94d 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ConferencesE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ConferencesE2ETest.kt @@ -2,12 +2,12 @@ package com.instructure.student.ui.e2e import android.util.Log import com.instructure.canvas.espresso.E2E +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.refresh import com.instructure.dataseeding.api.ConferencesApi -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -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.seedData import com.instructure.student.ui.utils.tokenLogin diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/DashboardE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/DashboardE2ETest.kt index e0fe3bcffa..9770e01244 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/DashboardE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/DashboardE2ETest.kt @@ -19,12 +19,12 @@ package com.instructure.student.ui.e2e import android.util.Log import androidx.test.espresso.Espresso import com.instructure.canvas.espresso.E2E +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.dataseeding.api.ConversationsApi import com.instructure.dataseeding.api.GroupsApi -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -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.seedData import com.instructure.student.ui.utils.tokenLogin @@ -101,11 +101,11 @@ class DashboardE2ETest : StudentTest() { dashboardPage.assertDisplaysGroup(group2, course1) Log.d(STEP_TAG,"Click on 'All Courses' button. Assert that the All Courses Page is loaded.") - dashboardPage.clickEditDashboard() - editDashboardPage.assertPageObjects() + dashboardPage.openAllCoursesPage() + allCoursesPage.assertPageObjects() Log.d(STEP_TAG, "Favorite '${course1.name}' course and navigate back to Dashboard Page.") - editDashboardPage.favoriteCourse(course1.name) + allCoursesPage.favoriteCourse(course1.name) Espresso.pressBack() Log.d(STEP_TAG,"Assert that only the favoured course, '${course1.name}' is displayed." + @@ -124,16 +124,16 @@ class DashboardE2ETest : StudentTest() { Log.d(STEP_TAG,"Click on 'All Courses' button. Assert that the All Courses Page is loaded.") dashboardPage.assertPageObjects() - dashboardPage.clickEditDashboard() - editDashboardPage.assertPageObjects() + dashboardPage.openAllCoursesPage() + allCoursesPage.assertPageObjects() Log.d(STEP_TAG, "Assert that the mass select button's text is 'Unselect All', since one of the courses is selected.") - editDashboardPage.assertCourseMassSelectButtonIsDisplayed(true) + allCoursesPage.assertCourseMassSelectButtonIsDisplayed(true) Log.d(STEP_TAG, "Toggle off favourite star icon of '${course1.name}' course." + "Assert that the 'mass' select button's label is 'Select All'.") - editDashboardPage.unfavoriteCourse(course1.name) - editDashboardPage.assertCourseMassSelectButtonIsDisplayed(false) + allCoursesPage.unfavoriteCourse(course1.name) + allCoursesPage.assertCourseMassSelectButtonIsDisplayed(false) Log.d(STEP_TAG, "Navigate back to Dashboard Page.") Espresso.pressBack() @@ -186,15 +186,15 @@ class DashboardE2ETest : StudentTest() { dashboardPage.assertCourseGrade(course2.name, "N/A") Log.d(STEP_TAG,"Click on 'All Courses' button.") - dashboardPage.clickEditDashboard() - editDashboardPage.assertPageObjects() + dashboardPage.openAllCoursesPage() + allCoursesPage.assertPageObjects() Log.d(STEP_TAG, "Assert that the group 'mass' select button's label is 'Select All'.") - editDashboardPage.swipeUp() - editDashboardPage.assertGroupMassSelectButtonIsDisplayed(false) + allCoursesPage.swipeUp() + allCoursesPage.assertGroupMassSelectButtonIsDisplayed(false) Log.d(STEP_TAG, "Favorite '${group.name}' course and navigate back to Dashboard Page.") - editDashboardPage.favoriteGroup(group.name) + allCoursesPage.favoriteGroup(group.name) Espresso.pressBack() Log.d(STEP_TAG,"Assert that only the favoured group, '${group.name}' is displayed." + @@ -203,18 +203,18 @@ class DashboardE2ETest : StudentTest() { dashboardPage.assertGroupNotDisplayed(group2) Log.d(STEP_TAG,"Click on 'All Courses' button.") - dashboardPage.clickEditDashboard() - editDashboardPage.assertPageObjects() + dashboardPage.openAllCoursesPage() + allCoursesPage.assertPageObjects() Thread.sleep(2000) //It can be flaky without this 2 seconds - editDashboardPage.swipeUp() + allCoursesPage.swipeUp() Log.d(STEP_TAG, "Assert that the group 'mass' select button's label is 'Unselect All'.") - editDashboardPage.assertGroupMassSelectButtonIsDisplayed(true) + allCoursesPage.assertGroupMassSelectButtonIsDisplayed(true) Log.d(STEP_TAG, "Toggle off favourite star icon of '${group.name}' group." + "Assert that the 'mass' select button's label is 'Select All'.") - editDashboardPage.unfavoriteGroup(group.name) - editDashboardPage.assertGroupMassSelectButtonIsDisplayed(false) + allCoursesPage.unfavoriteGroup(group.name) + allCoursesPage.assertGroupMassSelectButtonIsDisplayed(false) Log.d(STEP_TAG, "Navigate back to Dashboard Page.") Espresso.pressBack() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/DiscussionsE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/DiscussionsE2ETest.kt index 85506cb028..bf6bbeca3b 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/DiscussionsE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/DiscussionsE2ETest.kt @@ -20,15 +20,15 @@ import android.os.SystemClock.sleep import android.util.Log import androidx.test.espresso.Espresso import com.instructure.canvas.espresso.E2E +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.dataseeding.api.DiscussionTopicsApi import com.instructure.dataseeding.model.CanvasUserApiModel import com.instructure.dataseeding.model.CourseApiModel import com.instructure.espresso.ViewUtils import com.instructure.espresso.getCurrentDateInCanvasFormat -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -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.seedData import com.instructure.student.ui.utils.tokenLogin diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/FilesE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/FilesE2ETest.kt index 794c7dc02b..b3cdea4a7b 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/FilesE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/FilesE2ETest.kt @@ -20,6 +20,10 @@ import android.os.Environment import android.util.Log import androidx.test.espresso.Espresso import com.instructure.canvas.espresso.E2E +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.canvasapi2.managers.DiscussionManager import com.instructure.canvasapi2.models.CanvasContext import com.instructure.canvasapi2.models.DiscussionEntry @@ -36,10 +40,6 @@ import com.instructure.dataseeding.model.CourseApiModel import com.instructure.dataseeding.model.FileUploadType import com.instructure.dataseeding.model.SubmissionType import com.instructure.dataseeding.util.Randomizer -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -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.ViewUtils import com.instructure.student.ui.utils.seedData 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 2858633f86..c176601e99 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 @@ -4,6 +4,10 @@ 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.FeatureCategory +import com.instructure.canvas.espresso.Priority +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData import com.instructure.canvas.espresso.containsTextCaseInsensitive import com.instructure.dataseeding.api.AssignmentsApi import com.instructure.dataseeding.api.QuizzesApi @@ -18,10 +22,6 @@ import com.instructure.dataseeding.model.SubmissionType import com.instructure.dataseeding.util.days import com.instructure.dataseeding.util.fromNow import com.instructure.dataseeding.util.iso8601 -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData import com.instructure.student.R import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedData diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/InboxE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/InboxE2ETest.kt index b2d370238f..2008a63052 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/InboxE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/InboxE2ETest.kt @@ -21,14 +21,16 @@ import android.util.Log import androidx.test.espresso.Espresso import androidx.test.espresso.matcher.ViewMatchers import com.instructure.canvas.espresso.E2E +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.refresh import com.instructure.dataseeding.api.ConversationsApi import com.instructure.dataseeding.api.GroupsApi import com.instructure.dataseeding.model.CanvasUserApiModel -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData +import com.instructure.espresso.retry +import com.instructure.espresso.retryWithIncreasingDelay import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedData import com.instructure.student.ui.utils.tokenLogin @@ -113,6 +115,7 @@ class InboxE2ETest: StudentTest() { inboxPage.selectConversation(seededConversation) inboxPage.assertSelectedConversationNumber("1") inboxPage.clickUnArchive() + inboxPage.assertInboxEmpty() inboxPage.assertConversationNotDisplayed(seededConversation.subject) @@ -127,7 +130,10 @@ class InboxE2ETest: StudentTest() { inboxPage.selectConversations(listOf(seededConversation.subject)) inboxPage.assertSelectedConversationNumber("1") inboxPage.clickUnstar() - inboxPage.assertConversationNotStarred(seededConversation.subject) + + retryWithIncreasingDelay(times = 10, maxDelay = 3000, catchBlock = { refresh() }) { + inboxPage.assertConversationNotStarred(seededConversation.subject) + } Log.d(STEP_TAG, "Select the conversations (${seededConversation.subject} and archive it. Assert that it has not displayed in the 'INBOX' scope.") inboxPage.selectConversations(listOf(seededConversation.subject)) @@ -161,7 +167,10 @@ class InboxE2ETest: StudentTest() { Log.d(STEP_TAG, "Select the conversation. Unarchive it, and assert that it has not displayed in the 'ARCHIVED' scope.") inboxPage.selectConversations(listOf(seededConversation.subject)) inboxPage.clickUnArchive() - inboxPage.assertConversationNotDisplayed(seededConversation.subject) + + retryWithIncreasingDelay(times = 10, maxDelay = 3000, catchBlock = { refresh() }) { + inboxPage.assertConversationNotDisplayed(seededConversation.subject) + } Log.d(STEP_TAG, "Navigate to 'STARRED' scope and assert that the conversations is displayed there.") inboxPage.filterInbox("Starred") @@ -346,7 +355,10 @@ class InboxE2ETest: StudentTest() { Log.d(STEP_TAG, "Navigate to 'STARRED' scope. Assert that the conversation is displayed in the 'STARRED' scope.") inboxPage.filterInbox("Starred") - inboxPage.assertConversationDisplayed(seededConversation.subject) + + retry(times = 10, delay = 3000) { + inboxPage.assertConversationDisplayed(seededConversation.subject) + } Log.d(STEP_TAG, "Swipe '${seededConversation.subject}' left and assert it is removed from the 'STARRED' scope because it has became unstarred.") inboxPage.swipeConversationLeft(seededConversation) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/LoginE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/LoginE2ETest.kt index 03c3a51ddc..c9d3d7b60a 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/LoginE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/LoginE2ETest.kt @@ -18,6 +18,10 @@ package com.instructure.student.ui.e2e import android.util.Log import com.instructure.canvas.espresso.E2E +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.dataseeding.api.CoursesApi import com.instructure.dataseeding.api.EnrollmentsApi import com.instructure.dataseeding.api.SeedApi @@ -27,10 +31,6 @@ import com.instructure.dataseeding.model.CourseApiModel import com.instructure.dataseeding.model.EnrollmentTypes.STUDENT_ENROLLMENT import com.instructure.dataseeding.model.EnrollmentTypes.TEACHER_ENROLLMENT import com.instructure.dataseeding.util.CanvasNetworkAdapter -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -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.ViewUtils import com.instructure.student.ui.utils.seedData @@ -300,6 +300,8 @@ class LoginE2ETest : StudentTest() { private fun loginWithUser(user: CanvasUserApiModel, lastSchoolSaved: Boolean = false) { + Thread.sleep(5100) //Need to wait > 5 seconds before each login attempt because of new 'too many attempts' login policy on web. + if(lastSchoolSaved) { Log.d(STEP_TAG,"Click 'Find Another School' button.") loginLandingPage.clickFindAnotherSchoolButton() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ModulesE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ModulesE2ETest.kt index 48f911a9a0..862b6a0c87 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ModulesE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/ModulesE2ETest.kt @@ -19,6 +19,10 @@ package com.instructure.student.ui.e2e import android.util.Log import androidx.test.espresso.Espresso import com.instructure.canvas.espresso.E2E +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.dataseeding.api.AssignmentsApi import com.instructure.dataseeding.api.DiscussionTopicsApi import com.instructure.dataseeding.api.ModulesApi @@ -32,10 +36,6 @@ import com.instructure.dataseeding.model.SubmissionType import com.instructure.dataseeding.util.days import com.instructure.dataseeding.util.fromNow import com.instructure.dataseeding.util.iso8601 -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -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.seedData import com.instructure.student.ui.utils.tokenLogin 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 80020b123d..83723950af 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 @@ -19,7 +19,11 @@ 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.FeatureCategory +import com.instructure.canvas.espresso.Priority import com.instructure.canvas.espresso.ReleaseExclude +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData import com.instructure.canvas.espresso.refresh import com.instructure.dataseeding.api.AssignmentsApi import com.instructure.dataseeding.api.QuizzesApi @@ -34,10 +38,6 @@ import com.instructure.dataseeding.model.SubmissionType import com.instructure.dataseeding.util.days import com.instructure.dataseeding.util.fromNow import com.instructure.dataseeding.util.iso8601 -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -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.seedData import com.instructure.student.ui.utils.tokenLogin diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/PagesE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/PagesE2ETest.kt index 2f48aaacd3..584fa2f705 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/PagesE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/PagesE2ETest.kt @@ -20,14 +20,14 @@ import android.util.Log import androidx.test.espresso.Espresso import androidx.test.espresso.web.webdriver.Locator import com.instructure.canvas.espresso.E2E +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.dataseeding.api.PagesApi import com.instructure.dataseeding.model.CanvasUserApiModel import com.instructure.dataseeding.model.CourseApiModel import com.instructure.dataseeding.util.Randomizer -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData import com.instructure.student.ui.pages.WebViewTextCheck import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedData @@ -43,7 +43,7 @@ class PagesE2ETest: StudentTest() { @E2E @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.PAGES, TestCategory.E2E, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.PAGES, TestCategory.E2E) fun testPagesE2E() { Log.d(PREPARATION_TAG,"Seeding data.") diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/PeopleE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/PeopleE2ETest.kt index b63a31b4ab..89709d0222 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/PeopleE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/PeopleE2ETest.kt @@ -18,10 +18,10 @@ package com.instructure.student.ui.e2e import android.util.Log import com.instructure.canvas.espresso.E2E -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData +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.student.ui.utils.StudentTest import com.instructure.student.ui.utils.ViewUtils import com.instructure.student.ui.utils.seedData diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/QuizzesE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/QuizzesE2ETest.kt index 13261cd33e..f6094a40ba 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/QuizzesE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/QuizzesE2ETest.kt @@ -28,7 +28,11 @@ import androidx.test.espresso.web.webdriver.DriverAtoms.getText import androidx.test.espresso.web.webdriver.DriverAtoms.webScrollIntoView import androidx.test.espresso.web.webdriver.Locator import com.instructure.canvas.espresso.E2E +import com.instructure.canvas.espresso.FeatureCategory +import com.instructure.canvas.espresso.Priority import com.instructure.canvas.espresso.Stub +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData import com.instructure.canvas.espresso.containsTextCaseInsensitive import com.instructure.canvas.espresso.isElementDisplayed import com.instructure.dataseeding.api.QuizzesApi @@ -37,10 +41,6 @@ import com.instructure.dataseeding.model.CanvasUserApiModel import com.instructure.dataseeding.model.CourseApiModel import com.instructure.dataseeding.model.QuizAnswer import com.instructure.dataseeding.model.QuizQuestion -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData import com.instructure.student.R import com.instructure.student.ui.pages.WebViewTextCheck import com.instructure.student.ui.utils.StudentTest @@ -65,7 +65,7 @@ class QuizzesE2ETest: StudentTest() { @E2E @Stub @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.PAGES, TestCategory.E2E, true) + @TestMetaData(Priority.MANDATORY, FeatureCategory.PAGES, TestCategory.E2E) fun testQuizzesE2E() { Log.d(PREPARATION_TAG, "Seeding data.") diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/SettingsE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/SettingsE2ETest.kt index cb42b97277..babe1f110d 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/SettingsE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/SettingsE2ETest.kt @@ -22,13 +22,13 @@ import androidx.test.espresso.Espresso import androidx.test.espresso.intent.Intents import androidx.test.espresso.intent.Intents.intended import com.instructure.canvas.espresso.E2E +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.canvasapi2.utils.RemoteConfigParam import com.instructure.canvasapi2.utils.RemoteConfigUtils import com.instructure.espresso.ViewUtils -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData import com.instructure.student.R import com.instructure.student.ui.utils.IntentActionMatcher import com.instructure.student.ui.utils.StudentTest diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/SyllabusE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/SyllabusE2ETest.kt index e4cdad1e9c..8db435bf9b 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/SyllabusE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/SyllabusE2ETest.kt @@ -18,6 +18,10 @@ package com.instructure.student.ui.e2e import android.util.Log import com.instructure.canvas.espresso.E2E +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.dataseeding.api.AssignmentsApi import com.instructure.dataseeding.api.QuizzesApi import com.instructure.dataseeding.model.CanvasUserApiModel @@ -26,10 +30,6 @@ import com.instructure.dataseeding.model.SubmissionType import com.instructure.dataseeding.util.days import com.instructure.dataseeding.util.fromNow import com.instructure.dataseeding.util.iso8601 -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -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.seedData import com.instructure.student.ui.utils.tokenLogin 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 2c98efb118..32b3540313 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 @@ -15,10 +15,11 @@ import com.instructure.dataseeding.model.SubmissionType import com.instructure.dataseeding.util.days import com.instructure.dataseeding.util.fromNow import com.instructure.dataseeding.util.iso8601 -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData +import com.instructure.espresso.retry +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.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedAssignments import com.instructure.student.ui.utils.seedData @@ -71,12 +72,13 @@ class TodoE2ETest: StudentTest() { dashboardPage.clickTodoTab() Log.d(STEP_TAG,"Assert that ${testAssignment.name} assignment is displayed and ${borderDateAssignment.name} is displayed because it's 7 days away from now..") - todoPage.assertAssignmentDisplayed(testAssignment) - todoPage.assertAssignmentDisplayed(borderDateAssignment) - Log.d(STEP_TAG,"Assert that ${quiz.title} quiz is displayed and ${tooFarAwayQuiz.title} quiz is not displayed because it's end date is more than a week away..") - todoPage.assertQuizDisplayed(quiz) - todoPage.assertQuizNotDisplayed(tooFarAwayQuiz) + retry(times = 5, delay = 3000, catchBlock = { refresh() } ) { + todoPage.assertAssignmentDisplayed(testAssignment) + todoPage.assertAssignmentDisplayed(borderDateAssignment) + todoPage.assertQuizDisplayed(quiz) + todoPage.assertQuizNotDisplayed(tooFarAwayQuiz) + } Log.d(PREPARATION_TAG,"Submit ${testAssignment.name} assignment for ${student.name} student.") SubmissionsApi.seedAssignmentSubmission(SubmissionsApi.SubmissionSeedRequest( @@ -118,8 +120,8 @@ class TodoE2ETest: StudentTest() { Log.d(STEP_TAG, "Navigate back to the Dashboard Page. Open ${favoriteCourse.name} course. Mark it as favorite.") Espresso.pressBack() - dashboardPage.clickEditDashboard() - editDashboardPage.favoriteCourse(favoriteCourse.name) + dashboardPage.openAllCoursesPage() + allCoursesPage.favoriteCourse(favoriteCourse.name) Log.d(STEP_TAG, "Navigate back to the Dashboard Page and open the To Do Page again.") Espresso.pressBack() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/GradesElementaryE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/GradesElementaryE2ETest.kt index 4b675a8b75..088b6e0858 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/GradesElementaryE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/GradesElementaryE2ETest.kt @@ -19,6 +19,10 @@ package com.instructure.student.ui.e2e.k5 import android.util.Log import androidx.test.espresso.Espresso import com.instructure.canvas.espresso.E2E +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.canvasapi2.utils.toApiString import com.instructure.dataseeding.api.AssignmentsApi import com.instructure.dataseeding.api.GradingPeriodsApi @@ -28,10 +32,6 @@ import com.instructure.dataseeding.model.CanvasUserApiModel import com.instructure.dataseeding.model.GradingType import com.instructure.dataseeding.model.SubmissionType import com.instructure.espresso.page.getStringFromResource -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData import com.instructure.student.R import com.instructure.student.ui.pages.ElementaryDashboardPage import com.instructure.student.ui.utils.StudentTest diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/HomeroomE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/HomeroomE2ETest.kt index 6088a3f2f7..6c6703011d 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/HomeroomE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/HomeroomE2ETest.kt @@ -19,6 +19,10 @@ package com.instructure.student.ui.e2e.k5 import android.util.Log import androidx.test.espresso.Espresso import com.instructure.canvas.espresso.E2E +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.dataseeding.api.AssignmentsApi import com.instructure.dataseeding.model.AssignmentApiModel import com.instructure.dataseeding.model.CanvasUserApiModel @@ -28,10 +32,6 @@ import com.instructure.dataseeding.util.ago import com.instructure.dataseeding.util.days import com.instructure.dataseeding.util.iso8601 import com.instructure.espresso.page.getStringFromResource -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData import com.instructure.student.R import com.instructure.student.ui.pages.ElementaryDashboardPage import com.instructure.student.ui.utils.StudentTest diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/ImportantDatesE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/ImportantDatesE2ETest.kt index 54c3387d3e..d569f6c99b 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/ImportantDatesE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/ImportantDatesE2ETest.kt @@ -19,6 +19,10 @@ package com.instructure.student.ui.e2e.k5 import android.util.Log import androidx.test.espresso.Espresso import com.instructure.canvas.espresso.E2E +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.canvasapi2.utils.toDate import com.instructure.dataseeding.api.AssignmentsApi import com.instructure.dataseeding.model.AssignmentApiModel @@ -28,10 +32,6 @@ import com.instructure.dataseeding.model.SubmissionType import com.instructure.dataseeding.util.days import com.instructure.dataseeding.util.fromNow import com.instructure.dataseeding.util.iso8601 -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData import com.instructure.student.ui.pages.ElementaryDashboardPage import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedDataForK5 diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/ResourcesE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/ResourcesE2ETest.kt index a27cf7bd4b..dfdf5c229d 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/ResourcesE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/ResourcesE2ETest.kt @@ -19,11 +19,11 @@ package com.instructure.student.ui.e2e.k5 import android.util.Log import androidx.test.espresso.Espresso import com.instructure.canvas.espresso.E2E +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.dataseeding.model.CanvasUserApiModel -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData import com.instructure.student.ui.pages.ElementaryDashboardPage import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedDataForK5 diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/ScheduleE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/ScheduleE2ETest.kt index 2548685af9..a178f13783 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/ScheduleE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/k5/ScheduleE2ETest.kt @@ -19,6 +19,10 @@ package com.instructure.student.ui.e2e.k5 import android.util.Log import androidx.test.espresso.Espresso import com.instructure.canvas.espresso.E2E +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.canvasapi2.utils.toApiString import com.instructure.dataseeding.api.AssignmentsApi import com.instructure.dataseeding.model.AssignmentApiModel @@ -27,10 +31,6 @@ import com.instructure.dataseeding.model.GradingType import com.instructure.dataseeding.model.SubmissionType import com.instructure.espresso.page.getStringFromResource import com.instructure.espresso.page.withAncestor -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData import com.instructure.student.R import com.instructure.student.ui.pages.ElementaryDashboardPage import com.instructure.student.ui.utils.StudentTest diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/ManageOfflineContentE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/ManageOfflineContentE2ETest.kt index 2d0af62c9e..9aa20f8b6a 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/ManageOfflineContentE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/ManageOfflineContentE2ETest.kt @@ -19,12 +19,11 @@ package com.instructure.student.ui.e2e.offline import android.util.Log import androidx.test.espresso.Espresso import com.google.android.material.checkbox.MaterialCheckBox +import com.instructure.canvas.espresso.FeatureCategory import com.instructure.canvas.espresso.OfflineE2E -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData -import com.instructure.student.ui.e2e.offline.utils.OfflineTestUtils +import com.instructure.canvas.espresso.Priority +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedData import com.instructure.student.ui.utils.tokenLogin @@ -48,7 +47,6 @@ class ManageOfflineContentE2ETest : StudentTest() { val student = data.studentsList[0] val course1 = data.coursesList[0] val course2 = data.coursesList[1] - val testAnnouncement = data.announcementsList[0] Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) @@ -75,6 +73,12 @@ class ManageOfflineContentE2ETest : StudentTest() { Log.d(STEP_TAG, "Assert that the tool bar texts are displayed properly, so the subtitle is '${course1.name}', because we are on the Manage Offline Content page of '${course1.name}' course.") manageOfflineContentPage.assertToolbarTexts(course1.name) + Log.d(STEP_TAG, "Assert that the '${course1.name}' course's checkbox state became 'Checked'.") + manageOfflineContentPage.assertCheckedStateOfItem(course1.name, MaterialCheckBox.STATE_CHECKED) + + Log.d(STEP_TAG, "Expand '${course1.name}' course.") + manageOfflineContentPage.expandCollapseItem(course1.name) + Log.d(STEP_TAG, "Deselect the 'Announcements' and 'Discussions' of the '${course1.name}' course.") manageOfflineContentPage.changeItemSelectionState("Announcements") manageOfflineContentPage.changeItemSelectionState("Discussions") @@ -185,8 +189,8 @@ class ManageOfflineContentE2ETest : StudentTest() { Log.d(STEP_TAG, "Assert that both of the seeded courses are displayed as a selectable item in the Manage Offline Content page.") manageOfflineContentPage.assertCourseCountWithMatcher(2) - Log.d(STEP_TAG, "Click on the 'Sync' button.") - manageOfflineContentPage.clickOnSyncButton() + Log.d(STEP_TAG, "Click on the 'Sync' button and confirm sync.") + manageOfflineContentPage.clickOnSyncButtonAndConfirm() Log.d(STEP_TAG, "Wait for the 'Download Started' dashboard notification to be displayed, and the to disappear.") dashboardPage.waitForRender() @@ -198,7 +202,12 @@ class ManageOfflineContentE2ETest : StudentTest() { dashboardPage.waitForSyncProgressStartingNotificationToDisappear() Log.d(PREPARATION_TAG, "Turn off the Wi-Fi and Mobile Data on the device, so it will go offline.") - OfflineTestUtils.turnOffConnectionViaADB() + turnOffConnectionViaADB() + Thread.sleep(10000) //Need to wait a bit here because of a UI glitch that when network state change, the dashboard page 'pops' a bit and it can confuse the automation script. + device.waitForIdle() + device.waitForWindowUpdate(null, 10000) + + Log.d(STEP_TAG, "Wait for the Dashboard Page to be rendered. Refresh the page.") dashboardPage.waitForRender() Log.d(STEP_TAG, "Select '${course2.name}' course and open 'Grades' menu to check if it's really synced and can be seen in offline mode.") @@ -212,7 +221,7 @@ class ManageOfflineContentE2ETest : StudentTest() { @After fun tearDown() { Log.d(PREPARATION_TAG, "Turn back on the Wi-Fi and Mobile Data on the device via ADB, so it will come back online.") - OfflineTestUtils.turnOnConnectionViaADB() + turnOnConnectionViaADB() } } \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineAllCoursesE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineAllCoursesE2ETest.kt new file mode 100644 index 0000000000..b45e9395bb --- /dev/null +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineAllCoursesE2ETest.kt @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2023 - present Instructure, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.instructure.student.ui.e2e.offline + +import android.util.Log +import androidx.test.espresso.Espresso +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.google.android.material.checkbox.MaterialCheckBox +import com.instructure.canvas.espresso.FeatureCategory +import com.instructure.canvas.espresso.OfflineE2E +import com.instructure.canvas.espresso.Priority +import com.instructure.canvas.espresso.SecondaryFeatureCategory +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData +import com.instructure.student.ui.utils.StudentTest +import com.instructure.student.ui.utils.seedData +import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest +import org.junit.After +import org.junit.Test +import java.lang.Thread.sleep + +@HiltAndroidTest +class OfflineAllCoursesE2ETest : StudentTest() { + override fun displaysPageObjects() = Unit + + override fun enableAndConfigureAccessibilityChecks() = Unit + + @OfflineE2E + @Test + @TestMetaData(Priority.MANDATORY, FeatureCategory.DASHBOARD, TestCategory.E2E, SecondaryFeatureCategory.ALL_COURSES) + fun testOfflineAllCoursesE2E() { + + Log.d(PREPARATION_TAG, "Seeding data.") + val data = seedData(students = 1, teachers = 1, courses = 3, announcements = 1) + val student = data.studentsList[0] + val course1 = data.coursesList[0] + val course2 = data.coursesList[1] + val course3 = data.coursesList[2] + + Log.d(PREPARATION_TAG, "Get the device to be able to perform app-independent actions on it.") + val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + + Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId}.") + tokenLogin(student) + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Open the 'All Courses' page and wait for it to be rendered.") + dashboardPage.openAllCoursesPage() + allCoursesPage.assertPageObjects() + + Log.d(STEP_TAG, "Favourite '${course1.name}' course and assert if it became favourited. Then navigate back to Dashboard page.") + allCoursesPage.favoriteCourse(course1.name) + allCoursesPage.assertCourseFavorited(course1) + Espresso.pressBack() + + Log.d(STEP_TAG, "Open global 'Manage Offline Content' page via the more menu of the Dashboard Page.") + dashboardPage.openGlobalManageOfflineContentPage() + manageOfflineContentPage.assertPageObjects() + + Log.d(STEP_TAG, "Assert that the '${course1.name}' course's checkbox state is 'Unchecked'.") + manageOfflineContentPage.assertCheckedStateOfItem(course1.name, MaterialCheckBox.STATE_UNCHECKED) + manageOfflineContentPage.assertCheckedStateOfItem(course2.name, MaterialCheckBox.STATE_UNCHECKED) + + Log.d(STEP_TAG, "Select '${course1.name}' and '${course2.name}' courses' checkboxes and Sync them.") + manageOfflineContentPage.changeItemSelectionState(course1.name) + manageOfflineContentPage.changeItemSelectionState(course2.name) + manageOfflineContentPage.clickOnSyncButtonAndConfirm() + + Log.d(STEP_TAG, "Wait for the 'Download Started' dashboard notification to be displayed, and the to disappear.") + dashboardPage.waitForRender() + dashboardPage.waitForSyncProgressDownloadStartedNotification() + dashboardPage.waitForSyncProgressDownloadStartedNotificationToDisappear() + + Log.d(STEP_TAG, "Wait for the 'Syncing Offline Content' dashboard notification to be displayed, and the to disappear. (It should be displayed after the 'Download Started' notification immediately.)") + dashboardPage.waitForSyncProgressStartingNotification() + dashboardPage.waitForSyncProgressStartingNotificationToDisappear() + + Log.d(PREPARATION_TAG, "Turn off the Wi-Fi and Mobile Data on the device, so it will go offline.") + turnOffConnectionViaADB() + sleep(10000) //Need to wait a bit here because of a UI glitch that when network state change, the dashboard page 'pops' a bit and it can confuse the automation script. + device.waitForIdle() + device.waitForWindowUpdate(null, 10000) + + Log.d(STEP_TAG, "Wait for the Dashboard Page to be rendered, and assert that '${course1.name}' is the only course which is displayed on the offline mode Dashboard Page.") + dashboardPage.waitForRender() + dashboardPage.assertDisplaysCourse(course1) + dashboardPage.assertCourseNotDisplayed(course2) + dashboardPage.assertCourseNotDisplayed(course3) + + Log.d(STEP_TAG, "Open the 'All Courses' page and wait for it to be rendered.") + dashboardPage.openAllCoursesPage() + allCoursesPage.assertPageObjects() + + Log.d(STEP_TAG, "Assert that the plus 'Note' box is displayed in which warns the user that favouring courses can only be done in online mode.") + allCoursesPage.assertOfflineNoteDisplayed() + + Log.d(STEP_TAG, "Dismiss the offline 'Note' box and assert if it's disappear.") + allCoursesPage.dismissOfflineNoteBox() + allCoursesPage.assertOfflineNoteNotDisplayed() + + Log.d(STEP_TAG, "Assert that the select/unselect all button is not clickable because offline mode does not supports it.") + allCoursesPage.assertSelectUnselectAllButtonNotClickable() + + Log.d(STEP_TAG, "Try to unfavorite '${course1.name}' course and assert it does not happened because favoring does not allowed in offline state.") + allCoursesPage.unfavoriteCourse(course1.name) + allCoursesPage.assertCourseFavorited(course1) + + Log.d(STEP_TAG, "Assert that '${course3.name}' course's details are faded (and they having 0.4 alpha value) and it's offline sync icon is not displayed since it's not synced.") + allCoursesPage.assertCourseDetailsAlpha(course3.name, 0.4f) + allCoursesPage.assertCourseOfflineSyncButton(course3.name, ViewMatchers.Visibility.GONE) + + Log.d(STEP_TAG, "Assert that '${course1.name}' course's favourite star is faded (and it's having 0.4 alpha value) because favoring is not possible in offline mode," + + "the course title and open button are not faded (1.0 alpha value) and the offline sync icon is displayed since the course is synced.") + allCoursesPage.assertCourseFavouriteStarAlpha(course1.name, 0.4f) + allCoursesPage.assertCourseTitleAlpha(course1.name, 1.0f) + allCoursesPage.assertCourseOpenButtonAlpha(course1.name, 1.0f) + allCoursesPage.assertCourseOfflineSyncButton(course1.name, ViewMatchers.Visibility.VISIBLE) + + Log.d(STEP_TAG, "Click on '${course1.name}' course and assert if it will navigate the user to the CourseBrowser Page.") + allCoursesPage.openCourse(course1.name) + courseBrowserPage.assertTitleCorrect(course1) + + } + + @After + fun tearDown() { + Log.d(PREPARATION_TAG, "Turn back on the Wi-Fi and Mobile Data on the device, so it will come back online.") + turnOnConnectionViaADB() + } + +} \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineCourseBrowserE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineCourseBrowserE2ETest.kt new file mode 100644 index 0000000000..5ea2aeb906 --- /dev/null +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineCourseBrowserE2ETest.kt @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2023 - present Instructure, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.instructure.student.ui.e2e.offline + +import android.util.Log +import androidx.test.espresso.Espresso +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.instructure.canvas.espresso.FeatureCategory +import com.instructure.canvas.espresso.OfflineE2E +import com.instructure.canvas.espresso.Priority +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData +import com.instructure.student.ui.pages.CourseBrowserPage +import com.instructure.student.ui.utils.StudentTest +import com.instructure.student.ui.utils.seedData +import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest +import org.junit.After +import org.junit.Test +import java.lang.Thread.sleep + +@HiltAndroidTest +class OfflineCourseBrowserE2ETest : StudentTest() { + override fun displaysPageObjects() = Unit + + override fun enableAndConfigureAccessibilityChecks() = Unit + + @OfflineE2E + @Test + @TestMetaData(Priority.MANDATORY, FeatureCategory.COURSE, TestCategory.E2E) + fun testOfflineCourseBrowserPageUnavailableE2E() { + + Log.d(PREPARATION_TAG,"Seeding data.") + val data = seedData(students = 1, teachers = 1, courses = 1, announcements = 1) + val student = data.studentsList[0] + val course1 = data.coursesList[0] + val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + + Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId}.") + tokenLogin(student) + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Open global 'Manage Offline Content' page via the more menu of the Dashboard Page.") + dashboardPage.openGlobalManageOfflineContentPage() + + Log.d(STEP_TAG, "Select the entire '${course1.name}' course for sync. Click on the 'Sync' button.") + Log.d(STEP_TAG, "Expand '${course1.name}' course. Select only the 'Announcements' of the '${course1.name}' course. Click on the 'Sync' button and confirm the sync process.") + manageOfflineContentPage.expandCollapseItem(course1.name) + manageOfflineContentPage.changeItemSelectionState("Announcements") + manageOfflineContentPage.clickOnSyncButtonAndConfirm() + + Log.d(STEP_TAG, "Wait for the 'Download Started' dashboard notification to be displayed, and the to disappear.") + dashboardPage.waitForRender() + dashboardPage.waitForSyncProgressDownloadStartedNotification() + dashboardPage.waitForSyncProgressDownloadStartedNotificationToDisappear() + + Log.d(STEP_TAG, "Wait for the 'Syncing Offline Content' dashboard notification to be displayed, and the to disappear. (It should be displayed after the 'Download Started' notification immediately.)") + dashboardPage.waitForSyncProgressStartingNotification() + dashboardPage.waitForSyncProgressStartingNotificationToDisappear() + + Log.d(PREPARATION_TAG, "Turn off the Wi-Fi and Mobile Data on the device, so it will go offline.") + turnOffConnectionViaADB() + sleep(10000) //Need to wait a bit here because of a UI glitch that when network state change, the dashboard page 'pops' a bit and it can confuse the automation script. + device.waitForIdle() + device.waitForWindowUpdate(null, 10000) + + Log.d(STEP_TAG, "Wait for the Dashboard Page to be rendered. Refresh the page.") + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Select '${course1.name}' course and open 'Announcements' menu.") + sleep(5000) //Need to wait a bit here because of a UI glitch that when network state change, the dashboard page 'pops' a bit and it can confuse the automation script. + dashboardPage.selectCourse(course1) + + Log.d(STEP_TAG, "Assert that only the 'Announcements' tab is enabled because it is the only one which has been synced, and assert that all the other, previously synced tabs are disabled, because they weren't synced now.") + var enabledTabs = arrayOf("Announcements") + var disabledTabs = arrayOf("Discussions", "Grades", "People", "Syllabus", "BigBlueButton") + assertTabsEnabled(courseBrowserPage, enabledTabs) + assertTabsDisabled(courseBrowserPage, disabledTabs) + + Log.d(STEP_TAG, "Navigate back to Dashboard Page.Turn back on the Wi-Fi and Mobile Data on the device, and wait for it to come online.") + Espresso.pressBack() + turnOnConnectionViaADB() + dashboardPage.waitForNetworkComeBack() + + Log.d(STEP_TAG, "Open global 'Manage Offline Content' page via the more menu of the Dashboard Page.") + dashboardPage.openGlobalManageOfflineContentPage() + + Log.d(STEP_TAG, "Deselect the entire '${course1.name}' course for sync.") + manageOfflineContentPage.changeItemSelectionState(course1.name) + manageOfflineContentPage.clickOnSyncButtonAndConfirm() + + Log.d(STEP_TAG, "Wait for the 'Download Started' dashboard notification to be displayed, and the to disappear.") + dashboardPage.waitForRender() + dashboardPage.waitForSyncProgressDownloadStartedNotification() + dashboardPage.waitForSyncProgressDownloadStartedNotificationToDisappear() + + Log.d(STEP_TAG, "Wait for the 'Syncing Offline Content' dashboard notification to be displayed, and the to disappear. (It should be displayed after the 'Download Started' notification immediately.)") + dashboardPage.waitForSyncProgressStartingNotification() + dashboardPage.waitForSyncProgressStartingNotificationToDisappear() + + Log.d(PREPARATION_TAG, "Turn off the Wi-Fi and Mobile Data on the device, so it will go offline.") + turnOffConnectionViaADB() + + Log.d(STEP_TAG, "Select '${course1.name}' course and open 'Announcements' menu.") + sleep(10000) //Need to wait a bit here because of a UI glitch that when network state change, the dashboard page 'pops' a bit and it can confuse the automation script. + device.waitForIdle() + device.waitForWindowUpdate(null, 10000) + dashboardPage.selectCourse(course1) + + Log.d(STEP_TAG, "Assert that the 'Google Drive' and 'Collaborations' tabs are disabled because they aren't supported in offline mode, but the rest of the tabs are enabled because the whole course has been synced.") + enabledTabs = arrayOf("Announcements", "Discussions", "Grades", "People", "Syllabus", "BigBlueButton") + disabledTabs = arrayOf("Google Drive", "Collaborations") + assertTabsEnabled(courseBrowserPage, enabledTabs) + assertTabsDisabled(courseBrowserPage, disabledTabs) + } + + @After + fun tearDown() { + Log.d(PREPARATION_TAG, "Turn back on the Wi-Fi and Mobile Data on the device, so it will come back online.") + turnOnConnectionViaADB() + } + + private fun assertTabsEnabled(courseBrowserPage: CourseBrowserPage, tabs: Array) { + tabs.forEach { tab -> + courseBrowserPage.assertTabEnabled(tab) + } + } + + private fun assertTabsDisabled(courseBrowserPage: CourseBrowserPage, tabs: Array) { + tabs.forEach { tab -> + courseBrowserPage.assertTabDisabled(tab) + } + } +} \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineDashboardE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineDashboardE2ETest.kt index b874bb2430..4be5df3180 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineDashboardE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineDashboardE2ETest.kt @@ -17,11 +17,15 @@ package com.instructure.student.ui.e2e.offline import android.util.Log +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.google.android.material.checkbox.MaterialCheckBox +import com.instructure.canvas.espresso.FeatureCategory import com.instructure.canvas.espresso.OfflineE2E -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData +import com.instructure.canvas.espresso.Priority +import com.instructure.canvas.espresso.SecondaryFeatureCategory +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData import com.instructure.student.ui.e2e.offline.utils.OfflineTestUtils import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedData @@ -40,6 +44,7 @@ class OfflineDashboardE2ETest : StudentTest() { @Test @TestMetaData(Priority.MANDATORY, FeatureCategory.DASHBOARD, TestCategory.E2E) fun testOfflineDashboardE2E() { + Log.d(PREPARATION_TAG,"Seeding data.") val data = seedData(students = 1, teachers = 1, courses = 2, announcements = 1) val student = data.studentsList[0] @@ -47,6 +52,9 @@ class OfflineDashboardE2ETest : StudentTest() { val course2 = data.coursesList[1] val testAnnouncement = data.announcementsList[0] + Log.d(PREPARATION_TAG, "Get the device to be able to perform app-independent actions on it.") + val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() @@ -54,11 +62,12 @@ class OfflineDashboardE2ETest : StudentTest() { Log.d(STEP_TAG, "Open global 'Manage Offline Content' page via the more menu of the Dashboard Page.") dashboardPage.openGlobalManageOfflineContentPage() + Log.d(STEP_TAG, "Assert that the '${course1.name}' course's checkbox state is 'Unchecked'.") + manageOfflineContentPage.assertCheckedStateOfItem(course1.name, MaterialCheckBox.STATE_UNCHECKED) + Log.d(STEP_TAG, "Select the entire '${course1.name}' course for sync. Click on the 'Sync' button.") manageOfflineContentPage.changeItemSelectionState(course1.name) manageOfflineContentPage.clickOnSyncButtonAndConfirm() - manageOfflineContentPage.changeItemSelectionState(course1.name) - manageOfflineContentPage.clickOnSyncButton() Log.d(STEP_TAG, "Wait for the 'Download Started' dashboard notification to be displayed, and the to disappear.") dashboardPage.waitForRender() @@ -70,7 +79,10 @@ class OfflineDashboardE2ETest : StudentTest() { dashboardPage.waitForSyncProgressStartingNotificationToDisappear() Log.d(PREPARATION_TAG, "Turn off the Wi-Fi and Mobile Data on the device, so it will go offline.") - OfflineTestUtils.turnOffConnectionViaADB() + turnOffConnectionViaADB() + Thread.sleep(10000) //Need to wait a bit here because of a UI glitch that when network state change, the dashboard page 'pops' a bit and it can confuse the automation script. + device.waitForIdle() + device.waitForWindowUpdate(null, 10000) Log.d(STEP_TAG, "Wait for the Dashboard Page to be rendered. Refresh the page.") dashboardPage.waitForRender() @@ -78,7 +90,7 @@ class OfflineDashboardE2ETest : StudentTest() { Log.d(STEP_TAG, "Assert that the Offline Indicator (bottom banner) is displayed on the Dashboard Page.") OfflineTestUtils.assertOfflineIndicator() - Log.d(STEP_TAG, "Assert that the offline sync icon only displayed on the synced course's cours card.") + Log.d(STEP_TAG, "Assert that the offline sync icon only displayed on the synced course's course card.") dashboardPage.assertCourseOfflineSyncIconVisible(course1.name) dashboardPage.assertCourseOfflineSyncIconGone(course2.name) @@ -90,10 +102,68 @@ class OfflineDashboardE2ETest : StudentTest() { announcementListPage.assertTopicDisplayed(testAnnouncement.title) } + @OfflineE2E + @Test + @TestMetaData(Priority.MANDATORY, FeatureCategory.DASHBOARD, TestCategory.E2E, SecondaryFeatureCategory.OFFLINE_MODE) + fun testOfflineDashboardUnavailableFeaturesE2E() { + + Log.d(PREPARATION_TAG,"Seeding data.") + val data = seedData(students = 1, teachers = 1, courses = 1) + val student = data.studentsList[0] + val course = data.coursesList[0] + + Log.d(PREPARATION_TAG, "Get the device to be able to perform app-independent actions on it.") + val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + + Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId}.") + tokenLogin(student) + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Open the '${course.name}' course's 'Manage Offline Content' page via the more menu of the Dashboard Page.") + dashboardPage.clickCourseOverflowMenu(course.name, "Manage Offline Content") + + Log.d(STEP_TAG, "Select the entire '${course.name}' course for sync. Click on the 'Sync' button.") + manageOfflineContentPage.changeItemSelectionState(course.name) + manageOfflineContentPage.clickOnSyncButtonAndConfirm() + + Log.d(STEP_TAG, "Wait for the 'Download Started' dashboard notification to be displayed, and the to disappear.") + dashboardPage.waitForRender() + dashboardPage.waitForSyncProgressDownloadStartedNotification() + dashboardPage.waitForSyncProgressDownloadStartedNotificationToDisappear() + + Log.d(STEP_TAG, "Wait for the 'Syncing Offline Content' dashboard notification to be displayed, and the to disappear. (It should be displayed after the 'Download Started' notification immediately.)") + dashboardPage.waitForSyncProgressStartingNotification() + dashboardPage.waitForSyncProgressStartingNotificationToDisappear() + + Log.d(PREPARATION_TAG, "Turn off the Wi-Fi and Mobile Data on the device, so it will go offline.") + turnOffConnectionViaADB() + device.waitForIdle() + + Log.d(STEP_TAG, "Wait for the Dashboard Page to be rendered. Refresh the page.") + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Assert that the Offline Indicator (bottom banner) is displayed on the Dashboard Page.") + OfflineTestUtils.assertOfflineIndicator() + + Log.d(STEP_TAG, "Assert that the bottom menus (except Dashboard) are disabled and unavailable in offline mode.") + dashboardPage.assertBottomMenusAreDisabled() + + Log.d(STEP_TAG, "Try to open the '${course.name}' course's more menu of the Dashboard Page. Assert that the 'No Internet Connection' dialog is displayed. Dismiss it after the assertion.") + dashboardPage.clickOnCourseOverflowButton(course.name) + OfflineTestUtils.assertNoInternetConnectionDialog() + OfflineTestUtils.dismissNoInternetConnectionDialog() + + Log.d(STEP_TAG, "Try to open the global 'Manage Offline Content' page via the more menu of the Dashboard Page. Assert that the 'No Internet Connection' dialog is displayed. Dismiss it after the assertion.") + Thread.sleep(5000) //Wait for the system notification to disappear, because it overlaps the More menu button on the toolbar. + dashboardPage.openGlobalManageOfflineContentPage() + OfflineTestUtils.assertNoInternetConnectionDialog() + OfflineTestUtils.dismissNoInternetConnectionDialog() + } + @After fun tearDown() { Log.d(PREPARATION_TAG, "Turn back on the Wi-Fi and Mobile Data on the device, so it will come back online.") - OfflineTestUtils.turnOnConnectionViaADB() + turnOnConnectionViaADB() } } \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineFilesE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineFilesE2ETest.kt new file mode 100644 index 0000000000..a96cba7482 --- /dev/null +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineFilesE2ETest.kt @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2024 - present Instructure, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.instructure.student.ui.e2e.offline + +import android.util.Log +import androidx.test.espresso.Espresso +import com.google.android.material.checkbox.MaterialCheckBox +import com.instructure.canvas.espresso.FeatureCategory +import com.instructure.canvas.espresso.OfflineE2E +import com.instructure.canvas.espresso.Priority +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData +import com.instructure.dataseeding.api.FileFolderApi +import com.instructure.dataseeding.model.FileUploadType +import com.instructure.student.ui.utils.StudentTest +import com.instructure.student.ui.utils.seedData +import com.instructure.student.ui.utils.tokenLogin +import com.instructure.student.ui.utils.uploadTextFile +import dagger.hilt.android.testing.HiltAndroidTest +import org.junit.After +import org.junit.Test + +@HiltAndroidTest +class OfflineFilesE2ETest : StudentTest() { + override fun displaysPageObjects() = Unit + + override fun enableAndConfigureAccessibilityChecks() = Unit + + @OfflineE2E + @Test + @TestMetaData(Priority.MANDATORY, FeatureCategory.FILES, TestCategory.E2E) + fun testOfflineFilesE2E() { + + Log.d(PREPARATION_TAG,"Seeding data.") + val data = seedData(teachers = 1, courses = 1, students = 1) + val student = data.studentsList[0] + val teacher = data.teachersList[0] + val course = data.coursesList[0] + + val testCourseFolderName = "Goodya" + Log.d(PREPARATION_TAG, "Create a course folder within the 'Files' tab with the name: '$testCourseFolderName'.") + val courseRootFolder = FileFolderApi.getCourseRootFolder(course.id, teacher.token) + val courseTestFolder = FileFolderApi.createCourseFolder(courseRootFolder.id, testCourseFolderName, false, teacher.token) + + Log.d(PREPARATION_TAG, "Create a (text) file within the root folder (so the 'Files' tab file list) of the '${course.name}' course.") + val rootFolderTestTextFile = uploadTextFile(courseRootFolder.id, token = teacher.token, fileUploadType = FileUploadType.COURSE_FILE) + + Log.d(PREPARATION_TAG, "Create a (text) file within the '${courseTestFolder.name}' folder of the '${course.name}' course.") + val courseTestFolderTextFile = uploadTextFile(courseTestFolder.id, token = teacher.token, fileUploadType = FileUploadType.COURSE_FILE) + + Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId}.") + tokenLogin(student) + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Open the '${course.name}' course's 'Manage Offline Content' page via the more menu of the Dashboard Page.") + dashboardPage.clickCourseOverflowMenu(course.name, "Manage Offline Content") + + Log.d(STEP_TAG, "Assert that the '${course.name}' course's checkbox state is 'Unchecked'.") + manageOfflineContentPage.assertCheckedStateOfItem(course.name, MaterialCheckBox.STATE_UNCHECKED) + + Log.d(STEP_TAG, "Expand the course. Select the 'Files' of '${course.name}' course for sync. Click on the 'Sync' button.") + manageOfflineContentPage.expandCollapseItem(course.name) + manageOfflineContentPage.changeItemSelectionState("Files") + manageOfflineContentPage.clickOnSyncButtonAndConfirm() + + Log.d(STEP_TAG, "Wait for the 'Download Started' dashboard notification to be displayed, and the to disappear.") + dashboardPage.waitForRender() + dashboardPage.waitForSyncProgressDownloadStartedNotification() + dashboardPage.waitForSyncProgressDownloadStartedNotificationToDisappear() + + Log.d(STEP_TAG, "Wait for the 'Syncing Offline Content' dashboard notification to be displayed, and the to disappear. (It should be displayed after the 'Download Started' notification immediately.)") + dashboardPage.waitForSyncProgressStartingNotification() + dashboardPage.waitForSyncProgressStartingNotificationToDisappear() + + Log.d(PREPARATION_TAG, "Turn off the Wi-Fi and Mobile Data on the device, so it will go offline.") + turnOffConnectionViaADB() + Thread.sleep(10000) //Need to wait a bit here because of a UI glitch that when network state change, the dashboard page 'pops' a bit and it can confuse the automation script. + device.waitForIdle() + device.waitForWindowUpdate(null, 10000) + + Log.d(STEP_TAG, "Wait for the Dashboard Page to be rendered. Refresh the page.") + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Select '${course.name}' course and click on 'Files' tab to navigate to the File List Page.") + dashboardPage.selectCourse(course) + courseBrowserPage.selectFiles() + + Log.d(STEP_TAG, "Assert that under the 'Files' tab there are 2 items, a folder and a file which has been seeded recently, and there is 1 item within the folder.") + fileListPage.assertFileListCount(2) + fileListPage.assertFolderSize(courseTestFolder.name, 1) + + Log.d(STEP_TAG, "Assert that the folder's name is '${courseTestFolder.name}' and the file's name is '${rootFolderTestTextFile.fileName}'.") + fileListPage.assertItemDisplayed(rootFolderTestTextFile.fileName) + fileListPage.assertItemDisplayed(courseTestFolder.name) + + Log.d(STEP_TAG, "Open '${courseTestFolder.name}' folder and assert that the '${courseTestFolderTextFile.fileName}' file is displayed within it. Navigate back to File List Page.") + fileListPage.selectItem(courseTestFolder.name) + fileListPage.assertItemDisplayed(courseTestFolderTextFile.fileName) + Espresso.pressBack() + + Log.d(STEP_TAG, "Click on 'Search' (magnifying glass) icon and type '${rootFolderTestTextFile.fileName}', the file's name to the search input field.") + fileListPage.searchable.clickOnSearchButton() + fileListPage.searchable.typeToSearchBar(rootFolderTestTextFile.fileName) + + Log.d(STEP_TAG, "Assert that only 1 file matches for the search text, and it is '${rootFolderTestTextFile.fileName}', and no directories has been shown in the result.") + fileListPage.assertSearchResultCount(1) + fileListPage.assertSearchItemDisplayed(rootFolderTestTextFile.fileName) + fileListPage.assertItemNotDisplayed(testCourseFolderName) + Espresso.closeSoftKeyboard() + } + + @After + fun tearDown() { + Log.d(PREPARATION_TAG, "Turn back on the Wi-Fi and Mobile Data on the device, so it will come back online.") + turnOnConnectionViaADB() + } + +} \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineLeftSideMenuE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineLeftSideMenuE2ETest.kt new file mode 100644 index 0000000000..3b185a5d14 --- /dev/null +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineLeftSideMenuE2ETest.kt @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2023 - present Instructure, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.instructure.student.ui.e2e.offline + +import android.util.Log +import com.instructure.canvas.espresso.FeatureCategory +import com.instructure.canvas.espresso.OfflineE2E +import com.instructure.canvas.espresso.Priority +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData +import com.instructure.student.ui.e2e.offline.utils.OfflineTestUtils.assertNoInternetConnectionDialog +import com.instructure.student.ui.e2e.offline.utils.OfflineTestUtils.assertOfflineIndicator +import com.instructure.student.ui.e2e.offline.utils.OfflineTestUtils.dismissNoInternetConnectionDialog +import com.instructure.student.ui.utils.StudentTest +import com.instructure.student.ui.utils.seedData +import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest +import org.junit.After +import org.junit.Test + +@HiltAndroidTest +class OfflineLeftSideMenuE2ETest : StudentTest() { + override fun displaysPageObjects() = Unit + + override fun enableAndConfigureAccessibilityChecks() = Unit + + @OfflineE2E + @Test + @TestMetaData(Priority.MANDATORY, FeatureCategory.LEFT_SIDE_MENU, TestCategory.E2E) + fun testOfflineLeftSideMenuUnavailableFunctionsE2E() { + + Log.d(PREPARATION_TAG,"Seeding data.") + val data = seedData(students = 1, teachers = 1, courses = 2, announcements = 1) + val student = data.studentsList[0] + + Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId}.") + tokenLogin(student) + dashboardPage.waitForRender() + + Log.d(PREPARATION_TAG, "Turn off the Wi-Fi and Mobile Data on the device, so it will go offline.") + turnOffConnectionViaADB() + Thread.sleep(10000) //Need to wait a bit here because of a UI glitch that when network state change, the dashboard page 'pops' a bit and it can confuse the automation script. + + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Assert that the Offline Indicator (bottom banner) is displayed on the Dashboard Page.") + assertOfflineIndicator() + + Log.d(STEP_TAG, "Open Left Side Menu by clicking on the 'hamburger icon' on the Dashboard Page.") + dashboardPage.openLeftSideMenu() + + Log.d(STEP_TAG, "Assert that the offline indicator is displayed below the user info within the header.") + leftSideNavigationDrawerPage.assertOfflineIndicatorDisplayed() + + Log.d(STEP_TAG, "Assert that the 'Files, Bookmarks, Studio, Color Overlay, Help' menus are disabled in offline mode.") + leftSideNavigationDrawerPage.assertOfflineDisabledMenus(0.5f) + + Log.d(STEP_TAG, "Assert that the 'Settings, Show Grades, Change User, Log Out' menus are enabled in offline mode because they are supported.") + leftSideNavigationDrawerPage.assertOfflineEnabledMenus(1.0f) + + Log.d(STEP_TAG, "Click on 'Files' menu and assert that the 'No Internet Connection' dialog is popping-up. Dismiss it.") + leftSideNavigationDrawerPage.clickFilesMenu() + assertNoInternetConnectionDialog() + dismissNoInternetConnectionDialog() + + Log.d(STEP_TAG, "Click on 'Bookmarks' menu and assert that the 'No Internet Connection' dialog is popping-up. Dismiss it.") + leftSideNavigationDrawerPage.clickBookmarksMenu() + assertNoInternetConnectionDialog() + dismissNoInternetConnectionDialog() + + Log.d(STEP_TAG, "Click on 'Studio' menu and assert that the 'No Internet Connection' dialog is popping-up. Dismiss it.") + leftSideNavigationDrawerPage.clickStudioMenu() + assertNoInternetConnectionDialog() + dismissNoInternetConnectionDialog() + + Log.d(STEP_TAG, "Click on 'Help' menu and assert that the 'No Internet Connection' dialog is popping-up. Dismiss it.") + leftSideNavigationDrawerPage.clickHelpMenu() + assertNoInternetConnectionDialog() + dismissNoInternetConnectionDialog() + + } + + @After + fun tearDown() { + Log.d(PREPARATION_TAG, "Turn back on the Wi-Fi and Mobile Data on the device via ADB, so it will come back online.") + turnOnConnectionViaADB() + } + +} \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineLoginE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineLoginE2ETest.kt new file mode 100644 index 0000000000..a4718ab009 --- /dev/null +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineLoginE2ETest.kt @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2023 - present Instructure, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.instructure.student.ui.e2e.offline + +import android.util.Log +import com.instructure.canvas.espresso.FeatureCategory +import com.instructure.canvas.espresso.OfflineE2E +import com.instructure.canvas.espresso.Priority +import com.instructure.canvas.espresso.SecondaryFeatureCategory +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData +import com.instructure.dataseeding.model.CanvasUserApiModel +import com.instructure.student.ui.e2e.offline.utils.OfflineTestUtils.assertNoInternetConnectionDialog +import com.instructure.student.ui.e2e.offline.utils.OfflineTestUtils.dismissNoInternetConnectionDialog +import com.instructure.student.ui.utils.StudentTest +import com.instructure.student.ui.utils.seedData +import dagger.hilt.android.testing.HiltAndroidTest +import org.junit.After +import org.junit.Test +import java.lang.Thread.sleep + +@HiltAndroidTest +class OfflineLoginE2ETest : StudentTest() { + override fun displaysPageObjects() = Unit + + override fun enableAndConfigureAccessibilityChecks() = Unit + + @OfflineE2E + @Test + @TestMetaData(Priority.IMPORTANT, FeatureCategory.LOGIN, TestCategory.E2E, SecondaryFeatureCategory.CHANGE_USER) + fun testOfflineChangeUserE2E() { + + Log.d(PREPARATION_TAG, "Seeding data.") + val data = seedData(students = 2, teachers = 1, courses = 1) + val student1 = data.studentsList[0] + val student2 = data.studentsList[1] + + Log.d(STEP_TAG, "Login with user: ${student1.name}, login id: ${student1.loginId}.") + loginWithUser(student1) + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Assert that the Offline indicator is not displayed because we are in online mode yet.") + dashboardPage.assertOfflineIndicatorNotDisplayed() + + Log.d(STEP_TAG, "Click on 'Change User' button on the left-side menu.") + leftSideNavigationDrawerPage.clickChangeUserMenu() + + Log.d(STEP_TAG, "Login with user: ${student2.name}, login id: ${student2.loginId}.") + loginWithUser(student2, true) + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Assert that the Offline indicator is not displayed because we are in online mode yet.") + dashboardPage.assertOfflineIndicatorNotDisplayed() + + Log.d(PREPARATION_TAG, "Turn off the Wi-Fi and Mobile Data on the device, so it will go offline.") + turnOffConnectionViaADB() + device.waitForWindowUpdate(null, 10000) + + Log.d(STEP_TAG, "Click on 'Change User' button on the left-side menu.") + leftSideNavigationDrawerPage.clickChangeUserMenu() + + Log.d(STEP_TAG, "Assert that the previously logins has been displayed. Assert that ${student1.name} and ${student2.name} students are displayed within the previous login section.") + loginLandingPage.assertDisplaysPreviousLogins() + loginLandingPage.assertPreviousLoginUserDisplayed(student1.name) + loginLandingPage.assertPreviousLoginUserDisplayed(student2.name) + + Log.d(STEP_TAG, "Try to click on the last saved school's button and assert that the 'No Internet Connection' dialog will be displayed. Dismiss the dialog.") + loginLandingPage.clickOnLastSavedSchoolButton() + assertNoInternetConnectionDialog() + dismissNoInternetConnectionDialog() + + Log.d(STEP_TAG, "Try to click on the 'Find another school' button and assert that the 'No Internet Connection' dialog will be displayed. Dismiss the dialog.") + loginLandingPage.clickFindAnotherSchoolButton() + assertNoInternetConnectionDialog() + dismissNoInternetConnectionDialog() + + Log.d(STEP_TAG, "Login with the previous user, ${student1.name}, with one click, by clicking on the user's name on the bottom.") + loginLandingPage.loginWithPreviousUser(student1) + + Log.d(STEP_TAG, "Wait for the Dashboard Page to be rendered. Assert that the offline indicator is displayed to ensure we are in offline mode, and change user function is supported.") + dashboardPage.waitForRender() + dashboardPage.assertOfflineIndicatorDisplayed() + + Log.d(STEP_TAG, "Click on 'Change User' button on the left-side menu.") + leftSideNavigationDrawerPage.clickChangeUserMenu() + + Log.d(STEP_TAG, "Login with the previous user, ${student2.name}, with one click, by clicking on the user's name on the bottom.") + loginLandingPage.loginWithPreviousUser(student2) + + Log.d(STEP_TAG, "Wait for the Dashboard Page to be rendered. Assert that the offline indicator is displayed to ensure we are in offline mode, and change user function is supported.") + dashboardPage.waitForRender() + dashboardPage.assertOfflineIndicatorDisplayed() + + } + + private fun loginWithUser(user: CanvasUserApiModel, lastSchoolSaved: Boolean = false) { + + sleep(5100) //Need to wait > 5 seconds before each login attempt because of new 'too many attempts' login policy on web. + + if(lastSchoolSaved) { + Log.d(STEP_TAG,"Click 'Find Another School' button.") + loginLandingPage.clickFindAnotherSchoolButton() + } + else { + Log.d(STEP_TAG, "Click 'Find My School' button.") + loginLandingPage.clickFindMySchoolButton() + } + + Log.d(STEP_TAG,"Enter domain: ${user.domain}.") + loginFindSchoolPage.enterDomain(user.domain) + + Log.d(STEP_TAG,"Click on 'Next' button on the Toolbar.") + loginFindSchoolPage.clickToolbarNextMenuItem() + loginSignInPage.loginAs(user) + } + + @After + fun tearDown() { + Log.d(PREPARATION_TAG, "Turn back on the Wi-Fi and Mobile Data on the device, so it will come back online.") + turnOnConnectionViaADB() + } + +} \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflinePeopleE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflinePeopleE2ETest.kt new file mode 100644 index 0000000000..707757c566 --- /dev/null +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflinePeopleE2ETest.kt @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2024 - present Instructure, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.instructure.student.ui.e2e.offline + +import android.util.Log +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.instructure.canvas.espresso.FeatureCategory +import com.instructure.canvas.espresso.OfflineE2E +import com.instructure.canvas.espresso.Priority +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData +import com.instructure.student.ui.e2e.offline.utils.OfflineTestUtils +import com.instructure.student.ui.utils.StudentTest +import com.instructure.student.ui.utils.seedData +import com.instructure.student.ui.utils.tokenLogin +import dagger.hilt.android.testing.HiltAndroidTest +import org.junit.After +import org.junit.Test + +@HiltAndroidTest +class OfflinePeopleE2ETest : StudentTest() { + override fun displaysPageObjects() = Unit + + override fun enableAndConfigureAccessibilityChecks() = Unit + + @OfflineE2E + @Test + @TestMetaData(Priority.MANDATORY, FeatureCategory.PEOPLE, TestCategory.E2E) + fun testOfflinePeopleE2E() { + + Log.d(PREPARATION_TAG,"Seeding data.") + val data = seedData(students = 1, teachers = 1, courses = 1, announcements = 1) + val student = data.studentsList[0] + val teacher = data.teachersList[0] + val course = data.coursesList[0] + + Log.d(PREPARATION_TAG, "Get the device to be able to perform app-independent actions on it.") + val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + + Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId}.") + tokenLogin(student) + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Open the '${course.name}' course's 'Manage Offline Content' page via the more menu of the Dashboard Page.") + dashboardPage.clickCourseOverflowMenu(course.name, "Manage Offline Content") + + manageOfflineContentPage.expandCollapseItem(course.name) + Log.d(STEP_TAG, "Select the entire '${course.name}' course for sync. Click on the 'Sync' button.") + manageOfflineContentPage.changeItemSelectionState("People") + manageOfflineContentPage.clickOnSyncButtonAndConfirm() + + Log.d(STEP_TAG, "Wait for the 'Download Started' dashboard notification to be displayed, and the to disappear.") + dashboardPage.waitForRender() + dashboardPage.waitForSyncProgressDownloadStartedNotification() + dashboardPage.waitForSyncProgressDownloadStartedNotificationToDisappear() + + Log.d(STEP_TAG, "Wait for the 'Syncing Offline Content' dashboard notification to be displayed, and the to disappear. (It should be displayed after the 'Download Started' notification immediately.)") + dashboardPage.waitForSyncProgressStartingNotification() + dashboardPage.waitForSyncProgressStartingNotificationToDisappear() + + Log.d(PREPARATION_TAG, "Turn off the Wi-Fi and Mobile Data on the device, so it will go offline.") + turnOffConnectionViaADB() + Thread.sleep(10000) //Need to wait a bit here because of a UI glitch that when network state change, the dashboard page 'pops' a bit and it can confuse the automation script. + device.waitForIdle() + device.waitForWindowUpdate(null, 10000) + + Log.d(STEP_TAG, "Wait for the Dashboard Page to be rendered. Refresh the page.") + dashboardPage.waitForRender() + + Log.d(STEP_TAG, "Assert that the Offline Indicator (bottom banner) is displayed on the Dashboard Page.") + OfflineTestUtils.assertOfflineIndicator() + + Log.d(STEP_TAG, "Assert that the offline sync icon only displayed on the synced course's course card.") + dashboardPage.assertCourseOfflineSyncIconVisible(course.name) + + Log.d(STEP_TAG, "Select '${course.name}' course and open 'People' menu.") + dashboardPage.selectCourse(course) + courseBrowserPage.selectPeople() + + Log.d(STEP_TAG, "Select '${teacher.name}' teacher.") + peopleListPage.selectPerson(teacher) + + Log.d(STEP_TAG, "Assert that the compose message icon is not displayed (GONE) because it is not supported in offline mode.") + personDetailsPage.assertComposeMessageIcon(ViewMatchers.Visibility.GONE) + } + + @After + fun tearDown() { + Log.d(PREPARATION_TAG, "Turn back on the Wi-Fi and Mobile Data on the device, so it will come back online.") + turnOnConnectionViaADB() + } + +} \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineSyncProgressE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineSyncProgressE2ETest.kt index dc0b91da01..a424546711 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineSyncProgressE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineSyncProgressE2ETest.kt @@ -18,11 +18,14 @@ package com.instructure.student.ui.e2e.offline import android.util.Log import androidx.test.espresso.Espresso +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.google.android.material.checkbox.MaterialCheckBox +import com.instructure.canvas.espresso.FeatureCategory import com.instructure.canvas.espresso.OfflineE2E -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData +import com.instructure.canvas.espresso.Priority +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData import com.instructure.student.ui.e2e.offline.utils.OfflineTestUtils import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedData @@ -49,6 +52,9 @@ class OfflineSyncProgressE2ETest : StudentTest() { val course2 = data.coursesList[1] val testAnnouncement = data.announcementsList[0] + Log.d(PREPARATION_TAG, "Get the device to be able to perform app-independent actions on it.") + val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + Log.d(STEP_TAG,"Login with user: ${student.name}, login id: ${student.loginId}.") tokenLogin(student) dashboardPage.waitForRender() @@ -56,11 +62,12 @@ class OfflineSyncProgressE2ETest : StudentTest() { Log.d(STEP_TAG, "Open global 'Manage Offline Content' page via the more menu of the Dashboard Page.") dashboardPage.openGlobalManageOfflineContentPage() + Log.d(STEP_TAG, "Assert that the '${course1.name}' course's checkbox state is 'Unchecked'.") + manageOfflineContentPage.assertCheckedStateOfItem(course1.name, MaterialCheckBox.STATE_UNCHECKED) + Log.d(STEP_TAG, "Select the entire '${course1.name}' course for sync. Click on the 'Sync' button.") manageOfflineContentPage.changeItemSelectionState(course1.name) manageOfflineContentPage.clickOnSyncButtonAndConfirm() - manageOfflineContentPage.changeItemSelectionState(course1.name) - manageOfflineContentPage.clickOnSyncButton() Log.d(STEP_TAG, "Wait for the 'Download Started' dashboard notification to be displayed, and the to disappear.") dashboardPage.waitForRender() @@ -82,7 +89,11 @@ class OfflineSyncProgressE2ETest : StudentTest() { Espresso.pressBack() Log.d(PREPARATION_TAG, "Turn off the Wi-Fi and Mobile Data on the device, so it will go offline.") - OfflineTestUtils.turnOffConnectionViaADB() + turnOffConnectionViaADB() + Thread.sleep(10000) //Need to wait a bit here because of a UI glitch that when network state change, the dashboard page 'pops' a bit and it can confuse the automation script. + device.waitForIdle() + device.waitForWindowUpdate(null, 10000) + dashboardPage.waitForRender() Log.d(STEP_TAG, "Assert that the Offline Indicator (bottom banner) is displayed on the Dashboard Page.") @@ -103,7 +114,7 @@ class OfflineSyncProgressE2ETest : StudentTest() { @After fun tearDown() { Log.d(PREPARATION_TAG, "Turn back on the Wi-Fi and Mobile Data on the device via ADB, so it will come back online.") - OfflineTestUtils.turnOnConnectionViaADB() + turnOnConnectionViaADB() } } \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineSyncSettingsE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineSyncSettingsE2ETest.kt index fac7879901..67bae6de61 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineSyncSettingsE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineSyncSettingsE2ETest.kt @@ -17,13 +17,12 @@ package com.instructure.student.ui.e2e.offline import android.util.Log +import com.instructure.canvas.espresso.FeatureCategory import com.instructure.canvas.espresso.OfflineE2E -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData +import com.instructure.canvas.espresso.Priority +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData import com.instructure.pandautils.R -import com.instructure.student.ui.e2e.offline.utils.OfflineTestUtils import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.ViewUtils import com.instructure.student.ui.utils.seedData @@ -139,7 +138,7 @@ class OfflineSyncSettingsE2ETest : StudentTest() { @After fun tearDown() { Log.d(PREPARATION_TAG, "Turn back on the Wi-Fi and Mobile Data on the device via ADB, so it will come back online.") - OfflineTestUtils.turnOnConnectionViaADB() + turnOnConnectionViaADB() } } \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/utils/OfflineTestUtils.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/utils/OfflineTestUtils.kt index 717d37af98..0696eec9b3 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/utils/OfflineTestUtils.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/utils/OfflineTestUtils.kt @@ -16,14 +16,14 @@ */ package com.instructure.student.ui.e2e.offline.utils -import androidx.test.espresso.matcher.ViewMatchers.withChild -import androidx.test.espresso.matcher.ViewMatchers.withId -import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.matcher.ViewMatchers.* import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiScrollable import androidx.test.uiautomator.UiSelector import com.instructure.espresso.assertDisplayed +import com.instructure.espresso.click import com.instructure.espresso.matchers.WaitForViewMatcher.waitForView import com.instructure.espresso.page.plus import com.instructure.student.R @@ -31,18 +31,6 @@ import org.hamcrest.CoreMatchers.allOf object OfflineTestUtils { - fun turnOffConnectionViaADB() { - val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - device.executeShellCommand("svc wifi disable") - device.executeShellCommand("svc data disable") - } - - fun turnOnConnectionViaADB() { - val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - device.executeShellCommand("svc wifi enable") - device.executeShellCommand("svc data enable") - } - fun turnOffConnectionOnUI() { val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) @@ -80,4 +68,14 @@ object OfflineTestUtils { ) ).assertDisplayed() } + + fun assertNoInternetConnectionDialog() { + waitForView(withId(R.id.alertTitle) + withText(R.string.noInternetConnectionTitle)).assertDisplayed() + } + + fun dismissNoInternetConnectionDialog() { + onView(withText(android.R.string.ok) + isDescendantOfA(withId(R.id.buttonPanel) + + hasSibling(withId(R.id.topPanel) + + hasDescendant(withText(R.string.noInternetConnectionTitle))))).click() + } } \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/usergroups/UserGroupFilesE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/usergroups/UserGroupFilesE2ETest.kt index e9825d4c3d..be17d15451 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/usergroups/UserGroupFilesE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/usergroups/UserGroupFilesE2ETest.kt @@ -21,11 +21,11 @@ import androidx.test.espresso.Espresso import androidx.test.espresso.intent.Intents import com.instructure.canvas.espresso.E2E import com.instructure.dataseeding.api.GroupsApi -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.canvas.espresso.FeatureCategory +import com.instructure.canvas.espresso.Priority +import com.instructure.canvas.espresso.SecondaryFeatureCategory +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.seedData import com.instructure.student.ui.utils.tokenLogin diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AnnouncementInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AnnouncementInteractionTest.kt index 187ff93b34..75dd998301 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AnnouncementInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AnnouncementInteractionTest.kt @@ -17,6 +17,10 @@ package com.instructure.student.ui.interaction import androidx.test.espresso.Espresso import androidx.test.espresso.web.webdriver.Locator +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.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.addCoursePermissions import com.instructure.canvas.espresso.mockCanvas.addDiscussionTopicToCourse @@ -28,10 +32,6 @@ import com.instructure.canvasapi2.models.DiscussionTopicHeader import com.instructure.canvasapi2.models.Group import com.instructure.canvasapi2.models.Tab import com.instructure.canvasapi2.models.User -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData import com.instructure.student.ui.pages.WebViewTextCheck import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.tokenLogin @@ -52,7 +52,7 @@ class AnnouncementInteractionTest : StudentTest() { // Student enrolled in intended section can see and reply to the announcement // (This kind of seems like more of a test of the mocked endpoint, but we'll go with it.) @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.ANNOUNCEMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.ANNOUNCEMENTS, TestCategory.INTERACTION) fun testAnnouncement_replyToSectionSpecificAnnouncement() { val data = getToCourse(createSections = true) @@ -90,7 +90,7 @@ class AnnouncementInteractionTest : StudentTest() { // User can preview an announcement attachment @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.ANNOUNCEMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.ANNOUNCEMENTS, TestCategory.INTERACTION) fun testAnnouncement_previewAttachment() { val data = getToCourse() @@ -132,7 +132,7 @@ class AnnouncementInteractionTest : StudentTest() { // View/reply to an announcement @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.ANNOUNCEMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.ANNOUNCEMENTS, TestCategory.INTERACTION) fun testAnnouncement_reply() { val data = getToCourse() @@ -156,7 +156,7 @@ class AnnouncementInteractionTest : StudentTest() { // Tests that we can create an announcement (as teacher). @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.ANNOUNCEMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.ANNOUNCEMENTS, TestCategory.INTERACTION) fun testAnnouncementCreate_base() { val data = getToAnnouncementList() @@ -170,7 +170,7 @@ class AnnouncementInteractionTest : StudentTest() { // Tests code around closing / aborting announcement creation (as a teacher) @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ANNOUNCEMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ANNOUNCEMENTS, TestCategory.INTERACTION) fun testAnnouncementCreate_abort() { val data = getToAnnouncementList() val course = data.courses.values.first() @@ -188,7 +188,7 @@ class AnnouncementInteractionTest : StudentTest() { // Tests code around creating an announcement with no description (as a teacher) @Test - @TestMetaData(Priority.COMMON, FeatureCategory.ANNOUNCEMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.COMMON, FeatureCategory.ANNOUNCEMENTS, TestCategory.INTERACTION) fun testAnnouncementCreate_missingDescription() { getToAnnouncementList() @@ -198,7 +198,7 @@ class AnnouncementInteractionTest : StudentTest() { // Tests code around creating an announcement with no title (as a teacher) @Test - @TestMetaData(Priority.COMMON, FeatureCategory.ANNOUNCEMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.COMMON, FeatureCategory.ANNOUNCEMENTS, TestCategory.INTERACTION) fun testAnnouncementCreate_missingTitle() { getToAnnouncementList() discussionListPage.createAnnouncement("", "description") @@ -206,7 +206,7 @@ class AnnouncementInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ANNOUNCEMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ANNOUNCEMENTS, TestCategory.INTERACTION) fun testGroupAnnouncementCreateAsStudent() { getToGroup() @@ -217,7 +217,7 @@ class AnnouncementInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ANNOUNCEMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ANNOUNCEMENTS, TestCategory.INTERACTION) fun testSearchAnnouncement() { val data = getToAnnouncementList() val course = data.courses.values.first() 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 b78a0e9db0..38edfa87c9 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,6 +15,11 @@ */ package com.instructure.student.ui.interaction +import com.instructure.canvas.espresso.FeatureCategory +import com.instructure.canvas.espresso.Priority +import com.instructure.canvas.espresso.SecondaryFeatureCategory +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData import com.instructure.canvas.espresso.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.addAssignment import com.instructure.canvas.espresso.mockCanvas.addAssignmentsToGroups @@ -23,25 +28,21 @@ import com.instructure.canvas.espresso.mockCanvas.init import com.instructure.canvasapi2.models.Assignment import com.instructure.canvasapi2.models.CourseSettings import com.instructure.canvasapi2.utils.toApiString -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.dataseeding.model.SubmissionType import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.routeTo import com.instructure.student.ui.utils.tokenLogin import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Assert.assertNotNull import org.junit.Test -import java.util.Calendar +import java.util.* @HiltAndroidTest class AssignmentDetailsInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, false, SecondaryFeatureCategory.SUBMISSIONS_ONLINE_URL) + @TestMetaData(Priority.MANDATORY, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, SecondaryFeatureCategory.SUBMISSIONS_ONLINE_URL) fun testSubmission_submitAssignment() { // TODO - Test submitting for each submission type // For now, I'm going to just test one submission type @@ -53,7 +54,7 @@ class AssignmentDetailsInteractionTest : StudentTest() { val course = data.courses.values.first() val student = data.students[0] val token = data.tokenFor(student)!! - val assignment = data.addAssignment(courseId = course.id, submissionType = Assignment.SubmissionType.ONLINE_URL) + val assignment = data.addAssignment(courseId = course.id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_URL)) data.addSubmissionForAssignment( assignmentId = assignment.id, userId = data.users.values.first().id, @@ -118,7 +119,7 @@ class AssignmentDetailsInteractionTest : StudentTest() { @Test @TestMetaData(Priority.COMMON, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) - fun testDisplayBookmarMenu() { + fun testDisplayBookmarkMenu() { val data = setUpData() goToAssignmentList() val assignmentList = data.assignments @@ -194,7 +195,7 @@ class AssignmentDetailsInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testLetterGradeAssignmentWithoutQuantitativeRestriction() { val data = setUpData() val assignment = addAssignment(data, Assignment.GradingType.LETTER_GRADE, "B", 90.0, 100) @@ -207,7 +208,7 @@ class AssignmentDetailsInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testGpaScaleAssignmentWithoutQuantitativeRestriction() { setUpData() val assignment = addAssignment(MockCanvas.data, Assignment.GradingType.GPA_SCALE, "3.7", 90.0, 100) @@ -220,7 +221,7 @@ class AssignmentDetailsInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testPointsAssignmentWithoutQuantitativeRestriction() { setUpData() val assignment = addAssignment(MockCanvas.data, Assignment.GradingType.POINTS, "90", 90.0, 100) @@ -233,7 +234,7 @@ class AssignmentDetailsInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testPointsAssignmentExcusedWithoutQuantitativeRestriction() { setUpData() val assignment = addAssignment(MockCanvas.data, Assignment.GradingType.POINTS, null, 90.0, 100, excused = true) @@ -246,7 +247,7 @@ class AssignmentDetailsInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testPercentageAssignmentWithoutQuantitativeRestriction() { setUpData() val assignment = addAssignment(MockCanvas.data, Assignment.GradingType.PERCENT, "90%", 90.0, 100) @@ -259,7 +260,7 @@ class AssignmentDetailsInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testPassFailAssignmentWithoutQuantitativeRestriction() { setUpData() val assignment = addAssignment(MockCanvas.data, Assignment.GradingType.PASS_FAIL, "complete", 0.0, 0) @@ -272,7 +273,7 @@ class AssignmentDetailsInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testLetterGradeAssignmentWithQuantitativeRestriction() { val data = setUpData(restrictQuantitativeData = true) val assignment = addAssignment(data, Assignment.GradingType.LETTER_GRADE, "B", 90.0, 100) @@ -285,7 +286,7 @@ class AssignmentDetailsInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testGpaScaleAssignmentWithQuantitativeRestriction() { setUpData(restrictQuantitativeData = true) val assignment = addAssignment(MockCanvas.data, Assignment.GradingType.GPA_SCALE, "3.7", 90.0, 100) @@ -298,7 +299,7 @@ class AssignmentDetailsInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testPointsAssignmentWithQuantitativeRestriction() { setUpData(restrictQuantitativeData = true) val assignment = addAssignment(MockCanvas.data, Assignment.GradingType.POINTS, "65", 65.0, 100) @@ -311,7 +312,7 @@ class AssignmentDetailsInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testPointsAssignmentExcusedWithQuantitativeRestriction() { setUpData(restrictQuantitativeData = true) val assignment = addAssignment(MockCanvas.data, Assignment.GradingType.POINTS, null, 90.0, 100, excused = true) @@ -324,7 +325,7 @@ class AssignmentDetailsInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testPercentageAssignmentWithQuantitativeRestriction() { setUpData(restrictQuantitativeData = true) val assignment = addAssignment(MockCanvas.data, Assignment.GradingType.PERCENT, "70%", 70.0, 100) @@ -337,7 +338,7 @@ class AssignmentDetailsInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testPassFailAssignmentWithQuantitativeRestriction() { setUpData(restrictQuantitativeData = true) val assignment = addAssignment(MockCanvas.data, Assignment.GradingType.PASS_FAIL, "complete", 0.0, 0) @@ -349,6 +350,41 @@ class AssignmentDetailsInteractionTest : StudentTest() { assignmentDetailsPage.assertScoreNotDisplayed() } + @Test + @TestMetaData(Priority.IMPORTANT, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, SecondaryFeatureCategory.SUBMISSIONS_MULTIPLE_TYPE) + fun testSubmission_multipleSubmissionType() { + 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, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY, Assignment.SubmissionType.ONLINE_UPLOAD, Assignment.SubmissionType.MEDIA_RECORDING, Assignment.SubmissionType.DISCUSSION_TOPIC, Assignment.SubmissionType.ONLINE_URL)) + data.addSubmissionForAssignment( + assignmentId = assignment.id, + userId = data.users.values.first().id, + type = Assignment.SubmissionType.ONLINE_URL.apiString + ) + tokenLogin(data.domain, token, student) + routeTo("courses/${course.id}/assignments", data.domain) + assignmentListPage.waitForPage() + + assignmentListPage.clickAssignment(assignment) + assignmentDetailsPage.clickSubmit() + + assignmentDetailsPage.assertSubmissionTypeDisplayed("Text Entry") + assignmentDetailsPage.assertSubmissionTypeDisplayed("Website URL") + assignmentDetailsPage.assertSubmissionTypeDisplayed("File Upload") + assignmentDetailsPage.assertSubmissionTypeDisplayed("Media Recording") + + //Try 1 submission to check if it's possible to submit even when there are multiple submission types available. + assignmentDetailsPage.selectSubmissionType(SubmissionType.ONLINE_URL) + urlSubmissionUploadPage.submitText("https://google.com") + assignmentDetailsPage.assertStatusSubmitted() + } + private fun setUpData(restrictQuantitativeData: Boolean = false): MockCanvas { // Test clicking on the Submission and Rubric button to load the Submission Details Page val data = MockCanvas.init( @@ -401,7 +437,7 @@ class AssignmentDetailsInteractionTest : StudentTest() { val assignment = data.addAssignment( courseId = course.id, - submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), gradingType = Assignment.gradingTypeToAPIString(gradingType) ?: "", pointsPossible = maxScore, ) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AssignmentListInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AssignmentListInteractionTest.kt index b7c95d29ae..fab2876a38 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AssignmentListInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AssignmentListInteractionTest.kt @@ -15,17 +15,18 @@ */ package com.instructure.student.ui.interaction +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.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.addAssignment import com.instructure.canvas.espresso.mockCanvas.addSubmissionForAssignment import com.instructure.canvas.espresso.mockCanvas.init import com.instructure.canvasapi2.models.Assignment import com.instructure.canvasapi2.models.CourseSettings -import com.instructure.student.ui.utils.* -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -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.tokenLogin import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test @@ -79,7 +80,7 @@ class AssignmentListInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testLetterGradeAssignmentWithoutQuantitativeRestriction() { setUpData() val assignment = addAssignment(MockCanvas.data, Assignment.GradingType.LETTER_GRADE, "B", 90.0, 100) @@ -89,7 +90,7 @@ class AssignmentListInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testGpaScaleAssignmentWithoutQuantitativeRestriction() { setUpData() val assignment = addAssignment(MockCanvas.data, Assignment.GradingType.GPA_SCALE, "3.7", 90.0, 100) @@ -99,7 +100,7 @@ class AssignmentListInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testPointsAssignmentWithoutQuantitativeRestriction() { setUpData() val assignment = addAssignment(MockCanvas.data, Assignment.GradingType.POINTS, "90", 90.0, 100) @@ -109,7 +110,7 @@ class AssignmentListInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testPointsAssignmentExcusedWithoutQuantitativeRestriction() { setUpData() val assignment = addAssignment(MockCanvas.data, Assignment.GradingType.POINTS, null, 90.0, 100, excused = true) @@ -119,7 +120,7 @@ class AssignmentListInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testPercentageAssignmentWithoutQuantitativeRestriction() { setUpData() val assignment = addAssignment(MockCanvas.data, Assignment.GradingType.PERCENT, "90%", 90.0, 100) @@ -129,7 +130,7 @@ class AssignmentListInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testPassFailAssignmentWithoutQuantitativeRestriction() { setUpData() val assignment = addAssignment(MockCanvas.data, Assignment.GradingType.PASS_FAIL, "complete", 0.0, 0) @@ -139,7 +140,7 @@ class AssignmentListInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testLetterGradeAssignmentWithQuantitativeRestriction() { setUpData(restrictQuantitativeData = true) val assignment = addAssignment(MockCanvas.data, Assignment.GradingType.LETTER_GRADE, "B", 90.0, 100) @@ -149,7 +150,7 @@ class AssignmentListInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testGpaScaleAssignmentWithQuantitativeRestriction() { setUpData(restrictQuantitativeData = true) val assignment = addAssignment(MockCanvas.data, Assignment.GradingType.GPA_SCALE, "3.7", 90.0, 100) @@ -159,7 +160,7 @@ class AssignmentListInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testPointsAssignmentWithQuantitativeRestriction() { setUpData(restrictQuantitativeData = true) val assignment = addAssignment(MockCanvas.data, Assignment.GradingType.POINTS, "90", 90.0, 100) @@ -169,7 +170,7 @@ class AssignmentListInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testPointsAssignmentExcusedWithQuantitativeRestriction() { setUpData(restrictQuantitativeData = true) val assignment = addAssignment(MockCanvas.data, Assignment.GradingType.POINTS, null, 90.0, 100, excused = true) @@ -179,7 +180,7 @@ class AssignmentListInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testPercentageAssignmentWithQuantitativeRestriction() { setUpData(restrictQuantitativeData = true) val assignment = addAssignment(MockCanvas.data, Assignment.GradingType.PERCENT, "80%", 80.0, 100) @@ -189,7 +190,7 @@ class AssignmentListInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION) fun testPassFailAssignmentWithQuantitativeRestriction() { setUpData(restrictQuantitativeData = true) val assignment = addAssignment(MockCanvas.data, Assignment.GradingType.PASS_FAIL, "complete", 0.0, 0) @@ -225,7 +226,7 @@ class AssignmentListInteractionTest : StudentTest() { repeat(assignmentCount) { val assignment = data.addAssignment( courseId = course.id, - submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY) ) assignmentList.add(assignment) } @@ -253,7 +254,7 @@ class AssignmentListInteractionTest : StudentTest() { val assignment = data.addAssignment( courseId = course.id, - submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), gradingType = Assignment.gradingTypeToAPIString(gradingType) ?: "", pointsPossible = maxScore, ) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/BookmarkInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/BookmarkInteractionTest.kt index 2dc4dc3f22..ec8cac9747 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/BookmarkInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/BookmarkInteractionTest.kt @@ -16,16 +16,16 @@ package com.instructure.student.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.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.addAssignment import com.instructure.canvas.espresso.mockCanvas.addBookmark import com.instructure.canvas.espresso.mockCanvas.init import com.instructure.canvas.espresso.refresh import com.instructure.canvasapi2.models.Assignment -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -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.tokenLogin import dagger.hilt.android.testing.HiltAndroidTest @@ -38,7 +38,7 @@ class BookmarkInteractionTest : StudentTest() { // Test that we can create a bookmark via the UI and see it in the bookmark list @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.BOOKMARKS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.BOOKMARKS, TestCategory.INTERACTION) fun testBookmark_create() { val data = init() val course = data.courses.values.first() @@ -60,7 +60,7 @@ class BookmarkInteractionTest : StudentTest() { // Tests that we can click a bookmark and end up in the bookmarked location @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.BOOKMARKS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.BOOKMARKS, TestCategory.INTERACTION) fun testBookmark_click() { val data = init() val student = data.students.first() @@ -83,7 +83,7 @@ class BookmarkInteractionTest : StudentTest() { // Tests that we can change the name of a bookmark and still click through to the intended location @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.BOOKMARKS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.BOOKMARKS, TestCategory.INTERACTION) fun testBookmark_changeName() { val data = init() val student = data.students.first() @@ -115,7 +115,7 @@ class BookmarkInteractionTest : StudentTest() { val course = data.courses.values.first() val assignment = data.addAssignment( courseId = course.id, - submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY) ) val token = data.tokenFor(student)!! diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/CourseGradesInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/CourseGradesInteractionTest.kt index ec8a032701..f989450db0 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/CourseGradesInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/CourseGradesInteractionTest.kt @@ -17,6 +17,10 @@ package com.instructure.student.ui.interaction import androidx.test.espresso.matcher.ViewMatchers +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.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.addAssignment import com.instructure.canvas.espresso.mockCanvas.addSubmissionForAssignment @@ -25,12 +29,6 @@ import com.instructure.canvasapi2.models.Assignment import com.instructure.canvasapi2.models.CourseSettings import com.instructure.canvasapi2.models.Grades import com.instructure.canvasapi2.models.Tab -import com.instructure.espresso.page.getStringFromResource -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData -import com.instructure.student.R import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.tokenLogin import dagger.hilt.android.testing.HiltAndroidTest @@ -42,7 +40,7 @@ class CourseGradesInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION) fun testTotalGradeIsDisplayedWithGradeAndScoreWhenNotRestricted() { val data = setUpData(courseCount = 1, favoriteCourseCount = 1) setUpCustomGrade("A", 100.0, data, false) @@ -51,7 +49,7 @@ class CourseGradesInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION) fun testTotalGradeIsDisplayedWithOnlyScoreWhenNotRestrictedAndThereIsNoGrade() { val data = setUpData(courseCount = 1, favoriteCourseCount = 1) setUpCustomGrade(score = 100.0, data = data, restrictQuantitativeData = false) @@ -60,7 +58,7 @@ class CourseGradesInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION) fun testGradeIsDisplayedWithOnlyGradeWhenQuantitativeDataIsRestricted() { val data = setUpData(courseCount = 1, favoriteCourseCount = 1) setUpCustomGrade("A", 100.0, data, true) @@ -69,7 +67,7 @@ class CourseGradesInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION) fun testConvertedGradeIsDisplayedWithOnlyScoreWhenRestrictedAndThereIsNoGrade() { val data = setUpData(courseCount = 1, favoriteCourseCount = 1) setUpCustomGrade(score = 100.0, data = data, restrictQuantitativeData = true) @@ -78,7 +76,7 @@ class CourseGradesInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION) fun testLetterGradeAssignmentWithoutQuantitativeRestriction() { val data = setUpData(courseCount = 1, favoriteCourseCount = 1) setUpCustomGrade(score = 100.0, data = data, restrictQuantitativeData = false) @@ -90,7 +88,7 @@ class CourseGradesInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION) fun testGpaScaleAssignmentWithoutQuantitativeRestriction() { val data = setUpData(courseCount = 1, favoriteCourseCount = 1) setUpCustomGrade(score = 100.0, data = data, restrictQuantitativeData = false) @@ -102,7 +100,7 @@ class CourseGradesInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION) fun testPointsAssignmentWithoutQuantitativeRestriction() { val data = setUpData(courseCount = 1, favoriteCourseCount = 1) setUpCustomGrade(score = 100.0, data = data, restrictQuantitativeData = false) @@ -114,7 +112,7 @@ class CourseGradesInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION) fun testPointsAssignmentExcusedWithoutQuantitativeRestriction() { val data = setUpData(courseCount = 1, favoriteCourseCount = 1) setUpCustomGrade(score = 100.0, data = data, restrictQuantitativeData = false) @@ -126,7 +124,7 @@ class CourseGradesInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION) fun testPercentageAssignmentWithoutQuantitativeRestriction() { val data = setUpData(courseCount = 1, favoriteCourseCount = 1) setUpCustomGrade(score = 100.0, data = data, restrictQuantitativeData = false) @@ -138,7 +136,7 @@ class CourseGradesInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION) fun testPassFailAssignmentWithoutQuantitativeRestriction() { val data = setUpData(courseCount = 1, favoriteCourseCount = 1) setUpCustomGrade(score = 100.0, data = data, restrictQuantitativeData = false) @@ -150,7 +148,7 @@ class CourseGradesInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION) fun testLetterGradeAssignmentWithQuantitativeRestriction() { val data = setUpData(courseCount = 1, favoriteCourseCount = 1) setUpCustomGrade(score = 100.0, data = data, restrictQuantitativeData = true) @@ -162,7 +160,7 @@ class CourseGradesInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION) fun testGpaScaleAssignmentWithQuantitativeRestriction() { val data = setUpData(courseCount = 1, favoriteCourseCount = 1) setUpCustomGrade(score = 100.0, data = data, restrictQuantitativeData = true) @@ -174,7 +172,7 @@ class CourseGradesInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION) fun testPointsAssignmentWithQuantitativeRestriction() { val data = setUpData(courseCount = 1, favoriteCourseCount = 1) setUpCustomGrade(score = 100.0, data = data, restrictQuantitativeData = true) @@ -186,7 +184,7 @@ class CourseGradesInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION) fun testPointsAssignmentExcusedWithQuantitativeRestriction() { val data = setUpData(courseCount = 1, favoriteCourseCount = 1) setUpCustomGrade(score = 100.0, data = data, restrictQuantitativeData = true) @@ -198,7 +196,7 @@ class CourseGradesInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION) fun testPercentageAssignmentWithQuantitativeRestriction() { val data = setUpData(courseCount = 1, favoriteCourseCount = 1) setUpCustomGrade(score = 100.0, data = data, restrictQuantitativeData = true) @@ -210,7 +208,7 @@ class CourseGradesInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.GRADES, TestCategory.INTERACTION) fun testPassFailAssignmentWithQuantitativeRestriction() { val data = setUpData(courseCount = 1, favoriteCourseCount = 1) setUpCustomGrade(score = 100.0, data = data, restrictQuantitativeData = true) @@ -250,7 +248,7 @@ class CourseGradesInteractionTest : StudentTest() { val assignment = data.addAssignment( courseId = course.id, - submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), gradingType = Assignment.gradingTypeToAPIString(gradingType) ?: "", pointsPossible = maxScore, ) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/CourseInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/CourseInteractionTest.kt index 6f33a765b5..281aa55f3d 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/CourseInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/CourseInteractionTest.kt @@ -22,16 +22,16 @@ 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.Locator +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.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.addFileToCourse import com.instructure.canvas.espresso.mockCanvas.addPageToCourse import com.instructure.canvas.espresso.mockCanvas.init import com.instructure.canvasapi2.models.Course import com.instructure.canvasapi2.models.Tab -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -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.tokenLogin import dagger.hilt.android.testing.HiltAndroidTest @@ -46,7 +46,7 @@ class CourseInteractionTest : StudentTest() { // Link from a course page to another public course should open in the app @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.COURSE, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.COURSE, TestCategory.INTERACTION) fun testCourse_linkFromCoursePageToPublicCoursePage() { val data = getToCourse(courseCount = 2, favoriteCourseCount = 2) @@ -81,7 +81,7 @@ class CourseInteractionTest : StudentTest() { // user should be able to open/preview course file @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.FILES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.FILES, TestCategory.INTERACTION) fun testCourse_openFile() { // MBL-13499: Don't run this test on API 28 and above until we add HTTPS support diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/DashboardInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/DashboardInteractionTest.kt index f02805dfff..8392395b13 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/DashboardInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/DashboardInteractionTest.kt @@ -18,16 +18,16 @@ package com.instructure.student.ui.interaction import androidx.lifecycle.MutableLiveData 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.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.addAccountNotification import com.instructure.canvas.espresso.mockCanvas.init import com.instructure.canvasapi2.apis.EnrollmentAPI import com.instructure.canvasapi2.models.CourseSettings import com.instructure.canvasapi2.models.Grades -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData import com.instructure.pandautils.di.NetworkStateProviderModule import com.instructure.pandautils.utils.NetworkStateProvider import com.instructure.student.espresso.fakes.FakeNetworkStateProvider @@ -93,10 +93,10 @@ class DashboardInteractionTest : StudentTest() { dashboardPage.assertCourseNotShown(nonFavorite) dashboardPage.editFavorites() - editDashboardPage.assertCourseDisplayed(nonFavorite) - editDashboardPage.assertCourseNotFavorited(nonFavorite) - editDashboardPage.favoriteCourse(nonFavorite) - editDashboardPage.assertCourseFavorited(nonFavorite) + allCoursesPage.assertCourseDisplayed(nonFavorite) + allCoursesPage.assertCourseNotFavorited(nonFavorite) + allCoursesPage.favoriteCourse(nonFavorite) + allCoursesPage.assertCourseFavorited(nonFavorite) Espresso.pressBack() @@ -115,10 +115,10 @@ class DashboardInteractionTest : StudentTest() { dashboardPage.assertDisplaysCourse(favorite) dashboardPage.editFavorites() - editDashboardPage.assertCourseDisplayed(favorite) - editDashboardPage.assertCourseFavorited(favorite) - editDashboardPage.unfavoriteCourse(favorite) - editDashboardPage.assertCourseNotFavorited(favorite) + allCoursesPage.assertCourseDisplayed(favorite) + allCoursesPage.assertCourseFavorited(favorite) + allCoursesPage.unfavoriteCourse(favorite) + allCoursesPage.assertCourseNotFavorited(favorite) Espresso.pressBack() @@ -137,9 +137,9 @@ class DashboardInteractionTest : StudentTest() { data.courses.values.forEach { dashboardPage.assertDisplaysCourse(it) } dashboardPage.editFavorites() - toFavorite.forEach { editDashboardPage.assertCourseNotFavorited(it) } - editDashboardPage.selectAllCourses() - toFavorite.forEach { editDashboardPage.assertCourseFavorited(it) } + toFavorite.forEach { allCoursesPage.assertCourseNotFavorited(it) } + allCoursesPage.selectAllCourses() + toFavorite.forEach { allCoursesPage.assertCourseFavorited(it) } Espresso.pressBack() @@ -156,9 +156,9 @@ class DashboardInteractionTest : StudentTest() { toRemove.forEach { dashboardPage.assertDisplaysCourse(it) } dashboardPage.editFavorites() - toRemove.forEach { editDashboardPage.assertCourseFavorited(it) } - editDashboardPage.unselectAllCourses() - toRemove.forEach { editDashboardPage.assertCourseNotFavorited(it) } + toRemove.forEach { allCoursesPage.assertCourseFavorited(it) } + allCoursesPage.unselectAllCourses() + toRemove.forEach { allCoursesPage.assertCourseNotFavorited(it) } Espresso.pressBack() @@ -242,7 +242,7 @@ class DashboardInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.DASHBOARD, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.DASHBOARD, TestCategory.INTERACTION) fun testDashboardCourses_tappingCourseCardDisplaysCourseBrowser() { // Tapping on a course card opens course browser page val data = setUpData(courseCount = 1, favoriteCourseCount = 1) @@ -260,7 +260,7 @@ class DashboardInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.DASHBOARD, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.DASHBOARD, TestCategory.INTERACTION) fun testDashboardCourses_gradeIsDisplayedWhenShowGradesIsSelected() { // [Student] Grade is displayed when 'Show Grades' (located in navigation drawer) is selected val data = setUpData(courseCount = 1, favoriteCourseCount = 1) @@ -270,7 +270,7 @@ class DashboardInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.DASHBOARD, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.DASHBOARD, TestCategory.INTERACTION) fun testDashboardCourses_gradeIsNotDisplayedWhenShowGradesIsDeSelected() { // [Student] Grade is NOT displayed when 'Show Grades' (located in navigation drawer) is de-selected val data = setUpData(courseCount = 1, favoriteCourseCount = 1) @@ -280,7 +280,7 @@ class DashboardInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.DASHBOARD, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.DASHBOARD, TestCategory.INTERACTION) fun testDashboardCourses_gradeIsDisplayedWithGradeAndScoreWhenNotRestricted() { // [Student] Grade is displayed when 'Show Grades' (located in navigation drawer) is selected val data = setUpData(courseCount = 1, favoriteCourseCount = 1) @@ -291,7 +291,7 @@ class DashboardInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.DASHBOARD, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.DASHBOARD, TestCategory.INTERACTION) fun testDashboardCourses_gradeIsDisplayedWithGradeOnlyWhenQuantitativeDataIsRestricted() { // [Student] Grade is displayed when 'Show Grades' (located in navigation drawer) is selected val data = setUpData(courseCount = 1, favoriteCourseCount = 1) @@ -302,7 +302,7 @@ class DashboardInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION) fun testOfflineIndicatorDisplayedIfOffline() { goToDashboard(setUpData()) @@ -312,7 +312,7 @@ class DashboardInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION) fun testOfflineIndicatorNotDisplayedIfOnline() { goToDashboard(setUpData()) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/DiscussionsInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/DiscussionsInteractionTest.kt index 8ae21f49ac..25310d5be8 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/DiscussionsInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/DiscussionsInteractionTest.kt @@ -19,12 +19,23 @@ package com.instructure.student.ui.interaction import android.os.SystemClock.sleep import androidx.test.espresso.Espresso import androidx.test.espresso.web.webdriver.Locator -import com.instructure.canvas.espresso.mockCanvas.* -import com.instructure.canvasapi2.models.* -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData +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.mockCanvas.MockCanvas +import com.instructure.canvas.espresso.mockCanvas.addAssignment +import com.instructure.canvas.espresso.mockCanvas.addCoursePermissions +import com.instructure.canvas.espresso.mockCanvas.addDiscussionTopicToCourse +import com.instructure.canvas.espresso.mockCanvas.addFileToCourse +import com.instructure.canvas.espresso.mockCanvas.addReplyToDiscussion +import com.instructure.canvas.espresso.mockCanvas.init +import com.instructure.canvasapi2.models.Assignment +import com.instructure.canvasapi2.models.CanvasContextPermission +import com.instructure.canvasapi2.models.CourseSettings +import com.instructure.canvasapi2.models.DiscussionEntry +import com.instructure.canvasapi2.models.RemoteFile +import com.instructure.canvasapi2.models.Tab import com.instructure.student.ui.pages.WebViewTextCheck import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.tokenLogin @@ -39,7 +50,7 @@ class DiscussionsInteractionTest : StudentTest() { // Verify that a discussion header shows up properly after discussion creation @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION) fun testDiscussionCreate_base() { val data = getToCourse(studentCount = 1, courseCount = 1, enableDiscussionTopicCreation = true) @@ -58,7 +69,7 @@ class DiscussionsInteractionTest : StudentTest() { // It's actually impossible to attach anything to a discussion topic with our app, // so the attachment is done behind the scenes, after the fact. @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION) fun testDiscussionCreate_withAttachment() { val data = getToCourse(studentCount = 1, courseCount = 1, enableDiscussionTopicCreation = true) val course = data.courses.values.first() @@ -102,7 +113,7 @@ class DiscussionsInteractionTest : StudentTest() { // Test that you can't create a discussion when discussion creation is disabled @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION) fun testDiscussionCreate_disabledWhenNotPermitted() { val data = getToCourse(studentCount = 1, courseCount = 1, enableDiscussionTopicCreation = false) @@ -113,7 +124,7 @@ class DiscussionsInteractionTest : StudentTest() { // Tests that links to other Canvas content routes properly @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION) fun testDiscussion_linksRouteInApp() { val data = getToCourse(studentCount = 2, courseCount = 2, enableDiscussionTopicCreation = true) val course1 = data.courses.values.first() @@ -141,7 +152,7 @@ class DiscussionsInteractionTest : StudentTest() { // Replies automatically get marked as read as the user scrolls through the list @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION) fun testDiscussion_postsGetMarkedAsRead() { val data = getToCourse(studentCount = 1, courseCount = 1, enableDiscussionTopicCreation = true) val course = data.courses.values.first() @@ -180,7 +191,7 @@ class DiscussionsInteractionTest : StudentTest() { // Attachment is html, so that we can keep the viewing of it "in-house" // NOTE: Very similar to testDiscussionCreate_withAttachment @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION) fun testDiscussion_previewAttachment() { val data = getToCourse(studentCount = 1, courseCount = 1, enableDiscussionTopicCreation = true) @@ -222,7 +233,7 @@ class DiscussionsInteractionTest : StudentTest() { // Tests that users can like entries and the correct like count is displayed, if the liking is enabled @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION) fun testDiscussionLikePost_base() { val data = getToCourse(studentCount = 1, courseCount = 1, enableDiscussionTopicCreation = true) @@ -263,7 +274,7 @@ class DiscussionsInteractionTest : StudentTest() { // Tests that like count is shown if only graders can like @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION) fun testDiscussionLikes_whenOnlyGradersCanRate() { val data = getToCourse(studentCount = 1, courseCount = 1, enableDiscussionTopicCreation = true) val course = data.courses.values.first() @@ -300,7 +311,7 @@ class DiscussionsInteractionTest : StudentTest() { // Tests that discussion entry liking is not available when disabled @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION) fun testDiscussionLikePost_disabledWhenNotPermitted() { val data = getToCourse(studentCount = 1, courseCount = 1, enableDiscussionTopicCreation = true) data.discussionRatingsEnabled = false @@ -329,7 +340,7 @@ class DiscussionsInteractionTest : StudentTest() { // Test basic discussion view @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION) fun testDiscussionView_base() { val data = getToCourse(studentCount = 1, courseCount = 1, enableDiscussionTopicCreation = true) val course1 = data.courses.values.first() @@ -349,7 +360,7 @@ class DiscussionsInteractionTest : StudentTest() { // Test that you can reply to a discussion (if enabled) @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION) fun testDiscussionView_replies() { val data = getToCourse(studentCount = 1, courseCount = 1, enableDiscussionTopicCreation = true) val course1 = data.courses.values.first() @@ -376,7 +387,7 @@ class DiscussionsInteractionTest : StudentTest() { // Test that replies are not possible when they are not enabled @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION) fun testDiscussionView_repliesHiddenWhenNotPermitted() { val data = getToCourse(studentCount = 1, courseCount = 1, enableDiscussionTopicCreation = true) val course1 = data.courses.values.first() @@ -397,7 +408,7 @@ class DiscussionsInteractionTest : StudentTest() { // Test that a reply is displayed properly @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION) fun testDiscussionReply_base() { val data = getToCourse(studentCount = 1, courseCount = 1, enableDiscussionTopicCreation = true) val course1 = data.courses.values.first() @@ -425,7 +436,7 @@ class DiscussionsInteractionTest : StudentTest() { // It is a whole other gear to manually specify an attachment the same way that a user would, // so we add the attachments programmatically. @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION) fun testDiscussionReply_withAttachment() { val data = getToCourse(studentCount = 1, courseCount = 1, enableDiscussionTopicCreation = true) val course1 = data.courses.values.first() @@ -480,7 +491,7 @@ class DiscussionsInteractionTest : StudentTest() { // Tests that we can make a threaded reply to a reply @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION) fun testDiscussionReply_threaded() { val data = getToCourse(studentCount = 1, courseCount = 1, enableDiscussionTopicCreation = true) val course1 = data.courses.values.first() @@ -517,7 +528,7 @@ class DiscussionsInteractionTest : StudentTest() { // It is a whole other gear to manually specify an attachment the same way that a user would, // so we add the attachments programmatically. @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION) fun testDiscussionReply_threadedWithAttachment() { val data = getToCourse(studentCount = 1, courseCount = 1, enableDiscussionTopicCreation = true) val course1 = data.courses.values.first() @@ -581,7 +592,7 @@ class DiscussionsInteractionTest : StudentTest() { // Tests a discussion with a linked assignment. @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION) fun testDiscussion_linkedAssignment() { val data = MockCanvas.init(teacherCount = 1, studentCount = 1, courseCount = 1, favoriteCourseCount = 1) @@ -597,7 +608,7 @@ class DiscussionsInteractionTest : StudentTest() { // Add an assignment val assignment = data.addAssignment( courseId = course.id, - submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), name = assignmentName, pointsPossible = 12 ) @@ -622,7 +633,7 @@ class DiscussionsInteractionTest : StudentTest() { // Tests a discussion with a linked assignment, show possible points if not restricted @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION) fun testDiscussion_showPointsIfNotRestricted() { val data = MockCanvas.init(teacherCount = 1, studentCount = 1, courseCount = 1, favoriteCourseCount = 1) @@ -638,7 +649,7 @@ class DiscussionsInteractionTest : StudentTest() { // Add an assignment val assignment = data.addAssignment( courseId = course.id, - submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), name = assignmentName, pointsPossible = 12 ) @@ -667,7 +678,7 @@ class DiscussionsInteractionTest : StudentTest() { // Tests a discussion with a linked assignment, hide possible points if restricted @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.DISCUSSIONS, TestCategory.INTERACTION) fun testDiscussion_hidePointsIfRestricted() { val data = MockCanvas.init(teacherCount = 1, studentCount = 1, courseCount = 1, favoriteCourseCount = 1) @@ -683,7 +694,7 @@ class DiscussionsInteractionTest : StudentTest() { // Add an assignment val assignment = data.addAssignment( courseId = course.id, - submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), name = assignmentName, pointsPossible = 12 ) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ElementaryDashboardInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ElementaryDashboardInteractionTest.kt index 8ea359fcf4..4f0f1f8db9 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ElementaryDashboardInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ElementaryDashboardInteractionTest.kt @@ -16,12 +16,12 @@ */ package com.instructure.student.ui.interaction +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.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.init -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData import com.instructure.student.ui.pages.ElementaryDashboardPage import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.tokenLoginElementary diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ElementaryGradesInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ElementaryGradesInteractionTest.kt index 122ec49631..36923ce040 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ElementaryGradesInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ElementaryGradesInteractionTest.kt @@ -17,16 +17,15 @@ package com.instructure.student.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.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.addCourseWithEnrollment import com.instructure.canvas.espresso.mockCanvas.init -import com.instructure.canvasapi2.models.CourseSettings import com.instructure.canvasapi2.models.Enrollment import com.instructure.espresso.page.getStringFromResource -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData import com.instructure.student.R import com.instructure.student.ui.pages.ElementaryDashboardPage import com.instructure.student.ui.utils.StudentTest diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/GroupLinksInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/GroupLinksInteractionTest.kt index aa1c84ccdf..007b295f5f 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/GroupLinksInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/GroupLinksInteractionTest.kt @@ -18,6 +18,11 @@ package com.instructure.student.ui.interaction import android.os.Build import androidx.test.espresso.web.webdriver.Locator +import com.instructure.canvas.espresso.FeatureCategory +import com.instructure.canvas.espresso.Priority +import com.instructure.canvas.espresso.SecondaryFeatureCategory +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData import com.instructure.canvas.espresso.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.addDiscussionTopicToCourse import com.instructure.canvas.espresso.mockCanvas.addFileToFolder @@ -31,11 +36,6 @@ import com.instructure.canvasapi2.models.DiscussionTopicHeader import com.instructure.canvasapi2.models.Group import com.instructure.canvasapi2.models.Page import com.instructure.canvasapi2.models.Tab -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.pages.WebViewTextCheck import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.tokenLogin @@ -58,7 +58,7 @@ class GroupLinksInteractionTest : StudentTest() { // Link to group opens group browser - eg: "/groups/:id" @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION) fun testGroupLink_base() { setUpGroupAndSignIn() dashboardPage.selectGroup(group) @@ -67,7 +67,7 @@ class GroupLinksInteractionTest : StudentTest() { // Link to groups opens dashboard - eg: "/groups" @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION, false, SecondaryFeatureCategory.GROUPS_DASHBOARD) + @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION, SecondaryFeatureCategory.GROUPS_DASHBOARD) fun testGroupLink_dashboard() { setUpGroupAndSignIn() dashboardPage.assertDisplaysGroup(group, course) @@ -75,7 +75,7 @@ class GroupLinksInteractionTest : StudentTest() { // Test not favorite group on dashboard @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.GROUPS, TestCategory.INTERACTION, false, SecondaryFeatureCategory.GROUPS_DASHBOARD) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.GROUPS, TestCategory.INTERACTION, SecondaryFeatureCategory.GROUPS_DASHBOARD) fun testGroupLink_dashboard_favoriteLogics() { val data = setUpGroupAndSignIn() val user = data.users.values.first() @@ -91,7 +91,7 @@ class GroupLinksInteractionTest : StudentTest() { // Test that if no groups has selected as favorite then we display all groups @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.GROUPS, TestCategory.INTERACTION, false, SecondaryFeatureCategory.GROUPS_DASHBOARD) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.GROUPS, TestCategory.INTERACTION, SecondaryFeatureCategory.GROUPS_DASHBOARD) fun testGroupLink_dashboard_not_selected_displays_all() { val data = setUpGroupAndSignIn(isFavorite = false) val user = data.users.values.first() @@ -107,7 +107,7 @@ class GroupLinksInteractionTest : StudentTest() { // Link to file preview opens file - eg: "/groups/:id/files/folder/:id?preview=:id" @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION, false, SecondaryFeatureCategory.GROUPS_FILES) + @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION, SecondaryFeatureCategory.GROUPS_FILES) fun testGroupLink_filePreview() { // MBL-13499: This will cause an http request to our mock web server, and http requests from webviews are illegal @@ -128,7 +128,7 @@ class GroupLinksInteractionTest : StudentTest() { // Link to group announcement opens announcement - eg: "/groups/:id/discussion_topics/:id" @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION, false, SecondaryFeatureCategory.GROUPS_ANNOUNCEMENTS) + @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION, SecondaryFeatureCategory.GROUPS_ANNOUNCEMENTS) fun testGroupLink_announcement() { setUpGroupAndSignIn() dashboardPage.selectGroup(group) @@ -140,7 +140,7 @@ class GroupLinksInteractionTest : StudentTest() { // Link to group announcements list opens announcements - eg: "/groups/:id/announcements" @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION, false, SecondaryFeatureCategory.GROUPS_ANNOUNCEMENTS) + @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION, SecondaryFeatureCategory.GROUPS_ANNOUNCEMENTS) fun testGroupLink_announcementList() { setUpGroupAndSignIn() dashboardPage.selectGroup(group) @@ -150,7 +150,7 @@ class GroupLinksInteractionTest : StudentTest() { // Link to group discussion opens discussion - eg: "/groups/:id/discussion_topics/:id" @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION, false, SecondaryFeatureCategory.GROUPS_DISCUSSIONS) + @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION, SecondaryFeatureCategory.GROUPS_DISCUSSIONS) fun testGroupLink_discussion() { setUpGroupAndSignIn() dashboardPage.selectGroup(group) @@ -161,7 +161,7 @@ class GroupLinksInteractionTest : StudentTest() { // Link to group discussion list opens list - eg: "/groups/:id/discussion_topics" @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION, false, SecondaryFeatureCategory.GROUPS_DISCUSSIONS) + @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION, SecondaryFeatureCategory.GROUPS_DISCUSSIONS) fun testGroupLink_discussionList() { setUpGroupAndSignIn() dashboardPage.selectGroup(group) @@ -171,7 +171,7 @@ class GroupLinksInteractionTest : StudentTest() { // Link to group files list opens group files list - eg: "/groups/:id/files" @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION, false, SecondaryFeatureCategory.GROUPS_FILES) + @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION, SecondaryFeatureCategory.GROUPS_FILES) fun testGroupLink_files() { setUpGroupAndSignIn() dashboardPage.selectGroup(group) @@ -181,7 +181,7 @@ class GroupLinksInteractionTest : StudentTest() { // Link to group files folder opens folder - eg: "/groups/:id/files/folder/:id/" @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION, false, SecondaryFeatureCategory.GROUPS_FILES) + @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION, SecondaryFeatureCategory.GROUPS_FILES) fun testGroupLink_fileFolder() { setUpGroupAndSignIn() dashboardPage.selectGroup(group) @@ -193,7 +193,7 @@ class GroupLinksInteractionTest : StudentTest() { // Link to group page list opens pages - eg: "/groups/:id/pages" @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION, false, SecondaryFeatureCategory.GROUPS_PAGES) + @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION, SecondaryFeatureCategory.GROUPS_PAGES) fun testGroupLink_pagesList() { setUpGroupAndSignIn() dashboardPage.selectGroup(group) @@ -203,7 +203,7 @@ class GroupLinksInteractionTest : StudentTest() { // Link to group page opens page - eg: "/groups/:id/pages/:id" @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION, false, SecondaryFeatureCategory.GROUPS_PAGES) + @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION, SecondaryFeatureCategory.GROUPS_PAGES) fun testGroupLink_Page() { setUpGroupAndSignIn() dashboardPage.selectGroup(group) @@ -216,7 +216,7 @@ class GroupLinksInteractionTest : StudentTest() { // Link to group people list opens list - eg: "/groups/:id/users" @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION, false, SecondaryFeatureCategory.GROUPS_PEOPLE) + @TestMetaData(Priority.MANDATORY, FeatureCategory.GROUPS, TestCategory.INTERACTION, SecondaryFeatureCategory.GROUPS_PEOPLE) fun testGroupLink_people() { setUpGroupAndSignIn() dashboardPage.selectGroup(group) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/HomeroomInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/HomeroomInteractionTest.kt index 9b8350907f..fa08c724fc 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/HomeroomInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/HomeroomInteractionTest.kt @@ -16,14 +16,18 @@ */ package com.instructure.student.ui.interaction -import com.instructure.canvas.espresso.mockCanvas.* +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.mockCanvas.MockCanvas +import com.instructure.canvas.espresso.mockCanvas.addAssignment +import com.instructure.canvas.espresso.mockCanvas.addCourseWithEnrollment +import com.instructure.canvas.espresso.mockCanvas.addDiscussionTopicToCourse +import com.instructure.canvas.espresso.mockCanvas.init import com.instructure.canvasapi2.models.Assignment import com.instructure.canvasapi2.models.Enrollment import com.instructure.espresso.page.getStringFromResource -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData import com.instructure.student.R import com.instructure.student.ui.pages.ElementaryDashboardPage import com.instructure.student.ui.utils.StudentTest @@ -230,8 +234,8 @@ class HomeroomInteractionTest : StudentTest() { val courses = data.courses.values.filter { !it.homeroomCourse } - data.addAssignment(courses[0].id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY) - data.addAssignment(courses[0].id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY) + data.addAssignment(courses[0].id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY)) + data.addAssignment(courses[0].id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY)) goToHomeroomTab(data) @@ -255,8 +259,8 @@ class HomeroomInteractionTest : StudentTest() { val courses = data.courses.values.filter { !it.homeroomCourse } - val assignment1 = data.addAssignment(courses[0].id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY) - data.addAssignment(courses[0].id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY) + val assignment1 = data.addAssignment(courses[0].id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY)) + data.addAssignment(courses[0].id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY)) goToHomeroomTab(data) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ImportantDatesInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ImportantDatesInteractionTest.kt index 3fd8e2f5ff..53b95676a4 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ImportantDatesInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ImportantDatesInteractionTest.kt @@ -16,7 +16,11 @@ package com.instructure.student.ui.interaction +import com.instructure.canvas.espresso.FeatureCategory +import com.instructure.canvas.espresso.Priority import com.instructure.canvas.espresso.StubTablet +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData import com.instructure.canvas.espresso.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.addAssignment import com.instructure.canvas.espresso.mockCanvas.addAssignmentCalendarEvent @@ -26,10 +30,6 @@ import com.instructure.canvasapi2.models.Assignment import com.instructure.dataseeding.util.days import com.instructure.dataseeding.util.fromNow import com.instructure.dataseeding.util.iso8601 -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData import com.instructure.student.ui.pages.ElementaryDashboardPage import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.tokenLoginElementary @@ -64,7 +64,7 @@ class ImportantDatesInteractionTest : StudentTest() { val data = createMockData(courseCount = 1) val course = data.courses.values.toList()[0] - val assignment = data.addAssignment(courseId = course.id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY) + val assignment = data.addAssignment(courseId = course.id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY)) val assignmentScheduleItem = data.addAssignmentCalendarEvent(course.id, 2.days.fromNow.iso8601, assignment.name!!, assignment.description!!, true, assignment) goToImportantDatesTab(data) @@ -132,7 +132,7 @@ class ImportantDatesInteractionTest : StudentTest() { val data = createMockData(courseCount = 1) val course = data.courses.values.toList()[0] - val assignment = data.addAssignment(courseId = course.id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY) + val assignment = data.addAssignment(courseId = course.id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY)) val assignmentScheduleItem = data.addAssignmentCalendarEvent(course.id, 2.days.fromNow.iso8601, assignment.name!!, assignment.description!!, true, assignment) goToImportantDatesTab(data) @@ -152,7 +152,7 @@ class ImportantDatesInteractionTest : StudentTest() { val data = createMockData(courseCount = 1) val course = data.courses.values.toList()[0] - val assignment = data.addAssignment(courseId = course.id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY) + val assignment = data.addAssignment(courseId = course.id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY)) data.addAssignmentCalendarEvent(course.id, 2.days.fromNow.iso8601, assignment.name!!, assignment.description!!, true, assignment) val calendarEvent = data.addCourseCalendarEvent(course.id, 2.days.fromNow.iso8601, "Important event", "Important event description", true) @@ -176,7 +176,7 @@ class ImportantDatesInteractionTest : StudentTest() { val data = createMockData(courseCount = 1) val course = data.courses.values.toList()[0] - val assignment = data.addAssignment(courseId = course.id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY) + val assignment = data.addAssignment(courseId = course.id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY)) val twoDaysFromNowEvent = data.addAssignmentCalendarEvent(course.id, 2.days.fromNow.iso8601, "Important event two days later", "Important event two days later description", true, assignment) val threeDaysFromNowEvent = data.addCourseCalendarEvent(course.id, diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/InboxInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/InboxInteractionTest.kt index ab6d911ab9..bf6fbcdcec 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/InboxInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/InboxInteractionTest.kt @@ -17,12 +17,24 @@ package com.instructure.student.ui.interaction import androidx.test.espresso.Espresso import androidx.test.espresso.matcher.ViewMatchers -import com.instructure.canvas.espresso.mockCanvas.* -import com.instructure.canvasapi2.models.* -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData +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.mockCanvas.MockCanvas +import com.instructure.canvas.espresso.mockCanvas.addConversation +import com.instructure.canvas.espresso.mockCanvas.addConversations +import com.instructure.canvas.espresso.mockCanvas.addConversationsToCourseMap +import com.instructure.canvas.espresso.mockCanvas.addCoursePermissions +import com.instructure.canvas.espresso.mockCanvas.addRecipientsToCourse +import com.instructure.canvas.espresso.mockCanvas.addSentConversation +import com.instructure.canvas.espresso.mockCanvas.createBasicConversation +import com.instructure.canvas.espresso.mockCanvas.init +import com.instructure.canvasapi2.models.Attachment +import com.instructure.canvasapi2.models.CanvasContextPermission +import com.instructure.canvasapi2.models.Conversation +import com.instructure.canvasapi2.models.Course +import com.instructure.canvasapi2.models.User import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.tokenLogin import dagger.hilt.android.testing.HiltAndroidTest diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/LoginInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/LoginInteractionTest.kt index f70b605a3a..99c1dfdd98 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/LoginInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/LoginInteractionTest.kt @@ -15,11 +15,11 @@ */ package com.instructure.student.ui.interaction +import com.instructure.canvas.espresso.FeatureCategory +import com.instructure.canvas.espresso.Priority import com.instructure.canvas.espresso.Stub -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData import com.instructure.student.ui.utils.StudentTest import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test @@ -30,13 +30,13 @@ class LoginInteractionTest : StudentTest() { @Stub @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.LOGIN, TestCategory.INTERACTION, true) + @TestMetaData(Priority.MANDATORY, FeatureCategory.LOGIN, TestCategory.INTERACTION) fun testLogin_canFindSchool() { // Should be able to search for and select a school in the "What's your school's name?" page } @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.LOGIN, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.LOGIN, TestCategory.INTERACTION) fun testLogin_qrTutorialPageLoads() { // Should be able to view and assert page objects on the QR tutorial page loginLandingPage.clickQRCodeButton() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ModuleInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ModuleInteractionTest.kt index 20c99a602c..a4af822a17 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ModuleInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ModuleInteractionTest.kt @@ -18,6 +18,11 @@ package com.instructure.student.ui.interaction import android.text.Html import androidx.test.espresso.Espresso import androidx.test.espresso.web.webdriver.Locator +import com.instructure.canvas.espresso.FeatureCategory +import com.instructure.canvas.espresso.Priority +import com.instructure.canvas.espresso.SecondaryFeatureCategory +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData import com.instructure.canvas.espresso.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.addAssignment import com.instructure.canvas.espresso.mockCanvas.addDiscussionTopicToCourse @@ -43,11 +48,6 @@ import com.instructure.canvasapi2.models.Tab import com.instructure.dataseeding.util.days import com.instructure.dataseeding.util.fromNow import com.instructure.dataseeding.util.iso8601 -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.R import com.instructure.student.ui.pages.WebViewTextCheck import com.instructure.student.ui.utils.StudentTest @@ -71,7 +71,7 @@ class ModuleInteractionTest : StudentTest() { // Tapping an Assignment module item should navigate to that item's detail page @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.MODULES, TestCategory.INTERACTION, false, SecondaryFeatureCategory.MODULES_ASSIGNMENTS) + @TestMetaData(Priority.MANDATORY, FeatureCategory.MODULES, TestCategory.INTERACTION, SecondaryFeatureCategory.MODULES_ASSIGNMENTS) fun testModules_launchesIntoAssignment() { // Basic mock setup val data = getToCourseModules(studentCount = 1, courseCount = 1) @@ -81,7 +81,7 @@ class ModuleInteractionTest : StudentTest() { // Create an assignment and add it as a module item assignment = data.addAssignment( courseId = course1.id, - submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY) ) data.addItemToModule( course = course1, @@ -100,7 +100,7 @@ class ModuleInteractionTest : StudentTest() { // Tapping a Discussion module item should navigate to that item's detail page @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.MODULES, TestCategory.INTERACTION, false, SecondaryFeatureCategory.MODULES_DISCUSSIONS) + @TestMetaData(Priority.MANDATORY, FeatureCategory.MODULES, TestCategory.INTERACTION, SecondaryFeatureCategory.MODULES_DISCUSSIONS) fun testModules_launchesIntoDiscussion() { // Basic mock setup val data = getToCourseModules(studentCount = 1, courseCount = 1) @@ -151,7 +151,7 @@ class ModuleInteractionTest : StudentTest() { // Tapping an ExternalURL module item should navigate to that item's detail page @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.MODULES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.MODULES, TestCategory.INTERACTION) fun testModules_launchesIntoExternalURL() { // Basic mock setup val externalUrl = "https://www.google.com" @@ -176,7 +176,7 @@ class ModuleInteractionTest : StudentTest() { // Tapping a File module item should navigate to that item's detail page @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.MODULES, TestCategory.INTERACTION, false, SecondaryFeatureCategory.MODULES_FILES) + @TestMetaData(Priority.MANDATORY, FeatureCategory.MODULES, TestCategory.INTERACTION, SecondaryFeatureCategory.MODULES_FILES) fun testModules_launchesIntoFile() { // Basic mock setup val data = getToCourseModules(studentCount = 1, courseCount = 1) @@ -210,7 +210,7 @@ class ModuleInteractionTest : StudentTest() { // Tapping a Page module item should navigate to that item's detail page @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.MODULES, TestCategory.INTERACTION, false, SecondaryFeatureCategory.MODULES_PAGES) + @TestMetaData(Priority.MANDATORY, FeatureCategory.MODULES, TestCategory.INTERACTION, SecondaryFeatureCategory.MODULES_PAGES) fun testModules_launchesIntoPage() { // Basic mock setup val data = getToCourseModules(studentCount = 1, courseCount = 1) @@ -251,7 +251,7 @@ class ModuleInteractionTest : StudentTest() { // Tapping a Quiz module item should navigate to that item's detail page @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.MODULES, TestCategory.INTERACTION, false, SecondaryFeatureCategory.EVENTS_QUIZZES) + @TestMetaData(Priority.MANDATORY, FeatureCategory.MODULES, TestCategory.INTERACTION, SecondaryFeatureCategory.EVENTS_QUIZZES) fun testModules_launchesIntoQuiz() { // Basic mock setup val data = getToCourseModules(studentCount = 1, courseCount = 1) @@ -314,7 +314,7 @@ class ModuleInteractionTest : StudentTest() { // Tapping a module should collapse and hide all of that module's items in the module list // Tapping a collapsed module should expand it @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.MODULES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.MODULES, TestCategory.INTERACTION) fun testModules_modulesExpandAndCollapse() { // Basic mock setup val data = getToCourseModules(studentCount = 1, courseCount = 1) @@ -324,7 +324,7 @@ class ModuleInteractionTest : StudentTest() { // Create an assignment and add it as a module item assignment = data.addAssignment( courseId = course1.id, - submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY) ) data.addItemToModule( course = course1, @@ -353,7 +353,7 @@ class ModuleInteractionTest : StudentTest() { // After entering the detail page for a module item, pressing the back button or back arrow should navigate back // to the module list. This should also work if the detail page is accessed via deep link @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.MODULES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.MODULES, TestCategory.INTERACTION) fun testModules_navigateBackToModuleListFromModuleItem() { // Basic mock setup val data = getToCourseModules(studentCount = 1, courseCount = 1) @@ -373,7 +373,7 @@ class ModuleInteractionTest : StudentTest() { // When viewing the detail page for an item in a module with multiple items, the detail page should have // 'next' and 'previous' navigation buttons. Clicking these should navigate to the next/previous module items. @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.MODULES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.MODULES, TestCategory.INTERACTION) fun testModules_navigateToNextAndPreviousModuleItems() { // Basic mock setup val data = getToCourseModules(studentCount = 1, courseCount = 1) @@ -385,7 +385,7 @@ class ModuleInteractionTest : StudentTest() { // Create an assignment and add it as a module item assignment = data.addAssignment( courseId = course1.id, - submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY) ) data.addItemToModule( course = course1, @@ -482,7 +482,7 @@ class ModuleInteractionTest : StudentTest() { // Module can't be accessed unless all prerequisites have been fulfilled @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.MODULES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.MODULES, TestCategory.INTERACTION) fun testModules_moduleLockedWithUnfulfilledPrerequisite() { // Basic mock setup @@ -501,7 +501,7 @@ class ModuleInteractionTest : StudentTest() { // And let's add an assignment to the new module var unavailableAssignment = data.addAssignment( courseId = course1.id, - submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), // Man, this is a bit hokey, but it's what I had to do to get the assignment to show // up as unavailable in the assignment details page lockInfo = LockInfo( @@ -524,7 +524,7 @@ class ModuleInteractionTest : StudentTest() { // Module can't be accessed until the availability date has passed @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.MODULES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.MODULES, TestCategory.INTERACTION) fun testModules_moduleLockedUntilAvailabilityDate() { // Basic mock setup val data = getToCourseModules(studentCount = 1, courseCount = 1) @@ -541,7 +541,7 @@ class ModuleInteractionTest : StudentTest() { // And let's create an assignment and add it to the "locked" module. val lockedAssignment = data.addAssignment( courseId = course1.id, - submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY) ) data.addItemToModule( course = course1, @@ -558,7 +558,7 @@ class ModuleInteractionTest : StudentTest() { // Show possible points for assignments in modules if not restricted @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.MODULES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.MODULES, TestCategory.INTERACTION) fun testModules_showPossiblePointsIfNotRestricted() { val data = getToCourseModules(studentCount = 1, courseCount = 1) val course = data.courses.values.first() @@ -568,7 +568,7 @@ class ModuleInteractionTest : StudentTest() { val assignment = data.addAssignment( courseId = course.id, - submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), pointsPossible = 10 ) @@ -585,7 +585,7 @@ class ModuleInteractionTest : StudentTest() { // Hide possible points for assignments in modules if restricted @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.MODULES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.MODULES, TestCategory.INTERACTION) fun testModules_hidePossiblePointsIfRestricted() { val data = getToCourseModules(studentCount = 1, courseCount = 1) val course = data.courses.values.first() @@ -595,7 +595,7 @@ class ModuleInteractionTest : StudentTest() { val assignment = data.addAssignment( courseId = course.id, - submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), pointsPossible = 10 ) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/NavigationDrawerInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/NavigationDrawerInteractionTest.kt index 17ba053fe2..51c5b5dc53 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/NavigationDrawerInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/NavigationDrawerInteractionTest.kt @@ -23,6 +23,10 @@ import android.util.Log import androidx.lifecycle.MutableLiveData import androidx.test.espresso.intent.Intents import androidx.test.espresso.intent.matcher.IntentMatchers +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.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.init import com.instructure.canvasapi2.models.Course @@ -31,10 +35,6 @@ import com.instructure.canvasapi2.utils.ApiPrefs import com.instructure.canvasapi2.utils.ContextKeeper import com.instructure.loginapi.login.model.SignedInUser import com.instructure.loginapi.login.util.PreviousUsersUtils -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData import com.instructure.pandautils.di.NetworkStateProviderModule import com.instructure.pandautils.utils.NetworkStateProvider import com.instructure.student.R @@ -77,7 +77,7 @@ class NavigationDrawerInteractionTest : StudentTest() { // Should be able to change the user from the navigation drawer @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.LOGIN, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.LOGIN, TestCategory.INTERACTION) fun testNavDrawer_changeUser() { // This test fails on API-28 in FTL due to a "TOO_MANY_REGISTRATIONS" issue on logout. @@ -123,7 +123,7 @@ class NavigationDrawerInteractionTest : StudentTest() { // Should be able to log out from the navigation drawer @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.LOGIN, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.LOGIN, TestCategory.INTERACTION) fun testNavDrawer_logOut() { // This test fails on API-28 in FTL due to a "TOO_MANY_REGISTRATIONS" issue on logout. @@ -141,7 +141,7 @@ class NavigationDrawerInteractionTest : StudentTest() { // Should open a dialog and send a question for the selected course // (Checks to see that we can fill out the question and the SEND button exists.) @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION) fun testHelp_askQuestion() { signInStudent() @@ -152,7 +152,7 @@ class NavigationDrawerInteractionTest : StudentTest() { // Should open the Canvas guides in a WebView @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION) fun testHelp_searchCanvasGuides() { signInStudent() @@ -164,7 +164,7 @@ class NavigationDrawerInteractionTest : StudentTest() { // Should send an error report // (Checks to see that we can fill out an error report and that the SEND button is displayed.) @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION) fun testHelp_reportAProblem() { signInStudent() @@ -181,7 +181,7 @@ class NavigationDrawerInteractionTest : StudentTest() { // // So this is a watered-down test that just checks whether an email app chooser gets displayed. @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION) fun testHelp_submitFeatureIdea() { signInStudent() @@ -231,7 +231,7 @@ class NavigationDrawerInteractionTest : StudentTest() { // Should send an intent to open the listing for Student App in the Play Store @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION) fun testHelp_shareYourLove() { signInStudent() @@ -255,7 +255,7 @@ class NavigationDrawerInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION) fun testMenuItemForDefaultStudent() { signInStudent() @@ -263,7 +263,7 @@ class NavigationDrawerInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION) fun testMenuItemForElementaryStudent() { signInElementaryStudent() @@ -271,7 +271,7 @@ class NavigationDrawerInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION) fun testOfflineIndicatorDisplayedIfOffline() { signInStudent() @@ -281,7 +281,7 @@ class NavigationDrawerInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION) fun testOfflineIndicatorNotDisplayedIfOnline() { signInStudent() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/NotificationInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/NotificationInteractionTest.kt index 3a28429766..adde4b4802 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/NotificationInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/NotificationInteractionTest.kt @@ -15,16 +15,20 @@ */ package com.instructure.student.ui.interaction -import com.instructure.canvas.espresso.mockCanvas.* +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.mockCanvas.MockCanvas +import com.instructure.canvas.espresso.mockCanvas.addAssignment +import com.instructure.canvas.espresso.mockCanvas.addSubmissionForAssignment +import com.instructure.canvas.espresso.mockCanvas.addSubmissionStreamItem +import com.instructure.canvas.espresso.mockCanvas.init import com.instructure.canvasapi2.models.Assignment import com.instructure.canvasapi2.models.CourseSettings import com.instructure.dataseeding.util.ago import com.instructure.dataseeding.util.days import com.instructure.dataseeding.util.iso8601 -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -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.tokenLogin import dagger.hilt.android.testing.HiltAndroidTest @@ -36,7 +40,7 @@ class NotificationInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION) fun testClick_itWorks() { // Test that push notifications work when you click on them val data = goToNotifications() @@ -49,7 +53,7 @@ class NotificationInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION) fun testNotificationList_showGradeIfNotRestricted_points() { val grade = "10.0" val data = goToNotifications( @@ -65,7 +69,7 @@ class NotificationInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION) fun testNotificationList_showGradeIfNotRestricted_percent() { val grade = "10%" val data = goToNotifications( @@ -81,7 +85,7 @@ class NotificationInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION) fun testNotificationList_showGradeIfNotRestricted_letter() { val grade = "A" val data = goToNotifications( @@ -97,7 +101,7 @@ class NotificationInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION) fun testNotificationList_showGradeIfNotRestricted_gpa() { val grade = "GPA" val data = goToNotifications( @@ -113,7 +117,7 @@ class NotificationInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION) fun testNotificationList_showGradeIfNotRestricted_passFail() { val grade = "complete" val data = goToNotifications( @@ -130,7 +134,7 @@ class NotificationInteractionTest : StudentTest() { @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION) fun testNotificationList_showGradeUpdatedIfRestricted_points() { val grade = "15.0" val data = goToNotifications( @@ -146,7 +150,7 @@ class NotificationInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION) fun testNotificationList_convertGradeIfRestricted_percent() { val grade = "50%" val data = goToNotifications( @@ -162,7 +166,7 @@ class NotificationInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION) fun testNotificationList_showGradeIfRestricted_letter() { val grade = "A" val data = goToNotifications( @@ -178,7 +182,7 @@ class NotificationInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION) fun testNotificationList_showGradeIfRestricted_gpa() { val grade = "GPA" val data = goToNotifications( @@ -194,7 +198,7 @@ class NotificationInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION) fun testNotificationList_showGradeIfRestricted_passFail() { val grade = "complete" val data = goToNotifications( @@ -210,7 +214,7 @@ class NotificationInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.NOTIFICATIONS, TestCategory.INTERACTION) fun testNotificationList_showExcused() { val data = goToNotifications( restrictQuantitativeData = true, @@ -251,7 +255,7 @@ class NotificationInteractionTest : StudentTest() { repeat(numSubmissions) { val assignment = data.addAssignment( courseId = course.id, - submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), gradingType = Assignment.gradingTypeToAPIString(gradingType).orEmpty(), pointsPossible = 20 ) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/OfflineContentInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/OfflineContentInteractionTest.kt index 55cdbbd4e4..d429d3b65d 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/OfflineContentInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/OfflineContentInteractionTest.kt @@ -20,16 +20,16 @@ package com.instructure.student.ui.interaction import android.text.format.Formatter import androidx.test.espresso.Espresso import com.google.android.material.checkbox.MaterialCheckBox +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.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.addFileToCourse import com.instructure.canvas.espresso.mockCanvas.init import com.instructure.canvasapi2.models.Course import com.instructure.canvasapi2.models.Tab import com.instructure.dataseeding.util.Randomizer -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData import com.instructure.pandautils.R import com.instructure.pandautils.utils.StorageUtils import com.instructure.student.ui.utils.StudentTest @@ -47,14 +47,14 @@ class OfflineContentInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION) fun displaysNoCourses() { goToOfflineContent(createMockCanvas(courseCount = 0)) manageOfflineContentPage.assertDisplaysNoCourses() } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION) fun displaysEmptyCourse() { val data = createMockCanvas(courseCount = 1, hasTabs = false) goToOfflineContent(data) @@ -63,7 +63,7 @@ class OfflineContentInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION) fun displaysCoursesCollapsedIfGlobalOfflineContent() { val data = createMockCanvas() goToOfflineContent(data) @@ -72,15 +72,15 @@ class OfflineContentInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION, false) - fun displaysCourseExpandedIfCourseOfflineContent() { + @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION) + fun displaysCourseCollapsedIfCourseOfflineContent() { val data = createMockCanvas() goToOfflineContentByCourse(data) - manageOfflineContentPage.assertDisplaysItemWithExpandedState(data.courses.values.first().name, true) + manageOfflineContentPage.assertDisplaysItemWithExpandedState(data.courses.values.first().name, false) } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION) fun displaysCourseTabsAndFiles() { val data = createMockCanvas(courseCount = 1) goToOfflineContent(data) @@ -90,7 +90,7 @@ class OfflineContentInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION) fun expandCourse() { val data = createMockCanvas() goToOfflineContent(data) @@ -98,12 +98,11 @@ class OfflineContentInteractionTest : StudentTest() { manageOfflineContentPage.assertDisplaysItemWithExpandedState(course.name, false) manageOfflineContentPage.expandCollapseItem(course.name) manageOfflineContentPage.assertDisplaysItemWithExpandedState(course.name, true) - manageOfflineContentPage.assertItemDisplayed(data.courseTabs[course.id]!!.first().label!!) getCourseItemNames(data, course).forEach { manageOfflineContentPage.assertItemDisplayed(it) } } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION) fun selectCourse() { val data = createMockCanvas() goToOfflineContent(data) @@ -115,7 +114,7 @@ class OfflineContentInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION) fun selectTab() { val data = createMockCanvas() goToOfflineContent(data) @@ -129,7 +128,7 @@ class OfflineContentInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION) fun expandFilesTab() { val data = createMockCanvas() goToOfflineContent(data) @@ -144,7 +143,7 @@ class OfflineContentInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION) fun selectFilesTab() { val data = createMockCanvas() goToOfflineContent(data) @@ -161,7 +160,7 @@ class OfflineContentInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION) fun selectFile() { val data = createMockCanvas() goToOfflineContent(data) @@ -178,7 +177,7 @@ class OfflineContentInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION) fun selectAllFiles() { val data = createMockCanvas(courseCount = 1) goToOfflineContent(data) @@ -197,7 +196,7 @@ class OfflineContentInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION) fun selectAllTabs() { val data = createMockCanvas() goToOfflineContent(data) @@ -215,7 +214,7 @@ class OfflineContentInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION) fun selectAllToggle() { val data = createMockCanvas() goToOfflineContent(data) @@ -230,7 +229,7 @@ class OfflineContentInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION) fun displaysDiscardDialogIfNeeded() { goToOfflineContent() Espresso.pressBack() @@ -241,7 +240,7 @@ class OfflineContentInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION) fun displaysWifiOnlySyncDialog() { val data = createMockCanvas() val course = data.courses.values.first() @@ -257,7 +256,7 @@ class OfflineContentInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION) fun displaysSyncDialog() { val data = createMockCanvas() val course = data.courses.values.first() @@ -273,7 +272,7 @@ class OfflineContentInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION) fun savesChangesOnSync() { val data = createMockCanvas() goToOfflineContent(data) @@ -284,7 +283,7 @@ class OfflineContentInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.OFFLINE_CONTENT, TestCategory.INTERACTION) fun calculatesStorageInfoCorrectly() { val data = createMockCanvas(fileCount = 10, largeFiles = true) val course = data.courses.values.first() @@ -353,7 +352,7 @@ class OfflineContentInteractionTest : StudentTest() { } private fun getCourseItemNames(data: MockCanvas, course: Course): List { - return data.courseTabs[course.id]!!.map { it.label!! } + course.name + + return listOf(course.name) + data.courseTabs[course.id]!!.map { it.label!! } + data.folderFiles[data.courseRootFolders[course.id]!!.id]!!.map { it.displayName!! } } diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PdfInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PdfInteractionTest.kt index 5c744e014f..8c61146e4e 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PdfInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PdfInteractionTest.kt @@ -21,11 +21,21 @@ import androidx.test.espresso.web.webdriver.DriverAtoms import androidx.test.espresso.web.webdriver.Locator import androidx.test.platform.app.InstrumentationRegistry import com.instructure.annotations.FileCaching.FileCache -import com.instructure.canvas.espresso.mockCanvas.* +import com.instructure.canvas.espresso.FeatureCategory +import com.instructure.canvas.espresso.Priority +import com.instructure.canvas.espresso.SecondaryFeatureCategory +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData +import com.instructure.canvas.espresso.mockCanvas.MockCanvas +import com.instructure.canvas.espresso.mockCanvas.addAnnotation +import com.instructure.canvas.espresso.mockCanvas.addAssignment +import com.instructure.canvas.espresso.mockCanvas.addAssignmentsToGroups +import com.instructure.canvas.espresso.mockCanvas.addFileToCourse +import com.instructure.canvas.espresso.mockCanvas.addSubmissionForAssignment +import com.instructure.canvas.espresso.mockCanvas.init import com.instructure.canvasapi2.models.Assignment import com.instructure.canvasapi2.models.Attachment import com.instructure.canvasapi2.models.Tab -import com.instructure.panda_annotations.* import com.instructure.pandautils.loaders.OpenMediaAsyncTaskLoader import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.routeTo @@ -47,14 +57,14 @@ class PdfInteractionTest : StudentTest() { private lateinit var attachment: Attachment @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, false, SecondaryFeatureCategory.SUBMISSIONS_ANNOTATIONS) + @TestMetaData(Priority.MANDATORY, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, SecondaryFeatureCategory.SUBMISSIONS_ANNOTATIONS) fun testAnnotations_viewPdfSubmission() { goToAssignmentPdfSubmission() submissionDetailsPage.assertFileDisplayed(pdfFileName) } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, false, SecondaryFeatureCategory.SUBMISSIONS_ANNOTATIONS) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, SecondaryFeatureCategory.SUBMISSIONS_ANNOTATIONS) fun testAnnotations_viewAndSelectAnnotationsInSubmission() { goToAssignmentPdfSubmission() submissionDetailsPage.clickSubmissionContentAtPosition(.5f, .5f) @@ -62,7 +72,7 @@ class PdfInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, false, SecondaryFeatureCategory.SUBMISSIONS_ANNOTATIONS) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, SecondaryFeatureCategory.SUBMISSIONS_ANNOTATIONS) fun testAnnotations_selectAndCommentOnAnnotationWithNoExistingComments() { val sentCommentContents = "what up dog" // Configure the comment to be sent in mock Canvas @@ -75,7 +85,7 @@ class PdfInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, false, SecondaryFeatureCategory.SUBMISSIONS_ANNOTATIONS) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, SecondaryFeatureCategory.SUBMISSIONS_ANNOTATIONS) fun testAnnotations_selectAndCommentOnAnnotationWithExistingComments() { val sentCommentContents = "what up dog" // Configure the comment to be sent in mock Canvas and the existing comment @@ -89,7 +99,7 @@ class PdfInteractionTest : StudentTest() { @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.FILES, TestCategory.INTERACTION, false, SecondaryFeatureCategory.SUBMISSIONS_ANNOTATIONS) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.FILES, TestCategory.INTERACTION, SecondaryFeatureCategory.SUBMISSIONS_ANNOTATIONS) fun testAnnotations_openPdfFilesInPSPDFKit() { // Annotation toolbar icon needs to be present val data = getToCourse() @@ -110,7 +120,7 @@ class PdfInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, false, SecondaryFeatureCategory.SUBMISSIONS_ANNOTATIONS) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION, SecondaryFeatureCategory.SUBMISSIONS_ANNOTATIONS) fun testAnnotations_openPdfsInPSPDFKitFromLinksInAssignment() { // Annotation toolbar icon needs to be present, this link is specific to assignment details, as that was the advertised use case val data = MockCanvas.init( @@ -143,7 +153,7 @@ class PdfInteractionTest : StudentTest() { val pdfUrlElementId = "testLinkElement" val assignmentDescriptionHtml = """pdf baby!!!""" - val assignment = data.addAssignment(courseId = course.id, submissionType = Assignment.SubmissionType.ONLINE_UPLOAD, description = assignmentDescriptionHtml) + val assignment = data.addAssignment(courseId = course.id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_UPLOAD), description = assignmentDescriptionHtml) assignmentListPage.waitForPage() assignmentListPage.refresh() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PeopleInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PeopleInteractionTest.kt index effce85116..d3339ff86d 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PeopleInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PeopleInteractionTest.kt @@ -15,13 +15,13 @@ */ package com.instructure.student.ui.interaction +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.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.init import com.instructure.canvasapi2.models.User -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -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 @@ -33,7 +33,7 @@ class PeopleInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit // Not used for interaction tests @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.PEOPLE, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.PEOPLE, TestCategory.INTERACTION) fun testClick_openContextCard() { // Should be able to view all enrolled users and tap on one to open their context card goToPeopleList() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PickerSubmissionUploadInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PickerSubmissionUploadInteractionTest.kt index 3d3bffea00..333abfc1da 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PickerSubmissionUploadInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PickerSubmissionUploadInteractionTest.kt @@ -23,15 +23,15 @@ import android.net.Uri import androidx.test.espresso.intent.Intents import androidx.test.espresso.intent.matcher.IntentMatchers import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import com.instructure.canvas.espresso.FeatureCategory +import com.instructure.canvas.espresso.Priority import com.instructure.canvas.espresso.Stub +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData import com.instructure.canvas.espresso.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.addAssignment import com.instructure.canvas.espresso.mockCanvas.init import com.instructure.canvasapi2.models.Assignment -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -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.tokenLogin import dagger.hilt.android.testing.HiltAndroidTest @@ -70,34 +70,34 @@ class PickerSubmissionUploadInteractionTest : StudentTest() { @Stub @Test - @TestMetaData(Priority.COMMON, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, true) + @TestMetaData(Priority.COMMON, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION) fun testFab_camera() { } @Stub @Test - @TestMetaData(Priority.COMMON, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, true) + @TestMetaData(Priority.COMMON, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION) fun testFab_galleryPicker() { } @Stub @Test - @TestMetaData(Priority.COMMON, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, true) + @TestMetaData(Priority.COMMON, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION) fun testFab_filePicker() { } @Stub @Test - @TestMetaData(Priority.COMMON, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, true) + @TestMetaData(Priority.COMMON, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION) fun testDeleteFile() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION) fun testSubmit() { val data = goToSubmissionPicker() @@ -155,7 +155,7 @@ class PickerSubmissionUploadInteractionTest : StudentTest() { // Let's set up an assignment that requires an online upload val assignment = data.addAssignment( courseId = course.id, - submissionType = Assignment.SubmissionType.ONLINE_UPLOAD + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_UPLOAD) ) // Sign in diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ProfileSettingsInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ProfileSettingsInteractionTest.kt index a9fcba7951..cb58b48fd2 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ProfileSettingsInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ProfileSettingsInteractionTest.kt @@ -2,13 +2,13 @@ package com.instructure.student.ui.interaction import androidx.test.espresso.Espresso import androidx.test.rule.GrantPermissionRule +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.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.addUserPermissions import com.instructure.canvas.espresso.mockCanvas.init -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData import com.instructure.student.R import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.tokenLogin @@ -69,7 +69,7 @@ class ProfileSettingsInteractionTest : StudentTest() { // Creates a panda avatar, saves it, and verifies that a new panda avatar was saved. @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.SETTINGS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.SETTINGS, TestCategory.INTERACTION) fun testProfileSettings_createPandaAvatar() { val data = MockCanvas.init(studentCount = 1, teacherCount = 1, courseCount = 1, favoriteCourseCount = 1) val student = data.students[0] diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/QuizListInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/QuizListInteractionTest.kt index d35583483c..cd90ead0f5 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/QuizListInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/QuizListInteractionTest.kt @@ -15,15 +15,15 @@ */ package com.instructure.student.ui.interaction +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.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.addQuizToCourse import com.instructure.canvas.espresso.mockCanvas.init import com.instructure.canvasapi2.models.CourseSettings import com.instructure.canvasapi2.models.Quiz -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -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.tokenLogin import dagger.hilt.android.testing.HiltAndroidTest diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ResourcesInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ResourcesInteractionTest.kt index d4e42a2fcd..42c1233741 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ResourcesInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ResourcesInteractionTest.kt @@ -16,12 +16,16 @@ */ package com.instructure.student.ui.interaction -import com.instructure.canvas.espresso.mockCanvas.* +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.mockCanvas.MockCanvas +import com.instructure.canvas.espresso.mockCanvas.addCourseWithEnrollment +import com.instructure.canvas.espresso.mockCanvas.addEnrollment +import com.instructure.canvas.espresso.mockCanvas.addLTITool +import com.instructure.canvas.espresso.mockCanvas.init import com.instructure.canvasapi2.models.Enrollment -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData import com.instructure.student.ui.pages.ElementaryDashboardPage import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.tokenLoginElementary diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ScheduleInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ScheduleInteractionTest.kt index dc0d06eb3b..2cfe7c7cbb 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ScheduleInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ScheduleInteractionTest.kt @@ -16,7 +16,11 @@ */ package com.instructure.student.ui.interaction +import com.instructure.canvas.espresso.FeatureCategory +import com.instructure.canvas.espresso.Priority import com.instructure.canvas.espresso.StubLandscape +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData import com.instructure.canvas.espresso.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.addAssignment import com.instructure.canvas.espresso.mockCanvas.addTodo @@ -24,15 +28,11 @@ import com.instructure.canvas.espresso.mockCanvas.init import com.instructure.canvasapi2.models.Assignment import com.instructure.canvasapi2.utils.toApiString import com.instructure.espresso.page.getStringFromResource -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData import com.instructure.pandautils.utils.date.DateTimeProvider import com.instructure.student.R import com.instructure.student.ui.pages.ElementaryDashboardPage -import com.instructure.student.ui.utils.di.FakeDateTimeProvider import com.instructure.student.ui.utils.StudentTest +import com.instructure.student.ui.utils.di.FakeDateTimeProvider import com.instructure.student.ui.utils.tokenLoginElementary import dagger.hilt.android.testing.HiltAndroidTest import junit.framework.AssertionFailedError @@ -83,7 +83,7 @@ class ScheduleInteractionTest : StudentTest() { courses[0].name = "Course 1" val currentDate = dateTimeProvider.getCalendar().time.toApiString() - val assignment1 = data.addAssignment(courses[0].id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, dueAt = currentDate, name = "Assignment 1") + val assignment1 = data.addAssignment(courses[0].id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), dueAt = currentDate, name = "Assignment 1") goToScheduleTab(data) schedulePage.scrollToPosition(10) @@ -101,7 +101,7 @@ class ScheduleInteractionTest : StudentTest() { val courses = data.courses.values.filter { !it.homeroomCourse } val currentDate = dateTimeProvider.getCalendar().time.toApiString() - val assignment1 = data.addAssignment(courses[0].id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, dueAt = currentDate) + val assignment1 = data.addAssignment(courses[0].id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), dueAt = currentDate) goToScheduleTab(data) schedulePage.scrollToPosition(12) @@ -140,8 +140,8 @@ class ScheduleInteractionTest : StudentTest() { schedulePage.assertNoScheduleItemDisplayed() val currentDate = dateTimeProvider.getCalendar().time.toApiString() - val assignment1 = data.addAssignment(courses[0].id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, dueAt = currentDate) - val assignment2 = data.addAssignment(courses[0].id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, dueAt = currentDate) + val assignment1 = data.addAssignment(courses[0].id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), dueAt = currentDate) + val assignment2 = data.addAssignment(courses[0].id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), dueAt = currentDate) schedulePage.scrollToPosition(0) schedulePage.refresh() @@ -209,7 +209,7 @@ class ScheduleInteractionTest : StudentTest() { courses[0].name = "Course 1" val currentDate = dateTimeProvider.getCalendar().time.toApiString() - val assignment = data.addAssignment(courses[0].id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, dueAt = currentDate, name = "Assignment 1") + val assignment = data.addAssignment(courses[0].id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), dueAt = currentDate, name = "Assignment 1") goToScheduleTab(data) schedulePage.scrollToPosition(9) @@ -228,7 +228,7 @@ class ScheduleInteractionTest : StudentTest() { val courses = data.courses.values.filter { !it.homeroomCourse } val currentDate = dateTimeProvider.getCalendar().time.toApiString() - data.addAssignment(courses[0].id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, dueAt = currentDate) + data.addAssignment(courses[0].id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), dueAt = currentDate) goToScheduleTab(data) schedulePage.scrollToPosition(8) @@ -265,7 +265,7 @@ class ScheduleInteractionTest : StudentTest() { courses[0].name = "Course 1" val currentDate = dateTimeProvider.getCalendar().time.toApiString() - val assignment1 = data.addAssignment(courses[0].id, submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, dueAt = currentDate, name = "Assignment 1") + val assignment1 = data.addAssignment(courses[0].id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), dueAt = currentDate, name = "Assignment 1") goToScheduleTab(data) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SettingsInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SettingsInteractionTest.kt index 4238f38ec8..762fb310e1 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SettingsInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SettingsInteractionTest.kt @@ -20,15 +20,15 @@ import android.app.Instrumentation import android.content.Intent import androidx.test.espresso.intent.Intents import androidx.test.espresso.intent.matcher.IntentMatchers +import com.instructure.canvas.espresso.FeatureCategory +import com.instructure.canvas.espresso.Priority import com.instructure.canvas.espresso.StubMultiAPILevel +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData import com.instructure.canvas.espresso.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.init import com.instructure.canvasapi2.models.Course import com.instructure.canvasapi2.utils.ApiPrefs -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -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.tokenLogin import dagger.hilt.android.testing.HiltAndroidTest @@ -54,7 +54,7 @@ class SettingsInteractionTest : StudentTest() { // Should launch an intent to go to our canvas-android github page @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION) fun testLegal_showCanvasOnGithub() { setUpAndSignIn() @@ -75,7 +75,7 @@ class SettingsInteractionTest : StudentTest() { // Should display terms of use in a WebView @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION) fun testLegal_showTermsOfUse() { setUpAndSignIn() @@ -87,7 +87,7 @@ class SettingsInteractionTest : StudentTest() { // Should display the privacy policy in a WebView @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION) @StubMultiAPILevel("Failed API levels = { 28 }", "Somehow the Privacy Policy URL does not load on API lvl 28, but does on other API lvl devices.") fun testLegal_showPrivacyPolicy() { setUpAndSignIn() @@ -96,14 +96,14 @@ class SettingsInteractionTest : StudentTest() { settingsPage.openLegalPage() legalPage.openPrivacyPolicy() canvasWebViewPage.acceptCookiePolicyIfNecessary() - canvasWebViewPage.checkWebViewURL("https://www.instructure.com/canvas/privacy") + canvasWebViewPage.checkWebViewURL("https://www.instructure.com/policies/product-privacy-policy") } // Should open a page and have a pairing code that can be refreshed // (Checks to see that we can refresh and get a new code) @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION) fun testPairObserver_refreshCode() { setUpAndSignIn() @@ -117,7 +117,7 @@ class SettingsInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION) fun testOfflineContent_notDisplayedIfFeatureIsDisabled() { setUpAndSignIn(offlineEnabled = false) @@ -126,7 +126,7 @@ class SettingsInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.SETTINGS, TestCategory.INTERACTION) fun testOfflineContent_displayedIfFeatureIsEnabled() { setUpAndSignIn(offlineEnabled = true) 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 f19c657307..13ab1f1da3 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 @@ -131,7 +131,7 @@ class ShareExtensionInteractionTest : StudentTest() { val uri = setupFileOnDevice("sample.jpg") val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - val assignment = data.addAssignment(data.courses.values.first().id, submissionType = Assignment.SubmissionType.ONLINE_UPLOAD) + val assignment = data.addAssignment(data.courses.values.first().id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_UPLOAD)) login(student) device.pressHome() @@ -182,8 +182,8 @@ class ShareExtensionInteractionTest : StudentTest() { val uri = setupFileOnDevice("sample.jpg") val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - data.addAssignment(data.courses.values.first().id, submissionType = Assignment.SubmissionType.ONLINE_UPLOAD) - val assignment2 = data.addAssignment(data.courses.values.first().id, submissionType = Assignment.SubmissionType.ONLINE_UPLOAD) + data.addAssignment(data.courses.values.first().id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_UPLOAD)) + val assignment2 = data.addAssignment(data.courses.values.first().id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_UPLOAD)) login(student) device.pressHome() @@ -239,7 +239,7 @@ class ShareExtensionInteractionTest : StudentTest() { val uri = setupFileOnDevice("sample.jpg") val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - data.addAssignment(data.courses.values.first().id, submissionType = Assignment.SubmissionType.ONLINE_UPLOAD) + data.addAssignment(data.courses.values.first().id, submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_UPLOAD)) login(student) device.pressHome() 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 e7ed73b4d5..30203de91a 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 @@ -18,13 +18,24 @@ package com.instructure.student.ui.interaction import android.os.SystemClock.sleep import androidx.test.espresso.web.webdriver.Locator +import com.instructure.canvas.espresso.FeatureCategory +import com.instructure.canvas.espresso.Priority import com.instructure.canvas.espresso.Stub -import com.instructure.canvas.espresso.mockCanvas.* -import com.instructure.canvasapi2.models.* -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData +import com.instructure.canvas.espresso.mockCanvas.MockCanvas +import com.instructure.canvas.espresso.mockCanvas.addAssignment +import com.instructure.canvas.espresso.mockCanvas.addFileToCourse +import com.instructure.canvas.espresso.mockCanvas.addRubricToAssignment +import com.instructure.canvas.espresso.mockCanvas.addSubmissionForAssignment +import com.instructure.canvas.espresso.mockCanvas.init +import com.instructure.canvasapi2.models.Assignment +import com.instructure.canvasapi2.models.Attachment +import com.instructure.canvasapi2.models.Author +import com.instructure.canvasapi2.models.Course +import com.instructure.canvasapi2.models.RubricCriterion +import com.instructure.canvasapi2.models.RubricCriterionRating +import com.instructure.canvasapi2.models.SubmissionComment import com.instructure.student.ui.pages.WebViewTextCheck import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.tokenLogin @@ -41,12 +52,12 @@ class SubmissionDetailsInteractionTest : StudentTest() { // Clicking the "Description" button on a rubric criterion item should show a new page with the full description // Also checks to see that the rubric criterion is displayed correctly, and responds to clicks correctly @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION) fun testRubrics_showCriterionDescription() { val data = getToCourse() val assignment = data.addAssignment( courseId = course.id, - submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), pointsPossible = 10 ) @@ -85,13 +96,13 @@ class SubmissionDetailsInteractionTest : StudentTest() { // Should be able to add a comment on a submission @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION) fun testComments_addCommentToSingleAttemptSubmission() { val data = getToCourse() val assignment = data.addAssignment( courseId = course.id, - submissionType = Assignment.SubmissionType.ONLINE_URL + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_URL) ) courseBrowserPage.selectAssignments() @@ -113,7 +124,7 @@ class SubmissionDetailsInteractionTest : StudentTest() { val data = getToCourse() val assignment = data.addAssignment( courseId = course.id, - submissionType = Assignment.SubmissionType.ONLINE_URL, + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_URL), userSubmitted = true ) @@ -151,14 +162,14 @@ class SubmissionDetailsInteractionTest : StudentTest() { // Student can preview an assignment comment attachment @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION) fun testComments_previewAttachment() { val data = getToCourse() val user = data.users.values.first() val assignment = data.addAssignment( courseId = course.id, - submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY) ) // Some html for an attachment @@ -217,14 +228,14 @@ class SubmissionDetailsInteractionTest : StudentTest() { @Stub @Test - @TestMetaData(Priority.COMMON, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, true) + @TestMetaData(Priority.COMMON, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION) fun testComments_videoCommentPlayback() { // After recording a video comment, user should be able to view a replay } @Stub @Test - @TestMetaData(Priority.COMMON, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION, true) + @TestMetaData(Priority.COMMON, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION) fun testComments_audioCommentPlayback() { // After recording an audio comment, user should be able to hear an audio playback } diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SyllabusInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SyllabusInteractionTest.kt index 63a4f61c12..8b9b613563 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SyllabusInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SyllabusInteractionTest.kt @@ -15,6 +15,10 @@ */ package com.instructure.student.ui.interaction +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.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.addAssignment import com.instructure.canvas.espresso.mockCanvas.addCourseCalendarEvent @@ -26,10 +30,6 @@ import com.instructure.canvasapi2.models.Tab import com.instructure.dataseeding.util.days import com.instructure.dataseeding.util.fromNow import com.instructure.dataseeding.util.iso8601 -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -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.tokenLogin import dagger.hilt.android.testing.HiltAndroidTest @@ -43,7 +43,7 @@ class SyllabusInteractionTest : StudentTest() { // Tests that we can display a calendar event from the syllabus/summary, // and does some verification of the calendar event. @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.SYLLABUS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.SYLLABUS, TestCategory.INTERACTION) fun testSyllabus_calendarEvent() { val data = goToSyllabus(eventCount = 1, assignmentCount = 0) @@ -87,7 +87,7 @@ class SyllabusInteractionTest : StudentTest() { repeat(assignmentCount) { data.addAssignment( courseId = course.id, - submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), dueAt = 2.days.fromNow.iso8601 ) } diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SyncSettingsInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SyncSettingsInteractionTest.kt index 319772e5f3..2a5c54cd5f 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SyncSettingsInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/SyncSettingsInteractionTest.kt @@ -19,10 +19,10 @@ package com.instructure.student.ui.interaction import com.instructure.canvas.espresso.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.init -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData +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.pandautils.R import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.tokenLogin @@ -35,14 +35,14 @@ class SyncSettingsInteractionTest : StudentTest() { override fun displaysPageObjects() = Unit @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.SYNC_SETTINGS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.SYNC_SETTINGS, TestCategory.INTERACTION) fun testFurtherSettingsDisplayedByDefault() { goToSyncSettings() offlineSyncSettingsPage.assertFurtherSettingsIsDisplayed() } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.SYNC_SETTINGS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.SYNC_SETTINGS, TestCategory.INTERACTION) fun testClickAutoSyncHidesFurtherSettings() { goToSyncSettings() offlineSyncSettingsPage.clickAutoSyncSwitch() @@ -50,7 +50,7 @@ class SyncSettingsInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.SYNC_SETTINGS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.SYNC_SETTINGS, TestCategory.INTERACTION) fun testChangeFrequency() { goToSyncSettings() offlineSyncSettingsPage.assertSyncFrequencyLabelText(R.string.daily) @@ -60,7 +60,7 @@ class SyncSettingsInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.SYNC_SETTINGS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.SYNC_SETTINGS, TestCategory.INTERACTION) fun testChangeContentOverWifiOnly() { goToSyncSettings() offlineSyncSettingsPage.assertWifiOnlySwitchIsChecked() @@ -71,7 +71,7 @@ class SyncSettingsInteractionTest : StudentTest() { } @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.SYNC_SETTINGS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.SYNC_SETTINGS, TestCategory.INTERACTION) fun testChangesSavedCorrectly() { val data = createMockCanvas() goToSyncSettings(data) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/TodoInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/TodoInteractionTest.kt index b7129b3e7e..4e0d5abc4a 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/TodoInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/TodoInteractionTest.kt @@ -16,8 +16,12 @@ package com.instructure.student.ui.interaction import androidx.test.espresso.Espresso +import com.instructure.canvas.espresso.FeatureCategory +import com.instructure.canvas.espresso.Priority import com.instructure.canvas.espresso.StubLandscape import com.instructure.canvas.espresso.StubMultiAPILevel +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData import com.instructure.canvas.espresso.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.addAssignment import com.instructure.canvas.espresso.mockCanvas.addQuizToCourse @@ -28,10 +32,6 @@ import com.instructure.canvasapi2.models.Quiz import com.instructure.dataseeding.util.days import com.instructure.dataseeding.util.fromNow import com.instructure.dataseeding.util.iso8601 -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -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.tokenLogin import dagger.hilt.android.testing.HiltAndroidTest @@ -47,7 +47,7 @@ class TodoInteractionTest : StudentTest() { // Todo items should be clickable @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.TODOS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.MANDATORY, FeatureCategory.TODOS, TestCategory.INTERACTION) fun testClick_todoItemClickable() { val data = goToTodos() @@ -67,7 +67,7 @@ class TodoInteractionTest : StudentTest() { @Test @StubLandscape("Stubbed because on lowres device in landscape mode, the space is too narrow to scroll properly. Will be refactored and running when we changed to non-lowres device on nightly runs.") @StubMultiAPILevel("Somehow the 'OK' button within chooseFavoriteCourseFilter row is not clickable and not shown on the layout inspector as well.") - @TestMetaData(Priority.IMPORTANT, FeatureCategory.TODOS, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.TODOS, TestCategory.INTERACTION) fun testFilters() { val data = goToTodos(courseCount = 2, favoriteCourseCount = 1) val favoriteCourse = data.courses.values.first {course -> course.isFavorite} @@ -104,7 +104,7 @@ class TodoInteractionTest : StudentTest() { for(course in data.courses.values) { assignment = data.addAssignment( courseId = course.id, - submissionType = Assignment.SubmissionType.ONLINE_TEXT_ENTRY, + submissionTypeList = listOf(Assignment.SubmissionType.ONLINE_TEXT_ENTRY), dueAt = 1.days.fromNow.iso8601 ) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/UserFilesInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/UserFilesInteractionTest.kt index 80dbaf73ae..2e579c8a33 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/UserFilesInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/UserFilesInteractionTest.kt @@ -30,10 +30,10 @@ import androidx.test.rule.GrantPermissionRule import com.instructure.canvas.espresso.Stub import com.instructure.canvas.espresso.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.init -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData +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.pandautils.utils.Const import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.tokenLogin @@ -91,7 +91,7 @@ class UserFilesInteractionTest : StudentTest() { // Should be able to upload a file from the user's device // Mocks the result from the expected intent, then uploads it. @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.FILES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.FILES, TestCategory.INTERACTION) fun testUpload_deviceFile() { goToFilePicker() @@ -120,7 +120,7 @@ class UserFilesInteractionTest : StudentTest() { // Should be able to upload a file from the camera // Mocks the result from the expected intent, then uploads it. @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.FILES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.FILES, TestCategory.INTERACTION) fun testUpload_fileFromCamera() { goToFilePicker() @@ -161,7 +161,7 @@ class UserFilesInteractionTest : StudentTest() { // Should be able to upload a file from the user's photo gallery // Mocks the result from the expected intent, then uploads it. @Test - @TestMetaData(Priority.IMPORTANT, FeatureCategory.FILES, TestCategory.INTERACTION, false) + @TestMetaData(Priority.IMPORTANT, FeatureCategory.FILES, TestCategory.INTERACTION) fun testUpload_gallery() { goToFilePicker() @@ -191,28 +191,28 @@ class UserFilesInteractionTest : StudentTest() { @Stub @Test - @TestMetaData(Priority.COMMON, FeatureCategory.FILES, TestCategory.INTERACTION, true) + @TestMetaData(Priority.COMMON, FeatureCategory.FILES, TestCategory.INTERACTION) fun testView_previewAudio() { // Should be able to preview an audio file } @Stub @Test - @TestMetaData(Priority.COMMON, FeatureCategory.FILES, TestCategory.INTERACTION, true) + @TestMetaData(Priority.COMMON, FeatureCategory.FILES, TestCategory.INTERACTION) fun testView_previewVideo() { // Should be able to preview a video file } @Stub @Test - @TestMetaData(Priority.COMMON, FeatureCategory.FILES, TestCategory.INTERACTION, true) + @TestMetaData(Priority.COMMON, FeatureCategory.FILES, TestCategory.INTERACTION) fun testView_createDirectory() { // Should be able to create a directory and upload a file to that directory } @Stub @Test - @TestMetaData(Priority.COMMON, FeatureCategory.FILES, TestCategory.INTERACTION, true) + @TestMetaData(Priority.COMMON, FeatureCategory.FILES, TestCategory.INTERACTION) fun testView_previewImage() { // Should be able to preview an image file } diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/EditDashboardPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/AllCoursesPage.kt similarity index 62% rename from apps/student/src/androidTest/java/com/instructure/student/ui/pages/EditDashboardPage.kt rename to apps/student/src/androidTest/java/com/instructure/student/ui/pages/AllCoursesPage.kt index c4bd4d94cf..d14942bad9 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/EditDashboardPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/AllCoursesPage.kt @@ -16,25 +16,31 @@ package com.instructure.student.ui.pages -import androidx.test.espresso.matcher.ViewMatchers.hasDescendant -import androidx.test.espresso.matcher.ViewMatchers.withContentDescription -import androidx.test.espresso.matcher.ViewMatchers.withParent -import androidx.test.espresso.matcher.ViewMatchers.withText +import android.view.View +import androidx.appcompat.widget.AppCompatImageView +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.* import com.instructure.canvasapi2.models.Course +import com.instructure.dataseeding.model.CourseApiModel +import com.instructure.espresso.DoesNotExistAssertion +import com.instructure.espresso.ViewAlphaAssertion 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.plus +import com.instructure.espresso.page.waitForView import com.instructure.espresso.page.withId import com.instructure.espresso.page.withText import com.instructure.espresso.scrollTo import com.instructure.espresso.swipeUp import com.instructure.student.R +import org.hamcrest.Matcher import org.hamcrest.Matchers.allOf +import org.hamcrest.Matchers.anyOf import org.hamcrest.Matchers.containsString -class EditDashboardPage : BasePage(R.id.editDashboardPage) { +class AllCoursesPage : BasePage(R.id.editDashboardPage) { fun assertCourseDisplayed(course: Course) { val itemMatcher = allOf(withText(containsString(course.name)), withId(R.id.title)) @@ -102,6 +108,14 @@ class EditDashboardPage : BasePage(R.id.editDashboardPage) { onView(itemMatcher).assertDisplayed() } + fun assertCourseFavorited(course: CourseApiModel) { + val childMatcher = withContentDescription("Remove from dashboard") + val itemMatcher = allOf( + withContentDescription(containsString("Course ${course.name}, favorite")), + hasDescendant(childMatcher)) + onView(itemMatcher).assertDisplayed() + } + fun selectAllCourses() { val childMatcher = withContentDescription("Add all to dashboard") val itemMatcher = allOf(hasDescendant(withText(R.string.allCoursesCourseHeader)), hasDescendant(childMatcher)) @@ -116,6 +130,17 @@ class EditDashboardPage : BasePage(R.id.editDashboardPage) { onView(withParent(itemMatcher) + childMatcher).click() } + fun openCourse(courseName: String) { + onView(withId(R.id.title) + withText(courseName)).click() + } + + //OfflineMethod + fun assertSelectUnselectAllButtonNotClickable() { + val unselectAllMatcher = withContentDescription("Remove all from dashboard") + val selectAllMatcher = withContentDescription("Add all to dashboard") + onView(anyOf(unselectAllMatcher, selectAllMatcher)).check(matches(isNotClickable())) + } + fun assertCourseMassSelectButtonIsDisplayed(someSelected: Boolean) { if (someSelected) { @@ -149,4 +174,54 @@ class EditDashboardPage : BasePage(R.id.editDashboardPage) { onView(withId(R.id.swipeRefreshLayout) + withParent(withId(R.id.editDashboardPage))).swipeUp() } + //OfflineMethod + fun assertOfflineNoteDisplayed() { + waitForView(withId(R.id.noteTitle) + withText(R.string.allCoursesOfflineNoteTitle)).assertDisplayed() + onView(withId(R.id.note) + withText(R.string.allCoursesOfflineNote)) + } + + //OfflineMethod + fun assertOfflineNoteNotDisplayed() { + onView(withId(R.id.noteTitle) + withText(R.string.allCoursesOfflineNoteTitle)).check(DoesNotExistAssertion(10)) + } + + //OfflineMethod + fun dismissOfflineNoteBox() { + onView(allOf(isAssignableFrom(AppCompatImageView::class.java), hasSibling(withId(R.id.noteTitle)), hasSibling(withId(R.id.note)))).click() + onView(withId(R.id.noteTitle) + withText(R.string.allCoursesOfflineNoteTitle)).check(DoesNotExistAssertion(10)) + } + + //OfflineMethod + private fun assertViewAlpha(matcher: Matcher, expectedAlphaValue: Float) { + onView(matcher).check(ViewAlphaAssertion(expectedAlphaValue)) + } + + //OfflineMethod + fun assertCourseFavouriteStarAlpha(courseName: String, expectedAlphaValue: Float) { + assertViewAlpha(withId(R.id.favoriteButton) + hasSibling(withId(R.id.title) + withText(courseName)), expectedAlphaValue) + } + + //OfflineMethod + fun assertCourseTitleAlpha(courseName: String, expectedAlphaValue: Float) { + assertViewAlpha(withId(R.id.title) + withText(courseName), expectedAlphaValue) + } + + //OfflineMethod + fun assertCourseOpenButtonAlpha(courseName: String, expectedAlphaValue: Float) { + assertViewAlpha(withId(R.id.openButton) + hasSibling(withId(R.id.title) + withText(courseName)), expectedAlphaValue) + } + + //OfflineMethod + fun assertCourseDetailsAlpha(courseName: String, expectedAlphaValue: Float) { + assertCourseFavouriteStarAlpha(courseName, expectedAlphaValue) + assertCourseTitleAlpha(courseName, expectedAlphaValue) + assertCourseOpenButtonAlpha(courseName, expectedAlphaValue) + } + + //OfflineMethod + fun assertCourseOfflineSyncButton(courseName: String, visibility: Visibility) { + onView(withId(R.id.offlineSyncIcon) + hasSibling(withId(R.id.title) + withText(courseName))) + .check(matches(withEffectiveVisibility(visibility))) + } + } \ No newline at end of file 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 5164267d7f..5ac189a0f8 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 @@ -32,6 +32,7 @@ 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.dataseeding.model.SubmissionType import com.instructure.espresso.OnViewWithId import com.instructure.espresso.assertContainsText import com.instructure.espresso.assertDisplayed @@ -221,6 +222,23 @@ open class AssignmentDetailsPage : BasePage(R.id.assignmentDetailsPage) { waitForView(allOf(withId(R.id.attemptTitle), withAncestor(withId(R.id.attemptSpinner)))).assertDisplayed() waitForView(allOf(withId(R.id.attemptDate), withAncestor(withId(R.id.attemptSpinner)))).assertDisplayed() } + + fun selectSubmissionType(submissionType: SubmissionType) { + val viewMatcher = when (submissionType) { + SubmissionType.ONLINE_TEXT_ENTRY -> withId(R.id.submissionEntryText) + SubmissionType.ONLINE_UPLOAD -> withId(R.id.submissionEntryFile) + SubmissionType.ONLINE_URL -> withId(R.id.submissionEntryWebsite) + SubmissionType.MEDIA_RECORDING -> withId(R.id.submissionEntryMedia) + + else -> {withId(R.id.submissionEntryText)} + } + + onView(viewMatcher).click() + } + + fun assertSubmissionTypeDisplayed(submissionType: String) { + onView(withText(submissionType) + withAncestor(R.id.customPanel)).assertDisplayed() + } } /** diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CourseBrowserPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CourseBrowserPage.kt index d3a9a95924..87cbc0de71 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CourseBrowserPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CourseBrowserPage.kt @@ -17,12 +17,15 @@ package com.instructure.student.ui.pages import android.view.View +import android.widget.LinearLayout +import androidx.constraintlayout.widget.ConstraintLayout import androidx.test.espresso.Espresso.onView import androidx.test.espresso.PerformException import androidx.test.espresso.UiController import androidx.test.espresso.ViewAction import androidx.test.espresso.action.ViewActions import androidx.test.espresso.assertion.ViewAssertions.doesNotExist +import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.* import com.instructure.canvas.espresso.scrollRecyclerView import com.instructure.canvas.espresso.withCustomConstraints @@ -34,11 +37,15 @@ import com.instructure.espresso.WaitForViewWithId import com.instructure.espresso.assertHasText import com.instructure.espresso.click import com.instructure.espresso.page.BasePage +import com.instructure.espresso.page.plus +import com.instructure.espresso.scrollTo import com.instructure.espresso.swipeUp import com.instructure.pandautils.views.SwipeRefreshLayoutAppBar import com.instructure.student.R import org.hamcrest.Matcher import org.hamcrest.Matchers.allOf +import org.hamcrest.Matchers.anyOf +import org.hamcrest.Matchers.not open class CourseBrowserPage : BasePage(R.id.courseBrowserPage) { @@ -140,6 +147,15 @@ open class CourseBrowserPage : BasePage(R.id.courseBrowserPage) { onView(allOf(withText(tabTitle), withId(R.id.label))).check(doesNotExist()) } + //OfflineMethod + fun assertTabDisabled(tabTitle: String) { + onView(allOf(anyOf(isAssignableFrom(LinearLayout::class.java), isAssignableFrom(ConstraintLayout::class.java)), withChild(anyOf(withId(R.id.label), withId(R.id.unsupportedLabel)) + withText(tabTitle)))).scrollTo().check(matches(not(isEnabled()))) + } + + fun assertTabEnabled(tabTitle: String) { + onView(allOf(anyOf(isAssignableFrom(LinearLayout::class.java), isAssignableFrom(ConstraintLayout::class.java)), withChild(anyOf(withId(R.id.label), withId(R.id.unsupportedLabel)) + withText(tabTitle)))).scrollTo().check(matches(isEnabled())) + } + // Minimizes toolbar if it is not already minimized private fun minimizeToolbar() { try { 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 78121a4c7e..18971f74a5 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 @@ -38,6 +38,7 @@ import com.instructure.canvasapi2.models.Group import com.instructure.dataseeding.model.CourseApiModel import com.instructure.dataseeding.model.GroupApiModel import com.instructure.espresso.* +import com.instructure.espresso.matchers.WaitForViewMatcher.waitForViewToBeCompletelyDisplayed import com.instructure.espresso.page.* import com.instructure.student.R import com.instructure.student.ui.utils.ViewUtils @@ -146,6 +147,10 @@ class DashboardPage : BasePage(R.id.dashboardPage) { onView(hamburgerButtonMatcher).waitForCheck(matches(isDisplayed())) } + fun openLeftSideMenu() { + onView(hamburgerButtonMatcher).click() + } + private fun scrollAndAssertDisplayed(matcher: Matcher) { // Arggghhh... This scrolling logic on the recycler view is really unreliable and seems // to fail for nonsensical reasons. For now, "scrollAndAssertDisplayed"" is just going to @@ -255,20 +260,24 @@ class DashboardPage : BasePage(R.id.dashboardPage) { } fun switchCourseView() { - Espresso.openActionBarOverflowOrOptionsMenu(InstrumentationRegistry.getInstrumentation().targetContext) + clickDashboardGlobalOverflowButton() onView(withText(containsString("Switch to"))) .perform(click()); } - //OfflineMethod fun openGlobalManageOfflineContentPage() { - Espresso.openActionBarOverflowOrOptionsMenu(InstrumentationRegistry.getInstrumentation().targetContext) - onView(withText(containsString("Manage Offline Content"))) + clickDashboardGlobalOverflowButton() + onView(withText(containsString("Manage Offline Content"))) .perform(click()); } - fun clickEditDashboard() { - onView(withId(R.id.editDashboardTextView)).scrollTo().click() + private fun clickDashboardGlobalOverflowButton() { + waitForViewToBeCompletelyDisplayed(withContentDescription("More options") + withAncestor(R.id.toolbar)) + Espresso.openActionBarOverflowOrOptionsMenu(InstrumentationRegistry.getInstrumentation().targetContext) + } + + fun openAllCoursesPage() { + waitForView(withId(R.id.editDashboardTextView)).scrollTo().click() } fun assertCourseNotDisplayed(course: CourseApiModel) { @@ -304,12 +313,16 @@ class DashboardPage : BasePage(R.id.dashboardPage) { } fun clickCourseOverflowMenu(courseTitle: String, menuTitle: String) { + clickOnCourseOverflowButton(courseTitle) + waitForView(withId(R.id.title) + withText(menuTitle)).click() + } + + fun clickOnCourseOverflowButton(courseTitle: String) { val courseOverflowMatcher = withId(R.id.overflow) + withAncestor( withId(R.id.cardView) + withDescendant(withId(R.id.titleTextView) + withText(courseTitle)) ) waitForView(courseOverflowMatcher).scrollTo().click() - waitForView(withId(R.id.title) + withText(menuTitle)).click() } fun assertCourseGrade(courseName: String, courseGrade: String) { @@ -345,6 +358,22 @@ class DashboardPage : BasePage(R.id.dashboardPage) { onView(withId(R.id.offlineIndicator)).check(matches(withEffectiveVisibility(Visibility.GONE))) } + //OfflineMethod + fun waitForNetworkComeBack() { + assertDisplaysCourses() + retry(times = 5, delay = 2000) { + assertOfflineIndicatorNotDisplayed() + } + } + + //OfflineMethod + fun waitForNetworkOff() { + assertDisplaysCourses() + retry(times = 5, delay = 2000) { + assertOfflineIndicatorDisplayed() + } + } + //OfflineMethod fun assertCourseOfflineSyncIconVisible(courseName: String) { waitForView(withId(R.id.offlineSyncIcon) + hasSibling(withId(R.id.titleTextView) + withText(courseName))).check(matches(withEffectiveVisibility(Visibility.VISIBLE))) @@ -379,6 +408,14 @@ class DashboardPage : BasePage(R.id.dashboardPage) { fun waitForSyncProgressStartingNotificationToDisappear() { ViewUtils.waitForViewToDisappear(withText(com.instructure.pandautils.R.string.syncProgress_syncingOfflineContent), 30) } + + //OfflineMethod + fun assertBottomMenusAreDisabled() { + onView(withId(R.id.bottomNavigationCalendar)).check(matches(isNotEnabled())) + onView(withId(R.id.bottomNavigationToDo)).check(matches(isNotEnabled())) + onView(withId(R.id.bottomNavigationNotifications)).check(matches(isNotEnabled())) + onView(withId(R.id.bottomNavigationInbox)).check(matches(isNotEnabled())) + } } /** diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/FileListPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/FileListPage.kt index 1a5b4edd07..786d1e2406 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/FileListPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/FileListPage.kt @@ -20,9 +20,7 @@ import androidx.appcompat.widget.AppCompatButton 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.* import com.instructure.canvas.espresso.containsTextCaseInsensitive import com.instructure.canvas.espresso.scrollRecyclerView @@ -58,6 +56,11 @@ class FileListPage(val searchable: Searchable) : BasePage(R.id.fileListPage) { waitForView(matcher).scrollTo().assertDisplayed() } + fun assertSearchItemDisplayed(itemName: String) { + val matcher = allOf(withId(R.id.fileName), withAncestor(R.id.fileSearchRecyclerView), withText(itemName)) + waitForView(matcher).scrollTo().assertDisplayed() + } + fun assertItemNotDisplayed(itemName: String) { val matcher = allOf(withId(R.id.fileName), withText(itemName)) onView(matcher).assertNotDisplayed() @@ -127,14 +130,14 @@ class FileListPage(val searchable: Searchable) : BasePage(R.id.fileListPage) { fun assertSearchResultCount(expectedCount: Int) { Thread.sleep(2000) onView(withId(R.id.fileSearchRecyclerView) + withAncestor(R.id.container)).check( - ViewAssertions.matches(ViewMatchers.hasChildCount(expectedCount)) + matches(hasChildCount(expectedCount)) ) } fun assertFileListCount(expectedCount: Int) { Thread.sleep(2000) - onView(withId(R.id.listView) + withAncestor(R.id.container)).check( - ViewAssertions.matches(ViewMatchers.hasChildCount(expectedCount)) + onView(withId(R.id.listView) + withAncestor(R.id.fileListPage)).check( + matches(hasChildCount(expectedCount)) ) } diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/InboxPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/InboxPage.kt index 47ac688d03..8626f8d2a0 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/InboxPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/InboxPage.kt @@ -145,7 +145,6 @@ class InboxPage : BasePage(R.id.inboxPage) { hasSibling(allOf(withId(R.id.subjectView), withText(subject)))) waitForMatcherWithRefreshes(matcher) // May need to refresh before the star shows up onView(matcher).scrollTo().assertDisplayed() - } fun assertConversationNotStarred(subject: String) { @@ -154,9 +153,7 @@ class InboxPage : BasePage(R.id.inboxPage) { hasSibling(withId(R.id.userName)), hasSibling(withId(R.id.date)), hasSibling(allOf(withId(R.id.subjectView), withText(subject)))) - waitForMatcherWithRefreshes(matcher) // May need to refresh before the star shows up onView(matcher).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))) - } fun assertUnreadMarkerVisibility(conversation: Conversation, visibility: ViewMatchers.Visibility) { @@ -192,7 +189,7 @@ class InboxPage : BasePage(R.id.inboxPage) { } } - fun assertInboxEmpty() { + fun assertInboxEmpty() { waitForView(withId(R.id.emptyInboxView)).assertDisplayed() } 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 da39b4cefe..0fa4f1c98e 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 @@ -12,8 +12,22 @@ import com.instructure.canvas.espresso.CanvasTest 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.page.* +import com.instructure.espresso.OnViewWithContentDescription +import com.instructure.espresso.OnViewWithId +import com.instructure.espresso.ViewAlphaAssertion +import com.instructure.espresso.assertDisplayed +import com.instructure.espresso.assertNotDisplayed +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.waitForView +import com.instructure.espresso.page.waitForViewWithId +import com.instructure.espresso.page.withId +import com.instructure.espresso.scrollTo +import com.instructure.espresso.swipeDown +import com.instructure.espresso.swipeUp import com.instructure.student.R import org.hamcrest.CoreMatchers import org.hamcrest.Matcher @@ -86,6 +100,10 @@ class LeftSideNavigationDrawerPage : BasePage() { clickMenu(R.id.navigationDrawerItem_bookmarks) } + fun clickStudioMenu() { + clickMenu(R.id.navigationDrawerItem_studio) + } + fun clickSettingsMenu() { clickMenu(R.id.navigationDrawerSettings) } @@ -181,6 +199,73 @@ class LeftSideNavigationDrawerPage : BasePage() { logoutButton.assertDisplayed() } + //OfflineMethod + private fun assertViewAlpha(matcher: Matcher, expectedAlphaValue: Float) { + onView(matcher).check(ViewAlphaAssertion(expectedAlphaValue)) + } + + //OfflineMethod + private fun assertFilesMenuAlphaValue(expectedAlphaValue: Float) { + assertViewAlpha(withId(R.id.navigationDrawerItem_files), expectedAlphaValue) + } + + //OfflineMethod + private fun assertBookmarksMenuAlphaValue(expectedAlphaValue: Float) { + assertViewAlpha(withId(R.id.navigationDrawerItem_bookmarks), expectedAlphaValue) + } + + //OfflineMethod + private fun assertStudioMenuAlphaValue(expectedAlphaValue: Float) { + assertViewAlpha(withId(R.id.navigationDrawerItem_studio), expectedAlphaValue) + } + + //OfflineMethod + private fun assertSettingsMenuAlphaValue(expectedAlphaValue: Float) { + assertViewAlpha(withId(R.id.navigationDrawerSettings), expectedAlphaValue) + } + + //OfflineMethod + private fun assertShowGradesMenuAlphaValue(expectedAlphaValue: Float) { + assertViewAlpha(withId(R.id.navigationDrawerItem_showGrades), expectedAlphaValue) + } + + //OfflineMethod + private fun assertColorOverlayMenuAlphaValue(expectedAlphaValue: Float) { + assertViewAlpha(withId(R.id.navigationDrawerItem_colorOverlay), expectedAlphaValue) + } + + //OfflineMethod + private fun assertHelpMenuAlphaValue(expectedAlphaValue: Float) { + assertViewAlpha(withId(R.id.navigationDrawerItem_help), expectedAlphaValue) + } + + //OfflineMethod + private fun assertChangeUserMenuAlphaValue(expectedAlphaValue: Float) { + assertViewAlpha(withId(R.id.navigationDrawerItem_changeUser), expectedAlphaValue) + } + + //OfflineMethod + private fun assertLogoutMenuAlphaValue(expectedAlphaValue: Float) { + assertViewAlpha(withId(R.id.navigationDrawerItem_logout), expectedAlphaValue) + } + + //OfflineMethod + fun assertOfflineDisabledMenus(expectedAlphaValue: Float) { + assertFilesMenuAlphaValue(expectedAlphaValue) + assertBookmarksMenuAlphaValue(expectedAlphaValue) + assertStudioMenuAlphaValue(expectedAlphaValue) + assertColorOverlayMenuAlphaValue(expectedAlphaValue) + assertHelpMenuAlphaValue(expectedAlphaValue) + } + + //OfflineMethod + fun assertOfflineEnabledMenus(expectedAlphaValue: Float) { + assertSettingsMenuAlphaValue(expectedAlphaValue) + assertShowGradesMenuAlphaValue(expectedAlphaValue) + assertChangeUserMenuAlphaValue(expectedAlphaValue) + assertLogoutMenuAlphaValue(expectedAlphaValue) + } + /** * Custom ViewAction to set a SwitchCompat to the desired on/off position * [position]: true -> "on", false -> "off" diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/PersonDetailsPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/PersonDetailsPage.kt index f5458663e5..6a73ea9746 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/PersonDetailsPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/PersonDetailsPage.kt @@ -16,12 +16,18 @@ */ package com.instructure.student.ui.pages +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility import com.instructure.canvasapi2.models.User import com.instructure.espresso.OnViewWithId import com.instructure.espresso.assertContainsText import com.instructure.espresso.click import com.instructure.espresso.page.BasePage +import com.instructure.espresso.page.onView +import com.instructure.espresso.page.withId import com.instructure.student.R +import org.hamcrest.Matchers class PersonDetailsPage: BasePage(R.id.clickContainer) { @@ -37,4 +43,9 @@ class PersonDetailsPage: BasePage(R.id.clickContainer) { fun assertIsPerson(user: User) { userName.assertContainsText(user.name) } + + //OfflineMethod + fun assertComposeMessageIcon(visibility: ViewMatchers.Visibility) { + onView(Matchers.allOf(withId(R.id.compose))).check(matches(withEffectiveVisibility(visibility))) + } } \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/offline/ManageOfflineContentPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/offline/ManageOfflineContentPage.kt index 383f9fe2df..a64ec38b56 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/offline/ManageOfflineContentPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/offline/ManageOfflineContentPage.kt @@ -22,6 +22,8 @@ import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.Visibility import androidx.test.espresso.matcher.ViewMatchers.hasSibling import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility +import androidx.test.espresso.contrib.RecyclerViewActions +import androidx.test.espresso.matcher.ViewMatchers.* import com.instructure.canvas.espresso.containsTextCaseInsensitive import com.instructure.canvas.espresso.hasCheckedState import com.instructure.canvas.espresso.withRotation @@ -44,6 +46,7 @@ import com.instructure.espresso.page.withParent import com.instructure.espresso.page.withText import com.instructure.espresso.scrollTo import com.instructure.pandautils.R +import com.instructure.pandautils.binding.BindableViewHolder import org.hamcrest.CoreMatchers.allOf class ManageOfflineContentPage : BasePage(R.id.manageOfflineContentPage) { @@ -52,11 +55,13 @@ class ManageOfflineContentPage : BasePage(R.id.manageOfflineContentPage) { private val storageInfoContainer by WaitForViewWithId(R.id.storageInfoContainer) fun changeItemSelectionState(itemName: String) { + onView(withId(R.id.offlineContentRecyclerView)) + .perform(RecyclerViewActions.scrollTo(hasDescendant(withText(itemName)))) onView(withId(R.id.checkbox) + hasSibling(withId(R.id.title) + withText(itemName))).scrollTo().click() } fun expandCollapseItem(itemName: String) { - onView(withId(R.id.arrow) + withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE) + hasSibling(withId(R.id.title) + withText(itemName))).scrollTo().perform(ForceClick()) + onView(withId(R.id.arrow) + withEffectiveVisibility(Visibility.VISIBLE) + hasSibling(withId(R.id.title) + withText(itemName))).scrollTo().perform(ForceClick()) } fun expandCollapseFiles() { @@ -90,7 +95,7 @@ class ManageOfflineContentPage : BasePage(R.id.manageOfflineContentPage) { } fun assertSelectButtonText(selectAll: Boolean) { - if(selectAll) waitForView(withId(R.id.menu_select_all) + withText(R.string.offline_content_select_all)).assertDisplayed() + if (selectAll) waitForView(withId(R.id.menu_select_all) + withText(R.string.offline_content_select_all)).assertDisplayed() else waitForView(withId(R.id.menu_select_all) + withText(R.string.offline_content_deselect_all)).assertDisplayed() } @@ -103,11 +108,11 @@ class ManageOfflineContentPage : BasePage(R.id.manageOfflineContentPage) { } fun assertCourseCountWithMatcher(expectedCount: Int) { - ConstraintLayoutItemCountAssertionWithMatcher((allOf(withId(R.id.arrow), withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))), expectedCount) + ConstraintLayoutItemCountAssertionWithMatcher((allOf(withId(R.id.arrow), withEffectiveVisibility(Visibility.VISIBLE))), expectedCount) } fun assertCourseCount(expectedCount: Int) { - onView((allOf(withId(R.id.arrow), withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))).check(ConstraintLayoutItemCountAssertion(expectedCount)) + onView((allOf(withId(R.id.arrow), withEffectiveVisibility(Visibility.VISIBLE)))).check(ConstraintLayoutItemCountAssertion(expectedCount)) } fun assertToolbarTexts(courseName: String) { @@ -116,11 +121,14 @@ class ManageOfflineContentPage : BasePage(R.id.manageOfflineContentPage) { } fun assertCheckedStateOfItem(itemName: String, state: Int) { - onView(withId(R.id.checkbox) + hasSibling(withId(R.id.title) + withText(itemName)) + hasCheckedState(state)).scrollTo().assertDisplayed() + val matcher = withId(R.id.checkbox) + hasSibling(withId(R.id.title) + withText(itemName)) + hasCheckedState(state) + onView(withId(R.id.offlineContentRecyclerView)) + .perform(RecyclerViewActions.scrollTo(hasDescendant(withText(itemName)))) + onView(matcher).scrollTo().assertDisplayed() } fun waitForItemDisappear(itemName: String) { - onView(withId(R.id.checkbox) + hasSibling(withId(R.id.title) + withText(itemName))).check(DoesNotExistAssertion(5)) + waitForView(withId(R.id.checkbox) + hasSibling(withId(R.id.title) + withText(itemName))).check(DoesNotExistAssertion(5)) } fun assertDisplaysNoCourses() { @@ -128,7 +136,7 @@ class ManageOfflineContentPage : BasePage(R.id.manageOfflineContentPage) { } fun assertDisplaysEmptyCourse() { - Espresso.onView(ViewMatchers.withText(R.string.offline_content_empty_course_message)).assertDisplayed() + Espresso.onView(ViewMatchers.withText(R.string.offline_content_empty_course_message)).scrollTo().assertDisplayed() } fun assertDisplaysItemWithExpandedState(title: String, expanded: Boolean) { @@ -141,7 +149,10 @@ class ManageOfflineContentPage : BasePage(R.id.manageOfflineContentPage) { } fun assertItemDisplayed(title: String) { - Espresso.onView(ViewMatchers.withId(R.id.title) + ViewMatchers.withText(title)).scrollTo().assertDisplayed() + val matcher = withId(R.id.title) + withText(title) + onView(withId(R.id.offlineContentRecyclerView)) + .perform(RecyclerViewActions.scrollTo(hasDescendant(matcher))) + onView(matcher).scrollTo().assertDisplayed() } fun assertDiscardDialogDisplayed() { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/DiscussionSubmissionViewRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/DiscussionSubmissionViewRenderTest.kt index aa025108c2..b76a80cf54 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/DiscussionSubmissionViewRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/DiscussionSubmissionViewRenderTest.kt @@ -15,10 +15,10 @@ */ package com.instructure.student.ui.renderTests -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData +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.student.espresso.StudentRenderTest import com.instructure.student.mobius.assignmentDetails.submissionDetails.content.DiscussionSubmissionViewFragment import com.instructure.student.ui.pages.renderPages.DiscussionSubmissionViewRenderPage diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/PickerSubmissionUploadRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/PickerSubmissionUploadRenderTest.kt index bc0a772bb7..b1a7a71639 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/PickerSubmissionUploadRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/PickerSubmissionUploadRenderTest.kt @@ -18,16 +18,16 @@ package com.instructure.student.ui.renderTests import androidx.test.espresso.assertion.ViewAssertions.doesNotExist import androidx.test.ext.junit.runners.AndroidJUnit4 +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.canvasapi2.models.Assignment import com.instructure.canvasapi2.models.Course import com.instructure.espresso.assertDisplayed import com.instructure.espresso.assertHasText import com.instructure.espresso.assertNotDisplayed import com.instructure.espresso.assertVisible -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData import com.instructure.student.R import com.instructure.student.espresso.StudentRenderTest import com.instructure.student.mobius.assignmentDetails.submission.picker.PickerSubmissionMode diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionCommentsRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionCommentsRenderTest.kt index 7f1ec29f08..c3ddebf0ee 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionCommentsRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/SubmissionCommentsRenderTest.kt @@ -18,11 +18,15 @@ package com.instructure.student.ui.renderTests import androidx.test.core.app.ApplicationProvider import androidx.test.espresso.Espresso import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.instructure.canvas.espresso.FeatureCategory +import com.instructure.canvas.espresso.Priority +import com.instructure.canvas.espresso.SecondaryFeatureCategory +import com.instructure.canvas.espresso.TestCategory +import com.instructure.canvas.espresso.TestMetaData import com.instructure.canvasapi2.models.Assignment import com.instructure.canvasapi2.models.CanvasContext import com.instructure.canvasapi2.models.Submission import com.instructure.canvasapi2.models.User -import com.instructure.panda_annotations.* import com.instructure.student.PendingSubmissionComment import com.instructure.student.db.Db import com.instructure.student.db.getInstance @@ -131,7 +135,10 @@ class SubmissionCommentsRenderTest: StudentRenderTest() { } @Test - @TestMetaData(Priority.COMMON,FeatureCategory.ASSIGNMENTS,TestCategory.RENDER,secondaryFeature = SecondaryFeatureCategory.ASSIGNMENT_COMMENTS) + @TestMetaData( + Priority.COMMON, + FeatureCategory.ASSIGNMENTS, + TestCategory.RENDER,secondaryFeature = SecondaryFeatureCategory.ASSIGNMENT_COMMENTS) fun testSingleComment() { val state = SubmissionCommentsViewState( commentStates = listOf(commentItemIsAudience), @@ -143,7 +150,10 @@ class SubmissionCommentsRenderTest: StudentRenderTest() { } @Test - @TestMetaData(Priority.COMMON,FeatureCategory.ASSIGNMENTS,TestCategory.RENDER,secondaryFeature = SecondaryFeatureCategory.ASSIGNMENT_COMMENTS) + @TestMetaData( + Priority.COMMON, + FeatureCategory.ASSIGNMENTS, + TestCategory.RENDER,secondaryFeature = SecondaryFeatureCategory.ASSIGNMENT_COMMENTS) fun testSingleSubmission() { val state = SubmissionCommentsViewState( commentStates = listOf(submissionItem) @@ -153,7 +163,10 @@ class SubmissionCommentsRenderTest: StudentRenderTest() { } @Test - @TestMetaData(Priority.COMMON,FeatureCategory.ASSIGNMENTS,TestCategory.RENDER,secondaryFeature = SecondaryFeatureCategory.ASSIGNMENT_COMMENTS) + @TestMetaData( + Priority.COMMON, + FeatureCategory.ASSIGNMENTS, + TestCategory.RENDER,secondaryFeature = SecondaryFeatureCategory.ASSIGNMENT_COMMENTS) fun testSinglePendingComment() { val state = SubmissionCommentsViewState( commentStates = listOf(pendingCommentItem) @@ -163,7 +176,10 @@ class SubmissionCommentsRenderTest: StudentRenderTest() { } @Test - @TestMetaData(Priority.COMMON,FeatureCategory.ASSIGNMENTS,TestCategory.RENDER,secondaryFeature = SecondaryFeatureCategory.ASSIGNMENT_COMMENTS) + @TestMetaData( + Priority.COMMON, + FeatureCategory.ASSIGNMENTS, + TestCategory.RENDER,secondaryFeature = SecondaryFeatureCategory.ASSIGNMENT_COMMENTS) fun testSingleCommentDisplaysAuthorPronoun() { val commentItem = commentItemIsAudience.copy(authorPronouns = "Pro/Noun") val state = SubmissionCommentsViewState(commentStates = listOf(commentItem)) @@ -172,7 +188,10 @@ class SubmissionCommentsRenderTest: StudentRenderTest() { } @Test - @TestMetaData(Priority.COMMON,FeatureCategory.ASSIGNMENTS,TestCategory.RENDER,secondaryFeature = SecondaryFeatureCategory.ASSIGNMENT_COMMENTS) + @TestMetaData( + Priority.COMMON, + FeatureCategory.ASSIGNMENTS, + TestCategory.RENDER,secondaryFeature = SecondaryFeatureCategory.ASSIGNMENT_COMMENTS) fun testSingleSubmissionDisplaysAuthorPronoun() { val commentItem = submissionItem.copy(authorPronouns = "Pro/Noun") val state = SubmissionCommentsViewState(commentStates = listOf(commentItem)) @@ -181,7 +200,10 @@ class SubmissionCommentsRenderTest: StudentRenderTest() { } @Test - @TestMetaData(Priority.COMMON,FeatureCategory.ASSIGNMENTS,TestCategory.RENDER,secondaryFeature = SecondaryFeatureCategory.ASSIGNMENT_COMMENTS) + @TestMetaData( + Priority.COMMON, + FeatureCategory.ASSIGNMENTS, + TestCategory.RENDER,secondaryFeature = SecondaryFeatureCategory.ASSIGNMENT_COMMENTS) fun testSinglePendingCommentDisplaysAuthorPronoun() { val commentItem = pendingCommentItem.copy(authorPronouns = "Pro/Noun") val state = SubmissionCommentsViewState(commentStates = listOf(commentItem)) @@ -190,7 +212,10 @@ class SubmissionCommentsRenderTest: StudentRenderTest() { } @Test - @TestMetaData(Priority.COMMON,FeatureCategory.ASSIGNMENTS,TestCategory.RENDER,secondaryFeature = SecondaryFeatureCategory.ASSIGNMENT_COMMENTS) + @TestMetaData( + Priority.COMMON, + FeatureCategory.ASSIGNMENTS, + TestCategory.RENDER,secondaryFeature = SecondaryFeatureCategory.ASSIGNMENT_COMMENTS) fun testEmptyState() { val state = SubmissionCommentsViewState( commentStates = listOf(CommentItemState.Empty) @@ -200,7 +225,10 @@ class SubmissionCommentsRenderTest: StudentRenderTest() { } @Test - @TestMetaData(Priority.COMMON,FeatureCategory.ASSIGNMENTS,TestCategory.RENDER,secondaryFeature = SecondaryFeatureCategory.ASSIGNMENT_COMMENTS) + @TestMetaData( + Priority.COMMON, + FeatureCategory.ASSIGNMENTS, + TestCategory.RENDER,secondaryFeature = SecondaryFeatureCategory.ASSIGNMENT_COMMENTS) fun testFailedCommentDisplaysRetryAndDeleteOptions() { db.setCommentError(true, pendingCommentItem.pendingComment.id) val state = SubmissionCommentsViewState( @@ -212,7 +240,10 @@ class SubmissionCommentsRenderTest: StudentRenderTest() { } @Test - @TestMetaData(Priority.COMMON,FeatureCategory.ASSIGNMENTS,TestCategory.RENDER,secondaryFeature = SecondaryFeatureCategory.ASSIGNMENT_COMMENTS) + @TestMetaData( + Priority.COMMON, + FeatureCategory.ASSIGNMENTS, + TestCategory.RENDER,secondaryFeature = SecondaryFeatureCategory.ASSIGNMENT_COMMENTS) fun testMixedCommentsAndSubmission() { val state = SubmissionCommentsViewState( commentStates = listOf(commentItemIsAudience, submissionItem, commentItemNotAudience) @@ -228,7 +259,10 @@ class SubmissionCommentsRenderTest: StudentRenderTest() { } @Test - @TestMetaData(Priority.COMMON,FeatureCategory.ASSIGNMENTS,TestCategory.RENDER,secondaryFeature = SecondaryFeatureCategory.ASSIGNMENT_COMMENTS) + @TestMetaData( + Priority.COMMON, + FeatureCategory.ASSIGNMENTS, + TestCategory.RENDER,secondaryFeature = SecondaryFeatureCategory.ASSIGNMENT_COMMENTS) fun testAudienceDistinction() { val state = SubmissionCommentsViewState( commentStates = listOf(commentItemIsAudience, commentItemNotAudience) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/UploadStatusSubmissionRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/UploadStatusSubmissionRenderTest.kt index 904e9a4b77..97b2ca7df8 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/UploadStatusSubmissionRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/UploadStatusSubmissionRenderTest.kt @@ -16,10 +16,10 @@ package com.instructure.student.ui.renderTests import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData +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.student.FileSubmission import com.instructure.student.espresso.StudentRenderTest import com.instructure.student.mobius.assignmentDetails.submission.file.UploadStatusSubmissionModel diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/views/GradeCellRenderTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/views/GradeCellRenderTest.kt index 432f6a7c6c..ecb161ed32 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/views/GradeCellRenderTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/renderTests/views/GradeCellRenderTest.kt @@ -37,6 +37,7 @@ class GradeCellRenderTest : StudentRenderTest() { val submittedTitle by OnViewWithId(R.id.submittedTitle) val submittedSubtitle by OnViewWithId(R.id.submittedSubtitle) val pointsLabel by OnViewWithId(R.id.pointsLabel) + val yourGrade by OnViewWithId(R.id.yourGrade) val latePenalty by OnViewWithId(R.id.latePenalty) val finalGrade by OnViewWithId(R.id.finalGrade) val grade by OnViewWithId(R.id.grade) @@ -122,7 +123,8 @@ class GradeCellRenderTest : StudentRenderTest() { score = "91", showPointsLabel = true, outOf = "Out of 100 pts", - latePenalty = "Late Penalty (-2 pts)", + yourGrade = "Your Grade: 91 pts", + latePenalty = "Late Penalty: -2 pts", finalGrade = "Final Grade: 89 pts" ) setupViewWithState(state) @@ -132,6 +134,7 @@ class GradeCellRenderTest : StudentRenderTest() { score.assertDisplayed() pointsLabel.assertDisplayed() outOf.assertDisplayed() + yourGrade.assertDisplayed() latePenalty.assertDisplayed() finalGrade.assertDisplayed() } @@ -140,6 +143,7 @@ class GradeCellRenderTest : StudentRenderTest() { with(gradeCell) { score.assertHasText(state.score) outOf.assertHasText(state.outOf) + yourGrade.assertHasText(state.yourGrade) latePenalty.assertHasText(state.latePenalty) finalGrade.assertHasText(state.finalGrade) } diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTest.kt index 88c4ab96d8..e72cd5bd6b 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTest.kt @@ -32,6 +32,7 @@ import androidx.test.espresso.intent.Intents import androidx.test.espresso.intent.matcher.IntentMatchers import androidx.test.espresso.matcher.ViewMatchers import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice import com.instructure.canvas.espresso.CanvasTest import com.instructure.espresso.InstructureActivityTestRule import com.instructure.espresso.Searchable @@ -42,6 +43,7 @@ import com.instructure.student.R import com.instructure.student.activity.LoginActivity import com.instructure.student.espresso.StudentHiltTestApplication_Application import com.instructure.student.ui.pages.AboutPage +import com.instructure.student.ui.pages.AllCoursesPage import com.instructure.student.ui.pages.AnnotationCommentListPage import com.instructure.student.ui.pages.AnnouncementListPage import com.instructure.student.ui.pages.AssignmentDetailsPage @@ -56,7 +58,6 @@ import com.instructure.student.ui.pages.CourseGradesPage import com.instructure.student.ui.pages.DashboardPage import com.instructure.student.ui.pages.DiscussionDetailsPage import com.instructure.student.ui.pages.DiscussionListPage -import com.instructure.student.ui.pages.EditDashboardPage import com.instructure.student.ui.pages.ElementaryCoursePage import com.instructure.student.ui.pages.ElementaryDashboardPage import com.instructure.student.ui.pages.FileListPage @@ -112,6 +113,8 @@ import javax.inject.Inject abstract class StudentTest : CanvasTest() { + val device: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + override val activityRule: InstructureActivityTestRule = StudentActivityTestRule(LoginActivity::class.java) @@ -162,7 +165,7 @@ abstract class StudentTest : CanvasTest() { val leftSideNavigationDrawerPage = LeftSideNavigationDrawerPage() val discussionDetailsPage = DiscussionDetailsPage() val discussionListPage = DiscussionListPage(Searchable(R.id.search, R.id.search_src_text, R.id.search_close_btn)) - val editDashboardPage = EditDashboardPage() + val allCoursesPage = AllCoursesPage() val fileListPage = FileListPage(Searchable(R.id.search, R.id.queryInput, R.id.clearButton, R.id.backButton)) val fileUploadPage = FileUploadPage() val helpPage = HelpPage() diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTestExtensions.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTestExtensions.kt index 991c0e1778..ae17fa6e2a 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTestExtensions.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentTestExtensions.kt @@ -58,7 +58,7 @@ fun StudentTest.slowLogIn(enrollmentType: String = EnrollmentTypes.STUDENT_ENROL return user } -fun StudentTest.seedDataForK5( +fun seedDataForK5( teachers: Int = 0, tas: Int = 0, pastCourses: Int = 0, @@ -69,7 +69,8 @@ fun StudentTest.seedDataForK5( announcements: Int = 0, discussions: Int = 0, syllabusBody: String? = null, - gradingPeriods: Boolean = false): SeedApi.SeededDataApiModel { + gradingPeriods: Boolean = false +): SeedApi.SeededDataApiModel { val request = SeedApi.SeedDataRequest ( teachers = teachers, @@ -88,7 +89,7 @@ fun StudentTest.seedDataForK5( return SeedApi.seedDataForSubAccount(request) } -fun StudentTest.seedData( +fun seedData( teachers: Int = 0, tas: Int = 0, pastCourses: Int = 0, @@ -100,7 +101,8 @@ fun StudentTest.seedData( locked: Boolean = false, discussions: Int = 0, syllabusBody: String? = null, - gradingPeriods: Boolean = false): SeedApi.SeededDataApiModel { + gradingPeriods: Boolean = false +): SeedApi.SeededDataApiModel { val request = SeedApi.SeedDataRequest ( teachers = teachers, @@ -119,15 +121,16 @@ fun StudentTest.seedData( return SeedApi.seedData(request) } -fun StudentTest.seedAssignments( - courseId: Long, - assignments: Int = 1, - withDescription: Boolean = false, - lockAt: String = "", - unlockAt: String = "", - dueAt: String = "", - submissionTypes: List = emptyList(), - teacherToken: String): List { +fun seedAssignments( + courseId: Long, + assignments: Int = 1, + withDescription: Boolean = false, + lockAt: String = "", + unlockAt: String = "", + dueAt: String = "", + submissionTypes: List = emptyList(), + teacherToken: String +): List { return AssignmentsApi.seedAssignments(AssignmentsApi.CreateAssignmentRequest( courseId = courseId, @@ -203,7 +206,7 @@ fun StudentTest.tokenLoginElementary(user: CanvasUserApiModel) { elementaryDashboardPage.assertPageObjects() } -fun StudentTest.routeTo(route: String) { +fun routeTo(route: String) { val url = "canvas-student://${CanvasNetworkAdapter.canvasDomain}/$route" val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) val context = InstrumentationRegistry.getInstrumentation().targetContext @@ -213,7 +216,7 @@ fun StudentTest.routeTo(route: String) { context.startActivity(intent) } -fun StudentTest.routeTo(route: String, domain: String) { +fun routeTo(route: String, domain: String) { val url = "canvas-student://$domain/$route" val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) val context = InstrumentationRegistry.getInstrumentation().targetContext @@ -223,39 +226,41 @@ fun StudentTest.routeTo(route: String, domain: String) { context.startActivity(intent) } -fun StudentTest.routeTo(route: Route, activity: FragmentActivity) { +fun routeTo(route: Route, activity: FragmentActivity) { RouteMatcher.route(activity, route) } -fun StudentTest.seedAssignmentSubmission( - submissionSeeds: List, - assignmentId: Long, - courseId: Long, - studentToken: String, - commentSeeds: List = kotlin.collections.emptyList() +fun seedAssignmentSubmission( + submissionSeeds: List, + assignmentId: Long, + courseId: Long, + studentToken: String, + commentSeeds: List = emptyList() ): List { // Upload one submission file for each submission seed submissionSeeds.forEach { it.attachmentsList.add( when (it.submissionType) { - SubmissionType.ONLINE_UPLOAD -> uploadTextFile(courseId, assignmentId, studentToken, + SubmissionType.ONLINE_UPLOAD -> uploadTextFile( + courseId, assignmentId, studentToken, FileUploadType.ASSIGNMENT_SUBMISSION ) else -> AttachmentApiModel(displayName="", fileName="", id=0L) // Not handled right now } - ); + ) } // Add attachments to comment seeds commentSeeds.forEach { - val fileAttachments: MutableList = kotlin.collections.mutableListOf() + val fileAttachments: MutableList = mutableListOf() for (i in 0..it.amount) { if (it.fileType != FileType.NONE) { fileAttachments.add(when (it.fileType) { - FileType.PDF -> kotlin.TODO() - FileType.TEXT -> uploadTextFile(courseId, assignmentId, studentToken, + FileType.PDF -> TODO() + FileType.TEXT -> uploadTextFile( + courseId, assignmentId, studentToken, FileUploadType.COMMENT_ATTACHMENT ) else -> throw RuntimeException("Unknown file type passed into StudentTest.seedAssignmentSubmission") // Unknown type @@ -278,7 +283,12 @@ fun StudentTest.seedAssignmentSubmission( return SubmissionsApi.seedAssignmentSubmission(submissionRequest) } -fun StudentTest.uploadTextFile(courseId: Long, assignmentId: Long, token: String, fileUploadType: FileUploadType): AttachmentApiModel { +fun uploadTextFile( + courseId: Long, + assignmentId: Long? = null, + token: String, + fileUploadType: FileUploadType +): AttachmentApiModel { // Create the file val file = File( diff --git a/apps/student/src/main/java/com/instructure/student/activity/CallbackActivity.kt b/apps/student/src/main/java/com/instructure/student/activity/CallbackActivity.kt index 988a81b30b..4eb72c7801 100644 --- a/apps/student/src/main/java/com/instructure/student/activity/CallbackActivity.kt +++ b/apps/student/src/main/java/com/instructure/student/activity/CallbackActivity.kt @@ -18,6 +18,7 @@ package com.instructure.student.activity import android.os.Bundle +import androidx.lifecycle.lifecycleScope import com.google.firebase.crashlytics.FirebaseCrashlytics import com.heapanalytics.android.Heap import com.instructure.canvasapi2.StatusCallback @@ -39,12 +40,19 @@ import com.instructure.student.flutterChannels.FlutterComm import com.instructure.student.fragment.NotificationListFragment import com.instructure.student.service.StudentPageViewService import com.instructure.student.util.StudentPrefs +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Job +import kotlinx.coroutines.launch import retrofit2.Call import retrofit2.Response +import javax.inject.Inject +@AndroidEntryPoint abstract class CallbackActivity : ParentActivity(), OnUnreadCountInvalidated, NotificationListFragment.OnNotificationCountInvalidated { + @Inject + lateinit var featureFlagProvider: FeatureFlagProvider + private var loadInitialDataJob: Job? = null abstract fun gotLaunchDefinitions(launchDefinitions: List?) @@ -130,6 +138,8 @@ abstract class CallbackActivity : ParentActivity(), OnUnreadCountInvalidated, No getUnreadNotificationCount() + featureFlagProvider.fetchEnvironmentFeatureFlags() + initialCoreDataLoadingComplete() } catch { initialCoreDataLoadingComplete() 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 674981de32..7c99312709 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 @@ -143,9 +143,6 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog. @Inject lateinit var databaseProvider: DatabaseProvider - @Inject - lateinit var featureFlagProvider: FeatureFlagProvider - @Inject lateinit var repository: NavigationRepository @@ -230,7 +227,7 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog. R.id.navigationDrawerItem_stopMasquerading -> { MasqueradeHelper.stopMasquerading(startActivityClass) } - R.id.navigationDrawerSettings -> startActivity(Intent(applicationContext, SettingsActivity::class.java)) + R.id.navigationDrawerSettings -> startActivity(SettingsActivity.createIntent(applicationContext, featureFlagProvider.offlineEnabled())) } } } @@ -305,8 +302,6 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog. setupNavDrawerItems() - loadFeatureFlags() - checkAppUpdates() val savedBottomScreens = savedInstanceState?.getStringArrayList(BOTTOM_SCREENS_BUNDLE_KEY) @@ -345,12 +340,6 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog. } } - private fun loadFeatureFlags() { - lifecycleScope.launch { - featureFlagProvider.fetchEnvironmentFeatureFlags() - } - } - private fun requestNotificationsPermission() { if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { notificationsPermissionContract.launch(Manifest.permission.POST_NOTIFICATIONS) diff --git a/apps/student/src/main/java/com/instructure/student/activity/SettingsActivity.kt b/apps/student/src/main/java/com/instructure/student/activity/SettingsActivity.kt index b209e64757..edaed771de 100644 --- a/apps/student/src/main/java/com/instructure/student/activity/SettingsActivity.kt +++ b/apps/student/src/main/java/com/instructure/student/activity/SettingsActivity.kt @@ -33,6 +33,8 @@ import com.instructure.student.databinding.ActivitySettingsBinding import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject +private const val OFFLINE_ENABLED = "offlineEnabled" + @ScreenView(SCREEN_VIEW_SETTINGS) @AndroidEntryPoint class SettingsActivity : AppCompatActivity(){ @@ -42,10 +44,12 @@ class SettingsActivity : AppCompatActivity(){ private val binding by viewBinding(ActivitySettingsBinding::inflate) + var offlineEnabled: Boolean = false + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + offlineEnabled = intent.getBooleanExtra(OFFLINE_ENABLED, false) setContentView(binding.root) - networkStateProvider.isOnlineLiveData.observe(this) { isOnline -> binding.offlineIndicator.root.setVisible(!isOnline) } @@ -73,8 +77,10 @@ class SettingsActivity : AppCompatActivity(){ } companion object { - fun createIntent(context: Context): Intent { - return Intent(context, SettingsActivity::class.java) + fun createIntent(context: Context, offlineEnabled: Boolean): Intent { + return Intent(context, SettingsActivity::class.java).apply { + putExtra(OFFLINE_ENABLED, offlineEnabled) + } } } } diff --git a/apps/student/src/main/java/com/instructure/student/adapter/BaseListRecyclerAdapter.kt b/apps/student/src/main/java/com/instructure/student/adapter/BaseListRecyclerAdapter.kt index d801682e61..773e3221f7 100644 --- a/apps/student/src/main/java/com/instructure/student/adapter/BaseListRecyclerAdapter.kt +++ b/apps/student/src/main/java/com/instructure/student/adapter/BaseListRecyclerAdapter.kt @@ -102,8 +102,8 @@ abstract class BaseListRecyclerAdapter, T : Recycler fun onCallbackFinished() { isLoadedFirstPage = true shouldShowLoadingFooter() - adapterToRecyclerViewCallback.setDisplayNoConnection(false) - adapterToRecyclerViewCallback.setIsEmpty(isAllPagesLoaded && size() == 0) + adapterToRecyclerViewCallback?.setDisplayNoConnection(false) + adapterToRecyclerViewCallback?.setIsEmpty(isAllPagesLoaded && size() == 0) } /** diff --git a/apps/student/src/main/java/com/instructure/student/adapter/BookmarkRecyclerAdapter.kt b/apps/student/src/main/java/com/instructure/student/adapter/BookmarkRecyclerAdapter.kt index e65d179e7c..e3f695019e 100644 --- a/apps/student/src/main/java/com/instructure/student/adapter/BookmarkRecyclerAdapter.kt +++ b/apps/student/src/main/java/com/instructure/student/adapter/BookmarkRecyclerAdapter.kt @@ -18,7 +18,6 @@ package com.instructure.student.adapter import android.content.Context -import android.os.Handler import android.view.View import android.widget.FrameLayout import android.widget.ImageView @@ -30,7 +29,6 @@ import com.instructure.canvasapi2.models.Bookmark import com.instructure.canvasapi2.utils.APIHelper import com.instructure.canvasapi2.utils.ApiType import com.instructure.canvasapi2.utils.LinkHeaders -import com.instructure.pandautils.utils.ColorKeeper import com.instructure.pandautils.utils.ColorUtils import com.instructure.pandautils.utils.textAndIconColor import com.instructure.student.R @@ -39,7 +37,7 @@ import com.instructure.student.router.RouteMatcher import com.instructure.student.util.CacheControlFlags import retrofit2.Call import retrofit2.Response -import java.util.Locale +import java.util.* class BookmarkRecyclerAdapter(context: Context, isShortcutActivity: Boolean, private val mAdapterToFragmentCallback: BookmarkAdapterToFragmentCallback) : BaseListRecyclerAdapter(context, Bookmark::class.java) { @@ -81,7 +79,7 @@ class BookmarkRecyclerAdapter(context: Context, isShortcutActivity: Boolean, pri override fun onFail(call: Call>?, error: Throwable, response: Response<*>?) { if (response != null && !APIHelper.isCachedResponse(response) || !APIHelper.hasNetworkConnection()) { - adapterToRecyclerViewCallback.setIsEmpty(true) + adapterToRecyclerViewCallback?.setIsEmpty(true) } } 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 725f9456d3..66b1837c5f 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 @@ -135,6 +135,8 @@ class DashboardRecyclerAdapter( val dashboardCards = repository.getDashboardCourses(isRefresh) val syncedCourseIds = repository.getSyncedCourseIds() + resetData() + mCourseMap = courses.associateBy { it.id } // Map not null is needed because the dashboard api can return unpublished courses @@ -158,10 +160,10 @@ class DashboardRecyclerAdapter( notifyDataSetChanged() isAllPagesLoaded = true - if (itemCount == 0) adapterToRecyclerViewCallback.setIsEmpty(true) + if (itemCount == 0) adapterToRecyclerViewCallback?.setIsEmpty(true) mAdapterToFragmentCallback.onRefreshFinished() } catch { - adapterToRecyclerViewCallback.setDisplayNoConnection(true) + adapterToRecyclerViewCallback?.setDisplayNoConnection(true) mAdapterToFragmentCallback.onRefreshFinished() } } diff --git a/apps/student/src/main/java/com/instructure/student/adapter/ExpandableRecyclerAdapter.kt b/apps/student/src/main/java/com/instructure/student/adapter/ExpandableRecyclerAdapter.kt index 9f99e78f10..d26c813838 100644 --- a/apps/student/src/main/java/com/instructure/student/adapter/ExpandableRecyclerAdapter.kt +++ b/apps/student/src/main/java/com/instructure/student/adapter/ExpandableRecyclerAdapter.kt @@ -35,14 +35,14 @@ abstract class ExpandableRecyclerAdapter() { override fun onResponse(response: Response, linkHeaders: LinkHeaders, type: ApiType) { removeItem(todo) - adapterToRecyclerViewCallback.setIsEmpty(size() == 0) + adapterToRecyclerViewCallback?.setIsEmpty(size() == 0) } override fun onFail(call: Call?, error: Throwable, response: Response<*>?) { diff --git a/apps/student/src/main/java/com/instructure/student/di/ApplicationModule.kt b/apps/student/src/main/java/com/instructure/student/di/ApplicationModule.kt new file mode 100644 index 0000000000..38d3f681b2 --- /dev/null +++ b/apps/student/src/main/java/com/instructure/student/di/ApplicationModule.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2023 - present Instructure, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + */ + +package com.instructure.student.di + +import com.instructure.pandautils.utils.LogoutHelper +import com.instructure.student.util.StudentLogoutHelper +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +class ApplicationModule { + + @Provides + fun provideLogoutHelper(): LogoutHelper { + return StudentLogoutHelper() + } +} \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/di/DashboardModule.kt b/apps/student/src/main/java/com/instructure/student/di/DashboardModule.kt index e31afb88a7..ce8ef99516 100644 --- a/apps/student/src/main/java/com/instructure/student/di/DashboardModule.kt +++ b/apps/student/src/main/java/com/instructure/student/di/DashboardModule.kt @@ -19,6 +19,7 @@ package com.instructure.student.di import androidx.fragment.app.FragmentActivity import com.instructure.canvasapi2.apis.CourseAPI import com.instructure.canvasapi2.apis.GroupAPI +import com.instructure.canvasapi2.apis.UserAPI import com.instructure.canvasapi2.utils.ApiPrefs import com.instructure.pandautils.features.dashboard.edit.EditDashboardRouter import com.instructure.pandautils.features.dashboard.notifications.DashboardRouter @@ -56,9 +57,10 @@ class DashboardModule { fun provideDashboardNetworkDataSource( courseApi: CourseAPI.CoursesInterface, groupApi: GroupAPI.GroupInterface, - apiPrefs: ApiPrefs + apiPrefs: ApiPrefs, + userApi: UserAPI.UsersInterface ): DashboardNetworkDataSource { - return DashboardNetworkDataSource(courseApi, groupApi, apiPrefs) + return DashboardNetworkDataSource(courseApi, groupApi, apiPrefs, userApi) } @Provides diff --git a/apps/student/src/main/java/com/instructure/student/dialog/EditTextDialog.kt b/apps/student/src/main/java/com/instructure/student/dialog/EditTextDialog.kt index be1de289a2..1cf2d568e4 100644 --- a/apps/student/src/main/java/com/instructure/student/dialog/EditTextDialog.kt +++ b/apps/student/src/main/java/com/instructure/student/dialog/EditTextDialog.kt @@ -66,7 +66,14 @@ class EditTextDialog : AppCompatDialogFragment() { ViewStyler.themeEditText(requireContext(), binding.textInput, ThemePrefs.brandColor) binding.textInput.setText(mDefaultText) - binding.textInput.selectAll() + + val endIndex = mDefaultText.lastIndexOf(".") + if (endIndex != -1) { + binding.textInput.setSelection(0, endIndex) + } else { + binding.textInput.selectAll() + } + binding.textInput.requestFocus() val dialog = AlertDialog.Builder(requireContext()) .setCancelable(true) diff --git a/apps/student/src/main/java/com/instructure/student/dialog/LegalDialogStyled.kt b/apps/student/src/main/java/com/instructure/student/dialog/LegalDialogStyled.kt index d163ee5069..d0e1a5018c 100644 --- a/apps/student/src/main/java/com/instructure/student/dialog/LegalDialogStyled.kt +++ b/apps/student/src/main/java/com/instructure/student/dialog/LegalDialogStyled.kt @@ -87,7 +87,7 @@ class LegalDialogStyled : AppCompatDialogFragment() { } binding.privacyPolicy.onClick { - val intent = InternalWebViewActivity.createIntent(activity, "https://www.instructure.com/canvas/privacy", getString(R.string.privacyPolicy), false) + val intent = InternalWebViewActivity.createIntent(activity, "https://www.instructure.com/policies/product-privacy-policy", getString(R.string.privacyPolicy), false) requireContext().startActivity(intent) dialog?.dismiss() } diff --git a/apps/student/src/main/java/com/instructure/student/features/assignments/details/AssignmentDetailsViewModel.kt b/apps/student/src/main/java/com/instructure/student/features/assignments/details/AssignmentDetailsViewModel.kt index 345659d12b..487d856402 100644 --- a/apps/student/src/main/java/com/instructure/student/features/assignments/details/AssignmentDetailsViewModel.kt +++ b/apps/student/src/main/java/com/instructure/student/features/assignments/details/AssignmentDetailsViewModel.kt @@ -20,11 +20,23 @@ package com.instructure.student.features.assignments.details import android.app.Application import android.content.Context import android.content.res.Resources -import androidx.lifecycle.* +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.instructure.canvasapi2.managers.SubmissionManager import com.instructure.canvasapi2.models.* import com.instructure.canvasapi2.models.Assignment.SubmissionType -import com.instructure.canvasapi2.utils.* +import com.instructure.canvasapi2.utils.Analytics +import com.instructure.canvasapi2.utils.AnalyticsEventConstants +import com.instructure.canvasapi2.utils.ApiPrefs +import com.instructure.canvasapi2.utils.DateHelper +import com.instructure.canvasapi2.utils.NumberHelper +import com.instructure.canvasapi2.utils.Pronouns +import com.instructure.canvasapi2.utils.isNullOrEmpty +import com.instructure.canvasapi2.utils.isRtl +import com.instructure.canvasapi2.utils.isValid import com.instructure.interactions.bookmarks.Bookmarker import com.instructure.interactions.router.RouterParams import com.instructure.pandautils.BR @@ -32,7 +44,11 @@ import com.instructure.pandautils.features.assignmentdetails.AssignmentDetailsAt import com.instructure.pandautils.features.assignmentdetails.AssignmentDetailsAttemptViewData import com.instructure.pandautils.mvvm.Event import com.instructure.pandautils.mvvm.ViewState -import com.instructure.pandautils.utils.* +import com.instructure.pandautils.utils.AssignmentUtils2 +import com.instructure.pandautils.utils.ColorKeeper +import com.instructure.pandautils.utils.Const +import com.instructure.pandautils.utils.HtmlContentFormatter +import com.instructure.pandautils.utils.orDefault import com.instructure.student.R import com.instructure.student.db.StudentDb import com.instructure.student.features.assignments.details.gradecellview.GradeCellViewData @@ -44,7 +60,8 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import java.io.File import java.text.DateFormat -import java.util.* +import java.util.Date +import java.util.Locale import javax.inject.Inject import com.instructure.student.Submission as DatabaseSubmission @@ -340,10 +357,11 @@ class AssignmentDetailsViewModel @Inject constructor( ) // Observers shouldn't see the submit button OR if the course is soft concluded - val submitVisible = if (isObserver || !course?.isBetweenValidDateRange().orDefault()) { - false - } else { - when (assignment.turnInType) { + val submitVisible = when { + isObserver -> false + !course?.isBetweenValidDateRange().orDefault() -> false + assignment.submission?.excused.orDefault() -> false + else -> when (assignment.turnInType) { Assignment.TurnInType.QUIZ, Assignment.TurnInType.DISCUSSION -> true Assignment.TurnInType.ONLINE, Assignment.TurnInType.EXTERNAL_TOOL -> assignment.isAllowedToSubmit else -> false diff --git a/apps/student/src/main/java/com/instructure/student/features/assignments/details/gradecellview/GradeCellViewData.kt b/apps/student/src/main/java/com/instructure/student/features/assignments/details/gradecellview/GradeCellViewData.kt index 7fb3b6eae8..fe8c7ca580 100644 --- a/apps/student/src/main/java/com/instructure/student/features/assignments/details/gradecellview/GradeCellViewData.kt +++ b/apps/student/src/main/java/com/instructure/student/features/assignments/details/gradecellview/GradeCellViewData.kt @@ -24,6 +24,7 @@ data class GradeCellViewData( val grade: String = "", val gradeCellContentDescription: String = "", val outOf: String = "", + val yourGrade: String = "", val latePenalty: String = "", val finalGrade: String = "", val stats: GradeCellViewState.GradeStats? = null @@ -39,7 +40,6 @@ data class GradeCellViewData( } companion object { - @Suppress("DEPRECATION") fun fromSubmission( resources: Resources, courseColor: ThemedColor, @@ -145,7 +145,7 @@ data class GradeCellViewData( gradeCellContentDescription = contentDescription, ) } else { - val score = NumberHelper.formatDecimal(submission.enteredScore, 2, true) + val score = NumberHelper.formatDecimal(submission.score, 2, true) val chartPercent = (submission.enteredScore / assignment.pointsPossible).coerceIn(0.0, 1.0).toFloat() // If grading type is Points, don't show the grade since we're already showing it as the score var grade = if (assignment.gradingType != Assignment.POINTS_TYPE) submission.grade.orEmpty() else "" @@ -170,12 +170,15 @@ data class GradeCellViewData( var latePenalty = "" var finalGrade = "" + var yourGrade = "" // Adjust for late penalty, if any if (submission.pointsDeducted.orDefault() > 0.0) { grade = "" // Grade will be shown in the 'final grade' text val pointsDeducted = NumberHelper.formatDecimal(submission.pointsDeducted.orDefault(), 2, true) - latePenalty = resources.getString(R.string.latePenalty, pointsDeducted) + val achievedScore = NumberHelper.formatDecimal(submission.enteredScore, 2, true) + yourGrade = resources.getString(R.string.yourGrade, achievedScore) + latePenalty = resources.getString(R.string.latePenaltyUpdated, pointsDeducted) finalGrade = resources.getString(R.string.finalGradeFormatted, submission.grade) } @@ -210,6 +213,7 @@ data class GradeCellViewData( grade = grade, gradeCellContentDescription = gradeCellContentDescription, outOf = outOfText, + yourGrade = yourGrade, latePenalty = latePenalty, finalGrade = finalGrade, stats = stats diff --git a/apps/student/src/main/java/com/instructure/student/features/assignments/list/AssignmentListFragment.kt b/apps/student/src/main/java/com/instructure/student/features/assignments/list/AssignmentListFragment.kt index e198678fb9..97c13658cc 100644 --- a/apps/student/src/main/java/com/instructure/student/features/assignments/list/AssignmentListFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/features/assignments/list/AssignmentListFragment.kt @@ -75,7 +75,7 @@ class AssignmentListFragment : ParentFragment(), Bookmarkable { private var canvasContext by ParcelableArg(key = Const.CANVAS_CONTEXT) - private lateinit var recyclerAdapter: AssignmentListRecyclerAdapter + private var recyclerAdapter: AssignmentListRecyclerAdapter? = null private var termAdapter: TermSpinnerAdapter? = null private var filterPosition = 0 @@ -118,7 +118,7 @@ class AssignmentListFragment : ParentFragment(), Bookmarkable { override fun onRefreshFinished() { if (!isAdded) return // Refresh can finish after user has left screen, causing emptyView to be null setRefreshing(false) - if (recyclerAdapter.size() == 0) { + if (recyclerAdapter?.size() == 0) { setEmptyView(binding.emptyView, R.drawable.ic_panda_space, R.string.noAssignments, R.string.noAssignmentsSubtext) } } @@ -140,14 +140,16 @@ class AssignmentListFragment : ParentFragment(), Bookmarkable { binding.sortByTextView.setText(sortOrder.buttonTextRes) binding.sortByButton.contentDescription = getString(sortOrder.contentDescriptionRes) - configureRecyclerView( + recyclerAdapter?.let { + configureRecyclerView( view, requireContext(), - recyclerAdapter, + it, R.id.swipeRefreshLayout, R.id.emptyView, R.id.listView - ) + ) + } binding.appbar.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { _, i -> // Workaround for Toolbar not showing with swipe to refresh @@ -230,7 +232,7 @@ class AssignmentListFragment : ParentFragment(), Bookmarkable { dialog.dismiss() filterPosition = index filter = AssignmentListFilter.values()[index] - recyclerAdapter.filter = filter + recyclerAdapter?.filter = filter updateBadge() } @@ -262,7 +264,7 @@ class AssignmentListFragment : ParentFragment(), Bookmarkable { } else { emptyView.emptyViewText(getString(R.string.noItemsMatchingQuery, query)) } - recyclerAdapter.searchQuery = query + recyclerAdapter?.searchQuery = query } ViewStyler.themeToolbarColored(requireActivity(), toolbar, canvasContext) } @@ -290,22 +292,22 @@ class AssignmentListFragment : ParentFragment(), Bookmarkable { termSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onItemSelected(adapterView: AdapterView<*>, view: View?, i: Int, l: Long) { if (adapter.getItem(i)!!.title == getString(R.string.assignmentsListAllGradingPeriods)) { - recyclerAdapter.loadAssignment() + recyclerAdapter?.loadAssignment() } else { - recyclerAdapter.loadAssignmentsForGradingPeriod(adapter.getItem(i)!!.id, true) + recyclerAdapter?.loadAssignmentsForGradingPeriod(adapter.getItem(i)!!.id, true) termSpinner.isEnabled = false adapter.isLoading = true adapter.notifyDataSetChanged() } - recyclerAdapter.currentGradingPeriod = adapter.getItem(i) + recyclerAdapter?.currentGradingPeriod = adapter.getItem(i) } override fun onNothingSelected(adapterView: AdapterView<*>) {} } // If we have a "current" grading period select it - if (hasGradingPeriods && recyclerAdapter.currentGradingPeriod != null) { - val position = adapter.getPositionForId(recyclerAdapter.currentGradingPeriod?.id ?: 0) + if (hasGradingPeriods && recyclerAdapter?.currentGradingPeriod != null) { + val position = adapter.getPositionForId(recyclerAdapter?.currentGradingPeriod?.id ?: 0) if (position != -1) { termSpinner.setSelection(position) } else { @@ -318,16 +320,17 @@ class AssignmentListFragment : ParentFragment(), Bookmarkable { override fun onConfigurationChanged(newConfig: Configuration) = with(binding) { super.onConfigurationChanged(newConfig) - configureRecyclerView( + recyclerAdapter?.let { + configureRecyclerView( requireView(), requireContext(), - recyclerAdapter, + it, R.id.swipeRefreshLayout, R.id.emptyView, - R.id.listView, - R.string.noAssignments - ) - if (recyclerAdapter.size() == 0) { + R.id.listView + ) + } + if (recyclerAdapter?.size() == 0) { emptyView.changeTextSize() if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { if (isTablet) { @@ -348,7 +351,7 @@ class AssignmentListFragment : ParentFragment(), Bookmarkable { override fun onDestroy() { super.onDestroy() - recyclerAdapter.cancel() + recyclerAdapter?.cancel() } companion object { diff --git a/apps/student/src/main/java/com/instructure/student/features/dashboard/DashboardLocalDataSource.kt b/apps/student/src/main/java/com/instructure/student/features/dashboard/DashboardLocalDataSource.kt index d6f4fd1f7d..ff40b2e37b 100644 --- a/apps/student/src/main/java/com/instructure/student/features/dashboard/DashboardLocalDataSource.kt +++ b/apps/student/src/main/java/com/instructure/student/features/dashboard/DashboardLocalDataSource.kt @@ -16,8 +16,10 @@ */ package com.instructure.student.features.dashboard +import com.instructure.canvasapi2.models.CanvasContext import com.instructure.canvasapi2.models.Course import com.instructure.canvasapi2.models.DashboardCard +import com.instructure.canvasapi2.models.DashboardPositions import com.instructure.canvasapi2.models.Group import com.instructure.pandautils.room.offline.daos.DashboardCardDao import com.instructure.pandautils.room.offline.entities.DashboardCardEntity @@ -43,4 +45,28 @@ class DashboardLocalDataSource( suspend fun saveDashboardCards(dashboardCards: List) { dashboardCardDao.updateEntities(dashboardCards.map { DashboardCardEntity(it) }) } + + suspend fun updateDashboardCardsOrder(dashboardPositions: DashboardPositions) { + val cards = dashboardCardDao.findAll() + val coursesWithPosition = dashboardPositions.positions + .map { Pair(CanvasContext.fromContextCode(it.key), it.value) } + .filter { it.first is Course } + .associate { Pair((it.first as Course).id, it.second) } + + // If somehow we end up with different items in the positions response than the stored dashboard cards we should return and not update the positions + val cardIds = cards.map { it.id }.toSet() + val positionUpdateIds = coursesWithPosition.keys + if (cardIds != positionUpdateIds) return + + val newCards = cards.map { + val newPosition = coursesWithPosition[it.id] + if (newPosition != null) { + it.copy(position = newPosition) + } else { + it + } + } + + dashboardCardDao.updateEntities(newCards) + } } \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/features/dashboard/DashboardNetworkDataSource.kt b/apps/student/src/main/java/com/instructure/student/features/dashboard/DashboardNetworkDataSource.kt index b60a138f73..d12d9f323e 100644 --- a/apps/student/src/main/java/com/instructure/student/features/dashboard/DashboardNetworkDataSource.kt +++ b/apps/student/src/main/java/com/instructure/student/features/dashboard/DashboardNetworkDataSource.kt @@ -18,17 +18,21 @@ package com.instructure.student.features.dashboard import com.instructure.canvasapi2.apis.CourseAPI import com.instructure.canvasapi2.apis.GroupAPI +import com.instructure.canvasapi2.apis.UserAPI import com.instructure.canvasapi2.builders.RestParams import com.instructure.canvasapi2.models.Course import com.instructure.canvasapi2.models.DashboardCard +import com.instructure.canvasapi2.models.DashboardPositions import com.instructure.canvasapi2.models.Group import com.instructure.canvasapi2.utils.ApiPrefs +import com.instructure.canvasapi2.utils.DataResult import com.instructure.canvasapi2.utils.depaginate class DashboardNetworkDataSource( private val courseApi: CourseAPI.CoursesInterface, private val groupApi: GroupAPI.GroupInterface, - private val apiPrefs: ApiPrefs + private val apiPrefs: ApiPrefs, + private val userApi: UserAPI.UsersInterface ): DashboardDataSource { override suspend fun getCourses(forceNetwork: Boolean): List { @@ -53,4 +57,8 @@ class DashboardNetworkDataSource( override suspend fun getDashboardCards(forceNetwork: Boolean): List { return courseApi.getDashboardCourses(RestParams(isForceReadFromNetwork = forceNetwork)).dataOrNull.orEmpty() } + + suspend fun updateDashboardPositions(dashboardPositions: DashboardPositions): DataResult { + return userApi.updateDashboardPositions(dashboardPositions, RestParams(isForceReadFromNetwork = true)) + } } \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/features/dashboard/DashboardRepository.kt b/apps/student/src/main/java/com/instructure/student/features/dashboard/DashboardRepository.kt index 4d85ff6b22..29b546eb83 100644 --- a/apps/student/src/main/java/com/instructure/student/features/dashboard/DashboardRepository.kt +++ b/apps/student/src/main/java/com/instructure/student/features/dashboard/DashboardRepository.kt @@ -16,11 +16,11 @@ */ package com.instructure.student.features.dashboard -import com.instructure.canvasapi2.apis.CourseAPI -import com.instructure.canvasapi2.builders.RestParams import com.instructure.canvasapi2.models.Course import com.instructure.canvasapi2.models.DashboardCard +import com.instructure.canvasapi2.models.DashboardPositions import com.instructure.canvasapi2.models.Group +import com.instructure.canvasapi2.utils.DataResult import com.instructure.pandautils.repository.Repository import com.instructure.pandautils.room.offline.daos.CourseDao import com.instructure.pandautils.room.offline.daos.CourseSyncSettingsDao @@ -29,7 +29,7 @@ import com.instructure.pandautils.utils.NetworkStateProvider class DashboardRepository( private val localDataSource: DashboardLocalDataSource, - networkDataSource: DashboardNetworkDataSource, + private val networkDataSource: DashboardNetworkDataSource, networkStateProvider: NetworkStateProvider, featureFlagProvider: FeatureFlagProvider, private val courseSyncSettingsDao: CourseSyncSettingsDao, @@ -45,11 +45,14 @@ class DashboardRepository( } suspend fun getDashboardCourses(forceNetwork: Boolean): List { - val dashboardCards = dataSource().getDashboardCards(forceNetwork).sortedBy { it.position } + var dashboardCards = dataSource().getDashboardCards(forceNetwork) + if (dashboardCards.all { it.position == Int.MAX_VALUE }) { + dashboardCards = dashboardCards.mapIndexed { index, dashboardCard -> dashboardCard.copy(position = index) } + } if (isOnline() && isOfflineEnabled()) { localDataSource.saveDashboardCards(dashboardCards) } - return dashboardCards + return dashboardCards.sortedBy { it.position } } suspend fun getSyncedCourseIds(): Set { @@ -64,4 +67,12 @@ class DashboardRepository( val syncedCourses = courseDao.findByIds(syncedCourseIds) return syncedCourses.map { it.id }.toSet() } + + suspend fun updateDashboardPositions(dashboardPositions: DashboardPositions): DataResult { + val result = networkDataSource.updateDashboardPositions(dashboardPositions) + if (result is DataResult.Success) { + localDataSource.updateDashboardCardsOrder(dashboardPositions) + } + return networkDataSource.updateDashboardPositions(dashboardPositions) + } } \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/features/discussion/list/adapter/DiscussionListRecyclerAdapter.kt b/apps/student/src/main/java/com/instructure/student/features/discussion/list/adapter/DiscussionListRecyclerAdapter.kt index b98cb662c6..62ffa37c47 100644 --- a/apps/student/src/main/java/com/instructure/student/features/discussion/list/adapter/DiscussionListRecyclerAdapter.kt +++ b/apps/student/src/main/java/com/instructure/student/features/discussion/list/adapter/DiscussionListRecyclerAdapter.kt @@ -35,7 +35,7 @@ import com.instructure.student.holders.NoViewholder import com.instructure.student.interfaces.AdapterToFragmentCallback import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import java.util.Date +import java.util.* open class DiscussionListRecyclerAdapter( context: Context, @@ -125,7 +125,7 @@ open class DiscussionListRecyclerAdapter( } callback.onRefreshFinished() onCallbackFinished(ApiType.API) - adapterToRecyclerViewCallback.setIsEmpty(size() == 0) + adapterToRecyclerViewCallback?.setIsEmpty(size() == 0) } private fun getHeaderType(discussionTopicHeader: DiscussionTopicHeader): String { diff --git a/apps/student/src/main/java/com/instructure/student/features/grades/GradesListFragment.kt b/apps/student/src/main/java/com/instructure/student/features/grades/GradesListFragment.kt index d5b10ebda6..eb8dfb874d 100644 --- a/apps/student/src/main/java/com/instructure/student/features/grades/GradesListFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/features/grades/GradesListFragment.kt @@ -74,7 +74,7 @@ class GradesListFragment : ParentFragment(), Bookmarkable { private var gradingScheme = emptyList() private lateinit var allTermsGradingPeriod: GradingPeriod - private lateinit var recyclerAdapter: GradesListRecyclerAdapter + private var recyclerAdapter: GradesListRecyclerAdapter? = null private val course: Course get() = canvasContext as Course @@ -106,9 +106,9 @@ class GradesListFragment : ParentFragment(), Bookmarkable { //check to see if grade is empty for reset if (whatIf == null) { assignment.submission = null - recyclerAdapter.assignmentsHash[assignment.id]?.submission = null + recyclerAdapter?.assignmentsHash?.get(assignment.id)?.submission = null } else { - recyclerAdapter.assignmentsHash[assignment.id]?.submission = Submission( + recyclerAdapter?.assignmentsHash?.get(assignment.id)?.submission = Submission( score = whatIf, grade = whatIf.toString() ) @@ -122,14 +122,17 @@ class GradesListFragment : ParentFragment(), Bookmarkable { ) view.let { configureViews(it) - configureRecyclerView(it, requireContext(), recyclerAdapter, R.id.swipeRefreshLayout, R.id.gradesEmptyView, R.id.listView) + recyclerAdapter?.let {recyclerAdapter -> + configureRecyclerView(it, requireContext(), recyclerAdapter, R.id.swipeRefreshLayout, R.id.gradesEmptyView, R.id.listView) + } + } } override fun onDestroyView() { super.onDestroyView() computeGradesJob?.cancel() - recyclerAdapter.cancel() + recyclerAdapter?.cancel() } override fun applyTheme() { @@ -143,7 +146,10 @@ class GradesListFragment : ParentFragment(), Bookmarkable { override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) - view?.let { configureRecyclerView(it, requireContext(), recyclerAdapter, R.id.swipeRefreshLayout, R.id.gradesEmptyView, R.id.listView) } + view?.let { + recyclerAdapter?.let { recyclerAdapter -> + configureRecyclerView(it, requireContext(), recyclerAdapter, R.id.swipeRefreshLayout, R.id.gradesEmptyView, R.id.listView) } + } } private fun configureViews(rootView: View) { @@ -169,7 +175,7 @@ class GradesListFragment : ParentFragment(), Bookmarkable { computeGrades(showTotalCheckBox.isChecked, -1) } else { val gradeString = getGradeString( - recyclerAdapter.courseGrade, + recyclerAdapter?.courseGrade, !isChecked ) txtOverallGrade.text = gradeString @@ -182,24 +188,26 @@ class GradesListFragment : ParentFragment(), Bookmarkable { whatIfView.setOnClickListener { showWhatIfCheckBox.toggle() } showWhatIfCheckBox.setOnCheckedChangeListener { _, _ -> - val currentScoreVal = recyclerAdapter.courseGrade?.currentScore ?: 0.0 + val currentScoreVal = recyclerAdapter?.courseGrade?.currentScore ?: 0.0 val currentScore = NumberHelper.doubleToPercentage(currentScoreVal) if (!showWhatIfCheckBox.isChecked) { txtOverallGrade.text = currentScore - } else if (recyclerAdapter.whatIfGrade != null) { - txtOverallGrade.text = NumberHelper.doubleToPercentage(recyclerAdapter.whatIfGrade) + } else if (recyclerAdapter?.whatIfGrade != null) { + recyclerAdapter?.let { + txtOverallGrade.text = NumberHelper.doubleToPercentage(it.whatIfGrade) + } } // If the user is turning off what if grades we need to do a full refresh, should be // cached data, so fast. if (!showWhatIfCheckBox.isChecked) { - recyclerAdapter.whatIfGrade = null - recyclerAdapter.loadCachedData() + recyclerAdapter?.whatIfGrade = null + recyclerAdapter?.loadCachedData() } else { // Only log when what if grades is checked on Analytics.logEvent(AnalyticsEventConstants.WHAT_IF_GRADES) - recyclerAdapter.notifyDataSetChanged() + recyclerAdapter?.notifyDataSetChanged() } } } @@ -270,12 +278,12 @@ class GradesListFragment : ParentFragment(), Bookmarkable { override fun onNothingSelected(parent: AdapterView<*>?) {} override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { // The current item must always be set first - recyclerAdapter.currentGradingPeriod = termAdapter?.getItem(position) + recyclerAdapter?.currentGradingPeriod = termAdapter?.getItem(position) if (termAdapter?.getItem(position)?.title == getString(R.string.allGradingPeriods)) { - recyclerAdapter.loadData() + recyclerAdapter?.loadData() } else { if (termAdapter?.isEmpty == false) { - recyclerAdapter.loadAssignmentsForGradingPeriod( + recyclerAdapter?.loadAssignmentsForGradingPeriod( gradingPeriodID = termAdapter?.getItem(position)?.id.orDefault(), refreshFirst = true, forceNetwork = true @@ -290,8 +298,10 @@ class GradesListFragment : ParentFragment(), Bookmarkable { } // If we have a "current" grading period select it - if (recyclerAdapter.currentGradingPeriod != null) { - val position = termAdapter?.getPositionForId(recyclerAdapter.currentGradingPeriod?.id ?: -1) ?: -1 + if (recyclerAdapter?.currentGradingPeriod != null) { + val position = recyclerAdapter?.let { + termAdapter?.getPositionForId(it.currentGradingPeriod?.id ?: -1) ?: -1 + } ?: -1 if (position != -1) { termSpinner.setSelection(position) } else { @@ -335,7 +345,7 @@ class GradesListFragment : ParentFragment(), Bookmarkable { } private fun lockGrade(isLocked: Boolean) { - if (isLocked || recyclerAdapter.isAllGradingPeriodsSelected && !course.isTotalsForAllGradingPeriodsEnabled) { + if (isLocked || recyclerAdapter?.isAllGradingPeriodsSelected == true && !course.isTotalsForAllGradingPeriodsEnabled) { binding.txtOverallGrade.setInvisible() binding.lockedGradeImage.setVisible() binding.gradeToggleView.setGone() @@ -351,25 +361,27 @@ class GradesListFragment : ParentFragment(), Bookmarkable { private fun computeGrades(isShowTotalGrade: Boolean, lastPositionChanged: Int) { computeGradesJob = weave { val result = inBackground { - if (!isShowTotalGrade) { - if (course.isApplyAssignmentGroupWeights) { - calcGradesTotal(recyclerAdapter.assignmentGroups) - } else { - calcGradesTotalNoWeight(recyclerAdapter.assignmentGroups) - } - } else { //Calculates grade based on only graded assignments - if (course.isApplyAssignmentGroupWeights) { - calcGradesGraded(recyclerAdapter.assignmentGroups) - } else { - calcGradesGradedNoWeight(recyclerAdapter.assignmentGroups) + recyclerAdapter?.let { recyclerAdapter -> + if (!isShowTotalGrade) { + if (course.isApplyAssignmentGroupWeights) { + calcGradesTotal(recyclerAdapter.assignmentGroups) + } else { + calcGradesTotalNoWeight(recyclerAdapter.assignmentGroups) + } + } else { //Calculates grade based on only graded assignments + if (course.isApplyAssignmentGroupWeights) { + calcGradesGraded(recyclerAdapter.assignmentGroups) + } else { + calcGradesGradedNoWeight(recyclerAdapter.assignmentGroups) + } } } } - recyclerAdapter.whatIfGrade = result + recyclerAdapter?.whatIfGrade = result binding.txtOverallGrade.text = NumberHelper.doubleToPercentage(result) - if(lastPositionChanged >= 0) recyclerAdapter.notifyItemChanged(lastPositionChanged) + if(lastPositionChanged >= 0) recyclerAdapter?.notifyItemChanged(lastPositionChanged) } } @@ -392,7 +404,7 @@ class GradesListFragment : ParentFragment(), Bookmarkable { var totalPoints = 0.0 val weight = g.groupWeight for (a in g.assignments) { - val tempAssignment = recyclerAdapter.assignmentsHash[a.id].takeIf { !it?.omitFromFinalGrade.orDefault() } + val tempAssignment = recyclerAdapter?.assignmentsHash?.get(a.id).takeIf { !it?.omitFromFinalGrade.orDefault() } val tempSub = tempAssignment?.submission if (tempSub?.grade != null && tempAssignment.submissionTypesRaw.isNotEmpty()) { earnedPoints += tempSub.score @@ -427,7 +439,7 @@ class GradesListFragment : ParentFragment(), Bookmarkable { val weight = g.groupWeight var assignCount = 0 for (a in g.assignments) { - val tempAssignment = recyclerAdapter.assignmentsHash[a.id].takeIf { !it?.omitFromFinalGrade.orDefault() } + val tempAssignment = recyclerAdapter?.assignmentsHash?.get(a.id).takeIf { !it?.omitFromFinalGrade.orDefault() } val tempSub = tempAssignment?.submission if (tempSub?.grade != null && tempAssignment.submissionTypesRaw.isNotEmpty() && Const.PENDING_REVIEW != tempSub.workflowState) { assignCount++ // Determines if a group contains assignments @@ -476,7 +488,7 @@ class GradesListFragment : ParentFragment(), Bookmarkable { var totalPoints = 0.0 for (g in groups) { for (a in g.assignments) { - val tempAssignment = recyclerAdapter.assignmentsHash[a.id].takeIf { !it?.omitFromFinalGrade.orDefault() } + val tempAssignment = recyclerAdapter?.assignmentsHash?.get(a.id).takeIf { !it?.omitFromFinalGrade.orDefault() } val tempSub = tempAssignment?.submission if (tempSub?.grade != null && tempAssignment.submissionTypesRaw.isNotEmpty() && Const.PENDING_REVIEW != tempSub.workflowState) { earnedPoints += tempSub.score @@ -510,7 +522,7 @@ class GradesListFragment : ParentFragment(), Bookmarkable { var earnedPoints = 0.0 for (g in groups) { for (a in g.assignments) { - val tempAssignment = recyclerAdapter.assignmentsHash[a.id].takeIf { !it?.omitFromFinalGrade.orDefault() } + val tempAssignment = recyclerAdapter?.assignmentsHash?.get(a.id).takeIf { !it?.omitFromFinalGrade.orDefault() } val tempSub = tempAssignment?.submission if (tempSub?.grade != null && tempAssignment.submissionTypesRaw.isNotEmpty()) { totalPoints += tempAssignment.pointsPossible diff --git a/apps/student/src/main/java/com/instructure/student/features/grades/GradesListRecyclerAdapter.kt b/apps/student/src/main/java/com/instructure/student/features/grades/GradesListRecyclerAdapter.kt index bc534a568d..a0cd507ec8 100644 --- a/apps/student/src/main/java/com/instructure/student/features/grades/GradesListRecyclerAdapter.kt +++ b/apps/student/src/main/java/com/instructure/student/features/grades/GradesListRecyclerAdapter.kt @@ -21,11 +21,6 @@ import android.content.Context import android.view.View import android.widget.Toast import androidx.recyclerview.widget.RecyclerView -import com.instructure.canvasapi2.StatusCallback -import com.instructure.canvasapi2.managers.AssignmentManager -import com.instructure.canvasapi2.managers.CourseManager -import com.instructure.canvasapi2.managers.EnrollmentManager -import com.instructure.canvasapi2.managers.SubmissionManager import com.instructure.canvasapi2.models.Assignment import com.instructure.canvasapi2.models.AssignmentGroup import com.instructure.canvasapi2.models.CanvasContext @@ -33,9 +28,7 @@ import com.instructure.canvasapi2.models.Course import com.instructure.canvasapi2.models.CourseGrade import com.instructure.canvasapi2.models.Enrollment import com.instructure.canvasapi2.models.GradingPeriod -import com.instructure.canvasapi2.models.GradingPeriodResponse import com.instructure.canvasapi2.models.GradingSchemeRow -import com.instructure.canvasapi2.models.Submission import com.instructure.canvasapi2.utils.ApiPrefs import com.instructure.canvasapi2.utils.isNullOrEmpty import com.instructure.pandarecycler.util.GroupSortedList @@ -117,7 +110,7 @@ open class GradesListRecyclerAdapter( } fun loadCachedData() { - adapterToRecyclerViewCallback.refresh() + adapterToRecyclerViewCallback?.refresh() resetData() loadData(false) } diff --git a/apps/student/src/main/java/com/instructure/student/features/modules/list/ModuleListFragment.kt b/apps/student/src/main/java/com/instructure/student/features/modules/list/ModuleListFragment.kt index 134ea38aea..1d9e96862c 100644 --- a/apps/student/src/main/java/com/instructure/student/features/modules/list/ModuleListFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/features/modules/list/ModuleListFragment.kt @@ -64,7 +64,7 @@ class ModuleListFragment : ParentFragment(), Bookmarkable { private lateinit var recyclerBinding: PandaRecyclerRefreshLayoutBinding private var canvasContext: CanvasContext by ParcelableArg(key = Const.CANVAS_CONTEXT) - private lateinit var recyclerAdapter: ModuleListRecyclerAdapter + private var recyclerAdapter: ModuleListRecyclerAdapter? = null @Inject lateinit var repository: ModuleListRepository @@ -85,7 +85,7 @@ class ModuleListFragment : ParentFragment(), Bookmarkable { } override fun onDestroy() { - recyclerAdapter.cancel() + recyclerAdapter?.cancel() super.onDestroy() } @@ -105,7 +105,7 @@ class ModuleListFragment : ParentFragment(), Bookmarkable { override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) - if (recyclerAdapter.size() == 0) { + if (recyclerAdapter?.size() == 0) { recyclerBinding.emptyView.changeTextSize() if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { if (isTablet) { @@ -160,9 +160,9 @@ class ModuleListFragment : ParentFragment(), Bookmarkable { if (isLocked) return // Remove all the subheaders and stuff. - val groups = recyclerAdapter.groups + val groups = recyclerAdapter?.groups ?: arrayListOf() - val moduleItemsArray = groups.indices.mapTo(ArrayList()) { recyclerAdapter.getItems(groups[it]) } + val moduleItemsArray = groups.indices.mapTo(ArrayList()) { recyclerAdapter?.getItems(groups[it]) ?: arrayListOf() } val moduleHelper = ModuleProgressionUtility.prepareModulesForCourseProgression( requireContext(), moduleItem.id, groups, moduleItemsArray ) @@ -184,16 +184,16 @@ class ModuleListFragment : ParentFragment(), Bookmarkable { // We need to force the empty view to be visible to use it for errors on refresh recyclerBinding.emptyView.setVisible() setEmptyView(recyclerBinding.emptyView, R.drawable.ic_panda_nomodules, R.string.modulesLocked, R.string.modulesLockedSubtext) - } else if (recyclerAdapter.size() == 0) { + } else if (recyclerAdapter?.size() == 0) { setEmptyView(recyclerBinding.emptyView, R.drawable.ic_panda_nomodules, R.string.noModules, R.string.noModulesSubtext) } else if (!arguments?.getString(MODULE_ID).isNullOrEmpty()) { // We need to delay scrolling until the expand animation has completed, otherwise modules // that appear near the end of the list will not have the extra 'expanded' space needed // to scroll as far as possible toward the top recyclerBinding.listView.postDelayed({ - val groupPosition = recyclerAdapter.getGroupItemPosition(arguments!!.getString( + val groupPosition = recyclerAdapter?.getGroupItemPosition(arguments!!.getString( MODULE_ID - )!!.toLong()) + )!!.toLong()) ?: -1 if (groupPosition >= 0) { val lm = recyclerBinding.listView.layoutManager as? LinearLayoutManager lm?.scrollToPositionWithOffset(groupPosition, 0) @@ -203,22 +203,24 @@ class ModuleListFragment : ParentFragment(), Bookmarkable { } } }) - configureRecyclerView(requireView(), requireContext(), recyclerAdapter, R.id.swipeRefreshLayout, R.id.emptyView, R.id.listView) + recyclerAdapter?.let { + configureRecyclerView(requireView(), requireContext(), it, R.id.swipeRefreshLayout, R.id.emptyView, R.id.listView) + } } fun notifyOfItemChanged(`object`: ModuleObject?, item: ModuleItem?) { if (item == null || `object` == null) return - recyclerAdapter.addOrUpdateItem(`object`, item) + recyclerAdapter?.addOrUpdateItem(`object`, item) } - fun refreshModuleList() = recyclerAdapter.updateMasteryPathItems() + fun refreshModuleList() = recyclerAdapter?.updateMasteryPathItems() /** * Update the list without clearing the data or collapsing headers. Used to update possibly updated * items (like a page that has now been viewed) */ - private fun updateList(moduleObject: ModuleObject) = recyclerAdapter.updateWithoutResettingViews(moduleObject) + private fun updateList(moduleObject: ModuleObject) = recyclerAdapter?.updateWithoutResettingViews(moduleObject) // region Bus Events @@ -227,7 +229,7 @@ class ModuleListFragment : ParentFragment(), Bookmarkable { fun onModuleUpdated(event: ModuleUpdatedEvent) { event.once(javaClass.simpleName) { updateList(it) - recyclerAdapter.notifyDataSetChanged() + recyclerAdapter?.notifyDataSetChanged() } } // endregion diff --git a/apps/student/src/main/java/com/instructure/student/features/modules/progression/CourseModuleProgressionFragment.kt b/apps/student/src/main/java/com/instructure/student/features/modules/progression/CourseModuleProgressionFragment.kt index 9bf146ebba..7338f5d9a9 100644 --- a/apps/student/src/main/java/com/instructure/student/features/modules/progression/CourseModuleProgressionFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/features/modules/progression/CourseModuleProgressionFragment.kt @@ -127,8 +127,8 @@ class CourseModuleProgressionFragment : ParentFragment(), Bookmarkable { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - binding.prevItem.background = ColorKeeper.getColoredDrawable(requireActivity(), R.drawable.ic_chevron_left, canvasContext.textAndIconColor) - binding.nextItem.background = ColorKeeper.getColoredDrawable(requireActivity(), R.drawable.ic_chevron_right, canvasContext.textAndIconColor) + binding.prevItem.setImageDrawable(ColorKeeper.getColoredDrawable(requireActivity(), R.drawable.ic_chevron_left, canvasContext.textAndIconColor)) + binding.nextItem.setImageDrawable(ColorKeeper.getColoredDrawable(requireActivity(), R.drawable.ic_chevron_right, canvasContext.textAndIconColor)) } override fun onActivityCreated(savedInstanceState: Bundle?) { diff --git a/apps/student/src/main/java/com/instructure/student/features/modules/util/ModuleUtility.kt b/apps/student/src/main/java/com/instructure/student/features/modules/util/ModuleUtility.kt index 4a3bfe25ac..fdea8d6c99 100644 --- a/apps/student/src/main/java/com/instructure/student/features/modules/util/ModuleUtility.kt +++ b/apps/student/src/main/java/com/instructure/student/features/modules/util/ModuleUtility.kt @@ -56,11 +56,7 @@ object ModuleUtility { syncedFileIds: List, context: Context ): Fragment? = when (item.type) { - "Page" -> { - createFragmentWithOfflineCheck(isOnline, course, item, syncedTabs, context, setOf(Tab.PAGES_ID)) { - PageDetailsFragment.newInstance(PageDetailsFragment.makeRoute(course, item.title, item.pageUrl, navigatedFromModules)) - } - } + "Page" -> PageDetailsFragment.newInstance(PageDetailsFragment.makeRoute(course, item.title, item.pageUrl, navigatedFromModules)) "Assignment" -> { createFragmentWithOfflineCheck(isOnline, course, item, syncedTabs, context, setOf(Tab.ASSIGNMENTS_ID, Tab.GRADES_ID, Tab.SYLLABUS_ID)) { AssignmentDetailsFragment.newInstance(makeRoute(course, getAssignmentId(item))) @@ -78,10 +74,8 @@ object ModuleUtility { "Locked" -> LockedModuleItemFragment.newInstance(LockedModuleItemFragment.makeRoute(course, item.title!!, item.moduleDetails?.lockExplanation ?: "")) "SubHeader" -> null // Don't do anything with headers, they're just dividers so we don't show them here. "Quiz" -> { - createFragmentWithOfflineCheck(isOnline, course, item, syncedTabs, context, setOf(Tab.QUIZZES_ID)) { - val apiURL = removeDomain(item.url) - ModuleQuizDecider.newInstance(ModuleQuizDecider.makeRoute(course, item.htmlUrl!!, apiURL!!, item.contentId)) - } + val apiURL = removeDomain(item.url) + ModuleQuizDecider.newInstance(ModuleQuizDecider.makeRoute(course, item.htmlUrl!!, apiURL!!, item.contentId)) } "ChooseAssignmentGroup" -> { createFragmentWithOfflineCheck(isOnline, course, item, syncedTabs, context) { diff --git a/apps/student/src/main/java/com/instructure/student/features/pages/list/PageListFragment.kt b/apps/student/src/main/java/com/instructure/student/features/pages/list/PageListFragment.kt index 811db00ae2..740d0c4816 100644 --- a/apps/student/src/main/java/com/instructure/student/features/pages/list/PageListFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/features/pages/list/PageListFragment.kt @@ -62,7 +62,7 @@ class PageListFragment : ParentFragment(), Bookmarkable { private var canvasContext: CanvasContext by ParcelableArg(key = Const.CANVAS_CONTEXT) - private lateinit var recyclerAdapter: PageListRecyclerAdapter + private var recyclerAdapter: PageListRecyclerAdapter? = null private var defaultSelectedPageTitle = PageListRecyclerAdapter.FRONT_PAGE_DETERMINER // blank string is used to determine front page private var isShowFrontPage by BooleanArg(key = SHOW_FRONT_PAGE) @@ -73,7 +73,7 @@ class PageListFragment : ParentFragment(), Bookmarkable { @Subscribe fun onUpdatePage(event: PageUpdatedEvent) { event.once(javaClass.simpleName) { - recyclerAdapter.refresh() + recyclerAdapter?.refresh() } } @@ -88,7 +88,7 @@ class PageListFragment : ParentFragment(), Bookmarkable { } override fun onDestroyView() { - recyclerAdapter.cancel() + recyclerAdapter?.cancel() super.onDestroyView() } @@ -120,7 +120,9 @@ class PageListFragment : ParentFragment(), Bookmarkable { } }, defaultSelectedPageTitle) - configureRecyclerView(rootView!!, requireContext(), recyclerAdapter, R.id.swipeRefreshLayout, R.id.emptyView, R.id.listView) + recyclerAdapter?.let { + configureRecyclerView(rootView!!, requireContext(), it, R.id.swipeRefreshLayout, R.id.emptyView, R.id.listView) + } } override fun onActivityCreated(savedInstanceState: Bundle?) { @@ -137,7 +139,9 @@ class PageListFragment : ParentFragment(), Bookmarkable { override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) - configureRecyclerView(rootView!!, requireContext(), recyclerAdapter, R.id.swipeRefreshLayout, R.id.emptyView, R.id.listView) + recyclerAdapter?.let { + configureRecyclerView(rootView!!, requireContext(), it, R.id.swipeRefreshLayout, R.id.emptyView, R.id.listView) + } recyclerBinding.emptyView.changeTextSize() if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { if (isTablet) { @@ -172,7 +176,7 @@ class PageListFragment : ParentFragment(), Bookmarkable { } else { recyclerBinding.emptyView.emptyViewText(getString(R.string.noItemsMatchingQuery, query)) } - recyclerAdapter.searchQuery = query + recyclerAdapter?.searchQuery = query } ViewStyler.themeToolbarColored(requireActivity(), toolbar, canvasContext) } diff --git a/apps/student/src/main/java/com/instructure/student/features/pages/list/PageListRecyclerAdapter.kt b/apps/student/src/main/java/com/instructure/student/features/pages/list/PageListRecyclerAdapter.kt index 12b3ce5d5d..a759e1c3e3 100644 --- a/apps/student/src/main/java/com/instructure/student/features/pages/list/PageListRecyclerAdapter.kt +++ b/apps/student/src/main/java/com/instructure/student/features/pages/list/PageListRecyclerAdapter.kt @@ -109,7 +109,7 @@ open class PageListRecyclerAdapter( onCallbackFinished() } catch { if (itemCount == 0 || !APIHelper.hasNetworkConnection()) { - adapterToRecyclerViewCallback.setIsEmpty(true) + adapterToRecyclerViewCallback?.setIsEmpty(true) } else { context.toast(R.string.errorOccurred) } diff --git a/apps/student/src/main/java/com/instructure/student/features/quiz/list/QuizListRecyclerAdapter.kt b/apps/student/src/main/java/com/instructure/student/features/quiz/list/QuizListRecyclerAdapter.kt index c65219affd..24988615b6 100644 --- a/apps/student/src/main/java/com/instructure/student/features/quiz/list/QuizListRecyclerAdapter.kt +++ b/apps/student/src/main/java/com/instructure/student/features/quiz/list/QuizListRecyclerAdapter.kt @@ -90,7 +90,7 @@ class QuizListRecyclerAdapter( } adapterToFragmentCallback?.onRefreshFinished() - if (size() == 0) adapterToRecyclerViewCallback.setIsEmpty(true) + if (size() == 0) adapterToRecyclerViewCallback?.setIsEmpty(true) notifyDataSetChanged() } diff --git a/apps/student/src/main/java/com/instructure/student/fragment/ApplicationSettingsFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/ApplicationSettingsFragment.kt index 91ac257182..3415885946 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/ApplicationSettingsFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/ApplicationSettingsFragment.kt @@ -181,11 +181,8 @@ class ApplicationSettingsFragment : ParentFragment() { private fun setUpSyncSettings() { lifecycleScope.launch { - if (!featureFlagProvider.offlineEnabled()) { - binding.offlineContentDivider.setGone() - binding.offlineContentTitle.setGone() - binding.offlineSyncSettingsContainer.setGone() - } else { + val offlineEnabled = (activity as? SettingsActivity)?.offlineEnabled ?: false + if (offlineEnabled) { syncSettingsFacade.getSyncSettingsListenable().observe(viewLifecycleOwner) { syncSettings -> if (syncSettings == null) { binding.offlineSyncSettingsContainer.setGone() @@ -201,6 +198,10 @@ class ApplicationSettingsFragment : ParentFragment() { binding.offlineSyncSettingsContainer.onClick { addFragment(SyncSettingsFragment.newInstance()) } + } else { + binding.offlineContentDivider.setGone() + binding.offlineContentTitle.setGone() + binding.offlineSyncSettingsContainer.setGone() } } } diff --git a/apps/student/src/main/java/com/instructure/student/fragment/DashboardFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/DashboardFragment.kt index 2bfd905eaf..b599ab9883 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/DashboardFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/DashboardFragment.kt @@ -31,11 +31,14 @@ import androidx.lifecycle.lifecycleScope import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView +import androidx.work.WorkInfo.State +import androidx.work.WorkManager +import androidx.work.WorkQuery import com.instructure.canvasapi2.managers.CourseNicknameManager import com.instructure.canvasapi2.managers.UserManager import com.instructure.canvasapi2.models.* -import com.instructure.canvasapi2.utils.APIHelper import com.instructure.canvasapi2.utils.pageview.PageView import com.instructure.canvasapi2.utils.weave.awaitApi import com.instructure.canvasapi2.utils.weave.catch @@ -44,9 +47,12 @@ import com.instructure.interactions.router.Route import com.instructure.pandautils.analytics.SCREEN_VIEW_DASHBOARD import com.instructure.pandautils.analytics.ScreenView import com.instructure.pandautils.binding.viewBinding +import com.instructure.pandautils.features.dashboard.DashboardCourseItem import com.instructure.pandautils.features.dashboard.edit.EditDashboardFragment import com.instructure.pandautils.features.dashboard.notifications.DashboardNotificationsFragment import com.instructure.pandautils.features.offline.offlinecontent.OfflineContentFragment +import com.instructure.pandautils.features.offline.sync.AggregateProgressObserver +import com.instructure.pandautils.features.offline.sync.OfflineSyncWorker import com.instructure.pandautils.utils.* import com.instructure.student.R import com.instructure.student.adapter.DashboardRecyclerAdapter @@ -61,6 +67,7 @@ import com.instructure.student.events.ShowGradesToggledEvent import com.instructure.student.features.coursebrowser.CourseBrowserFragment import com.instructure.student.features.dashboard.DashboardRepository import com.instructure.student.flutterChannels.FlutterComm +import com.instructure.student.holders.CourseViewHolder import com.instructure.student.interfaces.CourseAdapterToFragmentCallback import com.instructure.student.router.RouteMatcher import com.instructure.student.util.StudentPrefs @@ -86,6 +93,12 @@ class DashboardFragment : ParentFragment() { @Inject lateinit var networkStateProvider: NetworkStateProvider + @Inject + lateinit var aggregateProgressObserver: AggregateProgressObserver + + @Inject + lateinit var workManager: WorkManager + private val binding by viewBinding(FragmentCourseGridBinding::bind) private lateinit var recyclerBinding: CourseGridRecyclerRefreshLayoutBinding @@ -96,6 +109,8 @@ class DashboardFragment : ParentFragment() { private var courseColumns: Int = LIST_SPAN_COUNT private var groupColumns: Int = LIST_SPAN_COUNT + private val runningWorkers = mutableSetOf() + private val somethingChangedReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent?) { if (recyclerAdapter != null && intent?.extras?.getBoolean(Const.COURSE_FAVORITES) == true) { @@ -120,6 +135,28 @@ class DashboardFragment : ParentFragment() { recyclerAdapter?.refresh() if (online) recyclerBinding.swipeRefreshLayout.isRefreshing = true } + + lifecycleScope.launch { + if (featureFlagProvider.offlineEnabled()) { + subscribeToOfflineSyncUpdates() + } + } + } + + private fun subscribeToOfflineSyncUpdates() { + val workQuery = WorkQuery.Builder.fromTags(listOf(OfflineSyncWorker.PERIODIC_TAG, OfflineSyncWorker.ONE_TIME_TAG)).build() + workManager.getWorkInfosLiveData(workQuery).observe(this) { workInfos -> + workInfos.forEach { workInfo -> + if (workInfo.state == State.RUNNING) { + runningWorkers.add(workInfo.id.toString()) + } + } + + if (workInfos?.any { (it.state == State.SUCCEEDED || it.state == State.FAILED) && runningWorkers.contains(it.id.toString()) } == true) { + recyclerAdapter?.silentRefresh() + runningWorkers.clear() + } + } } @@ -237,16 +274,13 @@ class DashboardFragment : ParentFragment() { } recyclerBinding.listView.fadeAnimationWithAction { - courseColumns = if (StudentPrefs.listDashboard) LIST_SPAN_COUNT else resources.getInteger(R.integer.course_card_columns) - groupColumns = if (StudentPrefs.listDashboard) LIST_SPAN_COUNT else resources.getInteger(R.integer.group_card_columns) - (recyclerBinding.listView.layoutManager as? GridLayoutManager)?.spanCount = courseColumns * groupColumns - view?.post { recyclerAdapter?.notifyDataSetChanged() } + configureGridSize() } } override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) - configureRecyclerView() + configureGridSize() if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { if (isTablet) { binding.emptyCoursesView.setGuidelines(.37f, .49f, .6f, .7f, .12f, .88f) @@ -263,6 +297,16 @@ class DashboardFragment : ParentFragment() { } } + private fun configureGridSize() { + courseColumns = + if (StudentPrefs.listDashboard) LIST_SPAN_COUNT else resources.getInteger(R.integer.course_card_columns) + groupColumns = + if (StudentPrefs.listDashboard) LIST_SPAN_COUNT else resources.getInteger(R.integer.group_card_columns) + (recyclerBinding.listView.layoutManager as? GridLayoutManager)?.spanCount = + courseColumns * groupColumns + view?.post { recyclerAdapter?.notifyDataSetChanged() } + } + private fun configureRecyclerView() = with(binding) { // Set up GridLayoutManager courseColumns = if (StudentPrefs.listDashboard) LIST_SPAN_COUNT else resources.getInteger(R.integer.course_card_columns) @@ -307,12 +351,92 @@ class DashboardFragment : ParentFragment() { recyclerBinding.listView.clipToPadding = false emptyCoursesView.onClickAddCourses { - if (!APIHelper.hasNetworkConnection()) { - toast(R.string.notAvailableOffline) - } else { - RouteMatcher.route(requireActivity(), EditDashboardFragment.makeRoute()) - } + RouteMatcher.route(requireActivity(), EditDashboardFragment.makeRoute()) } + + addItemTouchHelperForCardReorder() + } + + private fun addItemTouchHelperForCardReorder() { + val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback( + ItemTouchHelper.START or ItemTouchHelper.END or ItemTouchHelper.DOWN or ItemTouchHelper.UP, + 0 + ) { + + private var itemToMove: DashboardCourseItem? = null + + // We need to consider other items in the recyclerview when dealing with positions. + // In the callbacks we get the adapter position, but we want to get the course items position so we use this. + // If there is any other recyclerview item added above the courses this should be modified. + private val POSITION_MODIFIER = 1 + + override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { + super.onSelectedChanged(viewHolder, actionState) + + if (viewHolder == null) return + val fromPosition = viewHolder.bindingAdapterPosition + val fromItem = recyclerAdapter?.getItem(DashboardRecyclerAdapter.ItemType.COURSE_HEADER, fromPosition - POSITION_MODIFIER) as? DashboardCourseItem + + itemToMove = fromItem + } + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + val fromPosition = viewHolder.bindingAdapterPosition + val toPosition = target.bindingAdapterPosition + + val itemsSize = recyclerAdapter?.getItems(DashboardRecyclerAdapter.ItemType.COURSE_HEADER)?.size ?: 0 + + if (toPosition - POSITION_MODIFIER in 0..< itemsSize) { + recyclerAdapter?.notifyItemMoved(fromPosition, toPosition) + } + + return true + } + + override fun getDragDirs( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder + ): Int { + return if (viewHolder is CourseViewHolder && networkStateProvider.isOnline()) { + ItemTouchHelper.START or ItemTouchHelper.END or ItemTouchHelper.DOWN or ItemTouchHelper.UP + } else 0 + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit + + override fun clearView( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder + ) { + val finishingPosition = viewHolder.bindingAdapterPosition + + itemToMove?.let { + recyclerAdapter?.moveItems(DashboardRecyclerAdapter.ItemType.COURSE_HEADER, it, finishingPosition - 1) + recyclerAdapter?.notifyDataSetChanged() + itemToMove == null + } + + val courseItems = recyclerAdapter?.getItems(DashboardRecyclerAdapter.ItemType.COURSE_HEADER) + ?.mapNotNull { it as? DashboardCourseItem } ?: emptyList() + val positions = courseItems + .mapIndexed { index, course -> Pair(course.course.contextId, index) } + .toMap() + + val dashboardPositions = DashboardPositions(positions) + lifecycleScope.launch { + val updateResult = repository.updateDashboardPositions(dashboardPositions) + if (updateResult.isFail) { + toast(R.string.failedToUpdateDashboardOrder) + } + } + } + }) + + itemTouchHelper.attachToRecyclerView(recyclerBinding.listView) } override fun onStart() { diff --git a/apps/student/src/main/java/com/instructure/student/fragment/NotificationListFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/NotificationListFragment.kt index cdb1024802..d1adbbfd09 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/NotificationListFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/NotificationListFragment.kt @@ -65,19 +65,19 @@ class NotificationListFragment : ParentFragment(), Bookmarkable, FragmentManager private var canvasContext by ParcelableArg(key = Const.CANVAS_CONTEXT) - private lateinit var recyclerAdapter: NotificationListRecyclerAdapter + private var recyclerAdapter: NotificationListRecyclerAdapter? = null private var adapterToFragmentCallback: NotificationAdapterToFragmentCallback = object : NotificationAdapterToFragmentCallback { override fun onRowClicked(streamItem: StreamItem, position: Int, isOpenDetail: Boolean) { - recyclerAdapter.setSelectedPosition(position) + recyclerAdapter?.setSelectedPosition(position) onRowClick(streamItem) } override fun onRefreshFinished() { setRefreshing(false) binding.editOptions.setGone() - if (recyclerAdapter.size() == 0) { + if (recyclerAdapter?.size() == 0) { setEmptyView(recyclerBinding.emptyView, R.drawable.ic_panda_noalerts, R.string.noNotifications, R.string.noNotificationsSubtext) if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { recyclerBinding.emptyView.setGuidelines(.2f, .7f, .74f, .15f, .85f) @@ -112,21 +112,23 @@ class NotificationListFragment : ParentFragment(), Bookmarkable, FragmentManager override fun onViewCreated(view: View, savedInstanceState: Bundle?) { recyclerBinding = PandaRecyclerRefreshLayoutBinding.bind(binding.root) recyclerAdapter = NotificationListRecyclerAdapter(requireContext(), canvasContext, adapterToFragmentCallback) - configureRecyclerView( - view, - requireContext(), - recyclerAdapter, - R.id.swipeRefreshLayout, - R.id.emptyView, - R.id.listView - ) + recyclerAdapter?.let { + configureRecyclerView( + view, + requireContext(), + it, + R.id.swipeRefreshLayout, + R.id.emptyView, + R.id.listView + ) + } recyclerBinding.listView.isSelectionEnabled = false binding.confirmButton.text = getString(R.string.delete) - binding.confirmButton.setOnClickListener { recyclerAdapter.confirmButtonClicked() } + binding.confirmButton.setOnClickListener { recyclerAdapter?.confirmButtonClicked() } binding.cancelButton.text = getString(R.string.cancel) - binding.cancelButton.setOnClickListener { recyclerAdapter.cancelButtonClicked() } + binding.cancelButton.setOnClickListener { recyclerAdapter?.cancelButtonClicked() } applyTheme() @@ -139,14 +141,14 @@ class NotificationListFragment : ParentFragment(), Bookmarkable, FragmentManager if (activity?.supportFragmentManager?.fragments?.lastOrNull()?.javaClass == this.javaClass) { if (shouldRefreshOnResume) { recyclerBinding.swipeRefreshLayout.isRefreshing = true - recyclerAdapter.refresh() + recyclerAdapter?.refresh() shouldRefreshOnResume = false } } } override fun onDestroyView() { - recyclerAdapter.cancel() + recyclerAdapter?.cancel() activity?.supportFragmentManager?.removeOnBackStackChangedListener(this) super.onDestroyView() } @@ -170,16 +172,18 @@ class NotificationListFragment : ParentFragment(), Bookmarkable, FragmentManager override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) - configureRecyclerView( - requireView(), - requireContext(), - recyclerAdapter, - R.id.swipeRefreshLayout, - R.id.emptyView, - R.id.listView, + recyclerAdapter?.let { + configureRecyclerView( + requireView(), + requireContext(), + it, + R.id.swipeRefreshLayout, + R.id.emptyView, + R.id.listView, R.string.noNotifications - ) - if (recyclerAdapter.size() == 0) { + ) + } + if (recyclerAdapter?.size() == 0) { recyclerBinding.emptyView.changeTextSize() if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { if (isTablet) { diff --git a/apps/student/src/main/java/com/instructure/student/fragment/ParentFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/ParentFragment.kt index 4cbdbeb85e..24f868fa74 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/ParentFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/ParentFragment.kt @@ -60,6 +60,7 @@ import com.instructure.pandautils.utils.LoaderUtils import com.instructure.pandautils.utils.PermissionUtils import com.instructure.pandautils.utils.getDrawableCompat import com.instructure.pandautils.utils.hasPermissions +import com.instructure.pandautils.utils.toast import com.instructure.pandautils.views.EmptyView import com.instructure.student.R import com.instructure.student.activity.VideoViewActivity @@ -381,7 +382,12 @@ abstract class ParentFragment : DialogFragment(), FragmentInteractions, Navigati } onMainThread { - LoaderUtils.restartLoaderWithBundle>(LoaderManager.getInstance(owner), openMediaBundle, loaderCallbacks, R.id.openMediaLoaderID) + try { + LoaderUtils.restartLoaderWithBundle>(LoaderManager.getInstance(owner), openMediaBundle, loaderCallbacks, R.id.openMediaLoaderID) + } catch (e: Exception) { + toast(R.string.unexpectedErrorOpeningFile) + onMediaLoadingComplete() + } } } diff --git a/apps/student/src/main/java/com/instructure/student/fragment/ToDoListFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/ToDoListFragment.kt index dc9b95e04f..0ffb0d1a1d 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/ToDoListFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/ToDoListFragment.kt @@ -51,11 +51,11 @@ class ToDoListFragment : ParentFragment() { private var canvasContext by ParcelableArg(key = Const.CANVAS_CONTEXT) - private lateinit var recyclerAdapter: TodoListRecyclerAdapter + private var recyclerAdapter: TodoListRecyclerAdapter? = null private var adapterToFragmentCallback: NotificationAdapterToFragmentCallback = object : NotificationAdapterToFragmentCallback { override fun onRowClicked(todo: ToDo, position: Int, isOpenDetail: Boolean) { - recyclerAdapter.setSelectedPosition(position) + recyclerAdapter?.setSelectedPosition(position) onRowClick(todo) } @@ -63,7 +63,7 @@ class ToDoListFragment : ParentFragment() { if (!isAdded) return setRefreshing(false) binding.editOptions.setGone() - if (recyclerAdapter.size() == 0) { + if (recyclerAdapter?.size() == 0) { setEmptyView(recyclerViewBinding.emptyView, R.drawable.ic_panda_sleeping, R.string.noTodos, R.string.noTodosSubtext) } } @@ -91,26 +91,28 @@ class ToDoListFragment : ParentFragment() { } } recyclerAdapter = TodoListRecyclerAdapter(requireContext(), canvasContext, adapterToFragmentCallback) - configureRecyclerView( - view, - requireContext(), - recyclerAdapter, - R.id.swipeRefreshLayout, - R.id.emptyView, - R.id.listView - ) + recyclerAdapter?.let { + configureRecyclerView( + view, + requireContext(), + it, + R.id.swipeRefreshLayout, + R.id.emptyView, + R.id.listView + ) + } recyclerViewBinding.listView.isSelectionEnabled = false binding.confirmButton.text = getString(R.string.markAsDone) - binding.confirmButton.setOnClickListener { recyclerAdapter.confirmButtonClicked() } + binding.confirmButton.setOnClickListener { recyclerAdapter?.confirmButtonClicked() } binding.cancelButton.setText(R.string.cancel) - binding.cancelButton.setOnClickListener { recyclerAdapter.cancelButtonClicked() } + binding.cancelButton.setOnClickListener { recyclerAdapter?.cancelButtonClicked() } - updateFilterTitle(recyclerAdapter.getFilterMode()) + updateFilterTitle(recyclerAdapter?.getFilterMode() ?: NoFilter) binding.clearFilterTextView.setOnClickListener { - recyclerAdapter.loadDataWithFilter(NoFilter) - updateFilterTitle(recyclerAdapter.getFilterMode()) + recyclerAdapter?.loadDataWithFilter(NoFilter) + updateFilterTitle(recyclerAdapter?.getFilterMode() ?: NoFilter) } } @@ -132,16 +134,18 @@ class ToDoListFragment : ParentFragment() { override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) - configureRecyclerView( - requireView(), - requireContext(), - recyclerAdapter, - R.id.swipeRefreshLayout, - R.id.emptyView, - R.id.listView, + recyclerAdapter?.let { + configureRecyclerView( + requireView(), + requireContext(), + it, + R.id.swipeRefreshLayout, + R.id.emptyView, + R.id.listView, R.string.noTodos - ) - if (recyclerAdapter.size() == 0) { + ) + } + if (recyclerAdapter?.size() == 0) { recyclerViewBinding.emptyView.changeTextSize() if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { if (isTablet) { @@ -184,15 +188,15 @@ class ToDoListFragment : ParentFragment() { private fun showCourseFilterDialog() { val choices = arrayOf(getString(R.string.favoritedCoursesLabel)) - var checkedItem = choices.indexOf(getString(recyclerAdapter.getFilterMode().titleId)) + var checkedItem = choices.indexOf(getString(recyclerAdapter?.getFilterMode()?.titleId ?: NoFilter.titleId)) val dialog = AlertDialog.Builder(requireContext()) .setTitle(R.string.filterByEllipsis) .setSingleChoiceItems(choices, checkedItem) { _, index -> checkedItem = index }.setPositiveButton(android.R.string.ok) { _, _ -> - if (checkedItem >= 0) recyclerAdapter.loadDataWithFilter(convertFilterChoiceToMode(choices[checkedItem])) - updateFilterTitle(recyclerAdapter.getFilterMode()) + if (checkedItem >= 0) recyclerAdapter?.loadDataWithFilter(convertFilterChoiceToMode(choices[checkedItem])) + updateFilterTitle(recyclerAdapter?.getFilterMode() ?: NoFilter) }.setNegativeButton(android.R.string.cancel, null) .create() @@ -216,7 +220,7 @@ class ToDoListFragment : ParentFragment() { override fun onDestroyView() { super.onDestroyView() - recyclerAdapter.cancel() + recyclerAdapter?.cancel() } companion object { diff --git a/apps/student/src/main/java/com/instructure/student/fragment/ViewImageFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/ViewImageFragment.kt index e5c586603f..bce47bc595 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/ViewImageFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/ViewImageFragment.kt @@ -91,7 +91,7 @@ class ViewImageFragment : Fragment(), ShareableFile { private val requestListener = object : RequestListener { - override fun onLoadFailed(p0: GlideException?, p1: Any?, p2: Target?, p3: Boolean): Boolean { + override fun onLoadFailed(p0: GlideException?, p1: Any?, target: Target, p3: Boolean): Boolean { binding.photoView.setGone() binding.progressBar.setGone() binding.errorContainer.setVisible() @@ -100,11 +100,17 @@ class ViewImageFragment : Fragment(), ShareableFile { return false } - override fun onResourceReady(drawable: Drawable?, p1: Any?, p2: Target?, p3: DataSource?, p4: Boolean): Boolean { + override fun onResourceReady( + resource: Drawable, + model: Any, + p2: Target?, + dataSource: DataSource, + p4: Boolean + ): Boolean { binding.progressBar.setGone() // Try to set the background color using palette if we can - (drawable as? BitmapDrawable)?.bitmap?.let { colorBackground(it) } + (resource as? BitmapDrawable)?.bitmap?.let { colorBackground(it) } return false } } diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/drawer/files/ui/SubmissionFilesAdapter.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/drawer/files/ui/SubmissionFilesAdapter.kt index f9018ce7f9..0c1c06eb89 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/drawer/files/ui/SubmissionFilesAdapter.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submissionDetails/drawer/files/ui/SubmissionFilesAdapter.kt @@ -69,17 +69,22 @@ internal class SubmissionFilesHolder(view: View) : RecyclerView.ViewHolder(view) thumbnail.setVisible(data.thumbnailUrl.isValid()) data.thumbnailUrl.validOrNull()?.let { Glide.with(root.context).load(it).listener(object : RequestListener { - override fun onLoadFailed(e: GlideException?, model: Any?, target: Target?, isFirstResource: Boolean): Boolean { + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target, + isFirstResource: Boolean + ): Boolean { fileIcon.setVisible(true) thumbnail.setVisible(false) return false } override fun onResourceReady( - resource: Drawable?, - model: Any?, + resource: Drawable, + model: Any, target: Target?, - dataSource: DataSource?, + dataSource: DataSource, isFirstResource: Boolean ): Boolean { return false diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/gradeCell/GradeCellView.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/gradeCell/GradeCellView.kt index 49b796f7ad..b5a9df7835 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/gradeCell/GradeCellView.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/gradeCell/GradeCellView.kt @@ -67,6 +67,7 @@ class GradeCellView @JvmOverloads constructor( pointsLabel.setVisible(state.showPointsLabel) completeIcon.setVisible(state.showCompleteIcon) incompleteIcon.setVisible(state.showIncompleteIcon) + yourGrade.setTextForVisibility(state.yourGrade) latePenalty.setTextForVisibility(state.latePenalty) finalGrade.setTextForVisibility(state.finalGrade) grade.setTextForVisibility(state.grade) @@ -75,7 +76,6 @@ class GradeCellView @JvmOverloads constructor( outOf.contentDescription = state.outOfContentDescription // Accent color - latePenalty.setTextColor(state.accentColor) chart.setColor(state.accentColor) completeIcon.imageTintList = ColorStateList.valueOf(state.accentColor) incompleteIcon.imageTintList = ColorStateList.valueOf(state.accentColor) diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/gradeCell/GradeCellViewState.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/gradeCell/GradeCellViewState.kt index 42cf48e75c..f5f368b06a 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/gradeCell/GradeCellViewState.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/gradeCell/GradeCellViewState.kt @@ -43,6 +43,7 @@ sealed class GradeCellViewState { val gradeCellContentDescription: String = "", val outOf: String = "", val outOfContentDescription: String = "", + val yourGrade: String = "", val latePenalty: String = "", val finalGrade: String = "", val stats: GradeStats? = null @@ -132,8 +133,8 @@ sealed class GradeCellViewState { ) } - val score = NumberHelper.formatDecimal(submission.enteredScore, 2, true) - val graphPercent = (submission.enteredScore / assignment.pointsPossible).coerceIn(0.0, 1.0).toFloat() + val score = NumberHelper.formatDecimal(submission.score, 2, true) + val graphPercent = (submission.score / assignment.pointsPossible).coerceIn(0.0, 1.0).toFloat() // If grading type is Points, don't show the grade since we're already showing it as the score var grade = if (assignment.gradingType != Assignment.POINTS_TYPE) submission.grade.orEmpty() else "" @@ -148,12 +149,15 @@ sealed class GradeCellViewState { var latePenalty = "" var finalGrade = "" + var yourGrade = "" // Adjust for late penalty, if any - if (submission.pointsDeducted ?: 0.0 > 0.0) { + if ((submission.pointsDeducted ?: 0.0) > 0.0) { grade = "" // Grade will be shown in the 'final grade' text val pointsDeducted = NumberHelper.formatDecimal(submission.pointsDeducted!!, 2, true) - latePenalty = context.getString(R.string.latePenalty, pointsDeducted) + val achievedScore = NumberHelper.formatDecimal(submission.enteredScore, 2, true) + yourGrade = context.getString(R.string.yourGrade, achievedScore) + latePenalty = context.getString(R.string.latePenaltyUpdated, pointsDeducted) finalGrade = context.getString(R.string.finalGradeFormatted, submission.grade) } @@ -190,6 +194,7 @@ sealed class GradeCellViewState { grade = grade, gradeContentDescription = accessibleGradeString, gradeCellContentDescription = gradeCellContentDescription, + yourGrade = yourGrade, latePenalty = latePenalty, finalGrade = finalGrade, stats = stats diff --git a/apps/student/src/main/java/com/instructure/student/mobius/common/ui/MobiusFragment.kt b/apps/student/src/main/java/com/instructure/student/mobius/common/ui/MobiusFragment.kt index c1e5000be0..bd4b8b96b5 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/common/ui/MobiusFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/common/ui/MobiusFragment.kt @@ -18,7 +18,6 @@ package com.instructure.student.mobius.common.ui import android.app.Activity import android.content.Context -import android.content.ContextWrapper import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -29,10 +28,24 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics import com.instructure.canvasapi2.utils.Analytics import com.instructure.interactions.FragmentInteractions import com.instructure.interactions.Navigation +import com.instructure.pandautils.utils.getFragmentActivity import com.instructure.student.BuildConfig import com.instructure.student.fragment.ParentFragment -import com.instructure.student.mobius.common.* -import com.spotify.mobius.* +import com.instructure.student.mobius.common.ConsumerQueueWrapper +import com.instructure.student.mobius.common.CoroutineConnection +import com.instructure.student.mobius.common.GlobalEventMapper +import com.instructure.student.mobius.common.GlobalEventSource +import com.instructure.student.mobius.common.LateInit +import com.instructure.student.mobius.common.MobiusExceptionLogger +import com.instructure.student.mobius.common.contraMap +import com.spotify.mobius.Connectable +import com.spotify.mobius.Connection +import com.spotify.mobius.EventSource +import com.spotify.mobius.First +import com.spotify.mobius.Init +import com.spotify.mobius.Mobius +import com.spotify.mobius.MobiusLoop +import com.spotify.mobius.Update import com.spotify.mobius.android.MobiusAndroid import com.spotify.mobius.android.runners.MainThreadWorkRunner import com.spotify.mobius.functions.Consumer @@ -176,13 +189,7 @@ abstract class MobiusView(inflater: Lay get() = parent.context protected val activity: Activity - get() = getActivity(context) - - private fun getActivity(context: Context): Activity { - if (context is Activity) return context - if (context is ContextWrapper) return getActivity(context.baseContext) - else throw IllegalStateException("Not activity context") - } + get() = context.getFragmentActivity() abstract fun onConnect(output: Consumer) diff --git a/apps/student/src/main/java/com/instructure/student/tasks/StudentLogoutTask.kt b/apps/student/src/main/java/com/instructure/student/tasks/StudentLogoutTask.kt index ec829ffbc1..59360f590b 100644 --- a/apps/student/src/main/java/com/instructure/student/tasks/StudentLogoutTask.kt +++ b/apps/student/src/main/java/com/instructure/student/tasks/StudentLogoutTask.kt @@ -25,7 +25,6 @@ import com.heapanalytics.android.Heap import com.instructure.canvasapi2.utils.ContextKeeper import com.instructure.canvasapi2.utils.tryOrNull import com.instructure.loginapi.login.tasks.LogoutTask -import com.instructure.pandautils.features.offline.sync.CourseSyncWorker import com.instructure.pandautils.features.offline.sync.OfflineSyncWorker import com.instructure.pandautils.room.offline.DatabaseProvider import com.instructure.pandautils.typeface.TypefaceBehavior @@ -79,8 +78,8 @@ class StudentLogoutTask( override fun stopOfflineSync() { val workManager = WorkManager.getInstance(ContextKeeper.appContext) workManager.apply { - cancelAllWorkByTag(CourseSyncWorker.TAG) - cancelAllWorkByTag(OfflineSyncWorker.TAG) + cancelAllWorkByTag(OfflineSyncWorker.PERIODIC_TAG) + cancelAllWorkByTag(OfflineSyncWorker.ONE_TIME_TAG) } } } diff --git a/apps/student/src/main/java/com/instructure/student/util/BaseAppManager.kt b/apps/student/src/main/java/com/instructure/student/util/BaseAppManager.kt index 51b573aaa4..09f8856ab6 100644 --- a/apps/student/src/main/java/com/instructure/student/util/BaseAppManager.kt +++ b/apps/student/src/main/java/com/instructure/student/util/BaseAppManager.kt @@ -49,6 +49,11 @@ import io.flutter.embedding.engine.dart.DartExecutor abstract class BaseAppManager : com.instructure.canvasapi2.AppManager(), AnalyticsEventHandling, Configuration.Provider { + override val workManagerConfiguration: Configuration + get() = Configuration.Builder() + .setWorkerFactory(getWorkManagerFactory()) + .build() + override fun onCreate() { super.onCreate() @@ -153,11 +158,6 @@ abstract class BaseAppManager : com.instructure.canvasapi2.AppManager(), Analyti override fun performLogoutOnAuthError() = Unit - override fun getWorkManagerConfiguration(): Configuration = - Configuration.Builder() - .setWorkerFactory(getWorkManagerFactory()) - .build() - abstract fun getWorkManagerFactory(): WorkerFactory companion object { diff --git a/apps/student/src/main/java/com/instructure/student/util/StudentLogoutHelper.kt b/apps/student/src/main/java/com/instructure/student/util/StudentLogoutHelper.kt new file mode 100644 index 0000000000..620eb7d127 --- /dev/null +++ b/apps/student/src/main/java/com/instructure/student/util/StudentLogoutHelper.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 - present Instructure, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + */ + +package com.instructure.student.util + +import com.instructure.loginapi.login.tasks.LogoutTask +import com.instructure.pandautils.room.offline.DatabaseProvider +import com.instructure.pandautils.utils.LogoutHelper +import com.instructure.student.tasks.StudentLogoutTask + +class StudentLogoutHelper : LogoutHelper { + override fun logout(databaseProvider: DatabaseProvider) { + StudentLogoutTask(LogoutTask.Type.LOGOUT, databaseProvider = databaseProvider).execute() + } +} \ No newline at end of file diff --git a/apps/student/src/main/res/layout/course_module_progression.xml b/apps/student/src/main/res/layout/course_module_progression.xml index 4918489d1c..59ac4aa00f 100644 --- a/apps/student/src/main/res/layout/course_module_progression.xml +++ b/apps/student/src/main/res/layout/course_module_progression.xml @@ -147,23 +147,25 @@ -