From 35e7a7338c12eb3be68efa0d8bb9fd77f38d86e4 Mon Sep 17 00:00:00 2001 From: Marko Jevtic Date: Thu, 13 Aug 2015 12:46:48 +0000 Subject: [PATCH] Addresses design review feedback --- common/lib/xmodule/xmodule/capa_module.py | 16 +- common/lib/xmodule/xmodule/html_module.py | 14 +- .../xmodule/xmodule/tests/test_capa_module.py | 682 ++++++++++++++++++ .../xmodule/xmodule/tests/test_html_module.py | 26 +- 4 files changed, 734 insertions(+), 4 deletions(-) diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index be5b394fb0..7af5bac68e 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -196,8 +196,20 @@ class CapaDescriptor(CapaFields, RawDescriptor): Return dictionary prepared with module content and type for indexing. """ xblock_body = super(CapaDescriptor, self).index_dictionary() - # Removing solution - capa_content = re.sub(re.compile(r".*", re.DOTALL), "", self.data) + # Removing solutions and hints, as well as script and style + capa_content = re.sub( + re.compile( + r""" + .*? | + | + | + <[a-z]*hint.*?>.*? + """, + re.DOTALL | + re.VERBOSE), + "", + self.data + ) # Removing HTML-encoded non-breaking space characters capa_content = re.sub(r"(\s| |//)+", " ", html_to_text(capa_content)) # Removing HTML CDATA diff --git a/common/lib/xmodule/xmodule/html_module.py b/common/lib/xmodule/xmodule/html_module.py index bfcbb49022..49d54825f2 100644 --- a/common/lib/xmodule/xmodule/html_module.py +++ b/common/lib/xmodule/xmodule/html_module.py @@ -275,8 +275,20 @@ class HtmlDescriptor(HtmlFields, XmlDescriptor, EditingDescriptor): # pylint: d def index_dictionary(self): xblock_body = super(HtmlDescriptor, self).index_dictionary() + # Removing script and style + html_content = re.sub( + re.compile( + r""" + | + + """, + re.DOTALL | + re.VERBOSE), + "", + self.data + ) # Removing HTML-encoded non-breaking space characters - html_content = re.sub(r"(\s| |//)+", " ", html_to_text(self.data)) + html_content = re.sub(r"(\s| |//)+", " ", html_to_text(html_content)) # Removing HTML CDATA html_content = re.sub(r"", "", html_content) # Removing HTML comments diff --git a/common/lib/xmodule/xmodule/tests/test_capa_module.py b/common/lib/xmodule/xmodule/tests/test_capa_module.py index 9a775697a6..fc7b8af2c3 100644 --- a/common/lib/xmodule/xmodule/tests/test_capa_module.py +++ b/common/lib/xmodule/xmodule/tests/test_capa_module.py @@ -1707,6 +1707,363 @@ class CapaModuleTest(unittest.TestCase): @ddt.ddt class CapaDescriptorTest(unittest.TestCase): + + sample_checkbox_problem_xml = textwrap.dedent(""" + +

Title

+ +

Description

+ +

Example

+ +

The following languages are in the Indo-European family:

+ + + Urdu + Finnish + Marathi + French + Hungarian + + + +

Note: Make sure you select all of the correct options—there may be more than one!

+ + +
+

Explanation

+ +

Solution for CAPA problem

+ +
+
+ +
+ """) + + sample_dropdown_problem_xml = textwrap.dedent(""" + +

Dropdown problems allow learners to select only one option from a list of options.

+ +

Description

+ +

You can use the following example problem as a model.

+ +

Which of the following countries celebrates its independence on August 15?

+ + + + + + + +
+

Explanation

+ +

India became an independent nation on August 15, 1947.

+ +
+
+ +
+ """) + + sample_multichoice_problem_xml = textwrap.dedent(""" + +

Multiple choice problems allow learners to select only one option.

+ +

When you add the problem, be sure to select Settings to specify a Display Name and other values.

+ +

You can use the following example problem as a model.

+ +

Which of the following countries has the largest population?

