diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py
index 615ffb6ed0..edb20561bc 100644
--- a/cms/djangoapps/contentstore/tests/test_contentstore.py
+++ b/cms/djangoapps/contentstore/tests/test_contentstore.py
@@ -37,6 +37,14 @@ TEST_DATA_MODULESTORE = copy.deepcopy(settings.MODULESTORE)
TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data')
TEST_DATA_MODULESTORE['direct']['OPTIONS']['fs_root'] = path('common/test/data')
+class MongoCollectionFindWrapper(object):
+ def __init__(self, original):
+ self.original = original
+ self.counter = 0
+
+ def find(self, query, *args, **kwargs):
+ self.counter = self.counter+1
+ return self.original(query, *args, **kwargs)
@override_settings(MODULESTORE=TEST_DATA_MODULESTORE)
class ContentStoreToyCourseTest(ModuleStoreTestCase):
@@ -145,8 +153,6 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
# make sure the parent no longer points to the child object which was deleted
self.assertFalse(sequential.location.url() in chapter.children)
-
-
def test_about_overrides(self):
'''
This test case verifies that a course can use specialized override for about data, e.g. /about/Fall_2012/effort.html
@@ -205,7 +211,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
new_loc = descriptor.location._replace(org='MITx', course='999')
print "Checking {0} should now also be at {1}".format(descriptor.location.url(), new_loc.url())
resp = self.client.get(reverse('edit_unit', kwargs={'location': new_loc.url()}))
- self.assertEqual(resp.status_code, 200)
+ self.assertEqual(resp.status_code, 200)
def test_delete_course(self):
import_from_xml(modulestore(), 'common/test/data/', ['full'])
@@ -307,6 +313,28 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
# note, we know the link it should be because that's what in the 'full' course in the test data
self.assertContains(resp, '/c4x/edX/full/asset/handouts_schematic_tutorial.pdf')
+ def test_prefetch_children(self):
+ import_from_xml(modulestore(), 'common/test/data/', ['full'])
+ module_store = modulestore('direct')
+ location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012')
+
+ wrapper = MongoCollectionFindWrapper(module_store.collection.find)
+ module_store.collection.find = wrapper.find
+ course = module_store.get_item(location, depth=2)
+
+ # make sure we haven't done too many round trips to DB
+ # note we say 4 round trips here for 1) the course, 2 & 3) for the chapters and sequentials, and
+ # 4) because of the RT due to calculating the inherited metadata
+ self.assertEqual(wrapper.counter, 4)
+
+ # make sure we pre-fetched a known sequential which should be at depth=2
+ self.assertTrue(Location(['i4x', 'edX', 'full', 'sequential',
+ 'Administrivia_and_Circuit_Elements', None]) in course.system.module_data)
+
+ # make sure we don't have a specific vertical which should be at depth=3
+ self.assertFalse(Location(['i4x', 'edX', 'full', 'vertical', 'vertical_58',
+ None]) in course.system.module_data)
+
def test_export_course_with_unknown_metadata(self):
module_store = modulestore('direct')
content_store = contentstore()
diff --git a/common/lib/capa/capa/templates/choicegroup.html b/common/lib/capa/capa/templates/choicegroup.html
index e1ff40b6a1..758e2ffba1 100644
--- a/common/lib/capa/capa/templates/choicegroup.html
+++ b/common/lib/capa/capa/templates/choicegroup.html
@@ -17,7 +17,7 @@
% for choice_id, choice_description in choices:
diff --git a/common/lib/xmodule/xmodule/modulestore/mongo.py b/common/lib/xmodule/xmodule/modulestore/mongo.py
index f6fa98fc28..b76251bb99 100644
--- a/common/lib/xmodule/xmodule/modulestore/mongo.py
+++ b/common/lib/xmodule/xmodule/modulestore/mongo.py
@@ -366,6 +366,9 @@ class MongoModuleStore(ModuleStoreBase):
children.extend(item.get('definition', {}).get('children', []))
data[Location(item['location'])] = item
+ if depth == 0:
+ break;
+
# Load all children by id. See
# http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24or
# for or-query syntax
diff --git a/lms/djangoapps/courseware/features/problems.feature b/lms/djangoapps/courseware/features/problems.feature
index efeb338c45..dc8495af60 100644
--- a/lms/djangoapps/courseware/features/problems.feature
+++ b/lms/djangoapps/courseware/features/problems.feature
@@ -8,6 +8,7 @@ Feature: Answer problems
And I am viewing a "" problem
When I answer a "" problem "correctly"
Then My "" answer is marked "correct"
+ And The "" problem displays a "correct" answer
Examples:
| ProblemType |
@@ -25,6 +26,7 @@ Feature: Answer problems
And I am viewing a "" problem
When I answer a "" problem "incorrectly"
Then My "" answer is marked "incorrect"
+ And The "" problem displays a "incorrect" answer
Examples:
| ProblemType |
@@ -41,6 +43,7 @@ Feature: Answer problems
Given I am viewing a "" problem
When I check a problem
Then My "" answer is marked "incorrect"
+ And The "" problem displays a "blank" answer
Examples:
| ProblemType |
@@ -58,6 +61,7 @@ Feature: Answer problems
And I answer a "" problem "ly"
When I reset the problem
Then My "" answer is marked "unanswered"
+ And The "" problem displays a "blank" answer
Examples:
| ProblemType | Correctness |
diff --git a/lms/djangoapps/courseware/features/problems.py b/lms/djangoapps/courseware/features/problems.py
index 6b2239c38b..d2d379a212 100644
--- a/lms/djangoapps/courseware/features/problems.py
+++ b/lms/djangoapps/courseware/features/problems.py
@@ -1,9 +1,14 @@
+'''
+Steps for problem.feature lettuce tests
+'''
+
+
from lettuce import world, step
from lettuce.django import django_url
import random
import textwrap
-import time
-from common import i_am_registered_for_the_course, TEST_SECTION_NAME, section_location
+from common import i_am_registered_for_the_course, \
+ TEST_SECTION_NAME, section_location
from capa.tests.response_xml_factory import OptionResponseXMLFactory, \
ChoiceResponseXMLFactory, MultipleChoiceResponseXMLFactory, \
StringResponseXMLFactory, NumericalResponseXMLFactory, \
@@ -26,7 +31,7 @@ PROBLEM_FACTORY_DICT = {
'kwargs': {
'question_text': 'The correct answer is Choice 3',
'choices': [False, False, True, False],
- 'choice_names': ['choice_1', 'choice_2', 'choice_3', 'choice_4']}},
+ 'choice_names': ['choice_0', 'choice_1', 'choice_2', 'choice_3']}},
'checkbox': {
'factory': ChoiceResponseXMLFactory(),
@@ -88,6 +93,9 @@ PROBLEM_FACTORY_DICT = {
def add_problem_to_course(course, problem_type):
+ '''
+ Add a problem to the course we have created using factories.
+ '''
assert(problem_type in PROBLEM_FACTORY_DICT)
@@ -98,11 +106,12 @@ def add_problem_to_course(course, problem_type):
# Create a problem item using our generated XML
# We set rerandomize=always in the metadata so that the "Reset" button
# will appear.
- problem_item = world.ItemFactory.create(parent_location=section_location(course),
- template="i4x://edx/templates/problem/Blank_Common_Problem",
- display_name=str(problem_type),
- data=problem_xml,
- metadata={'rerandomize': 'always'})
+ template_name = "i4x://edx/templates/problem/Blank_Common_Problem"
+ world.ItemFactory.create(parent_location=section_location(course),
+ template=template_name,
+ display_name=str(problem_type),
+ data=problem_xml,
+ metadata={'rerandomize': 'always'})
@step(u'I am viewing a "([^"]*)" problem')
@@ -152,9 +161,9 @@ def answer_problem(step, problem_type, correctness):
elif problem_type == "multiple choice":
if correctness == 'correct':
- inputfield('multiple choice', choice='choice_3').check()
- else:
inputfield('multiple choice', choice='choice_2').check()
+ else:
+ inputfield('multiple choice', choice='choice_1').check()
elif problem_type == "checkbox":
if correctness == 'correct':
@@ -164,11 +173,13 @@ def answer_problem(step, problem_type, correctness):
inputfield('checkbox', choice='choice_3').check()
elif problem_type == 'string':
- textvalue = 'correct string' if correctness == 'correct' else 'incorrect'
+ textvalue = 'correct string' if correctness == 'correct' \
+ else 'incorrect'
inputfield('string').fill(textvalue)
elif problem_type == 'numerical':
- textvalue = "pi + 1" if correctness == 'correct' else str(random.randint(-2, 2))
+ textvalue = "pi + 1" if correctness == 'correct' \
+ else str(random.randint(-2, 2))
inputfield('numerical').fill(textvalue)
elif problem_type == 'formula':
@@ -203,6 +214,67 @@ def answer_problem(step, problem_type, correctness):
check_problem(step)
+@step(u'The "([^"]*)" problem displays a "([^"]*)" answer')
+def assert_problem_has_answer(step, problem_type, answer_class):
+ '''
+ Assert that the problem is displaying a particular answer.
+ These correspond to the same correct/incorrect
+ answers we set in answer_problem()
+
+ We can also check that a problem has been left blank
+ by setting answer_class='blank'
+ '''
+ assert answer_class in ['correct', 'incorrect', 'blank']
+
+ if problem_type == "drop down":
+ if answer_class == 'blank':
+ assert world.browser.is_element_not_present_by_css('option[selected="true"]')
+ else:
+ actual = world.browser.find_by_css('option[selected="true"]').value
+ expected = 'Option 2' if answer_class == 'correct' else 'Option 3'
+ assert actual == expected
+
+ elif problem_type == "multiple choice":
+ if answer_class == 'correct':
+ assert_checked('multiple choice', ['choice_2'])
+ elif answer_class == 'incorrect':
+ assert_checked('multiple choice', ['choice_1'])
+ else:
+ assert_checked('multiple choice', [])
+
+ elif problem_type == "checkbox":
+ if answer_class == 'correct':
+ assert_checked('checkbox', ['choice_0', 'choice_2'])
+ elif answer_class == 'incorrect':
+ assert_checked('checkbox', ['choice_3'])
+ else:
+ assert_checked('checkbox', [])
+
+ elif problem_type == 'string':
+ if answer_class == 'blank':
+ expected = ''
+ else:
+ expected = 'correct string' if answer_class == 'correct' \
+ else 'incorrect'
+
+ assert_textfield('string', expected)
+
+ elif problem_type == 'formula':
+ if answer_class == 'blank':
+ expected = ''
+ else:
+ expected = "x^2+2*x+y" if answer_class == 'correct' else 'x^2'
+
+ assert_textfield('formula', expected)
+
+ else:
+ # The other response types use random data,
+ # which would be difficult to check
+ # We trade input value coverage in the other tests for
+ # input type coverage in this test.
+ pass
+
+
@step(u'I check a problem')
def check_problem(step):
world.css_click("input.check")
@@ -227,7 +299,7 @@ CORRECTNESS_SELECTORS = {
'string': ['div.correct'],
'numerical': ['div.correct'],
'formula': ['div.correct'],
- 'script': ['div.correct'],
+ 'script': ['div.correct'],
'code': ['span.correct']},
'incorrect': {'drop down': ['span.incorrect'],
@@ -247,12 +319,14 @@ CORRECTNESS_SELECTORS = {
'numerical': ['div.unanswered'],
'formula': ['div.unanswered'],
'script': ['div.unanswered'],
- 'code': ['span.unanswered'] }}
+ 'code': ['span.unanswered']}}
@step(u'My "([^"]*)" answer is marked "([^"]*)"')
def assert_answer_mark(step, problem_type, correctness):
- """ Assert that the expected answer mark is visible for a given problem type.
+ """
+ Assert that the expected answer mark is visible
+ for a given problem type.
*problem_type* is a string identifying the type of problem (e.g. 'drop down')
*correctness* is in ['correct', 'incorrect', 'unanswered']
@@ -274,6 +348,7 @@ def assert_answer_mark(step, problem_type, correctness):
# Expect that we found the expected selector
assert(has_expected)
+
def inputfield(problem_type, choice=None, input_num=1):
""" Return the element for *problem_type*.
For example, if problem_type is 'string', return
@@ -289,8 +364,32 @@ def inputfield(problem_type, choice=None, input_num=1):
base = "_choice_" if problem_type == "multiple choice" else "_"
sel = sel + base + str(choice)
+
# If the input element doesn't exist, fail immediately
assert(world.browser.is_element_present_by_css(sel, wait_time=4))
# Retrieve the input element
return world.browser.find_by_css(sel)
+
+
+def assert_checked(problem_type, choices):
+ '''
+ Assert that choice names given in *choices* are the only
+ ones checked.
+
+ Works for both radio and checkbox problems
+ '''
+
+ all_choices = ['choice_0', 'choice_1', 'choice_2', 'choice_3']
+ for this_choice in all_choices:
+ element = inputfield(problem_type, choice=this_choice)
+
+ if this_choice in choices:
+ assert element.checked
+ else:
+ assert not element.checked
+
+
+def assert_textfield(problem_type, expected_text, input_num=1):
+ element = inputfield(problem_type, input_num=input_num)
+ assert element.value == expected_text