diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index d76b62dc06..78a94941cc 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -309,7 +309,13 @@ class CapaModule(CapaFields, XModule): d = self.get_score() score = d['score'] total = d['total'] + if total > 0: + if self.weight is not None: + # scale score and total by weight/total: + score = score * self.weight / total + total = self.weight + try: return Progress(score, total) except (TypeError, ValueError): @@ -321,11 +327,13 @@ class CapaModule(CapaFields, XModule): """ Return some html with data about the module """ + progress = self.get_progress() return self.system.render_template('problem_ajax.html', { 'element_id': self.location.html_id(), 'id': self.id, 'ajax_url': self.system.ajax_url, - 'progress': Progress.to_js_status_str(self.get_progress()) + 'progress_status': Progress.to_js_status_str(progress), + 'progress_detail': Progress.to_js_detail_str(progress), }) def check_button_name(self): @@ -485,8 +493,7 @@ class CapaModule(CapaFields, XModule): """ Return html for the problem. - Adds check, reset, save buttons as necessary based on the problem config - and state. + Adds check, reset, save buttons as necessary based on the problem config and state. """ try: @@ -516,13 +523,12 @@ class CapaModule(CapaFields, XModule): 'reset_button': self.should_show_reset_button(), 'save_button': self.should_show_save_button(), 'answer_available': self.answer_available(), - 'ajax_url': self.system.ajax_url, 'attempts_used': self.attempts, 'attempts_allowed': self.max_attempts, - 'progress': self.get_progress(), } html = self.system.render_template('problem.html', context) + if encapsulate: html = u'
'.format( id=self.location.html_id(), ajax_url=self.system.ajax_url @@ -584,6 +590,7 @@ class CapaModule(CapaFields, XModule): result.update({ 'progress_changed': after != before, 'progress_status': Progress.to_js_status_str(after), + 'progress_detail': Progress.to_js_detail_str(after), }) return json.dumps(result, cls=ComplexEncoder) @@ -614,6 +621,7 @@ class CapaModule(CapaFields, XModule): Problem can be completely wrong. Pressing RESET button makes this function to return False. """ + # used by conditional module return self.lcp.done def is_attempted(self): @@ -757,6 +765,7 @@ class CapaModule(CapaFields, XModule): """ return {'html': self.get_problem_html(encapsulate=False)} + @staticmethod def make_dict_of_responses(data): """ diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss index 9e6826242f..48912795f0 100644 --- a/common/lib/xmodule/xmodule/css/capa/display.scss +++ b/common/lib/xmodule/xmodule/css/capa/display.scss @@ -3,6 +3,7 @@ h2 { margin-bottom: 15px; &.problem-header { + display: inline-block; section.staff { margin-top: 30px; font-size: 80%; @@ -28,6 +29,13 @@ iframe[seamless]{ color: darken($error-red, 11%); } +section.problem-progress { + display: inline-block; + color: #999; + font-size: em(16); + font-weight: 100; + padding-left: 5px; +} section.problem { @media print { diff --git a/common/lib/xmodule/xmodule/js/fixtures/problem.html b/common/lib/xmodule/xmodule/js/fixtures/problem.html index 525b4323b7..07e147a9e7 100644 --- a/common/lib/xmodule/xmodule/js/fixtures/problem.html +++ b/common/lib/xmodule/xmodule/js/fixtures/problem.html @@ -1,6 +1,6 @@
diff --git a/common/lib/xmodule/xmodule/js/fixtures/problem_content.html b/common/lib/xmodule/xmodule/js/fixtures/problem_content.html index 3e580be722..5ccce952e7 100644 --- a/common/lib/xmodule/xmodule/js/fixtures/problem_content.html +++ b/common/lib/xmodule/xmodule/js/fixtures/problem_content.html @@ -1,5 +1,8 @@

Problem Header

+
+
+