+ + + Brazil + timely feedback -- explain why an almost correct answer is wrong + + Germany + Indonesia + Russia + + + + +
+

Explanation

+ +

According to September 2014 estimates:

+

The population of Indonesia is approximately 250 million.

+

The population of Brazil is approximately 200 million.

+

The population of Russia is approximately 146 million.

+

The population of Germany is approximately 81 million.

+ +
+
+ +
+ """) + + sample_numerical_input_problem_xml = textwrap.dedent(""" + +

In a numerical input problem, learners enter numbers or a specific and relatively simple mathematical + expression. Learners enter the response in plain text, and the system then converts the text to a symbolic + expression that learners can see below the response field.

+ +

The system can handle several types of characters, including basic operators, fractions, exponents, and + common constants such as "i". You can refer learners to "Entering Mathematical and Scientific Expressions" + in the edX Guide for Students for more information.

+ +

When you add the problem, be sure to select Settings to specify a Display Name and other values that + apply.

+ +

You can use the following example problems as models.

+ +

How many miles away from Earth is the sun? Use scientific notation to answer.

+ + + + + +

The square of what number is -100?

+ + + + + + +
+

Explanation

+ +

The sun is 93,000,000, or 9.3*10^7, miles away from Earth.

+

-100 is the square of 10 times the imaginary number, i.

+ +
+
+ +
+ """) + + sample_text_input_problem_xml = textwrap.dedent(""" + +

In text input problems, also known as "fill-in-the-blank" problems, learners enter text into a response + field. The text can include letters and characters such as punctuation marks. The text that the learner + enters must match your specified answer text exactly. You can specify more than one correct answer. + Learners must enter a response that matches one of the correct answers exactly.

+ +

When you add the problem, be sure to select Settings to specify a Display Name and other values that + apply.

+ +

You can use the following example problem as a model.

+ +

What was the first post-secondary school in China to allow both male and female students?

+ + + + + + + + +
+

Explanation

+ +

Nanjing Higher Normal Institute first admitted female students in 1920.

+ +
+
+ +
+ """) + + sample_checkboxes_with_hints_and_feedback_problem_xml = textwrap.dedent(""" + +

You can provide feedback for each option in a checkbox problem, with distinct feedback depending on + whether or not the learner selects that option.

+ +

You can also provide compound feedback for a specific combination of answers. For example, if you have + three possible answers in the problem, you can configure specific feedback for when a learner selects each + combination of possible answers.

+ +

You can also add hints for learners.

+ +

Be sure to select Settings to specify a Display Name and other values that apply.

+ +

Use the following example problem as a model.

+ +

Which of the following is a fruit? Check all that apply.

+ + + apple + You are correct that an apple is a fruit because it is the fertilized + ovary that comes from an apple tree and contains seeds. + Remember that an apple is also a fruit. + pumpkin + You are correct that a pumpkin is a fruit because it is the fertilized + ovary of a squash plant and contains seeds. + Remember that a pumpkin is also a fruit. + potato + A potato is a vegetable, not a fruit, because it does not come from a + flower and does not contain seeds. + You are correct that a potato is a vegetable because it is an edible + part of a plant in tuber form. + tomato + You are correct that a tomato is a fruit because it is the fertilized + ovary of a tomato plant and contains seeds. + Many people mistakenly think a tomato is a vegetable. However, because + a tomato is the fertilized ovary of a tomato plant and contains seeds, it is a fruit. + + An apple, pumpkin, and tomato are all fruits as they all are fertilized + ovaries of a plant and contain seeds. + You are correct that an apple, pumpkin, and tomato are all fruits as they + all are fertilized ovaries of a plant and contain seeds. However, a potato is not a fruit as it is an + edible part of a plant in tuber form and is a vegetable. + + + + + + A fruit is the fertilized ovary from a flower. + A fruit contains seeds of the plant. + +
+ """) + + sample_dropdown_with_hints_and_feedback_problem_xml = textwrap.dedent(""" + +

You can provide feedback for each available option in a dropdown problem.

+ +

You can also add hints for learners.

+ +

Be sure to select Settings to specify a Display Name and other values that apply.

+ +

