Commit 165d1a2e by Tim Krones Committed by GitHub

Merge pull request #132 from open-craft/completion-block

Implement Completion XBlock
parents 94bd72c3 488409bc
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-2015 Harvard, edX & OpenCraft
#
# This software's license gives you freedom; you can copy, convey,
# propagate, redistribute and/or modify this program under the terms of
# the GNU Affero General Public License (AGPL) as published by the Free
# Software Foundation (FSF), either version 3 of the License, or (at your
# option) any later version of the AGPL published by the FSF.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
# General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program in a file in the toplevel directory called
# "AGPLv3". If not, see <http://www.gnu.org/licenses/>.
#
# Imports ###########################################################
import logging
from xblock.core import XBlock
from xblock.fields import Scope, String, Boolean
from xblock.fragment import Fragment
from xblockutils.studio_editable import StudioEditableXBlockMixin
from xblockutils.resources import ResourceLoader
from .mixins import QuestionMixin, XBlockWithTranslationServiceMixin
from .sub_api import sub_api, SubmittingXBlockMixin
# Globals ###########################################################
log = logging.getLogger(__name__)
loader = ResourceLoader(__name__)
# Make '_' a no-op so we can scrape strings
def _(text):
return text
# Classes ###########################################################
@XBlock.needs('i18n')
class CompletionBlock(
SubmittingXBlockMixin, QuestionMixin, StudioEditableXBlockMixin, XBlockWithTranslationServiceMixin, XBlock
):
"""
An XBlock used by students to indicate that they completed a given task.
The student's answer is always considered "correct".
"""
CATEGORY = 'pb-completion'
STUDIO_LABEL = _(u'Completion')
answerable = True
question = String(
display_name=_('Question'),
help=_('Mentions a specific activity and asks the student whether they completed it.'),
scope=Scope.content,
default=_(
'Please indicate whether you attended the In Person Workshop session by (un-)checking the option below.'
),
)
answer = String(
display_name=_('Answer'),
help=_(
'Represents the answer that the student can (un-)check '
'to indicate whether they completed the activity that the question mentions.'
),
scope=Scope.content,
default=_('Yes, I attended the session.'),
)
student_value = Boolean(
help=_("Records student's answer."),
scope=Scope.user_state,
default=None,
)
editable_fields = ('display_name', 'show_title', 'question', 'answer')
def mentoring_view(self, context):
"""
Main view of this block.
"""
context = context.copy() if context else {}
context['question'] = self.question
context['answer'] = self.answer
context['checked'] = self.student_value if self.student_value is not None else False
context['title'] = self.display_name_with_default
context['hide_header'] = context.get('hide_header', False) or not self.show_title
html = loader.render_template('templates/html/completion.html', context)
fragment = Fragment(html)
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/completion.js'))
fragment.initialize_js('CompletionBlock')
return fragment
student_view = mentoring_view
preview_view = mentoring_view
def get_last_result(self):
""" Return the current/last result in the required format """
if self.student_value is None:
return {}
return {
'submission': self.student_value,
'status': 'correct',
'tips': [],
'weight': self.weight,
'score': 1,
}
def get_results(self):
""" Alias for get_last_result() """
return self.get_last_result()
def submit(self, value):
"""
Persist answer submitted by student.
"""
log.debug(u'Received Completion submission: "%s"', value)
self.student_value = value
if sub_api:
# Also send to the submissions API:
sub_api.create_submission(self.student_item_key, value)
result = self.get_last_result()
log.debug(u'Completion submission result: %s', result)
return result
......@@ -48,6 +48,7 @@ from xblockutils.studio_editable import (
)
from problem_builder.answer import AnswerBlock, AnswerRecapBlock
from problem_builder.completion import CompletionBlock
from problem_builder.mcq import MCQBlock, RatingBlock
from problem_builder.mrq import MRQBlock
from problem_builder.plot import PlotBlock
......@@ -381,7 +382,7 @@ class MentoringBlock(BaseMentoringBlock, StudioContainerWithNestedXBlocksMixin,
return [
NestedXBlockSpec(AnswerBlock, boilerplate='studio_default'),
MCQBlock, RatingBlock, MRQBlock,
MCQBlock, RatingBlock, MRQBlock, CompletionBlock,
NestedXBlockSpec(None, category="html", label=self._("HTML")),
AnswerRecapBlock, MentoringTableBlock, PlotBlock, SliderBlock
] + additional_blocks + message_block_shims
......
function CompletionBlock(runtime, element) {
var $completion = $('.pb-completion-value', element);
return {
mode: null,
mentoring: null,
init: function(options) {
this.mode = options.mode;
this.mentoring = options.mentoring;
$completion.on('change', options.onChange);
},
submit: function() {
return $completion.is(':checked');
},
handleSubmit: function(result) {
if (typeof result.submission !== 'undefined') {
this.updateCompletion(result);
$('.submit-result', element).css('visibility', 'visible');
}
},
handleReview: function(result) {
this.updateCompletion(result);
$completion.prop('disabled', true);
},
clearResult: function() {
$('.submit-result', element).css('visibility', 'hidden');
},
updateCompletion: function(result) {
$completion.prop('checked', result.submission);
}
};
}
......@@ -31,6 +31,7 @@ from xblockutils.studio_editable import (
)
from problem_builder.answer import AnswerBlock, AnswerRecapBlock
from problem_builder.completion import CompletionBlock
from problem_builder.mcq import MCQBlock, RatingBlock
from problem_builder.mixins import EnumerableChildMixin, StepParentMixin
from problem_builder.mrq import MRQBlock
......@@ -144,7 +145,7 @@ class MentoringStepBlock(
return [
NestedXBlockSpec(AnswerBlock, boilerplate='studio_default'),
MCQBlock, RatingBlock, MRQBlock,
MCQBlock, RatingBlock, MRQBlock, CompletionBlock,
NestedXBlockSpec(None, category="html", label=self._("HTML")),
AnswerRecapBlock, MentoringTableBlock, PlotBlock, SliderBlock
] + additional_blocks
......
{% load i18n %}
<div class="xblock-pb-completion">
{% if not hide_header %}<h3 class="question-title">{{ title }}</h3>{% endif %}
<div class="clearfix">
<p>{{ question|safe }}</p>
<p>
<label>
<input type="checkbox" class="pb-completion-value" {% if checked %}checked="checked"{% endif %} />
{{ answer|safe }}
</label>
</p>
</div>
<div class="clearfix">
<span class="submit-result fa icon-2x checkmark-correct icon-ok fa-check"
style="visibility: hidden;" aria-label="{% trans "Complete" %}"></span>
</div>
</div>
......@@ -86,6 +86,25 @@ class ProblemBuilderBaseTest(SeleniumXBlockTest, PopupCheckMixin):
self.browser.execute_script('document.querySelectorAll("header.banner")[0].style.display="none";')
return element
def wait_for_init(self):
""" Wait for the scenario to initialize """
self.wait_until_hidden(self.browser.find_element_by_css_selector('.messages'))
def reload_page(self):
"""
Reload current page.
"""
self.browser.execute_script("$(document).html(' ');")
return self.go_to_view("student_view")
@property
def checkmark(self):
return self.browser.find_element_by_css_selector('.submit-result')
@property
def submit_button(self):
return self.browser.find_element_by_css_selector('.submit input.input-main')
def click_submit(self, mentoring):
""" Click the submit button and wait for the response """
submit = mentoring.find_element_by_css_selector('.submit input.input-main')
......@@ -101,6 +120,12 @@ class ProblemBuilderBaseTest(SeleniumXBlockTest, PopupCheckMixin):
label.click()
break
def expect_checkmark_visible(self, visible):
self.assertEqual(self.checkmark.is_displayed(), visible)
def expect_submit_enabled(self, enabled):
self.assertEqual(self.submit_button.is_enabled(), enabled)
class MentoringBaseTest(SeleniumBaseTest, PopupCheckMixin):
module_name = __name__
......@@ -164,6 +189,10 @@ class MentoringAssessmentBaseTest(ProblemBuilderBaseTest):
return mentoring, controls
def wait_for_init(self):
""" Wait for the scenario to initialize """
self.wait_until_visible(self.browser.find_elements_by_css_selector('.sb-step')[0])
def assert_hidden(self, elem):
self.assertFalse(elem.is_displayed())
......
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-2015 Harvard, edX & OpenCraft
#
# This software's license gives you freedom; you can copy, convey,
# propagate, redistribute and/or modify this program under the terms of
# the GNU Affero General Public License (AGPL) as published by the Free
# Software Foundation (FSF), either version 3 of the License, or (at your
# option) any later version of the AGPL published by the FSF.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
# General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program in a file in the toplevel directory called
# "AGPLv3". If not, see <http://www.gnu.org/licenses/>.
#
# Imports ###########################################################
from .base_test import ProblemBuilderBaseTest, MentoringAssessmentBaseTest, GetChoices
# Classes ###########################################################
class CompletionBlockTestMixin(object):
"""
Mixin for testing completion blocks.
"""
@property
def checkmarks(self):
return self.browser.find_elements_by_css_selector('.submit-result')
@property
def completion_checkbox(self):
return self.browser.find_element_by_css_selector('.pb-completion-value')
@property
def completion_checkboxes(self):
return self.browser.find_elements_by_css_selector('.pb-completion-value')
def expect_checkmarks_visible(self, first_visible, second_visible):
first_checkmark, second_checkmark = self.checkmarks
self.assertEqual(first_checkmark.is_displayed(), first_visible)
self.assertEqual(second_checkmark.is_displayed(), second_visible)
def expect_checkbox_checked(self, checked):
self.assertEqual(bool(self.completion_checkbox.get_attribute('checked')), checked)
def expect_checkboxes_checked(self, first_checked, second_checked):
first_checkbox, second_checkbox = self.completion_checkboxes
self.assertEqual(bool(first_checkbox.get_attribute('checked')), first_checked)
self.assertEqual(bool(second_checkbox.get_attribute('checked')), second_checked)
class CompletionBlockTest(CompletionBlockTestMixin, ProblemBuilderBaseTest):
"""
Tests for CompletionBlock inside a normal Problem Builder block.
"""
def test_simple_flow(self):
"""
Test a regular Problem Builder block containing one completion block.
"""
self.pb_wrapper = self.load_scenario('completion_problem.xml', {'include_mcq': False})
self.wait_for_init()
# Checkbox of completion block should not have "checked" attribute set initially,
# and "Submit" should be enabled since leaving checkbox unchecked produces a valid value:
self.assertIsNone(self.completion_checkbox.get_attribute('checked'))
self.expect_checkmark_visible(False)
self.expect_submit_enabled(True)
# Confirm completion by checking checkbox, and click "Submit":
self.completion_checkbox.click()
self.click_submit(self.pb_wrapper)
# Now, we expect "Submit" to be disabled and the checkmark to be visible:
self.expect_checkbox_checked(True)
self.expect_checkmark_visible(True)
self.expect_submit_enabled(False)
# Uncheck checkbox
self.completion_checkbox.click()
# It should be possible to click "Submit" again, and the checkmark should be hidden:
self.expect_checkbox_checked(False)
self.expect_checkmark_visible(False)
self.expect_submit_enabled(True)
# Now reload the page:
self.pb_wrapper = self.reload_page()
self.wait_for_init()
# The checkbox should be checked (since that's the value we submitted earlier),
# and "Submit" should be disabled (to discourage submitting the same answer):
self.expect_checkbox_checked(True)
self.expect_checkmark_visible(True)
self.expect_submit_enabled(False)
def test_simple_flow_with_peer(self):
"""
Test a regular Problem Builder block containing one completion block and an MCQ.
"""
self.pb_wrapper = self.load_scenario("completion_problem.xml", {"include_mcq": True})
self.wait_for_init()
# Checkbox of completion block should not have "checked" attribute set initially,
# and "Submit" should be disabled until an MCQ choice is selected
self.assertIsNone(self.completion_checkbox.get_attribute('checked'))
self.expect_checkmark_visible(False)
self.expect_submit_enabled(False)
# Confirm completion by checking checkbox:
self.completion_checkbox.click()
# Checkmark should be hidden, and "Submit" should be disabled (did not select an MCQ choice yet):
self.expect_checkbox_checked(True)
self.expect_checkmark_visible(False)
self.expect_submit_enabled(False)
# Select an MCQ choice:
GetChoices(self.pb_wrapper).select('Yes')
# "Submit" button should now be enabled:
self.expect_checkmark_visible(False)
self.expect_submit_enabled(True)
# Submit answers
self.click_submit(self.pb_wrapper)
# Now, we expect submit to be disabled and the checkmark to be visible:
self.expect_checkmark_visible(True)
self.expect_submit_enabled(False)
# Uncheck checkbox
self.completion_checkbox.click()
# It should be possible to click "Submit" again, and the checkmark should be hidden:
self.expect_checkbox_checked(False)
self.expect_checkmark_visible(False)
self.expect_submit_enabled(True)
def test_multiple_completion_blocks(self):
"""
Test a regular Problem Builder block containing multiple completion blocks.
"""
self.pb_wrapper = self.load_scenario("completion_multiple_problem.xml")
self.wait_for_init()
first_checkbox, second_checkbox = self.completion_checkboxes
# Checkboxes of completion blocks should not have "checked" attribute set initially,
# and "Submit" should be enabled since leaving checkboxes unchecked produces a valid value:
self.assertIsNone(first_checkbox.get_attribute('checked'))
self.assertIsNone(second_checkbox.get_attribute('checked'))
self.expect_checkmarks_visible(False, False)
self.expect_submit_enabled(True)
# Confirm completion by checking first checkbox:
first_checkbox.click()
self.expect_checkboxes_checked(True, False)
self.expect_checkmarks_visible(False, False)
self.expect_submit_enabled(True)
# Submit answers
self.click_submit(self.pb_wrapper)
# Now, we expect "Submit" to be disabled and the checkmarks to be visible:
self.expect_checkboxes_checked(True, False)
self.expect_checkmarks_visible(True, True)
self.expect_submit_enabled(False)
# Uncheck first checkbox
first_checkbox.click()
# It should be possible to click "Submit" again, and the checkmarks should be hidden:
self.expect_checkboxes_checked(False, False)
self.expect_checkmarks_visible(False, False)
self.expect_submit_enabled(True)
# Now reload the page:
self.pb_wrapper = self.reload_page()
self.wait_for_init()
# The first checkbox should be checked, and the second checkbox should be unchecked
# (since these are the values we submitted earlier);
# "Submit" should be disabled (to discourage submitting the same answer):
self.expect_checkboxes_checked(True, False)
self.expect_checkmarks_visible(True, True)
self.expect_submit_enabled(False)
class CompletionStepBlockTest(CompletionBlockTestMixin, MentoringAssessmentBaseTest):
"""
Tests for CompletionBlock inside a Step Builder block.
"""
def test_step_with_completion_block(self):
"""
Test a regular Step Builder block containing one completion block and an MCQ.
"""
step_builder, controls = self.load_assessment_scenario("completion_step.xml")
self.wait_for_init()
self.assertIsNone(self.completion_checkbox.get_attribute('checked'))
# Submit step 1 (the completion block step), and advance to next step:
question = self.expect_question_visible(1, step_builder, question_text="Attendance Check")
self.assertIn("Did you attend the meeting?", question.text) # Question
self.assertIn("Yes, I did.", question.text) # Answer
self.assertTrue(controls.submit.is_enabled())
self.assert_hidden(controls.try_again)
self.completion_checkbox.click()
self.expect_checkbox_checked(True)
controls.submit.click()
self.do_submit_wait(controls, last=False)
self.wait_until_clickable(controls.next_question)
controls.next_question.click()
# Submit step 2 (the MCQ step), and advance to review step:
question = self.expect_question_visible(2, step_builder)
GetChoices(question).select("Yes")
controls.submit.click()
self.do_submit_wait(controls, last=True)
self.wait_until_clickable(controls.review)
controls.review.click()
self.wait_until_visible(controls.try_again)
# You can't get a completion block question wrong, but it does count as one correct point by default:
self.assertIn("You answered 2 questions correctly", step_builder.text)
......@@ -45,28 +45,27 @@ class SliderBlockTest(SliderBlockTestMixins, ProblemBuilderBaseTest):
pb_wrapper = self.load_scenario("slider_problem.xml", {"include_mcq": False})
self.wait_for_init()
# The initial value should be 50 and submit should be enabled since 50 is a valid value:
self.assertTrue(self.submit_button.is_enabled())
self.assertEqual(self.get_slider_value(), 50)
self.expect_checkmark_visible(False)
self.expect_submit_enabled(True)
# Set the value to 75:
self.set_slider_value(75)
self.assertEqual(self.get_slider_value(), 75)
self.click_submit(pb_wrapper)
# Now, we expect submit to be disabled and the checkmark to be visible:
self.expect_checkmark_visible(True)
self.assertFalse(self.submit_button.is_enabled())
self.expect_submit_enabled(False)
# Now change the value, and the button/checkmark should reset:
self.set_slider_value(45)
self.assertTrue(self.submit_button.is_enabled())
self.expect_checkmark_visible(False)
self.expect_submit_enabled(True)
# Now reload the page:
self.browser.execute_script("$(document).html(' ');")
pb_wrapper = self.go_to_view("student_view")
pb_wrapper = self.reload_page()
self.wait_for_init()
# Now the initial value should be 75 and submit should be disabled (to discourage submitting the same answer):
self.assertEqual(self.get_slider_value(), 75)
self.assertFalse(self.submit_button.is_enabled())
self.expect_checkmark_visible(True)
self.expect_submit_enabled(False)
def test_simple_flow_with_peer(self):
""" Test a regular Problem Builder block containing one slider and an MCQ """
......@@ -74,35 +73,23 @@ class SliderBlockTest(SliderBlockTestMixins, ProblemBuilderBaseTest):
self.wait_for_init()
# The initial value should be 50 and submit should be disabled until an MCQ choice is selected
self.assertEqual(self.get_slider_value(), 50)
self.assertFalse(self.submit_button.is_enabled())
self.expect_checkmark_visible(False)
self.expect_submit_enabled(False)
# Set the value to 15:
self.set_slider_value(15)
self.assertEqual(self.get_slider_value(), 15)
self.assertFalse(self.submit_button.is_enabled())
self.expect_submit_enabled(False)
# Choose a choice:
GetChoices(pb_wrapper).select('Yes')
self.assertTrue(self.submit_button.is_enabled())
self.expect_submit_enabled(True)
self.click_submit(pb_wrapper)
# Now, we expect submit to be disabled and the checkmark to be visible:
self.expect_checkmark_visible(True)
self.assertFalse(self.submit_button.is_enabled())
self.expect_submit_enabled(False)
# Now change the value, and the button/checkmark should reset:
self.set_slider_value(20)
self.assertTrue(self.submit_button.is_enabled())
self.expect_checkmark_visible(False)
def wait_for_init(self):
""" Wait for the scenario to initialize """
self.wait_until_hidden(self.browser.find_element_by_css_selector('.messages'))
@property
def submit_button(self):
return self.browser.find_element_by_css_selector('.submit input.input-main')
def expect_checkmark_visible(self, visible):
checkmark = self.browser.find_element_by_css_selector('.xblock-pb-slider .submit-result')
self.assertEqual(checkmark.is_displayed(), visible)
self.expect_submit_enabled(True)
class SliderStepBlockTest(SliderBlockTestMixins, MentoringAssessmentBaseTest):
......@@ -140,7 +127,3 @@ class SliderStepBlockTest(SliderBlockTestMixins, MentoringAssessmentBaseTest):
self.wait_until_visible(controls.try_again)
# You can't get a slider question wrong, but it does count as one correct point by default:
self.assertIn("You answered 2 questions correctly", step_builder.text)
def wait_for_init(self):
""" Wait for the scenario to initialize """
self.wait_until_visible(self.browser.find_elements_by_css_selector('.sb-step')[0])
<problem-builder url_name="pb_with_multiple_completion">
<pb-completion name="completion_1" question="Did you attend the meeting?" answer="Yes, I did." />
<pb-completion name="completion_2" question="Did you attend the social gathering?" answer="Yup, I was there." />
</problem-builder>
<problem-builder url_name="pb_with_completion">
<pb-completion name="completion_1" question="Did you attend the meeting?" answer="Yes, I did." />
{% if include_mcq %}
<pb-mcq name="mcq_2" question="Do you like this MCQ?" correct_choices='["yes"]'>
<pb-choice value="yes">Yes</pb-choice>
<pb-choice value="maybenot">Maybe not</pb-choice>
<pb-choice value="understand">I don't understand</pb-choice>
</pb-mcq>
{% endif %}
</problem-builder>
<step-builder display_name="Step Builder">
<sb-step display_name="Completion step">
<pb-completion name="completion_1" display_name="Attendance Check" question="Did you attend the meeting?" answer="Yes, I did."/>
</sb-step>
<sb-step display_name="MCQ step">
<pb-mcq name="mcq" display_name="Question 2" question="Do you like this MCQ?" correct_choices='["yes"]'>
<pb-choice value="yes">Yes</pb-choice>
<pb-choice value="no">No</pb-choice>
</pb-mcq>
</sb-step>
<sb-review-step>
<sb-review-score/>
</sb-review-step>
</step-builder>
......@@ -9,9 +9,11 @@ from xblock.field_data import DictFieldData
from problem_builder.mcq import MCQBlock
from problem_builder.mentoring import MentoringBlock, MentoringMessageBlock, _default_options_config
from .utils import BlockWithChildrenTestMixin
@ddt.ddt
class TestMentoringBlock(unittest.TestCase):
class TestMentoringBlock(BlockWithChildrenTestMixin, unittest.TestCase):
def test_sends_progress_event_when_rendered_student_view_with_display_submit_false(self):
block = MentoringBlock(MagicMock(), DictFieldData({
'display_submit': False
......@@ -87,6 +89,26 @@ class TestMentoringBlock(unittest.TestCase):
_, _, show_message = block._get_standard_results()
self.assertEqual(show_message, expected_show_message)
def test_allowed_nested_blocks(self):
block = MentoringBlock(Mock(), DictFieldData({}), Mock())
self.assert_allowed_nested_blocks(block, message_blocks=[
'pb-message', # Message type: "completed"
'pb-message', # Message type: "incomplete"
'pb-message', # Message type: "max_attempts_reached"
] +
(['pb-message'] if block.is_assessment else []) # Message type: "on-assessment-review"
)
def test_allowed_nested_blocks_assessment(self):
block = MentoringBlock(Mock(), DictFieldData({'mode': 'assessment'}), Mock())
self.assert_allowed_nested_blocks(block, message_blocks=[
'pb-message', # Message type: "completed"
'pb-message', # Message type: "incomplete"
'pb-message', # Message type: "max_attempts_reached"
] +
(['pb-message'] if block.is_assessment else []) # Message type: "on-assessment-review"
)
@ddt.ddt
class TestMentoringBlockTheming(unittest.TestCase):
......
import unittest
from mock import Mock
from xblock.field_data import DictFieldData
from problem_builder.step import MentoringStepBlock
from problem_builder.mixins import QuestionMixin, StepParentMixin
from mock import Mock, patch
from problem_builder.step import MentoringStepBlock
from .utils import BlockWithChildrenTestMixin
class Parent(StepParentMixin):
......@@ -96,51 +99,8 @@ class TestQuestionMixin(unittest.TestCase):
self.assertFalse(step2.lonely_child)
class TestMentoringStep(unittest.TestCase):
def get_allowed_blocks(self, block):
return [
getattr(allowed_block, 'category', getattr(allowed_block, 'CATEGORY', None))
for allowed_block in block.allowed_nested_blocks
]
class TestMentoringStep(BlockWithChildrenTestMixin, unittest.TestCase):
def test_allowed_nested_blocks(self):
block = MentoringStepBlock(Mock(), DictFieldData({}), Mock())
self.assertEqual(
self.get_allowed_blocks(block),
[
'pb-answer',
'pb-mcq',
'pb-rating',
'pb-mrq',
'html',
'pb-answer-recap',
'pb-table',
'sb-plot',
'pb-slider',
]
)
from sys import modules
xmodule_mock = Mock()
fake_modules = {
'xmodule': xmodule_mock,
'xmodule.video_module': xmodule_mock.video_module,
'xmodule.video_module.video_module': xmodule_mock.video_module.video_module,
'imagemodal': Mock()
}
with patch.dict(modules, fake_modules):
self.assertEqual(
self.get_allowed_blocks(block), [
'pb-answer',
'pb-mcq',
'pb-rating',
'pb-mrq',
'html',
'pb-answer-recap',
'pb-table',
'sb-plot',
'pb-slider',
'video',
'imagemodal',
]
)
self.assert_allowed_nested_blocks(block)
"""
Helper methods for testing Problem Builder / Step Builder blocks
"""
from mock import MagicMock, Mock
from mock import MagicMock, Mock, patch
from xblock.field_data import DictFieldData
......@@ -20,6 +20,58 @@ class ScoresTestMixin(object):
self.assertIsInstance(block.max_score(), (int, float))
class BlockWithChildrenTestMixin(object):
"""
Mixin for tests targeting blocks that contain nested child blocks.
"""
ALLOWED_NESTED_BLOCKS = [
'pb-answer',
'pb-mcq',
'pb-rating',
'pb-mrq',
'pb-completion',
'html',
'pb-answer-recap',
'pb-table',
'sb-plot',
'pb-slider',
]
ADDITIONAL_BLOCKS = [
'video',
'imagemodal',
]
def get_allowed_blocks(self, block):
"""
Return list of categories corresponding to child blocks allowed for `block`.
"""
return [
getattr(allowed_block, 'category', getattr(allowed_block, 'CATEGORY', None))
for allowed_block in block.allowed_nested_blocks
]
def assert_allowed_nested_blocks(self, block, message_blocks=[]):
self.assertEqual(
self.get_allowed_blocks(block),
self.ALLOWED_NESTED_BLOCKS + message_blocks
)
from sys import modules
xmodule_mock = Mock()
fake_modules = {
'xmodule': xmodule_mock,
'xmodule.video_module': xmodule_mock.video_module,
'xmodule.video_module.video_module': xmodule_mock.video_module.video_module,
'imagemodal': Mock()
}
with patch.dict(modules, fake_modules):
self.assertEqual(
self.get_allowed_blocks(block),
self.ALLOWED_NESTED_BLOCKS + self.ADDITIONAL_BLOCKS + message_blocks
)
def instantiate_block(cls, fields=None):
"""
Instantiate the given XBlock in a mock runtime.
......
......@@ -59,6 +59,7 @@ BLOCKS = [
'pb-rating = problem_builder.mcq:RatingBlock',
'pb-mrq = problem_builder.mrq:MRQBlock',
'pb-slider = problem_builder.slider:SliderBlock',
'pb-completion = problem_builder.completion:CompletionBlock',
'pb-message = problem_builder.message:MentoringMessageBlock',
'pb-tip = problem_builder.tip:TipBlock',
'pb-choice = problem_builder.choice:ChoiceBlock',
......
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