Problem Content

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 bca89b0dea..33d74e2335 100644 --- a/common/lib/xmodule/xmodule/js/spec/capa/display_spec.coffee +++ b/common/lib/xmodule/xmodule/js/spec/capa/display_spec.coffee @@ -77,6 +77,25 @@ describe 'Problem', -> [@problem.updateMathML, @stubbedJax, $('#input_example_1').get(0)] ] + describe 'renderProgressState', -> + beforeEach -> + @problem = new Problem($('.xmodule_display')) + #@renderProgressState = @problem.renderProgressState + + describe 'with a status of "none"', -> + it 'reports the number of points possible', -> + @problem.el.data('progress_status', 'none') + @problem.el.data('progress_detail', '0/1') + @problem.renderProgressState() + expect(@problem.$('.problem-progress').html()).toEqual "(1 point possible)" + + describe 'with any other valid status', -> + it 'reports the current score', -> + @problem.el.data('progress_status', 'foo') + @problem.el.data('progress_detail', '1/1') + @problem.renderProgressState() + expect(@problem.$('.problem-progress').html()).toEqual "(1/1 points)" + describe 'render', -> beforeEach -> @problem = new Problem($('.xmodule_display')) diff --git a/common/lib/xmodule/xmodule/js/src/capa/display.coffee b/common/lib/xmodule/xmodule/js/src/capa/display.coffee index 601fb749ac..09a398dd7a 100644 --- a/common/lib/xmodule/xmodule/js/src/capa/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/capa/display.coffee @@ -35,15 +35,34 @@ class @Problem @$('input.math').each (index, element) => MathJax.Hub.Queue [@refreshMath, null, element] + renderProgressState: => + detail = @el.data('progress_detail') + status = @el.data('progress_status') + # i18n + progress = "(#{detail} points)" + if status == 'none' and detail? and detail.indexOf('/') > 0 + a = detail.split('/') + possible = parseInt(a[1]) + if possible == 1 + # i18n + progress = "(#{possible} point possible)" + else + # i18n + progress = "(#{possible} points possible)" + @$('.problem-progress').html(progress) + updateProgress: (response) => if response.progress_changed - @el.attr progress: response.progress_status + @el.data('progress_status', response.progress_status) + @el.data('progress_detail', response.progress_detail) @el.trigger('progressChanged') + @renderProgressState() forceUpdate: (response) => - @el.attr progress: response.progress_status + @el.data('progress_status', response.progress_status) + @el.data('progress_detail', response.progress_detail) @el.trigger('progressChanged') - + @renderProgressState() queueing: => @queued_items = @$(".xqueue") @@ -113,7 +132,7 @@ class @Problem @setupInputTypes() @bind() @queueing() - + @forceUpdate response # TODO add hooks for problem types here by inspecting response.html and doing # stuff if a div w a class is found diff --git a/common/lib/xmodule/xmodule/js/src/sequence/display.coffee b/common/lib/xmodule/xmodule/js/src/sequence/display.coffee index 495b734785..bae1b89bab 100644 --- a/common/lib/xmodule/xmodule/js/src/sequence/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/sequence/display.coffee @@ -45,7 +45,7 @@ class @Sequence new_progress = "NA" _this = this $('.problems-wrapper').each (index) -> - progress = $(this).attr 'progress' + progress = $(this).data 'progress_status' new_progress = _this.mergeProgress progress, new_progress @progressTable[@position] = new_progress diff --git a/common/lib/xmodule/xmodule/tests/test_capa_module.py b/common/lib/xmodule/xmodule/tests/test_capa_module.py index 0f3dfa5b85..80c4e41e8f 100644 --- a/common/lib/xmodule/xmodule/tests/test_capa_module.py +++ b/common/lib/xmodule/xmodule/tests/test_capa_module.py @@ -1233,6 +1233,37 @@ class CapaModuleTest(unittest.TestCase): mock_log.exception.assert_called_once_with('Got bad progress') mock_log.reset_mock() + @patch('xmodule.capa_module.Progress') + def test_get_progress_calculate_progress_fraction(self, mock_progress): + """ + Check that score and total are calculated correctly for the progress fraction. + """ + module = CapaFactory.create() + module.weight = 1 + module.get_progress() + mock_progress.assert_called_with(0, 1) + + other_module = CapaFactory.create(correct=True) + other_module.weight = 1 + other_module.get_progress() + mock_progress.assert_called_with(1, 1) + + def test_get_html(self): + """ + Check that get_html() calls get_progress() with no arguments. + """ + module = CapaFactory.create() + module.get_progress = Mock(wraps=module.get_progress) + module.get_html() + module.get_progress.assert_called_once_with() + + def test_get_problem(self): + """ + Check that get_problem() returns the expected dictionary. + """ + module = CapaFactory.create() + self.assertEquals(module.get_problem("data"), {'html': module.get_problem_html(encapsulate=False)}) + class ComplexEncoderTest(unittest.TestCase): def test_default(self): diff --git a/lms/djangoapps/courseware/features/problems.feature b/lms/djangoapps/courseware/features/problems.feature index fe6a695475..b8b129bdd4 100644 --- a/lms/djangoapps/courseware/features/problems.feature +++ b/lms/djangoapps/courseware/features/problems.feature @@ -129,3 +129,45 @@ Feature: Answer problems When I press the button with the label "Hide Answer(s)" Then the button with the label "Show Answer(s)" does appear And I should not see "4.14159" anywhere on the page + + Scenario: I can see my score on a problem when I answer it and after I reset it + Given I am viewing a "" problem + When I answer a "" problem "ly" + Then I should see a score of "" + When I reset the problem + Then I should see a score of "" + + Examples: + | ProblemType | Correctness | Score | Points Possible | + | drop down | correct | 1/1 points | 1 point possible | + | drop down | incorrect | 1 point possible | 1 point possible | + | multiple choice | correct | 1/1 points | 1 point possible | + | multiple choice | incorrect | 1 point possible | 1 point possible | + | checkbox | correct | 1/1 points | 1 point possible | + | checkbox | incorrect | 1 point possible | 1 point possible | + | radio | correct | 1/1 points | 1 point possible | + | radio | incorrect | 1 point possible | 1 point possible | + | string | correct | 1/1 points | 1 point possible | + | string | incorrect | 1 point possible | 1 point possible | + | numerical | correct | 1/1 points | 1 point possible | + | numerical | incorrect | 1 point possible | 1 point possible | + | formula | correct | 1/1 points | 1 point possible | + | formula | incorrect | 1 point possible | 1 point possible | + | script | correct | 2/2 points | 2 points possible | + | script | incorrect | 2 points possible | 2 points possible | + + Scenario: I can see my score on a problem to which I submit a blank answer + Given I am viewing a "" problem + When I check a problem + Then I should see a score of "" + + Examples: + | ProblemType | Points Possible | + | drop down | 1 point possible | + | multiple choice | 1 point possible | + | checkbox | 1 point possible | + | radio | 1 point possible | + | string | 1 point possible | + | numerical | 1 point possible | + | formula | 1 point possible | + | script | 2 points possible | diff --git a/lms/djangoapps/courseware/features/problems.py b/lms/djangoapps/courseware/features/problems.py index e0c3c004da..997f77c8f2 100644 --- a/lms/djangoapps/courseware/features/problems.py +++ b/lms/djangoapps/courseware/features/problems.py @@ -142,6 +142,11 @@ def button_with_label_present(_step, buttonname, doesnt_appear): assert world.browser.is_text_present(buttonname, wait_time=5) +@step(u'I should see a score of "([^"]*)"$') +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): """ diff --git a/lms/templates/problem.html b/lms/templates/problem.html index fc49ab7ce7..f4f8e78b66 100644 --- a/lms/templates/problem.html +++ b/lms/templates/problem.html @@ -1,11 +1,11 @@ <%namespace name='static' file='static_content.html'/>

${ problem['name'] } - % if problem['weight'] != 1 and problem['weight'] is not None: - : ${ problem['weight'] } points - % endif

+
+
+
${ problem['html'] } diff --git a/lms/templates/problem_ajax.html b/lms/templates/problem_ajax.html index 42cd18c4e3..1babda1ae2 100644 --- a/lms/templates/problem_ajax.html +++ b/lms/templates/problem_ajax.html @@ -1 +1 @@ -
+