Use the following example problem as a model.

+ +

A/an ________ is a vegetable.

+ + + + + + + + + + + A fruit is the fertilized ovary from a flower. + A fruit contains seeds of the plant. + +
+ """) + + sample_multichoice_with_hints_and_feedback_problem_xml = textwrap.dedent(""" + +

You can provide feedback for each option in a multiple choice problem.

+ +

You can also add hints for learners.

+ +

Be sure to select Settings to specify a Display Name and other values that apply.

+ +

Use the following example problem as a model.

+ +

Which of the following is a vegetable?

+ + + apple An apple is the fertilized ovary that comes from an apple + tree and contains seeds, meaning it is a fruit. + pumpkin A pumpkin is the fertilized ovary of a squash plant and + contains seeds, meaning it is a fruit. + potato A potato is an edible part of a plant in tuber form and is a + vegetable. + tomato Many people mistakenly think a tomato is a vegetable. + However, because a tomato is the fertilized ovary of a tomato plant and contains seeds, it is a fruit. + + + + + + + A fruit is the fertilized ovary from a flower. + A fruit contains seeds of the plant. + +
+ """) + + sample_numerical_input_with_hints_and_feedback_problem_xml = textwrap.dedent(""" + +

You can provide feedback for correct answers in numerical input problems. You cannot provide feedback + for incorrect answers.

+ +

Use feedback for the correct answer to reinforce the process for arriving at the numerical value.

+ +

You can also add hints for learners.

+ +

Be sure to select Settings to specify a Display Name and other values that apply.

+ +

Use the following example problem as a model.

+ +

What is the arithmetic mean for the following set of numbers? (1, 5, 6, 3, 5)

+ + + + The mean for this set of numbers is 20 / 5, which equals 4. + + +
+

Explanation

+ +

The mean is calculated by summing the set of numbers and dividing by n. In this case: + (1 + 5 + 6 + 3 + 5) / 5 = 20 / 5 = 4.

+ +
+
+ + + The mean is calculated by summing the set of numbers and dividing by n. + n is the count of items in the set. + +
+ """) + + sample_text_input_with_hints_and_feedback_problem_xml = textwrap.dedent(""" + +

You can provide feedback for the correct answer in text input problems, as well as for specific + incorrect answers.

+ +

Use feedback on expected incorrect answers to address common misconceptions and to provide guidance on + how to arrive at the correct answer.

+ +

Be sure to select Settings to specify a Display Name and other values that apply.

+ +

Use the following example problem as a model.

+ +

Which U.S. state has the largest land area?

+ + + Alaska is 576,400 square miles, more than double the land area of the second largest state, + Texas. + While many people think Texas is the largest state, it is actually the + second largest, with 261,797 square miles. + California is the third largest state, with 155,959 square miles. + + + + + + Consider the square miles, not population. + Consider all 50 states, not just the continental United States. + +
+ """) + def _create_descriptor(self, xml, name=None): """ Creates a CapaDescriptor to run test against """ descriptor = CapaDescriptor(get_test_system(), scope_ids=1) @@ -1792,6 +2149,331 @@ class CapaDescriptorTest(unittest.TestCase): } ) + def test_solutions_not_indexed(self): + xml = textwrap.dedent(""" + + +
+

Explanation

+ +

This is what the 1st solution.

+ +
+
+ + +
+

Explanation

+ +

This is the 2nd solution.

