diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py index 9bfd455375..76ac5f2dfe 100644 --- a/common/lib/xmodule/setup.py +++ b/common/lib/xmodule/setup.py @@ -24,7 +24,6 @@ XMODULES = [ "videosequence = xmodule.seq_module:SequenceDescriptor", "custom_tag_template = xmodule.raw_module:RawDescriptor", "annotatable = xmodule.annotatable_module:AnnotatableDescriptor", - "word_cloud = xmodule.word_cloud_module:WordCloudDescriptor", "hidden = xmodule.hidden_module:HiddenDescriptor", "raw = xmodule.raw_module:RawDescriptor", "lti = xmodule.lti_module:LTIDescriptor", @@ -40,6 +39,7 @@ XBLOCKS = [ "vertical = xmodule.vertical_block:VerticalBlock", "video = xmodule.video_module:VideoBlock", "videoalpha = xmodule.video_module:VideoBlock", + "word_cloud = xmodule.word_cloud_module:WordCloudBlock", "wrapper = xmodule.wrapper_module:WrapperBlock", ] XBLOCKS_ASIDES = [ diff --git a/common/lib/xmodule/xmodule/static_content.py b/common/lib/xmodule/xmodule/static_content.py index 85855f831c..b3806711af 100755 --- a/common/lib/xmodule/xmodule/static_content.py +++ b/common/lib/xmodule/xmodule/static_content.py @@ -22,6 +22,7 @@ from path import Path as path from xmodule.capa_module import ProblemBlock from xmodule.html_module import AboutBlock, CourseInfoBlock, HtmlBlock, StaticTabBlock +from xmodule.word_cloud_module import WordCloudBlock from xmodule.x_module import XModuleDescriptor, HTMLSnippet LOG = logging.getLogger(__name__) @@ -69,6 +70,7 @@ XBLOCK_CLASSES = [ ProblemBlock, StaticTabBlock, VideoBlock, + WordCloudBlock, ] diff --git a/common/lib/xmodule/xmodule/tests/test_word_cloud.py b/common/lib/xmodule/xmodule/tests/test_word_cloud.py index 527699c348..835815a215 100644 --- a/common/lib/xmodule/xmodule/tests/test_word_cloud.py +++ b/common/lib/xmodule/xmodule/tests/test_word_cloud.py @@ -1,35 +1,89 @@ # -*- coding: utf-8 -*- """Test for Word cloud Xmodule functional logic.""" +import json +from django.test import TestCase +from fs.memoryfs import MemoryFS +from lxml import etree +from mock import Mock +from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator from webob.multidict import MultiDict +from xblock.field_data import DictFieldData -from xmodule.word_cloud_module import WordCloudDescriptor - -from . import LogicTest +from xmodule.word_cloud_module import WordCloudBlock +from . import get_test_descriptor_system, get_test_system -class WordCloudModuleTest(LogicTest): - """Logic tests for Word Cloud Xmodule.""" - descriptor_class = WordCloudDescriptor +class WordCloudBlockTest(TestCase): + """ + Logic tests for Word Cloud XBlock. + """ + raw_field_data = { 'all_words': {'cat': 10, 'dog': 5, 'mom': 1, 'dad': 2}, 'top_words': {'cat': 10, 'dog': 5, 'dad': 2}, 'submitted': False } + def test_xml_import_export_cycle(self): + """ + Test the import export cycle. + """ + + runtime = get_test_descriptor_system() + runtime.export_fs = MemoryFS() + + original_xml = ( + '\n' + ) + + olx_element = etree.fromstring(original_xml) + id_generator = Mock() + block = WordCloudBlock.parse_xml(olx_element, runtime, None, id_generator) + block.location = BlockUsageLocator( + CourseLocator('org', 'course', 'run', branch='revision'), 'word_cloud', 'block_id' + ) + + self.assertEqual(block.display_name, 'Favorite Fruits') + self.assertFalse(block.display_student_percents) + self.assertEqual(block.instructions, 'What are your favorite fruits?') + self.assertEqual(block.num_inputs, 3) + self.assertEqual(block.num_top_words, 100) + + node = etree.Element("unknown_root") + # This will export the olx to a separate file. + block.add_xml_to_node(node) + with runtime.export_fs.open(u'word_cloud/block_id.xml') as f: + exported_xml = f.read() + + self.assertEqual(exported_xml, original_xml) + def test_bad_ajax_request(self): - "Make sure that answer for incorrect request is error json" - response = self.ajax_request('bad_dispatch', {}) + """ + Make sure that answer for incorrect request is error json. + """ + + module_system = get_test_system() + block = WordCloudBlock(module_system, DictFieldData(self.raw_field_data), Mock()) + + response = json.loads(block.handle_ajax('bad_dispatch', {})) self.assertDictEqual(response, { 'status': 'fail', 'error': 'Unknown Command!' }) def test_good_ajax_request(self): - "Make sure that ajax request works correctly" + """ + Make sure that ajax request works correctly. + """ + + module_system = get_test_system() + block = WordCloudBlock(module_system, DictFieldData(self.raw_field_data), Mock()) + post_data = MultiDict(('student_words[]', word) for word in ['cat', 'cat', 'dog', 'sun']) - response = self.ajax_request('submit', post_data) + response = json.loads(block.handle_ajax('submit', post_data)) self.assertEqual(response['status'], 'success') self.assertEqual(response['submitted'], True) self.assertEqual(response['total_count'], 22) diff --git a/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py b/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py index 29c5806aaa..12cdaecb75 100644 --- a/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py +++ b/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py @@ -39,7 +39,7 @@ from xmodule.randomize_module import RandomizeDescriptor from xmodule.seq_module import SequenceDescriptor from xmodule.tests import get_test_descriptor_system, get_test_system from xmodule.vertical_block import VerticalBlock -from xmodule.word_cloud_module import WordCloudDescriptor +from xmodule.word_cloud_module import WordCloudBlock from xmodule.wrapper_module import WrapperBlock from xmodule.x_module import ( PUBLIC_VIEW, @@ -58,7 +58,7 @@ LEAF_XMODULES = { AnnotatableDescriptor: [{}], HtmlBlock: [{}], PollDescriptor: [{'display_name': 'Poll Display Name'}], - WordCloudDescriptor: [{}], + WordCloudBlock: [{}], } @@ -295,7 +295,8 @@ class XBlockWrapperTestMixin(object): # pylint: disable=no-member descriptor.runtime.id_reader.get_definition_id = Mock(return_value='a') descriptor.runtime.modulestore = modulestore - descriptor._xmodule.graded = 'False' + if hasattr(descriptor, '_xmodule'): + descriptor._xmodule.graded = 'False' self.check_property(descriptor) # Test that when an xmodule is generated from descriptor_cls diff --git a/common/lib/xmodule/xmodule/word_cloud_module.py b/common/lib/xmodule/xmodule/word_cloud_module.py index 920232e608..8f23c84a8a 100644 --- a/common/lib/xmodule/xmodule/word_cloud_module.py +++ b/common/lib/xmodule/xmodule/word_cloud_module.py @@ -16,10 +16,18 @@ import six from six.moves import map from web_fragments.fragment import Fragment from xblock.fields import Boolean, Dict, Integer, List, Scope, String -from xmodule.editing_module import MetadataOnlyEditingDescriptor -from xmodule.raw_module import EmptyDataRawDescriptor -from xmodule.x_module import XModule - +from xmodule.editing_module import EditingMixin +from xmodule.raw_module import EmptyDataRawMixin +from xmodule.util.xmodule_django import add_webpack_to_fragment +from xmodule.xml_module import XmlMixin +from xmodule.x_module import ( + HTMLSnippet, + ResourceTemplates, + shim_xmodule_js, + XModuleMixin, + XModuleDescriptorToXBlockMixin, + XModuleToXBlockMixin, +) log = logging.getLogger(__name__) # Make '_' a no-op so we can scrape strings. Using lambda instead of @@ -37,8 +45,20 @@ def pretty_bool(value): return value in bool_dict -class WordCloudFields(object): - """XFields for word cloud.""" +class WordCloudBlock( # pylint: disable=abstract-method + EmptyDataRawMixin, + XmlMixin, + EditingMixin, + XModuleDescriptorToXBlockMixin, + XModuleToXBlockMixin, + HTMLSnippet, + ResourceTemplates, + XModuleMixin, +): + """ + Word Cloud XBlock. + """ + display_name = String( display_name=_("Display Name"), help=_("The display name for this component."), @@ -91,12 +111,32 @@ class WordCloudFields(object): scope=Scope.user_state_summary ) + resources_dir = 'assets/word_cloud' + template_dir_name = 'word_cloud' -class WordCloudModule(WordCloudFields, XModule): - """WordCloud Xmodule""" - js = {'js': [resource_string(__name__, 'assets/word_cloud/src/js/word_cloud.js')]} - css = {'scss': [resource_string(__name__, 'css/word_cloud/display.scss')]} - js_module_name = "WordCloud" + preview_view_js = { + 'js': [ + resource_string(__name__, 'assets/word_cloud/src/js/word_cloud.js'), + ], + 'xmodule_js': resource_string(__name__, 'js/src/xmodule.js'), + } + preview_view_css = { + 'scss': [ + resource_string(__name__, 'css/word_cloud/display.scss'), + ], + } + + studio_view_js = { + 'js': [ + resource_string(__name__, 'js/src/raw/edit/metadata-only.js'), + ], + 'xmodule_js': resource_string(__name__, 'js/src/xmodule.js'), + } + studio_view_css = { + 'scss': [], + } + studio_js_module_name = "MetadataOnlyEditingDescriptor" + mako_template = "widgets/metadata-only-edit.html" def get_state(self): """Return success json answer for client.""" @@ -239,9 +279,8 @@ class WordCloudModule(WordCloudFields, XModule): Renders the output that a student will see. """ fragment = Fragment() - fragment.add_content(self.system.render_template('word_cloud.html', { - 'ajax_url': self.system.ajax_url, + 'ajax_url': self.ajax_url, 'display_name': self.display_name, 'instructions': self.instructions, 'element_class': self.location.block_type, @@ -249,6 +288,8 @@ class WordCloudModule(WordCloudFields, XModule): 'num_inputs': self.num_inputs, 'submitted': self.submitted, })) + add_webpack_to_fragment(fragment, 'WordCloudBlockPreview') + shim_xmodule_js(fragment, 'WordCloud') return fragment @@ -258,9 +299,13 @@ class WordCloudModule(WordCloudFields, XModule): """ return self.student_view(context) - -class WordCloudDescriptor(WordCloudFields, MetadataOnlyEditingDescriptor, EmptyDataRawDescriptor): - """Descriptor for WordCloud Xmodule.""" - module_class = WordCloudModule - resources_dir = 'assets/word_cloud' - template_dir_name = 'word_cloud' + def studio_view(self, _context): + """ + Return the studio view. + """ + fragment = Fragment( + self.system.render_template(self.mako_template, self.get_context()) + ) + add_webpack_to_fragment(fragment, 'WordCloudBlockStudio') + shim_xmodule_js(fragment, self.studio_js_module_name) + return fragment diff --git a/lms/djangoapps/courseware/tests/test_word_cloud.py b/lms/djangoapps/courseware/tests/test_word_cloud.py index 57238a144e..1fdae9fec7 100644 --- a/lms/djangoapps/courseware/tests/test_word_cloud.py +++ b/lms/djangoapps/courseware/tests/test_word_cloud.py @@ -255,7 +255,7 @@ class TestWordCloud(BaseTestXmodule): """ fragment = self.runtime.render(self.item_descriptor, STUDENT_VIEW) expected_context = { - 'ajax_url': self.item_descriptor.xmodule_runtime.ajax_url, + 'ajax_url': self.item_descriptor.ajax_url, 'display_name': self.item_descriptor.display_name, 'instructions': self.item_descriptor.instructions, 'element_class': self.item_descriptor.location.block_type,