Commit 6defd7ba by Sarina Canelake

Merge pull request #790 from edx/unanswered-on-input

Unanswered on input
parents b7e8af65 fe47dcb1
......@@ -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:
......
<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}"
......
<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:
......
<% 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,14 +82,22 @@ 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']
"""
# 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)
# Submit the problem
check_problem(step)
@step(u'I check a problem')
def check_problem(step):
......@@ -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(),
......
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