+ +
+
+ + +
+ """) + name = "Blank Common Capa Problem" + descriptor = self._create_descriptor(xml, name=name) + self.assertEquals( + descriptor.index_dictionary(), { + 'content_type': CapaDescriptor.INDEX_CONTENT_TYPE, + 'problem_types': [], + 'content': { + 'display_name': name, + 'capa_content': ' ' + } + } + ) + + def test_indexing_checkboxes(self): + name = "Checkboxes" + descriptor = self._create_descriptor(self.sample_checkbox_problem_xml, name=name) + capa_content = textwrap.dedent(""" + Title + Description + Example + The following languages are in the Indo-European family: + Urdu + Finnish + Marathi + French + Hungarian + Note: Make sure you select all of the correct options—there may be more than one! + """) + self.assertEquals(descriptor.problem_types, {"choiceresponse"}) + self.assertEquals( + descriptor.index_dictionary(), { + 'content_type': CapaDescriptor.INDEX_CONTENT_TYPE, + 'problem_types': ["choiceresponse"], + 'content': { + 'display_name': name, + 'capa_content': capa_content.replace("\n", " ") + } + } + ) + + def test_indexing_dropdown(self): + name = "Dropdown" + descriptor = self._create_descriptor(self.sample_dropdown_problem_xml, name=name) + capa_content = textwrap.dedent(""" + Dropdown problems allow learners to select only one option from a list of options. + Description + You can use the following example problem as a model. + Which of the following countries celebrates its independence on August 15? + """) + self.assertEquals(descriptor.problem_types, {"optionresponse"}) + self.assertEquals( + descriptor.index_dictionary(), { + 'content_type': CapaDescriptor.INDEX_CONTENT_TYPE, + 'problem_types': ["optionresponse"], + 'content': { + 'display_name': name, + 'capa_content': capa_content.replace("\n", " ") + } + } + ) + + def test_indexing_multiple_choice(self): + name = "Multiple Choice" + descriptor = self._create_descriptor(self.sample_multichoice_problem_xml, name=name) + capa_content = textwrap.dedent(""" + Multiple choice problems allow learners to select only one option. + When you add the problem, be sure to select Settings to specify a Display Name and other values. + You can use the following example problem as a model. + Which of the following countries has the largest population? + Brazil + Germany + Indonesia + Russia + """) + self.assertEquals(descriptor.problem_types, {"multiplechoiceresponse"}) + self.assertEquals( + descriptor.index_dictionary(), { + 'content_type': CapaDescriptor.INDEX_CONTENT_TYPE, + 'problem_types': ["multiplechoiceresponse"], + 'content': { + 'display_name': name, + 'capa_content': capa_content.replace("\n", " ") + } + } + ) + + def test_indexing_numerical_input(self): + name = "Numerical Input" + descriptor = self._create_descriptor(self.sample_numerical_input_problem_xml, name=name) + capa_content = textwrap.dedent(""" + In a numerical input problem, learners enter numbers or a specific and relatively simple mathematical + expression. Learners enter the response in plain text, and the system then converts the text to a symbolic + expression that learners can see below the response field. + The system can handle several types of characters, including basic operators, fractions, exponents, and + common constants such as "i". You can refer learners to "Entering Mathematical and Scientific Expressions" + in the edX Guide for Students for more information. + When you add the problem, be sure to select Settings to specify a Display Name and other values that + apply. + You can use the following example problems as models. + How many miles away from Earth is the sun? Use scientific notation to answer. + The square of what number is -100? + """) + self.assertEquals(descriptor.problem_types, {"numericalresponse"}) + self.assertEquals( + descriptor.index_dictionary(), { + 'content_type': CapaDescriptor.INDEX_CONTENT_TYPE, + 'problem_types': ["numericalresponse"], + 'content': { + 'display_name': name, + 'capa_content': capa_content.replace("\n", " ") + } + } + ) + + def test_indexing_text_input(self): + name = "Text Input" + descriptor = self._create_descriptor(self.sample_text_input_problem_xml, name=name) + capa_content = textwrap.dedent(""" + In text input problems, also known as "fill-in-the-blank" problems, learners enter text into a response + field. The text can include letters and characters such as punctuation marks. The text that the learner + enters must match your specified answer text exactly. You can specify more than one correct answer. + Learners must enter a response that matches one of the correct answers exactly. + When you add the problem, be sure to select Settings to specify a Display Name and other values that + apply. + You can use the following example problem as a model. + What was the first post-secondary school in China to allow both male and female students? + """) + self.assertEquals(descriptor.problem_types, {"stringresponse"}) + self.assertEquals( + descriptor.index_dictionary(), { + 'content_type': CapaDescriptor.INDEX_CONTENT_TYPE, + 'problem_types': ["stringresponse"], + 'content': { + 'display_name': name, + 'capa_content': capa_content.replace("\n", " ") + } + } + ) + + def test_indexing_checkboxes_with_hints_and_feedback(self): + name = "Checkboxes with Hints and Feedback" + descriptor = self._create_descriptor(self.sample_checkboxes_with_hints_and_feedback_problem_xml, name=name) + capa_content = textwrap.dedent(""" + You can provide feedback for each option in a checkbox problem, with distinct feedback depending on + whether or not the learner selects that option. + You can also provide compound feedback for a specific combination of answers. For example, if you have + three possible answers in the problem, you can configure specific feedback for when a learner selects each + combination of possible answers. + You can also add hints for learners. + Be sure to select Settings to specify a Display Name and other values that apply. + Use the following example problem as a model. + Which of the following is a fruit? Check all that apply. + apple + pumpkin + potato + tomato + """) + self.assertEquals(descriptor.problem_types, {"choiceresponse"}) + self.assertEquals( + descriptor.index_dictionary(), { + 'content_type': CapaDescriptor.INDEX_CONTENT_TYPE, + 'problem_types': ["choiceresponse"], + 'content': { + 'display_name': name, + 'capa_content': capa_content.replace("\n", " ") + } + } + ) + + def test_indexing_dropdown_with_hints_and_feedback(self): + name = "Dropdown with Hints and Feedback" + descriptor = self._create_descriptor(self.sample_dropdown_with_hints_and_feedback_problem_xml, name=name) + capa_content = textwrap.dedent(""" + You can provide feedback for each available option in a dropdown problem. + You can also add hints for learners. + Be sure to select Settings to specify a Display Name and other values that apply. + Use the following example problem as a model. + A/an ________ is a vegetable. + apple + pumpkin + potato + tomato + """) + self.assertEquals(descriptor.problem_types, {"optionresponse"}) + self.assertEquals( + descriptor.index_dictionary(), { + 'content_type': CapaDescriptor.INDEX_CONTENT_TYPE, + 'problem_types': ["optionresponse"], + 'content': { + 'display_name': name, + 'capa_content': capa_content.replace("\n", " ") + } + } + ) + + def test_indexing_multiple_choice_with_hints_and_feedback(self): + name = "Multiple Choice with Hints and Feedback" + descriptor = self._create_descriptor(self.sample_multichoice_with_hints_and_feedback_problem_xml, name=name) + capa_content = textwrap.dedent(""" + You can provide feedback for each option in a multiple choice problem. + You can also add hints for learners. + Be sure to select Settings to specify a Display Name and other values that apply. + Use the following example problem as a model. + Which of the following is a vegetable? + apple + pumpkin + potato + tomato + """) + self.assertEquals(descriptor.problem_types, {"multiplechoiceresponse"}) + self.assertEquals( + descriptor.index_dictionary(), { + 'content_type': CapaDescriptor.INDEX_CONTENT_TYPE, + 'problem_types': ["multiplechoiceresponse"], + 'content': { + 'display_name': name, + 'capa_content': capa_content.replace("\n", " ") + } + } + ) + + def test_indexing_numerical_input_with_hints_and_feedback(self): + name = "Numerical Input with Hints and Feedback" + descriptor = self._create_descriptor(self.sample_numerical_input_with_hints_and_feedback_problem_xml, name=name) + capa_content = textwrap.dedent(""" + You can provide feedback for correct answers in numerical input problems. You cannot provide feedback + for incorrect answers. + Use feedback for the correct answer to reinforce the process for arriving at the numerical value. + You can also add hints for learners. + Be sure to select Settings to specify a Display Name and other values that apply. + Use the following example problem as a model. + What is the arithmetic mean for the following set of numbers? (1, 5, 6, 3, 5) + """) + self.assertEquals(descriptor.problem_types, {"numericalresponse"}) + self.assertEquals( + descriptor.index_dictionary(), { + 'content_type': CapaDescriptor.INDEX_CONTENT_TYPE, + 'problem_types': ["numericalresponse"], + 'content': { + 'display_name': name, + 'capa_content': capa_content.replace("\n", " ") + } + } + ) + + def test_indexing_text_input_with_hints_and_feedback(self): + name = "Text Input with Hints and Feedback" + descriptor = self._create_descriptor(self.sample_text_input_with_hints_and_feedback_problem_xml, name=name) + capa_content = textwrap.dedent(""" + You can provide feedback for the correct answer in text input problems, as well as for specific + incorrect answers. + Use feedback on expected incorrect answers to address common misconceptions and to provide guidance on + how to arrive at the correct answer. + Be sure to select Settings to specify a Display Name and other values that apply. + Use the following example problem as a model. + Which U.S. state has the largest land area? + """) + self.assertEquals(descriptor.problem_types, {"stringresponse"}) + self.assertEquals( + descriptor.index_dictionary(), { + 'content_type': CapaDescriptor.INDEX_CONTENT_TYPE, + 'problem_types': ["stringresponse"], + 'content': { + 'display_name': name, + 'capa_content': capa_content.replace("\n", " ") + } + } + ) + + def test_indexing_problem_with_html_tags(self): + sample_problem_xml = textwrap.dedent(""" + + + +

