Skip to content

Commit

Permalink
Release Teacher 1.13.0 (40)
Browse files Browse the repository at this point in the history
  • Loading branch information
tamaskozmer authored Mar 12, 2021
1 parent 870eeda commit a37fb5a
Show file tree
Hide file tree
Showing 91 changed files with 1,222 additions and 516 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,26 @@ class AssignmentListInteractionTest : StudentTest() {
assignmentListPage.assertHasAssignment(assignment)
}

@Test
@TestMetaData(Priority.P1, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION)
fun sortAssignmentsByTimeByDefault() {
val assignment = getToAssignmentsPage()[0]
assignmentListPage.assertHasAssignment(assignment)
assignmentListPage.assertSortByButtonShowsSortByTime()
assignmentListPage.assertFindsUndatedAssignmentLabel()
}

@Test
@TestMetaData(Priority.P1, FeatureCategory.ASSIGNMENTS, TestCategory.INTERACTION)
fun sortAssignmentsByTypeWhenTypeIsSelectedInTheDialog() {
val assignment = getToAssignmentsPage()[0]

assignmentListPage.selectSortByType()

assignmentListPage.assertHasAssignment(assignment)
assignmentListPage.assertSortByButtonShowsSortByType()
}

private fun getToAssignmentsPage(assignmentCount: Int = 1): List<Assignment> {
val data = MockCanvas.init(
courseCount = 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,16 @@ package com.instructure.student.ui.pages
import android.view.View
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withChild
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withParent
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.instructure.canvasapi2.models.Assignment
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import com.instructure.canvas.espresso.scrollRecyclerView
import com.instructure.canvas.espresso.waitForMatcherWithRefreshes
import com.instructure.canvasapi2.models.Assignment
import com.instructure.dataseeding.model.AssignmentApiModel
import com.instructure.dataseeding.model.QuizApiModel
import com.instructure.espresso.OnViewWithId
import com.instructure.espresso.WaitForViewWithId
import com.instructure.espresso.WaitForViewWithText
import com.instructure.espresso.assertDisplayed
import com.instructure.espresso.click
import com.instructure.espresso.*
import com.instructure.espresso.page.BasePage
import com.instructure.espresso.page.waitForViewWithText
import com.instructure.espresso.scrollTo
import com.instructure.espresso.swipeDown
import com.instructure.student.R
import org.hamcrest.Matcher
import org.hamcrest.Matchers.allOf
Expand All @@ -46,13 +37,13 @@ import org.hamcrest.Matchers.containsString
class AssignmentListPage : BasePage(pageResId = R.id.assignmentListPage) {

private val assignmentListToolbar by OnViewWithId(R.id.toolbar)
private val gradingPeriodHeader by WaitForViewWithId(R.id.termSpinnerLayout)
private val sortByButton by OnViewWithId(R.id.sortByButton)
private val sortByTextView by OnViewWithId(R.id.sortByTextView)

// Only displayed when assignment list is empty
private val emptyView by WaitForViewWithId(R.id.emptyView, autoAssert = false)

// Only displayed when there are grading periods
private val gradingPeriodHeader by WaitForViewWithId(R.id.termSpinnerLayout, autoAssert = false)

// Only displayed when there are no assignments
private val emptyText by WaitForViewWithText(R.string.noItemsToDisplayShort, autoAssert = false)

Expand Down Expand Up @@ -140,4 +131,21 @@ class AssignmentListPage : BasePage(pageResId = R.id.assignmentListPage) {
fun assertHasGradingPeriods() {
gradingPeriodHeader.assertDisplayed()
}

fun assertSortByButtonShowsSortByTime() {
sortByTextView.check(matches(withText(R.string.sortByTime)))
}

fun assertSortByButtonShowsSortByType() {
sortByTextView.check(matches(withText(R.string.sortByType)))
}

fun assertFindsUndatedAssignmentLabel() {
onView(withText(R.string.undatedAssignments)).assertVisible()
}

fun selectSortByType() {
sortByButton.click()
onView(withText(R.string.sortByDialogTypeOption)).click()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,49 +55,49 @@ abstract class StudentTest : CanvasTest() {
/**
* Required for auto complete of page objects within tests
*/
val allCoursesPage = AllCoursesPage()
val annotationCommentListPage = AnnotationCommentListPage()
val assignmentDetailsPage = AssignmentDetailsPage()
val assignmentListPage = AssignmentListPage()
val bookmarkPage = BookmarkPage()
val calendarEventPage = CalendarEventPage()
val calendarPage = CalendarPage()
val canvasWebViewPage = CanvasWebViewPage()
val courseBrowserPage = CourseBrowserPage()
val courseGradesPage = CourseGradesPage()
val dashboardPage = DashboardPage()
val allCoursesPage = AllCoursesPage()
val discussionDetailsPage = DiscussionDetailsPage()
val discussionListPage = DiscussionListPage()
val editFavoritesPage = EditFavoritesPage()
val calendarPage = CalendarPage()
val todoPage = TodoPage()
val inboxPage = InboxPage()
val fileListPage = FileListPage()
val fileUploadPage = FileUploadPage()
val helpPage = HelpPage()
val inboxConversationPage = InboxConversationPage()
val newMessagePage = NewMessagePage()
val settingsPage = SettingsPage()
val pairObserverPage = PairObserverPage()
val inboxPage = InboxPage()
val legalPage = LegalPage()
val helpPage = HelpPage()
val loginFindSchoolPage = LoginFindSchoolPage()
val loginLandingPage = LoginLandingPage()
val loginSignInPage = LoginSignInPage()
val qrLoginPage = QRLoginPage()
val courseBrowserPage = CourseBrowserPage()
val assignmentDetailsPage = AssignmentDetailsPage()
val submissionDetailsPage = SubmissionDetailsPage()
val peopleListPage = PeopleListPage()
val personDetailsPage = PersonDetailsPage()
val moduleProgressionPage = ModuleProgressionPage()
val modulesPage = ModulesPage()
val syllabusPage = SyllabusPage()
val fileListPage = FileListPage()
val discussionListPage = DiscussionListPage()
val discussionDetailsPage = DiscussionDetailsPage()
val newMessagePage = NewMessagePage()
val notificationPage = NotificationPage()
val pageListPage = PageListPage()
val quizListPage = QuizListPage()
val urlSubmissionUploadPage = UrlSubmissionUploadPage()
val courseGradesPage = CourseGradesPage()
val moduleProgressionPage = ModuleProgressionPage()
val canvasWebViewPage = CanvasWebViewPage()
val fileUploadPage = FileUploadPage()
val annotationCommentListPage = AnnotationCommentListPage()
val pairObserverPage = PairObserverPage()
val pandaAvatarPage = PandaAvatarPage()
val peopleListPage = PeopleListPage()
val personDetailsPage = PersonDetailsPage()
val pickerSubmissionUploadPage = PickerSubmissionUploadPage()
val remoteConfigSettingsPage = RemoteConfigSettingsPage()
val profileSettingsPage = ProfileSettingsPage()
val calendarEventPage = CalendarEventPage()
val qrLoginPage = QRLoginPage()
val quizListPage = QuizListPage()
val quizTakingPage = QuizTakingPage()
val pandaAvatarPage = PandaAvatarPage()
val notificationPage = NotificationPage()
val bookmarkPage = BookmarkPage()
val remoteConfigSettingsPage = RemoteConfigSettingsPage()
val settingsPage = SettingsPage()
val submissionDetailsPage = SubmissionDetailsPage()
val syllabusPage = SyllabusPage()
val todoPage = TodoPage()
val urlSubmissionUploadPage = UrlSubmissionUploadPage()

// A no-op interaction to afford us an easy, harmless way to get a11y checking to trigger.
fun meaninglessSwipe() {
Expand Down
1 change: 0 additions & 1 deletion apps/student/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@
android:hardwareAccelerated="true"
android:supportsRtl="true"
android:largeHeap="true"
android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true"
android:networkSecurityConfig="@xml/network_security_config"
tools:replace="android:supportsRtl"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import com.instructure.canvasapi2.models.CanvasContext
import com.instructure.canvasapi2.models.Course
import com.instructure.canvasapi2.models.StorageQuotaExceededError
import com.instructure.canvasapi2.utils.ApiPrefs
import com.instructure.canvasapi2.utils.isNotDeleted
import com.instructure.canvasapi2.utils.weave.awaitApi
import com.instructure.canvasapi2.utils.weave.catch
import com.instructure.canvasapi2.utils.weave.tryWeave
Expand Down Expand Up @@ -131,6 +132,7 @@ class ShareFileUploadActivity : AppCompatActivity(), ShareFileDestinationDialog.
private fun getCourses() {
loadCoursesJob = tryWeave {
val courses = awaitApi<List<Course>> { CourseManager.getCourses(true, it) }
.filter { it.isNotDeleted() }
if (courses.isNotEmpty()) {
this@ShareFileUploadActivity.courses = ArrayList(courses)
if (uploadFileSourceFragment == null) showDestinationDialog()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,18 @@ package com.instructure.student.adapter
import android.app.Activity
import android.view.View
import android.widget.Toast
import com.instructure.student.R
import com.instructure.student.holders.CourseViewHolder
import com.instructure.student.interfaces.CourseAdapterToFragmentCallback
import com.instructure.canvasapi2.managers.CourseManager
import com.instructure.canvasapi2.models.Course
import com.instructure.canvasapi2.utils.APIHelper
import com.instructure.canvasapi2.utils.isInvited
import com.instructure.canvasapi2.utils.isNotDeleted
import com.instructure.canvasapi2.utils.weave.WeaveJob
import com.instructure.canvasapi2.utils.weave.awaitApi
import com.instructure.canvasapi2.utils.weave.catch
import com.instructure.canvasapi2.utils.weave.tryWeave
import com.instructure.student.R
import com.instructure.student.holders.CourseViewHolder
import com.instructure.student.interfaces.CourseAdapterToFragmentCallback


class AllCoursesRecyclerAdapter(
Expand All @@ -57,7 +58,7 @@ class AllCoursesRecyclerAdapter(
mApiCall?.cancel()
mApiCall = tryWeave {
val courses = awaitApi<List<Course>> { CourseManager.getCourses(isRefresh, it) }
.filter { !it.accessRestrictedByDate && !it.isInvited() }
.filter { !it.accessRestrictedByDate && !it.isInvited() && it.isNotDeleted() }
addAll(courses)
notifyDataSetChanged()
isAllPagesLoaded = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ class DashboardRecyclerAdapter(

// Get enrollment invites
val invites = awaitApi<List<Enrollment>> {
EnrollmentManager.getSelfEnrollments(null, listOf(EnrollmentAPI.STATE_INVITED), isRefresh, it)
EnrollmentManager.getSelfEnrollments(null, listOf(EnrollmentAPI.STATE_INVITED, EnrollmentAPI.STATE_CURRENT_AND_FUTURE), isRefresh, it)
}

// Map not null is needed because the dashboard api can return unpublished courses
Expand Down Expand Up @@ -205,10 +205,7 @@ class DashboardRecyclerAdapter(
addOrUpdateAllItems(ItemType.ANNOUNCEMENT_HEADER, announcements)

// Add course invites
val validInvites = invites.filter {
mCourseMap[it.courseId]?.let { course ->
course.isValidTerm() && !course.accessRestrictedByDate && isEnrollmentBetweenCourseDatesOrNotRestricted(course) } ?: false
}
val validInvites = invites.filter { it.enrollmentState == EnrollmentAPI.STATE_INVITED && hasValidCourseForEnrollment(it) }

addOrUpdateAllItems(ItemType.INVITATION_HEADER, validInvites)

Expand All @@ -222,13 +219,20 @@ class DashboardRecyclerAdapter(
}
}

private fun isEnrollmentBetweenCourseDatesOrNotRestricted(course: Course): Boolean {
val now = OffsetDateTime.now()
val startDate = OffsetDateTime.parse(course.startAt).withOffsetSameInstant(OffsetDateTime.now().offset)
val endDate = OffsetDateTime.parse(course.endAt).withOffsetSameInstant(OffsetDateTime.now().offset)
private fun hasValidCourseForEnrollment(enrollment: Enrollment): Boolean {
return mCourseMap[enrollment.courseId]?.let { course ->
course.isValidTerm() && !course.accessRestrictedByDate && isEnrollmentBeforeEndDateOrNotRestricted(course)
} ?: false
}

private fun isEnrollmentBeforeEndDateOrNotRestricted(course: Course): Boolean {
val isBeforeEndDate = course.endAt?.let {
val now = OffsetDateTime.now()
val endDate = OffsetDateTime.parse(it).withOffsetSameInstant(OffsetDateTime.now().offset)
now.isBefore(endDate)
} ?: true // Case when the course has no end date

val isBetweenCourseDates = now.isAfter(startDate) && now.isBefore(endDate)
return !course.restrictEnrollmentsToCourseDate || isBetweenCourseDates
return !course.restrictEnrollmentsToCourseDate || isBeforeEndDate
}

override fun itemLayoutResId(viewType: Int) = when (ItemType.values()[viewType]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import com.instructure.canvasapi2.models.CanvasComparable
import com.instructure.canvasapi2.models.Course
import com.instructure.canvasapi2.models.Group
import com.instructure.canvasapi2.utils.isInvited
import com.instructure.canvasapi2.utils.isValidTerm
import com.instructure.canvasapi2.utils.isNotDeleted
import com.instructure.canvasapi2.utils.weave.WeaveJob
import com.instructure.canvasapi2.utils.weave.awaitApis
import com.instructure.canvasapi2.utils.weave.catch
Expand Down Expand Up @@ -129,7 +129,7 @@ class EditFavoritesRecyclerAdapter(
val (rawCourses, rawGroups) = awaitApis<List<Course>,List<Group>>(
{ CourseManager.getCourses(true, it) },
{ GroupManager.getAllGroups(it,true)})
val validCourses = rawCourses.filter { !it.accessRestrictedByDate && !it.isInvited() }
val validCourses = rawCourses.filter { !it.accessRestrictedByDate && !it.isInvited() && it.isNotDeleted() }
addOrUpdateAllItems(ItemType.COURSE_HEADER,validCourses)
val courseMap = rawCourses.associateBy { it.id }
val groups = rawGroups.filter { group -> group.isActive(courseMap[group.courseId]) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,18 @@ import retrofit2.Response
import java.util.*

open class ModuleListRecyclerAdapter(
val courseContext: CanvasContext,
private val courseContext: CanvasContext,
context: Context,
val adapterToFragmentCallback: ModuleAdapterToFragmentCallback?
private var shouldExhaustPagination: Boolean,
private val adapterToFragmentCallback: ModuleAdapterToFragmentCallback?
) : ExpandableRecyclerAdapter<ModuleObject, ModuleItem, RecyclerView.ViewHolder>(context, ModuleObject::class.java, ModuleItem::class.java) {

private val mModuleItemCallbacks = HashMap<Long, ModuleItemCallback>()
private var mModuleObjectCallback: StatusCallback<List<ModuleObject>>? = null
private var checkCourseTabsJob: Job? = null

/* For testing purposes only */
protected constructor(context: Context) : this(CanvasContext.defaultCanvasContext(), context, null) // Callback not needed for testing, cast to null
protected constructor(context: Context) : this(CanvasContext.defaultCanvasContext(), context, false,null) // Callback not needed for testing, cast to null

init {
viewHolderHeaderClicked = object : ViewHolderHeaderClicked<ModuleObject> {
Expand All @@ -83,7 +84,7 @@ open class ModuleListRecyclerAdapter(

}
isExpandedByDefault = false
// isDisplayEmptyCell = true TODO - make this work with scroll to functionality
isDisplayEmptyCell = true
if (adapterToFragmentCallback != null) loadData() // Callback is null when testing
}

Expand Down Expand Up @@ -136,6 +137,7 @@ open class ModuleListRecyclerAdapter(
}

override fun refresh() {
shouldExhaustPagination = false
mModuleItemCallbacks.clear()
checkCourseTabsJob?.cancel()
collapseAll()
Expand Down Expand Up @@ -339,8 +341,8 @@ open class ModuleListRecyclerAdapter(
ModuleManager.getFirstPageModuleItems(courseContext, it.id, getModuleItemsCallback(it, true), true)
}
}
if(!this.moreCallsExist()) {
// Wait until we are done exhausting pagination
if(!shouldExhaustPagination || !this.moreCallsExist()) {
// If we should exhaust pagination wait until we are done exhausting pagination
adapterToFragmentCallback?.onRefreshFinished()
}
}
Expand All @@ -358,7 +360,11 @@ open class ModuleListRecyclerAdapter(

// We only want to show modules if its a course nav option OR set to as the homepage
if (tabs.find { it.tabId == "modules" } != null || (courseContext as Course).homePage?.apiString == "modules") {
ModuleManager.getAllModuleObjets(courseContext, mModuleObjectCallback!!, true)
if (shouldExhaustPagination) {
ModuleManager.getAllModuleObjets(courseContext, mModuleObjectCallback!!, true)
} else {
ModuleManager.getFirstPageModuleObjects(courseContext, mModuleObjectCallback!!, true)
}
} else {
adapterToFragmentCallback?.onRefreshFinished(true)
}
Expand All @@ -367,6 +373,10 @@ open class ModuleListRecyclerAdapter(
}
}

override fun loadNextPage(nextURL: String) {
ModuleManager.getNextPageModuleObjects(nextURL, mModuleObjectCallback!!, true)
}

// endregion

// region Module binder Helpers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import com.instructure.canvasapi2.utils.ApiPrefs.user
import com.instructure.canvasapi2.utils.ApiType
import com.instructure.canvasapi2.utils.DateHelper
import com.instructure.canvasapi2.utils.LinkHeaders
import com.instructure.canvasapi2.utils.isNotDeleted
import com.instructure.pandarecycler.util.GroupSortedList.GroupComparatorCallback
import com.instructure.pandarecycler.util.GroupSortedList.ItemComparatorCallback
import com.instructure.pandarecycler.util.Types
Expand Down Expand Up @@ -156,7 +157,8 @@ class NotificationListRecyclerAdapter(

coursesCallback = object : StatusCallback<List<Course>>() {
override fun onResponse(response: Response<List<Course>>, linkHeaders: LinkHeaders, type: ApiType) {
courseMap = createCourseMap(response.body())
val courses = response.body()?.filter { it.isNotDeleted() }
courseMap = createCourseMap(courses)
populateActivityStreamAdapter()
}
}
Expand Down
Loading

0 comments on commit a37fb5a

Please sign in to comment.