Commit 1fc4ac86 by Renzo Lucioni

Add feature showing current score next to problem title

parent 73e6e6f4
...@@ -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)
...@@ -607,13 +614,7 @@ class CapaModule(CapaFields, XModule): ...@@ -607,13 +614,7 @@ class CapaModule(CapaFields, XModule):
return False return False
def is_submitted(self): def is_submitted(self):
""" # used by conditional module
Used to decide to show or hide RESET or CHECK buttons.
Means that student submitted problem and nothing more.
Problem can be completely wrong.
Pressing RESET button makes this function to return False.
"""
return self.lcp.done return self.lcp.done
def is_attempted(self): def is_attempted(self):
...@@ -755,7 +756,8 @@ class CapaModule(CapaFields, XModule): ...@@ -755,7 +756,8 @@ class CapaModule(CapaFields, XModule):
Used if we want to reconfirm we have the right thing e.g. after Used if we want to reconfirm we have the right thing e.g. after
several AJAX calls. several AJAX calls.
""" """
return {'html': self.get_problem_html(encapsulate=False)} return {'html': self.get_problem_html()}
@staticmethod @staticmethod
def make_dict_of_responses(data): def make_dict_of_responses(data):
...@@ -934,7 +936,7 @@ class CapaModule(CapaFields, XModule): ...@@ -934,7 +936,7 @@ class CapaModule(CapaFields, XModule):
self.system.psychometrics_handler(self.get_state_for_lcp()) self.system.psychometrics_handler(self.get_state_for_lcp())
# render problem into HTML # render problem into HTML
html = self.get_problem_html(encapsulate=False) html = self.get_problem_html()
return {'success': success, return {'success': success,
'contents': html, 'contents': html,
...@@ -1101,7 +1103,7 @@ class CapaModule(CapaFields, XModule): ...@@ -1101,7 +1103,7 @@ class CapaModule(CapaFields, XModule):
self.system.track_function('reset_problem', event_info) self.system.track_function('reset_problem', event_info)
return {'success': True, return {'success': True,
'html': self.get_problem_html(encapsulate=False)} 'html': self.get_problem_html()}
class CapaDescriptor(CapaFields, RawDescriptor): class CapaDescriptor(CapaFields, RawDescriptor):
......
...@@ -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,31 @@ class @Problem ...@@ -35,15 +35,31 @@ 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')
progress = "(#{detail} points)"
if status == 'none' and detail? and detail.indexOf('/') > 0
a = detail.split('/')
possible = parseInt(a[1])
if possible == 1
progress = "(#{possible} point possible)"
else
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 +129,7 @@ class @Problem ...@@ -113,7 +129,7 @@ class @Problem
@setupInputTypes() @setupInputTypes()
@bind() @bind()
@queueing() @queueing()
@renderProgressState()
# 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
...@@ -240,7 +256,7 @@ class @Problem ...@@ -240,7 +256,7 @@ class @Problem
analytics.track "Problem Checked", analytics.track "Problem Checked",
problem_id: @id problem_id: @id
answers: @answers answers: @answers
$.postWithPrefix "#{@url}/problem_check", @answers, (response) => $.postWithPrefix "#{@url}/problem_check", @answers, (response) =>
switch response.success switch response.success
when 'incorrect', 'correct' when 'incorrect', 'correct'
......
...@@ -1233,6 +1233,51 @@ class CapaModuleTest(unittest.TestCase): ...@@ -1233,6 +1233,51 @@ 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)
@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.get_progress()
mock_progress.assert_called_with(0,1)
other_module = CapaFactory.create(correct=True)
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()})
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