Merge pull request #997 from MITx/feature/cale/js-tests
Feature/cale/js tests
This commit is contained in:
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -0,0 +1,3 @@
|
||||
[submodule "common/test/phantom-jasmine"]
|
||||
path = common/test/phantom-jasmine
|
||||
url = https://github.com/jcarver989/phantom-jasmine.git
|
||||
|
||||
2
Gemfile
2
Gemfile
@@ -3,3 +3,5 @@ ruby "1.9.3"
|
||||
gem 'rake'
|
||||
gem 'sass', '3.1.15'
|
||||
gem 'bourbon', '~> 1.3.6'
|
||||
gem 'colorize'
|
||||
gem 'launchy'
|
||||
|
||||
@@ -35,6 +35,7 @@ MITX_FEATURES = {
|
||||
'ENABLE_DISCUSSION_SERVICE': False,
|
||||
'AUTH_USE_MIT_CERTIFICATES' : False,
|
||||
}
|
||||
ENABLE_JASMINE = False
|
||||
|
||||
# needed to use lms student app
|
||||
GENERATE_RANDOM_USER_CREDENTIALS = False
|
||||
@@ -68,9 +69,7 @@ MAKO_TEMPLATES['main'] = [
|
||||
for namespace, template_dirs in lms.envs.common.MAKO_TEMPLATES.iteritems():
|
||||
MAKO_TEMPLATES['lms.' + namespace] = template_dirs
|
||||
|
||||
TEMPLATE_DIRS = (
|
||||
PROJECT_ROOT / "templates",
|
||||
)
|
||||
TEMPLATE_DIRS = MAKO_TEMPLATES['main']
|
||||
|
||||
MITX_ROOT_URL = ''
|
||||
|
||||
@@ -88,10 +87,6 @@ TEMPLATE_CONTEXT_PROCESSORS = (
|
||||
|
||||
LMS_BASE = None
|
||||
|
||||
################################# Jasmine ###################################
|
||||
JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee'
|
||||
|
||||
|
||||
#################### CAPA External Code Evaluation #############################
|
||||
XQUEUE_INTERFACE = {
|
||||
'url': 'http://localhost:8888',
|
||||
@@ -289,7 +284,4 @@ INSTALLED_APPS = (
|
||||
# For asset pipelining
|
||||
'pipeline',
|
||||
'staticfiles',
|
||||
|
||||
# For testing
|
||||
'django_jasmine',
|
||||
)
|
||||
|
||||
36
cms/envs/jasmine.py
Normal file
36
cms/envs/jasmine.py
Normal file
@@ -0,0 +1,36 @@
|
||||
"""
|
||||
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", ->
|
||||
it "should initialize Views", ->
|
||||
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", ->
|
||||
beforeEach ->
|
||||
@previousAjaxSettings = $.extend(true, {}, $.ajaxSettings)
|
||||
|
||||
@@ -3,75 +3,4 @@ describe "CMS.Models.Module", ->
|
||||
expect(new CMS.Models.Module().url).toEqual("/save_item")
|
||||
|
||||
it "set the correct default", ->
|
||||
expect(new CMS.Models.Module().defaults).toEqual({data: ""})
|
||||
|
||||
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()
|
||||
expect(new CMS.Models.Module().defaults).toEqual(undefined)
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
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)
|
||||
@@ -1,81 +1,74 @@
|
||||
describe "CMS.Views.ModuleEdit", ->
|
||||
beforeEach ->
|
||||
@stubModule = jasmine.createSpyObj("Module", ["editUrl", "loadModule"])
|
||||
spyOn($.fn, "load")
|
||||
@stubModule = jasmine.createSpy("CMS.Models.Module")
|
||||
@stubModule.id = 'stub-id'
|
||||
|
||||
|
||||
setFixtures """
|
||||
<div id="module-edit">
|
||||
<a href="#" class="save-update">save</a>
|
||||
<a href="#" class="cancel">cancel</a>
|
||||
<ol>
|
||||
<li>
|
||||
<a href="#" class="module-edit" data-id="i4x://mitx/course/html/module" data-type="html">submodule</a>
|
||||
</li>
|
||||
</ol>
|
||||
<li class="component" id="stub-id">
|
||||
<div class="component-editor">
|
||||
<div class="module-editor">
|
||||
${editor}
|
||||
</div>
|
||||
<a href="#" class="save-button">Save</a>
|
||||
<a href="#" class="cancel-button">Cancel</a>
|
||||
</div>
|
||||
""" #"
|
||||
<div class="component-actions">
|
||||
<a href="#" class="edit-button"><span class="edit-icon white"></span>Edit</a>
|
||||
<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)
|
||||
|
||||
@moduleEdit = new CMS.Views.ModuleEdit(
|
||||
el: $(".component")
|
||||
model: @stubModule
|
||||
onDelete: jasmine.createSpy()
|
||||
)
|
||||
CMS.unbind()
|
||||
|
||||
describe "defaults", ->
|
||||
it "set the correct tagName", ->
|
||||
expect(new CMS.Views.ModuleEdit(model: @stubModule).tagName).toEqual("section")
|
||||
describe "class definition", ->
|
||||
it "sets the correct tagName", ->
|
||||
expect(@moduleEdit.tagName).toEqual("li")
|
||||
|
||||
it "set the correct className", ->
|
||||
expect(new CMS.Views.ModuleEdit(model: @stubModule).className).toEqual("edit-pane")
|
||||
it "sets the correct className", ->
|
||||
expect(@moduleEdit.className).toEqual("component")
|
||||
|
||||
describe "view creation", ->
|
||||
beforeEach ->
|
||||
@stubModule.editUrl.andReturn("/edit_item?id=stub_module")
|
||||
new CMS.Views.ModuleEdit(el: $("#module-edit"), model: @stubModule)
|
||||
describe "methods", ->
|
||||
describe "initialize", ->
|
||||
beforeEach ->
|
||||
spyOn(CMS.Views.ModuleEdit.prototype, 'render')
|
||||
@moduleEdit = new CMS.Views.ModuleEdit(
|
||||
el: $(".component")
|
||||
model: @stubModule
|
||||
onDelete: jasmine.createSpy()
|
||||
)
|
||||
|
||||
it "load the edit via ajax and pass to the model", ->
|
||||
expect($.fn.load).toHaveBeenCalledWith("/edit_item?id=stub_module", jasmine.any(Function))
|
||||
if $.fn.load.mostRecentCall
|
||||
$.fn.load.mostRecentCall.args[1]()
|
||||
expect(@stubModule.loadModule).toHaveBeenCalledWith($("#module-edit").get(0))
|
||||
it "renders the module editor", ->
|
||||
expect(@moduleEdit.render).toHaveBeenCalled()
|
||||
|
||||
describe "save", ->
|
||||
beforeEach ->
|
||||
@stubJqXHR = jasmine.createSpy("stubJqXHR")
|
||||
@stubJqXHR.success = jasmine.createSpy("stubJqXHR.success").andReturn(@stubJqXHR)
|
||||
@stubJqXHR.error = jasmine.createSpy("stubJqXHR.error").andReturn(@stubJqXHR)
|
||||
@stubModule.save = jasmine.createSpy("stubModule.save").andReturn(@stubJqXHR)
|
||||
new CMS.Views.ModuleEdit(el: $(".module-edit"), model: @stubModule)
|
||||
spyOn(window, "alert")
|
||||
$(".save-update").click()
|
||||
describe "render", ->
|
||||
beforeEach ->
|
||||
spyOn(@moduleEdit, 'loadDisplay')
|
||||
spyOn(@moduleEdit, 'delegateEvents')
|
||||
@moduleEdit.render()
|
||||
|
||||
it "call save on the model", ->
|
||||
expect(@stubModule.save).toHaveBeenCalled()
|
||||
it "loads the module preview and editor via ajax on the view element", ->
|
||||
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()
|
||||
|
||||
it "alert user on success", ->
|
||||
@stubJqXHR.success.mostRecentCall.args[0]()
|
||||
expect(window.alert).toHaveBeenCalledWith("Your changes have been saved.")
|
||||
describe "loadDisplay", ->
|
||||
beforeEach ->
|
||||
spyOn(XModule, 'loadModule')
|
||||
@moduleEdit.loadDisplay()
|
||||
|
||||
it "alert user on error", ->
|
||||
@stubJqXHR.error.mostRecentCall.args[0]()
|
||||
expect(window.alert).toHaveBeenCalledWith("There was an error saving your changes. Please try again.")
|
||||
|
||||
describe "cancel", ->
|
||||
beforeEach ->
|
||||
spyOn(CMS, "popView")
|
||||
@view = new CMS.Views.ModuleEdit(el: $("#module-edit"), model: @stubModule)
|
||||
$(".cancel").click()
|
||||
|
||||
it "pop current view from viewStack", ->
|
||||
expect(CMS.popView).toHaveBeenCalled()
|
||||
|
||||
describe "editSubmodule", ->
|
||||
beforeEach ->
|
||||
@view = new CMS.Views.ModuleEdit(el: $("#module-edit"), model: @stubModule)
|
||||
spyOn(CMS, "pushView")
|
||||
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", ->
|
||||
expect(CMS.pushView).toHaveBeenCalledWith @view
|
||||
expect(CMS.Views.ModuleEdit).toHaveBeenCalledWith model: @model
|
||||
expect(CMS.Models.Module).toHaveBeenCalledWith
|
||||
id: "i4x://mitx/course/html/module"
|
||||
type: "html"
|
||||
it "loads the .xmodule-display inside the module editor", ->
|
||||
expect(XModule.loadModule).toHaveBeenCalled()
|
||||
expect(XModule.loadModule.mostRecentCall.args[0]).toBe($('.xmodule_display'))
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
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"
|
||||
@@ -1,7 +0,0 @@
|
||||
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")
|
||||
@@ -1,67 +0,0 @@
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
$ ->
|
||||
@@ -41,7 +19,3 @@ $ ->
|
||||
navigator.userAgent.match /iPhone|iPod|iPad/i
|
||||
|
||||
$('body').addClass 'touch-based-device' if onTouchBasedDevice()
|
||||
|
||||
|
||||
CMS.start($('section.main-container'))
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
class CMS.Models.NewModule extends Backbone.Model
|
||||
url: '/clone_item'
|
||||
|
||||
newUrl: ->
|
||||
"/new_item?#{$.param(parent_location: @get('parent_location'))}"
|
||||
@@ -1,28 +0,0 @@
|
||||
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()
|
||||
@@ -1,14 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
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()
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
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')
|
||||
@@ -1,3 +0,0 @@
|
||||
class CMS.Views.WeekEdit extends Backbone.View
|
||||
tagName: 'section'
|
||||
className: 'edit-pane'
|
||||
@@ -1,66 +0,0 @@
|
||||
<!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 'main' %}
|
||||
|
||||
{# 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>
|
||||
@@ -68,7 +68,7 @@ urlpatterns += (
|
||||
|
||||
)
|
||||
|
||||
if settings.DEBUG:
|
||||
if settings.ENABLE_JASMINE:
|
||||
## Jasmine
|
||||
urlpatterns = urlpatterns + (url(r'^_jasmine/', include('django_jasmine.urls')),)
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<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>
|
||||
<script src="{% static 'console-runner.js' %}"></script>
|
||||
|
||||
{# source files #}
|
||||
{% for url in suite.js_files %}
|
||||
@@ -19,8 +20,7 @@
|
||||
|
||||
{% load compressed %}
|
||||
{# static files #}
|
||||
{% compressed_js 'application' %}
|
||||
{% compressed_js 'module-js' %}
|
||||
{% compressed_js 'js-test-source' %}
|
||||
|
||||
{# spec files #}
|
||||
{% compressed_js 'spec' %}
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
<script>
|
||||
{% block jasmine %}
|
||||
var console_reporter = new jasmine.ConsoleReporter();
|
||||
(function() {
|
||||
var jasmineEnv = jasmine.getEnv();
|
||||
jasmineEnv.updateInterval = 1000;
|
||||
@@ -39,6 +40,7 @@
|
||||
var trivialReporter = new jasmine.TrivialReporter();
|
||||
|
||||
jasmineEnv.addReporter(trivialReporter);
|
||||
jasmine.getEnv().addReporter(console_reporter);
|
||||
|
||||
jasmineEnv.specFilter = function(spec) {
|
||||
return trivialReporter.specFilter(spec);
|
||||
1
common/test/phantom-jasmine
Submodule
1
common/test/phantom-jasmine
Submodule
Submodule common/test/phantom-jasmine added at a54d435b55
@@ -1,35 +1,55 @@
|
||||
# Running the CMS
|
||||
# Development Tasks
|
||||
|
||||
One can start the CMS by running `rake cms`. This will run the server on localhost
|
||||
port 8001.
|
||||
## Prerequisites
|
||||
|
||||
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
|
||||
`GITHUB_REPO_ROOT` (by default, `../data`). Then run the following command:
|
||||
### Databases
|
||||
|
||||
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):
|
||||
|
||||
@@ -43,10 +63,6 @@ xmodule can be tested independently, with this:
|
||||
|
||||
rake test_common/lib/xmodule
|
||||
|
||||
To see all available rake commands, do this:
|
||||
|
||||
rake -T
|
||||
|
||||
To run a single django test class:
|
||||
|
||||
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:
|
||||
|
||||
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
|
||||
|
||||
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 *
|
||||
|
||||
################################### FEATURES ###################################
|
||||
COURSEWARE_ENABLED = True
|
||||
ENABLE_JASMINE = False
|
||||
GENERATE_RANDOM_USER_CREDENTIALS = False
|
||||
PERFSTATS = False
|
||||
|
||||
@@ -446,16 +447,13 @@ PIPELINE_JS = {
|
||||
'source_filenames': module_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': {
|
||||
'source_filenames': discussion_js,
|
||||
'output_filename': 'js/discussion.js'
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
PIPELINE_DISABLE_WRAPPER = True
|
||||
|
||||
# Compile all coffee files in course data directories if they are out of date.
|
||||
@@ -540,9 +538,6 @@ INSTALLED_APPS = (
|
||||
'wiki.plugins.notifications',
|
||||
'course_wiki.plugins.markdownedx',
|
||||
|
||||
# For testing
|
||||
'django_jasmine',
|
||||
|
||||
# Discussion
|
||||
'django_comment_client',
|
||||
|
||||
|
||||
36
lms/envs/jasmine.py
Normal file
36
lms/envs/jasmine.py
Normal file
@@ -0,0 +1,36 @@
|
||||
"""
|
||||
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', )
|
||||
@@ -242,7 +242,7 @@ if settings.QUICKEDIT:
|
||||
urlpatterns += (url(r'^dogfood/(?P<id>[^/]*)$', 'dogfood.views.df_capa_problem'),)
|
||||
|
||||
|
||||
if settings.DEBUG:
|
||||
if settings.ENABLE_JASMINE:
|
||||
## Jasmine and admin
|
||||
urlpatterns=urlpatterns + (url(r'^_jasmine/', include('django_jasmine.urls')),
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
|
||||
73
rakefile
73
rakefile
@@ -1,5 +1,8 @@
|
||||
require 'rake/clean'
|
||||
require 'tempfile'
|
||||
require 'net/http'
|
||||
require 'launchy'
|
||||
require 'colorize'
|
||||
|
||||
# Build Constants
|
||||
REPO_ROOT = File.dirname(__FILE__)
|
||||
@@ -38,6 +41,43 @@ def django_admin(system, env, command, *args)
|
||||
return "#{django_admin} #{command} --settings=#{system}.envs.#{env} --pythonpath=. #{args.join(' ')}"
|
||||
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]
|
||||
|
||||
directory REPORT_DIR
|
||||
@@ -80,6 +120,23 @@ end
|
||||
end
|
||||
end
|
||||
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
|
||||
|
||||
$failed_tests = 0
|
||||
@@ -139,6 +196,20 @@ TEST_TASKS = []
|
||||
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|
|
||||
task_name = "test_#{lib}"
|
||||
|
||||
@@ -270,4 +341,4 @@ task :doc => :builddocs do
|
||||
Dir.chdir('docs/build/html') do
|
||||
sh('open index.html')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user