Commit 643dc203 by Stephen Sanchez Committed by gradyward

Merge pull request #529 from edx/grady/rubric-validation-event-handler

Validation and Dynamic Updates of Training Examples
parents 59c9ba27 fa6df96d
{% load i18n %}
{% spaceless %}
<li class="openassessment_criterion is-collapsible">
<li class="openassessment_criterion is-collapsible" data-criterion="{{ criterion_name }}">
<div class="openassessment_criterion_header view-outline">
<a class="action expand-collapse collapse"><i class="icon-caret-down ui-toggle-expansion"></i></a>
<h6 class="openassessment_criterion_header_title">{% trans "Criterion" %}</h6>
......@@ -32,7 +32,7 @@
</ul>
<ul class="openassessment_criterion_option_list">
{% for option in criterion_options %}
{% include "openassessmentblock/edit/oa_edit_option.html" with option_name=option.name option_label=option.label option_points=option.points option_explanation=option.explanation %}
{% include "openassessmentblock/edit/oa_edit_option.html" with criterion_name=criterion_name option_name=option.name option_label=option.label option_points=option.points option_explanation=option.explanation %}
{% endfor %}
</ul>
......
{% load i18n %}
{% spaceless %}
<li class="openassessment_criterion_option">
<li class="openassessment_criterion_option" data-criterion="{{ criterion_name }}" data-option="{{ option_name }}">
<div class="openassessment_option_header">
<div class="openassessment_option_header_title">{% trans "Option" %}</div>
<div class="openassessment_criterion_option_remove_button">
......
......@@ -9,6 +9,14 @@
{% include "openassessmentblock/edit/oa_edit_option.html" with option_name="" option_label="" option_points=1 option_explanation="" %}
</div>
<div id="openassessment_rubric_validation_alert" class="is--hidden">
<i class="openassessment_alert_icon"></i>
<div class="openassessment_alert_header">
<h2 class="openassessment_alert_title">{% trans "Rubric Change Impacts Settings Section" %}</h2>
<p class="openassessment_alert_message">{% trans "A change that you made to this assessment's rubric has an impact on some examples laid out in the settings tab. For more information, go to the Settings section and fix areas highlighted in red." %}</p>
</div>
</div>
<div id="openassessment_rubric_instructions">
<p class = openassessment_description>
{% trans "For open response problems, assessment is rubric-based. Rubric criterion have point breakdowns and explanations to help students with peer and self assessment steps. For more information on how to build your rubric, see our online help documentation."%}
......
......@@ -30,6 +30,9 @@
<ol id="openassessment_training_example_template" class="is--hidden">
{% include "openassessmentblock/edit/oa_training_example.html" with example=assessments.training.template %}
</ol>
<ol id="openassessment_training_example_criterion_template" class="is--hidden">
{% include "openassessmentblock/edit/oa_training_example_criterion.html" %}
</ol>
</div>
</li>
......
......@@ -19,30 +19,14 @@
<h2>{% trans "Scored Rubric" %}</h2>
<ol class="openassessment_training_example_criteria_selections list-input settings-list">
{% for criterion in example.criteria %}
<li class="field comp-setting-entry">
<div class="wrapper-comp-setting">
<label class="openassessment_training_example_criterion_name setting-label">
{{criterion.label}}
<select class="openassessment_training_example_criterion_option setting-input" data-criterion={{criterion.name}}>
<option value="Not Scored">{% trans "Not Scored" %}</option>
{% for option in criterion.options %}
<option value={{option.name}}
{% if criterion.option_selected == option.name %} selected {% endif %}
>
{{option.label}} - {{option.points}} {% trans "points" %}
</option>
{% endfor %}
</select>
</label>
</div>
</li>
{% include "openassessmentblock/edit/oa_training_example_criterion.html" with criterion=criterion %}
{% endfor %}
</ol>
</div>
<div class="openassessment_training_example_essay_wrapper">
<h2>{% trans "Response" %}</h2>
<textarea class="openassessment_training_example_essay">{{example.answer}}</textarea>
<textarea class="openassessment_training_example_essay">{{ example.answer }}</textarea>
</div>
</div>
</li>
......
{% load i18n %}
{% spaceless %}
<li class="field comp-setting-entry openassessment_training_example_criterion" data-criterion="{{ criterion.name }}">
<div class="wrapper-comp-setting">
<label class="openassessment_training_example_criterion_name setting-label">
<div class="openassessment_training_example_criterion_name_wrapper">
{{ criterion.label }}
</div>
<select class="openassessment_training_example_criterion_option setting-input" data-criterion="{{ criterion.name }}" data-option="{{ option.name }}">
<option value="">{% trans "Not Scored" %}</option>
{% for option in criterion.options %}
<option value={{ option.name }}
{% if criterion.option_selected == option.name %} selected {% endif %}
>
{{ option.label }} - {{ option.points }} {% trans "points" %}
</option>
{% endfor %}
</select>
</label>
</div>
</li>
{% endspaceless %}
\ No newline at end of file
......@@ -100,7 +100,7 @@ EDITOR_UPDATE_SCHEMA = Schema({
Required('criteria'): [
Schema({
Required('order_num'): All(int, Range(min=0)),
'name': utf8_validator,
Required('name'): utf8_validator,
Required('label'): utf8_validator,
Required('prompt'): utf8_validator,
Required('feedback'): All(
......@@ -114,7 +114,7 @@ EDITOR_UPDATE_SCHEMA = Schema({
Required('options'): [
Schema({
Required('order_num'): All(int, Range(min=0)),
'name': utf8_validator,
Required('name'): utf8_validator,
Required('label'): utf8_validator,
Required('explanation'): utf8_validator,
Required('points'): All(int, Range(min=0)),
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -18,6 +18,10 @@ describe("OpenAssessment.Container", function () {
var testIdNum = parseInt($(element).attr("test_id"), 10);
return { id: testIdNum };
};
this.addHandler = function() {};
this.removeHandler = function() {};
this.updateHandler = function() {};
};
var container = null;
......
......@@ -90,23 +90,26 @@ describe("OpenAssessment.EditRubricView", function() {
expect(criteria[0]).toEqual({
order_num: 0,
name: "0",
label: "",
prompt: "",
feedback: "disabled",
options: [],
options: []
});
expect(criteria[1]).toEqual({
name: "1",
order_num: 1,
label: "",
prompt: "",
feedback: "disabled",
options: [
{
order_num: 0,
label: "",
points: 1,
explanation: ""
explanation: "",
name: "0",
order_num: 0
}
]
});
......
......@@ -81,7 +81,9 @@ OpenAssessment.Container = function(containerItem, kwargs) {
// Initialize existing items, in case they need to install their
// own event handlers.
$("." + this.containerItemClass, this.containerElement).each(
function(index, element) { new container.containerItem(element); }
function(index, element) {
new container.containerItem(element);
}
);
};
......@@ -115,7 +117,9 @@ OpenAssessment.Container.prototype = {
} );
// Initialize the item, allowing it to install event handlers.
new this.containerItem(containerItem.get(0));
// Fire event handler for adding a new element
var handlerItem = new this.containerItem(containerItem);
handlerItem.addHandler();
},
/**
......@@ -128,7 +132,10 @@ OpenAssessment.Container.prototype = {
**/
remove: function(item) {
$(item.element).closest("." + this.containerItemClass).remove();
var itemElement = $(item.element).closest("." + this.containerItemClass);
var containerItem = new this.containerItem(itemElement);
containerItem.removeHandler();
itemElement.remove();
},
/**
......@@ -180,5 +187,5 @@ OpenAssessment.Container.prototype = {
var container = this;
return $("." + this.containerItemClass, this.containerElement)
.map(function() { return new container.containerItem(this); });
},
}
};
OpenAssessment.ItemUtilities = {
/**
Utility method for creating a unique name given a set of
options.
Args:
selector (JQuery selector): Selector used to find the relative attribute
for the name.
nameAttribute (str): The name of the attribute that stores the unique
names for a particular set.
Returns:
A unique name for an object in the collection.
*/
createUniqueName: function(selector, nameAttribute) {
var index = 0;
while (index <= selector.length) {
if (selector.parent().find("*[" + nameAttribute + "='" + index + "']").length == 0) {
return index.toString();
}
index++;
}
return null;
}
};
/**
The RubricOption Class used to construct and maintain references to rubric options from within an options
container object. Constructs a new RubricOption element.
......@@ -10,6 +36,8 @@ Returns:
**/
OpenAssessment.RubricOption = function(element) {
this.element = element;
this.modificationHandler = new OpenAssessment.RubricEventHandler();
$(this.element).focusout($.proxy(this.updateHandler, this));
};
OpenAssessment.RubricOption.prototype = {
......@@ -47,6 +75,76 @@ OpenAssessment.RubricOption.prototype = {
if (nameString !== "") { fields.name = nameString; }
return fields;
},
/**
Hook into the event handler for removal of a criterion option.
*/
addHandler: function (){
var criterionElement = $(this.element).closest(".openassessment_criterion");
var criterionName = $(criterionElement).data('criterion');
var criterionLabel = $(".openassessment_criterion_label", criterionElement).val();
var options = $(".openassessment_criterion_option", this.element.parent());
// Create the unique name for this option.
var name = OpenAssessment.ItemUtilities.createUniqueName(options, "data-option");
// Set the criterion name and option name in the new rubric element.
$(this.element)
.attr("data-criterion", criterionName)
.attr("data-option", name);
$(".openassessment_criterion_option_name", this.element).attr("value", name);
var fields = this.getFieldValues();
this.modificationHandler.notificationFired(
"optionAdd",
{
"criterionName": criterionName,
"criterionLabel": criterionLabel,
"name": name,
"label": fields.label,
"points": fields.points
}
);
},
/**
Hook into the event handler for removal of a criterion option.
*/
removeHandler: function (){
var criterionName = $(this.element).data('criterion');
var optionName = $(this.element).data('option');
this.modificationHandler.notificationFired(
"optionRemove",
{
"criterionName": criterionName,
"name": optionName
}
);
},
/**
Hook into the event handler when a rubric criterion option is
modified.
*/
updateHandler: function(){
var fields = this.getFieldValues();
var criterionName = $(this.element).data('criterion');
var optionName = $(this.element).data('option');
var optionLabel = fields.label;
var optionPoints = fields.points;
this.modificationHandler.notificationFired(
"optionUpdated",
{
"criterionName": criterionName,
"name": optionName,
"label": optionLabel,
"points": optionPoints
}
);
}
};
......@@ -62,15 +160,18 @@ Returns:
**/
OpenAssessment.RubricCriterion = function(element) {
this.element = element;
this.modificationHandler = new OpenAssessment.RubricEventHandler();
this.optionContainer = new OpenAssessment.Container(
OpenAssessment.RubricOption, {
containerElement: $(".openassessment_criterion_option_list", this.element).get(0),
templateElement: $("#openassessment_option_template").get(0),
addButtonElement: $(".openassessment_criterion_add_option", this.element).get(0),
removeButtonClass: "openassessment_criterion_option_remove_button",
containerItemClass: "openassessment_criterion_option",
containerItemClass: "openassessment_criterion_option"
}
);
$(this.element).focusout($.proxy(this.updateHandler, this));
};
......@@ -125,6 +226,42 @@ OpenAssessment.RubricCriterion.prototype = {
**/
addOption: function() {
this.optionContainer.add();
},
/**
Hook into the event handler for addition of a criterion.
*/
addHandler: function (){
var criteria = $(".openassessment_criterion", this.element.parent());
// Create the unique name for this option.
var name = OpenAssessment.ItemUtilities.createUniqueName(criteria, "data-criterion");
// Set the criterion name in the new rubric element.
$(this.element).attr("data-criterion", name);
$(".openassessment_criterion_name", this.element).attr("value", name);
},
/**
Hook into the event handler for removal of a criterion.
*/
removeHandler: function(){
var criterionName = $(this.element).data('criterion');
this.modificationHandler.notificationFired("criterionRemove", {'criterionName': criterionName});
},
/**
Hook into the event handler when a rubric criterion is modified.
*/
updateHandler: function(){
var fields = this.getFieldValues();
var criterionName = fields.name;
var criterionLabel = fields.label;
this.modificationHandler.notificationFired(
"criterionUpdated",
{'criterionName': criterionName, 'criterionLabel': criterionLabel}
);
}
};
......@@ -162,6 +299,10 @@ OpenAssessment.TrainingExample.prototype = {
answer: $('.openassessment_training_example_essay', this.element).first().prop('value'),
options_selected: optionsSelected
};
}
},
addHandler: function() {},
removeHandler: function() {},
updateHandler: function() {}
};
\ No newline at end of file
......@@ -9,9 +9,10 @@ OpenAssessment.EditRubricView = function(element) {
templateElement: $("#openassessment_criterion_template", this.element).get(0),
addButtonElement: $("#openassessment_rubric_add_criterion", this.element).get(0),
removeButtonClass: "openassessment_criterion_remove_button",
containerItemClass: "openassessment_criterion",
containerItemClass: "openassessment_criterion"
}
);
this.alert = new OpenAssessment.ValidationAlert($('#openassessment_rubric_validation_alert', this.element));
};
OpenAssessment.EditRubricView.prototype = {
......@@ -108,4 +109,51 @@ OpenAssessment.EditRubricView.prototype = {
getCriterionItem: function(index) {
return this.criteriaContainer.getItem(index);
}
};
\ No newline at end of file
};
/**
A class which controls the validation alert which we place at the top of the rubric page after
changes are made which will propagate to the settings section.
Args:
element (element): The element that specifies the div that the validation consists of.
Returns:
Openassessment.ValidationAlert
*/
OpenAssessment.ValidationAlert = function (element) {
this.element = element;
this.title = $(".openassessment_alert_title", this.element);
this.message = $(".openassessment_alert_message", this.element);
};
OpenAssessment.ValidationAlert.prototype = {
/**
Hides the alert.
*/
hide: function () {
this.element.addClass('is--hidden');
},
/**
Displays the alert.
*/
show : function () {
this.element.removeClass('is--hidden');
},
/**
Sets the message of the alert.
How will this work with internationalization?
Args:
newTitle (str): the new title that the message will have
newMessage (str): the new text that the message's body will contain
*/
setMessage: function (newTitle, newMessage){
this.title.text(newTitle);
this.message.text(newMessage);
}
};
OpenAssessment.RubricEventHandler = function () {
this.listeners = [
new OpenAssessment.StudentTrainingListener()
];
};
OpenAssessment.RubricEventHandler.prototype = {
notificationFired: function(name, data) {
for (var i = 0; i < this.listeners.length; i++) {
if (typeof(this.listeners[i][name]) === 'function') {
this.listeners[i][name](data);
}
}
}
};
OpenAssessment.StudentTrainingListener = function () {
this.element = $('#oa_student_training_editor');
this.alert = new OpenAssessment.ValidationAlert($('#openassessment_rubric_validation_alert'));
};
OpenAssessment.StudentTrainingListener.prototype = {
generateOptionString: function(name, points){
return name + ' - ' + points + gettext(' points')
},
/**
Event handler for updating training examples when a criterion option has
been updated.
Args:
criterionName (str): The name of the criterion that contains the updated
option.
name (str): The name of the option.
label (str): The label for the option.
points (int): The point value for the option.
*/
optionUpdated: function(data){
var view = this;
$('.openassessment_training_example_criterion[data-criterion="'
+ data.criterionName + '"', this.element).each(function(){
var criterion = this;
var option = $("option[value='" + data.name + "'", criterion);
$(option).text(view.generateOptionString(data.label, data.points));
});
},
/**
Update the available options for a particular criterion. If this is the
first option for a criterion, the criterion needs to be created for each
example as well.
Since names are unique, and set server side for new criteria and options,
this will use the label for but the name and label attributes in the
HTML. This will be resolved server-side.
Args:
criterionName (str): The name of the criterion that will have an
additional option.
criterionLabel (str): The label for the criterion that will have an
additional option.
name (str): The name of the new option.
label (str): The new option label.
points (int): The point value of the new option.
*/
optionAdd: function(data){
// First, check to see if the criterion exists on the training examples
var options = $('.openassessment_training_example_criterion_option[data-criterion="' + data.criterionName + '"]');
var view = this;
var criterionAdded = false;
var examplesUpdated = false;
if (options.length == 0) {
this.criterionAdd(data);
criterionAdded = true;
}
$('.openassessment_training_example_criterion_option', this.element).each(function(){
if ($(this).data('criterion') === data.criterionName) {
var criterion = this;
// Risky; making an assumption that options will remain simple.
// updates could cause this to get out of sync with templates,
// but this avoids overly complex templating code.
$(criterion).append($("<option></option>")
.attr("value", data.name)
.text(view.generateOptionString(data.label, data.points)));
examplesUpdated = true;
}
});
if (criterionAdded && examplesUpdated) {
this.displayAlertMsg(
gettext("Criterion Addition requires Training Example Updates"),
gettext("Because you added a criterion, student training examples " +
"will have to be updated.")
);
}
},
/**
Event handler for when an option is removed from a criterion on the rubric.
Training examples will be updated accordingly when this occurs, and the
user is notified that these changes have effected other sections of the
configuration.
If this is the last option in the criterion, removeAllOptions should be
invoked.
Args:
criterionName (str): The name of the criterion where the option is
being removed.
name (str): The option being removed.
*/
optionRemove: function(data){
var validator = this;
var changed = false;
$('.openassessment_training_example_criterion_option', this.element).each(function(){
var criterionOption = this;
if ($(criterionOption).data('criterion') === data.criterionName) {
if ($(criterionOption).val() == data.name) {
$(criterionOption).val("");
$(criterionOption).addClass("openassessment_highlighted_field");
changed = true;
}
$("option[value='" + data.name + "'", criterionOption).remove();
// If all options have been removed from the Criterion, remove
// the criterion entirely.
if ($("option", criterionOption).length == 1) {
validator.removeAllOptions(data);
return;
}
}
});
if (changed) {
this.displayAlertMsg(
gettext("Option Deletion Led to Invalidation"),
gettext("Because you deleted an option, some student training " +
"examples had to be reset.")
);
}
},
/**
Event handler for when all options are removed from a criterion. Right now,
the logic is the same as if the criterion was removed. When all options are
removed, training examples should be updated and the user should be
notified.
Args:
criterionName (str): The criterion where all options have been removed.
*/
removeAllOptions: function(data){
var changed = false;
$('.openassessment_training_example_criterion', this.element).each(function(){
var criterion = this;
if ($(criterion).data('criterion') == data.criterionName) {
$(criterion).remove();
changed = true;
}
});
if (changed) {
this.displayAlertMsg(
gettext("Option Deletion Led to Invalidation"),
gettext("The deletion of the last criterion option caused the " +
"criterion to be removed in the student training examples.")
);
}
},
/**
Event handler for when a criterion is removed from the rubric. If a
criterion is removed, we should ensure that the traing examples are updated
to reflect this change.
Args:
criterionName (str): The name of the criterion removed.
*/
criterionRemove: function(data){
var changed = false;
$('.openassessment_training_example_criterion[data-criterion="'
+ data.criterionName + '"', this.element).each(function(){
$(this).remove();
changed = true;
});
if (changed) {
this.displayAlertMsg(
gettext("Criterion Deletion Led to Invalidation"),
gettext("Because you deleted a criterion, there were student " +
"training examples where the criterion had to be removed.")
);
}
},
/**
Sets up the alert window based on a change message.
Args:
title (str): Title of the alert message.
msg (str): Message body for the alert.
*/
displayAlertMsg: function(title, msg){
this.alert.setMessage(title, msg);
this.alert.show();
},
/**
Handler for modifying the criterion label on every example, when the label
has changed in the rubric.
Args:
criterionName (str): Name of the criterion
criterionLabel (str): New label to replace on the training examples.
*/
criterionUpdated: function(data){
$('.openassessment_training_example_criterion[data-criterion="' +
data.criterionName + '"', this.element).each(function(){
$(".openassessment_training_example_criterion_name_wrapper", this).text(data.criterionLabel);
});
},
/**
Event handler used to generate a new criterion on each training example
when a criterion is created, and the first option is added. This should
update all examples as well as the template used to create new training
examples.
Args:
name (str): The name of the criterion being added.
label (str): The label for the new criterion.
*/
criterionAdd: function(data) {
var view = this.element;
var criterion = $("#openassessment_training_example_criterion_template")
.children().first()
.clone()
.removeAttr('id')
.attr('data-criterion', data.name)
.toggleClass('is--hidden', false)
.appendTo(".openassessment_training_example_criteria_selections", view);
criterion.find(".openassessment_training_example_criterion_option").attr('data-criterion', data.name);
criterion.find(".openassessment_training_example_criterion_name_wrapper").text(data.label);
}
};
......@@ -425,6 +425,46 @@
#oa_rubric_editor_wrapper{
#openassessment_rubric_validation_alert{
-webkit-animation: notificationSlideDown 1s ease-in-out 1;
-moz-animation: notificationSlideDown 1s ease-in-out 1;
animation: notificationSlideDown 1s ease-in-out 1;
-webkit-animation-fill-mode: forwards;
-moz-animation-fill-mode: forwards;
animation-fill-mode: forwards;
background-color: #323232;
height: auto;
border-bottom: 3px solid rgb(192, 172, 0);
padding: 10px;
position: absolute;
z-index: 10;
.openassessment_alert_icon:before{
font-family: FontAwesome;
content: "\f071";
display: inline-block;
color: rgb(192, 172, 0);
float: left;
font-size: 200%;
margin: 20px 0px 0px 25px;
}
.openassessment_alert_header {
width: 85%;
margin: 0 5% 0 10%;
.openassessment_alert_title {
width: auto;
color: white;
}
.openassessment_alert_message {
font-size: 80%;
color: darkgray;
}
}
}
.wrapper-comp-settings{
display: initial;
}
......@@ -470,7 +510,7 @@
}
.openassessment_criterion_header_remove {
@extend .openassessment_rubric_remove_button
@extend .openassessment_rubric_remove_button;
}
}
......@@ -587,7 +627,7 @@
}
.openassessment_option_header_remove{
@extend .openassessment_rubric_remove_button
@extend .openassessment_rubric_remove_button;
}
}
......@@ -711,6 +751,7 @@
#openassessment_rubric_feedback_wrapper{
padding: 0;
#openassessment_rubric_feedback_header{
background-color: $edx-gray-t1;
padding: 7.5px 7.5px 7.5px 15px;
......@@ -788,6 +829,10 @@
#student_training_settings_editor {
.openassessment_highlighted_field{
background-color: $edx-pink-l4;
}
.openassessment_training_example {
padding: 5px;
.openassessment_training_example_header {
......
......@@ -204,3 +204,7 @@
color: $copy-staff-color !important;
}
}
.modal-lg.modal-window.confirm.openassessment_modal_window{
z-index: 100000;
}
......@@ -4,7 +4,6 @@ Studio editing view for OpenAssessment XBlock.
import pkg_resources
import copy
import logging
from uuid import uuid4
from django.template import Context
from django.template.loader import get_template
from django.utils.translation import ugettext as _
......@@ -172,7 +171,7 @@ class StudioMixin(object):
return {'success': False, 'msg': _(
u'Validation error: No examples were provided for example based assessment.'
)}
# This is where we default to EASE for problems which are edited in the GUI
# This is where we default to EASE for problems which are edited in the GUI
assessment['algorithm_id'] = 'ease'
xblock_validator = validator(self)
......
......@@ -3,18 +3,21 @@
"criteria": [
{
"order_num": 0,
"name": "0",
"label": "Test criterion",
"prompt": "Test criterion prompt",
"options": [
{
"order_num": 0,
"points": 0,
"name": "0",
"label": "No",
"explanation": "No explanation"
},
{
"order_num": 1,
"points": 2,
"name": "1",
"label": "Yes",
"explanation": "Yes explanation"
}
......@@ -49,18 +52,21 @@
"criteria": [
{
"order_num": 0,
"name": "0",
"label": "Ṫëṡẗ ċṛïẗëïṛöṅ",
"prompt": "Téśt ćŕítéíŕőń ṕŕőḿṕt",
"options": [
{
"order_num": 0,
"points": 0,
"name": "0",
"label": "Ṅö",
"explanation": "Ńő éxṕĺáńátíőń"
},
{
"order_num": 1,
"points": 2,
"name": "1",
"label": "sǝʎ",
"explanation": "Чэѕ эхрlаиатіои"
}
......
......@@ -48,9 +48,10 @@ class StudioViewTest(XBlockHandlerTestCase):
]
}
RUBRIC_CRITERIA_WITH_AND_WITHOUT_NAMES = [
RUBRIC_CRITERIA = [
{
"order_num": 0,
"name": "0",
"label": "Test criterion with no name",
"prompt": "Test criterion prompt",
"feedback": "disabled",
......@@ -58,6 +59,7 @@ class StudioViewTest(XBlockHandlerTestCase):
{
"order_num": 0,
"points": 0,
"name": "0",
"label": "Test option with no name",
"explanation": "Test explanation"
}
......@@ -66,13 +68,14 @@ class StudioViewTest(XBlockHandlerTestCase):
{
"order_num": 1,
"label": "Test criterion that already has a name",
"name": "cd316c145cb14e06b377db65719ed41c",
"name": "1",
"prompt": "Test criterion prompt",
"feedback": "disabled",
"options": [
{
"order_num": 0,
"points": 0,
"name": "0",
"label": "Test option with no name",
"explanation": "Test explanation"
},
......@@ -80,7 +83,7 @@ class StudioViewTest(XBlockHandlerTestCase):
"order_num": 1,
"points": 0,
"label": "Test option that already has a name",
"name": "8bcdb0769b15482d9b2c3791d22e8ad2",
"name": "1",
"explanation": "Test explanation"
},
]
......@@ -156,30 +159,6 @@ class StudioViewTest(XBlockHandlerTestCase):
self.assertTrue(resp['success'], msg=resp.get('msg'))
self.assertEqual(xblock.editor_assessments_order, data['editor_assessments_order'])
@scenario('data/basic_scenario.xml')
def test_update_editor_context_assign_unique_names(self, xblock):
# Update the XBlock with a rubric that is missing
# some of the (unique) names for rubric criteria/options.
data = copy.deepcopy(self.UPDATE_EDITOR_DATA)
data['criteria'] = self.RUBRIC_CRITERIA_WITH_AND_WITHOUT_NAMES
xblock.published_date = None
resp = self.request(xblock, 'update_editor_context', json.dumps(data), response_format='json')
self.assertTrue(resp['success'], msg=resp.get('msg'))
# Check that the XBlock has assigned unique names for all criteria
criteria_names = set([criterion.get('name') for criterion in xblock.rubric_criteria])
self.assertEqual(len(criteria_names), 2)
self.assertNotIn(None, criteria_names)
# Check that the XBlock has assigned unique names for all options
option_names = set()
for criterion in xblock.rubric_criteria:
for option in criterion['options']:
option_names.add(option.get('name'))
self.assertEqual(len(option_names), 3)
self.assertNotIn(None, option_names)
@file_data('data/invalid_update_xblock.json')
@scenario('data/basic_scenario.xml')
def test_update_context_invalid_request_data(self, xblock, data):
......
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