Merge pull request #418 from edx/renzo/in-context-progress
Renzo/in context progress
This commit is contained in:
@@ -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'<div id="problem_{id}" class="problem" data-url="{ajax_url}">'.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):
|
||||
"""
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<section class='xmodule_display xmodule_CapaModule' data-type='Problem'>
|
||||
<section id='problem_1'
|
||||
class='problems-wrapper'
|
||||
class='problems-wrapper'
|
||||
data-problem-id='i4x://edX/101/problem/Problem1'
|
||||
data-url='/problem/Problem1'>
|
||||
</section>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<h2 class="problem-header">Problem Header</h2>
|
||||
|
||||
<section class='problem-progress'>
|
||||
</section>
|
||||
|
||||
<section class="problem">
|
||||
<p>Problem Content</p>
|
||||
|
||||
|
||||
@@ -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'))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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 "<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):
|
||||
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):
|
||||
"""
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
<h2 class="problem-header">
|
||||
${ problem['name'] }
|
||||
% if problem['weight'] != 1 and problem['weight'] is not None:
|
||||
: ${ problem['weight'] } points
|
||||
% endif
|
||||
</h2>
|
||||
|
||||
<section class="problem-progress">
|
||||
</section>
|
||||
|
||||
<section class="problem">
|
||||
${ problem['html'] }
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user