Merge pull request #1951 from edx/christina/fix-xhr
Workaround for "xhr.restore" failures.
This commit is contained in:
@@ -42,9 +42,10 @@ requirejs.config({
|
||||
|
||||
"mathjax": "//edx-static.s3.amazonaws.com/mathjax-MathJax-727332c/MathJax.js?config=TeX-MML-AM_HTMLorMML-full&delayStartupUntil=configured",
|
||||
"youtube": "//www.youtube.com/player_api?noext",
|
||||
"tender": "//edxedge.tenderapp.com/tender_widget"
|
||||
"tender": "//edxedge.tenderapp.com/tender_widget",
|
||||
|
||||
"coffee/src/ajax_prefix": "xmodule_js/common_static/coffee/src/ajax_prefix"
|
||||
"coffee/src/ajax_prefix": "xmodule_js/common_static/coffee/src/ajax_prefix",
|
||||
"js/spec/test_utils": "js/spec/test_utils",
|
||||
}
|
||||
shim: {
|
||||
"gettext": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
require ["jquery", "backbone", "coffee/src/main", "sinon", "jasmine-stealth", "jquery.cookie"],
|
||||
($, Backbone, main, sinon) ->
|
||||
require ["jquery", "backbone", "coffee/src/main", "js/spec/create_sinon", "jasmine-stealth", "jquery.cookie"],
|
||||
($, Backbone, main, create_sinon) ->
|
||||
describe "CMS", ->
|
||||
it "should initialize URL", ->
|
||||
expect(window.CMS.URL).toBeDefined()
|
||||
@@ -26,30 +26,30 @@ require ["jquery", "backbone", "coffee/src/main", "sinon", "jasmine-stealth", "j
|
||||
beforeEach ->
|
||||
setFixtures($("<script>", {id: "system-feedback-tpl", type: "text/template"}).text(tpl))
|
||||
appendSetFixtures(sandbox({id: "page-notification"}))
|
||||
@requests = requests = []
|
||||
@xhr = sinon.useFakeXMLHttpRequest()
|
||||
@xhr.onCreate = (xhr) -> requests.push(xhr)
|
||||
|
||||
afterEach ->
|
||||
@xhr.restore()
|
||||
|
||||
it "successful AJAX request does not pop an error notification", ->
|
||||
server = create_sinon['server'](200, this)
|
||||
|
||||
expect($("#page-notification")).toBeEmpty()
|
||||
$.ajax("/test")
|
||||
expect($("#page-notification")).toBeEmpty()
|
||||
@requests[0].respond(200)
|
||||
server.respond()
|
||||
expect($("#page-notification")).toBeEmpty()
|
||||
|
||||
it "AJAX request with error should pop an error notification", ->
|
||||
server = create_sinon['server'](500, this)
|
||||
|
||||
$.ajax("/test")
|
||||
@requests[0].respond(500)
|
||||
server.respond()
|
||||
expect($("#page-notification")).not.toBeEmpty()
|
||||
expect($("#page-notification")).toContain('div.wrapper-notification-error')
|
||||
|
||||
it "can override AJAX request with error so it does not pop an error notification", ->
|
||||
server = create_sinon['server'](500, this)
|
||||
|
||||
$.ajax
|
||||
url: "/test"
|
||||
notifyOnError: false
|
||||
@requests[0].respond(500)
|
||||
expect($("#page-notification")).toBeEmpty()
|
||||
|
||||
server.respond()
|
||||
expect($("#page-notification")).toBeEmpty()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
define ["js/models/section", "sinon", "js/utils/module"], (Section, sinon, ModuleUtils) ->
|
||||
define ["js/models/section", "js/spec/create_sinon", "js/utils/module"], (Section, create_sinon, ModuleUtils) ->
|
||||
describe "Section", ->
|
||||
describe "basic", ->
|
||||
beforeEach ->
|
||||
@@ -32,21 +32,19 @@ define ["js/models/section", "sinon", "js/utils/module"], (Section, sinon, Modul
|
||||
id: 42
|
||||
name: "Life, the Universe, and Everything"
|
||||
})
|
||||
@requests = requests = []
|
||||
@xhr = sinon.useFakeXMLHttpRequest()
|
||||
@xhr.onCreate = (xhr) -> requests.push(xhr)
|
||||
|
||||
afterEach ->
|
||||
@xhr.restore()
|
||||
|
||||
it "show/hide a notification when it saves to the server", ->
|
||||
server = create_sinon['server'](200, this)
|
||||
|
||||
@model.save()
|
||||
expect(Section.prototype.showNotification).toHaveBeenCalled()
|
||||
@requests[0].respond(200)
|
||||
server.respond()
|
||||
expect(Section.prototype.hideNotification).toHaveBeenCalled()
|
||||
|
||||
it "don't hide notification when saving fails", ->
|
||||
# this is handled by the global AJAX error handler
|
||||
server = create_sinon['server'](500, this)
|
||||
|
||||
@model.save()
|
||||
@requests[0].respond(500)
|
||||
server.respond()
|
||||
expect(Section.prototype.hideNotification).not.toHaveBeenCalled()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
define ["jasmine", "sinon", "squire"],
|
||||
(jasmine, sinon, Squire) ->
|
||||
define ["jasmine", "js/spec/create_sinon", "squire"],
|
||||
(jasmine, create_sinon, Squire) ->
|
||||
|
||||
feedbackTpl = readFixtures('system-feedback.underscore')
|
||||
assetTpl = readFixtures('asset.underscore')
|
||||
@@ -68,26 +68,20 @@ define ["jasmine", "sinon", "squire"],
|
||||
expect(@collection).toContain(@model)
|
||||
|
||||
describe "AJAX", ->
|
||||
beforeEach ->
|
||||
@requests = requests = []
|
||||
@xhr = sinon.useFakeXMLHttpRequest()
|
||||
@xhr.onCreate = (xhr) -> requests.push(xhr)
|
||||
|
||||
afterEach ->
|
||||
@xhr.restore()
|
||||
|
||||
it "should destroy itself on confirmation", ->
|
||||
requests = create_sinon["requests"](this)
|
||||
|
||||
@view.render().$(".remove-asset-button").click()
|
||||
ctorOptions = @promptSpies.constructor.mostRecentCall.args[0]
|
||||
# run the primary function to indicate confirmation
|
||||
ctorOptions.actions.primary.click(@promptSpies)
|
||||
# AJAX request has been sent, but not yet returned
|
||||
expect(@model.destroy).toHaveBeenCalled()
|
||||
expect(@requests.length).toEqual(1)
|
||||
expect(requests.length).toEqual(1)
|
||||
expect(@confirmationSpies.constructor).not.toHaveBeenCalled()
|
||||
expect(@collection.contains(@model)).toBeTruthy()
|
||||
# return a success response
|
||||
@requests[0].respond(200)
|
||||
requests[0].respond(200)
|
||||
expect(@confirmationSpies.constructor).toHaveBeenCalled()
|
||||
expect(@confirmationSpies.show).toHaveBeenCalled()
|
||||
savingOptions = @confirmationSpies.constructor.mostRecentCall.args[0]
|
||||
@@ -95,6 +89,8 @@ define ["jasmine", "sinon", "squire"],
|
||||
expect(@collection.contains(@model)).toBeFalsy()
|
||||
|
||||
it "should not destroy itself if server errors", ->
|
||||
requests = create_sinon["requests"](this)
|
||||
|
||||
@view.render().$(".remove-asset-button").click()
|
||||
ctorOptions = @promptSpies.constructor.mostRecentCall.args[0]
|
||||
# run the primary function to indicate confirmation
|
||||
@@ -102,29 +98,33 @@ define ["jasmine", "sinon", "squire"],
|
||||
# AJAX request has been sent, but not yet returned
|
||||
expect(@model.destroy).toHaveBeenCalled()
|
||||
# return an error response
|
||||
@requests[0].respond(404)
|
||||
requests[0].respond(404)
|
||||
expect(@confirmationSpies.constructor).not.toHaveBeenCalled()
|
||||
expect(@collection.contains(@model)).toBeTruthy()
|
||||
|
||||
it "should lock the asset on confirmation", ->
|
||||
requests = create_sinon["requests"](this)
|
||||
|
||||
@view.render().$(".lock-checkbox").click()
|
||||
# AJAX request has been sent, but not yet returned
|
||||
expect(@model.save).toHaveBeenCalled()
|
||||
expect(@requests.length).toEqual(1)
|
||||
expect(requests.length).toEqual(1)
|
||||
expect(@savingSpies.constructor).toHaveBeenCalled()
|
||||
expect(@savingSpies.show).toHaveBeenCalled()
|
||||
savingOptions = @savingSpies.constructor.mostRecentCall.args[0]
|
||||
expect(savingOptions.title).toMatch("Saving...")
|
||||
expect(@model.get("locked")).toBeFalsy()
|
||||
# return a success response
|
||||
@requests[0].respond(200)
|
||||
requests[0].respond(200)
|
||||
expect(@savingSpies.hide).toHaveBeenCalled()
|
||||
expect(@model.get("locked")).toBeTruthy()
|
||||
|
||||
it "should not lock the asset if server errors", ->
|
||||
requests = create_sinon["requests"](this)
|
||||
|
||||
@view.render().$(".lock-checkbox").click()
|
||||
# return an error response
|
||||
@requests[0].respond(404)
|
||||
requests[0].respond(404)
|
||||
# Don't call hide because that closes the notification showing the server error.
|
||||
expect(@savingSpies.hide).not.toHaveBeenCalled()
|
||||
expect(@model.get("locked")).toBeFalsy()
|
||||
@@ -172,9 +172,6 @@ define ["jasmine", "sinon", "squire"],
|
||||
waitsFor (=> @view), "AssetView was not created", 1000
|
||||
|
||||
$.ajax()
|
||||
@requests = requests = []
|
||||
@xhr = sinon.useFakeXMLHttpRequest()
|
||||
@xhr.onCreate = (xhr) -> requests.push(xhr)
|
||||
|
||||
afterEach ->
|
||||
delete window.analytics
|
||||
@@ -190,18 +187,22 @@ define ["jasmine", "sinon", "squire"],
|
||||
expect(@view.$el).toContainText("test asset 2")
|
||||
|
||||
it "should remove the deleted asset from the view", ->
|
||||
requests = create_sinon["requests"](this)
|
||||
|
||||
# Delete the 2nd asset with success from server.
|
||||
@view.render().$(".remove-asset-button")[1].click()
|
||||
@promptSpies.constructor.mostRecentCall.args[0].actions.primary.click(@promptSpies)
|
||||
req.respond(200) for req in @requests
|
||||
req.respond(200) for req in requests
|
||||
expect(@view.$el).toContainText("test asset 1")
|
||||
expect(@view.$el).not.toContainText("test asset 2")
|
||||
|
||||
it "does not remove asset if deletion failed", ->
|
||||
requests = create_sinon["requests"](this)
|
||||
|
||||
# Delete the 2nd asset, but mimic a failure from the server.
|
||||
@view.render().$(".remove-asset-button")[1].click()
|
||||
@promptSpies.constructor.mostRecentCall.args[0].actions.primary.click(@promptSpies)
|
||||
req.respond(404) for req in @requests
|
||||
req.respond(404) for req in requests
|
||||
expect(@view.$el).toContainText("test asset 1")
|
||||
expect(@view.$el).toContainText("test asset 2")
|
||||
|
||||
|
||||
@@ -1,217 +1,206 @@
|
||||
define ["js/views/course_info_handout", "js/views/course_info_update", "js/models/module_info", "js/collections/course_update", "sinon"],
|
||||
(CourseInfoHandoutsView, CourseInfoUpdateView, ModuleInfo, CourseUpdateCollection, sinon) ->
|
||||
define ["js/views/course_info_handout", "js/views/course_info_update", "js/models/module_info", "js/collections/course_update", "js/spec/create_sinon"],
|
||||
(CourseInfoHandoutsView, CourseInfoUpdateView, ModuleInfo, CourseUpdateCollection, create_sinon) ->
|
||||
|
||||
courseInfoPage = """
|
||||
<div class="course-info-wrapper">
|
||||
<div class="main-column window">
|
||||
<article class="course-updates" id="course-update-view">
|
||||
<ol class="update-list" id="course-update-list"></ol>
|
||||
</article>
|
||||
</div>
|
||||
<div class="sidebar window course-handouts" id="course-handouts-view"></div>
|
||||
</div>
|
||||
<div class="modal-cover"></div>
|
||||
"""
|
||||
|
||||
beforeEach ->
|
||||
window.analytics = jasmine.createSpyObj('analytics', ['track'])
|
||||
window.course_location_analytics = jasmine.createSpy()
|
||||
|
||||
afterEach ->
|
||||
delete window.analytics
|
||||
delete window.course_location_analytics
|
||||
|
||||
xdescribe "Course Updates", ->
|
||||
courseInfoTemplate = readFixtures('course_info_update.underscore')
|
||||
describe "Course Updates and Handouts", ->
|
||||
courseInfoPage = """
|
||||
<div class="course-info-wrapper">
|
||||
<div class="main-column window">
|
||||
<article class="course-updates" id="course-update-view">
|
||||
<ol class="update-list" id="course-update-list"></ol>
|
||||
</article>
|
||||
</div>
|
||||
<div class="sidebar window course-handouts" id="course-handouts-view"></div>
|
||||
</div>
|
||||
<div class="modal-cover"></div>
|
||||
"""
|
||||
|
||||
beforeEach ->
|
||||
setFixtures($("<script>", {id: "course_info_update-tpl", type: "text/template"}).text(courseInfoTemplate))
|
||||
appendSetFixtures courseInfoPage
|
||||
window.analytics = jasmine.createSpyObj('analytics', ['track'])
|
||||
window.course_location_analytics = jasmine.createSpy()
|
||||
|
||||
courseUpdatesXhr = sinon.useFakeXMLHttpRequest()
|
||||
@courseUpdatesRequests = requests = []
|
||||
courseUpdatesXhr.onCreate = (xhr) -> requests.push(xhr)
|
||||
@xhrRestore = courseUpdatesXhr.restore
|
||||
afterEach ->
|
||||
delete window.analytics
|
||||
delete window.course_location_analytics
|
||||
|
||||
@collection = new CourseUpdateCollection()
|
||||
@collection.url = 'course_info_update/'
|
||||
@courseInfoEdit = new CourseInfoUpdateView({
|
||||
el: $('.course-updates'),
|
||||
collection: @collection,
|
||||
base_asset_url : 'base-asset-url/'
|
||||
})
|
||||
describe "Course Updates", ->
|
||||
courseInfoTemplate = readFixtures('course_info_update.underscore')
|
||||
|
||||
@courseInfoEdit.render()
|
||||
beforeEach ->
|
||||
setFixtures($("<script>", {id: "course_info_update-tpl", type: "text/template"}).text(courseInfoTemplate))
|
||||
appendSetFixtures courseInfoPage
|
||||
|
||||
@event = {
|
||||
preventDefault : () -> 'no op'
|
||||
}
|
||||
@collection = new CourseUpdateCollection()
|
||||
@collection.url = 'course_info_update/'
|
||||
@courseInfoEdit = new CourseInfoUpdateView({
|
||||
el: $('.course-updates'),
|
||||
collection: @collection,
|
||||
base_asset_url : 'base-asset-url/'
|
||||
})
|
||||
|
||||
@createNewUpdate = (text) ->
|
||||
# Edit button is not in the template under test (it is in parent HTML).
|
||||
# Therefore call onNew directly.
|
||||
@courseInfoEdit.render()
|
||||
|
||||
@event = {
|
||||
preventDefault : () -> 'no op'
|
||||
}
|
||||
|
||||
@createNewUpdate = (text) ->
|
||||
# Edit button is not in the template under test (it is in parent HTML).
|
||||
# Therefore call onNew directly.
|
||||
@courseInfoEdit.onNew(@event)
|
||||
spyOn(@courseInfoEdit.$codeMirror, 'getValue').andReturn(text)
|
||||
@courseInfoEdit.$el.find('.save-button').click()
|
||||
|
||||
@cancelNewCourseInfo = (useCancelButton) ->
|
||||
@courseInfoEdit.onNew(@event)
|
||||
spyOn(@courseInfoEdit.$modalCover, 'hide').andCallThrough()
|
||||
|
||||
spyOn(@courseInfoEdit.$codeMirror, 'getValue').andReturn('unsaved changes')
|
||||
model = @collection.at(0)
|
||||
spyOn(model, "save").andCallThrough()
|
||||
|
||||
cancelEditingUpdate(@courseInfoEdit, @courseInfoEdit.$modalCover, useCancelButton)
|
||||
|
||||
expect(@courseInfoEdit.$modalCover.hide).toHaveBeenCalled()
|
||||
expect(model.save).not.toHaveBeenCalled()
|
||||
previewContents = @courseInfoEdit.$el.find('.update-contents').html()
|
||||
expect(previewContents).not.toEqual('unsaved changes')
|
||||
|
||||
@cancelExistingCourseInfo = (useCancelButton) ->
|
||||
@createNewUpdate('existing update')
|
||||
@courseInfoEdit.$el.find('.edit-button').click()
|
||||
spyOn(@courseInfoEdit.$modalCover, 'hide').andCallThrough()
|
||||
|
||||
spyOn(@courseInfoEdit.$codeMirror, 'getValue').andReturn('modification')
|
||||
model = @collection.at(0)
|
||||
spyOn(model, "save").andCallThrough()
|
||||
model.id = "saved_to_server"
|
||||
cancelEditingUpdate(@courseInfoEdit, @courseInfoEdit.$modalCover, useCancelButton)
|
||||
|
||||
expect(@courseInfoEdit.$modalCover.hide).toHaveBeenCalled()
|
||||
expect(model.save).not.toHaveBeenCalled()
|
||||
previewContents = @courseInfoEdit.$el.find('.update-contents').html()
|
||||
expect(previewContents).toEqual('existing update')
|
||||
|
||||
cancelEditingUpdate = (update, modalCover, useCancelButton) ->
|
||||
if useCancelButton
|
||||
update.$el.find('.cancel-button').click()
|
||||
else
|
||||
modalCover.click()
|
||||
|
||||
it "does not rewrite links on save", ->
|
||||
requests = create_sinon["requests"](this)
|
||||
|
||||
# Create a new update, verifying that the model is created
|
||||
# in the collection and save is called.
|
||||
expect(@collection.isEmpty()).toBeTruthy()
|
||||
@courseInfoEdit.onNew(@event)
|
||||
spyOn(@courseInfoEdit.$codeMirror, 'getValue').andReturn(text)
|
||||
expect(@collection.length).toEqual(1)
|
||||
model = @collection.at(0)
|
||||
spyOn(model, "save").andCallThrough()
|
||||
spyOn(@courseInfoEdit.$codeMirror, 'getValue').andReturn('/static/image.jpg')
|
||||
|
||||
# Click the "Save button."
|
||||
@courseInfoEdit.$el.find('.save-button').click()
|
||||
expect(model.save).toHaveBeenCalled()
|
||||
|
||||
@cancelNewCourseInfo = (useCancelButton) ->
|
||||
@courseInfoEdit.onNew(@event)
|
||||
spyOn(@courseInfoEdit.$modalCover, 'hide').andCallThrough()
|
||||
# Verify content sent to server does not have rewritten links.
|
||||
contentSaved = JSON.parse(requests[requests.length - 1].requestBody).content
|
||||
expect(contentSaved).toEqual('/static/image.jpg')
|
||||
|
||||
spyOn(@courseInfoEdit.$codeMirror, 'getValue').andReturn('unsaved changes')
|
||||
model = @collection.at(0)
|
||||
spyOn(model, "save").andCallThrough()
|
||||
it "does rewrite links for preview", ->
|
||||
# Create a new update.
|
||||
@createNewUpdate('/static/image.jpg')
|
||||
|
||||
cancelEditingUpdate(@courseInfoEdit, @courseInfoEdit.$modalCover, useCancelButton)
|
||||
|
||||
expect(@courseInfoEdit.$modalCover.hide).toHaveBeenCalled()
|
||||
expect(model.save).not.toHaveBeenCalled()
|
||||
# Verify the link is rewritten for preview purposes.
|
||||
previewContents = @courseInfoEdit.$el.find('.update-contents').html()
|
||||
expect(previewContents).not.toEqual('unsaved changes')
|
||||
expect(previewContents).toEqual('base-asset-url/image.jpg')
|
||||
|
||||
@cancelExistingCourseInfo = (useCancelButton) ->
|
||||
@createNewUpdate('existing update')
|
||||
it "shows static links in edit mode", ->
|
||||
@createNewUpdate('/static/image.jpg')
|
||||
|
||||
# Click edit and verify CodeMirror contents.
|
||||
@courseInfoEdit.$el.find('.edit-button').click()
|
||||
spyOn(@courseInfoEdit.$modalCover, 'hide').andCallThrough()
|
||||
expect(@courseInfoEdit.$codeMirror.getValue()).toEqual('/static/image.jpg')
|
||||
|
||||
spyOn(@courseInfoEdit.$codeMirror, 'getValue').andReturn('modification')
|
||||
model = @collection.at(0)
|
||||
spyOn(model, "save").andCallThrough()
|
||||
model.id = "saved_to_server"
|
||||
cancelEditingUpdate(@courseInfoEdit, @courseInfoEdit.$modalCover, useCancelButton)
|
||||
it "removes newly created course info on cancel", ->
|
||||
@cancelNewCourseInfo(true)
|
||||
|
||||
expect(@courseInfoEdit.$modalCover.hide).toHaveBeenCalled()
|
||||
expect(model.save).not.toHaveBeenCalled()
|
||||
previewContents = @courseInfoEdit.$el.find('.update-contents').html()
|
||||
expect(previewContents).toEqual('existing update')
|
||||
it "removes newly created course info on click outside modal", ->
|
||||
@cancelNewCourseInfo(false)
|
||||
|
||||
cancelEditingUpdate = (update, modalCover, useCancelButton) ->
|
||||
if useCancelButton
|
||||
update.$el.find('.cancel-button').click()
|
||||
else
|
||||
modalCover.click()
|
||||
it "does not remove existing course info on cancel", ->
|
||||
@cancelExistingCourseInfo(true)
|
||||
|
||||
afterEach ->
|
||||
@xhrRestore()
|
||||
it "does not remove existing course info on click outside modal", ->
|
||||
@cancelExistingCourseInfo(false)
|
||||
|
||||
it "does not rewrite links on save", ->
|
||||
# Create a new update, verifying that the model is created
|
||||
# in the collection and save is called.
|
||||
expect(@collection.isEmpty()).toBeTruthy()
|
||||
@courseInfoEdit.onNew(@event)
|
||||
expect(@collection.length).toEqual(1)
|
||||
model = @collection.at(0)
|
||||
spyOn(model, "save").andCallThrough()
|
||||
spyOn(@courseInfoEdit.$codeMirror, 'getValue').andReturn('/static/image.jpg')
|
||||
describe "Course Handouts", ->
|
||||
handoutsTemplate = readFixtures('course_info_handouts.underscore')
|
||||
|
||||
# Click the "Save button."
|
||||
@courseInfoEdit.$el.find('.save-button').click()
|
||||
expect(model.save).toHaveBeenCalled()
|
||||
beforeEach ->
|
||||
setFixtures($("<script>", {id: "course_info_handouts-tpl", type: "text/template"}).text(handoutsTemplate))
|
||||
appendSetFixtures courseInfoPage
|
||||
|
||||
# Verify content sent to server does not have rewritten links.
|
||||
contentSaved = JSON.parse(@courseUpdatesRequests[@courseUpdatesRequests.length - 1].requestBody).content
|
||||
expect(contentSaved).toEqual('/static/image.jpg')
|
||||
@model = new ModuleInfo({
|
||||
id: 'handouts-id',
|
||||
data: '/static/fromServer.jpg'
|
||||
})
|
||||
|
||||
it "does rewrite links for preview", ->
|
||||
# Create a new update.
|
||||
@createNewUpdate('/static/image.jpg')
|
||||
@handoutsEdit = new CourseInfoHandoutsView({
|
||||
el: $('#course-handouts-view'),
|
||||
model: @model,
|
||||
base_asset_url: 'base-asset-url/'
|
||||
});
|
||||
|
||||
# Verify the link is rewritten for preview purposes.
|
||||
previewContents = @courseInfoEdit.$el.find('.update-contents').html()
|
||||
expect(previewContents).toEqual('base-asset-url/image.jpg')
|
||||
@handoutsEdit.render()
|
||||
|
||||
it "shows static links in edit mode", ->
|
||||
@createNewUpdate('/static/image.jpg')
|
||||
it "does not rewrite links on save", ->
|
||||
requests = create_sinon["requests"](this)
|
||||
|
||||
# Click edit and verify CodeMirror contents.
|
||||
@courseInfoEdit.$el.find('.edit-button').click()
|
||||
expect(@courseInfoEdit.$codeMirror.getValue()).toEqual('/static/image.jpg')
|
||||
# Enter something in the handouts section, verifying that the model is saved
|
||||
# when "Save" is clicked.
|
||||
@handoutsEdit.$el.find('.edit-button').click()
|
||||
spyOn(@handoutsEdit.$codeMirror, 'getValue').andReturn('/static/image.jpg')
|
||||
spyOn(@model, "save").andCallThrough()
|
||||
@handoutsEdit.$el.find('.save-button').click()
|
||||
expect(@model.save).toHaveBeenCalled()
|
||||
|
||||
it "removes newly created course info on cancel", ->
|
||||
@cancelNewCourseInfo(true)
|
||||
contentSaved = JSON.parse(requests[requests.length - 1].requestBody).data
|
||||
expect(contentSaved).toEqual('/static/image.jpg')
|
||||
|
||||
it "removes newly created course info on click outside modal", ->
|
||||
@cancelNewCourseInfo(false)
|
||||
it "does rewrite links in initial content", ->
|
||||
expect(@handoutsEdit.$preview.html().trim()).toBe('base-asset-url/fromServer.jpg')
|
||||
|
||||
it "does not remove existing course info on cancel", ->
|
||||
@cancelExistingCourseInfo(true)
|
||||
it "does rewrite links after edit", ->
|
||||
# Edit handouts and save.
|
||||
@handoutsEdit.$el.find('.edit-button').click()
|
||||
spyOn(@handoutsEdit.$codeMirror, 'getValue').andReturn('/static/image.jpg')
|
||||
@handoutsEdit.$el.find('.save-button').click()
|
||||
|
||||
it "does not remove existing course info on click outside modal", ->
|
||||
@cancelExistingCourseInfo(false)
|
||||
# Verify preview text.
|
||||
expect(@handoutsEdit.$preview.html().trim()).toBe('base-asset-url/image.jpg')
|
||||
|
||||
xdescribe "Course Handouts", ->
|
||||
handoutsTemplate = readFixtures('course_info_handouts.underscore')
|
||||
it "shows static links in edit mode", ->
|
||||
# Click edit and verify CodeMirror contents.
|
||||
@handoutsEdit.$el.find('.edit-button').click()
|
||||
expect(@handoutsEdit.$codeMirror.getValue().trim()).toEqual('/static/fromServer.jpg')
|
||||
|
||||
beforeEach ->
|
||||
setFixtures($("<script>", {id: "course_info_handouts-tpl", type: "text/template"}).text(handoutsTemplate))
|
||||
appendSetFixtures courseInfoPage
|
||||
it "can open course handouts with bad html on edit", ->
|
||||
# Enter some bad html in handouts section, verifying that the
|
||||
# model/handoutform opens when "Edit" is clicked
|
||||
|
||||
courseHandoutsXhr = sinon.useFakeXMLHttpRequest()
|
||||
@handoutsRequests = requests = []
|
||||
courseHandoutsXhr.onCreate = (xhr) -> requests.push(xhr)
|
||||
@handoutsXhrRestore = courseHandoutsXhr.restore
|
||||
@model = new ModuleInfo({
|
||||
id: 'handouts-id',
|
||||
data: '<p><a href="[URL OF FILE]>[LINK TEXT]</a></p>'
|
||||
})
|
||||
@handoutsEdit = new CourseInfoHandoutsView({
|
||||
el: $('#course-handouts-view'),
|
||||
model: @model,
|
||||
base_asset_url: 'base-asset-url/'
|
||||
});
|
||||
@handoutsEdit.render()
|
||||
|
||||
@model = new ModuleInfo({
|
||||
id: 'handouts-id',
|
||||
data: '/static/fromServer.jpg'
|
||||
})
|
||||
|
||||
@handoutsEdit = new CourseInfoHandoutsView({
|
||||
el: $('#course-handouts-view'),
|
||||
model: @model,
|
||||
base_asset_url: 'base-asset-url/'
|
||||
});
|
||||
|
||||
@handoutsEdit.render()
|
||||
|
||||
afterEach ->
|
||||
@handoutsXhrRestore()
|
||||
|
||||
it "does not rewrite links on save", ->
|
||||
# Enter something in the handouts section, verifying that the model is saved
|
||||
# when "Save" is clicked.
|
||||
@handoutsEdit.$el.find('.edit-button').click()
|
||||
spyOn(@handoutsEdit.$codeMirror, 'getValue').andReturn('/static/image.jpg')
|
||||
spyOn(@model, "save").andCallThrough()
|
||||
@handoutsEdit.$el.find('.save-button').click()
|
||||
expect(@model.save).toHaveBeenCalled()
|
||||
|
||||
contentSaved = JSON.parse(@handoutsRequests[@handoutsRequests.length - 1].requestBody).data
|
||||
expect(contentSaved).toEqual('/static/image.jpg')
|
||||
|
||||
it "does rewrite links in initial content", ->
|
||||
expect(@handoutsEdit.$preview.html().trim()).toBe('base-asset-url/fromServer.jpg')
|
||||
|
||||
it "does rewrite links after edit", ->
|
||||
# Edit handouts and save.
|
||||
@handoutsEdit.$el.find('.edit-button').click()
|
||||
spyOn(@handoutsEdit.$codeMirror, 'getValue').andReturn('/static/image.jpg')
|
||||
@handoutsEdit.$el.find('.save-button').click()
|
||||
|
||||
# Verify preview text.
|
||||
expect(@handoutsEdit.$preview.html().trim()).toBe('base-asset-url/image.jpg')
|
||||
|
||||
it "shows static links in edit mode", ->
|
||||
# Click edit and verify CodeMirror contents.
|
||||
@handoutsEdit.$el.find('.edit-button').click()
|
||||
expect(@handoutsEdit.$codeMirror.getValue().trim()).toEqual('/static/fromServer.jpg')
|
||||
|
||||
it "can open course handouts with bad html on edit", ->
|
||||
# Enter some bad html in handouts section, verifying that the
|
||||
# model/handoutform opens when "Edit" is clicked
|
||||
|
||||
@model = new ModuleInfo({
|
||||
id: 'handouts-id',
|
||||
data: '<p><a href="[URL OF FILE]>[LINK TEXT]</a></p>'
|
||||
})
|
||||
@handoutsEdit = new CourseInfoHandoutsView({
|
||||
el: $('#course-handouts-view'),
|
||||
model: @model,
|
||||
base_asset_url: 'base-asset-url/'
|
||||
});
|
||||
@handoutsEdit.render()
|
||||
|
||||
expect($('.edit-handouts-form').is(':hidden')).toEqual(true)
|
||||
@handoutsEdit.$el.find('.edit-button').click()
|
||||
expect(@handoutsEdit.$codeMirror.getValue()).toEqual('<p><a href="[URL OF FILE]>[LINK TEXT]</a></p>')
|
||||
expect($('.edit-handouts-form').is(':hidden')).toEqual(false)
|
||||
expect($('.edit-handouts-form').is(':hidden')).toEqual(true)
|
||||
@handoutsEdit.$el.find('.edit-button').click()
|
||||
expect(@handoutsEdit.$codeMirror.getValue()).toEqual('<p><a href="[URL OF FILE]>[LINK TEXT]</a></p>')
|
||||
expect($('.edit-handouts-form').is(':hidden')).toEqual(false)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base", "date", "jquery.timepicker"],
|
||||
(Overview, Notification, sinon) ->
|
||||
define ["js/views/overview", "js/views/feedback_notification", "js/spec/create_sinon", "js/base", "date", "jquery.timepicker"],
|
||||
(Overview, Notification, create_sinon) ->
|
||||
|
||||
describe "Course Overview", ->
|
||||
beforeEach ->
|
||||
@@ -95,9 +95,6 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
|
||||
@notificationSpy = spyOn(Notification.Mini.prototype, 'show').andCallThrough()
|
||||
window.analytics = jasmine.createSpyObj('analytics', ['track'])
|
||||
window.course_location_analytics = jasmine.createSpy()
|
||||
@xhr = sinon.useFakeXMLHttpRequest()
|
||||
requests = @requests = []
|
||||
@xhr.onCreate = (req) -> requests.push(req)
|
||||
|
||||
Overview.overviewDragger.makeDraggable(
|
||||
'.unit',
|
||||
@@ -135,9 +132,11 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
|
||||
# expect(@requests[0].url).toEqual('/delete_item')
|
||||
|
||||
it "should not delete model when cancel is clicked", ->
|
||||
requests = create_sinon["requests"](this)
|
||||
|
||||
$('a.delete-section-button').click()
|
||||
$('a.action-secondary').click()
|
||||
expect(@requests.length).toEqual(0)
|
||||
expect(requests.length).toEqual(0)
|
||||
|
||||
# Fails sporadically in Jenkins.
|
||||
# it "should show a confirmation on delete", ->
|
||||
@@ -402,22 +401,19 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
|
||||
)
|
||||
expect($('#subsection-2')).not.toHaveClass('collapsed')
|
||||
|
||||
xdescribe "AJAX", ->
|
||||
describe "AJAX", ->
|
||||
beforeEach ->
|
||||
@requests = requests = []
|
||||
@xhr = sinon.useFakeXMLHttpRequest()
|
||||
@xhr.onCreate = (xhr) -> requests.push(xhr)
|
||||
|
||||
@savingSpies = spyOnConstructor(Notification, "Mini",
|
||||
["show", "hide"])
|
||||
@savingSpies.show.andReturn(@savingSpies)
|
||||
@clock = sinon.useFakeTimers()
|
||||
|
||||
afterEach ->
|
||||
@xhr.restore()
|
||||
@clock.restore()
|
||||
|
||||
it "should send an update on reorder", ->
|
||||
requests = create_sinon["requests"](this)
|
||||
|
||||
Overview.overviewDragger.dragState.dropDestination = $('#unit-4')
|
||||
Overview.overviewDragger.dragState.attachMethod = "after"
|
||||
Overview.overviewDragger.dragState.parentList = $('#subsection-2')
|
||||
@@ -431,7 +427,7 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
|
||||
null,
|
||||
{clientX: $('#unit-1').offset().left}
|
||||
)
|
||||
expect(@requests.length).toEqual(2)
|
||||
expect(requests.length).toEqual(2)
|
||||
expect(@savingSpies.constructor).toHaveBeenCalled()
|
||||
expect(@savingSpies.show).toHaveBeenCalled()
|
||||
expect(@savingSpies.hide).not.toHaveBeenCalled()
|
||||
@@ -440,11 +436,11 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
|
||||
expect($('#unit-1')).toHaveClass('was-dropped')
|
||||
# We expect 2 requests to be sent-- the first for removing Unit 1 from Subsection 1,
|
||||
# and the second for adding Unit 1 to the end of Subsection 2.
|
||||
expect(@requests[0].requestBody).toEqual('{"children":["second-unit-id","third-unit-id"]}')
|
||||
@requests[0].respond(200)
|
||||
expect(requests[0].requestBody).toEqual('{"children":["second-unit-id","third-unit-id"]}')
|
||||
requests[0].respond(200)
|
||||
expect(@savingSpies.hide).not.toHaveBeenCalled()
|
||||
expect(@requests[1].requestBody).toEqual('{"children":["fourth-unit-id","first-unit-id"]}')
|
||||
@requests[1].respond(200)
|
||||
expect(requests[1].requestBody).toEqual('{"children":["fourth-unit-id","first-unit-id"]}')
|
||||
requests[1].respond(200)
|
||||
expect(@savingSpies.hide).toHaveBeenCalled()
|
||||
# Class is removed in a timeout.
|
||||
@clock.tick(1001)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
define ["js/models/section", "js/views/section_show", "js/views/section_edit", "sinon"], (Section, SectionShow, SectionEdit, sinon) ->
|
||||
define ["js/models/section", "js/views/section_show", "js/views/section_edit", "js/spec/create_sinon"], (Section, SectionShow, SectionEdit, create_sinon) ->
|
||||
|
||||
describe "SectionShow", ->
|
||||
describe "Basic", ->
|
||||
@@ -39,9 +39,6 @@ define ["js/models/section", "js/views/section_show", "js/views/section_edit", "
|
||||
.andCallThrough()
|
||||
window.analytics = jasmine.createSpyObj('analytics', ['track'])
|
||||
window.course_location_analytics = jasmine.createSpy()
|
||||
@requests = requests = []
|
||||
@xhr = sinon.useFakeXMLHttpRequest()
|
||||
@xhr.onCreate = (xhr) -> requests.push(xhr)
|
||||
|
||||
@model = new Section({
|
||||
id: 42
|
||||
@@ -51,7 +48,6 @@ define ["js/models/section", "js/views/section_show", "js/views/section_edit", "
|
||||
@view.render()
|
||||
|
||||
afterEach ->
|
||||
@xhr.restore()
|
||||
delete window.analytics
|
||||
delete window.course_location_analytics
|
||||
|
||||
@@ -68,8 +64,10 @@ define ["js/models/section", "js/views/section_show", "js/views/section_edit", "
|
||||
expect(@model.save).toHaveBeenCalled()
|
||||
|
||||
it "should call switchToShowView when save() is successful", ->
|
||||
requests = create_sinon["requests"](this)
|
||||
|
||||
@view.$("input[type=submit]").click()
|
||||
@requests[0].respond(200)
|
||||
requests[0].respond(200)
|
||||
expect(@view.switchToShowView).toHaveBeenCalled()
|
||||
|
||||
it "should call showInvalidMessage when validation is unsuccessful", ->
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
define ["js/models/textbook", "js/models/chapter", "js/collections/chapter", "js/models/course",
|
||||
"js/collections/textbook", "js/views/show_textbook", "js/views/edit_textbook", "js/views/list_textbooks",
|
||||
"js/views/edit_chapter", "js/views/feedback_prompt", "js/views/feedback_notification",
|
||||
"sinon", "jasmine-stealth"],
|
||||
(Textbook, Chapter, ChapterSet, Course, TextbookSet, ShowTextbook, EditTextbook, ListTexbook, EditChapter, Prompt, Notification, sinon) ->
|
||||
"js/spec/create_sinon", "jasmine-stealth"],
|
||||
(Textbook, Chapter, ChapterSet, Course, TextbookSet, ShowTextbook, EditTextbook, ListTexbook, EditChapter, Prompt, Notification, create_sinon) ->
|
||||
feedbackTpl = readFixtures('system-feedback.underscore')
|
||||
|
||||
beforeEach ->
|
||||
@@ -74,34 +74,31 @@ define ["js/models/textbook", "js/models/chapter", "js/collections/chapter", "js
|
||||
|
||||
describe "AJAX", ->
|
||||
beforeEach ->
|
||||
@requests = requests = []
|
||||
@xhr = sinon.useFakeXMLHttpRequest()
|
||||
@xhr.onCreate = (xhr) -> requests.push(xhr)
|
||||
|
||||
@savingSpies = spyOnConstructor(Notification, "Mini",
|
||||
["show", "hide"])
|
||||
@savingSpies.show.andReturn(@savingSpies)
|
||||
CMS.URL.TEXTBOOKS = "/textbooks"
|
||||
|
||||
afterEach ->
|
||||
@xhr.restore()
|
||||
delete CMS.URL.TEXTBOOKS
|
||||
|
||||
it "should destroy itself on confirmation", ->
|
||||
requests = create_sinon["requests"](this)
|
||||
|
||||
@view.render().$(".delete").click()
|
||||
ctorOptions = @promptSpies.constructor.mostRecentCall.args[0]
|
||||
# run the primary function to indicate confirmation
|
||||
ctorOptions.actions.primary.click(@promptSpies)
|
||||
# AJAX request has been sent, but not yet returned
|
||||
expect(@model.destroy).toHaveBeenCalled()
|
||||
expect(@requests.length).toEqual(1)
|
||||
expect(requests.length).toEqual(1)
|
||||
expect(@savingSpies.constructor).toHaveBeenCalled()
|
||||
expect(@savingSpies.show).toHaveBeenCalled()
|
||||
expect(@savingSpies.hide).not.toHaveBeenCalled()
|
||||
savingOptions = @savingSpies.constructor.mostRecentCall.args[0]
|
||||
expect(savingOptions.title).toMatch(/Deleting/)
|
||||
# return a success response
|
||||
@requests[0].respond(200)
|
||||
requests[0].respond(200)
|
||||
expect(@savingSpies.hide).toHaveBeenCalled()
|
||||
expect(@collection.contains(@model)).toBeFalsy()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
define ["js/models/uploads", "js/views/uploads", "js/models/chapter", "sinon"], (FileUpload, UploadDialog, Chapter, sinon) ->
|
||||
define ["js/models/uploads", "js/views/uploads", "js/models/chapter", "js/spec/create_sinon"], (FileUpload, UploadDialog, Chapter, create_sinon) ->
|
||||
|
||||
feedbackTpl = readFixtures('system-feedback.underscore')
|
||||
|
||||
@@ -78,20 +78,18 @@ define ["js/models/uploads", "js/views/uploads", "js/models/chapter", "sinon"],
|
||||
|
||||
describe "Uploads", ->
|
||||
beforeEach ->
|
||||
@requests = requests = []
|
||||
@xhr = sinon.useFakeXMLHttpRequest()
|
||||
@xhr.onCreate = (xhr) -> requests.push(xhr)
|
||||
@clock = sinon.useFakeTimers()
|
||||
|
||||
afterEach ->
|
||||
@xhr.restore()
|
||||
@clock.restore()
|
||||
|
||||
it "can upload correctly", ->
|
||||
requests = create_sinon["requests"](this)
|
||||
|
||||
@view.upload()
|
||||
expect(@model.get("uploading")).toBeTruthy()
|
||||
expect(@requests.length).toEqual(1)
|
||||
request = @requests[0]
|
||||
expect(requests.length).toEqual(1)
|
||||
request = requests[0]
|
||||
expect(request.url).toEqual("/upload")
|
||||
expect(request.method).toEqual("POST")
|
||||
|
||||
@@ -102,14 +100,18 @@ define ["js/models/uploads", "js/views/uploads", "js/models/chapter", "sinon"],
|
||||
expect(@dialogResponse.pop()).toEqual("dummy_response")
|
||||
|
||||
it "can handle upload errors", ->
|
||||
requests = create_sinon["requests"](this)
|
||||
|
||||
@view.upload()
|
||||
@requests[0].respond(500)
|
||||
requests[0].respond(500)
|
||||
expect(@model.get("title")).toMatch(/error/)
|
||||
expect(@view.remove).not.toHaveBeenCalled()
|
||||
|
||||
it "removes itself after two seconds on successful upload", ->
|
||||
requests = create_sinon["requests"](this)
|
||||
|
||||
@view.upload()
|
||||
@requests[0].respond(200, {"Content-Type": "application/json"},
|
||||
requests[0].respond(200, {"Content-Type": "application/json"},
|
||||
'{"response": "dummy_response"}')
|
||||
expect(@view.remove).not.toHaveBeenCalled()
|
||||
@clock.tick(2001)
|
||||
|
||||
50
cms/static/js/spec/create_sinon.js
Normal file
50
cms/static/js/spec/create_sinon.js
Normal file
@@ -0,0 +1,50 @@
|
||||
define(["sinon"], function(sinon) {
|
||||
/* These utility methods are used by Jasmine tests to create a mock server or
|
||||
* get reference to mock requests. In either case, the cleanup (restore) is done with
|
||||
* an after function.
|
||||
*
|
||||
* This pattern is being used instead of the more common beforeEach/afterEach pattern
|
||||
* because we were seeing sporadic failures in the afterEach restore call. The cause of the
|
||||
* errors were that one test suite was incorrectly being linked as the parent of an unrelated
|
||||
* test suite (causing both suites' afterEach methods to be called). No solution for the root
|
||||
* cause has been found, but initializing sinon and cleaning it up on a method-by-method
|
||||
* basis seems to work. For more details, see STUD-1040.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get a reference to the mocked server, and respond
|
||||
* to all requests with the specified statusCode.
|
||||
*/
|
||||
var fakeServer = function (statusCode, that) {
|
||||
var server = sinon.fakeServer.create();
|
||||
that.after(function() {
|
||||
server.restore();
|
||||
});
|
||||
server.respondWith([statusCode, {}, '']);
|
||||
return server;
|
||||
};
|
||||
|
||||
/**
|
||||
* Keep track of all requests to a fake server, and
|
||||
* return a reference to the Array. This allows tests
|
||||
* to respond for individual requests.
|
||||
*/
|
||||
var fakeRequests = function (that) {
|
||||
var requests = [];
|
||||
var xhr = sinon.useFakeXMLHttpRequest();
|
||||
xhr.onCreate = function(request) {
|
||||
requests.push(request)
|
||||
};
|
||||
|
||||
that.after(function() {
|
||||
xhr.restore();
|
||||
});
|
||||
|
||||
return requests;
|
||||
};
|
||||
|
||||
return {
|
||||
"server": fakeServer,
|
||||
"requests": fakeRequests
|
||||
};
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
JS_TEST_SUITES = {
|
||||
'lms' => 'lms/static/js_test.yml',
|
||||
'cms' => 'cms/static/js_test.yml',
|
||||
# 'cms-squire' => 'cms/static/js_test_squire.yml',
|
||||
'cms-squire' => 'cms/static/js_test_squire.yml',
|
||||
'xmodule' => 'common/lib/xmodule/xmodule/js/js_test.yml',
|
||||
'common' => 'common/static/js_test.yml',
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user