Commit cbf5174b by cahrens

Merge branch 'feature/cale/cms-master' into feature/christina/tiny-mce

parents 882eed4f 5e28e598
......@@ -41,11 +41,14 @@ def i_press_the_category_delete_icon(step, category):
####### HELPER FUNCTIONS ##############
def create_studio_user(
uname='robot',
em='robot+studio@edx.org',
password='test'):
email='robot+studio@edx.org',
password='test',
is_staff=False):
studio_user = UserFactory.build(
username=uname,
email=em)
email=email,
password=password,
is_staff=is_staff)
studio_user.set_password(password)
studio_user.save()
......@@ -91,8 +94,9 @@ def fill_in_course_info(
def log_into_studio(
uname='robot',
email='robot+studio@edx.org',
password='test'):
create_studio_user(uname, email)
password='test',
is_staff=False):
create_studio_user(uname=uname, email=email, is_staff=is_staff)
world.browser.cookies.delete()
world.browser.visit(django_url('/'))
world.browser.is_element_present_by_css('body.no-header', 10)
......
Feature: Overview Toggle Section
In order to quickly view the details of a course's section or to scan the inventory of sections
As a course author
I want to toggle the visibility of each section's subsection details in the overview listing
Scenario: The default layout for the overview page is to show sections in expanded view
Given I have a course with multiple sections
When I navigate to the course overview page
Then I see the "Collapse All Sections" link
And all sections are expanded
Scenario: Expand/collapse for a course with no sections
Given I have a course with no sections
When I navigate to the course overview page
Then I do not see the "Collapse All Sections" link
Scenario: Collapse link appears after creating first section of a course
Given I have a course with no sections
When I navigate to the course overview page
And I add a section
Then I see the "Collapse All Sections" link
And all sections are expanded
Scenario: Collapse link is removed after last section of a course is deleted
Given I have a course with 1 section
And I navigate to the course overview page
When I press the "section" delete icon
And I confirm the alert
Then I do not see the "Collapse All Sections" link
Scenario: Collapsing all sections when all sections are expanded
Given I navigate to the courseware page of a course with multiple sections
And all sections are expanded
When I click the "Collapse All Sections" link
Then I see the "Expand All Sections" link
And all sections are collapsed
Scenario: Collapsing all sections when 1 or more sections are already collapsed
Given I navigate to the courseware page of a course with multiple sections
And all sections are expanded
When I collapse the first section
And I click the "Collapse All Sections" link
Then I see the "Expand All Sections" link
And all sections are collapsed
Scenario: Expanding all sections when all sections are collapsed
Given I navigate to the courseware page of a course with multiple sections
And I click the "Collapse All Sections" link
When I click the "Expand All Sections" link
Then I see the "Collapse All Sections" link
And all sections are expanded
Scenario: Expanding all sections when 1 or more sections are already expanded
Given I navigate to the courseware page of a course with multiple sections
And I click the "Collapse All Sections" link
When I expand the first section
And I click the "Expand All Sections" link
Then I see the "Collapse All Sections" link
And all sections are expanded
\ No newline at end of file
from lettuce import world, step
from terrain.factories import *
from common import *
from nose.tools import assert_true, assert_false, assert_equal
from logging import getLogger
logger = getLogger(__name__)
@step(u'I have a course with no sections$')
def have_a_course(step):
clear_courses()
course = CourseFactory.create()
@step(u'I have a course with 1 section$')
def have_a_course_with_1_section(step):
clear_courses()
course = CourseFactory.create()
section = ItemFactory.create(parent_location=course.location)
subsection1 = ItemFactory.create(
parent_location=section.location,
template = 'i4x://edx/templates/sequential/Empty',
display_name = 'Subsection One',)
@step(u'I have a course with multiple sections$')
def have_a_course_with_two_sections(step):
clear_courses()
course = CourseFactory.create()
section = ItemFactory.create(parent_location=course.location)
subsection1 = ItemFactory.create(
parent_location=section.location,
template = 'i4x://edx/templates/sequential/Empty',
display_name = 'Subsection One',)
section2 = ItemFactory.create(
parent_location=course.location,
display_name='Section Two',)
subsection2 = ItemFactory.create(
parent_location=section2.location,
template = 'i4x://edx/templates/sequential/Empty',
display_name = 'Subsection Alpha',)
subsection3 = ItemFactory.create(
parent_location=section2.location,
template = 'i4x://edx/templates/sequential/Empty',
display_name = 'Subsection Beta',)
@step(u'I navigate to the course overview page$')
def navigate_to_the_course_overview_page(step):
log_into_studio(is_staff=True)
course_locator = '.class-name'
css_click(course_locator)
@step(u'I navigate to the courseware page of a course with multiple sections')
def nav_to_the_courseware_page_of_a_course_with_multiple_sections(step):
step.given('I have a course with multiple sections')
step.given('I navigate to the course overview page')
@step(u'I add a section')
def i_add_a_section(step):
add_section(name='My New Section That I Just Added')
@step(u'I click the "([^"]*)" link$')
def i_click_the_text_span(step, text):
span_locator = '.toggle-button-sections span'
assert_true(world.browser.is_element_present_by_css(span_locator, 5))
# first make sure that the expand/collapse text is the one you expected
assert_equal(world.browser.find_by_css(span_locator).value, text)
css_click(span_locator)
@step(u'I collapse the first section$')
def i_collapse_a_section(step):
collapse_locator = 'section.courseware-section a.collapse'
css_click(collapse_locator)
@step(u'I expand the first section$')
def i_expand_a_section(step):
expand_locator = 'section.courseware-section a.expand'
css_click(expand_locator)
@step(u'I see the "([^"]*)" link$')
def i_see_the_span_with_text(step, text):
span_locator = '.toggle-button-sections span'
assert_true(world.browser.is_element_present_by_css(span_locator, 5))
assert_equal(world.browser.find_by_css(span_locator).value, text)
assert_true(world.browser.find_by_css(span_locator).visible)
@step(u'I do not see the "([^"]*)" link$')
def i_do_not_see_the_span_with_text(step, text):
# Note that the span will exist on the page but not be visible
span_locator = '.toggle-button-sections span'
assert_true(world.browser.is_element_present_by_css(span_locator))
assert_false(world.browser.find_by_css(span_locator).visible)
@step(u'all sections are expanded$')
def all_sections_are_expanded(step):
subsection_locator = 'div.subsection-list'
subsections = world.browser.find_by_css(subsection_locator)
for s in subsections:
assert_true(s.visible)
@step(u'all sections are collapsed$')
def all_sections_are_expanded(step):
subsection_locator = 'div.subsection-list'
subsections = world.browser.find_by_css(subsection_locator)
for s in subsections:
assert_false(s.visible)
\ No newline at end of file
<section class="problem-editor editor">
<div class="row">
<textarea class="markdown-box">markdown</textarea>
<textarea class="xml-box" rows="8" cols="40">xml</textarea>
</div>
</section>
\ No newline at end of file
<section class="problem-editor editor">
<div class="row">
<textarea class="xml-box" rows="8" cols="40">xml only</textarea>
</div>
</section>
\ No newline at end of file
describe 'MarkdownEditingDescriptor', ->
describe 'save stores the correct data', ->
it 'saves markdown from markdown editor', ->
loadFixtures 'problem-with-markdown.html'
@descriptor = new MarkdownEditingDescriptor($('.problem-editor'))
saveResult = @descriptor.save()
expect(saveResult.metadata.markdown).toEqual('markdown')
expect(saveResult.data).toEqual('<problem>\n<p>markdown</p>\n</problem>')
it 'clears markdown when xml editor is selected', ->
loadFixtures 'problem-with-markdown.html'
@descriptor = new MarkdownEditingDescriptor($('.problem-editor'))
@descriptor.createXMLEditor('replace with markdown')
saveResult = @descriptor.save()
expect(saveResult.metadata.markdown).toEqual(null)
expect(saveResult.data).toEqual('replace with markdown')
it 'saves xml from the xml editor', ->
loadFixtures 'problem-without-markdown.html'
@descriptor = new MarkdownEditingDescriptor($('.problem-editor'))
saveResult = @descriptor.save()
expect(saveResult.metadata.markdown).toEqual(null)
expect(saveResult.data).toEqual('xml only')
describe 'insertMultipleChoice', ->
it 'inserts the template if selection is empty', ->
......@@ -69,17 +89,13 @@ describe 'MarkdownEditingDescriptor', ->
Enter the number of fingers on a human hand:
= 5
<solution>
<div class='detailed-solution'>
Explanation
[Explanation]
Pi, or the the ratio between a circle's circumference to its diameter, is an irrational number known to extreme precision. It is value is approximately equal to 3.14.
Although you can get an exact value by typing 502*9 into a calculator, the result will be close to 500*10, or 5,000. The grader accepts any response within 15% of the true value, 4518, so that you can use any estimation technique that you like.
If you look at your hand, you can count that you have five fingers.
</div>
</solution>
[Explanation]
""")
expect(data).toEqual("""<problem>
<p>A numerical response problem accepts a line of text input from the student, and evaluates the input for correctness based on its numerical value.</p>
......@@ -104,7 +120,7 @@ describe 'MarkdownEditingDescriptor', ->
</numericalresponse>
<solution>
<div class='detailed-solution'>
<div class="detailed-solution">
<p>Explanation</p>
<p>Pi, or the the ratio between a circle's circumference to its diameter, is an irrational number known to extreme precision. It is value is approximately equal to 3.14.</p>
......@@ -112,6 +128,7 @@ describe 'MarkdownEditingDescriptor', ->
<p>Although you can get an exact value by typing 502*9 into a calculator, the result will be close to 500*10, or 5,000. The grader accepts any response within 15% of the true value, 4518, so that you can use any estimation technique that you like.</p>
<p>If you look at your hand, you can count that you have five fingers.</p>
</div>
</solution>
</problem>""")
......@@ -128,13 +145,9 @@ describe 'MarkdownEditingDescriptor', ->
( ) Android
( ) The Beatles
<solution>
<div class='detailed-solution'>
Explanation
[Explanation]
The release of the iPod allowed consumers to carry their entire music library with them in a format that did not rely on fragile and energy-intensive spinning disks.
</div>
</solution>
[Explanation]
""")
expect(data).toEqual("""<problem>
<p>A multiple choice problem presents radio buttons for student input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets.</p>
......@@ -154,10 +167,11 @@ describe 'MarkdownEditingDescriptor', ->
</multiplechoiceresponse>
<solution>
<div class='detailed-solution'>
<div class="detailed-solution">
<p>Explanation</p>
<p>The release of the iPod allowed consumers to carry their entire music library with them in a format that did not rely on fragile and energy-intensive spinning disks.</p>
</div>
</solution>
</problem>""")
......@@ -169,13 +183,9 @@ describe 'MarkdownEditingDescriptor', ->
Translation between Option Response and __________ is extremely straightforward:
[[(Multiple Choice), String Response, Numerical Response, External Response, Image Response]]
<solution>
<div class='detailed-solution'>
Explanation
[Explanation]
Multiple Choice also allows students to select from a variety of pre-written responses, although the format makes it easier for students to read very long response options. Optionresponse also differs slightly because students are more likely to think of an answer and then search for it rather than relying purely on recognition to answer the question.
</div>
</solution>
[Explanation]
""")
expect(data).toEqual("""<problem>
<p>OptionResponse gives a limited set of options for students to respond with, and presents those options in a format that encourages them to search for a specific answer rather than being immediately presented with options from which to recognize the correct answer.</p>
......@@ -189,14 +199,15 @@ describe 'MarkdownEditingDescriptor', ->
</optionresponse>
<solution>
<div class='detailed-solution'>
<div class="detailed-solution">
<p>Explanation</p>
<p>Multiple Choice also allows students to select from a variety of pre-written responses, although the format makes it easier for students to read very long response options. Optionresponse also differs slightly because students are more likely to think of an answer and then search for it rather than relying purely on recognition to answer the question.</p>
</div>
</solution>
</problem>""")
it 'converts OptionResponse to xml', ->
it 'converts StringResponse to xml', ->
data = MarkdownEditingDescriptor.markdownToXml("""A string response problem accepts a line of text input from the student, and evaluates the input for correctness based on an expected answer within each input box.
The answer is correct if it matches every character of the expected answer. This can be a problem with international spelling, dates, or anything where the format of the answer is not clear.
......@@ -204,14 +215,9 @@ describe 'MarkdownEditingDescriptor', ->
Which US state has Lansing as its capital?
= Michigan
<solution>
<div class='detailed-solution'>
Explanation
[Explanation]
Lansing is the capital of Michigan, although it is not Michgan's largest city, or even the seat of the county in which it resides.
</div>
</solution>
[Explanation]
""")
expect(data).toEqual("""<problem>
<p>A string response problem accepts a line of text input from the student, and evaluates the input for correctness based on an expected answer within each input box.</p>
......@@ -224,7 +230,7 @@ describe 'MarkdownEditingDescriptor', ->
</stringresponse>
<solution>
<div class='detailed-solution'>
<div class="detailed-solution">
<p>Explanation</p>
<p>Lansing is the capital of Michigan, although it is not Michgan's largest city, or even the seat of the county in which it resides.</p>
......@@ -262,6 +268,11 @@ describe 'MarkdownEditingDescriptor', ->
What happens w/ empty correct options?
[[()]]
[Explanation]see[/expLanation]
[explanation]
orphaned start
No p tags in the below
<script type='javascript'>
var two = 2;
......@@ -321,6 +332,17 @@ describe 'MarkdownEditingDescriptor', ->
<optioninput options="('')" correct=""></optioninput>
</optionresponse>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>see</p>
</div>
</solution>
<p>[explanation]</p>
<p>orphaned start</p>
<p>No p tags in the below</p>
<script type='javascript'>
var two = 2;
......
......@@ -260,6 +260,12 @@ class @MarkdownEditingDescriptor extends XModule.Descriptor
return selectString;
});
// replace explanations
xml = xml.replace(/\[explanation\]\n?([^\]]*)\[\/?explanation\]/gmi, function(match, p1) {
var selectString = '<solution>\n<div class="detailed-solution">\nExplanation\n\n' + p1 + '\n</div>\n</solution>';
return selectString;
});
// split scripts and wrap paragraphs
var splits = xml.split(/(\<\/?script.*?\>)/g);
var scriptFlag = false;
......
......@@ -29,19 +29,10 @@ metadata:
( ) The Beatles
<solution>
<div class='detailed-solution'>
Explanation
[Explanation]
The release of the iPod allowed consumers to carry their entire music library with them in a
format that did not rely on fragile and energy-intensive spinning disks.
</div>
</solution>
[Explanation]
"
data: |
<problem>
......
......@@ -28,26 +28,16 @@ metadata:
= 5
<solution>
<div class='detailed-solution'>
Explanation
[Explanation]
Pi, or the the ratio between a circle's circumference to its diameter, is an irrational number
known to extreme precision. It is value is approximately equal to 3.14.
Although you can get an exact value by typing 502*9 into a calculator, the result will be close to
500*10, or 5,000. The grader accepts any response within 15% of the true value, 4518, so that you
can use any estimation technique that you like.
If you look at your hand, you can count that you have five fingers.
</div>
</solution>
[Explanation]
"
data: |
......
......@@ -17,21 +17,12 @@ metadata:
[[(Multiple Choice), String Response, Numerical Response, External Response, Image Response]]
<solution>
<div class='detailed-solution'>
Explanation
[Explanation]
Multiple Choice also allows students to select from a variety of pre-written responses, although the
format makes it easier for students to read very long response options. Optionresponse also differs
slightly because students are more likely to think of an answer and then search for it rather than
relying purely on recognition to answer the question.
</div>
</solution>
[Explanation]
"
data: |
<problem>
......
......@@ -19,20 +19,10 @@ metadata:
= Michigan
<solution>
<div class='detailed-solution'>
Explanation
[Explanation]
Lansing is the capital of Michigan, although it is not Michgan's largest city,
or even the seat of the county in which it resides.
</div>
</solution>
[Explanation]
"
data: |
<problem showanswer="always">
......
......@@ -379,7 +379,7 @@ class XmlDescriptor(XModuleDescriptor):
# Write the definition to a file
url_path = name_to_pathname(self.url_name)
filepath = self.__class__._format_filepath(self.category, url_path)
resource_fs.makedir(os.path.dirname(filepath), allow_recreate=True)
resource_fs.makedir(os.path.dirname(filepath), recursive=True, allow_recreate=True)
with resource_fs.open(filepath, 'w') as file:
file.write(etree.tostring(xml_object, pretty_print=True, encoding='utf-8'))
......
import factory
from student.models import User, UserProfile, Registration
from datetime import datetime
import uuid
from factory import Factory
from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore
from time import gmtime
from uuid import uuid4
from xmodule.timeparse import stringify_time
class UserProfileFactory(factory.Factory):
class UserProfileFactory(Factory):
FACTORY_FOR = UserProfile
user = None
......@@ -13,13 +17,13 @@ class UserProfileFactory(factory.Factory):
mailing_address = None
goals = 'World domination'
class RegistrationFactory(factory.Factory):
class RegistrationFactory(Factory):
FACTORY_FOR = Registration
user = None
activation_key = uuid.uuid4().hex
activation_key = uuid4().hex
class UserFactory(factory.Factory):
class UserFactory(Factory):
FACTORY_FOR = User
username = 'robot'
......@@ -32,3 +36,113 @@ class UserFactory(factory.Factory):
is_superuser = False
last_login = datetime(2012, 1, 1)
date_joined = datetime(2011, 1, 1)
def XMODULE_COURSE_CREATION(class_to_create, **kwargs):
return XModuleCourseFactory._create(class_to_create, **kwargs)
def XMODULE_ITEM_CREATION(class_to_create, **kwargs):
return XModuleItemFactory._create(class_to_create, **kwargs)
class XModuleCourseFactory(Factory):
"""
Factory for XModule courses.
"""
ABSTRACT_FACTORY = True
_creation_function = (XMODULE_COURSE_CREATION,)
@classmethod
def _create(cls, target_class, *args, **kwargs):
template = Location('i4x', 'edx', 'templates', 'course', 'Empty')
org = kwargs.get('org')
number = kwargs.get('number')
display_name = kwargs.get('display_name')
location = Location('i4x', org, number,
'course', Location.clean(display_name))
store = modulestore('direct')
# Write the data to the mongo datastore
new_course = store.clone_item(template, location)
# This metadata code was copied from cms/djangoapps/contentstore/views.py
if display_name is not None:
new_course.metadata['display_name'] = display_name
new_course.metadata['data_dir'] = uuid4().hex
new_course.metadata['start'] = stringify_time(gmtime())
new_course.tabs = [{"type": "courseware"},
{"type": "course_info", "name": "Course Info"},
{"type": "discussion", "name": "Discussion"},
{"type": "wiki", "name": "Wiki"},
{"type": "progress", "name": "Progress"}]
# Update the data in the mongo datastore
store.update_metadata(new_course.location.url(), new_course.own_metadata)
return new_course
class Course:
pass
class CourseFactory(XModuleCourseFactory):
FACTORY_FOR = Course
template = 'i4x://edx/templates/course/Empty'
org = 'MITx'
number = '999'
display_name = 'Robot Super Course'
class XModuleItemFactory(Factory):
"""
Factory for XModule items.
"""
ABSTRACT_FACTORY = True
_creation_function = (XMODULE_ITEM_CREATION,)
@classmethod
def _create(cls, target_class, *args, **kwargs):
"""
kwargs must include parent_location, template. Can contain display_name
target_class is ignored
"""
DETACHED_CATEGORIES = ['about', 'static_tab', 'course_info']
parent_location = Location(kwargs.get('parent_location'))
template = Location(kwargs.get('template'))
display_name = kwargs.get('display_name')
store = modulestore('direct')
# This code was based off that in cms/djangoapps/contentstore/views.py
parent = store.get_item(parent_location)
dest_location = parent_location._replace(category=template.category, name=uuid4().hex)
new_item = store.clone_item(template, dest_location)
# TODO: This needs to be deleted when we have proper storage for static content
new_item.metadata['data_dir'] = parent.metadata['data_dir']
# replace the display name with an optional parameter passed in from the caller
if display_name is not None:
new_item.metadata['display_name'] = display_name
store.update_metadata(new_item.location.url(), new_item.own_metadata)
if new_item.location.category not in DETACHED_CATEGORIES:
store.update_children(parent_location, parent.definition.get('children', []) + [new_item.location.url()])
return new_item
class Item:
pass
class ItemFactory(XModuleItemFactory):
FACTORY_FOR = Item
parent_location = 'i4x://MITx/999/course/Robot_Super_Course'
template = 'i4x://edx/templates/chapter/Empty'
display_name = 'Section One'
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment