diff --git a/common/lib/xmodule/xmodule/library_content_module.py b/common/lib/xmodule/xmodule/library_content_module.py
index 8a59c4a764..e2e2481324 100644
--- a/common/lib/xmodule/xmodule/library_content_module.py
+++ b/common/lib/xmodule/xmodule/library_content_module.py
@@ -35,8 +35,10 @@ def enum(**enums):
def _get_capa_types():
+ """
+ Gets capa types tags and labels
+ """
capa_types = {
- ANY_CAPA_TYPE_VALUE: _('Any Type'),
'annotationinput': _('Annotation'),
'checkboxgroup': _('Checkbox Group'),
'checkboxtextgroup': _('Checkbox Text Group'),
@@ -54,7 +56,7 @@ def _get_capa_types():
'javascriptinput': _('Javascript Input'),
'jsinput': _('JS Input'),
'matlabinput': _('Matlab'),
- 'optioninput': _('Select option'),
+ 'optioninput': _('Select Option'),
'radiogroup': _('Radio Group'),
'radiotextgroup': _('Radio Text Group'),
'schematic': _('Schematic'),
@@ -63,7 +65,7 @@ def _get_capa_types():
'vsepr_input': _('VSEPR'),
}
- return sorted([
+ return [{'value': ANY_CAPA_TYPE_VALUE, 'display_name': _('Any Type')}] + sorted([
{'value': capa_type, 'display_name': caption}
for capa_type, caption in capa_types.items()
], key=lambda item: item.get('display_name'))
@@ -221,6 +223,9 @@ class LibraryContentModule(LibraryContentFields, XModule, StudioEditableModule):
any particular student.
"""
def _filter_children(self, child_locator):
+ """
+ Filters children by CAPA problem type, if configured
+ """
if self.capa_type == ANY_CAPA_TYPE_VALUE:
return True
@@ -234,7 +239,6 @@ class LibraryContentModule(LibraryContentFields, XModule, StudioEditableModule):
return any(self.capa_type in capa_input.tags for capa_input in block.lcp.inputs.values())
-
def selected_children(self):
"""
Returns a set() of block_ids indicating which of the possible children
@@ -427,8 +431,8 @@ class LibraryContentDescriptor(LibraryContentFields, MakoModuleDescriptor, XmlDe
If source_libraries has been edited, refresh_children automatically.
"""
old_source_libraries = LibraryList().from_json(old_metadata.get('source_libraries', []))
- if (set(old_source_libraries) != set(self.source_libraries) or
- old_metadata.get('capa_type', ANY_CAPA_TYPE_VALUE) != self.capa_type):
+ if set(old_source_libraries) != set(self.source_libraries) or \
+ old_metadata.get('capa_type', ANY_CAPA_TYPE_VALUE) != self.capa_type:
try:
self.refresh_children(None, None, update_db=False) # update_db=False since update_item() is about to be called anyways
except ValueError:
diff --git a/common/test/acceptance/pages/lms/library.py b/common/test/acceptance/pages/lms/library.py
index 8655fae79f..3397082344 100644
--- a/common/test/acceptance/pages/lms/library.py
+++ b/common/test/acceptance/pages/lms/library.py
@@ -16,6 +16,9 @@ class LibraryContentXBlockWrapper(PageObject):
self.locator = locator
def is_browser_on_page(self):
+ """
+ Checks if page is opened
+ """
return self.q(css='{}[data-id="{}"]'.format(self.BODY_SELECTOR, self.locator)).present
def _bounded_selector(self, selector):
@@ -35,3 +38,11 @@ class LibraryContentXBlockWrapper(PageObject):
"""
child_blocks = self.q(css=self._bounded_selector("div[data-id]"))
return frozenset(child.text for child in child_blocks)
+
+ @property
+ def children_headers(self):
+ """
+ Gets headers if all child XBlocks as list of strings
+ """
+ child_blocks_headers = self.q(css=self._bounded_selector("div[data-id] h2.problem-header"))
+ return frozenset(child.text for child in child_blocks_headers)
diff --git a/common/test/acceptance/pages/studio/library.py b/common/test/acceptance/pages/studio/library.py
index ea7f2299f9..71df4c4ad1 100644
--- a/common/test/acceptance/pages/studio/library.py
+++ b/common/test/acceptance/pages/studio/library.py
@@ -122,6 +122,7 @@ class StudioLibraryContentXBlockEditModal(CourseOutlineModal, PageObject):
LIBRARY_LABEL = "Libraries"
COUNT_LABEL = "Count"
SCORED_LABEL = "Scored"
+ PROBLEM_TYPE_LABEL = "Problem Type"
def is_browser_on_page(self):
"""
@@ -196,6 +197,24 @@ class StudioLibraryContentXBlockEditModal(CourseOutlineModal, PageObject):
scored_select.select_by_value(str(scored))
EmptyPromise(lambda: self.scored == scored, "scored is updated in modal.").fulfill()
+ @property
+ def capa_type(self):
+ """
+ Gets value of CAPA type select
+ """
+ return self.get_metadata_input(self.PROBLEM_TYPE_LABEL).get_attribute('value')
+
+ @capa_type.setter
+ def capa_type(self, value):
+ """
+ Sets value of CAPA type select
+ """
+ select_element = self.get_metadata_input(self.PROBLEM_TYPE_LABEL)
+ select_element.click()
+ problem_type_select = Select(select_element)
+ problem_type_select.select_by_value(value)
+ EmptyPromise(lambda: self.capa_type == value, "problem type is updated in modal.").fulfill()
+
def _add_library_key(self):
"""
Adds library key input
diff --git a/common/test/acceptance/tests/lms/test_library.py b/common/test/acceptance/tests/lms/test_library.py
index f83e6b94e9..4d6f34e822 100644
--- a/common/test/acceptance/tests/lms/test_library.py
+++ b/common/test/acceptance/tests/lms/test_library.py
@@ -19,22 +19,22 @@ SUBSECTION_NAME = 'Test Subsection'
UNIT_NAME = 'Test Unit'
-@ddt.ddt
-class LibraryContentTest(UniqueCourseTest):
- """
- Test courseware.
- """
+class LibraryContentTestBase(UniqueCourseTest):
+ """ Base class for library content block tests """
USERNAME = "STUDENT_TESTER"
EMAIL = "student101@example.com"
STAFF_USERNAME = "STAFF_TESTER"
STAFF_EMAIL = "staff101@example.com"
+ def populate_library_fixture(self, library_fixture):
+ pass
+
def setUp(self):
"""
Set up library, course and library content XBlock
"""
- super(LibraryContentTest, self).setUp()
+ super(LibraryContentTestBase, self).setUp()
self.courseware_page = CoursewarePage(self.browser, self.course_id)
@@ -46,11 +46,7 @@ class LibraryContentTest(UniqueCourseTest):
)
self.library_fixture = LibraryFixture('test_org', self.unique_id, 'Test Library {}'.format(self.unique_id))
- self.library_fixture.add_children(
- XBlockFixtureDesc("html", "Html1", data='html1'),
- XBlockFixtureDesc("html", "Html2", data='html2'),
- XBlockFixtureDesc("html", "Html3", data='html3'),
- )
+ self.populate_library_fixture(self.library_fixture)
self.library_fixture.install()
self.library_info = self.library_fixture.library_info
@@ -83,7 +79,7 @@ class LibraryContentTest(UniqueCourseTest):
self.course_fixture.install()
- def _refresh_library_content_children(self, count=1):
+ def _change_library_content_settings(self, count=1, capa_type=None):
"""
Performs library block refresh in Studio, configuring it to show {count} children
"""
@@ -91,6 +87,8 @@ class LibraryContentTest(UniqueCourseTest):
library_container_block = StudioLibraryContainerXBlockWrapper.from_xblock_wrapper(unit_page.xblocks[0])
modal = StudioLibraryContentXBlockEditModal(library_container_block.edit())
modal.count = count
+ if capa_type is not None:
+ modal.capa_type = capa_type
library_container_block.save_settings()
self._go_to_unit_page(change_login=False)
unit_page.wait_for_page()
@@ -124,6 +122,7 @@ class LibraryContentTest(UniqueCourseTest):
block_id = block_id if block_id is not None else self.lib_block.locator
#pylint: disable=attribute-defined-outside-init
self.library_content_page = LibraryContentXBlockWrapper(self.browser, block_id)
+ self.library_content_page.wait_for_page()
def _auto_auth(self, username, email, staff):
"""
@@ -132,6 +131,22 @@ class LibraryContentTest(UniqueCourseTest):
AutoAuthPage(self.browser, username=username, email=email,
course_id=self.course_id, staff=staff).visit()
+
+@ddt.ddt
+class LibraryContentTest(LibraryContentTestBase):
+ """
+ Test courseware.
+ """
+ def populate_library_fixture(self, library_fixture):
+ """
+ Populates library fixture with XBlock Fixtures
+ """
+ library_fixture.add_children(
+ XBlockFixtureDesc("html", "Html1", data='html1'),
+ XBlockFixtureDesc("html", "Html2", data='html2'),
+ XBlockFixtureDesc("html", "Html3", data='html3'),
+ )
+
@ddt.data(1, 2, 3)
def test_shows_random_xblocks_from_configured(self, count):
"""
@@ -143,7 +158,7 @@ class LibraryContentTest(UniqueCourseTest):
When I go to LMS courseware page for library content xblock as student
Then I can see {count} random xblocks from the library
"""
- self._refresh_library_content_children(count=count)
+ self._change_library_content_settings(count=count)
self._auto_auth(self.USERNAME, self.EMAIL, False)
self._goto_library_block_page()
children_contents = self.library_content_page.children_contents
@@ -160,9 +175,128 @@ class LibraryContentTest(UniqueCourseTest):
When I go to LMS courseware page for library content xblock as student
Then I can see all xblocks from the library
"""
- self._refresh_library_content_children(count=10)
+ self._change_library_content_settings(count=10)
self._auto_auth(self.USERNAME, self.EMAIL, False)
self._goto_library_block_page()
children_contents = self.library_content_page.children_contents
self.assertEqual(len(children_contents), 3)
self.assertEqual(children_contents, self.library_xblocks_texts)
+
+
+@ddt.ddt
+class StudioLibraryContainerCapaFilterTest(LibraryContentTestBase):
+ """
+ Test Library Content block in LMS
+ """
+ def _get_problem_choice_group_text(self, name, items):
+ """ Generates Choice Group CAPA problem XML """
+ items_text = "\n".join([
+ "{item}".format(correct=correct, item=item)
+ for item, correct in items
+ ])
+
+ return """
+ {name}
+
+ {items}
+
+""".format(name=name, items=items_text)
+
+ def _get_problem_select_text(self, name, items, correct):
+ """ Generates Select Option CAPA problem XML """
+ items_text = ",".join(map(lambda item: "'{0}'".format(item), items))
+
+ return """
+{name}
+
+
+
+""".format(name=name, options=items_text, correct=correct)
+
+ def populate_library_fixture(self, library_fixture):
+ """
+ Populates library fixture with XBlock Fixtures
+ """
+ library_fixture.add_children(
+ XBlockFixtureDesc(
+ "problem", "Problem Choice Group 1",
+ data=self._get_problem_choice_group_text("Problem Choice Group 1 Text", [("1", False), ('2', True)])
+ ),
+ XBlockFixtureDesc(
+ "problem", "Problem Choice Group 2",
+ data=self._get_problem_choice_group_text("Problem Choice Group 2 Text", [("Q", True), ('W', False)])
+ ),
+ XBlockFixtureDesc(
+ "problem", "Problem Select 1",
+ data=self._get_problem_select_text("Problem Select 1 Text", ["Option 1", "Option 2"], "Option 1")
+ ),
+ XBlockFixtureDesc(
+ "problem", "Problem Select 2",
+ data=self._get_problem_select_text("Problem Select 2 Text", ["Option 3", "Option 4"], "Option 4")
+ ),
+ )
+
+ @property
+ def _problem_headers(self):
+ """ Expected XBLock headers according to populate_library_fixture """
+ return frozenset(child.display_name.upper() for child in self.library_fixture.children)
+
+ @ddt.data(1, 3)
+ def test_any_capa_type_shows_all(self, count):
+ """
+ Scenario: Ensure setting "Any Type" for Problem Type does not filter out Problems
+ Given I have a library with two "Select Option" and two "Choice Group" problems, and a course containing
+ LibraryContent XBlock configured to draw XBlocks from that library
+ When I go to studio unit page for library content xblock as staff
+ And I set library content xblock Problem Type to "Any Type" and Count to {count}
+ And I refresh library content xblock and pulbish unit
+ When I go to LMS courseware page for library content xblock as student
+ Then I can see {count} xblocks from the library of any type
+ """
+ self._change_library_content_settings(count=count, capa_type="Any Type")
+ self._auto_auth(self.USERNAME, self.EMAIL, False)
+ self._goto_library_block_page()
+ children_headers = self.library_content_page.children_headers
+ self.assertEqual(len(children_headers), count)
+ self.assertLessEqual(children_headers, self._problem_headers)
+
+ @ddt.data(
+ ('Choice Group', 1, ["Problem Choice Group 1", "Problem Choice Group 2"]),
+ ('Select Option', 2, ["Problem Select 1", "Problem Select 2"]),
+ )
+ @ddt.unpack
+ def test_capa_type_shows_only_chosen_type(self, capa_type, count, expected_headers):
+ """
+ Scenario: Ensure setting "{capa_type}" for Problem Type draws aonly problem of {capa_type} from library
+ Given I have a library with two "Select Option" and two "Choice Group" problems, and a course containing
+ LibraryContent XBlock configured to draw XBlocks from that library
+ When I go to studio unit page for library content xblock as staff
+ And I set library content xblock Problem Type to "{capa_type}" and Count to {count}
+ And I refresh library content xblock and pulbish unit
+ When I go to LMS courseware page for library content xblock as student
+ Then I can see {count} xblocks from the library of {capa_type}
+ """
+ self._change_library_content_settings(count=count, capa_type=capa_type)
+ self._auto_auth(self.USERNAME, self.EMAIL, False)
+ self._goto_library_block_page()
+ children_headers = self.library_content_page.children_headers
+ self.assertEqual(len(children_headers), count)
+ self.assertLessEqual(children_headers, self._problem_headers)
+ self.assertLessEqual(children_headers, set(map(lambda header: header.upper(), expected_headers)))
+
+ def test_missing_capa_type_shows_none(self):
+ """
+ Scenario: Ensure setting "{capa_type}" for Problem Type that is not present in library results in empty XBlock
+ Given I have a library with two "Select Option" and two "Choice Group" problems, and a course containing
+ LibraryContent XBlock configured to draw XBlocks from that library
+ When I go to studio unit page for library content xblock as staff
+ And I set library content xblock Problem Type to type not present in library
+ And I refresh library content xblock and pulbish unit
+ When I go to LMS courseware page for library content xblock as student
+ Then I can see no xblocks
+ """
+ self._change_library_content_settings(count=1, capa_type="Matlab")
+ self._auto_auth(self.USERNAME, self.EMAIL, False)
+ self._goto_library_block_page()
+ children_headers = self.library_content_page.children_headers
+ self.assertEqual(len(children_headers), 0)