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): """