Skip to content

Commit d02d6d8

Browse files
Release Student 8.1.0 (280)
2 parents 8a74d54 + 09a750e commit d02d6d8

File tree

155 files changed

+3238
-932
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

155 files changed

+3238
-932
lines changed

apps/parent/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ android {
4040
applicationId "com.instructure.parentapp"
4141
minSdkVersion Versions.MIN_SDK
4242
targetSdkVersion Versions.TARGET_SDK
43-
versionCode 59
44-
versionName "4.3.0"
43+
versionCode 60
44+
versionName "4.4.0"
4545

4646
buildConfigField "boolean", "IS_TESTING", "false"
4747
testInstrumentationRunner 'com.instructure.parentapp.ui.espresso.ParentHiltTestRunner'

apps/parent/src/main/java/com/instructure/parentapp/features/calendar/ParentCalendarRepository.kt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,29 @@ class ParentCalendarRepository(
9090
.mapContextName()
9191
}
9292

93+
val calendarSubAssignments = async {
94+
calendarEventApi.getCalendarEvents(
95+
false,
96+
CalendarEventAPI.CalendarEventType.SUB_ASSIGNMENT.apiName,
97+
startDate,
98+
endDate,
99+
contextCodes,
100+
restParams
101+
).depaginate {
102+
calendarEventApi.next(it, restParams)
103+
}.dataOrThrow
104+
.filterNot { it.isHidden }
105+
.toPlannerItems(PlannableType.SUB_ASSIGNMENT)
106+
.mapContextName()
107+
}
108+
93109
val plannerNotes = async {
94110
plannerApi.getPlannerNotes(startDate, endDate, contextCodes, restParams).depaginate {
95111
plannerApi.nextPagePlannerNotes(it, restParams)
96112
}.dataOrThrow.toPlannerItems()
97113
}
98114

99-
return@coroutineScope listOf(calendarEvents, calendarAssignments, plannerNotes).awaitAll()
115+
return@coroutineScope listOf(calendarEvents, calendarAssignments, calendarSubAssignments, plannerNotes).awaitAll()
100116
}
101117

102118
return allItems.flatten().sortedBy { it.plannableDate }

apps/parent/src/test/java/com/instructure/parentapp/features/calendar/ParentCalendarRepositoryTest.kt

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ class ParentCalendarRepositoryTest {
8181
contextCode = "course_1"
8282
)
8383

84+
val subAssignment = ScheduleItem(
85+
itemId = "1234",
86+
title = "Discussion Checkpoint",
87+
subAssignment = Assignment(id = 1234L, dueAt = LocalDateTime.now().plusHours(4).toApiString()),
88+
itemType = ScheduleItem.Type.TYPE_ASSIGNMENT,
89+
contextCode = "course_1"
90+
)
91+
8492
val calendarEvent = ScheduleItem(
8593
itemId = "0",
8694
title = "calendar event",
@@ -115,6 +123,14 @@ class ParentCalendarRepositoryTest {
115123
)
116124
} returns DataResult.Success(listOf(assignment))
117125

126+
coEvery {
127+
calendarEventApi.getCalendarEvents(
128+
any(),
129+
CalendarEventAPI.CalendarEventType.SUB_ASSIGNMENT.apiName,
130+
any(), any(), any(), any()
131+
)
132+
} returns DataResult.Success(listOf(subAssignment))
133+
118134
coEvery {
119135
calendarEventApi.getCalendarEvents(
120136
any(),
@@ -127,16 +143,21 @@ class ParentCalendarRepositoryTest {
127143

128144
val result = calendarRepository.getPlannerItems("2023-1-1", "2023-1-2", listOf("course_1"), true)
129145

130-
assertEquals(3, result.size)
146+
assertEquals(4, result.size)
131147
// Planner items should be sorted by date
132148
val plannerNoteResult = result[0]
133149
val assignmentResult = result[1]
134150
val calendarEventResult = result[2]
151+
val subAssignmentResult = result[3]
135152

136153
assertEquals(assignment.assignment?.id, assignmentResult.plannable.id)
137154
assertEquals(assignment.title, assignmentResult.plannable.title)
138155
assertEquals(assignment.contextCode, assignmentResult.canvasContext.contextId)
139156

157+
assertEquals(subAssignment.itemId, subAssignmentResult.plannable.id.toString())
158+
assertEquals(subAssignment.title, subAssignmentResult.plannable.title)
159+
assertEquals(subAssignment.contextCode, subAssignmentResult.canvasContext.contextId)
160+
140161
assertEquals(calendarEvent.itemId, calendarEventResult.plannable.id.toString())
141162
assertEquals(calendarEvent.title, calendarEventResult.plannable.title)
142163
assertEquals(calendarEvent.contextCode, calendarEventResult.canvasContext.contextId)
@@ -215,6 +236,14 @@ class ParentCalendarRepositoryTest {
215236
)
216237
} returns DataResult.Success(listOf(assignment, assignmentHidden))
217238

239+
coEvery {
240+
calendarEventApi.getCalendarEvents(
241+
any(),
242+
CalendarEventAPI.CalendarEventType.SUB_ASSIGNMENT.apiName,
243+
any(), any(), any(), any()
244+
)
245+
} returns DataResult.Success(emptyList())
246+
218247
coEvery {
219248
calendarEventApi.getCalendarEvents(
220249
any(),

apps/student/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ android {
3737
applicationId "com.instructure.candroid"
3838
minSdkVersion Versions.MIN_SDK
3939
targetSdkVersion Versions.TARGET_SDK
40-
versionCode = 279
41-
versionName = '8.0.0'
40+
versionCode = 280
41+
versionName = '8.1.0'
4242

4343
vectorDrawables.useSupportLibrary = true
4444
testInstrumentationRunner 'com.instructure.student.espresso.StudentHiltTestRunner'

apps/student/src/androidTest/java/com/instructure/student/ui/e2e/QuizzesE2ETest.kt

Lines changed: 49 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@
1616
*/
1717
package com.instructure.student.ui.e2e
1818

19-
import android.os.SystemClock.sleep
2019
import android.util.Log
21-
import androidx.test.espresso.Espresso.pressBack
2220
import androidx.test.espresso.matcher.ViewMatchers.withId
2321
import androidx.test.espresso.matcher.ViewMatchers.withText
2422
import androidx.test.espresso.web.assertion.WebViewAssertions.webMatches
@@ -30,11 +28,9 @@ import androidx.test.espresso.web.webdriver.Locator
3028
import com.instructure.canvas.espresso.E2E
3129
import com.instructure.canvas.espresso.FeatureCategory
3230
import com.instructure.canvas.espresso.Priority
33-
import com.instructure.canvas.espresso.Stub
3431
import com.instructure.canvas.espresso.TestCategory
3532
import com.instructure.canvas.espresso.TestMetaData
3633
import com.instructure.canvas.espresso.containsTextCaseInsensitive
37-
import com.instructure.canvas.espresso.isElementDisplayed
3834
import com.instructure.dataseeding.api.QuizzesApi
3935
import com.instructure.dataseeding.model.QuizAnswer
4036
import com.instructure.dataseeding.model.QuizQuestion
@@ -54,13 +50,7 @@ class QuizzesE2ETest: StudentTest() {
5450

5551
override fun enableAndConfigureAccessibilityChecks() = Unit
5652

57-
// Fairly basic test of web view-based quizzes. Seeds/takes a quiz with two multiple-choice
58-
// questions.
59-
//
60-
// STUBBING THIS OUT. Usually passes locally, but I can't get a simple webClick() to work on FTL.
61-
// See comments below.
6253
@E2E
63-
@Stub
6454
@Test
6555
@TestMetaData(Priority.MANDATORY, FeatureCategory.PAGES, TestCategory.E2E)
6656
fun testQuizzesE2E() {
@@ -90,99 +80,71 @@ class QuizzesE2ETest: StudentTest() {
9080
Log.d(STEP_TAG, "Navigate to Quizzes Page.")
9181
courseBrowserPage.selectQuizzes()
9282

93-
Log.d(ASSERTION_TAG, "Assert that '${quizPublished.title}' published quiz is displayed and '${quizUnpublished.title}' unpublished quiz has not displayed.")
83+
Log.d(ASSERTION_TAG, "Assert that '${quizPublished.title}' published quiz is displayed and '${quizUnpublished.title}' unpublished quiz has not displayed and the quiz group name is displayed.")
9484
quizListPage.assertQuizDisplayed(quizPublished)
9585
quizListPage.assertQuizNotDisplayed(quizUnpublished)
86+
quizListPage.assertAssignmentQuizzesGroupDisplayed()
87+
88+
Log.d(STEP_TAG, "Collapse the quiz group.")
89+
quizListPage.collapseAssignmentQuizzesGroup()
90+
91+
Log.d(ASSERTION_TAG, "Assert that the '${quizPublished.title}' quiz is NOT displayed.")
92+
quizListPage.assertQuizNotDisplayed(quizPublished)
93+
94+
Log.d(STEP_TAG, "Expand the quiz group.")
95+
quizListPage.expandAssignmentQuizzesGroup()
96+
97+
Log.d(ASSERTION_TAG, "Assert that the '${quizPublished.title}' quiz is displayed again.")
98+
quizListPage.assertQuizDisplayed(quizPublished)
99+
100+
Log.d(STEP_TAG, "Open the search bar and search for the '${quizUnpublished.title}' quiz.")
101+
quizListPage.searchable.clickOnSearchButton()
102+
quizListPage.searchable.typeToSearchBar(quizUnpublished.title)
103+
104+
Log.d(ASSERTION_TAG, "Assert that the empty view is displayed.")
105+
quizListPage.assertEmptyStateDisplayed()
106+
107+
Log.d(STEP_TAG, "Clear the search bar and search for the '${quizPublished.title}' quiz.")
108+
quizListPage.searchable.clickOnClearSearchButton()
109+
quizListPage.searchable.typeToSearchBar(quizUnpublished.title)
110+
111+
Log.d(ASSERTION_TAG, "Assert that ONLY the '${quizPublished.title}' quiz is displayed in the search results page.")
112+
quizListPage.assertQuizDisplayed(quizPublished)
113+
quizListPage.assertQuizItemCount(1)
96114

97115
Log.d(STEP_TAG, "Select '${quizPublished.title}' quiz.")
98116
quizListPage.selectQuiz(quizPublished)
99117

100-
Log.d(ASSERTION_TAG, "Assert that the '${quizPublished.title}' quiz title is displayed.")
118+
Log.d(ASSERTION_TAG, "Assert that the '${quizPublished.title}' quiz title is displayed on the quiz details page.")
101119
canvasWebViewPage.runTextChecks(WebViewTextCheck(locatorType = Locator.ID, locatorValue = "quiz_title", textValue = quizPublished.title))
102120

103-
// Launch the quiz
104-
// Pressing the "Take the Quiz" button does not work on an FTL Api 25 device.
105-
// Not even the logic below, which tries 10 times to press the button!
106-
// Every time the button is pressed on an FTL device, we get this console message:
107-
//
108-
// 09-29 07:24:22.796: I/chromium(7428): [INFO:CONSOLE(29)] "Uncaught TypeError: e.preventDefault(...)
109-
// is not a function", source: https://mobileqa.beta.instructure.com/courses/3092218/quizzes/7177808?force_user=1&persist_headless=1 (29)
110-
//
111-
// The applicable code is in a script element in the header portion of the web view content:
112-
// <script>
113-
// function _earlyClick(e){
114-
// var c = e.target
115-
// while (c && c.ownerDocument) {
116-
// if (c.getAttribute('href') == '#' || c.getAttribute('data-method')) {
117-
// e.preventDefault()
118-
// (_earlyClick.clicks = _earlyClick.clicks || []).push(c)
119-
// break
120-
// }
121-
// c = c.parentNode
122-
// }
123-
// }
124-
// document.addEventListener('click', _earlyClick)
125-
// </script>
126-
//
127-
// My best guess is that Espresso-Web is clicking on the wrong location, in an
128-
// area where a preventDefault() function does not apply.
129-
//
130-
// Also, there is some slight variation in webview versions:
131-
// --Local emulator: 55.0.2883.91
132-
// --FTL emulator: 53.0.2785.135
133-
// Not sure if that would make a difference.
134-
//
135-
// Possible solution: Write a custom atom to do the work instead of relying on webClick() via pressButton()
136-
var attemptsLeft = 10
137-
while(attemptsLeft > 0) {
138-
try {
139-
canvasWebViewPage.pressButton(locatorType = Locator.ID, locatorValue = "take_quiz_link")
140-
141-
// If the pressButton() call above was successful, then this check should fail.
142-
canvasWebViewPage.runTextChecks(
143-
WebViewTextCheck(
144-
locatorType = Locator.ID,
145-
locatorValue = "take_quiz_link",
146-
textValue = "Take the Quiz"
147-
)
148-
)
149-
attemptsLeft -= 1
150-
sleep(1000) // delay between attempts
151-
Log.v("QuizTest", "Retrying take-quiz button press")
152-
}
153-
catch(t: Throwable) {
154-
break
155-
}
156-
}
121+
Log.d(ASSERTION_TAG, "Assert that the 'Take the Quiz' button is displayed on the quiz details page.")
122+
canvasWebViewPage.runTextChecks(
123+
WebViewTextCheck(
124+
locatorType = Locator.ID,
125+
locatorValue = "take_quiz_link",
126+
textValue = "Take the Quiz"
127+
)
128+
)
157129

158-
// Enter answers to questions. Right now, only multiple-choice questions are supported.
159-
Log.d(STEP_TAG, "Enter answers to the questions:")
130+
Log.d(STEP_TAG, "Press 'Take the Quiz' button.")
131+
canvasWebViewPage.pressButton(locatorType = Locator.ID, locatorValue = "take_quiz_link")
132+
133+
Log.d(STEP_TAG, "Enter answers to the questions.")
134+
Thread.sleep(2000) // Wait for the quiz to load
160135
for(question in quizQuestions) {
161136
Log.d(ASSERTION_TAG, "Assert that the following question is displayed: '${question.questionText}'.")
162-
quizTakingPage.verifyQuestionDisplayed(question.id!!, question.questionText!!)
137+
quizTakingPage.assertQuestionDisplayed(question.id!!, question.questionText!!)
163138
if(question.questionType == "multiple_choice_question") {
164139
Log.d(STEP_TAG, "Choosing an answer for the following question: '${question.questionText}'.")
165-
quizTakingPage.selectAnyAnswer(question.id!!) // Just choose any answer
140+
quizTakingPage.selectAnyAnswer(question.id!!)
166141
}
167142
}
168143

169-
Log.d(PREPARATION_TAG, "Submit the '${quizPublished.title}' quiz.")
144+
Log.d(STEP_TAG, "Submit the '${quizPublished.title}' quiz.")
170145
quizTakingPage.submitQuiz()
171146

172-
// Interesting situation here. If you wait long enough, the web page will update itself,
173-
// which affects the number of pressBack() commands that it takes to get back to the
174-
// quiz list page, and might also affect whether or not the "Attempt History" portion of
175-
// the page is displayed.
176-
//
177-
// Chosen strategy: pressBack() until you get to the quiz list page,
178-
// then reload the quiz details to get the latest info.
179-
Log.d(STEP_TAG, "Navigate back to Quizzes Page.")
180-
while(!isElementDisplayed(R.id.quizListPage)) pressBack()
181-
182-
Log.d(STEP_TAG, "Select '${quizPublished.title}' quiz.")
183-
quizListPage.selectQuiz(quizPublished)
184-
185-
sleep(5000)
147+
Thread.sleep(3000) // Wait for the quiz submission to finish.
186148
Log.d(ASSERTION_TAG, "Assert (on web) that the '${quizPublished.title}' quiz now has a history.")
187149
onWebView(withId(R.id.contentWebView))
188150
.withElement(findElement(Locator.ID, "quiz-submission-version-table"))
@@ -195,15 +157,13 @@ class QuizzesE2ETest: StudentTest() {
195157
.check(webMatches(getText(),containsString("LATEST")))
196158

197159
Log.d(STEP_TAG, "Navigate back to Course Browser Page.")
198-
ViewUtils.pressBackButton(2)
160+
ViewUtils.pressBackButton(3)
199161

200162
Log.d(STEP_TAG, "Navigate to Grades Page.")
201163
courseBrowserPage.selectGrades()
202-
// For some reason, this quiz is resulting in a 10/10 grade, although with the weights assigned and
203-
// answers given it should be 5/10. Let's just make sure that a "10" shows up.
164+
204165
Log.d(ASSERTION_TAG, "Assert that the corresponding grade (10) is displayed for '${quizPublished.title}' quiz.")
205166
courseGradesPage.assertGradeDisplayed(withText(quizPublished.title), containsTextCaseInsensitive("10"))
206-
207167
}
208168

209169
private fun makeQuizQuestions() = listOf(
@@ -227,14 +187,6 @@ class QuizzesE2ETest: StudentTest() {
227187
QuizAnswer(id = 1, weight = 0, text = "Who-Who Who-Who")
228188
)
229189
)
230-
231-
// Can't test essay questions yet. More specifically, can't test answering essay questions.
232-
// QuizQuestion(
233-
// questionText = "Why should I give you an A?",
234-
// questionType = "essay_question",
235-
// pointsPossible = 12,
236-
// answers = listOf()
237-
// )
238190
)
239191

240192
}

0 commit comments

Comments
 (0)