diff --git a/common/lib/xmodule/xmodule/capa_base.py b/common/lib/xmodule/xmodule/capa_base.py
index d02627af30..d6e15d6f28 100644
--- a/common/lib/xmodule/xmodule/capa_base.py
+++ b/common/lib/xmodule/xmodule/capa_base.py
@@ -20,6 +20,7 @@ except ImportError:
from pytz import utc
from capa.capa_problem import LoncapaProblem, LoncapaSystem
+from capa.inputtypes import Status
from capa.responsetypes import StudentInputError, ResponseError, LoncapaProblemError
from capa.util import convert_files_to_filenames, get_inner_html_from_xpath
from xblock.fields import Boolean, Dict, Float, Integer, Scope, String, XMLString
@@ -942,7 +943,12 @@ class CapaMixin(CapaFields):
"""
For the "show answer" button.
- Returns the answers: {'answers' : answers}
+ Returns the answers and rendered "correct status span" HTML:
+ {'answers' : answers, 'correct_status_html': correct_status_span_html}.
+ The "correct status span" HTML is injected beside the correct answers
+ for radio button and checkmark problems, so that there is a visual
+ indication of the correct answers that is not solely based on color
+ (and also screen reader text).
"""
event_info = dict()
event_info['problem_id'] = self.location.to_deprecated_string()
@@ -968,7 +974,13 @@ class CapaMixin(CapaFields):
new_answer = {answer_id: answers[answer_id]}
new_answers.update(new_answer)
- return {'answers': new_answers}
+ return {
+ 'answers': new_answers,
+ 'correct_status_html': self.runtime.render_template(
+ 'status_span.html',
+ {'status': Status('correct', self.runtime.service(self, "i18n").ugettext)}
+ )
+ }
# Figure out if we should move these to capa_problem?
def get_problem(self, _data):
diff --git a/common/lib/xmodule/xmodule/js/spec/capa/display_spec.coffee b/common/lib/xmodule/xmodule/js/spec/capa/display_spec.coffee
index 4ca8d5585b..a6b197646a 100644
--- a/common/lib/xmodule/xmodule/js/spec/capa/display_spec.coffee
+++ b/common/lib/xmodule/xmodule/js/spec/capa/display_spec.coffee
@@ -457,24 +457,6 @@ describe 'Problem', ->
expect(window.SR.readText).toHaveBeenCalledWith 'Answers to this problem are now shown. Navigate through the problem to review it with answers inline.'
- describe 'multiple choice question', ->
- beforeEach ->
- @problem.el.prepend '''
-
-
-
-
- '''
-
- it 'set the correct_answer attribute on the choice', ->
- spyOn($, 'postWithPrefix').and.callFake (url, callback) ->
- callback answers: '1_1': [2, 3]
- @problem.show()
- expect($('label[for="input_1_1_1"]')).not.toHaveAttr 'correct_answer', 'true'
- expect($('label[for="input_1_1_2"]')).toHaveAttr 'correct_answer', 'true'
- expect($('label[for="input_1_1_3"]')).toHaveAttr 'correct_answer', 'true'
- expect($('label[for="input_1_2_1"]')).not.toHaveAttr 'correct_answer', 'true'
-
describe 'radio text question', ->
radio_text_xml='''
diff --git a/common/lib/xmodule/xmodule/js/src/capa/display.js b/common/lib/xmodule/xmodule/js/src/capa/display.js
index 19aab27fa9..9295822bc2 100644
--- a/common/lib/xmodule/xmodule/js/src/capa/display.js
+++ b/common/lib/xmodule/xmodule/js/src/capa/display.js
@@ -681,17 +681,8 @@
var answers;
answers = response.answers;
$.each(answers, function(key, value) {
- var answer, choice, i, len, results;
- if ($.isArray(value)) {
- results = [];
- for (i = 0, len = value.length; i < len; i++) {
- choice = value[i];
- results.push(that.$('label[for="input_' + key + '_' + choice + '"]').attr({
- correct_answer: 'true'
- }));
- }
- return results;
- } else {
+ var answer;
+ if (!$.isArray(value)) {
answer = that.$('#answer_' + key + ', #solution_' + key);
edx.HtmlUtils.setHtml(answer, edx.HtmlUtils.HTML(value));
Collapsible.setCollapsibles(answer);
@@ -722,7 +713,7 @@
display = that.inputtypeDisplays[$(inputtype).attr('id')];
showMethod = that.inputtypeShowAnswerMethods[cls];
if (showMethod != null) {
- results.push(showMethod(inputtype, display, answers));
+ results.push(showMethod(inputtype, display, answers, response.correct_status_html));
} else {
results.push(void 0);
}
@@ -947,10 +938,10 @@
var $status;
$status = $('#status_' + id);
if ($status[0]) {
- $status.removeAttr('class').addClass('unanswered');
+ $status.removeAttr('class').addClass('status unanswered');
} else {
$('', {
- class: 'unanswered',
+ class: 'status unanswered',
style: 'display: inline-block;',
id: 'status_' + id
});
@@ -1030,16 +1021,30 @@
};
Problem.prototype.inputtypeShowAnswerMethods = {
- choicegroup: function(element, display, answers) {
- var answer, choice, inputId, i, len, results, $element;
+ choicegroup: function(element, display, answers, correctStatusHtml) {
+ var answer, choice, inputId, i, len, results, $element, $inputLabel, $inputStatus;
$element = $(element);
inputId = $element.attr('id').replace(/inputtype_/, '');
answer = answers[inputId];
results = [];
for (i = 0, len = answer.length; i < len; i++) {
choice = answer[i];
- results.push($element.find('#input_' + inputId + '_' + choice).parent('label').
- addClass('choicegroup_correct'));
+ $inputLabel = $element.find('#input_' + inputId + '_' + choice).parent('label');
+ $inputStatus = $inputLabel.find('#status_' + inputId);
+ // If the correct answer was already Submitted before "Show Answer" was selected,
+ // the status HTML will already be present. Otherwise, inject the status HTML.
+
+ // If the learner clicked a different answer after Submit, their submitted answers
+ // will be marked as "unanswered". In that case, for correct answers update the
+ // classes accordingly.
+ if ($inputStatus.hasClass('unanswered')) {
+ $inputStatus.removeAttr('class').addClass('status correct');
+ $inputLabel.addClass('choicegroup_correct');
+ } else if (!$inputLabel.hasClass('choicegroup_correct')) {
+ // If the status HTML is not already present (due to clicking Submit), append
+ // the status HTML for correct answers.
+ results.push($inputLabel.addClass('choicegroup_correct').append(correctStatusHtml));
+ }
}
return results;
},
diff --git a/common/test/acceptance/pages/lms/problem.py b/common/test/acceptance/pages/lms/problem.py
index 285dbd97ad..443727311f 100644
--- a/common/test/acceptance/pages/lms/problem.py
+++ b/common/test/acceptance/pages/lms/problem.py
@@ -413,10 +413,17 @@ class ProblemPage(PageObject):
"""
Check if correct answer/choice highlighted for choice group.
"""
- xpath = '//fieldset/div[contains(@class, "field")][{0}]/label[contains(@class, "choicegroup_correct")]'
+ correct_status_xpath = '//fieldset/div[contains(@class, "field")][{0}]/label[contains(@class, "choicegroup_correct")]/span[contains(@class, "status correct")]' # pylint: disable=line-too-long
+ any_status_xpath = '//fieldset/div[contains(@class, "field")][{0}]/label/span'
for choice in correct_choices:
- if not self.q(xpath=xpath.format(choice)).is_present():
+ if not self.q(xpath=correct_status_xpath.format(choice)).is_present():
return False
+
+ # Check that there is only a single status span, as there were some bugs with multiple
+ # spans (with various classes) being appended.
+ if not len(self.q(xpath=any_status_xpath.format(choice)).results) == 1:
+ return False
+
return True
@property
diff --git a/common/test/acceptance/tests/lms/test_problem_types.py b/common/test/acceptance/tests/lms/test_problem_types.py
index 2276043d4b..aca5480ceb 100644
--- a/common/test/acceptance/tests/lms/test_problem_types.py
+++ b/common/test/acceptance/tests/lms/test_problem_types.py
@@ -535,6 +535,35 @@ class MultipleChoiceProblemTypeTest(ProblemTypeTestBase, ProblemTypeTestMixin):
else:
self.problem_page.click_choice("choice_choice_2")
+ @attr(shard=7)
+ def test_can_show_answer(self):
+ """
+ Scenario: Verifies that show answer button is working as expected.
+
+ Given that I am on courseware page
+ And I can see a CAPA problem with show answer button
+ When I click "Show Answer" button
+ The correct answer is displayed with a single correctness indicator.
+ """
+ # Click the correct answer, but don't submit yet. No correctness shows.
+ self.answer_problem('correct')
+ self.assertFalse(self.problem_page.is_correct_choice_highlighted(correct_choices=[3]))
+
+ # After submit, the answer should be marked as correct.
+ self.problem_page.click_submit()
+ self.assertTrue(self.problem_page.is_correct_choice_highlighted(correct_choices=[3]))
+
+ # Switch to an incorrect answer. This will hide the correctness indicator.
+ self.answer_problem('incorrect')
+ self.assertFalse(self.problem_page.is_correct_choice_highlighted(correct_choices=[3]))
+
+ # Now click Show Answer. A single correctness indicator should be shown.
+ self.problem_page.click_show()
+ self.assertTrue(self.problem_page.is_correct_choice_highlighted(correct_choices=[3]))
+
+ # Finally, make sure that clicking Show Answer moved focus to the correct place.
+ self.problem_page.wait_for_focus_on_problem_meta()
+
class RadioProblemTypeTest(ProblemTypeTestBase, ProblemTypeTestMixin):
"""