Commit 2314c609 by brianhw

Merge pull request #1859 from MITx/fix/brian/conditional-error

cope with ErrorModule as source (or required) module, and add tests.
parents 976b7192 7486e0f6
...@@ -92,9 +92,13 @@ class ConditionalModule(ConditionalFields, XModule): ...@@ -92,9 +92,13 @@ class ConditionalModule(ConditionalFields, XModule):
if xml_value and self.required_modules: if xml_value and self.required_modules:
for module in self.required_modules: for module in self.required_modules:
if not hasattr(module, attr_name): if not hasattr(module, attr_name):
raise Exception('Error in conditional module: \ # We don't throw an exception here because it is possible for
required module {module} has no {module_attr}'.format( # the descriptor of a required module to have a property but
module=module, module_attr=attr_name)) # for the resulting module to be a (flavor of) ErrorModule.
# So just log and return false.
log.warn('Error in conditional module: \
required module {module} has no {module_attr}'.format(module=module, module_attr=attr_name))
return False
attr = getattr(module, attr_name) attr = getattr(module, attr_name)
if callable(attr): if callable(attr):
...@@ -137,16 +141,15 @@ class ConditionalModule(ConditionalFields, XModule): ...@@ -137,16 +141,15 @@ class ConditionalModule(ConditionalFields, XModule):
def get_icon_class(self): def get_icon_class(self):
new_class = 'other' new_class = 'other'
if self.is_condition_satisfied(): # HACK: This shouldn't be hard-coded to two types
# HACK: This shouldn't be hard-coded to two types # OBSOLETE: This obsoletes 'type'
# OBSOLETE: This obsoletes 'type' class_priority = ['video', 'problem']
class_priority = ['video', 'problem']
child_classes = [self.system.get_module(child_descriptor).get_icon_class()
child_classes = [self.system.get_module(child_descriptor).get_icon_class() for child_descriptor in self.descriptor.get_children()]
for child_descriptor in self.descriptor.get_children()] for c in class_priority:
for c in class_priority: if c in child_classes:
if c in child_classes: new_class = c
new_class = c
return new_class return new_class
......
import json import json
from path import path
import unittest import unittest
from fs.memoryfs import MemoryFS from fs.memoryfs import MemoryFS
from ast import literal_eval
from lxml import etree
from mock import Mock, patch from mock import Mock, patch
from collections import defaultdict
from xmodule.x_module import XMLParsingSystem, XModuleDescriptor from xmodule.error_module import NonStaffErrorDescriptor
from xmodule.xml_module import is_pointer_tag
from xmodule.errortracker import make_error_tracker
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.xml import ImportSystem, XMLModuleStore from xmodule.modulestore.xml import ImportSystem, XMLModuleStore
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.conditional_module import ConditionalModule
from .test_export import DATA_DIR from xmodule.tests.test_export import DATA_DIR
ORG = 'test_org' ORG = 'test_org'
COURSE = 'conditional' # name of directory with course data COURSE = 'conditional' # name of directory with course data
from . import test_system from . import test_system
...@@ -47,10 +43,118 @@ class DummySystem(ImportSystem): ...@@ -47,10 +43,118 @@ class DummySystem(ImportSystem):
def render_template(self, template, context): def render_template(self, template, context):
raise Exception("Shouldn't be called") raise Exception("Shouldn't be called")
class ConditionalFactory(object):
"""
A helper class to create a conditional module and associated source and child modules
to allow for testing.
"""
@staticmethod
def create(system, source_is_error_module=False):
"""
return a dict of modules: the conditional with a single source and a single child.
Keys are 'cond_module', 'source_module', and 'child_module'.
if the source_is_error_module flag is set, create a real ErrorModule for the source.
"""
# construct source descriptor and module:
source_location = Location(["i4x", "edX", "conditional_test", "problem", "SampleProblem"])
if source_is_error_module:
# Make an error descriptor and module
source_descriptor = NonStaffErrorDescriptor.from_xml('some random xml data',
system,
org=source_location.org,
course=source_location.course,
error_msg='random error message')
source_module = source_descriptor.xmodule(system)
else:
source_descriptor = Mock()
source_descriptor.location = source_location
source_module = Mock()
# construct other descriptors:
child_descriptor = Mock()
cond_descriptor = Mock()
cond_descriptor.get_required_module_descriptors = lambda: [source_descriptor, ]
cond_descriptor.get_children = lambda: [child_descriptor, ]
cond_descriptor.xml_attributes = {"attempted": "true"}
# create child module:
child_module = Mock()
child_module.get_html = lambda: '<p>This is a secret</p>'
child_module.displayable_items = lambda: [child_module]
module_map = {source_descriptor: source_module, child_descriptor: child_module}
system.get_module = lambda descriptor: module_map[descriptor]
# construct conditional module:
cond_location = Location(["i4x", "edX", "conditional_test", "conditional", "SampleConditional"])
model_data = {'data': '<conditional/>'}
cond_module = ConditionalModule(system, cond_location, cond_descriptor, model_data)
# return dict:
return {'cond_module': cond_module,
'source_module': source_module,
'child_module': child_module }
class ConditionalModuleBasicTest(unittest.TestCase):
"""
Make sure that conditional module works, using mocks for
other modules.
"""
def setUp(self):
self.test_system = test_system()
def test_icon_class(self):
'''verify that get_icon_class works independent of condition satisfaction'''
modules = ConditionalFactory.create(self.test_system)
for attempted in ["false", "true"]:
for icon_class in [ 'other', 'problem', 'video']:
modules['source_module'].is_attempted = attempted
modules['child_module'].get_icon_class = lambda: icon_class
self.assertEqual(modules['cond_module'].get_icon_class(), icon_class)
def test_get_html(self):
modules = ConditionalFactory.create(self.test_system)
# because test_system returns the repr of the context dict passed to render_template,
# we reverse it here
html = modules['cond_module'].get_html()
html_dict = literal_eval(html)
self.assertEqual(html_dict['element_id'], 'i4x-edX-conditional_test-conditional-SampleConditional')
self.assertEqual(html_dict['id'], 'i4x://edX/conditional_test/conditional/SampleConditional')
self.assertEqual(html_dict['depends'], 'i4x-edX-conditional_test-problem-SampleProblem')
def test_handle_ajax(self):
modules = ConditionalFactory.create(self.test_system)
modules['source_module'].is_attempted = "false"
ajax = json.loads(modules['cond_module'].handle_ajax('', ''))
print "ajax: ", ajax
html = ajax['html']
self.assertFalse(any(['This is a secret' in item for item in html]))
# now change state of the capa problem to make it completed
modules['source_module'].is_attempted = "true"
ajax = json.loads(modules['cond_module'].handle_ajax('', ''))
print "post-attempt ajax: ", ajax
html = ajax['html']
self.assertTrue(any(['This is a secret' in item for item in html]))
def test_error_as_source(self):
'''
Check that handle_ajax works properly if the source is really an ErrorModule,
and that the condition is not satisfied.
'''
modules = ConditionalFactory.create(self.test_system, source_is_error_module=True)
ajax = json.loads(modules['cond_module'].handle_ajax('', ''))
html = ajax['html']
self.assertFalse(any(['This is a secret' in item for item in html]))
class ConditionalModuleTest(unittest.TestCase):
class ConditionalModuleXmlTest(unittest.TestCase):
"""
Make sure ConditionalModule works, by loading data in from an XML-defined course.
"""
@staticmethod @staticmethod
def get_system(load_error_modules=True): def get_system(load_error_modules=True):
'''Get a dummy system''' '''Get a dummy system'''
...@@ -106,7 +210,7 @@ class ConditionalModuleTest(unittest.TestCase): ...@@ -106,7 +210,7 @@ class ConditionalModuleTest(unittest.TestCase):
html_expect = "{'ajax_url': 'courses/course_id/modx/a_location', 'element_id': 'i4x-HarvardX-ER22x-conditional-condone', 'id': 'i4x://HarvardX/ER22x/conditional/condone', 'depends': 'i4x-HarvardX-ER22x-problem-choiceprob'}" html_expect = "{'ajax_url': 'courses/course_id/modx/a_location', 'element_id': 'i4x-HarvardX-ER22x-conditional-condone', 'id': 'i4x://HarvardX/ER22x/conditional/condone', 'depends': 'i4x-HarvardX-ER22x-problem-choiceprob'}"
self.assertEqual(html, html_expect) self.assertEqual(html, html_expect)
gdi = module.get_display_items() gdi = module.get_display_items()
print "gdi=", gdi print "gdi=", gdi
ajax = json.loads(module.handle_ajax('', '')) ajax = json.loads(module.handle_ajax('', ''))
...@@ -121,3 +225,4 @@ class ConditionalModuleTest(unittest.TestCase): ...@@ -121,3 +225,4 @@ class ConditionalModuleTest(unittest.TestCase):
print "post-attempt ajax: ", ajax print "post-attempt ajax: ", ajax
html = ajax['html'] html = ajax['html']
self.assertTrue(any(['This is a secret' in item for item in html])) self.assertTrue(any(['This is a secret' in item for item in html]))
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