Merge pull request #790 from edx/unanswered-on-input
Unanswered on input
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
|
||||
% for choice_id, choice_description in choices:
|
||||
<label for="input_${id}_${choice_id}"
|
||||
## If the student has selected this choice...
|
||||
% if input_type == 'radio' and ( (isinstance(value, basestring) and (choice_id == value)) or (not isinstance(value, basestring) and choice_id in value) ):
|
||||
<%
|
||||
if status == 'correct':
|
||||
@@ -32,6 +33,7 @@
|
||||
% endif
|
||||
>
|
||||
<input type="${input_type}" name="input_${id}${name_array_suffix}" id="input_${id}_${choice_id}" aria-describedby="answer_${id}" value="${choice_id}"
|
||||
## If the student selected this choice...
|
||||
% if input_type == 'radio' and ( (isinstance(value, basestring) and (choice_id == value)) or (not isinstance(value, basestring) and choice_id in value) ):
|
||||
checked="true"
|
||||
% elif input_type != 'radio' and choice_id in value:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<section id="formulaequationinput_${id}" class="formulaequationinput">
|
||||
<section id="formulaequationinput_${id}" class="inputtype formulaequationinput">
|
||||
<div class="${reported_status}" id="status_${id}">
|
||||
<input type="text" name="input_${id}" id="input_${id}"
|
||||
data-input-id="${id}" value="${value|h}"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<form class="option-input">
|
||||
<form class="inputtype option-input">
|
||||
<select name="input_${id}" id="input_${id}" aria-describedby="answer_${id}">
|
||||
<option value="option_${id}_dummy_default"> </option>
|
||||
% for option_id, option_description in options:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<% doinline = "inline" if inline else "" %>
|
||||
|
||||
<section id="inputtype_${id}" class="${'text-input-dynamath' if do_math else ''} capa_inputtype ${doinline}" >
|
||||
<section id="inputtype_${id}" class="${'text-input-dynamath' if do_math else ''} capa_inputtype ${doinline} textline" >
|
||||
|
||||
% if preprocessor is not None:
|
||||
<div class="text-input-dynamath_data" data-preprocessor="${preprocessor['class_name']}"/>
|
||||
|
||||
@@ -352,10 +352,10 @@ class TextlineTemplateTest(TemplateTestCase):
|
||||
super(TextlineTemplateTest, self).setUp()
|
||||
|
||||
def test_section_class(self):
|
||||
cases = [({}, ' capa_inputtype '),
|
||||
({'do_math': True}, 'text-input-dynamath capa_inputtype '),
|
||||
({'inline': True}, ' capa_inputtype inline'),
|
||||
({'do_math': True, 'inline': True}, 'text-input-dynamath capa_inputtype inline'), ]
|
||||
cases = [({}, ' capa_inputtype textline'),
|
||||
({'do_math': True}, 'text-input-dynamath capa_inputtype textline'),
|
||||
({'inline': True}, ' capa_inputtype inline textline'),
|
||||
({'do_math': True, 'inline': True}, 'text-input-dynamath capa_inputtype inline textline'), ]
|
||||
|
||||
for (context, css_class) in cases:
|
||||
base_context = self.context.copy()
|
||||
|
||||
@@ -25,6 +25,8 @@ class @Problem
|
||||
@$('section.action button.show').click @show
|
||||
@$('section.action input.save').click @save
|
||||
|
||||
@bindResetCorrectness()
|
||||
|
||||
# Collapsibles
|
||||
Collapsible.setCollapsibles(@el)
|
||||
|
||||
@@ -370,6 +372,56 @@ class @Problem
|
||||
element.CodeMirror.save() if element.CodeMirror.save
|
||||
@answers = @inputs.serialize()
|
||||
|
||||
bindResetCorrectness: ->
|
||||
# Loop through all input types
|
||||
# Bind the reset functions at that scope.
|
||||
$inputtypes = @el.find(".capa_inputtype").add(@el.find(".inputtype"))
|
||||
$inputtypes.each (index, inputtype) =>
|
||||
classes = $(inputtype).attr('class').split(' ')
|
||||
for cls in classes
|
||||
bindMethod = @bindResetCorrectnessByInputtype[cls]
|
||||
if bindMethod?
|
||||
bindMethod(inputtype)
|
||||
|
||||
# Find all places where each input type displays its correct-ness
|
||||
# Replace them with their original state--'unanswered'.
|
||||
bindResetCorrectnessByInputtype:
|
||||
# These are run at the scope of the capa inputtype
|
||||
# They should set handlers on each <input> to reset the whole.
|
||||
formulaequationinput: (element) ->
|
||||
$(element).find('input').on 'input', ->
|
||||
$p = $(element).find('p.status')
|
||||
$p.text gettext("unanswered")
|
||||
$p.parent().removeClass().addClass "unanswered"
|
||||
|
||||
choicegroup: (element) ->
|
||||
$element = $(element)
|
||||
id = ($element.attr('id').match /^inputtype_(.*)$/)[1]
|
||||
$element.find('input').on 'change', ->
|
||||
$status = $("#status_#{id}")
|
||||
if $status[0] # We found a status icon.
|
||||
$status.removeClass().addClass "unanswered"
|
||||
$status.empty().css 'display', 'inline-block'
|
||||
else
|
||||
# Recreate the unanswered dot on left.
|
||||
$("<span>", {"class": "unanswered", "style": "display: inline-block;", "id": "status_#{id}"})
|
||||
|
||||
$element.find("label").removeClass()
|
||||
|
||||
'option-input': (element) ->
|
||||
$select = $(element).find('select')
|
||||
id = ($select.attr('id').match /^input_(.*)$/)[1]
|
||||
$select.on 'change', ->
|
||||
$status = $("#status_#{id}")
|
||||
.removeClass().addClass("unanswered")
|
||||
.find('span').text(gettext('Status: unsubmitted'))
|
||||
|
||||
textline: (element) ->
|
||||
$(element).find('input').on 'input', ->
|
||||
$p = $(element).find('p.status')
|
||||
$p.text "unanswered"
|
||||
$p.parent().removeClass().addClass "unanswered"
|
||||
|
||||
inputtypeSetupMethods:
|
||||
|
||||
'text-input-dynamath': (element) =>
|
||||
|
||||
@@ -15,11 +15,14 @@ describe("Formula Equation Preview", function () {
|
||||
var $fixture = this.$fixture = $('\
|
||||
<section class="problems-wrapper" data-url="THE_URL">\
|
||||
<section class="formulaequationinput">\
|
||||
<input type="text" id="input_THE_ID" data-input-id="THE_ID"\
|
||||
value="prefilled_value"/>\
|
||||
<div id="input_THE_ID_preview" class="equation">\
|
||||
\[\]\
|
||||
<img class="loading" style="visibility:hidden"/>\
|
||||
<div class="INITIAL_STATUS" id="status_THE_ID">\
|
||||
<input type="text" id="input_THE_ID" data-input-id="THE_ID"\
|
||||
value="PREFILLED_VALUE"/>\
|
||||
<p class="status">INITIAL_STATUS</p>\
|
||||
<div id="input_THE_ID_preview" class="equation">\
|
||||
\[\]\
|
||||
<img class="loading" style="visibility:hidden"/>\
|
||||
</div>\
|
||||
</div>\
|
||||
</section>\
|
||||
</section>');
|
||||
@@ -62,10 +65,10 @@ describe("Formula Equation Preview", function () {
|
||||
MathJax.Hub.Queue = jasmine.createSpy('MathJax.Hub.Queue');
|
||||
});
|
||||
|
||||
it('(the test) should be able to swap out the behavior of $', function () {
|
||||
it('(the test) is able to swap out the behavior of $', function () {
|
||||
// This was a pain to write, make sure it doesn't get screwed up.
|
||||
|
||||
// Find the DOM element using DOM methods.
|
||||
// Find the element using DOM methods.
|
||||
var legitInput = this.$fixture[0].getElementsByTagName("input")[0];
|
||||
|
||||
// Use the (modified) jQuery.
|
||||
@@ -96,7 +99,7 @@ describe("Formula Equation Preview", function () {
|
||||
"THE_URL",
|
||||
"THE_ID",
|
||||
"preview_formcalc",
|
||||
{formula: "prefilled_value",
|
||||
{formula: "PREFILLED_VALUE",
|
||||
request_start: jasmine.any(Number)},
|
||||
jasmine.any(Function)
|
||||
]);
|
||||
@@ -117,7 +120,7 @@ describe("Formula Equation Preview", function () {
|
||||
});
|
||||
});
|
||||
|
||||
it("shouldn't be requested for empty input", function () {
|
||||
it("isn't requested for empty input", function () {
|
||||
Problem.inputAjax.reset();
|
||||
|
||||
// When we make an input of '',
|
||||
@@ -136,7 +139,7 @@ describe("Formula Equation Preview", function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should limit the number of requests per second', function () {
|
||||
it('limits the number of requests per second', function () {
|
||||
var minDelay = formulaEquationPreview.minDelay;
|
||||
var end = Date.now() + minDelay * 1.1;
|
||||
var step = 10; // ms
|
||||
@@ -171,7 +174,7 @@ describe("Formula Equation Preview", function () {
|
||||
});
|
||||
|
||||
describe("Visible results (icon and mathjax)", function () {
|
||||
it('should display a loading icon when requests are open', function () {
|
||||
it('displays a loading icon when requests are open', function () {
|
||||
var $img = $("img.loading");
|
||||
expect($img.css('visibility')).toEqual('hidden');
|
||||
formulaEquationPreview.enable();
|
||||
@@ -199,7 +202,7 @@ describe("Formula Equation Preview", function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should update MathJax and loading icon on callback', function () {
|
||||
it('updates MathJax and loading icon on callback', function () {
|
||||
formulaEquationPreview.enable();
|
||||
waitsFor(function () {
|
||||
return Problem.inputAjax.wasCalled;
|
||||
@@ -217,7 +220,7 @@ describe("Formula Equation Preview", function () {
|
||||
expect($("img.loading").css('visibility')).toEqual('hidden');
|
||||
|
||||
// We should look in the preview div for the MathJax.
|
||||
var previewDiv = $("div")[0];
|
||||
var previewDiv = $("#input_THE_ID_preview")[0];
|
||||
expect(MathJax.Hub.getAllJax).toHaveBeenCalledWith(previewDiv);
|
||||
|
||||
// Refresh the MathJax.
|
||||
@@ -242,7 +245,7 @@ describe("Formula Equation Preview", function () {
|
||||
|
||||
// Cannot find MathJax.
|
||||
MathJax.Hub.getAllJax.andReturn([]);
|
||||
spyOn(console, 'error');
|
||||
spyOn(console, 'warn');
|
||||
|
||||
callback({
|
||||
preview: 'THE_FORMULA',
|
||||
@@ -250,10 +253,10 @@ describe("Formula Equation Preview", function () {
|
||||
});
|
||||
|
||||
// Tests.
|
||||
expect(console.error).toHaveBeenCalled();
|
||||
expect(console.warn).toHaveBeenCalled();
|
||||
|
||||
// We should look in the preview div for the MathJax.
|
||||
var previewElement = $("div")[0];
|
||||
var previewElement = $("#input_THE_ID_preview")[0];
|
||||
expect(previewElement.firstChild.data).toEqual("\\[THE_FORMULA\\]");
|
||||
|
||||
// Refresh the MathJax.
|
||||
@@ -263,7 +266,7 @@ describe("Formula Equation Preview", function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should display errors from the server well', function () {
|
||||
it('displays errors from the server well', function () {
|
||||
var $img = $("img.loading");
|
||||
formulaEquationPreview.enable();
|
||||
waitsFor(function () {
|
||||
@@ -329,7 +332,7 @@ describe("Formula Equation Preview", function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should update requests sequentially', function () {
|
||||
it('updates requests sequentially', function () {
|
||||
var $img = $("img.loading");
|
||||
|
||||
expect($img.css('visibility')).toEqual('visible');
|
||||
@@ -349,7 +352,7 @@ describe("Formula Equation Preview", function () {
|
||||
expect($img.css('visibility')).toEqual('hidden')
|
||||
});
|
||||
|
||||
it("shouldn't display outdated information", function () {
|
||||
it("doesn't display outdated information", function () {
|
||||
var $img = $("img.loading");
|
||||
|
||||
expect($img.css('visibility')).toEqual('visible');
|
||||
@@ -368,7 +371,7 @@ describe("Formula Equation Preview", function () {
|
||||
expect($img.css('visibility')).toEqual('hidden')
|
||||
});
|
||||
|
||||
it("shouldn't show an error if the responses are close together",
|
||||
it("doesn't show an error if the responses are close together",
|
||||
function () {
|
||||
this.callbacks[0]({
|
||||
error: 'OOPSIE',
|
||||
|
||||
@@ -52,12 +52,14 @@ formulaEquationPreview.enable = function () {
|
||||
// Show the loading icon.
|
||||
inputData.$img.css('visibility', 'visible');
|
||||
|
||||
// Say we are waiting for request.
|
||||
inputData.isWaitingForRequest = true;
|
||||
// First thing in `sendRequest`, say we aren't anymore.
|
||||
throttledRequest(inputData, this.value);
|
||||
};
|
||||
|
||||
$this.on("input", initializeRequest);
|
||||
// send an initial
|
||||
// Ask for initial preview.
|
||||
initializeRequest.call(this);
|
||||
}
|
||||
|
||||
@@ -85,7 +87,7 @@ formulaEquationPreview.enable = function () {
|
||||
// // This is run when ajax call fails.
|
||||
// // Have an error message and other stuff here?
|
||||
// inputData.$img.css('visibility', 'hidden');
|
||||
// }); */
|
||||
// });
|
||||
}
|
||||
else {
|
||||
inputData.requestCallback({
|
||||
@@ -140,7 +142,7 @@ formulaEquationPreview.enable = function () {
|
||||
);
|
||||
}
|
||||
else if (latex) {
|
||||
console.error("Oops no mathjax for ", latex);
|
||||
console.warn("[FormulaEquationInput] Oops no mathjax for ", latex);
|
||||
// Fall back to modifying the actual element.
|
||||
var textNode = previewElement.childNodes[0];
|
||||
textNode.data = "\\[" + latex + "\\]";
|
||||
|
||||
@@ -7,7 +7,7 @@ Feature: Answer problems
|
||||
Given External graders respond "correct"
|
||||
And I am viewing a "<ProblemType>" problem
|
||||
When I answer a "<ProblemType>" problem "correctly"
|
||||
Then My "<ProblemType>" answer is marked "correct"
|
||||
Then my "<ProblemType>" answer is marked "correct"
|
||||
And The "<ProblemType>" problem displays a "correct" answer
|
||||
|
||||
Examples:
|
||||
@@ -28,7 +28,7 @@ Feature: Answer problems
|
||||
Given External graders respond "incorrect"
|
||||
And I am viewing a "<ProblemType>" problem
|
||||
When I answer a "<ProblemType>" problem "incorrectly"
|
||||
Then My "<ProblemType>" answer is marked "incorrect"
|
||||
Then my "<ProblemType>" answer is marked "incorrect"
|
||||
And The "<ProblemType>" problem displays a "incorrect" answer
|
||||
|
||||
Examples:
|
||||
@@ -48,7 +48,7 @@ Feature: Answer problems
|
||||
Scenario: I can submit a blank answer
|
||||
Given I am viewing a "<ProblemType>" problem
|
||||
When I check a problem
|
||||
Then My "<ProblemType>" answer is marked "incorrect"
|
||||
Then my "<ProblemType>" answer is marked "incorrect"
|
||||
And The "<ProblemType>" problem displays a "blank" answer
|
||||
|
||||
Examples:
|
||||
@@ -69,7 +69,7 @@ Feature: Answer problems
|
||||
Given I am viewing a "<ProblemType>" problem
|
||||
And I answer a "<ProblemType>" problem "<Correctness>ly"
|
||||
When I reset the problem
|
||||
Then My "<ProblemType>" answer is marked "unanswered"
|
||||
Then my "<ProblemType>" answer is marked "unanswered"
|
||||
And The "<ProblemType>" problem displays a "blank" answer
|
||||
|
||||
Examples:
|
||||
@@ -171,3 +171,68 @@ Feature: Answer problems
|
||||
| numerical | 1 point possible |
|
||||
| formula | 1 point possible |
|
||||
| script | 2 points possible |
|
||||
|
||||
|
||||
Scenario: I can reset the correctness of a problem after changing my answer
|
||||
Given I am viewing a "<ProblemType>" problem
|
||||
Then my "<ProblemType>" answer is marked "unanswered"
|
||||
When I answer a "<ProblemType>" problem "<InitialCorrectness>ly"
|
||||
And I wait for "1" seconds
|
||||
And I input an answer on a "<ProblemType>" problem "<OtherCorrectness>ly"
|
||||
Then my "<ProblemType>" answer is marked "unanswered"
|
||||
And I reset the problem
|
||||
|
||||
Examples:
|
||||
| ProblemType | InitialCorrectness | OtherCorrectness |
|
||||
| drop down | correct | incorrect |
|
||||
| drop down | incorrect | correct |
|
||||
| checkbox | correct | incorrect |
|
||||
| checkbox | incorrect | correct |
|
||||
| string | correct | incorrect |
|
||||
| string | incorrect | correct |
|
||||
| numerical | correct | incorrect |
|
||||
| numerical | incorrect | correct |
|
||||
| formula | correct | incorrect |
|
||||
| formula | incorrect | correct |
|
||||
| script | correct | incorrect |
|
||||
| script | incorrect | correct |
|
||||
|
||||
# Radio groups behave slightly differently than other types of checkboxes, because they
|
||||
# don't put their status to the top left of the boxes (like checkboxes do), thus, they'll
|
||||
# not ever have a status of "unanswered" once you've made an answer. They should simply NOT
|
||||
# be marked either correct or incorrect. Arguably this behavior should be changed; when it
|
||||
# is, these cases should move into the above Scenario.
|
||||
Scenario: I can reset the correctness of a radiogroup problem after changing my answer
|
||||
Given I am viewing a "<ProblemType>" problem
|
||||
When I answer a "<ProblemType>" problem "<InitialCorrectness>ly"
|
||||
And I wait for "1" seconds
|
||||
Then my "<ProblemType>" answer is marked "<InitialCorrectness>"
|
||||
And I input an answer on a "<ProblemType>" problem "<OtherCorrectness>ly"
|
||||
Then my "<ProblemType>" answer is NOT marked "<InitialCorrectness>"
|
||||
And my "<ProblemType>" answer is NOT marked "<OtherCorrectness>"
|
||||
And I reset the problem
|
||||
|
||||
Examples:
|
||||
| ProblemType | InitialCorrectness | OtherCorrectness |
|
||||
| multiple choice | correct | incorrect |
|
||||
| multiple choice | incorrect | correct |
|
||||
| radio | correct | incorrect |
|
||||
| radio | incorrect | correct |
|
||||
|
||||
|
||||
Scenario: I can reset the correctness of a problem after submitting a blank answer
|
||||
Given I am viewing a "<ProblemType>" problem
|
||||
When I check a problem
|
||||
And I input an answer on a "<ProblemType>" problem "correctly"
|
||||
Then my "<ProblemType>" answer is marked "unanswered"
|
||||
|
||||
Examples:
|
||||
| ProblemType |
|
||||
| drop down |
|
||||
| multiple choice |
|
||||
| checkbox |
|
||||
| radio |
|
||||
| string |
|
||||
| numerical |
|
||||
| formula |
|
||||
| script |
|
||||
|
||||
@@ -82,15 +82,23 @@ def answer_problem_step(step, problem_type, correctness):
|
||||
*problem_type* is a string representing the type of problem (e.g. 'drop down')
|
||||
*correctness* is in ['correct', 'incorrect']
|
||||
"""
|
||||
|
||||
assert(correctness in ['correct', 'incorrect'])
|
||||
assert(problem_type in PROBLEM_DICT)
|
||||
answer_problem(problem_type, correctness)
|
||||
# Change the answer on the page
|
||||
input_problem_answer(step, problem_type, correctness)
|
||||
|
||||
# Submit the problem
|
||||
check_problem(step)
|
||||
|
||||
|
||||
@step(u'I input an answer on a "([^"]*)" problem "([^"]*)ly"')
|
||||
def input_problem_answer(_, problem_type, correctness):
|
||||
"""
|
||||
Have the browser input an answer (either correct or incorrect)
|
||||
"""
|
||||
assert(correctness in ['correct', 'incorrect'])
|
||||
assert(problem_type in PROBLEM_DICT)
|
||||
answer_problem(problem_type, correctness)
|
||||
|
||||
|
||||
@step(u'I check a problem')
|
||||
def check_problem(step):
|
||||
world.css_click("input.check")
|
||||
@@ -146,8 +154,8 @@ def see_score(_step, score):
|
||||
assert world.browser.is_text_present(score)
|
||||
|
||||
|
||||
@step(u'My "([^"]*)" answer is marked "([^"]*)"')
|
||||
def assert_answer_mark(step, problem_type, correctness):
|
||||
@step(u'[Mm]y "([^"]*)" answer is( NOT)? marked "([^"]*)"')
|
||||
def assert_answer_mark(_step, problem_type, isnt_marked, correctness):
|
||||
"""
|
||||
Assert that the expected answer mark is visible
|
||||
for a given problem type.
|
||||
@@ -162,7 +170,10 @@ def assert_answer_mark(step, problem_type, correctness):
|
||||
|
||||
# At least one of the correct selectors should be present
|
||||
for sel in PROBLEM_DICT[problem_type][correctness]:
|
||||
has_expected = world.is_css_present(sel)
|
||||
if isnt_marked:
|
||||
has_expected = world.is_css_not_present(sel)
|
||||
else:
|
||||
has_expected = world.is_css_present(sel)
|
||||
|
||||
# As soon as we find the selector, break out of the loop
|
||||
if has_expected:
|
||||
|
||||
@@ -24,6 +24,8 @@ from capa.tests.response_xml_factory import OptionResponseXMLFactory, \
|
||||
# Factories from capa.tests.response_xml_factory that we will use
|
||||
# to generate the problem XML, with the keyword args used to configure
|
||||
# the output.
|
||||
# 'correct', 'incorrect', and 'unanswered' keys are lists of CSS selectors
|
||||
# the presence of any in the list is sufficient
|
||||
PROBLEM_DICT = {
|
||||
'drop down': {
|
||||
'factory': OptionResponseXMLFactory(),
|
||||
|
||||
Reference in New Issue
Block a user