Merge pull request #1996 from cpennington/conditional-module-acceptance-tests
Add acceptance tests of the conditional module
This commit is contained in:
@@ -96,7 +96,11 @@ class ConditionalModule(ConditionalFields, XModule):
|
||||
xml_value = self.descriptor.xml_attributes.get(xml_attr)
|
||||
if xml_value:
|
||||
return xml_value, attr_name
|
||||
raise Exception('Error in conditional module: unknown condition "%s"' % xml_attr)
|
||||
raise Exception(
|
||||
'Error in conditional module: no known conditional found in {!r}'.format(
|
||||
self.descriptor.xml_attributes.keys()
|
||||
)
|
||||
)
|
||||
|
||||
@lazy
|
||||
def required_modules(self):
|
||||
|
||||
@@ -32,7 +32,9 @@ class PollFields(object):
|
||||
poll_answer = String(help="Student answer", scope=Scope.user_state, default='')
|
||||
poll_answers = Dict(help="All possible answers for the poll fro other students", scope=Scope.user_state_summary)
|
||||
|
||||
# List of answers, in the form {'id': 'some id', 'text': 'the answer text'}
|
||||
answers = List(help="Poll answers from xml", scope=Scope.content, default=[])
|
||||
|
||||
question = String(help="Poll question", scope=Scope.content, default='')
|
||||
|
||||
|
||||
|
||||
22
lms/djangoapps/courseware/features/conditional.feature
Normal file
22
lms/djangoapps/courseware/features/conditional.feature
Normal file
@@ -0,0 +1,22 @@
|
||||
@shard_2
|
||||
Feature: LMS.Conditional Module
|
||||
As a student, I want to view a Conditional component in the LMS
|
||||
|
||||
Scenario: A Conditional hides content when conditions aren't satisfied
|
||||
Given that a course has a Conditional conditioned on problem attempted=True
|
||||
And that the conditioned problem has not been attempted
|
||||
When I view the conditional
|
||||
Then the conditional contents are hidden
|
||||
|
||||
Scenario: A Conditional shows content when conditions are satisfied
|
||||
Given that a course has a Conditional conditioned on problem attempted=True
|
||||
And that the conditioned problem has been attempted
|
||||
When I view the conditional
|
||||
Then the conditional contents are visible
|
||||
|
||||
Scenario: A Conditional containing a Poll is updated when the poll is answered
|
||||
Given that a course has a Conditional conditioned on poll poll_answer=yes
|
||||
When I view the conditional
|
||||
Then the conditional contents are hidden
|
||||
When I answer the conditioned poll "yes"
|
||||
Then the conditional contents are visible
|
||||
119
lms/djangoapps/courseware/features/conditional.py
Normal file
119
lms/djangoapps/courseware/features/conditional.py
Normal file
@@ -0,0 +1,119 @@
|
||||
|
||||
from lettuce import world, steps
|
||||
from nose.tools import assert_in, assert_equals, assert_true
|
||||
|
||||
from common import i_am_registered_for_the_course, visit_scenario_item
|
||||
from problems_setup import add_problem_to_course, answer_problem
|
||||
|
||||
@steps
|
||||
class ConditionalSteps(object):
|
||||
COURSE_NUM = 'test_course'
|
||||
|
||||
def setup_conditional(self, step, condition_type, condition, cond_value):
|
||||
r'that a course has a Conditional conditioned on (?P<condition_type>\w+) (?P<condition>\w+)=(?P<cond_value>\w+)$'
|
||||
|
||||
i_am_registered_for_the_course(step, self.COURSE_NUM)
|
||||
|
||||
world.scenario_dict['VERTICAL'] = world.ItemFactory(
|
||||
parent_location=world.scenario_dict['SECTION'].location,
|
||||
category='vertical',
|
||||
display_name="Test Vertical",
|
||||
)
|
||||
|
||||
world.scenario_dict['WRAPPER'] = world.ItemFactory(
|
||||
parent_location=world.scenario_dict['VERTICAL'].location,
|
||||
category='wrapper',
|
||||
display_name="Test Poll Wrapper"
|
||||
)
|
||||
|
||||
if condition_type == 'problem':
|
||||
world.scenario_dict['CONDITION_SOURCE'] = add_problem_to_course(self.COURSE_NUM, 'string')
|
||||
elif condition_type == 'poll':
|
||||
world.scenario_dict['CONDITION_SOURCE'] = world.ItemFactory(
|
||||
parent_location=world.scenario_dict['WRAPPER'].location,
|
||||
category='poll_question',
|
||||
display_name='Conditional Poll',
|
||||
data={
|
||||
'question': 'Is this a good poll?',
|
||||
'answers': [
|
||||
{'id': 'yes', 'text': 'Yes, of course'},
|
||||
{'id': 'no', 'text': 'Of course not!'}
|
||||
],
|
||||
}
|
||||
)
|
||||
else:
|
||||
raise Exception("Unknown condition type: {!r}".format(condition_type))
|
||||
|
||||
metadata = {
|
||||
'xml_attributes': {
|
||||
'sources': world.scenario_dict['CONDITION_SOURCE'].location.url()
|
||||
}
|
||||
}
|
||||
metadata['xml_attributes'][condition] = cond_value
|
||||
|
||||
world.scenario_dict['CONDITIONAL'] = world.ItemFactory(
|
||||
parent_location=world.scenario_dict['WRAPPER'].location,
|
||||
category='conditional',
|
||||
display_name="Test Conditional",
|
||||
metadata=metadata
|
||||
)
|
||||
|
||||
world.ItemFactory(
|
||||
parent_location=world.scenario_dict['CONDITIONAL'].location,
|
||||
category='html',
|
||||
display_name='Conditional Contents',
|
||||
data='<html><div class="hidden-contents">Hidden Contents</p></html>'
|
||||
)
|
||||
|
||||
|
||||
def setup_problem_attempts(self, step, not_attempted=None):
|
||||
r'that the conditioned problem has (?P<not_attempted>not )?been attempted$'
|
||||
visit_scenario_item('CONDITION_SOURCE')
|
||||
|
||||
if not_attempted is None:
|
||||
answer_problem(self.COURSE_NUM, 'string', True)
|
||||
world.css_click("input.check")
|
||||
|
||||
def when_i_view_the_conditional(self, step):
|
||||
r'I view the conditional$'
|
||||
visit_scenario_item('CONDITIONAL')
|
||||
world.wait_for_js_variable_truthy('$(".xblock-student_view[data-type=Conditional]").data("initialized")')
|
||||
|
||||
def check_visibility(self, step, visible):
|
||||
r'the conditional contents are (?P<visible>\w+)$'
|
||||
world.wait_for_ajax_complete()
|
||||
|
||||
assert_in(visible, ('visible', 'hidden'))
|
||||
|
||||
if visible == 'visible':
|
||||
world.wait_for_visible('.hidden-contents')
|
||||
assert_true(world.css_visible('.hidden-contents'))
|
||||
else:
|
||||
assert_true(world.is_css_not_present('.hidden-contents'))
|
||||
|
||||
def answer_poll(self, step, answer):
|
||||
r' I answer the conditioned poll "([^"]*)"$'
|
||||
visit_scenario_item('CONDITION_SOURCE')
|
||||
world.wait_for_js_variable_truthy('$(".xblock-student_view[data-type=Poll]").data("initialized")')
|
||||
world.wait_for_ajax_complete()
|
||||
|
||||
answer_text = [
|
||||
poll_answer['text']
|
||||
for poll_answer
|
||||
in world.scenario_dict['CONDITION_SOURCE'].answers
|
||||
if poll_answer['id'] == answer
|
||||
][0]
|
||||
|
||||
text_selector = '.poll_answer .text'
|
||||
|
||||
poll_texts = world.retry_on_exception(
|
||||
lambda: [elem.text for elem in world.css_find(text_selector)]
|
||||
)
|
||||
|
||||
for idx, poll_text in enumerate(poll_texts):
|
||||
if poll_text == answer_text:
|
||||
world.css_click(text_selector, index=idx)
|
||||
return
|
||||
|
||||
|
||||
ConditionalSteps()
|
||||
@@ -72,7 +72,7 @@ def input_problem_answer(_, problem_type, correctness):
|
||||
"""
|
||||
assert(correctness in ['correct', 'incorrect'])
|
||||
assert(problem_type in PROBLEM_DICT)
|
||||
answer_problem(problem_type, correctness)
|
||||
answer_problem(world.scenario_dict['COURSE'].number, problem_type, correctness)
|
||||
|
||||
|
||||
@step(u'I check a problem')
|
||||
@@ -98,7 +98,7 @@ def assert_problem_has_answer(step, problem_type, answer_class):
|
||||
'''
|
||||
assert answer_class in ['correct', 'incorrect', 'blank']
|
||||
assert problem_type in PROBLEM_DICT
|
||||
problem_has_answer(problem_type, answer_class)
|
||||
problem_has_answer(world.scenario_dict['COURSE'].number, problem_type, answer_class)
|
||||
|
||||
|
||||
@step(u'I reset the problem')
|
||||
|
||||
@@ -162,46 +162,48 @@ PROBLEM_DICT = {
|
||||
}
|
||||
|
||||
|
||||
def answer_problem(problem_type, correctness):
|
||||
def answer_problem(course, problem_type, correctness):
|
||||
# Make sure that the problem has been completely rendered before
|
||||
# starting to input an answer.
|
||||
world.wait_for_ajax_complete()
|
||||
|
||||
section_loc = section_location(course)
|
||||
|
||||
if problem_type == "drop down":
|
||||
select_name = "input_i4x-edx-model_course-problem-drop_down_2_1"
|
||||
select_name = "input_i4x-{0.org}-{0.course}-problem-drop_down_2_1".format(section_loc)
|
||||
option_text = 'Option 2' if correctness == 'correct' else 'Option 3'
|
||||
world.select_option(select_name, option_text)
|
||||
|
||||
elif problem_type == "multiple choice":
|
||||
if correctness == 'correct':
|
||||
world.css_check(inputfield('multiple choice', choice='choice_2'))
|
||||
world.css_check(inputfield(course, 'multiple choice', choice='choice_2'))
|
||||
else:
|
||||
world.css_check(inputfield('multiple choice', choice='choice_1'))
|
||||
world.css_check(inputfield(course, 'multiple choice', choice='choice_1'))
|
||||
|
||||
elif problem_type == "checkbox":
|
||||
if correctness == 'correct':
|
||||
world.css_check(inputfield('checkbox', choice='choice_0'))
|
||||
world.css_check(inputfield('checkbox', choice='choice_2'))
|
||||
world.css_check(inputfield(course, 'checkbox', choice='choice_0'))
|
||||
world.css_check(inputfield(course, 'checkbox', choice='choice_2'))
|
||||
else:
|
||||
world.css_check(inputfield('checkbox', choice='choice_3'))
|
||||
world.css_check(inputfield(course, 'checkbox', choice='choice_3'))
|
||||
|
||||
elif problem_type == 'radio':
|
||||
if correctness == 'correct':
|
||||
world.css_check(inputfield('radio', choice='choice_2'))
|
||||
world.css_check(inputfield(course, 'radio', choice='choice_2'))
|
||||
else:
|
||||
world.css_check(inputfield('radio', choice='choice_1'))
|
||||
world.css_check(inputfield(course, 'radio', choice='choice_1'))
|
||||
|
||||
elif problem_type == 'string':
|
||||
textvalue = 'correct string' if correctness == 'correct' else 'incorrect'
|
||||
world.css_fill(inputfield('string'), textvalue)
|
||||
world.css_fill(inputfield(course, 'string'), textvalue)
|
||||
|
||||
elif problem_type == 'numerical':
|
||||
textvalue = "pi + 1" if correctness == 'correct' else str(random.randint(-2, 2))
|
||||
world.css_fill(inputfield('numerical'), textvalue)
|
||||
world.css_fill(inputfield(course, 'numerical'), textvalue)
|
||||
|
||||
elif problem_type == 'formula':
|
||||
textvalue = "x^2+2*x+y" if correctness == 'correct' else 'x^2'
|
||||
world.css_fill(inputfield('formula'), textvalue)
|
||||
world.css_fill(inputfield(course, 'formula'), textvalue)
|
||||
|
||||
elif problem_type == 'script':
|
||||
# Correct answer is any two integers that sum to 10
|
||||
@@ -213,8 +215,8 @@ def answer_problem(problem_type, correctness):
|
||||
if correctness == 'incorrect':
|
||||
second_addend += random.randint(1, 10)
|
||||
|
||||
world.css_fill(inputfield('script', input_num=1), str(first_addend))
|
||||
world.css_fill(inputfield('script', input_num=2), str(second_addend))
|
||||
world.css_fill(inputfield(course, 'script', input_num=1), str(first_addend))
|
||||
world.css_fill(inputfield(course, 'script', input_num=2), str(second_addend))
|
||||
|
||||
elif problem_type == 'code':
|
||||
# The fake xqueue server is configured to respond
|
||||
@@ -233,15 +235,16 @@ def answer_problem(problem_type, correctness):
|
||||
choice = "choiceinput_0bc" if correctness == 'correct' else "choiceinput_1bc"
|
||||
world.css_fill(
|
||||
inputfield(
|
||||
course,
|
||||
problem_type,
|
||||
choice="choiceinput_0_numtolerance_input_0"
|
||||
),
|
||||
input_value
|
||||
)
|
||||
world.css_check(inputfield(problem_type, choice=choice))
|
||||
world.css_check(inputfield(course, problem_type, choice=choice))
|
||||
|
||||
|
||||
def problem_has_answer(problem_type, answer_class):
|
||||
def problem_has_answer(course, problem_type, answer_class):
|
||||
if problem_type == "drop down":
|
||||
if answer_class == 'blank':
|
||||
assert world.is_css_not_present('option[selected="true"]')
|
||||
@@ -252,52 +255,52 @@ def problem_has_answer(problem_type, answer_class):
|
||||
|
||||
elif problem_type == "multiple choice":
|
||||
if answer_class == 'correct':
|
||||
assert_checked('multiple choice', ['choice_2'])
|
||||
assert_checked(course, 'multiple choice', ['choice_2'])
|
||||
elif answer_class == 'incorrect':
|
||||
assert_checked('multiple choice', ['choice_1'])
|
||||
assert_checked(course, 'multiple choice', ['choice_1'])
|
||||
else:
|
||||
assert_checked('multiple choice', [])
|
||||
assert_checked(course, 'multiple choice', [])
|
||||
|
||||
elif problem_type == "checkbox":
|
||||
if answer_class == 'correct':
|
||||
assert_checked('checkbox', ['choice_0', 'choice_2'])
|
||||
assert_checked(course, 'checkbox', ['choice_0', 'choice_2'])
|
||||
elif answer_class == 'incorrect':
|
||||
assert_checked('checkbox', ['choice_3'])
|
||||
assert_checked(course, 'checkbox', ['choice_3'])
|
||||
else:
|
||||
assert_checked('checkbox', [])
|
||||
assert_checked(course, 'checkbox', [])
|
||||
|
||||
elif problem_type == "radio":
|
||||
if answer_class == 'correct':
|
||||
assert_checked('radio', ['choice_2'])
|
||||
assert_checked(course, 'radio', ['choice_2'])
|
||||
elif answer_class == 'incorrect':
|
||||
assert_checked('radio', ['choice_1'])
|
||||
assert_checked(course, 'radio', ['choice_1'])
|
||||
else:
|
||||
assert_checked('radio', [])
|
||||
assert_checked(course, 'radio', [])
|
||||
|
||||
elif problem_type == 'string':
|
||||
if answer_class == 'blank':
|
||||
expected = ''
|
||||
else:
|
||||
expected = 'correct string' if answer_class == 'correct' else 'incorrect'
|
||||
assert_textfield('string', expected)
|
||||
assert_textfield(course, '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)
|
||||
assert_textfield(course, 'formula', expected)
|
||||
|
||||
elif problem_type in ("radio_text", "checkbox_text"):
|
||||
if answer_class == 'blank':
|
||||
expected = ('', '')
|
||||
assert_choicetext_values(problem_type, (), expected)
|
||||
assert_choicetext_values(course, problem_type, (), expected)
|
||||
elif answer_class == 'incorrect':
|
||||
expected = ('5', '')
|
||||
assert_choicetext_values(problem_type, ["choiceinput_1bc"], expected)
|
||||
assert_choicetext_values(course, problem_type, ["choiceinput_1bc"], expected)
|
||||
else:
|
||||
expected = ('8', '')
|
||||
assert_choicetext_values(problem_type, ["choiceinput_0bc"], expected)
|
||||
assert_choicetext_values(course, problem_type, ["choiceinput_0bc"], expected)
|
||||
|
||||
else:
|
||||
# The other response types use random data,
|
||||
@@ -325,14 +328,16 @@ def add_problem_to_course(course, problem_type, extra_meta=None):
|
||||
# We set rerandomize=always in the metadata so that the "Reset" button
|
||||
# will appear.
|
||||
category_name = "problem"
|
||||
return world.ItemFactory.create(parent_location=section_location(course),
|
||||
category=category_name,
|
||||
display_name=str(problem_type),
|
||||
data=problem_xml,
|
||||
metadata=metadata)
|
||||
return world.ItemFactory.create(
|
||||
parent_location=section_location(course),
|
||||
category=category_name,
|
||||
display_name=str(problem_type),
|
||||
data=problem_xml,
|
||||
metadata=metadata
|
||||
)
|
||||
|
||||
|
||||
def inputfield(problem_type, choice=None, input_num=1):
|
||||
def inputfield(course, problem_type, choice=None, input_num=1):
|
||||
""" Return the css selector for `problem_type`.
|
||||
For example, if problem_type is 'string', return
|
||||
the text field for the string problem in the test course.
|
||||
@@ -340,14 +345,20 @@ def inputfield(problem_type, choice=None, input_num=1):
|
||||
`choice` is the name of the checkbox input in a group
|
||||
of checkboxes. """
|
||||
|
||||
sel = ("input#input_i4x-edx-model_course-problem-%s_2_%s" %
|
||||
(problem_type.replace(" ", "_"), str(input_num)))
|
||||
section_loc = section_location(course)
|
||||
|
||||
# this is necessary due to naming requirement for this problem type
|
||||
# this is necessary due to naming requirement for this problem type
|
||||
if problem_type in ("radio_text", "checkbox_text"):
|
||||
sel = "input#i4x-edx-model_course-problem-{0}_2_{1}".format(
|
||||
problem_type.replace(" ", "_"), str(input_num)
|
||||
)
|
||||
selector_template = "input#i4x-{org}-{course}-problem-{ptype}_2_{input}"
|
||||
else:
|
||||
selector_template = "input#input_i4x-{org}-{course}-problem-{ptype}_2_{input}"
|
||||
|
||||
sel = selector_template.format(
|
||||
org=section_loc.org,
|
||||
course=section_loc.course,
|
||||
ptype=problem_type.replace(" ", "_"),
|
||||
input=input_num,
|
||||
)
|
||||
|
||||
if choice is not None:
|
||||
base = "_choice_" if problem_type == "multiple choice" else "_"
|
||||
@@ -360,7 +371,7 @@ def inputfield(problem_type, choice=None, input_num=1):
|
||||
return sel
|
||||
|
||||
|
||||
def assert_checked(problem_type, choices):
|
||||
def assert_checked(course, problem_type, choices):
|
||||
'''
|
||||
Assert that choice names given in *choices* are the only
|
||||
ones checked.
|
||||
@@ -371,7 +382,7 @@ def assert_checked(problem_type, choices):
|
||||
all_choices = ['choice_0', 'choice_1', 'choice_2', 'choice_3']
|
||||
for this_choice in all_choices:
|
||||
def check_problem():
|
||||
element = world.css_find(inputfield(problem_type, choice=this_choice))
|
||||
element = world.css_find(inputfield(course, problem_type, choice=this_choice))
|
||||
if this_choice in choices:
|
||||
assert element.checked
|
||||
else:
|
||||
@@ -379,12 +390,12 @@ def assert_checked(problem_type, choices):
|
||||
world.retry_on_exception(check_problem)
|
||||
|
||||
|
||||
def assert_textfield(problem_type, expected_text, input_num=1):
|
||||
element_value = world.css_value(inputfield(problem_type, input_num=input_num))
|
||||
def assert_textfield(course, problem_type, expected_text, input_num=1):
|
||||
element_value = world.css_value(inputfield(course, problem_type, input_num=input_num))
|
||||
assert element_value == expected_text
|
||||
|
||||
|
||||
def assert_choicetext_values(problem_type, choices, expected_values):
|
||||
def assert_choicetext_values(course, problem_type, choices, expected_values):
|
||||
"""
|
||||
Asserts that only the given choices are checked, and given
|
||||
text fields have a desired value
|
||||
@@ -397,7 +408,7 @@ def assert_choicetext_values(problem_type, choices, expected_values):
|
||||
"choiceinput_1_numtolerance_input_0"
|
||||
]
|
||||
for this_choice in all_choices:
|
||||
element = world.css_find(inputfield(problem_type, choice=this_choice))
|
||||
element = world.css_find(inputfield(course, problem_type, choice=this_choice))
|
||||
|
||||
if this_choice in choices:
|
||||
assert element.checked
|
||||
@@ -405,6 +416,6 @@ def assert_choicetext_values(problem_type, choices, expected_values):
|
||||
assert not element.checked
|
||||
|
||||
for (name, expected) in zip(all_inputs, expected_values):
|
||||
element = world.css_find(inputfield(problem_type, name))
|
||||
element = world.css_find(inputfield(course, problem_type, name))
|
||||
# Remove any trailing spaces that may have been added
|
||||
assert element.value.strip() == expected
|
||||
|
||||
Reference in New Issue
Block a user