Commit ccb2b71e by Andy Armstrong

Implement staff assessment form submission

parent 0d3b0780
......@@ -19,12 +19,14 @@
<div class="staff-info__student ui-staff__content__section">
<div class="wrapper--input" class="staff-info__student__form">
<form class="openassessment_student_info_form">
<div class="form--error"></div>
<label class="label">{% trans "Enter an individual learner's username or email" %}
<input type="text" class="openassessment__student_username value" maxlength="255">
</label>
<ul class="list list--actions">
<li class="list--actions__item">
<button class="action--submit action--submit-username"><span class="copy">{% trans "Submit" %}</span></button>
<div class="student-form-error"></div>
</li>
</ul>
</form>
......
{% load i18n %}
{% load tz %}
<div class="openassessment__student-info staff-info__student__report">
<div class="openassessment__student-info staff-info__student__report"
data-submission-uuid="{{ submission.uuid }}">
{% if submission %}
<h2 class="title">
<span class="label">
......@@ -11,7 +12,7 @@
</span>
</h2>
<div class="staff-info__status ui-staff__content__section wrapper--ui--collapse">
<div class="staff-info__status ui-staff__content__section wrapper--ui--collapse staff-info__student__response">
<div class="ui-staff ui-toggle-visibility is--collapsed">
<h2 class="staff-info__title ui-staff__subtitle ui-toggle-visibility__control">
<i class="icon fa fa-caret-right"></i>
......@@ -19,14 +20,17 @@
</h2>
<div class="ui-toggle-visibility__content">
{% if workflow_cancellation %}
{% blocktrans with removed_by_username=workflow_cancellation.cancelled_by removed_datetime=workflow_cancellation.created_at|utc|date:"N j, Y H:i e" %}
Learner submission removed by {{ removed_by_username }} on {{ removed_datetime }}
{% endblocktrans %}
<br>
<p>
{% blocktrans with removed_by_username=workflow_cancellation.cancelled_by removed_datetime=workflow_cancelled_at|utc|date:"F j, Y H:i e" %}
Learner submission removed by {{ removed_by_username }} on {{ removed_datetime }}
{% endblocktrans %}
</p>
<!-- Comments: Reason for Cancellation-->
{% blocktrans with comments=workflow_cancellation.comments %}
Comments: {{ comments }}
{% endblocktrans %}
<p>
{% blocktrans with comments=workflow_cancellation.comments %}
Comments: {{ comments }}
{% endblocktrans %}
</p>
{% else %}
{% include "openassessmentblock/oa_submission_answer.html" with answer=submission.answer answer_text_label="The learner's response to the question above:" %}
{% endif %}
......@@ -213,8 +217,8 @@
</div>
{% endif %}
<div class="staff-info__status ui-staff__content__section wrapper--ui--collapse">
<div class="ui-staff ui-toggle-visibility is--collapsed">
<div class="staff-info__status ui-staff__content__section wrapper--ui--collapse staff-info__student__grade">
<div class="ui-staff ui-toggle-visibility {% if expanded_view != 'final-grade' %}is--collapsed {% endif %}">
<h2 class="staff-info__title ui-staff__subtitle ui-toggle-visibility__control">
<i class="icon fa fa-caret-right"></i>
<span>{% trans "Learner's Final Grade" %}</span>
......@@ -229,7 +233,7 @@
{% elif workflow_status == "waiting" %}
<p>{% trans "The submission is waiting for assessments." %}</p>
{% elif workflow_status == "cancelled" %}
<p>{% trans "The submission has been cancelled." %}</p>
<p>{% trans "The learner's submission has been removed from peer assessment. The learner receives a grade of zero unless you reset the learner's attempts for the problem to allow them to resubmit a response." %}</p>
{% elif workflow_status == None %}
<p>{% trans "The problem has not been started." %}</p>
{% else %}
......@@ -239,27 +243,27 @@
</div>
</div>
<div class="staff-info__staff-override ui-staff__action__section wrapper--ui--collapse">
<div class="ui-staff ui-toggle-visibility is--collapsed">
<h2 class="staff-info__title ui-staff__title ui-toggle-visibility__control">
<i class="icon fa fa-caret-right"></i>
<span>{% trans "Assessment Override" %}</span>
</h2>
{% if not workflow_cancellation %}
<div class="staff-info__staff-override ui-staff__content__section wrapper--ui--collapse">
<div class="ui-staff ui-toggle-visibility {% if expanded_view != 'staff-override' %}is--collapsed {% endif %}">
<h2 class="staff-info__title ui-staff__subtitle ui-toggle-visibility__control">
<i class="icon fa fa-caret-right"></i>
<span>{% trans "Submit Assessment Grade Override" %}</span>
</h2>
<div class="staff-info__staff-override__content ui-toggle-visibility__content">
<div class="wrapper--input">
{% include "openassessmentblock/staff_area/oa_staff_assessment.html" %}
<div class="staff-info__staff-override__content ui-toggle-visibility__content">
<div class="wrapper--input">
{% include "openassessmentblock/staff_area/oa_staff_assessment.html" %}
</div>
</div>
</div>
</div>
</div>
{% if not workflow_cancellation %}
<div class="staff-info__workflow-cancellation ui-staff__action__section wrapper--ui--collapse">
<div class="staff-info__workflow-cancellation ui-staff__content__section wrapper--ui--collapse">
<div class="ui-staff ui-toggle-visibility is--collapsed">
<h2 class="staff-info__title ui-staff__title ui-toggle-visibility__control">
<h2 class="staff-info__title ui-staff__subtitle ui-toggle-visibility__control">
<i class="icon fa fa-caret-right"></i>
<span>{% trans "Remove submission from peer grading" %}</span>
<span>{% trans "Remove Submission From Peer Grading" %}</span>
</h2>
<div class="staff-info__cancel-submission__content ui-toggle-visibility__content">
......
......@@ -6,14 +6,9 @@ import logging
from django.db import DatabaseError
from openassessment.assessment.api import peer as peer_api
from openassessment.assessment.api import ai as ai_api
from openassessment.assessment.api import student_training as training_api
from openassessment.assessment.errors import (
PeerAssessmentError, StudentTrainingInternalError, AIError,
PeerAssessmentInternalError)
from openassessment.assessment.errors import PeerAssessmentError, PeerAssessmentInternalError
from submissions import api as sub_api
from .models import AssessmentWorkflow, AssessmentWorkflowCancellation, AssessmentWorkflowStep
from .models import AssessmentWorkflow, AssessmentWorkflowCancellation
from .serializers import AssessmentWorkflowSerializer, AssessmentWorkflowCancellationSerializer
from .errors import (
AssessmentWorkflowError, AssessmentWorkflowInternalError,
......
......@@ -24,6 +24,7 @@ from openassessment.assessment.api import self as self_api
from openassessment.assessment.api import ai as ai_api
from openassessment.fileupload import api as file_api
from openassessment.workflow import api as workflow_api
from openassessment.workflow.models import AssessmentWorkflowCancellation
from openassessment.fileupload import exceptions as file_exceptions
......@@ -214,13 +215,17 @@ class StaffAreaMixin(object):
"""
try:
student_username = data.params.get('student_username', '')
path, context = self.get_student_info_path_and_context(student_username)
expanded_view = data.params.get('expanded_view', [])
path, context = self.get_student_info_path_and_context(
student_username,
expanded_view=expanded_view
)
return self.render_assessment(path, context)
except PeerAssessmentInternalError:
return self.render_error(self._(u"Error finding assessment workflow cancellation."))
def get_student_info_path_and_context(self, student_username):
def get_student_info_path_and_context(self, student_username, expanded_view=None):
"""
Get the proper path and context for rendering the student info
section of the staff area.
......@@ -284,17 +289,25 @@ class StaffAreaMixin(object):
if workflow_cancellation:
workflow_cancellation['cancelled_by'] = self.get_username(workflow_cancellation['cancelled_by_id'])
# Get the date that the workflow was cancelled to use in preference to the serialized date string
cancellation_model = AssessmentWorkflowCancellation.get_latest_workflow_cancellation(submission_uuid)
workflow_cancelled_at = cancellation_model.created_at
else:
workflow_cancelled_at = None
context = {
'submission': create_submission_dict(submission, self.prompts) if submission else None,
'score': workflow.get('score'),
'workflow_status': workflow.get('status'),
'workflow_cancellation': workflow_cancellation,
'workflow_cancelled_at': workflow_cancelled_at,
'peer_assessments': peer_assessments,
'submitted_assessments': submitted_assessments,
'self_assessment': self_assessment,
'example_based_assessment': example_based_assessment,
'rubric_criteria': copy.deepcopy(self.rubric_criteria_with_labels),
'student_username': student_username,
'expanded_view': expanded_view,
}
if peer_assessments or self_assessment or example_based_assessment:
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -50,7 +50,8 @@ describe("OpenAssessment.BaseView", function() {
// Create a new stub server
server = new StubServer();
server.renderLatex = jasmine.createSpy('renderLatex')
server.renderLatex = jasmine.createSpy('renderLatex');
// Create the object under test
var el = $("#openassessment").get(0);
view = new OpenAssessment.BaseView(runtime, el, server);
......
......@@ -26,16 +26,9 @@ describe("OpenAssessment.GradeView", function() {
};
};
// Stub base view
var StubBaseView = function() {
this.showLoadError = function(msg) {};
this.toggleActionError = function(msg, step) {};
this.setUpCollapseExpand = function(sel) {};
};
// Stubs
var baseView = null;
var server = null;
var runtime = {};
// View under test
var view = null;
......@@ -47,12 +40,10 @@ describe("OpenAssessment.GradeView", function() {
// Create the stub server
server = new StubServer();
// Create the stub base view
baseView = new StubBaseView();
// Create and install the view
var el = $('#openassessment-base').get(0);
view = new OpenAssessment.GradeView(el, server, baseView);
var gradeElement = $('#openassessment__grade').get(0);
var baseView = new OpenAssessment.BaseView(runtime, gradeElement, server, {});
view = new OpenAssessment.GradeView(gradeElement, server, baseView);
view.installHandlers();
});
......
......@@ -12,11 +12,11 @@ describe("OpenAssessment.PeerView", function() {
}
).promise();
this.peerAssess = function(optionsSelected, feedback) {
this.peerAssess = function() {
return successPromise;
};
this.render = function(step) {
this.render = function() {
return successPromise;
};
......@@ -25,40 +25,28 @@ describe("OpenAssessment.PeerView", function() {
};
};
// Stub base view
var StubBaseView = function() {
this.showLoadError = function(msg) {};
this.toggleActionError = function(msg, step) {};
this.setUpCollapseExpand = function(sel) {};
this.scrollToTop = function() {};
this.loadAssessmentModules = function() {};
this.loadMessageView = function() {};
};
// Stubs
var baseView = null;
var server = null;
var runtime = {};
// View under test
var view = null;
var createPeerAssessmentView = function(template) {
loadFixtures(template);
beforeEach(function() {
// Load the DOM fixture
loadFixtures('oa_peer_assessment.html');
var assessmentElement = $('#openassessment__peer-assessment').get(0);
var baseView = new OpenAssessment.BaseView(runtime, assessmentElement, server, {});
var view = new OpenAssessment.PeerView(assessmentElement, server, baseView);
view.installHandlers();
return view;
};
beforeEach(function() {
// Create a new stub server
server = new StubServer();
// Create the stub base view
baseView = new StubBaseView();
// Create the object under test
var el = $("#openassessment-base").get(0);
view = new OpenAssessment.PeerView(el, server, baseView);
view.installHandlers();
server.renderLatex = jasmine.createSpy('renderLatex');
});
it("Sends a peer assessment to the server", function() {
var view = createPeerAssessmentView('oa_peer_assessment.html');
spyOn(server, 'peerAssess').and.callThrough();
// Select options in the rubric
......@@ -89,6 +77,7 @@ describe("OpenAssessment.PeerView", function() {
});
it("Re-enables the peer assess button on error", function() {
var view = createPeerAssessmentView('oa_peer_assessment.html');
// Simulate a server error
spyOn(server, 'peerAssess').and.callFake(function() {
expect(view.peerSubmitEnabled()).toBe(false);
......@@ -103,8 +92,8 @@ describe("OpenAssessment.PeerView", function() {
});
it("Re-enables the continued grading button on error", function() {
jasmine.getFixtures().fixturesPath = 'base/fixtures';
loadFixtures('oa_peer_complete.html');
var view = createPeerAssessmentView('oa_peer_complete.html');
// Simulate a server error
spyOn(server, 'renderContinuedPeer').and.callFake(function() {
expect(view.continueAssessmentEnabled()).toBe(false);
......
......@@ -9,7 +9,7 @@ describe("OpenAssessment.ResponseView", function() {
'image/gif',
'image/jpeg',
'image/pjpeg',
'image/png',
'image/png'
];
var ALLOWED_FILE_MIME_TYPES = [
......@@ -17,7 +17,7 @@ describe("OpenAssessment.ResponseView", function() {
'image/gif',
'image/jpeg',
'image/pjpeg',
'image/png',
'image/png'
];
var FILE_TYPE_WHITE_LIST = ['pdf', 'doc', 'docx', 'html'];
......@@ -71,7 +71,7 @@ describe("OpenAssessment.ResponseView", function() {
// Store the args we were passed so we can verify them
this.uploadArgs = {
url: url,
data: data,
data: data
};
// Return a promise indicating success or error
......@@ -79,18 +79,9 @@ describe("OpenAssessment.ResponseView", function() {
};
};
var StubBaseView = function() {
this.loadAssessmentModules = function() {};
this.peerView = { load: function() {} };
this.gradeView = { load: function() {} };
this.showLoadError = function() {};
this.toggleActionError = function() {};
this.setUpCollapseExpand = function() {};
};
// Stubs
var baseView = null;
var server = null;
var runtime = {};
var fileUploader = null;
var data = null;
......@@ -120,7 +111,6 @@ describe("OpenAssessment.ResponseView", function() {
server = new StubServer();
server.renderLatex = jasmine.createSpy('renderLatex');
fileUploader = new StubFileUploader();
baseView = new StubBaseView();
data = {
"ALLOWED_IMAGE_MIME_TYPES": ALLOWED_IMAGE_MIME_TYPES,
"ALLOWED_FILE_MIME_TYPES": ALLOWED_FILE_MIME_TYPES,
......@@ -129,8 +119,9 @@ describe("OpenAssessment.ResponseView", function() {
};
// Create and install the view
var el = $('#openassessment-base').get(0);
view = new OpenAssessment.ResponseView(el, server, fileUploader, baseView, data);
var responseElement = $('#openassessment__response').get(0);
var baseView = new OpenAssessment.BaseView(runtime, responseElement, server, {});
view = new OpenAssessment.ResponseView(responseElement, server, fileUploader, baseView, data);
view.installHandlers();
// Stub the confirmation step
......@@ -296,14 +287,14 @@ describe("OpenAssessment.ResponseView", function() {
}).promise();
});
spyOn(view, 'load');
spyOn(baseView, 'loadAssessmentModules');
spyOn(view.baseView, 'loadAssessmentModules');
view.response(['Test response 1', 'Test response 2']);
view.submit();
// Expect the current and next step to have been reloaded
expect(view.load).toHaveBeenCalled();
expect(baseView.loadAssessmentModules).toHaveBeenCalled();
expect(view.baseView.loadAssessmentModules).toHaveBeenCalled();
});
it("enables the unsaved work warning when the user changes the response text", function() {
......@@ -442,39 +433,45 @@ describe("OpenAssessment.ResponseView", function() {
});
it("selects too large of a file", function() {
spyOn(baseView, 'toggleActionError').and.callThrough();
spyOn(view.baseView, 'toggleActionError').and.callThrough();
var files = [{type: 'image/jpeg', size: 6000000, name: 'huge-picture.jpg', data: ''}];
view.prepareUpload(files, 'image');
expect(baseView.toggleActionError).toHaveBeenCalledWith('upload', 'File size must be 5MB or less.');
expect(view.baseView.toggleActionError).toHaveBeenCalledWith('upload', 'File size must be 5MB or less.');
});
it("selects the wrong image file type", function() {
spyOn(baseView, 'toggleActionError').and.callThrough();
spyOn(view.baseView, 'toggleActionError').and.callThrough();
var files = [{type: 'image/jpg', size: 1024, name: 'picture.exe', data: ''}];
view.prepareUpload(files, 'image');
expect(baseView.toggleActionError).toHaveBeenCalledWith('upload', 'You can upload files with these file types: JPG, PNG or GIF');
expect(view.baseView.toggleActionError).toHaveBeenCalledWith(
'upload', 'You can upload files with these file types: JPG, PNG or GIF'
);
});
it("selects the wrong pdf or image file type", function() {
spyOn(baseView, 'toggleActionError').and.callThrough();
spyOn(view.baseView, 'toggleActionError').and.callThrough();
var files = [{type: 'application/exe', size: 1024, name: 'application.exe', data: ''}];
view.prepareUpload(files, 'pdf-and-image');
expect(baseView.toggleActionError).toHaveBeenCalledWith('upload', 'You can upload files with these file types: JPG, PNG, GIF or PDF');
expect(view.baseView.toggleActionError).toHaveBeenCalledWith(
'upload', 'You can upload files with these file types: JPG, PNG, GIF or PDF'
);
});
it("selects the wrong file extension", function() {
spyOn(baseView, 'toggleActionError').and.callThrough();
spyOn(view.baseView, 'toggleActionError').and.callThrough();
var files = [{type: 'application/exe', size: 1024, name: 'application.exe', data: ''}];
view.prepareUpload(files, 'custom');
expect(baseView.toggleActionError).toHaveBeenCalledWith('upload', 'You can upload files with these file types: pdf, doc, docx, html');
expect(view.baseView.toggleActionError).toHaveBeenCalledWith(
'upload', 'You can upload files with these file types: pdf, doc, docx, html'
);
});
it("submits a file with extension in the black list", function() {
spyOn(baseView, 'toggleActionError').and.callThrough();
spyOn(view.baseView, 'toggleActionError').and.callThrough();
view.data.FILE_TYPE_WHITE_LIST = ['exe'];
var files = [{type: 'application/exe', size: 1024, name: 'application.exe', data: ''}];
view.prepareUpload(files, 'custom');
expect(baseView.toggleActionError).toHaveBeenCalledWith('upload', 'File type is not allowed.');
expect(view.baseView.toggleActionError).toHaveBeenCalledWith('upload', 'File type is not allowed.');
});
it("uploads an image using a one-time URL", function() {
......@@ -504,7 +501,7 @@ describe("OpenAssessment.ResponseView", function() {
it("displays an error if a one-time file upload URL cannot be retrieved", function() {
// Configure the server to fail when retrieving the one-time URL
server.uploadUrlError = true;
spyOn(baseView, 'toggleActionError').and.callThrough();
spyOn(view.baseView, 'toggleActionError').and.callThrough();
// Attempt to upload a file
var files = [{type: 'image/jpeg', size: 1024, name: 'picture.jpg', data: ''}];
......@@ -512,13 +509,13 @@ describe("OpenAssessment.ResponseView", function() {
view.fileUpload();
// Expect an error to be displayed
expect(baseView.toggleActionError).toHaveBeenCalledWith('upload', 'ERROR');
expect(view.baseView.toggleActionError).toHaveBeenCalledWith('upload', 'ERROR');
});
it("displays an error if a file could not be uploaded", function() {
// Configure the file upload server to return an error
fileUploader.uploadError = true;
spyOn(baseView, 'toggleActionError').and.callThrough();
spyOn(view.baseView, 'toggleActionError').and.callThrough();
// Attempt to upload a file
var files = [{type: 'image/jpeg', size: 1024, name: 'picture.jpg', data: ''}];
......@@ -526,6 +523,6 @@ describe("OpenAssessment.ResponseView", function() {
view.fileUpload();
// Expect an error to be displayed
expect(baseView.toggleActionError).toHaveBeenCalledWith('upload', 'ERROR');
expect(view.baseView.toggleActionError).toHaveBeenCalledWith('upload', 'ERROR');
});
});
......@@ -21,17 +21,8 @@ describe("OpenAssessment.SelfView", function() {
};
};
// Stub base view
var StubBaseView = function() {
this.showLoadError = function(msg) {};
this.toggleActionError = function(msg, step) {};
this.setUpCollapseExpand = function(sel) {};
this.loadAssessmentModules = function() {};
this.scrollToTop = function() {};
};
// Stubs
var baseView = null;
var runtime = {};
var server = null;
// View under test
......@@ -43,13 +34,12 @@ describe("OpenAssessment.SelfView", function() {
// Create a new stub server
server = new StubServer();
// Create the stub base view
baseView = new StubBaseView();
server.renderLatex = jasmine.createSpy('renderLatex');
// Create the object under test
var el = $("#openassessment").get(0);
view = new OpenAssessment.SelfView(el, server, baseView);
var assessmentElement = $("#openassessment__self-assessment").get(0);
var baseView = new OpenAssessment.BaseView(runtime, assessmentElement, server, {});
view = new OpenAssessment.SelfView(assessmentElement, server, baseView);
view.installHandlers();
});
......
......@@ -20,6 +20,14 @@ describe('OpenAssessment.StaffAreaView', function() {
});
};
this.studentInfo = function() {
var server = this;
return $.Deferred(function(defer) {
var fragment = readFixtures('oa_student_info.html');
defer.resolveWith(server, [fragment]);
});
};
this.scheduleTraining = function() {
var server = this;
return $.Deferred(function(defer) {
......@@ -46,19 +54,9 @@ describe('OpenAssessment.StaffAreaView', function() {
};
// Stub base view
var StubBaseView = function() {
this.showLoadError = function() {};
this.toggleActionError = function() {};
this.setUpCollapseExpand = function() {};
this.scrollToTop = function() {};
this.loadAssessmentModules = function() {};
this.loadMessageView = function() {};
};
// Stubs
var baseView = null;
var server = null;
var runtime = {};
/**
* Create a staff area view.
......@@ -69,8 +67,9 @@ describe('OpenAssessment.StaffAreaView', function() {
if (serverResponse) {
server.data = serverResponse;
}
var el = $('#openassessment').get(0);
var view = new OpenAssessment.StaffAreaView(el, server, baseView);
var assessmentElement = $('#openassessment').get(0);
var baseView = new OpenAssessment.BaseView(runtime, assessmentElement, server, {});
var view = new OpenAssessment.StaffAreaView(assessmentElement, server, baseView);
view.load();
return view;
};
......@@ -93,8 +92,6 @@ describe('OpenAssessment.StaffAreaView', function() {
// Create a new stub server
server = new StubServer();
server.renderLatex = jasmine.createSpy('renderLatex');
// Create the stub base view
baseView = new StubBaseView();
});
describe('Initial rendering', function() {
......@@ -165,46 +162,64 @@ describe('OpenAssessment.StaffAreaView', function() {
});
});
describe('Submission Management', function() {
it('updates submission cancellation button when comments changes', function() {
// Prevent the server's response from resolving,
// so we can see what happens before view gets re-rendered.
spyOn(server, 'cancelSubmission').and.callFake(function() {
return $.Deferred(function() {}).promise();
});
// Load the fixture
loadFixtures('oa_student_info.html');
var view = createStaffArea();
// comments is blank --> cancel submission button disabled
view.comment('');
view.handleCommentChanged();
expect(view.cancelSubmissionEnabled()).toBe(false);
describe('Student Info', function() {
var chooseStudent = function(view, studentName) {
var studentNameField = $('.openassessment__student_username', view.element),
submitButton = $('.action--submit-username', view.element);
studentNameField.val(studentName);
submitButton.click();
};
// Response is whitespace --> cancel submission button disabled
view.comment(' \n \n ');
view.handleCommentChanged();
expect(view.cancelSubmissionEnabled()).toBe(false);
beforeEach(function() {
loadFixtures('oa_base_course_staff.html');
appendLoadFixtures('oa_student_info.html');
});
// Response is not blank --> cancel submission button enabled
view.comment('Cancellation reason.');
view.handleCommentChanged();
expect(view.cancelSubmissionEnabled()).toBe(true);
it('shows an error when clicking "Submit" with no student name chosen', function() {
var staffArea = createStaffArea();
chooseStudent(staffArea, '');
expect($('.openassessment_student_info_form .form--error', staffArea.element).text().trim())
.toBe('A learner name must be provided.');
});
it('submits the cancel submission comments to the server', function() {
spyOn(server, 'cancelSubmission').and.callThrough();
describe('Submission Management', function() {
it('updates submission cancellation button when comments changes', function() {
// Prevent the server's response from resolving,
// so we can see what happens before view gets re-rendered.
spyOn(server, 'cancelSubmission').and.callFake(function() {
return $.Deferred(function() {}).promise();
});
var staffArea = createStaffArea();
chooseStudent(staffArea, 'testStudent');
// comments is blank --> cancel submission button disabled
staffArea.comment('');
staffArea.handleCommentChanged();
expect(staffArea.cancelSubmissionEnabled()).toBe(false);
// Response is whitespace --> cancel submission button disabled
staffArea.comment(' \n \n ');
staffArea.handleCommentChanged();
expect(staffArea.cancelSubmissionEnabled()).toBe(false);
// Response is not blank --> cancel submission button enabled
staffArea.comment('Cancellation reason.');
staffArea.handleCommentChanged();
expect(staffArea.cancelSubmissionEnabled()).toBe(true);
});
// Load the fixture
loadFixtures('oa_student_info.html');
var view = createStaffArea();
it('submits the cancel submission comments to the server', function() {
spyOn(server, 'cancelSubmission').and.callThrough();
view.comment('Cancellation reason.');
view.cancelSubmission('Bob');
var staffArea = createStaffArea();
chooseStudent(staffArea, 'testStudent');
expect(server.cancelSubmission).toHaveBeenCalledWith('Bob', 'Cancellation reason.');
staffArea.comment('Cancellation reason.');
staffArea.cancelSubmission('Bob');
expect(server.cancelSubmission).toHaveBeenCalledWith('Bob', 'Cancellation reason.');
});
});
});
......
......@@ -27,18 +27,9 @@ describe("OpenAssessment.StudentTrainingView", function() {
this.corrections = {};
};
// Stub base view
var StubBaseView = function() {
this.showLoadError = function(msg) {};
this.toggleActionError = function(msg, step) {};
this.setUpCollapseExpand = function(sel) {};
this.scrollToTop = function() {};
this.loadAssessmentModules = function() {};
};
// Stubs
var baseView = null;
var server = null;
var runtime = {};
// View under test
var view = null;
......@@ -50,12 +41,11 @@ describe("OpenAssessment.StudentTrainingView", function() {
// Create a new stub server
server = new StubServer();
server.renderLatex = jasmine.createSpy('renderLatex')
// Create the stub base view
baseView = new StubBaseView();
// Create the object under test
var el = $("#openassessment-base").get(0);
view = new OpenAssessment.StudentTrainingView(el, server, baseView);
var trainingElement = $('#openassessment__student-training').get(0);
var baseView = new OpenAssessment.BaseView(runtime, trainingElement, server, {});
view = new OpenAssessment.StudentTrainingView(trainingElement, server, baseView);
view.installHandlers();
});
......@@ -112,7 +102,7 @@ describe("OpenAssessment.StudentTrainingView", function() {
// Simulate that the user answered the problem correctly, so there are no corrections
server.corrections = {};
spyOn(server, 'trainingAssess').and.callThrough();
spyOn(baseView, 'loadAssessmentModules').and.callThrough();
spyOn(view.baseView, 'loadAssessmentModules').and.callThrough();
// Select rubric options
var optionsSelected = {};
......@@ -128,6 +118,6 @@ describe("OpenAssessment.StudentTrainingView", function() {
expect(server.trainingAssess).toHaveBeenCalledWith(optionsSelected);
// Expect that the steps were reloaded
expect(baseView.loadAssessmentModules).toHaveBeenCalled();
expect(view.baseView.loadAssessmentModules).toHaveBeenCalled();
});
});
......@@ -4,3 +4,4 @@ Common test configuration, loaded before any of the spec files.
// Set the fixture path
jasmine.getFixtures().fixturesPath = 'base/fixtures';
......@@ -44,11 +44,10 @@ OpenAssessment.BaseView.prototype = {
},
/**
Install click handlers to expand/collapse a section.
Args:
parentSel (JQuery selector): CSS selector for the container element.
**/
* Install click handlers to expand/collapse a section.
*
* @param parentSel JQuery selector for the container element.
*/
setUpCollapseExpand: function (parentSel) {
parentSel.on('click', '.ui-toggle-visibility__control', function (eventData) {
var sel = $(eventData.target).closest('.ui-toggle-visibility');
......@@ -58,8 +57,8 @@ OpenAssessment.BaseView.prototype = {
},
/**
Asynchronously load each sub-view into the DOM.
**/
* Asynchronously load each sub-view into the DOM.
*/
load: function() {
this.responseView.load();
this.loadAssessmentModules();
......@@ -67,9 +66,9 @@ OpenAssessment.BaseView.prototype = {
},
/**
Refresh the Assessment Modules. This should be called any time an action is
performed by the user.
**/
* Refresh the Assessment Modules. This should be called any time an action is
* performed by the user.
*/
loadAssessmentModules: function() {
this.trainingView.load();
this.peerView.load();
......@@ -92,21 +91,20 @@ OpenAssessment.BaseView.prototype = {
},
/**
Refresh the message only (called by PeerView to update and avoid race condition)
**/
* Refresh the message only (called by PeerView to update and avoid race condition)
*/
loadMessageView: function() {
this.messageView.load();
},
/**
Report an error to the user.
Args:
type (str): Which type of error. Options are "save", submit", "peer", and "self".
msg (str or null): The error message to display.
If null, hide the error message (with one exception: loading errors are never hidden once displayed)
**/
toggleActionError: function(type, msg) {
* Report an error to the user.
*
* @param {string} type The type of error. Options are "save", submit", "peer", and "self".
* @param {string} message The error message to display, or if null hide the message.
* Note: loading errors are never hidden once displayed.
*/
toggleActionError: function(type, message) {
var element = this.element;
var container = null;
if (type === 'save') {
......@@ -124,29 +122,32 @@ OpenAssessment.BaseView.prototype = {
// If we don't have anywhere to put the message, just log it to the console
if (container === null) {
if (msg !== null) { console.log(msg); }
if (message !== null) { console.log(message); }
}
else {
// Insert the error message
var msgHtml = (msg === null) ? "" : msg;
var msgHtml = (message === null) ? "" : message;
$(container + " .message__content", element).html('<p>' + msgHtml + '</p>');
// Toggle the error class
$(container, element).toggleClass('has--error', msg !== null);
$(container, element).toggleClass('has--error', message !== null);
}
},
/**
Report an error loading a step.
Args:
step (str): the step that could not be loaded.
**/
showLoadError: function(step) {
var container = '#openassessment__' + step;
$(container).toggleClass('has--error', true);
$(container + ' .step__status__value i').removeClass().addClass('icon fa fa-exclamation-triangle');
$(container + ' .step__status__value .copy').html(gettext('Unable to Load'));
* Report an error loading a step.
*
* @param {string} stepName The step that could not be loaded.
* @param {string} errorMessage An optional error message to use instead of the default.
*/
showLoadError: function(stepName, errorMessage) {
if (!errorMessage) {
errorMessage = gettext('Unable to load');
}
var $container = $('#openassessment__' + stepName);
$container.toggleClass('has--error', true);
$container.find('.step__status__value i').removeClass().addClass('icon fa fa-exclamation-triangle');
$container.find('.step__status__value .copy').html(errorMessage);
}
};
......
......@@ -35,6 +35,11 @@
.ui-staff__content {
margin-top: 0;
}
.staff-info__cancel-submission__content,
.staff-info__staff-override__content {
padding: 0;
}
}
.ui-toggle-visibility__content {
......
......@@ -115,6 +115,10 @@ $link-hover: $edx-blue-l1 !default; // from our Pattern Library http://ux.edx.or
.label {
color: $heading-staff-color;
margin-bottom: ($baseline-v/2);
input {
display: block;
}
}
.action--submit {
......@@ -157,34 +161,14 @@ $link-hover: $edx-blue-l1 !default; // from our Pattern Library http://ux.edx.or
}
}
.value {
width: $max-width/2;
}
.ui-staff__action__section {
.staff-info__cancel-submission__content,
.staff-info__staff-override__content {
padding: $baseline-v ($baseline-h/2);
background-color: white;
margin-bottom: ($baseline-v/2);
.ui-staff__title {
text-transform: none;
font-size: 18px;
font-weight: 600;
line-height: 27px;
}
.label {
color: $edx-gray-d4;
}
.title {
color: $edx-gray-d4;
}
}
.title--sub {
font-size: 18px;
color: $edx-gray-d4;
}
.value {
width: $max-width/2;
}
// staff assessments
......@@ -242,7 +226,7 @@ $link-hover: $edx-blue-l1 !default; // from our Pattern Library http://ux.edx.or
@extend %ui-rubric-answers;
}
// genereal feedback question
// general feedback question
.assessment__rubric__question--feedback {
.wrapper--input {
......
......@@ -199,17 +199,6 @@
}
.staff-info__student {
.ui-staff__action__section {
.ui-staff__title span {
color: $edx-blue !important;
font-weight: 600 !important;
}
.ui-staff__title i {
color: $edx-blue !important;
}
}
// 'p' elements in LMS have a color set on them.
.student__answer__display__content p {
color: inherit;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment