Update Speed Grader after Posting/Hiding Grades

Closes: GRADE-1943

Test Plan:
 - post or hide grades in speed grader
 - the status change should be reflected immediately in speed grader
   without needing to refresh the page

Change-Id: I4f2840dd1f24b8b919a69316343eb58c1854a915
Reviewed-on: https://gerrit.instructure.com/192495
Tested-by: Jenkins
Reviewed-by: Adrian Packel <apackel@instructure.com>
Reviewed-by: Gary Mei <gmei@instructure.com>
QA-Review: Adrian Packel <apackel@instructure.com>
Product-Review: Jonathan Fenton <jfenton@instructure.com>
This commit is contained in:
Derek Bender 2019-05-07 15:10:01 -05:00
parent c1facc1216
commit 9aa8be7dc9
10 changed files with 476 additions and 210 deletions

View File

@ -55,6 +55,7 @@ export default class HideAssignmentGradesTray extends PureComponent {
this.state = {
hideBySections: false,
hidingGrades: false,
onExited() {},
open: false,
selectedSectionIds: []
}
@ -113,7 +114,7 @@ export default class HideAssignmentGradesTray extends PureComponent {
message: successMessage,
type: 'success'
})
} catch (error) {
} catch (_error) {
showFlashAlert({
message: I18n.t('There was a problem hiding assignment grades.'),
type: 'error'

View File

@ -60,6 +60,7 @@ export default class PostAssignmentGradesTray extends PureComponent {
postBySections: false,
postType: EVERYONE,
postingGrades: false,
onExited() {},
open: false,
selectedSectionIds: [],
submissions: []

View File

@ -22,14 +22,24 @@ import ReactDOM from 'react-dom'
import HideAssignmentGradesTray from '../../grading/HideAssignmentGradesTray'
import PostAssignmentGradesTray from '../../grading/PostAssignmentGradesTray'
function submissionsPostedAtUpdater({submissionsMap, updateSubmission, afterUpdateSubmission}) {
return function({postedAt, userIds}) {
userIds.forEach(userId => {
const submission = submissionsMap[userId]
submission.posted_at = postedAt
updateSubmission(submission)
})
afterUpdateSubmission()
}
}
export default class PostPolicies {
constructor({assignment, sections}) {
constructor({assignment, sections, updateSubmission, afterUpdateSubmission}) {
this._assignment = assignment
this._sections = sections
this.initialize()
}
this._updateSubmission = updateSubmission
this._afterUpdateSubmission = afterUpdateSubmission
initialize() {
const $hideContainer = document.getElementById('hide-assignment-grades-tray')
const bindHideTray = ref => {
this._hideAssignmentGradesTray = ref
@ -56,18 +66,28 @@ export default class PostPolicies {
}
}
showHideAssignmentGradesTray({onExited}) {
showHideAssignmentGradesTray({submissionsMap}) {
const onHidden = submissionsPostedAtUpdater({
afterUpdateSubmission: this._afterUpdateSubmission,
submissionsMap,
updateSubmission: this._updateSubmission
})
this._hideAssignmentGradesTray.show({
assignment: this._assignment,
onExited,
onHidden,
sections: this._sections
})
}
showPostAssignmentGradesTray({onExited, submissions}) {
showPostAssignmentGradesTray({submissionsMap, submissions}) {
const onPosted = submissionsPostedAtUpdater({
afterUpdateSubmission: this._afterUpdateSubmission,
submissionsMap,
updateSubmission: this._updateSubmission
})
this._postAssignmentGradesTray.show({
assignment: this._assignment,
onExited,
onPosted,
sections: this._sections,
submissions
})

View File

@ -29,7 +29,7 @@ import I18n from 'i18n!SpeedGraderPostGradesMenu'
export default function SpeedGraderPostGradesMenu(props) {
const menuTrigger = (
<Button variant="icon-inverse" icon={props.allowPostingGrades ? IconOff : IconEye}>
<ScreenReaderContent>{I18n.t('Post and Hide Grades')}</ScreenReaderContent>
<ScreenReaderContent>{I18n.t('Post or Hide Grades')}</ScreenReaderContent>
</Button>
)

View File

@ -491,6 +491,29 @@ function initDropdown() {
}
}
function setupPostPolicies() {
if (!ENV.post_policies_enabled) return
const {jsonData} = window
const gradesPublished = !jsonData.moderated_grading || jsonData.grades_published_at != null
EG.postPolicies = new PostPolicies({
assignment: {
anonymousGrading: jsonData.anonymous_grading,
gradesPublished,
id: jsonData.id,
name: jsonData.title
},
sections: jsonData.context.active_course_sections,
updateSubmission: EG.setOrUpdateSubmission,
afterUpdateSubmission() {
renderPostGradesMenu()
EG.showGrade()
}
})
renderPostGradesMenu()
}
function setupHeader({showMuteButton = true}) {
const elements = {
nav: $gradebook_header.find('#prev-student-button, #next-student-button'),
@ -1344,21 +1367,7 @@ EG = {
initDropdown()
initGroupAssignmentMode()
setupHandleStatePopped()
if (ENV.post_policies_enabled) {
const {jsonData} = window
EG.postPolicies = new PostPolicies({
assignment: {
anonymizeStudents: jsonData.anonymize_students,
gradesPublished: !jsonData.moderated_grading || jsonData.grades_published_at != null,
id: jsonData.id,
name: jsonData.title
},
sections: jsonData.context.active_course_sections
})
renderPostGradesMenu()
}
setupPostPolicies()
}
},
@ -3636,29 +3645,38 @@ function renderSettingsMenu() {
showHelpMenuItem: ENV.show_help_menu_item
}
const settingsMenu = <SpeedGraderSettingsMenu {...props} />
ReactDOM.render(settingsMenu, document.getElementById(SPEED_GRADER_SETTINGS_MOUNT_POINT))
const mountPoint = document.getElementById(SPEED_GRADER_SETTINGS_MOUNT_POINT)
ReactDOM.render(<SpeedGraderSettingsMenu {...props} />, mountPoint)
}
function teardownSettingsMenu() {
const mountPoint = document.getElementById(SPEED_GRADER_SETTINGS_MOUNT_POINT)
ReactDOM.unmountComponentAtNode(mountPoint)
}
function renderPostGradesMenu() {
const {submissionsMap} = window.jsonData
const submissions = window.jsonData.studentsWithSubmissions.map(student => student.submission)
const allowHidingGrades = submissions.some(submission => submission.posted_at != null)
const allowPostingGrades = submissions.some(submission => submission.posted_at == null)
function onHideGrades() {
EG.postPolicies.showHideAssignmentGradesTray({submissionsMap})
}
function onPostGrades() {
EG.postPolicies.showPostAssignmentGradesTray({submissionsMap, submissions})
}
const props = {
allowHidingGrades,
allowPostingGrades,
onHideGrades: () => {
EG.postPolicies.showHideAssignmentGradesTray({onExited: () => {}})
},
onPostGrades: () => {
EG.postPolicies.showPostAssignmentGradesTray({onExited: () => {}, submissions})
}
onHideGrades,
onPostGrades
}
const postGradesMenu = <SpeedGraderPostGradesMenu {...props} />
ReactDOM.render(
postGradesMenu,
<SpeedGraderPostGradesMenu {...props} />,
document.getElementById(SPEED_GRADER_POST_GRADES_MENU_MOUNT_POINT)
)
}
@ -3727,6 +3745,7 @@ export default {
EG.postPolicies.destroy()
}
teardownSettingsMenu()
teardownHandleStatePopped()
teardownBeforeLeavingSpeedgrader()
},

View File

@ -164,6 +164,15 @@ QUnit.module('HideAssignmentGradesTray', suiteHooks => {
await waitForTrayClosed()
notOk(getTrayElement())
})
test('calls optional onExited', async () => {
await show()
await waitForElement(getTrayElement)
getCloseIconButton().click()
await waitForTrayClosed()
const {callCount} = context.onExited
strictEqual(callCount, 1)
})
})
QUnit.module('"Specific Sections" toggle', hooks => {
@ -200,6 +209,15 @@ QUnit.module('HideAssignmentGradesTray', suiteHooks => {
await waitForTrayClosed()
notOk(getTrayElement())
})
test('calls optional onExited', async () => {
await show()
await waitForElement(getTrayElement)
getCloseButton().click()
await waitForTrayClosed()
const {callCount} = context.onExited
strictEqual(callCount, 1)
})
})
QUnit.module('"Hide" Button', hooks => {

View File

@ -177,6 +177,15 @@ QUnit.module('PostAssignmentGradesTray', suiteHooks => {
await waitForTrayClosed()
notOk(getTrayElement())
})
test('calls optional onExited', async () => {
await show()
await waitForElement(getTrayElement)
getCloseIconButton().click()
await waitForTrayClosed()
const {callCount} = context.onExited
strictEqual(callCount, 1)
})
})
QUnit.module('"Specific Sections" toggle', hooks => {
@ -213,6 +222,15 @@ QUnit.module('PostAssignmentGradesTray', suiteHooks => {
await waitForTrayClosed()
notOk(getTrayElement())
})
test('calls optional onExited', async () => {
await show()
await waitForElement(getTrayElement)
getCloseIconButton().click()
await waitForTrayClosed()
const {callCount} = context.onExited
strictEqual(callCount, 1)
})
})
QUnit.module('unposted summary', () => {

View File

@ -16,13 +16,24 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import ReactDOM from 'react-dom'
import {unmountComponentAtNode} from 'react-dom'
import PostPolicies from '../../../../../app/jsx/speed_grader/PostPolicies'
QUnit.module('SpeedGrader PostPolicies', suiteHooks => {
let $hideTrayMountPoint
let $postTrayMountPoint
let afterUpdateSubmission
let postPolicies
let updateSubmission
function expectedAssignment() {
return {
anonymizeStudents: false,
gradesPublished: true,
id: '2301',
name: 'Math 1.1'
}
}
suiteHooks.beforeEach(() => {
$hideTrayMountPoint = document.createElement('div')
@ -33,14 +44,15 @@ QUnit.module('SpeedGrader PostPolicies', suiteHooks => {
document.body.appendChild($hideTrayMountPoint)
document.body.appendChild($postTrayMountPoint)
const assignment = {
anonymizeStudents: false,
gradesPublished: true,
id: '2301',
name: 'Math 1.1'
}
const sections = [{id: '2001', name: 'Hogwarts'}, {id: '2002', name: 'Freshmen'}]
postPolicies = new PostPolicies({assignment, sections})
afterUpdateSubmission = sinon.stub()
updateSubmission = sinon.stub()
postPolicies = new PostPolicies({
afterUpdateSubmission,
assignment: expectedAssignment(),
sections,
updateSubmission
})
})
suiteHooks.afterEach(() => {
@ -51,121 +63,116 @@ QUnit.module('SpeedGrader PostPolicies', suiteHooks => {
test('renders the "Hide Assignment Grades" tray', () => {
const $trayContainer = document.getElementById('hide-assignment-grades-tray')
const unmounted = ReactDOM.unmountComponentAtNode($trayContainer)
const unmounted = unmountComponentAtNode($trayContainer)
strictEqual(unmounted, true)
})
test('renders the "Post Assignment Grades" tray', () => {
const $trayContainer = document.getElementById('post-assignment-grades-tray')
const unmounted = ReactDOM.unmountComponentAtNode($trayContainer)
const unmounted = unmountComponentAtNode($trayContainer)
strictEqual(unmounted, true)
})
QUnit.module('#destroy()', () => {
QUnit.module('#destroy', () => {
test('unmounts the "Hide Assignment Grades" tray', () => {
postPolicies.destroy()
const $trayContainer = document.getElementById('hide-assignment-grades-tray')
const unmounted = ReactDOM.unmountComponentAtNode($trayContainer)
const unmounted = unmountComponentAtNode($trayContainer)
strictEqual(unmounted, false)
})
test('unmounts the "Post Assignment Grades" tray', () => {
postPolicies.destroy()
const $trayContainer = document.getElementById('post-assignment-grades-tray')
const unmounted = ReactDOM.unmountComponentAtNode($trayContainer)
const unmounted = unmountComponentAtNode($trayContainer)
strictEqual(unmounted, false)
})
})
QUnit.module('#showHideAssignmentGradesTray()', hooks => {
QUnit.module('#showHideAssignmentGradesTray', hooks => {
function hideGradesShowArgs() {
return postPolicies._hideAssignmentGradesTray.show.firstCall.args[0]
}
hooks.beforeEach(() => {
sinon.stub(postPolicies._hideAssignmentGradesTray, 'show')
})
test('shows the "Hide Assignment Grades" tray', () => {
test('calls "show" for the "Hide Assignment Grades" tray', () => {
postPolicies.showHideAssignmentGradesTray({})
strictEqual(postPolicies._hideAssignmentGradesTray.show.callCount, 1)
})
test('includes the assignment id when showing the "Hide Assignment Grades" tray', () => {
test('passes the assignment to "show"', () => {
postPolicies.showHideAssignmentGradesTray({})
const [{assignment}] = postPolicies._hideAssignmentGradesTray.show.lastCall.args
strictEqual(assignment.id, '2301')
const {assignment} = hideGradesShowArgs()
deepEqual(assignment, expectedAssignment())
})
test('includes the assignment name when showing the "Hide Assignment Grades" tray', () => {
test('passes the sections to "show"', () => {
postPolicies.showHideAssignmentGradesTray({})
const [{assignment}] = postPolicies._hideAssignmentGradesTray.show.lastCall.args
strictEqual(assignment.name, 'Math 1.1')
})
test('includes the assignment anonymizeStudents', () => {
postPolicies.showHideAssignmentGradesTray({})
const [{assignment}] = postPolicies._hideAssignmentGradesTray.show.lastCall.args
strictEqual(assignment.anonymizeStudents, false)
})
test('includes the assignment gradesPublished', () => {
postPolicies.showHideAssignmentGradesTray({})
const [{assignment}] = postPolicies._hideAssignmentGradesTray.show.lastCall.args
strictEqual(assignment.gradesPublished, true)
})
test('includes the sections', () => {
postPolicies.showHideAssignmentGradesTray({})
const [{sections}] = postPolicies._hideAssignmentGradesTray.show.lastCall.args
const {sections} = hideGradesShowArgs()
deepEqual(sections, [{id: '2001', name: 'Hogwarts'}, {id: '2002', name: 'Freshmen'}])
})
test('includes the `onExited` callback when showing the "Hide Assignment Grades" tray', () => {
const callback = sinon.stub()
postPolicies.showHideAssignmentGradesTray({onExited: callback})
const [{onExited}] = postPolicies._hideAssignmentGradesTray.show.lastCall.args
strictEqual(onExited, callback)
test('passes updateSubmission to "show"', () => {
postPolicies.showHideAssignmentGradesTray({
submissionsMap: {
'1': {posted_at: new Date().toISOString()}
}
})
const {onHidden} = hideGradesShowArgs()
onHidden({userIds: ['1']})
strictEqual(updateSubmission.callCount, 1)
})
test('passes afterUpdateSubmission to "show"', () => {
postPolicies.showHideAssignmentGradesTray({
submissionsMap: {'1': {posted_at: new Date().toISOString()}}
})
const {onHidden} = hideGradesShowArgs()
onHidden({userIds: ['1']})
strictEqual(afterUpdateSubmission.callCount, 1)
})
test('onHidden updates posted_at', () => {
const submissionsMap = {
'1': {posted_at: new Date().toISOString()}
}
postPolicies.showHideAssignmentGradesTray({submissionsMap})
const {onHidden} = hideGradesShowArgs()
onHidden({postedAt: null, userIds: ['1']})
strictEqual(submissionsMap['1'].posted_at, null)
})
})
QUnit.module('#showPostAssignmentGradesTray()', hooks => {
QUnit.module('#showPostAssignmentGradesTray', hooks => {
function postGradesShowArgs() {
return postPolicies._postAssignmentGradesTray.show.firstCall.args[0]
}
hooks.beforeEach(() => {
sinon.stub(postPolicies._postAssignmentGradesTray, 'show')
})
test('shows the "Post Assignment Grades" tray', () => {
test('calls "show" for the "Post Assignment Grades" tray', () => {
postPolicies.showPostAssignmentGradesTray({})
strictEqual(postPolicies._postAssignmentGradesTray.show.callCount, 1)
})
test('includes the assignment id when showing the "Post Assignment Grades" tray', () => {
test('passes the assignment to "show"', () => {
postPolicies.showPostAssignmentGradesTray({})
const [{assignment}] = postPolicies._postAssignmentGradesTray.show.lastCall.args
strictEqual(assignment.id, '2301')
const {assignment} = postGradesShowArgs()
deepEqual(assignment, expectedAssignment())
})
test('includes the assignment name when showing the "Post Assignment Grades" tray', () => {
test('passes sections to "show"', () => {
postPolicies.showPostAssignmentGradesTray({})
const [{assignment}] = postPolicies._postAssignmentGradesTray.show.lastCall.args
strictEqual(assignment.name, 'Math 1.1')
})
test('includes the assignment anonymizeStudents', () => {
postPolicies.showPostAssignmentGradesTray({})
const [{assignment}] = postPolicies._postAssignmentGradesTray.show.lastCall.args
strictEqual(assignment.anonymizeStudents, false)
})
test('includes the assignment gradesPublished', () => {
postPolicies.showPostAssignmentGradesTray({})
const [{assignment}] = postPolicies._postAssignmentGradesTray.show.lastCall.args
strictEqual(assignment.gradesPublished, true)
})
test('includes the sections', () => {
postPolicies.showPostAssignmentGradesTray({})
const [{sections}] = postPolicies._postAssignmentGradesTray.show.lastCall.args
const {sections} = postGradesShowArgs()
deepEqual(sections, [{id: '2001', name: 'Hogwarts'}, {id: '2002', name: 'Freshmen'}])
})
test('includes the submissions', () => {
test('passes submissions to "show"', () => {
const submission = {
id: '93',
assignment_id: '2301',
@ -173,15 +180,33 @@ QUnit.module('SpeedGrader PostPolicies', suiteHooks => {
user_id: '441'
}
postPolicies.showPostAssignmentGradesTray({submissions: [submission]})
const [{submissions}] = postPolicies._postAssignmentGradesTray.show.lastCall.args
const {submissions} = postGradesShowArgs()
deepEqual(submissions, [submission])
})
test('includes the `onExited` callback when showing the "Post Assignment Grades" tray', () => {
const callback = sinon.stub()
postPolicies.showPostAssignmentGradesTray({onExited: callback})
const [{onExited}] = postPolicies._postAssignmentGradesTray.show.lastCall.args
strictEqual(onExited, callback)
test('passes updateSubmission to "show"', () => {
postPolicies.showPostAssignmentGradesTray({submissionsMap: {'1': {posted_at: null}}})
const {onPosted} = postGradesShowArgs()
onPosted({userIds: ['1']})
strictEqual(updateSubmission.callCount, 1)
})
test('passes afterUpdateSubmission to "show"', () => {
postPolicies.showPostAssignmentGradesTray({submissionsMap: {'1': {posted_at: null}}})
const {onPosted} = postGradesShowArgs()
onPosted({userIds: ['1']})
strictEqual(afterUpdateSubmission.callCount, 1)
})
test('onPosted updates posted_at', () => {
const submissionsMap = {
'1': {posted_at: null}
}
postPolicies.showPostAssignmentGradesTray({submissionsMap})
const postedAt = new Date().toISOString()
const {onPosted} = postGradesShowArgs()
onPosted({postedAt, userIds: ['1']})
strictEqual(submissionsMap['1'].posted_at, postedAt)
})
})
})

View File

@ -17,12 +17,12 @@
*/
import $ from 'jquery'
import React from 'react'
import ReactDOM from 'react-dom'
import _ from 'underscore'
import SpeedGrader from 'speed_grader'
import SpeedGraderHelpers from 'speed_grader_helpers'
import _ from 'underscore'
import JQuerySelectorCache from 'jsx/shared/helpers/JQuerySelectorCache'
import fakeENV from 'helpers/fakeENV'
import numberHelper from 'jsx/shared/helpers/numberHelper'
@ -141,6 +141,7 @@ QUnit.module('SpeedGrader#showDiscussion', {
},
teardown() {
SpeedGrader.teardown()
SpeedGrader.EG.domReady.restore()
$.getJSON.restore()
SpeedGrader.EG.currentStudent = this.originalStudent
@ -242,6 +243,7 @@ test('can handle non-nested submission history', () => {
SpeedGrader.setup()
SpeedGrader.EG.refreshSubmissionsToView()
ok(true, 'should not throw an exception')
SpeedGrader.teardown()
})
test('includes submission time for submissions when not anonymizing', () => {
@ -250,6 +252,7 @@ test('includes submission time for submissions when not anonymizing', () => {
const submissionDropdown = document.getElementById('multiple_submissions')
ok(submissionDropdown.innerHTML.includes('Jan 1, 2010'))
SpeedGrader.teardown()
})
test('includes submission time for submissions when the user is an admin', () => {
@ -261,6 +264,7 @@ test('includes submission time for submissions when the user is an admin', () =>
const submissionDropdown = document.getElementById('multiple_submissions')
ok(submissionDropdown.innerHTML.includes('Jan 1, 2010'))
SpeedGrader.teardown()
})
test('omits submission time for submissions when anonymizing and not an admin', () => {
@ -272,6 +276,7 @@ test('omits submission time for submissions when anonymizing and not an admin',
const submissionDropdown = document.getElementById('multiple_submissions')
notOk(submissionDropdown.innerHTML.includes('Jan 1, 2010'))
SpeedGrader.teardown()
})
test('sets submission history container content to empty when submission history is blank', () => {
@ -281,6 +286,7 @@ test('sets submission history container content to empty when submission history
SpeedGrader.EG.refreshSubmissionsToView()
const submissionDropdown = document.getElementById('multiple_submissions')
strictEqual(submissionDropdown.innerHTML, '')
SpeedGrader.teardown()
})
QUnit.module('#showSubmissionDetails', function(hooks) {
@ -316,6 +322,7 @@ QUnit.module('#showSubmissionDetails', function(hooks) {
})
hooks.afterEach(function() {
SpeedGrader.teardown()
SpeedGrader.EG.domReady.restore()
$.ajaxJSON.restore()
$.getJSON.restore()
@ -725,9 +732,9 @@ QUnit.module('SpeedGrader#handleGradeSubmit', {
teardown() {
SpeedGrader.EG.currentStudent = this.originalStudent
teardownFixtures()
window.jsonData = this.originalWindowJSONData
SpeedGrader.teardown()
teardownFixtures()
fakeENV.teardown()
SpeedGraderHelpers.reloadPage.restore()
}
@ -862,6 +869,7 @@ QUnit.module('renderLtiLaunch', {
},
teardown() {
SpeedGrader.teardown()
SpeedGrader.EG.domReady.restore()
$.ajaxJSON.restore()
$.getJSON.restore()
@ -1792,78 +1800,80 @@ QUnit.module('SpeedGrader', suiteHooks => {
})
QUnit.module('"Post Policies"', hooks => {
function getPostAndHideGradesButton() {
function postAndHideGradesButton() {
return document.querySelector('span#speed_grader_post_grades_menu_mount_point button')
}
function getHideGradesMenuItem() {
function hideGradesMenuItem() {
return document.querySelector('[name="hideGrades"]')
}
function getPostGradesMenuItem() {
function postGradesMenuItem() {
return document.querySelector('[name="postGrades"]')
}
let showHideAssignmentGradesTrayStub
let showPostAssignmentGradesTrayStub
const postedSubmission = {
id: '3001',
posted_at: new Date().toISOString(),
user_id: '1101'
}
const unpostedSubmission = {
id: '3002',
posted_at: null,
user_id: '1102'
}
const windowJsonData = {
anonymize_students: false,
grades_published_at: null,
moderated_grading: false,
id: '2301',
title: 'Assignment 1',
context: {
students: [{id: '1101'}, {id: '1102'}],
enrollments: [{user_id: '1101', course_section_id: '2001'}, {user_id: '1102', course_section_id: '2001'}],
active_course_sections: []
},
submissions: [postedSubmission, unpostedSubmission]
}
let showHideAssignmentGradesTray
let showPostAssignmentGradesTray
let setOrUpdateSubmission
let showGrade
let postedSubmission
let unpostedSubmission
hooks.beforeEach(() => {
ENV.post_policies_enabled = true
window.jsonData = windowJsonData
fakeENV.setup({
assignment_id: '17',
course_id: '29',
grading_role: 'moderator',
help_url: 'example.com/support',
post_policies_enabled: true,
show_help_menu_item: false
})
postedSubmission = {posted_at: new Date().toISOString(), user_id: '1101'}
unpostedSubmission = {posted_at: null, user_id: '1102'}
SpeedGrader.setup()
window.jsonData = {
context: {
students: [{id: '1101'}, {id: '1102'}],
enrollments: [{user_id: '1101'}, {user_id: '1102'}],
active_course_sections: []
},
submissions: [postedSubmission, unpostedSubmission]
}
SpeedGrader.EG.jsonReady()
showHideAssignmentGradesTrayStub = sinon.stub(SpeedGrader.EG.postPolicies, 'showHideAssignmentGradesTray')
showPostAssignmentGradesTrayStub = sinon.stub(SpeedGrader.EG.postPolicies, 'showPostAssignmentGradesTray')
getPostAndHideGradesButton().click()
showHideAssignmentGradesTray = sinon.stub(SpeedGrader.EG.postPolicies, 'showHideAssignmentGradesTray')
showPostAssignmentGradesTray = sinon.stub(SpeedGrader.EG.postPolicies, 'showPostAssignmentGradesTray')
setOrUpdateSubmission = sinon.stub(SpeedGrader.EG, 'setOrUpdateSubmission')
showGrade = sinon.stub(SpeedGrader.EG, 'showGrade')
postAndHideGradesButton().click()
})
hooks.afterEach(() => {
showPostAssignmentGradesTrayStub.restore()
showHideAssignmentGradesTrayStub.restore()
showGrade.restore()
setOrUpdateSubmission.restore()
showPostAssignmentGradesTray.restore()
showHideAssignmentGradesTray.restore()
delete window.jsonData
fakeENV.teardown()
SpeedGrader.teardown()
})
QUnit.module('Post Grades', () => {
test('shows the Post Assignment Grades Tray', () => {
getPostGradesMenuItem().click()
strictEqual(showPostAssignmentGradesTrayStub.callCount, 1)
postGradesMenuItem().click()
strictEqual(showPostAssignmentGradesTray.callCount, 1)
})
test('passes the submissions to showPostAssignmentGradesTray', () => {
getPostGradesMenuItem().click()
deepEqual(showPostAssignmentGradesTrayStub.firstCall.args[0].submissions, window.jsonData.submissions)
postGradesMenuItem().click()
deepEqual(showPostAssignmentGradesTray.firstCall.args[0].submissions, window.jsonData.submissions)
})
})
QUnit.module('Hide Grades', () => {
test('shows the Hide Assignment Grades Tray', () => {
getHideGradesMenuItem().click()
strictEqual(showHideAssignmentGradesTrayStub.callCount, 1)
hideGradesMenuItem().click()
strictEqual(showHideAssignmentGradesTray.callCount, 1)
})
})
})
@ -1900,6 +1910,7 @@ test('shows an error when the gateway times out', function() {
const message =
'Something went wrong. Please try refreshing the page. If the problem persists, there may be too many records on "Assignment Title" to load SpeedGrader.'
strictEqual($('#speed_grader_timeout_alert').text(), message)
SpeedGrader.teardown()
})
QUnit.module('SpeedGrader - clicking save rubric button', function(hooks) {
@ -1965,10 +1976,11 @@ QUnit.module('SpeedGrader - clicking save rubric button', function(hooks) {
})
hooks.afterEach(function() {
delete window.jsonData
SpeedGrader.teardown()
teardownFixtures()
fakeENV.teardown()
disableWhileLoadingStub.restore()
SpeedGrader.teardown()
$.ajaxJSON.restore()
$.fn.ready.restore()
})
@ -2068,12 +2080,12 @@ QUnit.module('SpeedGrader - clicking save rubric button for an anonymous assignm
hooks.afterEach(() => {
window.jsonData = originalWindowJsonData
SpeedGrader.EG.currentStudent = originalSpeedGraderEGCurrentStudent
SpeedGrader.teardown()
teardownFixtures()
rubricAssessmentDataStub.restore()
fakeENV.teardown()
rubricAssessmentDataStub.restore()
disableWhileLoadingStub.restore()
$.ajaxJSON.restore()
SpeedGrader.teardown()
})
test('sends the anonymous submission ID in rubric_assessment[anonymous_id] if the assignment is anonymous', () => {
@ -2138,6 +2150,7 @@ test('does not show an error when the gateway times out', function() {
SpeedGrader.setup()
strictEqual($('#speed_grader_timeout_alert').text(), '')
domReadyStub.restore()
SpeedGrader.teardown()
})
QUnit.module('SpeedGrader', function(suiteHooks) {
@ -2332,15 +2345,13 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
})
hooks.afterEach(() => {
teardownFixtures()
delete SpeedGrader.EG.currentStudent
window.jsonData = jsonData
SpeedGrader.teardown()
document.querySelector('.ui-selectmenu-menu').remove()
teardownFixtures()
})
test('mounts the progressIcon when attachment uplod_status is pending', function() {
test('mounts the progressIcon when attachment upload_status is pending', function() {
const attachment = {content_type: 'application/rtf', upload_status: 'pending'}
SpeedGrader.EG.renderAttachment(attachment)
@ -2403,8 +2414,55 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
})
})
QUnit.module('#setup', function(hooks) {
hooks.beforeEach(function() {
QUnit.module('#setup', hooks => {
let assignment
let student
let enrollment
let submissionComment
let submission
let windowJsonData
hooks.beforeEach(() => {
assignment = {
anonymous_grading: false,
title: 'An Assigment'
}
student = {
id: '1',
submission_history: []
}
enrollment = {user_id: student.id, course_section_id: '1'}
submissionComment = {
created_at: new Date().toISOString(),
publishable: false,
comment: 'a comment',
author_id: 1,
author_name: 'an author'
}
submission = {
id: '3',
user_id: '1',
grade_matches_current_submission: true,
workflow_state: 'active',
submitted_at: new Date().toISOString(),
grade: 'A',
assignment_id: '456',
submission_history: [],
submission_comments: [submissionComment]
}
windowJsonData = {
...assignment,
context_id: '123',
context: {
students: [student],
enrollments: [enrollment],
active_course_sections: [],
rep_for_student: {}
},
submissions: [submission],
gradingPeriods: []
}
fakeENV.setup({
...window.ENV,
assignment_id: '17',
@ -2427,6 +2485,57 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
$('.ui-dialog').remove()
})
QUnit.module('PostPolicy setup', ({beforeEach, afterEach}) => {
let setOrUpdateSubmission
let showGrade
let show
let render
beforeEach(() => {
fakeENV.setup({
...ENV,
grading_role: undefined,
post_policies_enabled: true,
RUBRIC_ASSESSMENT: {}
})
setOrUpdateSubmission = sinon.spy(SpeedGrader.EG, 'setOrUpdateSubmission')
SpeedGrader.setup()
window.jsonData = windowJsonData
SpeedGrader.EG.jsonReady()
setupCurrentStudent()
show = sinon.spy(SpeedGrader.EG.postPolicies._postAssignmentGradesTray, 'show')
const {jsonData: {submissionsMap, submissions}} = window
SpeedGrader.EG.postPolicies.showPostAssignmentGradesTray({submissionsMap, submissions})
const {firstCall: {args: [{onPosted}]}} = show
showGrade = sinon.spy(SpeedGrader.EG, 'showGrade')
render = sinon.spy(ReactDOM, 'render')
onPosted({postedAt: new Date().toISOString(), userIds: [Object.keys(submissionsMap)]})
})
afterEach(() => {
render.restore()
showGrade.restore()
show.restore()
setOrUpdateSubmission.restore()
fakeENV.teardown()
})
test('updateSubmissions calls setOrUpdateSubmission', () => {
strictEqual(setOrUpdateSubmission.callCount, 1)
})
test('afterUpdateSubmissions calls showGrade', () => {
strictEqual(showGrade.callCount, 1)
})
test('afterUpdateSubmissions calls renders SpeedGraderPostGradesMenu', () => {
const callCount = render.getCalls().filter(call =>
call.args[0].type.name === 'SpeedGraderPostGradesMenu'
).length
strictEqual(callCount, 1)
})
})
test('populates the settings mount point', () => {
SpeedGrader.setup()
const mountPoint = document.getElementById('speed_grader_settings_mount_point')
@ -2562,10 +2671,10 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
})
hooks.afterEach(() => {
teardownFixtures()
delete SpeedGrader.EG.currentStudent
window.jsonData = jsonData
SpeedGrader.teardown()
teardownFixtures()
document.querySelector('.ui-selectmenu-menu').remove()
})
@ -2824,8 +2933,8 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
})
})
QUnit.module('Anonymous Enabled', () => {
hooks.beforeEach(() => {
QUnit.module('Anonymous Enabled', anonymousEnabledHooks => {
anonymousEnabledHooks.beforeEach(() => {
fakeENV.setup({
...ENV,
assignment_id: '17',
@ -2850,7 +2959,7 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
commentElement = $('#comment_fixture')
})
hooks.afterEach(() => {
anonymousEnabledHooks.afterEach(() => {
delete SpeedGrader.EG.currentStudent
window.jsonData = originalJsonData
SpeedGrader.teardown()
@ -3002,11 +3111,11 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
})
hooks.afterEach(() => {
teardownFixtures()
fakeENV.teardown()
window.jsonData = originalJsonData
delete SpeedGrader.EG.currentStudent
SpeedGrader.teardown()
teardownFixtures()
fakeENV.teardown()
})
test('when students are anonymized no link is shown', () => {
@ -3071,11 +3180,11 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
})
hooks.afterEach(() => {
teardownFixtures()
fakeENV.teardown()
window.jsonData = originalJsonData
delete SpeedGrader.EG.currentStudent
SpeedGrader.teardown()
teardownFixtures()
fakeENV.teardown()
})
test('renderComment adds the comment text to the submit button for draft comments', () => {
@ -3318,7 +3427,7 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
})
})
QUnit.module('#renderPostGradesMenu', hooks => {
QUnit.module('Post Grades Menu', hooks => {
const findRenderCall = () => ReactDOM.render.args.find(
argsForCall => argsForCall[1].id === 'speed_grader_post_grades_menu_mount_point'
)
@ -3341,10 +3450,77 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
ReactDOM.render.restore()
})
test('renders the "Post Grades" menu', () => {
test('renders the Post Grades" menu once', () => {
SpeedGrader.EG.jsonReady()
const renderCalls = ReactDOM.render.args.filter(argsForCall =>
argsForCall[1].id === 'speed_grader_post_grades_menu_mount_point'
)
strictEqual(renderCalls.length, 1)
})
ok(findRenderCall())
QUnit.module('Posting Grades', ({beforeEach, afterEach}) => {
let createElementSpy
let showPostAssignmentGradesTrayStub
let onPostGrades
beforeEach(() => {
createElementSpy = sinon.spy(React, 'createElement')
SpeedGrader.EG.jsonReady()
onPostGrades = createElementSpy.args.find(argsForCall =>
argsForCall[0].name === 'SpeedGraderPostGradesMenu'
)[1].onPostGrades
showPostAssignmentGradesTrayStub = sinon.stub(SpeedGrader.EG.postPolicies, 'showPostAssignmentGradesTray')
onPostGrades()
})
afterEach(() => {
showPostAssignmentGradesTrayStub.restore()
createElementSpy.restore()
})
test('onPostGrades calls showPostAssignmentGradesTray', () => {
strictEqual(showPostAssignmentGradesTrayStub.callCount, 1)
})
test('onPostGrades calls showPostAssignmentGradesTray with submissionsMap', () => {
const {firstCall: {args: [{submissionsMap}]}} = showPostAssignmentGradesTrayStub
deepEqual(submissionsMap, window.jsonData.submissionsMap)
})
test('onPostGrades calls showPostAssignmentGradesTray with submissions', () => {
const {firstCall: {args: [{submissions}]}} = showPostAssignmentGradesTrayStub
deepEqual(submissions, window.jsonData.studentsWithSubmissions.map(student => student.submission))
})
})
QUnit.module('Hiding Grades', ({beforeEach, afterEach}) => {
let createElementSpy
let showHideAssignmentGradesTrayStub
let onHideGrades
beforeEach(() => {
createElementSpy = sinon.spy(React, 'createElement')
SpeedGrader.EG.jsonReady()
onHideGrades = createElementSpy.args.find(argsForCall =>
argsForCall[0].name === 'SpeedGraderPostGradesMenu'
)[1].onHideGrades
showHideAssignmentGradesTrayStub = sinon.stub(SpeedGrader.EG.postPolicies, 'showHideAssignmentGradesTray')
onHideGrades()
})
afterEach(() => {
showHideAssignmentGradesTrayStub.restore()
createElementSpy.restore()
})
test('onHideGrades calls showHideAssignmentGradesTray', () => {
strictEqual(showHideAssignmentGradesTrayStub.callCount, 1)
})
test('onHideGrades calls showHideAssignmentGradesTray with submissionsMap', () => {
const {firstCall: {args: [{submissionsMap}]}} = showHideAssignmentGradesTrayStub
deepEqual(submissionsMap, window.jsonData.submissionsMap)
})
})
test('passes the allowHidingGrades prop as true if any submissions are posted', () => {
@ -3420,10 +3596,10 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
})
hooks.afterEach(function() {
teardownFixtures()
window.jsonData = originalJsonData
SpeedGrader.teardown()
SpeedGrader.EG.goToStudent.restore()
window.jsonData = originalJsonData
teardownFixtures()
})
test('goToStudent is called with next student anonymous_id', () => {
@ -3461,9 +3637,9 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
hooks.afterEach(function() {
SpeedGrader.EG.goToStudent.restore()
teardownFixtures()
SpeedGrader.teardown()
window.jsonData = originalJsonData
SpeedGrader.teardown()
teardownFixtures()
})
test('goToStudent is called with student anonymous_id', () => {
@ -3567,11 +3743,11 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
})
hooks.afterEach(() => {
teardownFixtures()
SpeedGrader.EG.updateHistoryForCurrentStudent.restore()
window.jsonData = originalJsonData
delete SpeedGrader.EG.currentStudent
SpeedGrader.teardown()
teardownFixtures()
})
test('pushes the current student onto the browser history if "push" is specified as the behavior', () => {
@ -3632,10 +3808,10 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
})
hooks.afterEach(() => {
teardownFixtures()
window.jsonData = originalJsonData
delete SpeedGrader.EG.currentStudent
SpeedGrader.teardown()
teardownFixtures()
})
QUnit.module('when a behavior of "push" is specified', pushHooks => {
@ -3929,10 +4105,10 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
})
hooks.afterEach(() => {
teardownFixtures()
window.jsonData = originalJsonData
delete SpeedGrader.EG.currentStudent
SpeedGrader.teardown()
teardownFixtures()
})
test('assessment_user_id is set via anonymous id', () => {
@ -3981,10 +4157,10 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
})
hooks.afterEach(() => {
teardownFixtures()
window.jsonData = originalJsonData
delete SpeedGrader.EG.currentStudent
SpeedGrader.teardown()
teardownFixtures()
})
test('attachmentElement has submitter_id set to anonymous id', () => {
@ -4013,10 +4189,10 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
})
hooks.afterEach(() => {
teardownFixtures()
window.jsonData = originalJsonData
delete SpeedGrader.EG.currentStudent
SpeedGrader.teardown()
teardownFixtures()
})
test('calls isStudentConcluded with student looked up by anonymous id', () => {
@ -4055,10 +4231,10 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
})
hooks.afterEach(() => {
teardownFixtures()
window.jsonData = originalJsonData
delete SpeedGrader.EG.currentStudent
SpeedGrader.teardown()
teardownFixtures()
})
test('calls ajaxJSON with anonymous submission url with anonymous id', () => {
@ -4137,10 +4313,10 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
})
hooks.afterEach(() => {
teardownFixtures()
window.jsonData = originalJsonData
delete SpeedGrader.EG.currentStudent
SpeedGrader.teardown()
teardownFixtures()
})
test('calls isStudentConcluded with student looked up by anonymous id', () => {
@ -4259,10 +4435,10 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
})
hooks.afterEach(() => {
teardownFixtures()
window.jsonData = originalJsonData
delete SpeedGrader.EG.currentStudent
SpeedGrader.teardown()
teardownFixtures()
document.querySelector('.ui-selectmenu-menu').remove()
})
@ -4303,10 +4479,10 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
})
hooks.afterEach(() => {
teardownFixtures()
window.jsonData = originalJsonData
delete SpeedGrader.EG.currentStudent
SpeedGrader.teardown()
teardownFixtures()
})
test("the iframe src points to a user's submission by anonymous_id", () => {
@ -4350,10 +4526,10 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
})
hooks.afterEach(() => {
teardownFixtures()
window.jsonData = originalJsonData
delete SpeedGrader.EG.currentStudent
SpeedGrader.teardown()
teardownFixtures()
})
test('attachment src points to the submission download url', () => {
@ -4934,8 +5110,8 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
})
hooks.afterEach(() => {
teardownFixtures()
SpeedGrader.teardown()
teardownFixtures()
})
QUnit.module('when a submission is unsubmitted', () => {
@ -5098,10 +5274,10 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
test('does not attempt to select a representative', () => {
queryParamsStub.returns({anonymous_id: 'fffff'})
window.jsonData.context.rep_for_student['fffff'] = 'rrrrr'
window.jsonData.context.rep_for_student.fffff = 'rrrrr'
SpeedGrader.EG.setInitiallyLoadedStudent()
strictEqual(SpeedGrader.EG.currentStudent.anonymous_id, 'fffff')
delete window.jsonData.context.rep_for_student['fffff']
delete window.jsonData.context.rep_for_student.fffff
})
test('defaults to the first ungraded student if no student is specified', () => {
@ -5222,8 +5398,8 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
element.remove()
})
teardownFixtures()
SpeedGrader.teardown()
teardownFixtures()
window.jsonData = originalWindowJSONData
})

View File

@ -22,8 +22,9 @@ require_relative '../pages/student_grades_page'
describe 'SpeedGrader Post Policy' do
include_context "in-process server selenium tests"
before(:all) { skip('GRADE-2192') }
before :once do
skip('Unskip in GRADE-1943')
# course
course_with_teacher(
course_name: "Post Policy Course",
@ -70,13 +71,12 @@ describe 'SpeedGrader Post Policy' do
context 'when post everyone' do
before :each do
skip('Unskip in GRADE-1943')
manually_post_grades('Everyone')
end
it 'post grades option disabled' do
skip('Unskip in GRADE-1943')
# TODO: expect post option to be disabled for Speedgrader.post_grades
expect(Speedgrader.post_grades).to be_disabled
end
it 'students see grade', priority: '1', test_id: 3757534 do
@ -88,7 +88,6 @@ describe 'SpeedGrader Post Policy' do
context 'when post everyone for section' do
before :each do
skip('Unskip in GRADE-1943')
manually_post_grades('Everyone', @section2)
end
@ -99,7 +98,6 @@ describe 'SpeedGrader Post Policy' do
end
it 'does not post for other section', priority: '1', test_id: 3757535 do
skip('Unskip in GRADE-1943')
expect(Speedgrader.hidden_pill).to be_displayed
# TODO: verify students not in section have eyeball icon
@ -110,7 +108,6 @@ describe 'SpeedGrader Post Policy' do
end
it 'Post tray shows unposted count', priority: '1', test_id: 3757535 do
skip('Unskip in GRADE-1943')
expect(Speedgrader.PostGradesTray.unposted_count).to eq '2'
# TODO: expect unposted indicator to be displayed and show count 2
end
@ -118,7 +115,6 @@ describe 'SpeedGrader Post Policy' do
context 'when hide posted grades for everyone' do
before :each do
skip('Unskip in GRADE-1943')
manually_post_grades('Everyone')
wait_for_ajaximations
Speedgrader.visit(@course.id, @assignment.id)
@ -127,7 +123,6 @@ describe 'SpeedGrader Post Policy' do
end
it 'header has HIDDEN icon', priority: '1', test_id: 3757537 do
skip('Unskip in GRADE-1943')
# TODO: expect header to have HIDDEN icon Speedgrader.grades_not_posted_icon
end
@ -139,14 +134,12 @@ describe 'SpeedGrader Post Policy' do
end
it 'hidden pill displayed in side panel', priority: '1', test_id: 3757537 do
skip('Unskip in GRADE-1943')
expect(Speedgrader.hidden_pill).to be_displayed
end
end
context 'when hide posted grades for section' do
before :each do
skip('Unskip in GRADE-1943')
manually_post_grades('Everyone')
wait_for_ajaximations
manually_hide_grades(@section2)
@ -154,7 +147,6 @@ describe 'SpeedGrader Post Policy' do
end
it 'students in section have hidden grades', priority: '1', test_id: 3756683 do
skip('Unskip in GRADE-1943')
@students1.each do |student|
verify_student_grade_comments_displayed(student, '')
# TODO: expect hidden icon to be displayed
@ -178,7 +170,6 @@ describe 'SpeedGrader Post Policy' do
end
before :each do
skip('Unskip in GRADE-1943')
manually_post_grades('Graded')
end
@ -189,7 +180,6 @@ describe 'SpeedGrader Post Policy' do
end
it 'does not post for ungraded', priority: '1', test_id: 3756680 do
skip('Unskip in GRADE-1943')
# TODO: verify ungraded students still have eyeball icon
verify_student_grade_comments_displayed(@students[3], '')
# TODO: expect icon to be displayed
@ -205,7 +195,6 @@ describe 'SpeedGrader Post Policy' do
end
before :each do
skip('Unskip in GRADE-1943')
manually_post_grades('Graded', @section2)
end
@ -214,7 +203,6 @@ describe 'SpeedGrader Post Policy' do
end
it 'does not post ungraded for section', priority: '1', test_id: 3757539 do
skip('Unskip in GRADE-1943')
# TODO: verify students in section without grades have eyeball icon
verify_student_grade_comments_displayed(@students2.second, '')
# TODO: expect eyeball icon to be displayed