make anonymous grading checks consider muted status

Unmuting an anonymous assignment will now cause that assignment to show
in the UI as though it is not anonymous. In addition, posting grades for
a moderated anonymous assignment will now cause that assignment to show
in the UI as though it is not anonymous.

closes GRADE-1310
closes GRADE-1313

Test Plan:
1. Verify unmuting an anonymous assignment causes that assignment to no
   longer be anonymized in the UI. You can do this by spot checking
   gradebook (old/new/individual) and SpeedGrader. In addition,
   re-muting an unmuted anonymous assignment should cause the assignment
   to become anonymized in the UI.
2. Verify posting grades for a moderated, anonymous assignment causes
   that assignment to no longer be anonymized in the UI (even if the
   assignment is muted).

Change-Id: I113c61b4e7fa0eb8909448d8ba7cffb22600e17c
Reviewed-on: https://gerrit.instructure.com/156151
Reviewed-by: Gary Mei <gmei@instructure.com>
Reviewed-by: Keith T. Garner <kgarner@instructure.com>
QA-Review: James Butters <jbutters@instructure.com>
Tested-by: Jenkins
Product-Review: Sidharth Oberoi <soberoi@instructure.com>
This commit is contained in:
Spencer Olson 2018-07-02 16:47:29 -05:00
parent a1eb8725bd
commit c5ee434ff5
52 changed files with 690 additions and 210 deletions

View File

@ -67,15 +67,25 @@ export default class AssignmentMuter {
}
afterUpdate (serverResponse) {
const assignment = serverResponse.assignment
if (this.setter) {
this.setter(this.assignment, 'muted', serverResponse.assignment.muted)
this.setter(
this.assignment,
'anonymize_students',
assignment.anonymize_students
)
this.setter(this.assignment, 'muted', assignment.muted)
} else {
this.assignment.muted = serverResponse.assignment.muted
this.assignment.anonymize_students = assignment.anonymize_students
this.assignment.muted = assignment.muted
}
if (!(this.options && this.options.openDialogInstantly)) this.updateLink()
this.$dialog.dialog('close')
$.publish('assignment_muting_toggled', [this.assignment])
$.publish(
'assignment_muting_toggled',
[{...this.assignment, anonymize_students: assignment.anonymize_students, muted: assignment.muted}]
)
}
confirmUnmute () {

View File

@ -101,7 +101,7 @@ export default class SubmissionDetailsDialog {
buildSpeedGraderUrl = () => {
const assignmentParam = `assignment_id=${this.assignment.id}`
const speedGraderUrlParams = this.assignment.anonymous_grading
const speedGraderUrlParams = this.assignment.anonymize_students
? assignmentParam
: `${assignmentParam}#{"student_id":"${this.student.id}"}`
return encodeURI(`${this.options.context_url}/gradebook/speed_grader?${speedGraderUrlParams}`)

View File

@ -928,7 +928,7 @@ define [
).property('selectedStudent', 'selectedAssignment')
hideComments: (->
@get('selectedAssignment.muted') && (@get('selectedAssignment.anonymous_grading') || @get('selectedAssignment.moderated_grading')) || false
@get('selectedAssignment.anonymize_students')
).property('selectedAssignment')
selectedSubmissionLate: (->

View File

@ -918,41 +918,17 @@ QUnit.module 'ScreenReader Gradebook', (suiteHooks) ->
initializeApp()
asyncHelper.waitForRequests()
test 'false when assignment is not muted', ->
test 'false when anonymize_students is false', ->
assignment = srgb.get('assignments.firstObject')
assignment.muted = false
assignment.anonymous_grading = true
assignment.moderated_grading = true
assignment.anonymize_students = false
Ember.run ->
srgb.set('selectedAssignment', assignment)
equal srgb.get('hideComments'), false
test 'false when assignment is muted but not anonymous or moderated', ->
test 'true when anonymize_students is true', ->
assignment = srgb.get('assignments.firstObject')
assignment.muted = true
assignment.anonymous_grading = false
assignment.moderated_grading = false
Ember.run ->
srgb.set('selectedAssignment', assignment)
equal srgb.get('hideComments'), false
test 'true when assignment is muted and anonymous', ->
assignment = srgb.get('assignments.firstObject')
assignment.muted = true
assignment.anonymous_grading = true
assignment.moderated_grading = false
Ember.run ->
srgb.set('selectedAssignment', assignment)
equal srgb.get('hideComments'), true
test 'true when assignment is muted and moderated', ->
assignment = srgb.get('assignments.firstObject')
assignment.muted = true
assignment.anonymous_grading = false
assignment.moderated_grading = true
assignment.anonymize_students = true
Ember.run ->
srgb.set('selectedAssignment', assignment)

View File

@ -319,7 +319,6 @@ define [
assignment.assignment_group = group
assignment.due_at = tz.parse(assignment.due_at)
assignment.moderation_in_progress = assignment.moderated_grading and !assignment.grades_published
assignment.hide_grades_when_muted = assignment.anonymous_grading
@updateAssignmentEffectiveDueDates(assignment)
@assignments[assignment.id] = assignment
@ -561,12 +560,17 @@ define [
matchingSection and matchingFilter
handleAssignmentMutingChange: (assignment) =>
gradebookAssignment = @assignments[assignment.id]
gradebookAssignment.anonymize_students = assignment.anonymize_students
gradebookAssignment.muted = assignment.muted
idx = @grid.getColumnIndex("assignment_#{assignment.id}")
colDef = @grid.getColumns()[idx]
colDef.name = @assignmentHeaderHtml(assignment)
@grid.setColumns(@grid.getColumns())
@fixColumnReordering()
@buildRows()
allStudents = Object.values(@students).concat(Object.values(@studentViewStudents))
@setupGrading(allStudents)
handleAssignmentGroupWeightChange: (assignment_group_options) =>
columns = @grid.getColumns()
@ -723,7 +727,7 @@ define [
if !assignment?
@staticCellFormatter(row, col, '')
else if assignment.hide_grades_when_muted and assignment.muted
else if assignment.anonymize_students
@lockedAndHiddenGradeCellFormatter(row, col, 'anonymous')
else if submission.workflow_state == 'pending_review'
(SubmissionCell[assignment.grading_type] || SubmissionCell).formatter(row, col, submission, assignment, student, formatterOpts)

View File

@ -174,7 +174,7 @@ define [
classes.push('resubmitted') if submission.grade_matches_current_submission == false
classes.push('late') if submission.late
classes.push('ungraded') if ''+assignment.submission_types is "not_graded"
if assignment.anonymous_grading and (assignment.muted or assignment.moderation_in_progress)
if assignment.anonymize_students
classes.push('anonymous')
else if assignment.moderation_in_progress
classes.push('moderated')

View File

@ -362,7 +362,7 @@ class AssignmentsController < ApplicationController
respond_to do |format|
if @assignment && @assignment.send(method)
format.json { render :json => @assignment }
format.json { render json: @assignment.as_json(methods: :anonymize_students) }
else
format.json { render :json => @assignment, :status => :bad_request }
end

View File

@ -135,7 +135,7 @@ class EportfolioEntriesController < ApplicationController
# @entry.check_for_matching_attachment_id
@headers = false
render template: 'submissions/show_preview', locals: {
anonymous_now: @assignment.anonymous_grading? && @assignment.muted?
anonymize_students: @assignment.anonymize_students?
}
end
end

View File

@ -562,7 +562,7 @@ class GradebooksController < ApplicationController
def submissions_json(submissions:, assignments:)
submissions.map do |sub|
assignment = assignments[sub[:assignment_id].to_i]
omitted_field = assignment.anonymous_grading? ? :user_id : :anonymous_id
omitted_field = assignment.anonymize_students? ? :user_id : :anonymous_id
json_params = {
include: { submission_history: { methods: %i[late missing], except: omitted_field } },
except: omitted_field
@ -667,7 +667,7 @@ class GradebooksController < ApplicationController
append_sis_data(env)
js_env(env)
render :speed_grader, locals: { anonymous_grading: @assignment.anonymous_grading? }
render :speed_grader, locals: { anonymize_students: @assignment.anonymize_students? }
end
format.json do

View File

@ -54,7 +54,7 @@ module Submissions
redirect_to(named_context_url(@context, redirect_path_name, @assignment.quiz.id, redirect_params))
else
render template: 'submissions/show_preview', locals: {
anonymous_now: @assignment.anonymous_grading? && @assignment.muted?
anonymize_students: @assignment.anonymize_students?
}
end
end

View File

@ -22,13 +22,13 @@ module GradebooksHelper
end
def force_anonymous_grading?(assignment)
anonymous_survey?(assignment) || assignment.anonymous_grading?
anonymous_survey?(assignment) || assignment.anonymize_students?
end
def force_anonymous_grading_reason(assignment)
if anonymous_survey?(assignment)
I18n.t("Student names must be hidden because this is an anonymous survey.")
elsif assignment.anonymous_grading?
elsif assignment.anonymize_students?
I18n.t("Student names must be hidden because anonymous grading is required.")
else
""

View File

@ -40,7 +40,7 @@ import GradingPeriodsHelper from '../grading/helpers/GradingPeriodsHelper'
) {
if (assignment.moderated_grading && !assignment.grades_published) {
return { locked: true, hideGrade: false };
} else if (assignment.anonymous_grading && assignment.muted) {
} else if (assignment.anonymize_students) {
return { locked: true, hideGrade: true };
} else if (!visibleToStudent(assignment, student)) {
return { locked: true, hideGrade: true, tooltip: TOOLTIP_KEYS.NONE };

View File

@ -66,7 +66,7 @@ function cellMappingsForMultipleGradingPeriods (assignment, student, selectedGra
}
function cellMapForSubmission (assignment, student, hasGradingPeriods, selectedGradingPeriodID, isAdmin) {
if (!assignment.published || (assignment.anonymous_grading && assignment.muted)) {
if (!assignment.published || assignment.anonymize_students) {
return { locked: true, hideGrade: true };
} else if (assignment.moderated_grading && !assignment.grades_published) {
return { locked: true, hideGrade: false };

View File

@ -35,7 +35,7 @@ import MessageStudentsWhoHelper from '../../../shared/helpers/messageStudentsWho
import ColumnHeader from './ColumnHeader'
function SecondaryDetailLine (props) {
const anonymous = props.assignment.anonymousGrading;
const anonymous = props.assignment.anonymizeStudents;
const unpublished = !props.assignment.published;
if (anonymous || unpublished) {
@ -74,7 +74,7 @@ function SecondaryDetailLine (props) {
SecondaryDetailLine.propTypes = {
assignment: shape({
anonymousGrading: bool.isRequired,
anonymizeStudents: bool.isRequired,
muted: bool.isRequired,
pointsPossible: number,
published: bool.isRequired
@ -85,7 +85,7 @@ export default class AssignmentColumnHeader extends ColumnHeader {
static propTypes = {
...ColumnHeader.propTypes,
assignment: shape({
anonymousGrading: bool.isRequired,
anonymizeStudents: bool.isRequired,
courseId: string.isRequired,
htmlUrl: string.isRequired,
id: string.isRequired,

View File

@ -62,7 +62,7 @@ function getProps (column, gradebook, options) {
addGradebookElement: gradebook.keyboardNav.addGradebookElement,
assignment: {
anonymousGrading: assignment.anonymous_grading,
anonymizeStudents: assignment.anonymize_students,
courseId: assignment.course_id,
htmlUrl: assignment.html_url,
id: assignment.id,

View File

@ -101,16 +101,14 @@ function CompleteIncompleteSelect (props) {
}
function stateFromProps (props) {
const { anonymousGrading, muted } = props.assignment;
const hideGrade = anonymousGrading && muted;
let normalizedGrade;
if (props.enterGradesAs === 'passFail') {
normalizedGrade = hideGrade ? null : props.submission.enteredGrade;
normalizedGrade = props.assignment.anonymizeStudents ? null : props.submission.enteredGrade;
} else {
const propsCopy = { ...props };
if (hideGrade) {
if (props.assignment.anonymizeStudents) {
const submission = { ...props.submission, enteredScore: null };
propsCopy.submission = submission;
}
@ -127,6 +125,7 @@ function stateFromProps (props) {
export default class GradeInput extends React.Component {
static propTypes = {
assignment: shape({
anonymizeStudents: bool.isRequired,
gradingType: oneOf(['gpa_scale', 'letter_grade', 'not_graded', 'pass_fail', 'points', 'percent']).isRequired,
pointsPossible: number
}).isRequired,

View File

@ -70,8 +70,8 @@ export default class SubmissionTray extends React.Component {
htmlUrl: string.isRequired,
muted: bool.isRequired,
published: bool.isRequired,
anonymousGrading: bool.isRequired,
moderatedGrading: bool.isRequired
anonymizeStudents: bool.isRequired,
moderatedGrading: bool.isRequired,
}).isRequired,
contentRef: func,
currentUserId: string.isRequired,
@ -170,8 +170,8 @@ export default class SubmissionTray extends React.Component {
}
renderSubmissionComments () {
const { anonymousGrading, moderatedGrading, muted } = this.props.assignment;
if (muted && (anonymousGrading || moderatedGrading)) {
const {anonymizeStudents, moderatedGrading, muted} = this.props.assignment;
if (anonymizeStudents || (moderatedGrading && muted)) {
return;
}
@ -204,7 +204,7 @@ export default class SubmissionTray extends React.Component {
renderSpeedGraderLink (speedGraderProps) {
const buttonProps = { variant: 'link', href: speedGraderProps.speedGraderUrl }
if (speedGraderProps.anonymousGrading) {
if (speedGraderProps.anonymizeStudents) {
buttonProps.onClick = (e) => {
e.preventDefault();
this.props.onAnonymousSpeedGraderClick(speedGraderProps.speedGraderUrl);
@ -224,7 +224,7 @@ export default class SubmissionTray extends React.Component {
const { name, avatarUrl } = this.props.student;
const assignmentParam = `assignment_id=${this.props.submission.assignmentId}`;
const studentParam = `#{"student_id":"${this.props.student.id}"}`;
const speedGraderUrlParams = this.props.assignment.anonymousGrading
const speedGraderUrlParams = this.props.assignment.anonymizeStudents
? assignmentParam
: `${assignmentParam}${studentParam}`
const speedGraderUrl = encodeURI(`/courses/${this.props.courseId}/gradebook/speed_grader?${speedGraderUrlParams}`)
@ -250,7 +250,7 @@ export default class SubmissionTray extends React.Component {
let speedGraderProps = null;
if (this.props.speedGraderEnabled) {
speedGraderProps = {
anonymousGrading: this.props.assignment.anonymousGrading,
anonymizeStudents: this.props.assignment.anonymizeStudents,
speedGraderUrl
};
}

View File

@ -2834,8 +2834,19 @@ class Assignment < ActiveRecord::Base
end
end
def anonymize_students?
return false unless anonymous_grading?
# Only anonymize students for moderated assignments if grades have not been published.
# Only anonymize students for non-moderated assignments if the assignment is muted.
moderated_grading? ? !grades_published? : muted?
end
alias anonymize_students anonymize_students?
def can_view_student_names?(user)
!anonymous_grading && context.grants_any_right?(user, :manage_grades, :view_all_grades)
return false if anonymize_students?
context.grants_any_right?(user, :manage_grades, :view_all_grades)
end
private

View File

@ -51,6 +51,7 @@ class Assignment
},
:include_root => false
)
res['anonymize_students'] = @assignment.anonymize_students?
# include :provisional here someday if we need to distinguish
# between provisional and real comments (also in

View File

@ -1420,6 +1420,10 @@ class Quizzes::Quiz < ActiveRecord::Base
filters
end
def anonymize_students?
assignment.present? && assignment.anonymize_students?
end
def self.class_names
%w(Quiz Quizzes::Quiz)
end

View File

@ -463,7 +463,7 @@ class Submission < ActiveRecord::Base
def can_view_details?(user)
return false unless grants_right?(user, :read)
return true unless self.assignment.anonymous_grading && self.assignment.muted
return true unless self.assignment.anonymize_students?
user == self.user || Account.site_admin.grants_right?(user, :update)
end

View File

@ -207,8 +207,8 @@
<div id="submission_files_list">
<div id="submission_file_hidden" class="submission-file" style='display:none;'>
<%
resource = anonymous_grading ? :context_assignment_anonymous_submission_url : :context_assignment_submission_url
submission_id_or_anonymous_id = anonymous_grading ? "{{anonymousId}}" : "{{submissionId}}"
resource = anonymize_students ? :context_assignment_anonymous_submission_url : :context_assignment_submission_url
submission_id_or_anonymous_id = anonymize_students ? "{{anonymousId}}" : "{{submissionId}}"
download_url = context_url(@context, resource, @assignment.id, submission_id_or_anonymous_id, download: "{{attachmentId}}")
preview_url = context_url(@context, resource, @assignment.id, submission_id_or_anonymous_id, download: "{{attachmentId}}", inline: 1)
%>

View File

@ -86,7 +86,7 @@
<div style="text-align: center;">
<% if @submission.processed %>
<%
preview_url = if anonymous_now
preview_url = if anonymize_students
context_url(
@context,
:context_assignment_anonymous_submission_url,
@ -133,7 +133,7 @@
<div class="file-upload-submission-info">
<%
preview_url = if anonymous_now
preview_url = if anonymize_students
context_url(
@context,
:context_assignment_anonymous_submission_url,

View File

@ -355,7 +355,7 @@ module Api::V1::Assignment
end
hash['anonymous_grading'] = value_to_boolean(assignment.anonymous_grading)
hash['anonymize_students'] = assignment.anonymize_students?
hash
end

View File

@ -252,7 +252,7 @@ module Api::V1::Submission
attachment = attachments.pop
attachments.each(&:destroy_permanently_plus)
anonymous = assignment.anonymous_grading?
anonymous = assignment.anonymize_students?
# Remove the earlier attachment and re-create it if it's "stale"
if attachment

View File

@ -403,7 +403,7 @@ class ContentZipper
@submission = submission
@logger.debug(" checking submission for #{(submission.user.id)}")
users_name = get_user_name(students, submission) unless @assignment.anonymous_grading?
users_name = get_user_name(students, submission) unless @assignment.anonymize_students?
filename = get_filename(users_name, submission)
case submission.submission_type

View File

@ -22,8 +22,8 @@ import I18n from 'i18n!gradebook'
import './jquery.instructure_date_and_time'
import './jquery.instructure_misc_helpers'
export function setupIsAnonymous ({anonymous_grading}) {
return anonymous_grading
export function setupIsAnonymous ({anonymize_students}) {
return anonymize_students
}
export function setupAnonymizableId (isAnonymous) {

View File

@ -4503,15 +4503,33 @@ describe AssignmentsApiController, type: :request do
end
end
it "contains true for anonymous_grading when the assignment has anonymous grading enabled" do
@assignment.anonymous_grading = true
expect(result['anonymous_grading']).to be true
end
it "contains false for anonymous_grading when the assignment has anonymous grading disabled" do
@assignment.anonymous_grading = false
expect(result['anonymous_grading']).to be false
end
it 'is false for anonymize_students when the assignment is not anonymous' do
expect(result['anonymize_students']).to be false
end
context 'when the assignment is anonymous' do
before(:once) do
@assignment.anonymous_grading = true
end
it 'contains true for anonymous_grading' do
expect(result['anonymous_grading']).to be true
end
it 'is true for anonymize_students when the assignment is muted' do
@assignment.muted = true
expect(result['anonymize_students']).to be true
end
it 'is false for anonymize_students when the assignment is unmuted' do
expect(result['anonymize_students']).to be false
end
end
end
context "update_from_params" do

View File

@ -0,0 +1,123 @@
/*
* Copyright (C) 2018 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import AssignmentMuter from 'coffeescripts/AssignmentMuter'
import $ from 'jquery'
QUnit.module('AssignmentMuter', (suiteHooks) => {
let assignment
let responseAssignment
suiteHooks.beforeEach(() => {
assignment = {id: '1', name: 'foo', anonymize_students: false, muted: false}
responseAssignment = {id: '1', name: 'foo', anonymize_students: true, muted: true}
})
function createAssignmentMuter(setterFn) {
const muter = new AssignmentMuter({text: () => {}}, assignment, '/bar', setterFn, {})
muter.$dialog = {dialog() {}}
return muter
}
QUnit.module('#afterUpdate', (hooks) => {
hooks.beforeEach(() => {
sinon.stub($, 'publish')
})
hooks.afterEach(() => {
$.publish.restore()
})
test('closes the dialog', () => {
const muter = createAssignmentMuter()
sinon.stub(muter.$dialog, 'dialog')
muter.afterUpdate({assignment: responseAssignment})
ok(muter.$dialog.dialog.calledWith('close'))
})
QUnit.module('when not passed a setter function', () => {
test('sets anonymize_students on the assignment', () => {
const muter = createAssignmentMuter()
muter.afterUpdate({assignment: responseAssignment})
strictEqual(muter.assignment.anonymize_students, responseAssignment.anonymize_students)
})
test('sets muted on the assignment', () => {
const muter = createAssignmentMuter()
muter.afterUpdate({assignment: responseAssignment})
strictEqual(muter.assignment.muted, responseAssignment.muted)
})
test('publishes "assignment_muting_toggled" message', () => {
const muter = createAssignmentMuter()
muter.afterUpdate({assignment: responseAssignment})
ok($.publish.calledWith('assignment_muting_toggled'))
})
test('publishes "assignment_muting_toggled" message with the updated anonymize_students attribute', () => {
const muter = createAssignmentMuter()
muter.afterUpdate({assignment: responseAssignment})
const [publishedAssignment] = $.publish.getCall(0).args[1]
strictEqual(publishedAssignment.anonymize_students, responseAssignment.anonymize_students)
})
test('publishes "assignment_muting_toggled" message with the updated muted attribute', () => {
const muter = createAssignmentMuter()
muter.afterUpdate({assignment: responseAssignment})
const [publishedAssignment] = $.publish.getCall(0).args[1]
strictEqual(publishedAssignment.muted, responseAssignment.muted)
})
})
QUnit.module('when passed a setter function', () => {
test('sets anonymize_students via the setter function', () => {
const setterFn = sinon.stub()
const muter = createAssignmentMuter(setterFn)
muter.afterUpdate({assignment: responseAssignment})
ok(setterFn.calledWith(assignment, 'anonymize_students', responseAssignment.anonymize_students))
})
test('sets muted via the setter function', () => {
const setterFn = sinon.stub()
const muter = createAssignmentMuter(setterFn)
muter.afterUpdate({assignment: responseAssignment})
ok(setterFn.calledWith(assignment, 'muted', responseAssignment.anonymize_students))
})
test('publishes "assignment_muting_toggled" message', () => {
const muter = createAssignmentMuter(() => {})
muter.afterUpdate({assignment: responseAssignment})
ok($.publish.calledWith('assignment_muting_toggled'))
})
test('publishes "assignment_muting_toggled" message with the updated anonymize_students attribute', () => {
const muter = createAssignmentMuter(() => {})
muter.afterUpdate({assignment: responseAssignment})
const [publishedAssignment] = $.publish.getCall(0).args[1]
strictEqual(publishedAssignment.anonymize_students, responseAssignment.anonymize_students)
})
test('publishes "assignment_muting_toggled" message with the updated muted attribute', () => {
const muter = createAssignmentMuter(() => {})
muter.afterUpdate({assignment: responseAssignment})
const [publishedAssignment] = $.publish.getCall(0).args[1]
strictEqual(publishedAssignment.muted, responseAssignment.muted)
})
})
})
})

View File

@ -885,7 +885,8 @@ QUnit.module('Gradebook#gotAllAssignmentGroups', suiteHooks => {
name: 'test',
published: true,
anonymous_grading: false,
moderated_grading: false
moderated_grading: false,
anonymize_students: false
}
moderatedUnpublishedAssignment = {
id: 2,
@ -893,7 +894,8 @@ QUnit.module('Gradebook#gotAllAssignmentGroups', suiteHooks => {
published: true,
anonymous_grading: false,
moderated_grading: true,
grades_published: false
grades_published: false,
anonymize_students: false
}
moderatedPublishedAssignment = {
id: 3,
@ -901,7 +903,8 @@ QUnit.module('Gradebook#gotAllAssignmentGroups', suiteHooks => {
published: true,
anonymous_grading: false,
moderated_grading: true,
grades_published: true
grades_published: true,
anonymize_students: false
}
anonymousUnmoderatedAssignment = {
id: 4,
@ -909,7 +912,8 @@ QUnit.module('Gradebook#gotAllAssignmentGroups', suiteHooks => {
published: true,
anonymous_grading: true,
moderated_grading: false,
grades_published: true
grades_published: true,
anonymize_students: true
}
anonymousModeratedAssignment = {
id: 5,
@ -917,7 +921,8 @@ QUnit.module('Gradebook#gotAllAssignmentGroups', suiteHooks => {
published: true,
anonymous_grading: true,
moderated_grading: true,
grades_published: true
grades_published: true,
anonymize_students: true
}
assignmentGroups = [{
@ -956,16 +961,6 @@ QUnit.module('Gradebook#gotAllAssignmentGroups', suiteHooks => {
gradebook.gotAllAssignmentGroups(assignmentGroups)
strictEqual(unmoderatedAssignment.moderation_in_progress, false)
})
test('sets hide_grades_when_muted to true for an anonymous assignment', () => {
gradebook.gotAllAssignmentGroups(assignmentGroups)
strictEqual(anonymousUnmoderatedAssignment.hide_grades_when_muted, true)
})
test('sets hide_grades_when_muted to false for a non-anonymous assignment', () => {
gradebook.gotAllAssignmentGroups(assignmentGroups)
strictEqual(unmoderatedAssignment.hide_grades_when_muted, false)
})
})
QUnit.module('Gradebook#calculateAndRoundGroupTotalScore', (hooks) => {
@ -992,3 +987,54 @@ QUnit.module('Gradebook#calculateAndRoundGroupTotalScore', (hooks) => {
strictEqual(score, 94.67)
})
})
QUnit.module('Gradebook#handleAssignmentMutingChange', (hooks) => {
let gradebook
let updatedAssignment
hooks.beforeEach(() => {
gradebook = createGradebook()
gradebook.assignments = {14: {id: '14', muted: false, anonymize_students: false}}
gradebook.grid = {
getColumnIndex: sinon.stub().returns(0),
getColumns: sinon.stub().returns([{}]),
invalidateRow() {},
render() {},
setColumns() {},
}
gradebook.students = {4: {id: '4', name: 'fred'}}
gradebook.studentViewStudents = {6: {id: '6', name: 'fake fred'}}
sinon.stub(gradebook, 'buildRows')
updatedAssignment = {id: '14', muted: true, anonymize_students: true}
})
test('updates the anonymize_students attribute on the assignment', () => {
gradebook.handleAssignmentMutingChange(updatedAssignment)
strictEqual(gradebook.assignments[updatedAssignment.id].anonymize_students, updatedAssignment.anonymize_students)
})
test('updates the muted attribute on the assignment', () => {
gradebook.handleAssignmentMutingChange(updatedAssignment)
strictEqual(gradebook.assignments[updatedAssignment.id].muted, updatedAssignment.muted)
})
test('updates grade cells', () => {
sinon.stub(gradebook, 'setupGrading')
gradebook.handleAssignmentMutingChange(updatedAssignment)
strictEqual(gradebook.setupGrading.callCount, 1)
})
test('updates grade cells for students', () => {
sinon.stub(gradebook, 'setupGrading')
gradebook.handleAssignmentMutingChange(updatedAssignment)
const studentIDs = gradebook.setupGrading.getCall(0).args[0].map(student => student.id)
ok(studentIDs.includes('4'))
})
test('updates grade cells for fake students', () => {
sinon.stub(gradebook, 'setupGrading')
gradebook.handleAssignmentMutingChange(updatedAssignment)
const studentIDs = gradebook.setupGrading.getCall(0).args[0].map(student => student.id)
ok(studentIDs.includes('6'))
})
})

View File

@ -549,25 +549,15 @@ test('#loadValue sets the value to grade when entered_grade is not available', f
})
QUnit.module('SubmissionCell#classesBasedOnSubmission', () => {
test('returns anonymous when anonymous_grading and moderation_in_progress are set on the assignment', () => {
const assignment = {anonymous_grading: true, moderation_in_progress: true}
test('returns anonymous when anonymize_students is set on the assignment', () => {
const assignment = {anonymize_students: true}
strictEqual(SubmissionCell.classesBasedOnSubmission({}, assignment).includes('anonymous'), true)
})
test('returns anonymous when anonymous_grading and muted are set on the assignment', () => {
const assignment = {anonymous_grading: true, muted: true}
strictEqual(SubmissionCell.classesBasedOnSubmission({}, assignment).includes('anonymous'), true)
})
test('does not return anonymous if anonymous_grading is not set on the assignment', () => {
test('does not return anonymous if anonymize_students is not set on the assignment', () => {
strictEqual(SubmissionCell.classesBasedOnSubmission({}, {}).includes('anonymous'), false)
})
test('does not return anonymous if anonymous_grading is set but not moderation_in_progress or muted', () => {
const assignment = {anonymous_grading: true}
strictEqual(SubmissionCell.classesBasedOnSubmission({}, assignment).includes('anonymous'), false)
})
test('returns moderated when moderation_in_progress is set on the assignment', () => {
const assignment = {moderation_in_progress: true}
strictEqual(SubmissionCell.classesBasedOnSubmission({}, assignment).includes('moderated'), true)
@ -582,8 +572,8 @@ QUnit.module('SubmissionCell#classesBasedOnSubmission', () => {
strictEqual(SubmissionCell.classesBasedOnSubmission({}, {}).includes('moderated'), false)
})
test('does not return moderated if moderation_in_progress and anonymous_grading are set on the assignment', () => {
const assignment = {moderation_in_progress: true, anonymous_grading: true}
test('does not return moderated if anonymize_students is set on the assignment', () => {
const assignment = {anonymize_students: true}
strictEqual(SubmissionCell.classesBasedOnSubmission({}, assignment).includes('moderated'), false)
})
@ -597,8 +587,8 @@ QUnit.module('SubmissionCell#classesBasedOnSubmission', () => {
strictEqual(SubmissionCell.classesBasedOnSubmission({}, assignment).includes('muted'), false)
})
test('does not return muted when muted and anonymous_grading are set on the assignment', () => {
const assignment = {anonymous_grading: true, muted: true}
test('does not return muted when anonymize_students is set on the assignment', () => {
const assignment = {anonymize_students: true}
strictEqual(SubmissionCell.classesBasedOnSubmission({}, assignment).includes('muted'), false)
})

View File

@ -60,11 +60,13 @@ test('speed_grader_enabled sets speedgrader url', function() {
})
test('speedGraderUrl excludes student id when assignment is anonymously graded', function() {
this.assignment.anonymous_grading = true
const server = sinon.fakeServer.create()
this.assignment.anonymize_students = true
this.options.context_url = 'http://some-fake-url'
const dialog = new SubmissionDetailsDialog(this.assignment, this.user, this.options)
notOk(dialog.submission.speedGraderUrl.match(/student_id/))
server.restore()
})
test('speedGraderUrl includes student id when assignment is not anonymously graded', function() {

View File

@ -456,13 +456,13 @@ QUnit.module('SpeedgraderHelpers.setupIsAnonymous', suiteHooks => {
fakeENV.teardown()
})
test('returns true when assignment is anonymously graded', () => {
strictEqual(setupIsAnonymous({anonymous_grading: true}), true)
test('returns true when assignment has anonymize_students set to true', () => {
strictEqual(setupIsAnonymous({anonymize_students: true}), true)
fakeENV.teardown()
})
test('returns false when assignment is not anonymously graded', () => {
strictEqual(setupIsAnonymous({anonymous_grading: false}), false)
test('returns false when assignment has anonymize_students set to false', () => {
strictEqual(setupIsAnonymous({anonymize_students: false}), false)
})
})

View File

@ -744,6 +744,35 @@ describe AssignmentsController do
@assignment.reload
expect(@assignment).not_to be_muted
end
describe 'anonymize_students' do
it "is included in the response" do
put 'toggle_mute', params: { course_id: @course.id, assignment_id: @assignment.id, status: true }, format: 'json'
assignment_json = json_parse(response.body)['assignment']
expect(assignment_json).to have_key('anonymize_students')
end
it "is true if the assignment is anonymous and muted" do
@assignment.update!(anonymous_grading: true)
@assignment.unmute!
put 'toggle_mute', params: { course_id: @course.id, assignment_id: @assignment.id, status: true }, format: 'json'
assignment_json = json_parse(response.body)['assignment']
expect(assignment_json.fetch('anonymize_students')).to be true
end
it "is false if the assignment is anonymous and unmuted" do
@assignment.update!(anonymous_grading: true)
put 'toggle_mute', params: { course_id: @course.id, assignment_id: @assignment.id, status: false }, format: 'json'
assignment_json = json_parse(response.body)['assignment']
expect(assignment_json.fetch('anonymize_students')).to be false
end
it "is false if the assignment is not anonymous" do
put 'toggle_mute', params: { course_id: @course.id, assignment_id: @assignment.id, status: true }, format: 'json'
assignment_json = json_parse(response.body)['assignment']
expect(assignment_json.fetch('anonymize_students')).to be false
end
end
end
end

View File

@ -150,4 +150,52 @@ describe EportfolioEntriesController do
end
end
end
describe "GET 'submission'" do
before(:once) do
eportfolio_entry(@category)
course = Course.create!
course.enroll_student(@user, enrollment_state: @active)
@assignment = course.assignments.create!
@submission = @assignment.submissions.find_by(user: @user)
end
it 'requires authorization' do
get 'submission', params: { eportfolio_id: @portfolio.id, entry_id: @entry.id, submission_id: @submission.id }
assert_unauthorized
end
it 'passes anonymize_students: false to the template if the assignment is not anonymous' do
user_session(@user)
expect(controller).to receive(:render).with({
template: 'submissions/show_preview',
locals: { anonymize_students: false }
}).and_call_original
get 'submission', params: { eportfolio_id: @portfolio.id, entry_id: @entry.id, submission_id: @submission.id }
end
it 'passes anonymize_students: false to the template if the assignment is anonymous and unmuted' do
user_session(@user)
@assignment.update!(anonymous_grading: true)
@assignment.unmute!
expect(controller).to receive(:render).with({
template: 'submissions/show_preview',
locals: { anonymize_students: false }
}).and_call_original
get 'submission', params: { eportfolio_id: @portfolio.id, entry_id: @entry.id, submission_id: @submission.id }
end
it 'passes anonymize_students: true to the template if the assignment is anonymous and muted' do
user_session(@user)
@assignment.update!(anonymous_grading: true, muted: true)
expect(controller).to receive(:render).with({
template: 'submissions/show_preview',
locals: { anonymize_students: true }
}).and_call_original
get 'submission', params: { eportfolio_id: @portfolio.id, entry_id: @entry.id, submission_id: @submission.id }
end
end
end

View File

@ -1272,35 +1272,42 @@ describe GradebooksController do
end
describe 'anonymous assignment' do
it 'works with the absense of user_id and the presence of anonymous_id' do
user_session(@teacher)
before(:once) do
@assignment.update!(anonymous_grading: true)
post 'update_submission', params: {
end
let(:post_params) do
{
course_id: @course.id,
submission: {
assignment_id: @assignment.id,
anonymous_id: @submission.anonymous_id,
grade: 10
}
}, format: :json
}
end
it 'works with the absence of user_id and the presence of anonymous_id' do
user_session(@teacher)
post(:update_submission, params: post_params, format: :json)
submissions = json.map {|submission| submission.fetch('submission').fetch('anonymous_id')}
expect(submissions).to contain_exactly(@submission.anonymous_id)
end
it 'does not include user_ids' do
it 'does not include user_ids for muted anonymous assignments' do
user_session(@teacher)
@assignment.update!(anonymous_grading: true)
post 'update_submission', params: {
course_id: @course.id,
submission: {
assignment_id: @assignment.id,
anonymous_id: @submission.anonymous_id,
grade: 10
}
}, format: :json
post(:update_submission, params: post_params, format: :json)
submissions = json.map {|submission| submission['submission'].key?('user_id')}
expect(submissions).to contain_exactly(false)
end
it 'includes user_ids for unmuted anonymous assignments' do
user_session(@teacher)
@assignment.unmute!
post(:update_submission, params: post_params, format: :json)
submission = json.first.fetch('submission')
expect(submission).to have_key('user_id')
end
end
end

View File

@ -74,10 +74,17 @@ describe GradebooksHelper do
expect(helper.force_anonymous_grading?(assignment)).to eq true
end
it 'returns true for an anonymously-graded assignment' do
it 'returns true for a muted anonymously-graded assignment' do
assignment = assignment_model
assignment.anonymous_grading = true
expect(helper.force_anonymous_grading?(assignment)).to eq true
assignment.muted = true
expect(helper.force_anonymous_grading?(assignment)).to be true
end
it 'returns false for an unmuted anonymously-graded assignment' do
assignment = assignment_model
assignment.anonymous_grading = true
expect(helper.force_anonymous_grading?(assignment)).to be false
end
it 'returns false for a non-anonymously-graded assignment' do
@ -99,6 +106,7 @@ describe GradebooksHelper do
it 'returns anonymous grading' do
assignment = assignment_model
assignment.anonymous_grading = true
assignment.muted = true
expect(helper.force_anonymous_grading_reason(assignment)).to match(/anonymous grading/)
end
end

View File

@ -66,8 +66,8 @@ define([
strictEqual(state.hideGrade, false);
});
test ('submission has grade hidden if anonymous and muted', function() {
const assignment = { id: '1', anonymous_grading: true, muted: true };
test ('submission has grade hidden if anonymize_students is true', function() {
const assignment = { id: '1', anonymize_students: true };
const map = createAndSetupMap(assignment);
const state = map.getSubmissionState({ user_id: student.id, assignment_id: assignment.id });
strictEqual(state.hideGrade, true);

View File

@ -66,8 +66,8 @@ define([
strictEqual(state.locked, false);
});
test ('submission is locked if anonymous and muted', function() {
const assignment = { id: '1', anonymous_grading: true, muted: true };
test ('submission is locked if anonymize_students is true', function() {
const assignment = { id: '1', anonymize_students: true };
const map = createAndSetupMap(assignment);
const state = map.getSubmissionState({ user_id: student.id, assignment_id: assignment.id });
strictEqual(state.locked, true);

View File

@ -42,7 +42,11 @@ const baseAssignment = fromJS({
effectiveDueDates: {1: {due_at: new Date(), grading_period_id: '2', in_closed_grading_period: true}}
})
const unpublishedAssignment = baseAssignment.merge({published: false})
const anonymousMutedAssignment = baseAssignment.merge({anonymous_grading: true, muted: true})
const anonymousMutedAssignment = baseAssignment.merge({
anonymize_students: true,
anonymous_grading: true,
muted: true
})
const moderatedAndGradesUnpublishedAssignment =
baseAssignment.merge({moderated_grading: true, grades_published: false})
const hiddenFromStudent =
@ -224,15 +228,15 @@ QUnit.module('#setSubmissionCellState', function() {
assignment = { id: '1', published: true, anonymous_grading: true }
})
test('the submission state is locked when the assignment is muted', function() {
assignment.muted = true
test('the submission state is locked when anonymize_students is true', function() {
assignment.anonymize_students = true
const map = createAndSetupMap(assignment, studentWithSubmission)
const submission = map.getSubmissionState({ user_id: studentWithSubmission.id, assignment_id: assignment.id })
strictEqual(submission.locked, true)
})
test('the submission state is hidden when the assignment is muted', function() {
assignment.muted = true
test('the submission state is hidden when anonymize_students is true', function() {
assignment.anonymize_students = true
const map = createAndSetupMap(assignment, studentWithSubmission)
const submission = map.getSubmissionState({ user_id: studentWithSubmission.id, assignment_id: assignment.id })
strictEqual(submission.hideGrade, true)

View File

@ -45,7 +45,7 @@ QUnit.module('AssignmentColumnHeaderRenderer', function (suiteHooks) {
assignment = {
id: '2301',
anonymous_grading: false,
anonymize_students: false,
assignment_visibility: null,
course_id: '1201',
grading_type: 'points',
@ -173,15 +173,15 @@ QUnit.module('AssignmentColumnHeaderRenderer', function (suiteHooks) {
equal(component.props.downloadSubmissionsAction, gradebook.getDownloadSubmissionsAction.returnValues[0]);
});
test('the anonymousGrading prop is `true` when the assignment is anonymous', function () {
assignment.anonymous_grading = true
test('the anonymizeStudents prop is `true` when the assignment is anonymous', function () {
assignment.anonymize_students = true
render()
strictEqual(component.props.assignment.anonymousGrading, true)
strictEqual(component.props.assignment.anonymizeStudents, true)
})
test('the anonymousGrading prop is `false` when the assignment is not anonymous', function () {
test('the anonymizeStudents prop is `false` when the assignment is not anonymous', function () {
render()
strictEqual(component.props.assignment.anonymousGrading, false)
strictEqual(component.props.assignment.anonymizeStudents, false)
})
test('shows the "enter grades as" setting for a "points" assignment', function () {

View File

@ -26,6 +26,7 @@ import {findFlyoutMenuContent, findMenuItem} from './columnHeaderHelpers'
function createAssignmentProp ({ assignment } = {}) {
return {
anonymizeStudents: false,
courseId: '42',
htmlUrl: 'http://assignment_htmlUrl',
id: '1',
@ -738,14 +739,14 @@ QUnit.module('AssignmentColumnHeader: non-standard assignment', function (hooks)
test('renders an unpublished status when the assignment is unpublished and anonymously graded', function () {
props.assignment.published = false;
props.assignment.anonymousGrading = true;
props.assignment.anonymizeStudents = true;
wrapper = mountComponent(props);
const secondaryDetail = wrapper.find('.Gradebook__ColumnHeaderDetail--secondary');
strictEqual(secondaryDetail.text(), 'Unpublished');
});
test('renders an anonymous status when the assignment is anonymously graded', function() {
props.assignment.anonymousGrading = true;
props.assignment.anonymizeStudents = true;
wrapper = mountComponent(props);
const secondaryDetail = wrapper.find('.Gradebook__ColumnHeaderDetail--secondary');
strictEqual(secondaryDetail.text(), 'Anonymous');

View File

@ -32,6 +32,7 @@ QUnit.module('GradeInput', suiteHooks => {
suiteHooks.beforeEach(() => {
assignment = {
anonymizeStudents: false,
gradingType: 'points'
}
submission = {
@ -108,9 +109,8 @@ QUnit.module('GradeInput', suiteHooks => {
strictEqual(input.prop('value'), '')
})
test('is blank when the assignment is anonymously graded and muted', () => {
props.assignment.anonymousGrading = true
props.assignment.muted = true
test('is blank when the assignment has anonymized students', () => {
props.assignment.anonymizeStudents = true
mountComponent()
const input = wrapper.find('input')
strictEqual(input.prop('value'), '')
@ -432,9 +432,8 @@ QUnit.module('GradeInput', suiteHooks => {
strictEqual(input.prop('value'), 'Excused')
})
test('is blank when the assignment is anonymously graded and muted', () => {
props.assignment.anonymousGrading = true
props.assignment.muted = true
test('is blank when the assignment has anonymized students', () => {
props.assignment.anonymizeStudents = true
mountComponent()
const input = wrapper.find('input')
strictEqual(input.prop('value'), '')
@ -701,9 +700,8 @@ QUnit.module('GradeInput', suiteHooks => {
strictEqual(input.prop('value'), 'Excused')
})
test('is blank when the assignment is anonymously graded and muted', () => {
props.assignment.anonymousGrading = true
props.assignment.muted = true
test('is blank when the assignment has anonymized students', () => {
props.assignment.anonymizeStudents = true
mountComponent()
const input = wrapper.find('input')
strictEqual(input.prop('value'), '')
@ -968,9 +966,8 @@ QUnit.module('GradeInput', suiteHooks => {
strictEqual(input.prop('disabled'), true)
})
test('shows empty string if anonymously graded and muted', () => {
props.assignment.anonymousGrading = true
props.assignment.muted = true
test('shows empty string if the assignment has anonymized students', () => {
props.assignment.anonymizeStudents = true
mountComponent()
const input = wrapper.find('select')
strictEqual(input.prop('value'), '');
@ -1137,7 +1134,7 @@ QUnit.module('GradeInput', suiteHooks => {
mountComponent()
submission = {enteredScore: 50, excused: false}
assignment = {pointsPossible: 10, gradingType: 'points'}
assignment = {anonymizeStudents: false, pointsPossible: 10, gradingType: 'points'}
wrapper.setProps({submission, assignment})
ok(wrapper.text().includes('This grade is unusually high'))

View File

@ -56,6 +56,7 @@ QUnit.module('SubmissionTray', function (hooks) {
gradingDisabled: false,
gradingScheme: [['A', 0.90], ['B+', 0.85], ['B', 0.80], ['B-', 0.75]],
locale: 'en',
onAnonymousSpeedGraderClick () {},
onGradeSubmission () {},
onRequestClose () {},
onClose () {},
@ -83,6 +84,7 @@ QUnit.module('SubmissionTray', function (hooks) {
updateSubmission () {},
updateSubmissionComment () {},
assignment: {
anonymizeStudents: false,
name: 'Book Report',
gradingType: 'points',
htmlUrl: 'http://htmlUrl/',
@ -204,11 +206,10 @@ QUnit.module('SubmissionTray', function (hooks) {
test('invokes "onAnonymousSpeedGraderClick" when the SpeedGrader link is clicked if the assignment is anonymous', function () {
const props = {
assignment: {
anonymizeStudents: true,
name: 'Book Report',
gradingType: 'points',
htmlUrl: 'http://htmlUrl/',
anonymousGrading: true,
muted: false,
published: true
},
onAnonymousSpeedGraderClick: sinon.stub()
@ -218,8 +219,8 @@ QUnit.module('SubmissionTray', function (hooks) {
strictEqual(props.onAnonymousSpeedGraderClick.callCount, 1)
})
test('omits student_id from SpeedGrader link if enabled and assignment is anonymously graded', function() {
mountComponent({assignment: {anonymousGrading: true}});
test('omits student_id from SpeedGrader link if enabled and assignment has anonymized students', function() {
mountComponent({assignment: {anonymizeStudents: true}});
const speedGraderLink = document.querySelector('.SubmissionTray__Container a[href*="speed_grader"]').getAttribute('href');
notOk(speedGraderLink.match(/student_id/))
});
@ -522,8 +523,10 @@ QUnit.module('SubmissionTray', function (hooks) {
QUnit.module('Grade Input', function () {
test('receives the "assignment" given to the Tray', function () {
const assignment = {
anonymizeStudents: false,
gradingType: 'points',
htmlUrl: 'http://htmlUrl/',
moderatedGrading: false,
muted: false,
name: 'Book Report',
published: true
@ -604,25 +607,61 @@ QUnit.module('SubmissionTray', function (hooks) {
});
test('renders new comment form if assignment is not muted', function () {
mountComponent({assignment: {muted: false, anonymousGrading: false, moderatedGrading: true}});
const assignment = {
anonymizeStudents: false,
gradingType: 'points',
htmlUrl: 'foo',
moderatedGrading: true,
muted: false,
name: 'foo',
published: false
};
mountComponent({assignment});
const form = wrapContent().find(SubmissionCommentCreateForm);
strictEqual(form.length, 1);
});
test('renders new comment form if assignment is muted and not anonymous or moderated', function () {
mountComponent({assignment: {muted: true, anonymousGrading: false, moderatedGrading: false}});
const assignment = {
anonymizeStudents: false,
gradingType: 'points',
htmlUrl: 'foo',
moderatedGrading: false,
muted: true,
name: 'foo',
published: false
};
mountComponent({assignment});
const form = wrapContent().find(SubmissionCommentCreateForm);
strictEqual(form.length, 1);
});
test('does not render new comment form if assignment is muted and anonymous', function () {
mountComponent({assignment: {muted: true, anonymousGrading: true, moderatedGrading: false}});
test('does not render new comment form if assignment has anonymized students', function () {
const assignment = {
anonymizeStudents: true,
gradingType: 'points',
htmlUrl: 'foo',
moderatedGrading: false,
muted: true,
name: 'foo',
published: false
};
mountComponent({assignment});
const form = wrapContent().find(SubmissionCommentCreateForm);
strictEqual(form.length, 0);
});
test('does not render new comment form if assignment is muted and moderated', function () {
mountComponent({assignment: {muted: true, anonymousGrading: false, moderatedGrading: true}});
const assignment = {
anonymizeStudents: false,
gradingType: 'points',
htmlUrl: 'foo',
moderatedGrading: true,
muted: true,
name: 'foo',
published: false
};
mountComponent({assignment});
const form = wrapContent().find(SubmissionCommentCreateForm);
strictEqual(form.length, 0);
});

View File

@ -1334,6 +1334,7 @@ QUnit.module('SpeedGrader - clicking save rubric button for an anonymous assignm
GROUP_GRADING_MODE: false,
points_possible: 10,
anonymous_grading: true,
anonymize_students: true,
submissions: [],
context: {
students: [
@ -1722,7 +1723,7 @@ QUnit.module('SpeedGrader', function(suiteHooks) {
})
QUnit.module('Anonymous Assignments', anonymousHooks => {
const assignment = {anonymous_grading: true}
const assignment = {anonymous_grading: true, anonymize_students: true}
const originalJsonData = window.jsonData
const alpha = {anonymous_id: '00000'}
const omega = {anonymous_id: 'zzzzz'}

View File

@ -377,4 +377,23 @@ describe Api::V1::Submission do
end
end
end
describe '#submission_zip' do
let(:attachment) { fake_controller.submission_zip(assignment) }
it 'locks the attachment if the assignment is anonymous and muted' do
assignment.muted = true
assignment.anonymous_grading = true
expect(attachment).to be_locked
end
it 'does not lock the attachment if the assignment is anonymous and unmuted' do
assignment.anonymous_grading = true
expect(attachment).not_to be_locked
end
it 'does not lock the attachment if the assignment is not anonymous' do
expect(attachment).not_to be_locked
end
end
end

View File

@ -58,6 +58,40 @@ describe ContentZipper do
expect(expected_file_patterns).to be_empty
end
context 'anonymous assignments' do
before(:once) do
course = Course.create!
student = User.create!(name: 'fred')
@teacher = User.create!
course.enroll_student(student, enrollment_state: :active)
course.enroll_teacher(@teacher, enrollment_state: :active)
@assignment = course.assignments.create!(anonymous_grading: true, muted: true)
@assignment.submit_homework(student, body: 'homework')
@attachment = @assignment.attachments.create!(
display_name: 'my_download.zip',
user: @teacher,
workflow_state: 'to_be_zipped'
)
end
it 'omits user names if the assignment is anonymous and muted' do
ContentZipper.process_attachment(@attachment, @teacher)
Zip::File.open(@attachment.reload.full_filename) do |zip_file|
filename = zip_file.first.name
expect(filename).not_to match(/fred/)
end
end
it 'includes user names if the assignment is anonymous and unmuted' do
@assignment.unmute!
ContentZipper.process_attachment(@attachment, @teacher)
Zip::File.open(@attachment.reload.full_filename) do |zip_file|
filename = zip_file.first.name
expect(filename).to match(/fred/)
end
end
end
it "should ignore undownloadable submissions" do
course_with_student(active_all: true)
@user.update_attributes!(sortable_name: 'some_999_, _1234_guy')

View File

@ -596,6 +596,42 @@ describe Assignment do
end
end
describe '#anonymize_students?' do
before(:once) do
@assignment = @course.assignments.build
end
it 'returns false when the assignment is not graded anonymously' do
expect(@assignment).not_to be_anonymize_students
end
context 'when the assignment is anonymously graded' do
before(:once) do
@assignment.anonymous_grading = true
end
it 'returns true when the assignment is muted' do
@assignment.muted = true
expect(@assignment).to be_anonymize_students
end
it 'returns false when the assignment is unmuted' do
expect(@assignment).not_to be_anonymize_students
end
it 'returns true when the assignment is moderated and grades are unpublished' do
@assignment.moderated_grading = true
expect(@assignment).to be_anonymize_students
end
it 'returns false when the assignment is moderated and grades are published' do
@assignment.moderated_grading = true
@assignment.grades_published_at = Time.zone.now
expect(@assignment).not_to be_anonymize_students
end
end
end
describe '#can_view_student_names?' do
let_once(:admin) do
admin = account_admin_user
@ -642,9 +678,33 @@ describe Assignment do
expect(assignment.can_view_student_names?(@teacher)).to be false
end
it 'returns false when the user is an admin' do
it 'returns false when the user is an admin and the assignment is muted' do
expect(assignment.can_view_student_names?(admin)).to be false
end
it 'returns true when the user is an admin and the assignment is unmuted' do
assignment.muted = false
expect(assignment.can_view_student_names?(admin)).to be true
end
context 'when the assignment is moderated' do
before(:once) do
assignment.moderated_grading = true
end
it 'returns false when the user is not an admin' do
expect(assignment.can_view_student_names?(@teacher)).to be false
end
it 'returns true when the user is an admin and grades are published' do
assignment.grades_published_at = Time.zone.now
expect(assignment.can_view_student_names?(admin)).to be true
end
it 'returns false when the user is an admin and grades are unpublished' do
expect(assignment.can_view_student_names?(admin)).to be false
end
end
end
end

View File

@ -159,6 +159,41 @@ describe Quizzes::Quiz do
end
end
describe '#anonymize_students?' do
before(:once) do
@quiz = @course.quizzes.build
@assignment = @course.assignments.build
end
it 'returns false if there is no assignment' do
expect(@quiz).not_to be_anonymize_students
end
it 'returns true if the assignment is anonymous and muted' do
@quiz.assignment = @assignment
@assignment.anonymous_grading = true
@assignment.muted = true
expect(@quiz).to be_anonymize_students
end
it 'returns false if the assignment is anonymous and unmuted' do
@quiz.assignment = @assignment
@assignment.anonymous_grading = true
expect(@quiz).not_to be_anonymize_students
end
it 'returns false if the assignment is not anonymous' do
@quiz.assignment = @assignment
expect(@quiz).not_to be_anonymize_students
end
it 'calls Assignment#anonymize_students if an assignment is present' do
@quiz.assignment = @assignment
expect(@assignment).to receive(:anonymize_students?).once
@quiz.anonymize_students?
end
end
describe "#unpublish!" do
it "sets the workflow state to unpublished and save!s the quiz" do
quiz = @course.quizzes.build :title => "hello"

View File

@ -4075,34 +4075,68 @@ describe Submission do
describe "#can_view_details?" do
before :each do
@assignment.update!(anonymous_grading: true, muted: true)
@assignment.update!(anonymous_grading: true)
@submission = @assignment.submit_homework(@student, submission_type: 'online_text_entry', body: 'a body')
end
it "returns false if user isn't present" do
expect(@submission).not_to be_can_view_details(nil)
context 'when the assignment is muted' do
it "returns false if user isn't present" do
expect(@submission).not_to be_can_view_details(nil)
end
it "returns true for submitting student if assignment anonymous grading" do
expect(@submission.can_view_details?(@student)).to be true
end
it "returns false for non-submitting student if assignment anonymous grading" do
new_student = User.create!
@context.enroll_student(new_student, enrollment_state: 'active')
expect(@submission.can_view_details?(@new_student)).to be false
end
it "returns false for teacher if assignment anonymous grading" do
expect(@submission.can_view_details?(@teacher)).to be false
end
it "returns false for admin if assignment anonymous grading" do
expect(@submission.can_view_details?(account_admin_user)).to be false
end
it "returns true for site admin if assignment anonymous grading" do
expect(@submission.can_view_details?(site_admin_user)).to be true
end
end
it "returns true for submitting student if assignment anonymous grading and muted" do
expect(@submission.can_view_details?(@student)).to be true
end
context 'when the assignment is unmuted' do
before(:each) do
@assignment.unmute!
end
it "returns false for non-submitting student if assignment anonymous grading and muted" do
new_student = User.create!
@context.enroll_student(new_student, enrollment_state: 'active')
expect(@submission.can_view_details?(@new_student)).to be false
end
it "returns false if user isn't present" do
expect(@submission).not_to be_can_view_details(nil)
end
it "returns false for teacher if assignment anonymous grading and muted" do
expect(@submission.can_view_details?(@teacher)).to be false
end
it "returns true for submitting student if assignment anonymous grading" do
expect(@submission.can_view_details?(@student)).to be true
end
it "returns false for admin if assignment anonymous grading and muted" do
expect(@submission.can_view_details?(account_admin_user)).to be false
end
it "returns false for non-submitting student if assignment anonymous grading" do
new_student = User.create!
@context.enroll_student(new_student, enrollment_state: 'active')
expect(@submission.can_view_details?(@new_student)).to be false
end
it "returns true for site admin if assignment anonymous grading and muted" do
expect(@submission.can_view_details?(site_admin_user)).to be true
it "returns true for teacher if assignment anonymous grading" do
expect(@submission.can_view_details?(@teacher)).to be true
end
it "returns true for admin if assignment anonymous grading" do
expect(@submission.can_view_details?(account_admin_user)).to be true
end
it "returns true for site admin if assignment anonymous grading" do
expect(@submission.can_view_details?(site_admin_user)).to be true
end
end
end

View File

@ -49,23 +49,6 @@ describe 'Original Gradebook' do
user_session(@teacher1)
end
context 'in submission detail' do
before(:each) do
@anonymous_assignment.unmute!
Gradebook::MultipleGradingPeriods.visit_gradebook(@course)
Gradebook::MultipleGradingPeriods.open_comment_dialog(0,1)
end
it 'cannot navigate to speedgrader for specific student', priority: '1', test_id: 3493483 do
# try to navigate to @student_2
Gradebook::MultipleGradingPeriods.submission_detail_speedgrader_link.click
driver.switch_to.window(driver.window_handles.last)
wait_for_ajaximations
expect(driver.current_url).not_to include "student_id"
end
end
context 'grade cells', priority: '1', test_id: 3496299 do
before(:each) do
Gradebook::MultipleGradingPeriods.visit_gradebook(@course)

View File

@ -24,7 +24,7 @@ describe "/gradebooks/speed_grader" do
include GroupsCommon
let(:locals) do
{ anonymous_grading: false }
{ anonymize_students: false }
end
before do
@ -141,9 +141,6 @@ describe "/gradebooks/speed_grader" do
end
context 'grading box' do
before(:once) do
end
let(:html) do
render template: 'gradebooks/speed_grader', locals: locals
Nokogiri::HTML.fragment(response.body)