This has HTML comment in it.

+ + +

HTML end.

+ + +
+ """) + name = "Mixed business" + descriptor = self._create_descriptor(sample_problem_xml, name=name) + capa_content = textwrap.dedent(""" + This has HTML comment in it. + HTML end. + """) + self.assertEquals( + descriptor.index_dictionary(), { + 'content_type': CapaDescriptor.INDEX_CONTENT_TYPE, + 'problem_types': [], + 'content': { + 'display_name': name, + 'capa_content': capa_content.replace("\n", " ") + } + } + ) + class ComplexEncoderTest(unittest.TestCase): def test_default(self): diff --git a/common/lib/xmodule/xmodule/tests/test_html_module.py b/common/lib/xmodule/xmodule/tests/test_html_module.py index fe6828e89f..b755166444 100644 --- a/common/lib/xmodule/xmodule/tests/test_html_module.py +++ b/common/lib/xmodule/xmodule/tests/test_html_module.py @@ -59,7 +59,7 @@ class HtmlDescriptorIndexingTestCase(unittest.TestCase): Make sure that HtmlDescriptor can format data for indexing as expected. """ - def test_index_dictionary(self): + def test_index_dictionary_simple_html_module(self): sample_xml = '''

Hello World!

@@ -71,6 +71,7 @@ class HtmlDescriptorIndexingTestCase(unittest.TestCase): "content_type": "Text" }) + def test_index_dictionary_cdata_html_module(self): sample_xml_cdata = '''

