Merge branch 'master' into feature/btalbot/studio-tenderwidget

This commit is contained in:
Brian Talbot
2013-03-26 14:49:14 -04:00
15 changed files with 395 additions and 110 deletions

View File

@@ -0,0 +1,25 @@
Feature: Course Settings
As a course author, I want to be able to configure my course settings.
Scenario: User can set course dates
Given I have opened a new course in Studio
When I select Schedule and Details
And I set course dates
Then I see the set dates on refresh
Scenario: User can clear previously set course dates (except start date)
Given I have set course dates
And I clear all the dates except start
Then I see cleared dates on refresh
Scenario: User cannot clear the course start date
Given I have set course dates
And I clear the course start date
Then I receive a warning about course start date
And The previously set start date is shown on refresh
Scenario: User can correct the course start date warning
Given I have tried to clear the course start
And I have entered a new course start date
Then The warning about course start date goes away
And My new course start date is shown on refresh

View File

@@ -0,0 +1,163 @@
from lettuce import world, step
from common import *
from terrain.steps import reload_the_page
from selenium.webdriver.common.keys import Keys
import time
from nose.tools import assert_true, assert_false, assert_equal
COURSE_START_DATE_CSS = "#course-start-date"
COURSE_END_DATE_CSS = "#course-end-date"
ENROLLMENT_START_DATE_CSS = "#course-enrollment-start-date"
ENROLLMENT_END_DATE_CSS = "#course-enrollment-end-date"
COURSE_START_TIME_CSS = "#course-start-time"
COURSE_END_TIME_CSS = "#course-end-time"
ENROLLMENT_START_TIME_CSS = "#course-enrollment-start-time"
ENROLLMENT_END_TIME_CSS = "#course-enrollment-end-time"
DUMMY_TIME = "3:30pm"
DEFAULT_TIME = "12:00am"
############### ACTIONS ####################
@step('I select Schedule and Details$')
def test_i_select_schedule_and_details(step):
expand_icon_css = 'li.nav-course-settings i.icon-expand'
if world.browser.is_element_present_by_css(expand_icon_css):
css_click(expand_icon_css)
link_css = 'li.nav-course-settings-schedule a'
css_click(link_css)
@step('I have set course dates$')
def test_i_have_set_course_dates(step):
step.given('I have opened a new course in Studio')
step.given('I select Schedule and Details')
step.given('And I set course dates')
@step('And I set course dates$')
def test_and_i_set_course_dates(step):
set_date_or_time(COURSE_START_DATE_CSS, '12/20/2013')
set_date_or_time(COURSE_END_DATE_CSS, '12/26/2013')
set_date_or_time(ENROLLMENT_START_DATE_CSS, '12/1/2013')
set_date_or_time(ENROLLMENT_END_DATE_CSS, '12/10/2013')
set_date_or_time(COURSE_START_TIME_CSS, DUMMY_TIME)
set_date_or_time(ENROLLMENT_END_TIME_CSS, DUMMY_TIME)
pause()
@step('Then I see the set dates on refresh$')
def test_then_i_see_the_set_dates_on_refresh(step):
reload_the_page(step)
verify_date_or_time(COURSE_START_DATE_CSS, '12/20/2013')
verify_date_or_time(COURSE_END_DATE_CSS, '12/26/2013')
verify_date_or_time(ENROLLMENT_START_DATE_CSS, '12/01/2013')
verify_date_or_time(ENROLLMENT_END_DATE_CSS, '12/10/2013')
verify_date_or_time(COURSE_START_TIME_CSS, DUMMY_TIME)
# Unset times get set to 12 AM once the corresponding date has been set.
verify_date_or_time(COURSE_END_TIME_CSS, DEFAULT_TIME)
verify_date_or_time(ENROLLMENT_START_TIME_CSS, DEFAULT_TIME)
verify_date_or_time(ENROLLMENT_END_TIME_CSS, DUMMY_TIME)
@step('And I clear all the dates except start$')
def test_and_i_clear_all_the_dates_except_start(step):
set_date_or_time(COURSE_END_DATE_CSS, '')
set_date_or_time(ENROLLMENT_START_DATE_CSS, '')
set_date_or_time(ENROLLMENT_END_DATE_CSS, '')
pause()
@step('Then I see cleared dates on refresh$')
def test_then_i_see_cleared_dates_on_refresh(step):
reload_the_page(step)
verify_date_or_time(COURSE_END_DATE_CSS, '')
verify_date_or_time(ENROLLMENT_START_DATE_CSS, '')
verify_date_or_time(ENROLLMENT_END_DATE_CSS, '')
verify_date_or_time(COURSE_END_TIME_CSS, '')
verify_date_or_time(ENROLLMENT_START_TIME_CSS, '')
verify_date_or_time(ENROLLMENT_END_TIME_CSS, '')
# Verify course start date (required) and time still there
verify_date_or_time(COURSE_START_DATE_CSS, '12/20/2013')
verify_date_or_time(COURSE_START_TIME_CSS, DUMMY_TIME)
@step('I clear the course start date$')
def test_i_clear_the_course_start_date(step):
set_date_or_time(COURSE_START_DATE_CSS, '')
@step('I receive a warning about course start date$')
def test_i_receive_a_warning_about_course_start_date(step):
assert_css_with_text('.message-error', 'The course must have an assigned start date.')
assert_true('error' in css_find(COURSE_START_DATE_CSS).first._element.get_attribute('class'))
assert_true('error' in css_find(COURSE_START_TIME_CSS).first._element.get_attribute('class'))
@step('The previously set start date is shown on refresh$')
def test_the_previously_set_start_date_is_shown_on_refresh(step):
reload_the_page(step)
verify_date_or_time(COURSE_START_DATE_CSS, '12/20/2013')
verify_date_or_time(COURSE_START_TIME_CSS, DUMMY_TIME)
@step('Given I have tried to clear the course start$')
def test_i_have_tried_to_clear_the_course_start(step):
step.given("I have set course dates")
step.given("I clear the course start date")
step.given("I receive a warning about course start date")
@step('I have entered a new course start date$')
def test_i_have_entered_a_new_course_start_date(step):
set_date_or_time(COURSE_START_DATE_CSS, '12/22/2013')
pause()
@step('The warning about course start date goes away$')
def test_the_warning_about_course_start_date_goes_away(step):
assert_equal(0, len(css_find('.message-error')))
assert_false('error' in css_find(COURSE_START_DATE_CSS).first._element.get_attribute('class'))
assert_false('error' in css_find(COURSE_START_TIME_CSS).first._element.get_attribute('class'))
@step('My new course start date is shown on refresh$')
def test_my_new_course_start_date_is_shown_on_refresh(step):
reload_the_page(step)
verify_date_or_time(COURSE_START_DATE_CSS, '12/22/2013')
# Time should have stayed from before attempt to clear date.
verify_date_or_time(COURSE_START_TIME_CSS, DUMMY_TIME)
############### HELPER METHODS ####################
def set_date_or_time(css, date_or_time):
"""
Sets date or time field.
"""
css_fill(css, date_or_time)
e = css_find(css).first
# hit Enter to apply the changes
e._element.send_keys(Keys.ENTER)
def verify_date_or_time(css, date_or_time):
"""
Verifies date or time field.
"""
assert_equal(date_or_time, css_find(css).first.value)
def pause():
"""
Must sleep briefly to allow last time save to finish,
else refresh of browser will fail.
"""
time.sleep(float(1))

View File

@@ -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()

View File

@@ -251,7 +251,7 @@ function getEdxTimeFromDateTimeVals(date_val, time_val, format) {
time_val = '00:00';
// Note, we are using date.js utility which has better parsing abilities than the built in JS date parsing
date = Date.parse(date_val + " " + time_val);
var date = Date.parse(date_val + " " + time_val);
if (format == null)
format = 'yyyy-MM-ddTHH:mm';
@@ -269,6 +269,7 @@ function getEdxTimeFromDateTimeInputs(date_id, time_id, format) {
}
function autosaveInput(e) {
var self = this;
if (this.saveTimer) {
clearTimeout(this.saveTimer);
}
@@ -276,7 +277,7 @@ function autosaveInput(e) {
this.saveTimer = setTimeout(function () {
$changedInput = $(e.target);
saveSubsection();
this.saveTimer = null;
self.saveTimer = null;
}, 500);
}
@@ -318,6 +319,7 @@ function saveSubsection() {
data: JSON.stringify({ 'id': id, 'metadata': metadata}),
success: function () {
$spinner.delay(500).fadeOut(150);
$changedInput = null;
},
error: function () {
showToastMessage('There has been an error while saving your changes.');

View File

@@ -37,6 +37,9 @@ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({
// Returns either nothing (no return call) so that validate works or an object of {field: errorstring} pairs
// A bit funny in that the video key validation is asynchronous; so, it won't stop the validation.
var errors = {};
if (newattrs.start_date === null) {
errors.start_date = "The course must have an assigned start date.";
}
if (newattrs.start_date && newattrs.end_date && newattrs.start_date >= newattrs.end_date) {
errors.end_date = "The course end date cannot be before the course start date.";
}

View File

@@ -101,6 +101,12 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
cacheModel.save(fieldName, newVal);
}
}
else {
// Clear date (note that this clears the time as well, as date and time are linked).
// Note also that the validation logic prevents us from clearing the start date
// (start date is required by the back end).
cacheModel.save(fieldName, null);
}
};
// instrument as date and time pickers

View File

@@ -25,14 +25,7 @@ CMS.Views.ValidatingView = Backbone.View.extend({
for (var field in error) {
var ele = this.$el.find('#' + this.fieldToSelectorMap[field]);
this._cacheValidationErrors.push(ele);
var inputElements = 'input, textarea';
if ($(ele).is(inputElements)) {
$(ele).addClass('error');
}
else {
// put error on the contained inputs
$(ele).find(inputElements).addClass('error');
}
this.getInputElements(ele).addClass('error');
$(ele).parent().append(this.errorTemplate({message : error[field]}));
}
},
@@ -40,12 +33,8 @@ CMS.Views.ValidatingView = Backbone.View.extend({
clearValidationErrors : function() {
// error is object w/ fields and error strings
while (this._cacheValidationErrors.length > 0) {
var ele = this._cacheValidationErrors.pop();
if ($(ele).is('div')) {
// put error on the contained inputs
$(ele).find('input, textarea').removeClass('error');
}
else $(ele).removeClass('error');
var ele = this._cacheValidationErrors.pop();
this.getInputElements(ele).removeClass('error');
$(ele).nextAll('.message-error').remove();
}
},
@@ -68,5 +57,16 @@ CMS.Views.ValidatingView = Backbone.View.extend({
},
inputUnfocus : function(event) {
$("label[for='" + event.currentTarget.id + "']").removeClass("is-focused");
},
getInputElements: function(ele) {
var inputElements = 'input, textarea';
if ($(ele).is(inputElements)) {
return $(ele);
}
else {
// put error on the contained inputs
return $(ele).find(inputElements);
}
}
});

View File

@@ -17,7 +17,7 @@
% for choice_id, choice_description in choices:
<label for="input_${id}_${choice_id}"
% if input_type == 'radio' and choice_id in value:
% if input_type == 'radio' and choice_id == value:
<%
if status == 'correct':
correctness = 'correct'
@@ -32,8 +32,10 @@
% endif
>
<input type="${input_type}" name="input_${id}${name_array_suffix}" id="input_${id}_${choice_id}" value="${choice_id}"
% if choice_id in value:
% if input_type == 'radio' and choice_id == value:
checked="true"
% elif input_type != 'radio' and choice_id in value:
checked="true"
% endif
/> ${choice_description} </label>

View File

@@ -7,6 +7,8 @@ import requests
import time
from datetime import datetime
import dateutil.parser
from xmodule.modulestore import Location
from xmodule.seq_module import SequenceDescriptor, SequenceModule
from xmodule.timeparse import parse_time
@@ -150,7 +152,7 @@ class CourseFields(object):
enrollment_end = Date(help="Date that enrollment for this class is closed", scope=Scope.settings)
start = Date(help="Start time when this module is visible", scope=Scope.settings)
end = Date(help="Date that this class ends", scope=Scope.settings)
advertised_start = StringOrDate(help="Date that this course is advertised to start", scope=Scope.settings)
advertised_start = String(help="Date that this course is advertised to start", scope=Scope.settings)
grading_policy = Object(help="Grading policy definition for this class", scope=Scope.content)
show_calculator = Boolean(help="Whether to show the calculator in this course", default=False, scope=Scope.settings)
display_name = String(help="Display name for this module", scope=Scope.settings)
@@ -537,10 +539,12 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
announcement = self.announcement
if announcement is not None:
announcement = to_datetime(announcement)
if self.advertised_start is None or isinstance(self.advertised_start, basestring):
try:
start = dateutil.parser.parse(self.advertised_start)
except (ValueError, AttributeError):
start = to_datetime(self.start)
else:
start = to_datetime(self.advertised_start)
now = to_datetime(time.gmtime())
return announcement, start, now

View File

@@ -23,6 +23,8 @@ class Date(ModelType):
"""
if field is None:
return field
elif field is "":
return None
elif isinstance(field, basestring):
d = dateutil.parser.parse(field)
return d.utctimetuple()

View File

@@ -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

View File

@@ -89,18 +89,19 @@ class IsNewCourseTestCase(unittest.TestCase):
((day2, None, None), (day1, None, None), self.assertLess),
((day1, None, None), (day1, None, None), self.assertEqual),
# Non-parseable advertised starts are ignored in preference
# to actual starts
((day2, None, "Spring 2013"), (day1, None, "Fall 2012"), self.assertLess),
((day1, None, "Spring 2013"), (day1, None, "Fall 2012"), self.assertEqual),
# Non-parseable advertised starts are ignored in preference to actual starts
((day2, None, "Spring"), (day1, None, "Fall"), self.assertLess),
((day1, None, "Spring"), (day1, None, "Fall"), self.assertEqual),
# Partially parsable advertised starts should take priority over start dates
((day2, None, "October 2013"), (day2, None, "October 2012"), self.assertLess),
((day2, None, "October 2013"), (day1, None, "October 2013"), self.assertEqual),
# Parseable advertised starts take priority over start dates
((day1, None, day2), (day1, None, day1), self.assertLess),
((day2, None, day2), (day1, None, day2), self.assertEqual),
]
data = []
for a, b, assertion in dates:
a_score = self.get_dummy_course(start=a[0], announcement=a[1], advertised_start=a[2]).sorting_score
b_score = self.get_dummy_course(start=b[0], announcement=b[1], advertised_start=b[2]).sorting_score

View File

@@ -8,6 +8,7 @@ Feature: Answer problems
And I am viewing a "<ProblemType>" problem
When I answer a "<ProblemType>" problem "correctly"
Then My "<ProblemType>" answer is marked "correct"
And The "<ProblemType>" problem displays a "correct" answer
Examples:
| ProblemType |
@@ -25,6 +26,7 @@ Feature: Answer problems
And I am viewing a "<ProblemType>" problem
When I answer a "<ProblemType>" problem "incorrectly"
Then My "<ProblemType>" answer is marked "incorrect"
And The "<ProblemType>" problem displays a "incorrect" answer
Examples:
| ProblemType |
@@ -41,6 +43,7 @@ Feature: Answer problems
Given I am viewing a "<ProblemType>" problem
When I check a problem
Then My "<ProblemType>" answer is marked "incorrect"
And The "<ProblemType>" problem displays a "blank" answer
Examples:
| ProblemType |
@@ -58,6 +61,7 @@ Feature: Answer problems
And I answer a "<ProblemType>" problem "<Correctness>ly"
When I reset the problem
Then My "<ProblemType>" answer is marked "unanswered"
And The "<ProblemType>" problem displays a "blank" answer
Examples:
| ProblemType | Correctness |

View File

@@ -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 <input> 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

View File

@@ -10,7 +10,7 @@
left: 0;
margin: 100px auto;
top: 0;
z-index: 200;
height:500px;
}
.login-box input[type=submit] {
@@ -18,75 +18,18 @@
height: auto !important;
}
#lean_overlay {
display: block;
position: fixed;
left: 0px;
top: 0px;
z-index: 100;
width:100%;
height:100%;
}
</style>
</%block>
<section id="login-modal" class="modal login-modal login-box">
<div class="inner-wrapper">
<header>
<h2>Log In</h2>
<hr>
</header>
<form id="login_form" class="login_form" method="post" data-remote="true" action="/login">
<label>E-mail</label>
<input name="email" type="email">
<label>Password</label>
<input name="password" type="password">
<label class="remember-me">
<input name="remember" type="checkbox" value="true">
Remember me
</label>
<div class="submit">
<input name="submit" type="submit" value="Access My Courses">
</div>
</form>
<section class="login-extra">
<p>
<span>Not enrolled? <a href="#signup-modal" class="close-login" rel="leanModal">Sign up.</a></span>
<a href="#forgot-password-modal" rel="leanModal" class="pwd-reset">Forgot password?</a>
</p>
% if settings.MITX_FEATURES.get('AUTH_USE_OPENID'):
<p>
<a href="${MITX_ROOT_URL}/openid/login/">login via openid</a>
</p>
% endif
</section>
<div class="close-modal">
<div class="inner">
<p>&#10005;</p>
</div>
</div>
</div>
</section>
<section class='login-box'></section>
<script type="text/javascript">
(function() {
$(document).delegate('#login_form', 'ajax:success', function(data, json, xhr) {
if(json.success) {
next = getParameterByName('next');
if(next) {
location.href = next;
} else {
location.href = "${reverse('dashboard')}";
}
} else {
if($('#login_error').length == 0) {
$('#login_form').prepend('<div id="login_error" class="modal-form-error"></div>');
}
$('#login_error').html(json.value).stop().css("display", "block");
$(document).ready(
function() {
// show dialog
$('#login').click();
}
});
);
})(this)
</script>