Commit b432db1a by Chris Dodge

Merge branch 'feature/cale/cms-master' of github.com:MITx/mitx into feature/cdodge/static-tab-edit

parents 7dbabce4 e3d65a0c
[submodule "common/test/phantom-jasmine"]
path = common/test/phantom-jasmine
url = https://github.com/jcarver989/phantom-jasmine.git
...@@ -3,3 +3,5 @@ ruby "1.9.3" ...@@ -3,3 +3,5 @@ ruby "1.9.3"
gem 'rake' gem 'rake'
gem 'sass', '3.1.15' gem 'sass', '3.1.15'
gem 'bourbon', '~> 1.3.6' gem 'bourbon', '~> 1.3.6'
gem 'colorize'
gem 'launchy'
...@@ -16,4 +16,6 @@ Changes ...@@ -16,4 +16,6 @@ Changes
Upcoming Upcoming
-------- --------
* Created changelog * Fix: Deleting last component in a unit does not work
\ No newline at end of file * Fix: Unit name is editable when a unit is public
* Fix: Visual feedback inconsistent when saving a unit name change
...@@ -585,7 +585,10 @@ def save_item(request): ...@@ -585,7 +585,10 @@ def save_item(request):
data = request.POST['data'] data = request.POST['data']
store.update_item(item_location, data) store.update_item(item_location, data)
if request.POST.get('children') is not None: # cdodge: note calling request.POST.get('children') will return None if children is an empty array
# so it lead to a bug whereby the last component to be deleted in the UI was not actually
# deleting the children object from the children collection
if 'children' in request.POST and request.POST['children'] is not None:
children = request.POST['children'] children = request.POST['children']
store.update_children(item_location, children) store.update_children(item_location, children)
......
...@@ -35,6 +35,7 @@ MITX_FEATURES = { ...@@ -35,6 +35,7 @@ MITX_FEATURES = {
'ENABLE_DISCUSSION_SERVICE': False, 'ENABLE_DISCUSSION_SERVICE': False,
'AUTH_USE_MIT_CERTIFICATES' : False, 'AUTH_USE_MIT_CERTIFICATES' : False,
} }
ENABLE_JASMINE = False
# needed to use lms student app # needed to use lms student app
GENERATE_RANDOM_USER_CREDENTIALS = False GENERATE_RANDOM_USER_CREDENTIALS = False
...@@ -68,9 +69,7 @@ MAKO_TEMPLATES['main'] = [ ...@@ -68,9 +69,7 @@ MAKO_TEMPLATES['main'] = [
for namespace, template_dirs in lms.envs.common.MAKO_TEMPLATES.iteritems(): for namespace, template_dirs in lms.envs.common.MAKO_TEMPLATES.iteritems():
MAKO_TEMPLATES['lms.' + namespace] = template_dirs MAKO_TEMPLATES['lms.' + namespace] = template_dirs
TEMPLATE_DIRS = ( TEMPLATE_DIRS = MAKO_TEMPLATES['main']
PROJECT_ROOT / "templates",
)
MITX_ROOT_URL = '' MITX_ROOT_URL = ''
...@@ -88,10 +87,6 @@ TEMPLATE_CONTEXT_PROCESSORS = ( ...@@ -88,10 +87,6 @@ TEMPLATE_CONTEXT_PROCESSORS = (
LMS_BASE = None LMS_BASE = None
################################# Jasmine ###################################
JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee'
#################### CAPA External Code Evaluation ############################# #################### CAPA External Code Evaluation #############################
XQUEUE_INTERFACE = { XQUEUE_INTERFACE = {
'url': 'http://localhost:8888', 'url': 'http://localhost:8888',
...@@ -289,7 +284,4 @@ INSTALLED_APPS = ( ...@@ -289,7 +284,4 @@ INSTALLED_APPS = (
# For asset pipelining # For asset pipelining
'pipeline', 'pipeline',
'staticfiles', 'staticfiles',
# For testing
'django_jasmine',
) )
"""
This configuration is used for running jasmine tests
"""
from .test import *
from logsettings import get_logger_config
ENABLE_JASMINE = True
DEBUG = True
LOGGING = get_logger_config(TEST_ROOT / "log",
logging_env="dev",
tracking_filename="tracking.log",
dev_env=True,
debug=True)
PIPELINE_JS['js-test-source'] = {
'source_filenames': sum([
pipeline_group['source_filenames']
for group_name, pipeline_group
in PIPELINE_JS.items()
if group_name != 'spec'
], []),
'output_filename': 'js/cms-test-source.js'
}
PIPELINE_JS['spec'] = {
'source_filenames': sorted(rooted_glob(PROJECT_ROOT / 'static/', 'coffee/spec/**/*.coffee')),
'output_filename': 'js/cms-spec.js'
}
JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee'
STATICFILES_DIRS.append(COMMON_ROOT / 'test' / 'phantom-jasmine' / 'lib')
INSTALLED_APPS += ('django_jasmine', )
...@@ -8,72 +8,6 @@ describe "CMS", -> ...@@ -8,72 +8,6 @@ describe "CMS", ->
it "should initialize Views", -> it "should initialize Views", ->
expect(CMS.Views).toBeDefined() expect(CMS.Views).toBeDefined()
describe "start", ->
beforeEach ->
@element = $("<div>")
spyOn(CMS.Views, "Course").andReturn(jasmine.createSpyObj("Course", ["render"]))
CMS.start(@element)
it "create the Course", ->
expect(CMS.Views.Course).toHaveBeenCalledWith(el: @element)
expect(CMS.Views.Course().render).toHaveBeenCalled()
describe "view stack", ->
beforeEach ->
@currentView = jasmine.createSpy("currentView")
CMS.viewStack = [@currentView]
describe "replaceView", ->
beforeEach ->
@newView = jasmine.createSpy("newView")
CMS.on("content.show", (@expectedView) =>)
CMS.replaceView(@newView)
it "replace the views on the viewStack", ->
expect(CMS.viewStack).toEqual([@newView])
it "trigger content.show on CMS", ->
expect(@expectedView).toEqual(@newView)
describe "pushView", ->
beforeEach ->
@newView = jasmine.createSpy("newView")
CMS.on("content.show", (@expectedView) =>)
CMS.pushView(@newView)
it "push new view onto viewStack", ->
expect(CMS.viewStack).toEqual([@currentView, @newView])
it "trigger content.show on CMS", ->
expect(@expectedView).toEqual(@newView)
describe "popView", ->
it "remove the current view from the viewStack", ->
CMS.popView()
expect(CMS.viewStack).toEqual([])
describe "when there's no view on the viewStack", ->
beforeEach ->
CMS.viewStack = [@currentView]
CMS.on("content.hide", => @eventTriggered = true)
CMS.popView()
it "trigger content.hide on CMS", ->
expect(@eventTriggered).toBeTruthy
describe "when there's previous view on the viewStack", ->
beforeEach ->
@parentView = jasmine.createSpyObj("parentView", ["delegateEvents"])
CMS.viewStack = [@parentView, @currentView]
CMS.on("content.show", (@expectedView) =>)
CMS.popView()
it "trigger content.show with the previous view on CMS", ->
expect(@expectedView).toEqual @parentView
it "re-bind events on the view", ->
expect(@parentView.delegateEvents).toHaveBeenCalled()
describe "main helper", -> describe "main helper", ->
beforeEach -> beforeEach ->
@previousAjaxSettings = $.extend(true, {}, $.ajaxSettings) @previousAjaxSettings = $.extend(true, {}, $.ajaxSettings)
......
...@@ -3,75 +3,4 @@ describe "CMS.Models.Module", -> ...@@ -3,75 +3,4 @@ describe "CMS.Models.Module", ->
expect(new CMS.Models.Module().url).toEqual("/save_item") expect(new CMS.Models.Module().url).toEqual("/save_item")
it "set the correct default", -> it "set the correct default", ->
expect(new CMS.Models.Module().defaults).toEqual({data: ""}) expect(new CMS.Models.Module().defaults).toEqual(undefined)
describe "loadModule", ->
describe "when the module exists", ->
beforeEach ->
@fakeModule = jasmine.createSpy("fakeModuleObject")
window.FakeModule = jasmine.createSpy("FakeModule").andReturn(@fakeModule)
@module = new CMS.Models.Module(type: "FakeModule")
@stubDiv = $('<div />')
@stubElement = $('<div class="xmodule_edit" />')
@stubElement.data('type', "FakeModule")
@stubDiv.append(@stubElement)
@module.loadModule(@stubDiv)
afterEach ->
window.FakeModule = undefined
it "initialize the module", ->
expect(window.FakeModule).toHaveBeenCalled()
# Need to compare underlying nodes, because jquery selectors
# aren't equal even when they point to the same node.
# http://stackoverflow.com/questions/9505437/how-to-test-jquery-with-jasmine-for-element-id-if-used-as-this
expectedNode = @stubElement[0]
actualNode = window.FakeModule.mostRecentCall.args[0][0]
expect(actualNode).toEqual(expectedNode)
expect(@module.module).toEqual(@fakeModule)
describe "when the module does not exists", ->
beforeEach ->
@previousConsole = window.console
window.console = jasmine.createSpyObj("fakeConsole", ["error"])
@module = new CMS.Models.Module(type: "HTML")
@module.loadModule($("<div>"))
afterEach ->
window.console = @previousConsole
it "print out error to log", ->
expect(window.console.error).toHaveBeenCalled()
expect(window.console.error.mostRecentCall.args[0]).toMatch("^Unable to load")
describe "editUrl", ->
it "construct the correct URL based on id", ->
expect(new CMS.Models.Module(id: "i4x://mit.edu/module/html_123").editUrl())
.toEqual("/edit_item?id=i4x%3A%2F%2Fmit.edu%2Fmodule%2Fhtml_123")
describe "save", ->
beforeEach ->
spyOn(Backbone.Model.prototype, "save")
@module = new CMS.Models.Module()
describe "when the module exists", ->
beforeEach ->
@module.module = jasmine.createSpyObj("FakeModule", ["save"])
@module.module.save.andReturn("module data")
@module.save()
it "set the data and call save on the module", ->
expect(@module.get("data")).toEqual("\"module data\"")
it "call save on the backbone model", ->
expect(Backbone.Model.prototype.save).toHaveBeenCalled()
describe "when the module does not exists", ->
beforeEach ->
@module.save()
it "call save on the backbone model", ->
expect(Backbone.Model.prototype.save).toHaveBeenCalled()
describe "CMS.Views.Course", ->
beforeEach ->
setFixtures """
<section id="main-section">
<section class="main-content"></section>
<ol id="weeks">
<li class="cal week-one" style="height: 50px"></li>
<li class="cal week-two" style="height: 100px"></li>
</ol>
</section>
"""
CMS.unbind()
describe "render", ->
beforeEach ->
spyOn(CMS.Views, "Week").andReturn(jasmine.createSpyObj("Week", ["render"]))
new CMS.Views.Course(el: $("#main-section")).render()
it "create week view for each week",->
expect(CMS.Views.Week.calls[0].args[0])
.toEqual({ el: $(".week-one").get(0), height: 101 })
expect(CMS.Views.Week.calls[1].args[0])
.toEqual({ el: $(".week-two").get(0), height: 101 })
describe "on content.show", ->
beforeEach ->
@view = new CMS.Views.Course(el: $("#main-section"))
@subView = jasmine.createSpyObj("subView", ["render"])
@subView.render.andReturn(el: "Subview Content")
spyOn(@view, "contentHeight").andReturn(100)
CMS.trigger("content.show", @subView)
afterEach ->
$("body").removeClass("content")
it "add content class to body", ->
expect($("body").attr("class")).toEqual("content")
it "replace content in .main-content", ->
expect($(".main-content")).toHaveHtml("Subview Content")
it "set height on calendar", ->
expect($(".cal")).toHaveCss(height: "100px")
it "set minimum height on all sections", ->
expect($("#main-section>section")).toHaveCss(minHeight: "100px")
describe "on content.hide", ->
beforeEach ->
$("body").addClass("content")
@view = new CMS.Views.Course(el: $("#main-section"))
$(".cal").css(height: 100)
$("#main-section>section").css(minHeight: 100)
CMS.trigger("content.hide")
afterEach ->
$("body").removeClass("content")
it "remove content class from body", ->
expect($("body").attr("class")).toEqual("")
it "remove content from .main-content", ->
expect($(".main-content")).toHaveHtml("")
it "reset height on calendar", ->
expect($(".cal")).not.toHaveCss(height: "100px")
it "reset minimum height on all sections", ->
expect($("#main-section>section")).not.toHaveCss(minHeight: "100px")
describe "maxWeekHeight", ->
it "return maximum height of the week element", ->
@view = new CMS.Views.Course(el: $("#main-section"))
expect(@view.maxWeekHeight()).toEqual(101)
describe "contentHeight", ->
beforeEach ->
$("body").append($('<header id="test">').height(100).hide())
afterEach ->
$("body>header#test").remove()
it "return the window height minus the header bar", ->
@view = new CMS.Views.Course(el: $("#main-section"))
expect(@view.contentHeight()).toEqual($(window).height() - 100)
describe "CMS.Views.ModuleEdit", -> describe "CMS.Views.ModuleEdit", ->
beforeEach -> beforeEach ->
@stubModule = jasmine.createSpyObj("Module", ["editUrl", "loadModule"]) @stubModule = jasmine.createSpy("CMS.Models.Module")
spyOn($.fn, "load") @stubModule.id = 'stub-id'
setFixtures """ setFixtures """
<div id="module-edit"> <li class="component" id="stub-id">
<a href="#" class="save-update">save</a> <div class="component-editor">
<a href="#" class="cancel">cancel</a> <div class="module-editor">
<ol> ${editor}
<li>
<a href="#" class="module-edit" data-id="i4x://mitx/course/html/module" data-type="html">submodule</a>
</li>
</ol>
</div> </div>
""" #" <a href="#" class="save-button">Save</a>
CMS.unbind() <a href="#" class="cancel-button">Cancel</a>
</div>
describe "defaults", -> <div class="component-actions">
it "set the correct tagName", -> <a href="#" class="edit-button"><span class="edit-icon white"></span>Edit</a>
expect(new CMS.Views.ModuleEdit(model: @stubModule).tagName).toEqual("section") <a href="#" class="delete-button"><span class="delete-icon white"></span>Delete</a>
</div>
<a href="#" class="drag-handle"></a>
<section class="xmodule_display xmodule_stub" data-type="StubModule">
<div id="stub-module-content"/>
</section>
</li>
"""
spyOn($.fn, 'load').andReturn(@moduleData)
it "set the correct className", -> @moduleEdit = new CMS.Views.ModuleEdit(
expect(new CMS.Views.ModuleEdit(model: @stubModule).className).toEqual("edit-pane") el: $(".component")
model: @stubModule
onDelete: jasmine.createSpy()
)
CMS.unbind()
describe "view creation", -> describe "class definition", ->
beforeEach -> it "sets the correct tagName", ->
@stubModule.editUrl.andReturn("/edit_item?id=stub_module") expect(@moduleEdit.tagName).toEqual("li")
new CMS.Views.ModuleEdit(el: $("#module-edit"), model: @stubModule)
it "load the edit via ajax and pass to the model", -> it "sets the correct className", ->
expect($.fn.load).toHaveBeenCalledWith("/edit_item?id=stub_module", jasmine.any(Function)) expect(@moduleEdit.className).toEqual("component")
if $.fn.load.mostRecentCall
$.fn.load.mostRecentCall.args[1]()
expect(@stubModule.loadModule).toHaveBeenCalledWith($("#module-edit").get(0))
describe "save", -> describe "methods", ->
describe "initialize", ->
beforeEach -> beforeEach ->
@stubJqXHR = jasmine.createSpy("stubJqXHR") spyOn(CMS.Views.ModuleEdit.prototype, 'render')
@stubJqXHR.success = jasmine.createSpy("stubJqXHR.success").andReturn(@stubJqXHR) @moduleEdit = new CMS.Views.ModuleEdit(
@stubJqXHR.error = jasmine.createSpy("stubJqXHR.error").andReturn(@stubJqXHR) el: $(".component")
@stubModule.save = jasmine.createSpy("stubModule.save").andReturn(@stubJqXHR) model: @stubModule
new CMS.Views.ModuleEdit(el: $(".module-edit"), model: @stubModule) onDelete: jasmine.createSpy()
spyOn(window, "alert") )
$(".save-update").click()
it "call save on the model", ->
expect(@stubModule.save).toHaveBeenCalled()
it "alert user on success", ->
@stubJqXHR.success.mostRecentCall.args[0]()
expect(window.alert).toHaveBeenCalledWith("Your changes have been saved.")
it "alert user on error", -> it "renders the module editor", ->
@stubJqXHR.error.mostRecentCall.args[0]() expect(@moduleEdit.render).toHaveBeenCalled()
expect(window.alert).toHaveBeenCalledWith("There was an error saving your changes. Please try again.")
describe "cancel", -> describe "render", ->
beforeEach -> beforeEach ->
spyOn(CMS, "popView") spyOn(@moduleEdit, 'loadDisplay')
@view = new CMS.Views.ModuleEdit(el: $("#module-edit"), model: @stubModule) spyOn(@moduleEdit, 'delegateEvents')
$(".cancel").click() @moduleEdit.render()
it "pop current view from viewStack", -> it "loads the module preview and editor via ajax on the view element", ->
expect(CMS.popView).toHaveBeenCalled() expect(@moduleEdit.$el.load).toHaveBeenCalledWith("/preview_component/#{@moduleEdit.model.id}", jasmine.any(Function))
@moduleEdit.$el.load.mostRecentCall.args[1]()
expect(@moduleEdit.loadDisplay).toHaveBeenCalled()
expect(@moduleEdit.delegateEvents).toHaveBeenCalled()
describe "editSubmodule", -> describe "loadDisplay", ->
beforeEach -> beforeEach ->
@view = new CMS.Views.ModuleEdit(el: $("#module-edit"), model: @stubModule) spyOn(XModule, 'loadModule')
spyOn(CMS, "pushView") @moduleEdit.loadDisplay()
spyOn(CMS.Views, "ModuleEdit")
.andReturn(@view = jasmine.createSpy("Views.ModuleEdit"))
spyOn(CMS.Models, "Module")
.andReturn(@model = jasmine.createSpy("Models.Module"))
$(".module-edit").click()
it "push another module editing view into viewStack", -> it "loads the .xmodule-display inside the module editor", ->
expect(CMS.pushView).toHaveBeenCalledWith @view expect(XModule.loadModule).toHaveBeenCalled()
expect(CMS.Views.ModuleEdit).toHaveBeenCalledWith model: @model expect(XModule.loadModule.mostRecentCall.args[0]).toBe($('.xmodule_display'))
expect(CMS.Models.Module).toHaveBeenCalledWith
id: "i4x://mitx/course/html/module"
type: "html"
describe "CMS.Views.Module", ->
beforeEach ->
setFixtures """
<div id="module" data-id="i4x://mitx/course/html/module" data-type="html">
<a href="#" class="module-edit">edit</a>
</div>
"""
describe "edit", ->
beforeEach ->
@view = new CMS.Views.Module(el: $("#module"))
spyOn(CMS, "replaceView")
spyOn(CMS.Views, "ModuleEdit")
.andReturn(@view = jasmine.createSpy("Views.ModuleEdit"))
spyOn(CMS.Models, "Module")
.andReturn(@model = jasmine.createSpy("Models.Module"))
$(".module-edit").click()
it "replace the main view with ModuleEdit view", ->
expect(CMS.replaceView).toHaveBeenCalledWith @view
expect(CMS.Views.ModuleEdit).toHaveBeenCalledWith model: @model
expect(CMS.Models.Module).toHaveBeenCalledWith
id: "i4x://mitx/course/html/module"
type: "html"
describe "CMS.Views.WeekEdit", ->
describe "defaults", ->
it "set the correct tagName", ->
expect(new CMS.Views.WeekEdit().tagName).toEqual("section")
it "set the correct className", ->
expect(new CMS.Views.WeekEdit().className).toEqual("edit-pane")
describe "CMS.Views.Week", ->
beforeEach ->
setFixtures """
<div id="week" data-id="i4x://mitx/course/chapter/week">
<div class="editable"></div>
<textarea class="editable-textarea"></textarea>
<a href="#" class="week-edit" >edit</a>
<ul class="modules">
<li id="module-one" class="module"></li>
<li id="module-two" class="module"></li>
</ul>
</div>
"""
CMS.unbind()
describe "render", ->
beforeEach ->
spyOn(CMS.Views, "Module").andReturn(jasmine.createSpyObj("Module", ["render"]))
$.fn.inlineEdit = jasmine.createSpy("$.fn.inlineEdit")
@view = new CMS.Views.Week(el: $("#week"), height: 100).render()
it "set the height of the element", ->
expect(@view.el).toHaveCss(height: "100px")
it "make .editable as inline editor", ->
expect($.fn.inlineEdit.calls[0].object.get(0))
.toEqual($(".editable").get(0))
it "make .editable-test as inline editor", ->
expect($.fn.inlineEdit.calls[1].object.get(0))
.toEqual($(".editable-textarea").get(0))
it "create module subview for each module", ->
expect(CMS.Views.Module.calls[0].args[0])
.toEqual({ el: $("#module-one").get(0) })
expect(CMS.Views.Module.calls[1].args[0])
.toEqual({ el: $("#module-two").get(0) })
describe "edit", ->
beforeEach ->
new CMS.Views.Week(el: $("#week"), height: 100).render()
spyOn(CMS, "replaceView")
spyOn(CMS.Views, "WeekEdit")
.andReturn(@view = jasmine.createSpy("Views.WeekEdit"))
$(".week-edit").click()
it "replace the content with edit week view", ->
expect(CMS.replaceView).toHaveBeenCalledWith @view
expect(CMS.Views.WeekEdit).toHaveBeenCalled()
describe "on content.show", ->
beforeEach ->
@view = new CMS.Views.Week(el: $("#week"), height: 100).render()
@view.$el.height("")
@view.setHeight()
it "set the correct height", ->
expect(@view.el).toHaveCss(height: "100px")
describe "on content.hide", ->
beforeEach ->
@view = new CMS.Views.Week(el: $("#week"), height: 100).render()
@view.$el.height("100px")
@view.resetHeight()
it "remove height from the element", ->
expect(@view.el).not.toHaveCss(height: "100px")
...@@ -6,28 +6,6 @@ AjaxPrefix.addAjaxPrefix(jQuery, -> CMS.prefix) ...@@ -6,28 +6,6 @@ AjaxPrefix.addAjaxPrefix(jQuery, -> CMS.prefix)
prefix: $("meta[name='path_prefix']").attr('content') prefix: $("meta[name='path_prefix']").attr('content')
viewStack: []
start: (el) ->
new CMS.Views.Course(el: el).render()
replaceView: (view) ->
@viewStack = [view]
CMS.trigger('content.show', view)
pushView: (view) ->
@viewStack.push(view)
CMS.trigger('content.show', view)
popView: ->
@viewStack.pop()
if _.isEmpty(@viewStack)
CMS.trigger('content.hide')
else
view = _.last(@viewStack)
CMS.trigger('content.show', view)
view.delegateEvents()
_.extend CMS, Backbone.Events _.extend CMS, Backbone.Events
$ -> $ ->
...@@ -41,7 +19,3 @@ $ -> ...@@ -41,7 +19,3 @@ $ ->
navigator.userAgent.match /iPhone|iPod|iPad/i navigator.userAgent.match /iPhone|iPod|iPad/i
$('body').addClass 'touch-based-device' if onTouchBasedDevice() $('body').addClass 'touch-based-device' if onTouchBasedDevice()
CMS.start($('section.main-container'))
class CMS.Models.NewModule extends Backbone.Model
url: '/clone_item'
newUrl: ->
"/new_item?#{$.param(parent_location: @get('parent_location'))}"
class CMS.Views.Course extends Backbone.View
initialize: ->
CMS.on('content.show', @showContent)
CMS.on('content.hide', @hideContent)
render: ->
@$('#weeks > li').each (index, week) =>
new CMS.Views.Week(el: week, height: @maxWeekHeight()).render()
return @
showContent: (subview) =>
$('body').addClass('content')
@$('.main-content').html(subview.render().el)
@$('.cal').css height: @contentHeight()
@$('>section').css minHeight: @contentHeight()
hideContent: =>
$('body').removeClass('content')
@$('.main-content').empty()
@$('.cal').css height: ''
@$('>section').css minHeight: ''
maxWeekHeight: ->
weekElementBorderSize = 1
_.max($('#weeks > li').map -> $(this).height()) + weekElementBorderSize
contentHeight: ->
$(window).height() - $('body>header').outerHeight()
class CMS.Views.Module extends Backbone.View
events:
"click .module-edit": "edit"
edit: (event) =>
event.preventDefault()
previewType = @$el.data('preview-type')
moduleType = @$el.data('type')
CMS.replaceView new CMS.Views.ModuleEdit
model: new CMS.Models.Module
id: @$el.data('id')
type: if moduleType == 'None' then null else moduleType
previewType: if previewType == 'None' then null else previewType
class CMS.Views.ModuleAdd extends Backbone.View
tagName: 'section'
className: 'add-pane'
events:
'click .cancel': 'cancel'
'click .save': 'save'
initialize: ->
@$el.load @model.newUrl()
save: (event) ->
event.preventDefault()
@model.save({
name: @$el.find('.name').val()
template: $(event.target).data('template-id')
}, {
success: -> CMS.popView()
error: -> alert('Create failed')
})
cancel: (event) ->
event.preventDefault()
CMS.popView()
...@@ -169,12 +169,21 @@ class CMS.Views.UnitEdit.NameEdit extends Backbone.View ...@@ -169,12 +169,21 @@ class CMS.Views.UnitEdit.NameEdit extends Backbone.View
initialize: => initialize: =>
@model.on('change:metadata', @render) @model.on('change:metadata', @render)
@model.on('change:state', @setEnabled)
@setEnabled()
@saveName @saveName
@$spinner = $('<span class="spinner-in-field-icon"></span>'); @$spinner = $('<span class="spinner-in-field-icon"></span>');
render: => render: =>
@$('.unit-display-name-input').val(@model.get('metadata').display_name) @$('.unit-display-name-input').val(@model.get('metadata').display_name)
setEnabled: =>
disabled = @model.get('state') == 'public'
if disabled
@$('.unit-display-name-input').attr('disabled', true)
else
@$('.unit-display-name-input').removeAttr('disabled')
saveName: => saveName: =>
# Treat the metadata dictionary as immutable # Treat the metadata dictionary as immutable
metadata = $.extend({}, @model.get('metadata')) metadata = $.extend({}, @model.get('metadata'))
...@@ -191,6 +200,7 @@ class CMS.Views.UnitEdit.NameEdit extends Backbone.View ...@@ -191,6 +200,7 @@ class CMS.Views.UnitEdit.NameEdit extends Backbone.View
'margin-top': '-10px' 'margin-top': '-10px'
}); });
inputField.after(@$spinner); inputField.after(@$spinner);
@$spinner.fadeIn(10)
# save the name after a slight delay # save the name after a slight delay
if @timer if @timer
......
class CMS.Views.Week extends Backbone.View
events:
'click .week-edit': 'edit'
'click .new-module': 'new'
initialize: ->
CMS.on('content.show', @resetHeight)
CMS.on('content.hide', @setHeight)
render: ->
@setHeight()
@$('.editable').inlineEdit()
@$('.editable-textarea').inlineEdit(control: 'textarea')
@$('.modules .module').each ->
new CMS.Views.Module(el: this).render()
return @
edit: (event) ->
event.preventDefault()
CMS.replaceView(new CMS.Views.WeekEdit())
setHeight: =>
@$el.height(@options.height)
resetHeight: =>
@$el.height('')
new: (event) =>
event.preventDefault()
CMS.replaceView new CMS.Views.ModuleAdd
model: new CMS.Models.NewModule
parent_location: @$el.data('id')
class CMS.Views.WeekEdit extends Backbone.View
tagName: 'section'
className: 'edit-pane'
...@@ -69,7 +69,7 @@ urlpatterns += ( ...@@ -69,7 +69,7 @@ urlpatterns += (
) )
if settings.DEBUG: if settings.ENABLE_JASMINE:
## Jasmine ## Jasmine
urlpatterns = urlpatterns + (url(r'^_jasmine/', include('django_jasmine.urls')),) urlpatterns = urlpatterns + (url(r'^_jasmine/', include('django_jasmine.urls')),)
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
<script src="{% static 'jasmine-latest/jasmine.js' %}"></script> <script src="{% static 'jasmine-latest/jasmine.js' %}"></script>
<script src="{% static 'jasmine-latest/jasmine-html.js' %}"></script> <script src="{% static 'jasmine-latest/jasmine-html.js' %}"></script>
<script src="{% static 'js/vendor/jasmine-jquery.js' %}"></script> <script src="{% static 'js/vendor/jasmine-jquery.js' %}"></script>
<script src="{% static 'console-runner.js' %}"></script>
{# source files #} {# source files #}
{% for url in suite.js_files %} {% for url in suite.js_files %}
...@@ -19,7 +20,7 @@ ...@@ -19,7 +20,7 @@
{% load compressed %} {% load compressed %}
{# static files #} {# static files #}
{% compressed_js 'main' %} {% compressed_js 'js-test-source' %}
{# spec files #} {# spec files #}
{% compressed_js 'spec' %} {% compressed_js 'spec' %}
...@@ -31,6 +32,7 @@ ...@@ -31,6 +32,7 @@
<script> <script>
{% block jasmine %} {% block jasmine %}
var console_reporter = new jasmine.ConsoleReporter();
(function() { (function() {
var jasmineEnv = jasmine.getEnv(); var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000; jasmineEnv.updateInterval = 1000;
...@@ -38,6 +40,7 @@ ...@@ -38,6 +40,7 @@
var trivialReporter = new jasmine.TrivialReporter(); var trivialReporter = new jasmine.TrivialReporter();
jasmineEnv.addReporter(trivialReporter); jasmineEnv.addReporter(trivialReporter);
jasmine.getEnv().addReporter(console_reporter);
jasmineEnv.specFilter = function(spec) { jasmineEnv.specFilter = function(spec) {
return trivialReporter.specFilter(spec); return trivialReporter.specFilter(spec);
......
Subproject commit a54d435b5556650efbcdb0490e6c7928ac75238a
# Running the CMS # Development Tasks
One can start the CMS by running `rake cms`. This will run the server on localhost ## Prerequisites
port 8001.
However, the server also needs data to work from. ### Ruby
## Installing Mongodb To install all of the libraries needed for our rake commands, run `bundle install`.
This will read the `Gemfile` and install all of the gems specified there.
Please see http://www.mongodb.org/downloads for more detailed instructions. ### Python
### Ubuntu In order, run the following:
sudo apt-get install mongodb pip install -r pre-requirements.txt
pip install -r requirements.txt
pip install -r test-requirements.txt
### OSX ### Binaries
Use the MacPorts package `mongodb` or the Homebrew formula `mongodb` Install the following:
## Initializing Mongodb * Mongodb (http://www.mongodb.org/)
Check out the course data directories that you want to work with into the ### Databases
`GITHUB_REPO_ROOT` (by default, `../data`). Then run the following command:
Run the following to setup the relational database before starting servers:
rake django-admin[import,cms,dev,../data] rake resetdb
Replace `../data` with your `GITHUB_REPO_ROOT` if it's not the default value. ## Starting development servers
This will import all courses in your data directory into mongodb Both the LMS and Studio can be started using the following shortcut tasks
## Unit tests rake lms # Start the LMS
rake cms # Start studio
rake lms[cms.dev] # Start LMS to run alongside Studio
rake lms[cms.dev_preview] # Start LMS to run alongside Studio in preview mode
Under the hood, this executes `django-admin.py runserver --pythonpath=$WORKING_DIRECTORY --settings=lms.envs.dev`,
which starts a local development server.
Both of these commands take arguments to start the servers in different environments
or with additional options:
# Start the LMS using the test configuration, on port 5000
rake lms[test,5000] # Executes django-admin.py runserver --pythonpath=$WORKING_DIRECTORY --setings=lms.envs.test 5000
*N.B.* You may have to escape the `[` characters, depending on your shell: `rake "lms[test,5000]"`
## Running tests
### Python Tests
This runs all the tests (long, uses collectstatic): This runs all the tests (long, uses collectstatic):
...@@ -43,10 +63,6 @@ xmodule can be tested independently, with this: ...@@ -43,10 +63,6 @@ xmodule can be tested independently, with this:
rake test_common/lib/xmodule rake test_common/lib/xmodule
To see all available rake commands, do this:
rake -T
To run a single django test class: To run a single django test class:
django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/courseware/tests/tests.py:TestViewAuth django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/courseware/tests/tests.py:TestViewAuth
...@@ -67,6 +83,28 @@ To run a single nose test: ...@@ -67,6 +83,28 @@ To run a single nose test:
Very handy: if you uncomment the `--pdb` argument in `NOSE_ARGS` in `lms/envs/test.py`, it will drop you into pdb on error. This lets you go up and down the stack and see what the values of the variables are. Check out http://docs.python.org/library/pdb.html Very handy: if you uncomment the `--pdb` argument in `NOSE_ARGS` in `lms/envs/test.py`, it will drop you into pdb on error. This lets you go up and down the stack and see what the values of the variables are. Check out http://docs.python.org/library/pdb.html
### Javascript Tests
These commands start a development server with jasmine testing enabled, and launch your default browser
pointing to those tests
rake browse_jasmine_{lms,cms}
To run the tests headless, you must install phantomjs (http://phantomjs.org/download.html).
rake phantomjs_jasmine_{lms,cms}
If the `phantomjs` binary is not on the path, set the `PHANTOMJS_PATH` environment variable to point to it
PHANTOMJS_PATH=/path/to/phantomjs rake phantomjs_jasmine_{lms,cms}
## Getting More Information
Run the following to see a list of all rake tasks available and their arguments
rake -T
## Content development ## Content development
If you change course content, while running the LMS in dev mode, it is unnecessary to restart to refresh the modulestore. If you change course content, while running the LMS in dev mode, it is unnecessary to restart to refresh the modulestore.
......
...@@ -30,6 +30,7 @@ from .discussionsettings import * ...@@ -30,6 +30,7 @@ from .discussionsettings import *
################################### FEATURES ################################### ################################### FEATURES ###################################
COURSEWARE_ENABLED = True COURSEWARE_ENABLED = True
ENABLE_JASMINE = False
GENERATE_RANDOM_USER_CREDENTIALS = False GENERATE_RANDOM_USER_CREDENTIALS = False
PERFSTATS = False PERFSTATS = False
...@@ -446,16 +447,13 @@ PIPELINE_JS = { ...@@ -446,16 +447,13 @@ PIPELINE_JS = {
'source_filenames': module_js, 'source_filenames': module_js,
'output_filename': 'js/lms-modules.js', 'output_filename': 'js/lms-modules.js',
}, },
'spec': {
'source_filenames': sorted(rooted_glob(PROJECT_ROOT / 'static/', 'coffee/spec/**/*.coffee')),
'output_filename': 'js/lms-spec.js'
},
'discussion': { 'discussion': {
'source_filenames': discussion_js, 'source_filenames': discussion_js,
'output_filename': 'js/discussion.js' 'output_filename': 'js/discussion.js'
} },
} }
PIPELINE_DISABLE_WRAPPER = True PIPELINE_DISABLE_WRAPPER = True
# Compile all coffee files in course data directories if they are out of date. # Compile all coffee files in course data directories if they are out of date.
...@@ -540,9 +538,6 @@ INSTALLED_APPS = ( ...@@ -540,9 +538,6 @@ INSTALLED_APPS = (
'wiki.plugins.notifications', 'wiki.plugins.notifications',
'course_wiki.plugins.markdownedx', 'course_wiki.plugins.markdownedx',
# For testing
'django_jasmine',
# Discussion # Discussion
'django_comment_client', 'django_comment_client',
......
"""
This configuration is used for running jasmine tests
"""
from .test import *
from logsettings import get_logger_config
ENABLE_JASMINE = True
DEBUG = True
LOGGING = get_logger_config(TEST_ROOT / "log",
logging_env="dev",
tracking_filename="tracking.log",
dev_env=True,
debug=True)
PIPELINE_JS['js-test-source'] = {
'source_filenames': sum([
pipeline_group['source_filenames']
for group_name, pipeline_group
in PIPELINE_JS.items()
if group_name != 'spec'
], []),
'output_filename': 'js/lms-test-source.js'
}
PIPELINE_JS['spec'] = {
'source_filenames': sorted(rooted_glob(PROJECT_ROOT / 'static/', 'coffee/spec/**/*.coffee')),
'output_filename': 'js/lms-spec.js'
}
JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee'
STATICFILES_DIRS.append(COMMON_ROOT / 'test' / 'phantom-jasmine' / 'lib')
INSTALLED_APPS += ('django_jasmine', )
<!doctype html>
<html>
<head>
<title>Jasmine Spec Runner</title>
{% load staticfiles %}
<link rel="stylesheet" href="{% static 'jasmine-latest/jasmine.css' %}" media="screen">
{# core files #}
<script src="{% static 'jasmine-latest/jasmine.js' %}"></script>
<script src="{% static 'jasmine-latest/jasmine-html.js' %}"></script>
<script src="{% static 'js/vendor/jasmine-jquery.js' %}"></script>
{# source files #}
{% for url in suite.js_files %}
<script src="{{ url }}"></script>
{% endfor %}
{% load compressed %}
{# static files #}
{% compressed_js 'application' %}
{% compressed_js 'module-js' %}
{# spec files #}
{% compressed_js 'spec' %}
</head>
<body>
<h1>Jasmine Spec Runner</h1>
<script>
{% block jasmine %}
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var trivialReporter = new jasmine.TrivialReporter();
jasmineEnv.addReporter(trivialReporter);
jasmineEnv.specFilter = function(spec) {
return trivialReporter.specFilter(spec);
};
// Additional configuration can be done in this block
{% block jasmine_extra %}{% endblock %}
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
}
})();
{% endblock %}
</script>
</body>
</html>
...@@ -242,7 +242,7 @@ if settings.QUICKEDIT: ...@@ -242,7 +242,7 @@ if settings.QUICKEDIT:
urlpatterns += (url(r'^dogfood/(?P<id>[^/]*)$', 'dogfood.views.df_capa_problem'),) urlpatterns += (url(r'^dogfood/(?P<id>[^/]*)$', 'dogfood.views.df_capa_problem'),)
if settings.DEBUG: if settings.ENABLE_JASMINE:
## Jasmine and admin ## Jasmine and admin
urlpatterns=urlpatterns + (url(r'^_jasmine/', include('django_jasmine.urls')), urlpatterns=urlpatterns + (url(r'^_jasmine/', include('django_jasmine.urls')),
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', include(admin.site.urls)),
......
require 'rake/clean' require 'rake/clean'
require 'tempfile' require 'tempfile'
require 'net/http'
require 'launchy'
require 'colorize'
# Build Constants # Build Constants
REPO_ROOT = File.dirname(__FILE__) REPO_ROOT = File.dirname(__FILE__)
...@@ -38,6 +41,43 @@ def django_admin(system, env, command, *args) ...@@ -38,6 +41,43 @@ def django_admin(system, env, command, *args)
return "#{django_admin} #{command} --settings=#{system}.envs.#{env} --pythonpath=. #{args.join(' ')}" return "#{django_admin} #{command} --settings=#{system}.envs.#{env} --pythonpath=. #{args.join(' ')}"
end end
def django_for_jasmine(system, django_reload)
if !django_reload
reload_arg = '--noreload'
end
django_pid = fork do
exec(*django_admin(system, 'jasmine', 'runserver', "12345", reload_arg).split(' '))
end
jasmine_url = 'http://localhost:12345/_jasmine/'
up = false
start_time = Time.now
until up do
if Time.now - start_time > 30
abort "Timed out waiting for server to start to run jasmine tests"
end
begin
response = Net::HTTP.get_response(URI(jasmine_url))
puts response.code
up = response.code == '200'
rescue => e
puts e.message
ensure
puts('Waiting server to start')
sleep(0.5)
end
end
begin
yield jasmine_url
ensure
if django_reload
Process.kill(:SIGKILL, -Process.getpgid(django_pid))
else
Process.kill(:SIGKILL, django_pid)
end
Process.wait(django_pid)
end
end
task :default => [:test, :pep8, :pylint] task :default => [:test, :pep8, :pylint]
directory REPORT_DIR directory REPORT_DIR
...@@ -80,6 +120,23 @@ end ...@@ -80,6 +120,23 @@ end
end end
end end
task :pylint => "pylint_#{system}" task :pylint => "pylint_#{system}"
desc "Open jasmine tests in your default browser"
task "browse_jasmine_#{system}" do
django_for_jasmine(system, true) do |jasmine_url|
Launchy.open(jasmine_url)
puts "Press ENTER to terminate".red
$stdin.gets
end
end
desc "Use phantomjs to run jasmine tests from the console"
task "phantomjs_jasmine_#{system}" do
phantomjs = ENV['PHANTOMJS_PATH'] || 'phantomjs'
django_for_jasmine(system, false) do |jasmine_url|
sh("#{phantomjs} common/test/phantom-jasmine/lib/run_jasmine_test.coffee #{jasmine_url}")
end
end
end end
$failed_tests = 0 $failed_tests = 0
...@@ -139,6 +196,20 @@ TEST_TASKS = [] ...@@ -139,6 +196,20 @@ TEST_TASKS = []
end end
end end
desc "Reset the relational database used by django. WARNING: this will delete all of your existing users"
task :resetdb, [:env] do |t, args|
args.with_defaults(:env => 'dev')
sh(django_admin(:lms, args.env, 'syncdb'))
sh(django_admin(:lms, args.env, 'migrate'))
end
desc "Update the relational database to the latest migration"
task :migrate, [:env] do |t, args|
args.with_defaults(:env => 'dev')
sh(django_admin(:lms, args.env, 'migrate'))
end
Dir["common/lib/*"].each do |lib| Dir["common/lib/*"].each do |lib|
task_name = "test_#{lib}" task_name = "test_#{lib}"
......
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