This has CDATA in it.

@@ -83,6 +84,7 @@ class HtmlDescriptorIndexingTestCase(unittest.TestCase): "content_type": "Text" }) + def test_index_dictionary_multiple_spaces_html_module(self): sample_xml_tab_spaces = '''

Text has spaces :)

@@ -94,6 +96,7 @@ class HtmlDescriptorIndexingTestCase(unittest.TestCase): "content_type": "Text" }) + def test_index_dictionary_html_module_with_comment(self): sample_xml_comment = '''

This has HTML comment in it.

@@ -106,6 +109,7 @@ class HtmlDescriptorIndexingTestCase(unittest.TestCase): "content_type": "Text" }) + def test_index_dictionary_html_module_with_both_comments_and_cdata(self): sample_xml_mix_comment_cdata = ''' @@ -120,3 +124,23 @@ class HtmlDescriptorIndexingTestCase(unittest.TestCase): "content": {"html_content": " This has HTML comment in it. HTML end. ", "display_name": "Text"}, "content_type": "Text" }) + + def test_index_dictionary_html_module_with_script_and_style_tags(self): + sample_xml_style_script_tags = ''' + + + +

This has HTML comment in it.

+ + +

HTML end.

+ + + ''' + descriptor = instantiate_descriptor(data=sample_xml_style_script_tags) + self.assertEqual(descriptor.index_dictionary(), { + "content": {"html_content": " This has HTML comment in it. HTML end. ", "display_name": "Text"}, + "content_type": "Text" + })