Commit 461b8059 by Christina Roberts

Merge pull request #1951 from edx/christina/fix-xhr

Workaround for "xhr.restore" failures.
parents 3f947e80 0ef923e0
...@@ -42,9 +42,10 @@ requirejs.config({ ...@@ -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", "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", "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: { shim: {
"gettext": { "gettext": {
......
require ["jquery", "backbone", "coffee/src/main", "sinon", "jasmine-stealth", "jquery.cookie"], require ["jquery", "backbone", "coffee/src/main", "js/spec/create_sinon", "jasmine-stealth", "jquery.cookie"],
($, Backbone, main, sinon) -> ($, Backbone, main, create_sinon) ->
describe "CMS", -> describe "CMS", ->
it "should initialize URL", -> it "should initialize URL", ->
expect(window.CMS.URL).toBeDefined() expect(window.CMS.URL).toBeDefined()
...@@ -26,30 +26,30 @@ require ["jquery", "backbone", "coffee/src/main", "sinon", "jasmine-stealth", "j ...@@ -26,30 +26,30 @@ require ["jquery", "backbone", "coffee/src/main", "sinon", "jasmine-stealth", "j
beforeEach -> beforeEach ->
setFixtures($("<script>", {id: "system-feedback-tpl", type: "text/template"}).text(tpl)) setFixtures($("<script>", {id: "system-feedback-tpl", type: "text/template"}).text(tpl))
appendSetFixtures(sandbox({id: "page-notification"})) 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", -> it "successful AJAX request does not pop an error notification", ->
server = create_sinon['server'](200, this)
expect($("#page-notification")).toBeEmpty() expect($("#page-notification")).toBeEmpty()
$.ajax("/test") $.ajax("/test")
expect($("#page-notification")).toBeEmpty() expect($("#page-notification")).toBeEmpty()
@requests[0].respond(200) server.respond()
expect($("#page-notification")).toBeEmpty() expect($("#page-notification")).toBeEmpty()
it "AJAX request with error should pop an error notification", -> it "AJAX request with error should pop an error notification", ->
server = create_sinon['server'](500, this)
$.ajax("/test") $.ajax("/test")
@requests[0].respond(500) server.respond()
expect($("#page-notification")).not.toBeEmpty() expect($("#page-notification")).not.toBeEmpty()
expect($("#page-notification")).toContain('div.wrapper-notification-error') expect($("#page-notification")).toContain('div.wrapper-notification-error')
it "can override AJAX request with error so it does not pop an error notification", -> it "can override AJAX request with error so it does not pop an error notification", ->
server = create_sinon['server'](500, this)
$.ajax $.ajax
url: "/test" url: "/test"
notifyOnError: false notifyOnError: false
@requests[0].respond(500)
expect($("#page-notification")).toBeEmpty()
server.respond()
expect($("#page-notification")).toBeEmpty()
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 "Section", ->
describe "basic", -> describe "basic", ->
beforeEach -> beforeEach ->
...@@ -32,21 +32,19 @@ define ["js/models/section", "sinon", "js/utils/module"], (Section, sinon, Modul ...@@ -32,21 +32,19 @@ define ["js/models/section", "sinon", "js/utils/module"], (Section, sinon, Modul
id: 42 id: 42
name: "Life, the Universe, and Everything" 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", -> it "show/hide a notification when it saves to the server", ->
server = create_sinon['server'](200, this)
@model.save() @model.save()
expect(Section.prototype.showNotification).toHaveBeenCalled() expect(Section.prototype.showNotification).toHaveBeenCalled()
@requests[0].respond(200) server.respond()
expect(Section.prototype.hideNotification).toHaveBeenCalled() expect(Section.prototype.hideNotification).toHaveBeenCalled()
it "don't hide notification when saving fails", -> it "don't hide notification when saving fails", ->
# this is handled by the global AJAX error handler # this is handled by the global AJAX error handler
server = create_sinon['server'](500, this)
@model.save() @model.save()
@requests[0].respond(500) server.respond()
expect(Section.prototype.hideNotification).not.toHaveBeenCalled() expect(Section.prototype.hideNotification).not.toHaveBeenCalled()
define ["jasmine", "sinon", "squire"], define ["jasmine", "js/spec/create_sinon", "squire"],
(jasmine, sinon, Squire) -> (jasmine, create_sinon, Squire) ->
feedbackTpl = readFixtures('system-feedback.underscore') feedbackTpl = readFixtures('system-feedback.underscore')
assetTpl = readFixtures('asset.underscore') assetTpl = readFixtures('asset.underscore')
...@@ -68,26 +68,20 @@ define ["jasmine", "sinon", "squire"], ...@@ -68,26 +68,20 @@ define ["jasmine", "sinon", "squire"],
expect(@collection).toContain(@model) expect(@collection).toContain(@model)
describe "AJAX", -> describe "AJAX", ->
beforeEach ->
@requests = requests = []
@xhr = sinon.useFakeXMLHttpRequest()
@xhr.onCreate = (xhr) -> requests.push(xhr)
afterEach ->
@xhr.restore()
it "should destroy itself on confirmation", -> it "should destroy itself on confirmation", ->
requests = create_sinon["requests"](this)
@view.render().$(".remove-asset-button").click() @view.render().$(".remove-asset-button").click()
ctorOptions = @promptSpies.constructor.mostRecentCall.args[0] ctorOptions = @promptSpies.constructor.mostRecentCall.args[0]
# run the primary function to indicate confirmation # run the primary function to indicate confirmation
ctorOptions.actions.primary.click(@promptSpies) ctorOptions.actions.primary.click(@promptSpies)
# AJAX request has been sent, but not yet returned # AJAX request has been sent, but not yet returned
expect(@model.destroy).toHaveBeenCalled() expect(@model.destroy).toHaveBeenCalled()
expect(@requests.length).toEqual(1) expect(requests.length).toEqual(1)
expect(@confirmationSpies.constructor).not.toHaveBeenCalled() expect(@confirmationSpies.constructor).not.toHaveBeenCalled()
expect(@collection.contains(@model)).toBeTruthy() expect(@collection.contains(@model)).toBeTruthy()
# return a success response # return a success response
@requests[0].respond(200) requests[0].respond(200)
expect(@confirmationSpies.constructor).toHaveBeenCalled() expect(@confirmationSpies.constructor).toHaveBeenCalled()
expect(@confirmationSpies.show).toHaveBeenCalled() expect(@confirmationSpies.show).toHaveBeenCalled()
savingOptions = @confirmationSpies.constructor.mostRecentCall.args[0] savingOptions = @confirmationSpies.constructor.mostRecentCall.args[0]
...@@ -95,6 +89,8 @@ define ["jasmine", "sinon", "squire"], ...@@ -95,6 +89,8 @@ define ["jasmine", "sinon", "squire"],
expect(@collection.contains(@model)).toBeFalsy() expect(@collection.contains(@model)).toBeFalsy()
it "should not destroy itself if server errors", -> it "should not destroy itself if server errors", ->
requests = create_sinon["requests"](this)
@view.render().$(".remove-asset-button").click() @view.render().$(".remove-asset-button").click()
ctorOptions = @promptSpies.constructor.mostRecentCall.args[0] ctorOptions = @promptSpies.constructor.mostRecentCall.args[0]
# run the primary function to indicate confirmation # run the primary function to indicate confirmation
...@@ -102,29 +98,33 @@ define ["jasmine", "sinon", "squire"], ...@@ -102,29 +98,33 @@ define ["jasmine", "sinon", "squire"],
# AJAX request has been sent, but not yet returned # AJAX request has been sent, but not yet returned
expect(@model.destroy).toHaveBeenCalled() expect(@model.destroy).toHaveBeenCalled()
# return an error response # return an error response
@requests[0].respond(404) requests[0].respond(404)
expect(@confirmationSpies.constructor).not.toHaveBeenCalled() expect(@confirmationSpies.constructor).not.toHaveBeenCalled()
expect(@collection.contains(@model)).toBeTruthy() expect(@collection.contains(@model)).toBeTruthy()
it "should lock the asset on confirmation", -> it "should lock the asset on confirmation", ->
requests = create_sinon["requests"](this)
@view.render().$(".lock-checkbox").click() @view.render().$(".lock-checkbox").click()
# AJAX request has been sent, but not yet returned # AJAX request has been sent, but not yet returned
expect(@model.save).toHaveBeenCalled() expect(@model.save).toHaveBeenCalled()
expect(@requests.length).toEqual(1) expect(requests.length).toEqual(1)
expect(@savingSpies.constructor).toHaveBeenCalled() expect(@savingSpies.constructor).toHaveBeenCalled()
expect(@savingSpies.show).toHaveBeenCalled() expect(@savingSpies.show).toHaveBeenCalled()
savingOptions = @savingSpies.constructor.mostRecentCall.args[0] savingOptions = @savingSpies.constructor.mostRecentCall.args[0]
expect(savingOptions.title).toMatch("Saving...") expect(savingOptions.title).toMatch("Saving...")
expect(@model.get("locked")).toBeFalsy() expect(@model.get("locked")).toBeFalsy()
# return a success response # return a success response
@requests[0].respond(200) requests[0].respond(200)
expect(@savingSpies.hide).toHaveBeenCalled() expect(@savingSpies.hide).toHaveBeenCalled()
expect(@model.get("locked")).toBeTruthy() expect(@model.get("locked")).toBeTruthy()
it "should not lock the asset if server errors", -> it "should not lock the asset if server errors", ->
requests = create_sinon["requests"](this)
@view.render().$(".lock-checkbox").click() @view.render().$(".lock-checkbox").click()
# return an error response # 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. # Don't call hide because that closes the notification showing the server error.
expect(@savingSpies.hide).not.toHaveBeenCalled() expect(@savingSpies.hide).not.toHaveBeenCalled()
expect(@model.get("locked")).toBeFalsy() expect(@model.get("locked")).toBeFalsy()
...@@ -172,9 +172,6 @@ define ["jasmine", "sinon", "squire"], ...@@ -172,9 +172,6 @@ define ["jasmine", "sinon", "squire"],
waitsFor (=> @view), "AssetView was not created", 1000 waitsFor (=> @view), "AssetView was not created", 1000
$.ajax() $.ajax()
@requests = requests = []
@xhr = sinon.useFakeXMLHttpRequest()
@xhr.onCreate = (xhr) -> requests.push(xhr)
afterEach -> afterEach ->
delete window.analytics delete window.analytics
...@@ -190,18 +187,22 @@ define ["jasmine", "sinon", "squire"], ...@@ -190,18 +187,22 @@ define ["jasmine", "sinon", "squire"],
expect(@view.$el).toContainText("test asset 2") expect(@view.$el).toContainText("test asset 2")
it "should remove the deleted asset from the view", -> it "should remove the deleted asset from the view", ->
requests = create_sinon["requests"](this)
# Delete the 2nd asset with success from server. # Delete the 2nd asset with success from server.
@view.render().$(".remove-asset-button")[1].click() @view.render().$(".remove-asset-button")[1].click()
@promptSpies.constructor.mostRecentCall.args[0].actions.primary.click(@promptSpies) @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).toContainText("test asset 1")
expect(@view.$el).not.toContainText("test asset 2") expect(@view.$el).not.toContainText("test asset 2")
it "does not remove asset if deletion failed", -> it "does not remove asset if deletion failed", ->
requests = create_sinon["requests"](this)
# Delete the 2nd asset, but mimic a failure from the server. # Delete the 2nd asset, but mimic a failure from the server.
@view.render().$(".remove-asset-button")[1].click() @view.render().$(".remove-asset-button")[1].click()
@promptSpies.constructor.mostRecentCall.args[0].actions.primary.click(@promptSpies) @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 1")
expect(@view.$el).toContainText("test asset 2") expect(@view.$el).toContainText("test asset 2")
......
define ["js/views/course_info_handout", "js/views/course_info_update", "js/models/module_info", "js/collections/course_update", "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, sinon) -> (CourseInfoHandoutsView, CourseInfoUpdateView, ModuleInfo, CourseUpdateCollection, create_sinon) ->
courseInfoPage = """ describe "Course Updates and Handouts", ->
<div class="course-info-wrapper"> courseInfoPage = """
<div class="main-column window"> <div class="course-info-wrapper">
<article class="course-updates" id="course-update-view"> <div class="main-column window">
<ol class="update-list" id="course-update-list"></ol> <article class="course-updates" id="course-update-view">
</article> <ol class="update-list" id="course-update-list"></ol>
</div> </article>
<div class="sidebar window course-handouts" id="course-handouts-view"></div> </div>
</div> <div class="sidebar window course-handouts" id="course-handouts-view"></div>
<div class="modal-cover"></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')
beforeEach -> beforeEach ->
setFixtures($("<script>", {id: "course_info_update-tpl", type: "text/template"}).text(courseInfoTemplate)) window.analytics = jasmine.createSpyObj('analytics', ['track'])
appendSetFixtures courseInfoPage window.course_location_analytics = jasmine.createSpy()
courseUpdatesXhr = sinon.useFakeXMLHttpRequest()
@courseUpdatesRequests = requests = []
courseUpdatesXhr.onCreate = (xhr) -> requests.push(xhr)
@xhrRestore = courseUpdatesXhr.restore
@collection = new CourseUpdateCollection()
@collection.url = 'course_info_update/'
@courseInfoEdit = new CourseInfoUpdateView({
el: $('.course-updates'),
collection: @collection,
base_asset_url : 'base-asset-url/'
})
@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) -> afterEach ->
delete window.analytics
delete window.course_location_analytics
describe "Course Updates", ->
courseInfoTemplate = readFixtures('course_info_update.underscore')
beforeEach ->
setFixtures($("<script>", {id: "course_info_update-tpl", type: "text/template"}).text(courseInfoTemplate))
appendSetFixtures courseInfoPage
@collection = new CourseUpdateCollection()
@collection.url = 'course_info_update/'
@courseInfoEdit = new CourseInfoUpdateView({
el: $('.course-updates'),
collection: @collection,
base_asset_url : 'base-asset-url/'
})
@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) @courseInfoEdit.onNew(@event)
spyOn(@courseInfoEdit.$modalCover, 'hide').andCallThrough() expect(@collection.length).toEqual(1)
spyOn(@courseInfoEdit.$codeMirror, 'getValue').andReturn('unsaved changes')
model = @collection.at(0) model = @collection.at(0)
spyOn(model, "save").andCallThrough() spyOn(model, "save").andCallThrough()
spyOn(@courseInfoEdit.$codeMirror, 'getValue').andReturn('/static/image.jpg')
cancelEditingUpdate(@courseInfoEdit, @courseInfoEdit.$modalCover, useCancelButton) # Click the "Save button."
@courseInfoEdit.$el.find('.save-button').click()
expect(@courseInfoEdit.$modalCover.hide).toHaveBeenCalled() expect(model.save).toHaveBeenCalled()
expect(model.save).not.toHaveBeenCalled()
previewContents = @courseInfoEdit.$el.find('.update-contents').html()
expect(previewContents).not.toEqual('unsaved changes')
@cancelExistingCourseInfo = (useCancelButton) -> # Verify content sent to server does not have rewritten links.
@createNewUpdate('existing update') contentSaved = JSON.parse(requests[requests.length - 1].requestBody).content
@courseInfoEdit.$el.find('.edit-button').click() expect(contentSaved).toEqual('/static/image.jpg')
spyOn(@courseInfoEdit.$modalCover, 'hide').andCallThrough()
spyOn(@courseInfoEdit.$codeMirror, 'getValue').andReturn('modification') it "does rewrite links for preview", ->
model = @collection.at(0) # Create a new update.
spyOn(model, "save").andCallThrough() @createNewUpdate('/static/image.jpg')
model.id = "saved_to_server"
cancelEditingUpdate(@courseInfoEdit, @courseInfoEdit.$modalCover, useCancelButton)
expect(@courseInfoEdit.$modalCover.hide).toHaveBeenCalled() # Verify the link is rewritten for preview purposes.
expect(model.save).not.toHaveBeenCalled()
previewContents = @courseInfoEdit.$el.find('.update-contents').html() previewContents = @courseInfoEdit.$el.find('.update-contents').html()
expect(previewContents).toEqual('existing update') expect(previewContents).toEqual('base-asset-url/image.jpg')
cancelEditingUpdate = (update, modalCover, useCancelButton) ->
if useCancelButton
update.$el.find('.cancel-button').click()
else
modalCover.click()
afterEach ->
@xhrRestore()
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')
# Click the "Save button."
@courseInfoEdit.$el.find('.save-button').click()
expect(model.save).toHaveBeenCalled()
# 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')
it "does rewrite links for preview", -> it "shows static links in edit mode", ->
# Create a new update. @createNewUpdate('/static/image.jpg')
@createNewUpdate('/static/image.jpg')
# Verify the link is rewritten for preview purposes. # Click edit and verify CodeMirror contents.
previewContents = @courseInfoEdit.$el.find('.update-contents').html() @courseInfoEdit.$el.find('.edit-button').click()
expect(previewContents).toEqual('base-asset-url/image.jpg') expect(@courseInfoEdit.$codeMirror.getValue()).toEqual('/static/image.jpg')
it "shows static links in edit mode", -> it "removes newly created course info on cancel", ->
@createNewUpdate('/static/image.jpg') @cancelNewCourseInfo(true)
# Click edit and verify CodeMirror contents. it "removes newly created course info on click outside modal", ->
@courseInfoEdit.$el.find('.edit-button').click() @cancelNewCourseInfo(false)
expect(@courseInfoEdit.$codeMirror.getValue()).toEqual('/static/image.jpg')
it "does not remove existing course info on cancel", ->
it "removes newly created course info on cancel", -> @cancelExistingCourseInfo(true)
@cancelNewCourseInfo(true)
it "does not remove existing course info on click outside modal", ->
it "removes newly created course info on click outside modal", -> @cancelExistingCourseInfo(false)
@cancelNewCourseInfo(false)
describe "Course Handouts", ->
it "does not remove existing course info on cancel", -> handoutsTemplate = readFixtures('course_info_handouts.underscore')
@cancelExistingCourseInfo(true)
beforeEach ->
it "does not remove existing course info on click outside modal", -> setFixtures($("<script>", {id: "course_info_handouts-tpl", type: "text/template"}).text(handoutsTemplate))
@cancelExistingCourseInfo(false) appendSetFixtures courseInfoPage
xdescribe "Course Handouts", -> @model = new ModuleInfo({
handoutsTemplate = readFixtures('course_info_handouts.underscore') id: 'handouts-id',
data: '/static/fromServer.jpg'
beforeEach -> })
setFixtures($("<script>", {id: "course_info_handouts-tpl", type: "text/template"}).text(handoutsTemplate))
appendSetFixtures courseInfoPage @handoutsEdit = new CourseInfoHandoutsView({
el: $('#course-handouts-view'),
courseHandoutsXhr = sinon.useFakeXMLHttpRequest() model: @model,
@handoutsRequests = requests = [] base_asset_url: 'base-asset-url/'
courseHandoutsXhr.onCreate = (xhr) -> requests.push(xhr) });
@handoutsXhrRestore = courseHandoutsXhr.restore
@handoutsEdit.render()
@model = new ModuleInfo({
id: 'handouts-id', it "does not rewrite links on save", ->
data: '/static/fromServer.jpg' requests = create_sinon["requests"](this)
})
# Enter something in the handouts section, verifying that the model is saved
@handoutsEdit = new CourseInfoHandoutsView({ # when "Save" is clicked.
el: $('#course-handouts-view'), @handoutsEdit.$el.find('.edit-button').click()
model: @model, spyOn(@handoutsEdit.$codeMirror, 'getValue').andReturn('/static/image.jpg')
base_asset_url: 'base-asset-url/' spyOn(@model, "save").andCallThrough()
}); @handoutsEdit.$el.find('.save-button').click()
expect(@model.save).toHaveBeenCalled()
@handoutsEdit.render()
contentSaved = JSON.parse(requests[requests.length - 1].requestBody).data
afterEach -> expect(contentSaved).toEqual('/static/image.jpg')
@handoutsXhrRestore()
it "does rewrite links in initial content", ->
it "does not rewrite links on save", -> expect(@handoutsEdit.$preview.html().trim()).toBe('base-asset-url/fromServer.jpg')
# Enter something in the handouts section, verifying that the model is saved
# when "Save" is clicked. it "does rewrite links after edit", ->
@handoutsEdit.$el.find('.edit-button').click() # Edit handouts and save.
spyOn(@handoutsEdit.$codeMirror, 'getValue').andReturn('/static/image.jpg') @handoutsEdit.$el.find('.edit-button').click()
spyOn(@model, "save").andCallThrough() spyOn(@handoutsEdit.$codeMirror, 'getValue').andReturn('/static/image.jpg')
@handoutsEdit.$el.find('.save-button').click() @handoutsEdit.$el.find('.save-button').click()
expect(@model.save).toHaveBeenCalled()
# Verify preview text.
contentSaved = JSON.parse(@handoutsRequests[@handoutsRequests.length - 1].requestBody).data expect(@handoutsEdit.$preview.html().trim()).toBe('base-asset-url/image.jpg')
expect(contentSaved).toEqual('/static/image.jpg')
it "shows static links in edit mode", ->
it "does rewrite links in initial content", -> # Click edit and verify CodeMirror contents.
expect(@handoutsEdit.$preview.html().trim()).toBe('base-asset-url/fromServer.jpg') @handoutsEdit.$el.find('.edit-button').click()
expect(@handoutsEdit.$codeMirror.getValue().trim()).toEqual('/static/fromServer.jpg')
it "does rewrite links after edit", ->
# Edit handouts and save. it "can open course handouts with bad html on edit", ->
@handoutsEdit.$el.find('.edit-button').click() # Enter some bad html in handouts section, verifying that the
spyOn(@handoutsEdit.$codeMirror, 'getValue').andReturn('/static/image.jpg') # model/handoutform opens when "Edit" is clicked
@handoutsEdit.$el.find('.save-button').click()
@model = new ModuleInfo({
# Verify preview text. id: 'handouts-id',
expect(@handoutsEdit.$preview.html().trim()).toBe('base-asset-url/image.jpg') data: '<p><a href="[URL OF FILE]>[LINK TEXT]</a></p>'
})
it "shows static links in edit mode", -> @handoutsEdit = new CourseInfoHandoutsView({
# Click edit and verify CodeMirror contents. el: $('#course-handouts-view'),
@handoutsEdit.$el.find('.edit-button').click() model: @model,
expect(@handoutsEdit.$codeMirror.getValue().trim()).toEqual('/static/fromServer.jpg') base_asset_url: 'base-asset-url/'
});
it "can open course handouts with bad html on edit", -> @handoutsEdit.render()
# Enter some bad html in handouts section, verifying that the
# model/handoutform opens when "Edit" is clicked expect($('.edit-handouts-form').is(':hidden')).toEqual(true)
@handoutsEdit.$el.find('.edit-button').click()
@model = new ModuleInfo({ expect(@handoutsEdit.$codeMirror.getValue()).toEqual('<p><a href="[URL OF FILE]>[LINK TEXT]</a></p>')
id: 'handouts-id', expect($('.edit-handouts-form').is(':hidden')).toEqual(false)
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)
define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base", "date", "jquery.timepicker"], define ["js/views/overview", "js/views/feedback_notification", "js/spec/create_sinon", "js/base", "date", "jquery.timepicker"],
(Overview, Notification, sinon) -> (Overview, Notification, create_sinon) ->
describe "Course Overview", -> describe "Course Overview", ->
beforeEach -> beforeEach ->
...@@ -95,9 +95,6 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base ...@@ -95,9 +95,6 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
@notificationSpy = spyOn(Notification.Mini.prototype, 'show').andCallThrough() @notificationSpy = spyOn(Notification.Mini.prototype, 'show').andCallThrough()
window.analytics = jasmine.createSpyObj('analytics', ['track']) window.analytics = jasmine.createSpyObj('analytics', ['track'])
window.course_location_analytics = jasmine.createSpy() window.course_location_analytics = jasmine.createSpy()
@xhr = sinon.useFakeXMLHttpRequest()
requests = @requests = []
@xhr.onCreate = (req) -> requests.push(req)
Overview.overviewDragger.makeDraggable( Overview.overviewDragger.makeDraggable(
'.unit', '.unit',
...@@ -135,9 +132,11 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base ...@@ -135,9 +132,11 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
# expect(@requests[0].url).toEqual('/delete_item') # expect(@requests[0].url).toEqual('/delete_item')
it "should not delete model when cancel is clicked", -> it "should not delete model when cancel is clicked", ->
requests = create_sinon["requests"](this)
$('a.delete-section-button').click() $('a.delete-section-button').click()
$('a.action-secondary').click() $('a.action-secondary').click()
expect(@requests.length).toEqual(0) expect(requests.length).toEqual(0)
# Fails sporadically in Jenkins. # Fails sporadically in Jenkins.
# it "should show a confirmation on delete", -> # it "should show a confirmation on delete", ->
...@@ -402,22 +401,19 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base ...@@ -402,22 +401,19 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
) )
expect($('#subsection-2')).not.toHaveClass('collapsed') expect($('#subsection-2')).not.toHaveClass('collapsed')
xdescribe "AJAX", -> describe "AJAX", ->
beforeEach -> beforeEach ->
@requests = requests = []
@xhr = sinon.useFakeXMLHttpRequest()
@xhr.onCreate = (xhr) -> requests.push(xhr)
@savingSpies = spyOnConstructor(Notification, "Mini", @savingSpies = spyOnConstructor(Notification, "Mini",
["show", "hide"]) ["show", "hide"])
@savingSpies.show.andReturn(@savingSpies) @savingSpies.show.andReturn(@savingSpies)
@clock = sinon.useFakeTimers() @clock = sinon.useFakeTimers()
afterEach -> afterEach ->
@xhr.restore()
@clock.restore() @clock.restore()
it "should send an update on reorder", -> it "should send an update on reorder", ->
requests = create_sinon["requests"](this)
Overview.overviewDragger.dragState.dropDestination = $('#unit-4') Overview.overviewDragger.dragState.dropDestination = $('#unit-4')
Overview.overviewDragger.dragState.attachMethod = "after" Overview.overviewDragger.dragState.attachMethod = "after"
Overview.overviewDragger.dragState.parentList = $('#subsection-2') Overview.overviewDragger.dragState.parentList = $('#subsection-2')
...@@ -431,7 +427,7 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base ...@@ -431,7 +427,7 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
null, null,
{clientX: $('#unit-1').offset().left} {clientX: $('#unit-1').offset().left}
) )
expect(@requests.length).toEqual(2) expect(requests.length).toEqual(2)
expect(@savingSpies.constructor).toHaveBeenCalled() expect(@savingSpies.constructor).toHaveBeenCalled()
expect(@savingSpies.show).toHaveBeenCalled() expect(@savingSpies.show).toHaveBeenCalled()
expect(@savingSpies.hide).not.toHaveBeenCalled() expect(@savingSpies.hide).not.toHaveBeenCalled()
...@@ -440,11 +436,11 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base ...@@ -440,11 +436,11 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
expect($('#unit-1')).toHaveClass('was-dropped') expect($('#unit-1')).toHaveClass('was-dropped')
# We expect 2 requests to be sent-- the first for removing Unit 1 from Subsection 1, # 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. # 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"]}') expect(requests[0].requestBody).toEqual('{"children":["second-unit-id","third-unit-id"]}')
@requests[0].respond(200) requests[0].respond(200)
expect(@savingSpies.hide).not.toHaveBeenCalled() expect(@savingSpies.hide).not.toHaveBeenCalled()
expect(@requests[1].requestBody).toEqual('{"children":["fourth-unit-id","first-unit-id"]}') expect(requests[1].requestBody).toEqual('{"children":["fourth-unit-id","first-unit-id"]}')
@requests[1].respond(200) requests[1].respond(200)
expect(@savingSpies.hide).toHaveBeenCalled() expect(@savingSpies.hide).toHaveBeenCalled()
# Class is removed in a timeout. # Class is removed in a timeout.
@clock.tick(1001) @clock.tick(1001)
......
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 "SectionShow", ->
describe "Basic", -> describe "Basic", ->
...@@ -39,9 +39,6 @@ define ["js/models/section", "js/views/section_show", "js/views/section_edit", " ...@@ -39,9 +39,6 @@ define ["js/models/section", "js/views/section_show", "js/views/section_edit", "
.andCallThrough() .andCallThrough()
window.analytics = jasmine.createSpyObj('analytics', ['track']) window.analytics = jasmine.createSpyObj('analytics', ['track'])
window.course_location_analytics = jasmine.createSpy() window.course_location_analytics = jasmine.createSpy()
@requests = requests = []
@xhr = sinon.useFakeXMLHttpRequest()
@xhr.onCreate = (xhr) -> requests.push(xhr)
@model = new Section({ @model = new Section({
id: 42 id: 42
...@@ -51,7 +48,6 @@ define ["js/models/section", "js/views/section_show", "js/views/section_edit", " ...@@ -51,7 +48,6 @@ define ["js/models/section", "js/views/section_show", "js/views/section_edit", "
@view.render() @view.render()
afterEach -> afterEach ->
@xhr.restore()
delete window.analytics delete window.analytics
delete window.course_location_analytics delete window.course_location_analytics
...@@ -68,8 +64,10 @@ define ["js/models/section", "js/views/section_show", "js/views/section_edit", " ...@@ -68,8 +64,10 @@ define ["js/models/section", "js/views/section_show", "js/views/section_edit", "
expect(@model.save).toHaveBeenCalled() expect(@model.save).toHaveBeenCalled()
it "should call switchToShowView when save() is successful", -> it "should call switchToShowView when save() is successful", ->
requests = create_sinon["requests"](this)
@view.$("input[type=submit]").click() @view.$("input[type=submit]").click()
@requests[0].respond(200) requests[0].respond(200)
expect(@view.switchToShowView).toHaveBeenCalled() expect(@view.switchToShowView).toHaveBeenCalled()
it "should call showInvalidMessage when validation is unsuccessful", -> it "should call showInvalidMessage when validation is unsuccessful", ->
......
define ["js/models/textbook", "js/models/chapter", "js/collections/chapter", "js/models/course", 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/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", "js/views/edit_chapter", "js/views/feedback_prompt", "js/views/feedback_notification",
"sinon", "jasmine-stealth"], "js/spec/create_sinon", "jasmine-stealth"],
(Textbook, Chapter, ChapterSet, Course, TextbookSet, ShowTextbook, EditTextbook, ListTexbook, EditChapter, Prompt, Notification, sinon) -> (Textbook, Chapter, ChapterSet, Course, TextbookSet, ShowTextbook, EditTextbook, ListTexbook, EditChapter, Prompt, Notification, create_sinon) ->
feedbackTpl = readFixtures('system-feedback.underscore') feedbackTpl = readFixtures('system-feedback.underscore')
beforeEach -> beforeEach ->
...@@ -74,34 +74,31 @@ define ["js/models/textbook", "js/models/chapter", "js/collections/chapter", "js ...@@ -74,34 +74,31 @@ define ["js/models/textbook", "js/models/chapter", "js/collections/chapter", "js
describe "AJAX", -> describe "AJAX", ->
beforeEach -> beforeEach ->
@requests = requests = []
@xhr = sinon.useFakeXMLHttpRequest()
@xhr.onCreate = (xhr) -> requests.push(xhr)
@savingSpies = spyOnConstructor(Notification, "Mini", @savingSpies = spyOnConstructor(Notification, "Mini",
["show", "hide"]) ["show", "hide"])
@savingSpies.show.andReturn(@savingSpies) @savingSpies.show.andReturn(@savingSpies)
CMS.URL.TEXTBOOKS = "/textbooks" CMS.URL.TEXTBOOKS = "/textbooks"
afterEach -> afterEach ->
@xhr.restore()
delete CMS.URL.TEXTBOOKS delete CMS.URL.TEXTBOOKS
it "should destroy itself on confirmation", -> it "should destroy itself on confirmation", ->
requests = create_sinon["requests"](this)
@view.render().$(".delete").click() @view.render().$(".delete").click()
ctorOptions = @promptSpies.constructor.mostRecentCall.args[0] ctorOptions = @promptSpies.constructor.mostRecentCall.args[0]
# run the primary function to indicate confirmation # run the primary function to indicate confirmation
ctorOptions.actions.primary.click(@promptSpies) ctorOptions.actions.primary.click(@promptSpies)
# AJAX request has been sent, but not yet returned # AJAX request has been sent, but not yet returned
expect(@model.destroy).toHaveBeenCalled() expect(@model.destroy).toHaveBeenCalled()
expect(@requests.length).toEqual(1) expect(requests.length).toEqual(1)
expect(@savingSpies.constructor).toHaveBeenCalled() expect(@savingSpies.constructor).toHaveBeenCalled()
expect(@savingSpies.show).toHaveBeenCalled() expect(@savingSpies.show).toHaveBeenCalled()
expect(@savingSpies.hide).not.toHaveBeenCalled() expect(@savingSpies.hide).not.toHaveBeenCalled()
savingOptions = @savingSpies.constructor.mostRecentCall.args[0] savingOptions = @savingSpies.constructor.mostRecentCall.args[0]
expect(savingOptions.title).toMatch(/Deleting/) expect(savingOptions.title).toMatch(/Deleting/)
# return a success response # return a success response
@requests[0].respond(200) requests[0].respond(200)
expect(@savingSpies.hide).toHaveBeenCalled() expect(@savingSpies.hide).toHaveBeenCalled()
expect(@collection.contains(@model)).toBeFalsy() expect(@collection.contains(@model)).toBeFalsy()
......
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') feedbackTpl = readFixtures('system-feedback.underscore')
...@@ -78,20 +78,18 @@ define ["js/models/uploads", "js/views/uploads", "js/models/chapter", "sinon"], ...@@ -78,20 +78,18 @@ define ["js/models/uploads", "js/views/uploads", "js/models/chapter", "sinon"],
describe "Uploads", -> describe "Uploads", ->
beforeEach -> beforeEach ->
@requests = requests = []
@xhr = sinon.useFakeXMLHttpRequest()
@xhr.onCreate = (xhr) -> requests.push(xhr)
@clock = sinon.useFakeTimers() @clock = sinon.useFakeTimers()
afterEach -> afterEach ->
@xhr.restore()
@clock.restore() @clock.restore()
it "can upload correctly", -> it "can upload correctly", ->
requests = create_sinon["requests"](this)
@view.upload() @view.upload()
expect(@model.get("uploading")).toBeTruthy() expect(@model.get("uploading")).toBeTruthy()
expect(@requests.length).toEqual(1) expect(requests.length).toEqual(1)
request = @requests[0] request = requests[0]
expect(request.url).toEqual("/upload") expect(request.url).toEqual("/upload")
expect(request.method).toEqual("POST") expect(request.method).toEqual("POST")
...@@ -102,14 +100,18 @@ define ["js/models/uploads", "js/views/uploads", "js/models/chapter", "sinon"], ...@@ -102,14 +100,18 @@ define ["js/models/uploads", "js/views/uploads", "js/models/chapter", "sinon"],
expect(@dialogResponse.pop()).toEqual("dummy_response") expect(@dialogResponse.pop()).toEqual("dummy_response")
it "can handle upload errors", -> it "can handle upload errors", ->
requests = create_sinon["requests"](this)
@view.upload() @view.upload()
@requests[0].respond(500) requests[0].respond(500)
expect(@model.get("title")).toMatch(/error/) expect(@model.get("title")).toMatch(/error/)
expect(@view.remove).not.toHaveBeenCalled() expect(@view.remove).not.toHaveBeenCalled()
it "removes itself after two seconds on successful upload", -> it "removes itself after two seconds on successful upload", ->
requests = create_sinon["requests"](this)
@view.upload() @view.upload()
@requests[0].respond(200, {"Content-Type": "application/json"}, requests[0].respond(200, {"Content-Type": "application/json"},
'{"response": "dummy_response"}') '{"response": "dummy_response"}')
expect(@view.remove).not.toHaveBeenCalled() expect(@view.remove).not.toHaveBeenCalled()
@clock.tick(2001) @clock.tick(2001)
......
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
};
});
JS_TEST_SUITES = { JS_TEST_SUITES = {
'lms' => 'lms/static/js_test.yml', 'lms' => 'lms/static/js_test.yml',
'cms' => 'cms/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', 'xmodule' => 'common/lib/xmodule/xmodule/js/js_test.yml',
'common' => 'common/static/js_test.yml', 'common' => 'common/static/js_test.yml',
} }
......
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