diff --git a/cms/static/cms/js/spec/main.js b/cms/static/cms/js/spec/main.js index 076c47376c..db82630e76 100644 --- a/cms/static/cms/js/spec/main.js +++ b/cms/static/cms/js/spec/main.js @@ -228,7 +228,6 @@ 'coffee/spec/models/upload_spec', 'coffee/spec/views/course_info_spec', 'coffee/spec/views/metadata_edit_spec', - 'coffee/spec/views/module_edit_spec', 'coffee/spec/views/textbook_spec', 'coffee/spec/views/upload_spec', 'js/spec/video/transcripts/utils_spec', @@ -250,6 +249,7 @@ 'js/spec/views/assets_spec', 'js/spec/views/baseview_spec', 'js/spec/views/container_spec', + 'js/spec/views/module_edit_spec', 'js/spec/views/paged_container_spec', 'js/spec/views/group_configuration_spec', 'js/spec/views/unit_outline_spec', diff --git a/cms/static/coffee/spec/views/module_edit_spec.coffee b/cms/static/coffee/spec/views/module_edit_spec.coffee deleted file mode 100644 index a631c4ddde..0000000000 --- a/cms/static/coffee/spec/views/module_edit_spec.coffee +++ /dev/null @@ -1,180 +0,0 @@ -define ["jquery", "common/js/components/utils/view_utils", "js/spec_helpers/edit_helpers", - "coffee/src/views/module_edit", "js/models/module_info", "xmodule"], - ($, ViewUtils, edit_helpers, ModuleEdit, ModuleModel) -> - - describe "ModuleEdit", -> - beforeEach -> - @stubModule = new ModuleModel - id: "stub-id" - - setFixtures """ - - """ - edit_helpers.installEditTemplates(true); - spyOn($, 'ajax').and.returnValue(@moduleData) - - @moduleEdit = new ModuleEdit( - el: $(".component") - model: @stubModule - onDelete: jasmine.createSpy() - ) - - describe "class definition", -> - it "sets the correct tagName", -> - expect(@moduleEdit.tagName).toEqual("li") - - it "sets the correct className", -> - expect(@moduleEdit.className).toEqual("component") - - describe "methods", -> - describe "initialize", -> - beforeEach -> - spyOn(ModuleEdit.prototype, 'render') - @moduleEdit = new ModuleEdit( - el: $(".component") - model: @stubModule - onDelete: jasmine.createSpy() - ) - - it "renders the module editor", -> - expect(ModuleEdit.prototype.render).toHaveBeenCalled() - - describe "render", -> - beforeEach -> - spyOn(@moduleEdit, 'loadDisplay') - spyOn(@moduleEdit, 'delegateEvents') - spyOn($.fn, 'append') - spyOn(ViewUtils, 'loadJavaScript').and.returnValue($.Deferred().resolve().promise()); - - window.MockXBlock = (runtime, element) -> - return { } - - window.loadedXBlockResources = undefined - - @moduleEdit.render() - $.ajax.calls.mostRecent().args[0].success( - html: '
Response html
' - resources: [ - ['hash1', {kind: 'text', mimetype: 'text/css', data: 'inline-css'}], - ['hash2', {kind: 'url', mimetype: 'text/css', data: 'css-url'}], - ['hash3', {kind: 'text', mimetype: 'application/javascript', data: 'inline-js'}], - ['hash4', {kind: 'url', mimetype: 'application/javascript', data: 'js-url'}], - ['hash5', {placement: 'head', mimetype: 'text/html', data: 'head-html'}], - ['hash6', {placement: 'not-head', mimetype: 'text/html', data: 'not-head-html'}], - ] - ) - - afterEach -> - window.MockXBlock = null - - it "loads the module preview via ajax on the view element", -> - expect($.ajax).toHaveBeenCalledWith( - url: "/xblock/#{@moduleEdit.model.id}/student_view" - type: "GET" - cache: false - headers: - Accept: 'application/json' - success: jasmine.any(Function) - ) - - expect($.ajax).not.toHaveBeenCalledWith( - url: "/xblock/#{@moduleEdit.model.id}/studio_view" - type: "GET" - headers: - Accept: 'application/json' - success: jasmine.any(Function) - ) - expect(@moduleEdit.loadDisplay).toHaveBeenCalled() - expect(@moduleEdit.delegateEvents).toHaveBeenCalled() - - it "loads the editing view via ajax on demand", -> - edit_helpers.installEditTemplates(true); - expect($.ajax).not.toHaveBeenCalledWith( - url: "/xblock/#{@moduleEdit.model.id}/studio_view" - type: "GET" - cache : false - headers: - Accept: 'application/json' - success: jasmine.any(Function) - ) - - @moduleEdit.clickEditButton({'preventDefault': jasmine.createSpy('event.preventDefault')}) - - mockXBlockEditorHtml = readFixtures('mock/mock-xblock-editor.underscore') - - $.ajax.calls.mostRecent().args[0].success( - html: mockXBlockEditorHtml - resources: [ - ['hash1', {kind: 'text', mimetype: 'text/css', data: 'inline-css'}], - ['hash2', {kind: 'url', mimetype: 'text/css', data: 'css-url'}], - ['hash3', {kind: 'text', mimetype: 'application/javascript', data: 'inline-js'}], - ['hash4', {kind: 'url', mimetype: 'application/javascript', data: 'js-url'}], - ['hash5', {placement: 'head', mimetype: 'text/html', data: 'head-html'}], - ['hash6', {placement: 'not-head', mimetype: 'text/html', data: 'not-head-html'}], - ] - ) - - expect($.ajax).toHaveBeenCalledWith( - url: "/xblock/#{@moduleEdit.model.id}/studio_view" - type: "GET" - cache: false - headers: - Accept: 'application/json' - success: jasmine.any(Function) - ) - expect(@moduleEdit.delegateEvents).toHaveBeenCalled() - - it "loads inline css from fragments", -> - expect($('head').append).toHaveBeenCalledWith("") - - it "loads css urls from fragments", -> - expect($('head').append).toHaveBeenCalledWith("") - - it "loads inline js from fragments", -> - expect($('head').append).toHaveBeenCalledWith("") - - it "loads js urls from fragments", -> - expect(ViewUtils.loadJavaScript).toHaveBeenCalledWith("js-url") - - it "loads head html", -> - expect($('head').append).toHaveBeenCalledWith("head-html") - - it "doesn't load body html", -> - expect($.fn.append).not.toHaveBeenCalledWith('not-head-html') - - it "doesn't reload resources", -> - count = $('head').append.calls.count() - $.ajax.calls.mostRecent().args[0].success( - html: '
Response html 2
' - resources: [ - ['hash1', {kind: 'text', mimetype: 'text/css', data: 'inline-css'}], - ] - ) - expect($('head').append.calls.count()).toBe(count) - - describe "loadDisplay", -> - beforeEach -> - spyOn(XBlock, 'initializeBlock') - @moduleEdit.loadDisplay() - - it "loads the .xmodule-display inside the module editor", -> - expect(XBlock.initializeBlock).toHaveBeenCalled() - expect(XBlock.initializeBlock.calls.mostRecent().args[0].get(0)).toBe($('.xblock-student_view').get(0)) diff --git a/cms/static/coffee/src/views/module_edit.coffee b/cms/static/coffee/src/views/module_edit.coffee deleted file mode 100644 index b04495f8c5..0000000000 --- a/cms/static/coffee/src/views/module_edit.coffee +++ /dev/null @@ -1,56 +0,0 @@ -define ["jquery", "underscore", "gettext", "xblock/runtime.v1", - "js/views/xblock", "js/views/modals/edit_xblock"], -($, _, gettext, XBlock, XBlockView, EditXBlockModal) -> - class ModuleEdit extends XBlockView - tagName: 'li' - className: 'component' - editorMode: 'editor-mode' - - events: - "click .edit-button": 'clickEditButton' - "click .delete-button": 'onDelete' - - initialize: -> - @onDelete = @options.onDelete - @render() - - loadDisplay: -> - # Not all components render an inline student view, e.g. child containers which - # instead render a link to a separate container page. - xblockElement = @$el.find('.xblock-student_view') - if xblockElement.length > 0 - XBlock.initializeBlock(xblockElement) - - createItem: (parent, payload, callback=->) -> - payload.parent_locator = parent - $.postJSON( - @model.urlRoot + '/' - payload - (data) => - @model.set(id: data.locator) - @$el.data('locator', data.locator) - @$el.data('courseKey', data.courseKey) - @render() - ).success(callback) - - loadView: (viewName, target, callback) -> - if @model.id - $.ajax( - url: "#{decodeURIComponent(@model.url())}/#{viewName}" - type: 'GET' - cache: false - headers: - Accept: 'application/json' - success: (fragment) => - @renderXBlockFragment(fragment, target).done(callback) - ) - - render: -> @loadView('student_view', @$el, => - @loadDisplay() - @delegateEvents() - ) - - clickEditButton: (event) -> - event.preventDefault() - modal = new EditXBlockModal(); - modal.edit(this.$el, self.model, { refresh: _.bind(@render, this) }) diff --git a/cms/static/js/spec/views/module_edit_spec.js b/cms/static/js/spec/views/module_edit_spec.js new file mode 100644 index 0000000000..ebd54396d4 --- /dev/null +++ b/cms/static/js/spec/views/module_edit_spec.js @@ -0,0 +1,265 @@ +(function() { + 'use strict'; + define([ + 'jquery', 'common/js/components/utils/view_utils', 'js/spec_helpers/edit_helpers', + 'js/views/module_edit', 'js/models/module_info', 'xmodule'], + function($, ViewUtils, edit_helpers, ModuleEdit, ModuleModel) { + describe('ModuleEdit', function() { + beforeEach(function() { + this.stubModule = new ModuleModel({ + id: 'stub-id' + }); + setFixtures(''); + edit_helpers.installEditTemplates(true); + spyOn($, 'ajax').and.returnValue(this.moduleData); + this.moduleEdit = new ModuleEdit({ + el: $('.component'), + model: this.stubModule, + onDelete: jasmine.createSpy() + }); + return this.moduleEdit; + }); + describe('class definition', function() { + it('sets the correct tagName', function() { + return expect(this.moduleEdit.tagName).toEqual('li'); + }); + it('sets the correct className', function() { + return expect(this.moduleEdit.className).toEqual('component'); + }); + }); + describe('methods', function() { + describe('initialize', function() { + beforeEach(function() { + spyOn(ModuleEdit.prototype, 'render'); + this.moduleEdit = new ModuleEdit({ + el: $('.component'), + model: this.stubModule, + onDelete: jasmine.createSpy() + }); + return this.moduleEdit; + }); + it('renders the module editor', function() { + return expect(ModuleEdit.prototype.render).toHaveBeenCalled(); + }); + }); + describe('render', function() { + beforeEach(function() { + spyOn(this.moduleEdit, 'loadDisplay'); + spyOn(this.moduleEdit, 'delegateEvents'); + spyOn($.fn, 'append'); + spyOn(ViewUtils, 'loadJavaScript').and.returnValue($.Deferred().resolve().promise()); + window.MockXBlock = function() { + return {}; + }; + window.loadedXBlockResources = void 0; + this.moduleEdit.render(); + return $.ajax.calls.mostRecent().args[0].success({ + html: '
Response html
', + resources: [ + [ + 'hash1', { + kind: 'text', + mimetype: 'text/css', + data: 'inline-css' + } + ], [ + 'hash2', { + kind: 'url', + mimetype: 'text/css', + data: 'css-url' + } + ], [ + 'hash3', { + kind: 'text', + mimetype: 'application/javascript', + data: 'inline-js' + } + ], [ + 'hash4', { + kind: 'url', + mimetype: 'application/javascript', + data: 'js-url' + } + ], [ + 'hash5', { + placement: 'head', + mimetype: 'text/html', + data: 'head-html' + } + ], [ + 'hash6', { + placement: 'not-head', + mimetype: 'text/html', + data: 'not-head-html' + } + ] + ] + }); + }); + afterEach(function() { + window.MockXBlock = null; + return window.MockXBlock; + }); + it('loads the module preview via ajax on the view element', function() { + expect($.ajax).toHaveBeenCalledWith({ + url: '/xblock/' + this.moduleEdit.model.id + '/student_view', + type: 'GET', + cache: false, + headers: { + Accept: 'application/json' + }, + success: jasmine.any(Function) + }); + expect($.ajax).not.toHaveBeenCalledWith({ + url: '/xblock/' + this.moduleEdit.model.id + '/studio_view', + type: 'GET', + headers: { + Accept: 'application/json' + }, + success: jasmine.any(Function) + }); + expect(this.moduleEdit.loadDisplay).toHaveBeenCalled(); + return expect(this.moduleEdit.delegateEvents).toHaveBeenCalled(); + }); + it('loads the editing view via ajax on demand', function() { + var mockXBlockEditorHtml; + edit_helpers.installEditTemplates(true); + expect($.ajax).not.toHaveBeenCalledWith({ + url: '/xblock/' + this.moduleEdit.model.id + '/studio_view', + type: 'GET', + cache: false, + headers: { + Accept: 'application/json' + }, + success: jasmine.any(Function) + }); + this.moduleEdit.clickEditButton({ + 'preventDefault': jasmine.createSpy('event.preventDefault') + }); + mockXBlockEditorHtml = readFixtures('mock/mock-xblock-editor.underscore'); + $.ajax.calls.mostRecent().args[0].success({ + html: mockXBlockEditorHtml, + resources: [ + [ + 'hash1', { + kind: 'text', + mimetype: 'text/css', + data: 'inline-css' + } + ], [ + 'hash2', { + kind: 'url', + mimetype: 'text/css', + data: 'css-url' + } + ], [ + 'hash3', { + kind: 'text', + mimetype: 'application/javascript', + data: 'inline-js' + } + ], [ + 'hash4', { + kind: 'url', + mimetype: 'application/javascript', + data: 'js-url' + } + ], [ + 'hash5', { + placement: 'head', + mimetype: 'text/html', + data: 'head-html' + } + ], [ + 'hash6', { + placement: 'not-head', + mimetype: 'text/html', + data: 'not-head-html' + } + ] + ] + }); + expect($.ajax).toHaveBeenCalledWith({ + url: '/xblock/' + this.moduleEdit.model.id + '/studio_view', + type: 'GET', + cache: false, + headers: { + Accept: 'application/json' + }, + success: jasmine.any(Function) + }); + return expect(this.moduleEdit.delegateEvents).toHaveBeenCalled(); + }); + it('loads inline css from fragments', function() { + var args = ""; + return expect($('head').append).toHaveBeenCalledWith(args); + }); + it('loads css urls from fragments', function() { + var args = ""; + return expect($('head').append).toHaveBeenCalledWith(args); + }); + it('loads inline js from fragments', function() { + return expect($('head').append).toHaveBeenCalledWith(''); + }); + it('loads js urls from fragments', function() { + return expect(ViewUtils.loadJavaScript).toHaveBeenCalledWith('js-url'); + }); + it('loads head html', function() { + return expect($('head').append).toHaveBeenCalledWith('head-html'); + }); + it("doesn't load body html", function() { + return expect($.fn.append).not.toHaveBeenCalledWith("not-head-html"); + }); + it("doesn't reload resources", function() { + var count; + count = $('head').append.calls.count(); + $.ajax.calls.mostRecent().args[0].success({ + html: '
Response html 2
', + resources: [ + [ + 'hash1', { + kind: 'text', + mimetype: 'text/css', + data: 'inline-css' + } + ] + ] + }); + return expect($('head').append.calls.count()).toBe(count); + }); + }); + describe('loadDisplay', function() { + beforeEach(function() { + spyOn(XBlock, 'initializeBlock'); + return this.moduleEdit.loadDisplay(); + }); + it('loads the .xmodule-display inside the module editor', function() { + expect(XBlock.initializeBlock).toHaveBeenCalled(); + var sel = '.xblock-student_view'; + return expect(XBlock.initializeBlock.calls.mostRecent().args[0].get(0)).toBe($(sel).get(0)); + }); + }); + }); + }); + }); +}).call(this); diff --git a/cms/static/js/views/module_edit.js b/cms/static/js/views/module_edit.js new file mode 100644 index 0000000000..64aa7bafff --- /dev/null +++ b/cms/static/js/views/module_edit.js @@ -0,0 +1,111 @@ +(function() { + 'use strict'; + + var __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { + var key; + for (key in parent) { + if (__hasProp.call(parent, key)) { + child[key] = parent[key]; + } + } + function Ctor() { + this.constructor = child; + } + Ctor.prototype = parent.prototype; + child.prototype = new Ctor(); + child.__super__ = parent.prototype; + return child; + }; + + define(['jquery', 'underscore', 'gettext', 'xblock/runtime.v1', 'js/views/xblock', 'js/views/modals/edit_xblock'], + function($, _, gettext, XBlock, XBlockView, EditXBlockModal) { + var ModuleEdit = (function(_super) { + + __extends(ModuleEdit, _super); + + function ModuleEdit() { + return ModuleEdit.__super__.constructor.apply(this, arguments); + } + + ModuleEdit.prototype.tagName = 'li'; + + ModuleEdit.prototype.className = 'component'; + + ModuleEdit.prototype.editorMode = 'editor-mode'; + + ModuleEdit.prototype.events = { + 'click .edit-button': 'clickEditButton', + 'click .delete-button': 'onDelete' + }; + + ModuleEdit.prototype.initialize = function() { + this.onDelete = this.options.onDelete; + return this.render(); + }; + + ModuleEdit.prototype.loadDisplay = function() { + var xblockElement; + xblockElement = this.$el.find('.xblock-student_view'); + if (xblockElement.length > 0) { + return XBlock.initializeBlock(xblockElement); + } + }; + + ModuleEdit.prototype.createItem = function(parent, payload, callback) { + var _this = this; + if (_.isNull(callback)) { + callback = function() {}; + } + payload.parent_locator = parent; + return $.postJSON(this.model.urlRoot + '/', payload, function(data) { + _this.model.set({ + id: data.locator + }); + _this.$el.data('locator', data.locator); + _this.$el.data('courseKey', data.courseKey); + return _this.render(); + }).success(callback); + }; + + ModuleEdit.prototype.loadView = function(viewName, target, callback) { + var _this = this; + if (this.model.id) { + return $.ajax({ + url: '' + (decodeURIComponent(this.model.url())) + '/' + viewName, + type: 'GET', + cache: false, + headers: { + Accept: 'application/json' + }, + success: function(fragment) { + return _this.renderXBlockFragment(fragment, target).done(callback); + } + }); + } + }; + + ModuleEdit.prototype.render = function() { + var _this = this; + return this.loadView('student_view', this.$el, function() { + _this.loadDisplay(); + return _this.delegateEvents(); + }); + }; + + ModuleEdit.prototype.clickEditButton = function(event) { + var modal; + event.preventDefault(); + modal = new EditXBlockModal(); + return modal.edit(this.$el, this.model, { + refresh: _.bind(this.render, this) + }); + }; + + return ModuleEdit; + + })(XBlockView); + return ModuleEdit; + }); + +}).call(this); diff --git a/cms/static/js/views/tabs.js b/cms/static/js/views/tabs.js index 9f83edeb19..04ace25d7e 100644 --- a/cms/static/js/views/tabs.js +++ b/cms/static/js/views/tabs.js @@ -19,7 +19,7 @@ }; define(['underscore', 'jquery', 'jquery.ui', 'backbone', 'common/js/components/views/feedback_prompt', - 'common/js/components/views/feedback_notification', 'coffee/src/views/module_edit', + 'common/js/components/views/feedback_notification', 'js/views/module_edit', 'js/models/module_info', 'js/utils/module'], function(_, $, ui, Backbone, PromptView, NotificationView, ModuleEditView, ModuleModel, ModuleUtils) { var TabsEdit;