Commit 011ceb8e by Renzo Lucioni

Merge pull request #418 from edx/renzo/in-context-progress

Renzo/in context progress
parents 73e6e6f4 9af87e41
...@@ -309,7 +309,13 @@ class CapaModule(CapaFields, XModule): ...@@ -309,7 +309,13 @@ class CapaModule(CapaFields, XModule):
d = self.get_score() d = self.get_score()
score = d['score'] score = d['score']
total = d['total'] total = d['total']
if total > 0: 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: try:
return Progress(score, total) return Progress(score, total)
except (TypeError, ValueError): except (TypeError, ValueError):
...@@ -321,11 +327,13 @@ class CapaModule(CapaFields, XModule): ...@@ -321,11 +327,13 @@ class CapaModule(CapaFields, XModule):
""" """
Return some html with data about the module Return some html with data about the module
""" """
progress = self.get_progress()
return self.system.render_template('problem_ajax.html', { return self.system.render_template('problem_ajax.html', {
'element_id': self.location.html_id(), 'element_id': self.location.html_id(),
'id': self.id, 'id': self.id,
'ajax_url': self.system.ajax_url, '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): def check_button_name(self):
...@@ -485,8 +493,7 @@ class CapaModule(CapaFields, XModule): ...@@ -485,8 +493,7 @@ class CapaModule(CapaFields, XModule):
""" """
Return html for the problem. Return html for the problem.
Adds check, reset, save buttons as necessary based on the problem config Adds check, reset, save buttons as necessary based on the problem config and state.
and state.
""" """
try: try:
...@@ -516,13 +523,12 @@ class CapaModule(CapaFields, XModule): ...@@ -516,13 +523,12 @@ class CapaModule(CapaFields, XModule):
'reset_button': self.should_show_reset_button(), 'reset_button': self.should_show_reset_button(),
'save_button': self.should_show_save_button(), 'save_button': self.should_show_save_button(),
'answer_available': self.answer_available(), 'answer_available': self.answer_available(),
'ajax_url': self.system.ajax_url,
'attempts_used': self.attempts, 'attempts_used': self.attempts,
'attempts_allowed': self.max_attempts, 'attempts_allowed': self.max_attempts,
'progress': self.get_progress(),
} }
html = self.system.render_template('problem.html', context) html = self.system.render_template('problem.html', context)
if encapsulate: if encapsulate:
html = u'<div id="problem_{id}" class="problem" data-url="{ajax_url}">'.format( html = u'<div id="problem_{id}" class="problem" data-url="{ajax_url}">'.format(
id=self.location.html_id(), ajax_url=self.system.ajax_url id=self.location.html_id(), ajax_url=self.system.ajax_url
...@@ -584,6 +590,7 @@ class CapaModule(CapaFields, XModule): ...@@ -584,6 +590,7 @@ class CapaModule(CapaFields, XModule):
result.update({ result.update({
'progress_changed': after != before, 'progress_changed': after != before,
'progress_status': Progress.to_js_status_str(after), 'progress_status': Progress.to_js_status_str(after),
'progress_detail': Progress.to_js_detail_str(after),
}) })
return json.dumps(result, cls=ComplexEncoder) return json.dumps(result, cls=ComplexEncoder)
...@@ -614,6 +621,7 @@ class CapaModule(CapaFields, XModule): ...@@ -614,6 +621,7 @@ class CapaModule(CapaFields, XModule):
Problem can be completely wrong. Problem can be completely wrong.
Pressing RESET button makes this function to return False. Pressing RESET button makes this function to return False.
""" """
# used by conditional module
return self.lcp.done return self.lcp.done
def is_attempted(self): def is_attempted(self):
...@@ -757,6 +765,7 @@ class CapaModule(CapaFields, XModule): ...@@ -757,6 +765,7 @@ class CapaModule(CapaFields, XModule):
""" """
return {'html': self.get_problem_html(encapsulate=False)} return {'html': self.get_problem_html(encapsulate=False)}
@staticmethod @staticmethod
def make_dict_of_responses(data): def make_dict_of_responses(data):
""" """
......
...@@ -3,6 +3,7 @@ h2 { ...@@ -3,6 +3,7 @@ h2 {
margin-bottom: 15px; margin-bottom: 15px;
&.problem-header { &.problem-header {
display: inline-block;
section.staff { section.staff {
margin-top: 30px; margin-top: 30px;
font-size: 80%; font-size: 80%;
...@@ -28,6 +29,13 @@ iframe[seamless]{ ...@@ -28,6 +29,13 @@ iframe[seamless]{
color: darken($error-red, 11%); 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 { section.problem {
@media print { @media print {
......
<section class='xmodule_display xmodule_CapaModule' data-type='Problem'> <section class='xmodule_display xmodule_CapaModule' data-type='Problem'>
<section id='problem_1' <section id='problem_1'
class='problems-wrapper' class='problems-wrapper'
data-problem-id='i4x://edX/101/problem/Problem1' data-problem-id='i4x://edX/101/problem/Problem1'
data-url='/problem/Problem1'> data-url='/problem/Problem1'>
</section> </section>
......
<h2 class="problem-header">Problem Header</h2> <h2 class="problem-header">Problem Header</h2>
<section class='problem-progress'>
</section>
<section class="problem"> <section class="problem">
<p>Problem Content</p> <p>Problem Content</p>
......
...@@ -77,6 +77,25 @@ describe 'Problem', -> ...@@ -77,6 +77,25 @@ describe 'Problem', ->
[@problem.updateMathML, @stubbedJax, $('#input_example_1').get(0)] [@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', -> describe 'render', ->
beforeEach -> beforeEach ->
@problem = new Problem($('.xmodule_display')) @problem = new Problem($('.xmodule_display'))
......
...@@ -35,15 +35,34 @@ class @Problem ...@@ -35,15 +35,34 @@ class @Problem
@$('input.math').each (index, element) => @$('input.math').each (index, element) =>
MathJax.Hub.Queue [@refreshMath, null, 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) => updateProgress: (response) =>
if response.progress_changed 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') @el.trigger('progressChanged')
@renderProgressState()
forceUpdate: (response) => 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') @el.trigger('progressChanged')
@renderProgressState()
queueing: => queueing: =>
@queued_items = @$(".xqueue") @queued_items = @$(".xqueue")
...@@ -113,7 +132,7 @@ class @Problem ...@@ -113,7 +132,7 @@ class @Problem
@setupInputTypes() @setupInputTypes()
@bind() @bind()
@queueing() @queueing()
@forceUpdate response
# TODO add hooks for problem types here by inspecting response.html and doing # TODO add hooks for problem types here by inspecting response.html and doing
# stuff if a div w a class is found # stuff if a div w a class is found
......
...@@ -45,7 +45,7 @@ class @Sequence ...@@ -45,7 +45,7 @@ class @Sequence
new_progress = "NA" new_progress = "NA"
_this = this _this = this
$('.problems-wrapper').each (index) -> $('.problems-wrapper').each (index) ->
progress = $(this).attr 'progress' progress = $(this).data 'progress_status'
new_progress = _this.mergeProgress progress, new_progress new_progress = _this.mergeProgress progress, new_progress
@progressTable[@position] = new_progress @progressTable[@position] = new_progress
......
...@@ -1233,6 +1233,37 @@ class CapaModuleTest(unittest.TestCase): ...@@ -1233,6 +1233,37 @@ class CapaModuleTest(unittest.TestCase):
mock_log.exception.assert_called_once_with('Got bad progress') mock_log.exception.assert_called_once_with('Got bad progress')
mock_log.reset_mock() 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): class ComplexEncoderTest(unittest.TestCase):
def test_default(self): def test_default(self):
......
...@@ -129,3 +129,45 @@ Feature: Answer problems ...@@ -129,3 +129,45 @@ Feature: Answer problems
When I press the button with the label "Hide Answer(s)" When I press the button with the label "Hide Answer(s)"
Then the button with the label "Show Answer(s)" does appear Then the button with the label "Show Answer(s)" does appear
And I should not see "4.14159" anywhere on the page 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 "<ProblemType>" problem
When I answer a "<ProblemType>" problem "<Correctness>ly"
Then I should see a score of "<Score>"
When I reset the problem
Then I should see a score of "<Points Possible>"
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 "<ProblemType>" problem
When I check a problem
Then I should see a score of "<Points Possible>"
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 |
...@@ -142,6 +142,11 @@ def button_with_label_present(_step, buttonname, doesnt_appear): ...@@ -142,6 +142,11 @@ def button_with_label_present(_step, buttonname, doesnt_appear):
assert world.browser.is_text_present(buttonname, wait_time=5) 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 "([^"]*)"') @step(u'My "([^"]*)" answer is marked "([^"]*)"')
def assert_answer_mark(step, problem_type, correctness): def assert_answer_mark(step, problem_type, correctness):
""" """
......
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<h2 class="problem-header"> <h2 class="problem-header">
${ problem['name'] } ${ problem['name'] }
% if problem['weight'] != 1 and problem['weight'] is not None:
: ${ problem['weight'] } points
% endif
</h2> </h2>
<section class="problem-progress">
</section>
<section class="problem"> <section class="problem">
${ problem['html'] } ${ problem['html'] }
......
<section id="problem_${element_id}" class="problems-wrapper" data-problem-id="${id}" data-url="${ajax_url}" progress="${progress}"></section> <section id="problem_${element_id}" class="problems-wrapper" data-problem-id="${id}" data-url="${ajax_url}" data-progress_status="${progress_status}" data-progress_detail="${progress_detail}"></section>
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