diff --git a/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py b/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py index 0f0788c4b6..84187d1c80 100644 --- a/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py +++ b/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py @@ -105,8 +105,7 @@ def click_component_from_menu(category, component_type, is_advanced): @world.absorb def edit_component_and_select_settings(): - world.wait_for(lambda _driver: world.css_visible('a.edit-button')) - world.css_click('a.edit-button') + world.edit_component() world.css_click('.settings-button') @@ -127,7 +126,6 @@ def select_editor_tab(tab_name): world.wait_for_ajax_complete() - def enter_xml_in_advanced_problem(step, text): """ Edits an advanced problem (assumes only on page), diff --git a/cms/djangoapps/contentstore/features/pages.py b/cms/djangoapps/contentstore/features/pages.py index 2306dfb768..43f7bacfef 100644 --- a/cms/djangoapps/contentstore/features/pages.py +++ b/cms/djangoapps/contentstore/features/pages.py @@ -50,8 +50,7 @@ def change_name(step, new_name): world.css_fill(input_css, new_name) if world.is_firefox(): world.trigger_event(input_css) - save_button = 'a.action-save' - world.css_click(save_button) + world.save_component() @step(u'I drag the first static page to the last$') diff --git a/cms/djangoapps/contentstore/features/problem-editor.py b/cms/djangoapps/contentstore/features/problem-editor.py index 1c480b2d47..9bada6f78c 100644 --- a/cms/djangoapps/contentstore/features/problem-editor.py +++ b/cms/djangoapps/contentstore/features/problem-editor.py @@ -286,5 +286,5 @@ def set_weight(weight): def open_high_level_source(): - world.css_click('a.edit-button') + world.edit_component() world.css_click('.launch-latex-compiler > a') diff --git a/cms/djangoapps/contentstore/features/transcripts.py b/cms/djangoapps/contentstore/features/transcripts.py index c1bcb5e27c..4a864b6026 100644 --- a/cms/djangoapps/contentstore/features/transcripts.py +++ b/cms/djangoapps/contentstore/features/transcripts.py @@ -218,9 +218,7 @@ def check_transcripts_field(_step, values, field_name): @step('I save changes$') def save_changes(_step): - save_css = 'a.action-save' - world.css_click(save_css) - world.wait_for_ajax_complete() + world.save_component() @step('I open tab "([^"]*)"$') diff --git a/cms/djangoapps/contentstore/features/video-editor.py b/cms/djangoapps/contentstore/features/video-editor.py index 833fecdeaf..952a92ef36 100644 --- a/cms/djangoapps/contentstore/features/video-editor.py +++ b/cms/djangoapps/contentstore/features/video-editor.py @@ -112,11 +112,10 @@ def set_show_captions(step, setting): # Prevent cookies from overriding course settings world.browser.cookies.delete('hide_captions') - world.css_click('a.edit-button') - world.wait_for(lambda _driver: world.css_visible('a.action-save')) + world.edit_component() world.select_editor_tab('Advanced') world.browser.select('Transcript Display', setting) - world.css_click('a.action-save') + world.save_component() @step('when I view the video it (.*) show the captions$') @@ -161,7 +160,7 @@ def correct_video_settings(_step): @step('my video display name change is persisted on save$') def video_name_persisted(step): - world.css_click('a.action-save') + world.save_component() reload_the_page(step) world.wait_for_xmodule() world.edit_component() diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py index 3161575309..8203952009 100644 --- a/cms/djangoapps/contentstore/views/component.py +++ b/cms/djangoapps/contentstore/views/component.py @@ -273,7 +273,7 @@ def unit_handler(request, tag=None, package_id=None, branch=None, version_guid=N 'context_course': course, 'unit': item, 'unit_locator': locator, - 'xblocks': [xblock for xblock in xblocks], + 'xblocks': xblocks, 'locators': locators, 'component_templates': component_templates, 'draft_preview_link': preview_lms_link, @@ -320,8 +320,8 @@ def container_handler(request, tag=None, package_id=None, branch=None, version_g parent = get_parent_xblock(parent) ancestor_xblocks.reverse() - unit = None if not ancestor_xblocks else ancestor_xblocks[0] - unit_publish_state = None if not unit else compute_publish_state(unit) + unit = ancestor_xblocks[0] if ancestor_xblocks else None + unit_publish_state = compute_publish_state(unit) if unit else None return render_to_response('container.html', { 'context_course': course, diff --git a/cms/static/js/spec/views/modals/base_modal_spec.js b/cms/static/js/spec/views/modals/base_modal_spec.js index 50d7f2dbff..7f5126e63e 100644 --- a/cms/static/js/spec/views/modals/base_modal_spec.js +++ b/cms/static/js/spec/views/modals/base_modal_spec.js @@ -2,7 +2,7 @@ define(["jquery", "underscore", "js/views/modals/base_modal"], function ($, _, BaseModal) { describe("BaseModal", function() { - var baseViewPrototype, MockModal; + var baseViewPrototype, MockModal, modal; MockModal = BaseModal.extend({ initialize: function() { @@ -14,8 +14,14 @@ define(["jquery", "underscore", "js/views/modals/base_modal"], } }); + afterEach(function() { + if (modal) { + modal.hide(); + } + }); + it('is visible after show is called', function () { - var modal = new MockModal(); + modal = new MockModal(); modal.render(); modal.show(); expect($('body')).toHaveClass('modal-window-is-shown'); @@ -24,7 +30,7 @@ define(["jquery", "underscore", "js/views/modals/base_modal"], }); it('is invisible after hide is called', function () { - var modal = new MockModal(); + modal = new MockModal(); modal.render(); modal.show(); modal.hide(); diff --git a/cms/static/js/spec/views/xblock_container_spec.js b/cms/static/js/spec/views/xblock_container_spec.js index 3b451e22c7..fb8f8047c8 100644 --- a/cms/static/js/spec/views/xblock_container_spec.js +++ b/cms/static/js/spec/views/xblock_container_spec.js @@ -40,6 +40,9 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/edit_helpers" afterEach(function() { window.MockXBlock = null; + if (edit_helpers.isShowingModal()) { + edit_helpers.cancelModal(); + } }); mockContainerXBlockHtml = readFixtures('mock/mock-container-xblock.underscore'); @@ -66,14 +69,18 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/edit_helpers" "resources": [] }); editButtons = containerView.$('.edit-button'); + // The container renders four mock xblocks, so there should be four edit buttons expect(editButtons.length).toBe(4); editButtons.first().click(); + // Make sure that the correct xblock is requested to be edited + expect(requests[requests.length - 1].url).toBe( + '/xblock/testCourse/branch/draft/block/html447/studio_view' + ); create_sinon.respondWithJson(requests, { html: mockXBlockEditorHtml, "resources": [] }); expect($('.wrapper-modal-window')).toHaveClass('is-shown'); - edit_helpers.cancelModal(); }); }); }); diff --git a/cms/static/js/spec_helpers/edit_helpers.js b/cms/static/js/spec_helpers/edit_helpers.js index 06055a0cf3..62289158b2 100644 --- a/cms/static/js/spec_helpers/edit_helpers.js +++ b/cms/static/js/spec_helpers/edit_helpers.js @@ -13,6 +13,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/views/modals/edit_xblock", editorModeButtonTemplate = readFixtures('editor-mode-button.underscore'), installEditTemplates, showEditModal, + isShowingModal, cancelModal; installEditTemplates = function() { @@ -39,6 +40,10 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/views/modals/edit_xblock", return modal; }; + isShowingModal = function() { + return $('.wrapper-modal-window').length > 0; + }; + cancelModal = function(modal) { var modalElement, cancelButton; if (modal) { @@ -51,10 +56,10 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/views/modals/edit_xblock", cancelButton.click(); }; - return { - "installEditTemplates": installEditTemplates, - "showEditModal": showEditModal, - "cancelModal": cancelModal + 'installEditTemplates': installEditTemplates, + 'showEditModal': showEditModal, + 'isShowingModal': isShowingModal, + 'cancelModal': cancelModal }; }); diff --git a/cms/static/js/views/baseview.js b/cms/static/js/views/baseview.js index 9610025895..61c3272bba 100644 --- a/cms/static/js/views/baseview.js +++ b/cms/static/js/views/baseview.js @@ -43,6 +43,8 @@ define(["jquery", "underscore", "backbone", "js/utils/handle_iframe_binding"], toggleExpandCollapse: function(event) { var target = $(event.target); + // Don't propagate the event as it is possible that two views will both contain + // this element, e.g. clicking on the element of a child view container in a parent. event.stopPropagation(); event.preventDefault(); target.closest('.expand-collapse').toggleClass('expand').toggleClass('collapse'); diff --git a/cms/static/js/views/modals/base_modal.js b/cms/static/js/views/modals/base_modal.js index 9cf6258a25..f9e94f8037 100644 --- a/cms/static/js/views/modals/base_modal.js +++ b/cms/static/js/views/modals/base_modal.js @@ -1,8 +1,8 @@ /** * This is a base modal implementation that provides common utilities. */ -define(["jquery", "underscore", "underscore.string", "gettext", "js/views/baseview"], - function($, _, str, gettext, BaseView) { +define(["jquery", "js/views/baseview"], + function($, BaseView) { var BaseModal = BaseView.extend({ options: $.extend({}, BaseView.prototype.options, { type: "prompt", diff --git a/cms/static/js/views/modals/edit_xblock.js b/cms/static/js/views/modals/edit_xblock.js index a263445e41..d11681fd92 100644 --- a/cms/static/js/views/modals/edit_xblock.js +++ b/cms/static/js/views/modals/edit_xblock.js @@ -20,9 +20,9 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal", /** * Show an edit modal for the specified xblock - * @param xblockElement The - * @param rootXBlockInfo - * @param options + * @param xblockElement The element that contains the xblock to be edited. + * @param rootXBlockInfo An XBlockInfo model that describes the root xblock on the page. + * @param options A standard options object. */ edit: function(xblockElement, rootXBlockInfo, options) { this.xblockElement = xblockElement; @@ -36,20 +36,17 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal", }, render: function() { - var xblockInfo = this.xblockInfo; this.$el.html(this.template({ - xblockInfo: xblockInfo + xblockInfo: this.xblockInfo })); }, displayXBlock: function() { - var xblockInfo = this.xblockInfo, - editorView = new XBlockEditorView({ - el: this.$('.xblock-editor'), - model: xblockInfo - }); - this.editorView = editorView; - editorView.render({ + this.editorView = new XBlockEditorView({ + el: this.$('.xblock-editor'), + model: this.xblockInfo + }); + this.editorView.render({ success: _.bind(this.onDisplayXBlock, this) }); }, @@ -69,13 +66,22 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal", } else { this.$('.modal-window-title').text(title); if (editorView.getMetadataEditor()) { - this.addModeButton('editor', gettext("Editor")); - this.addModeButton('settings', gettext("Settings")); + this.addDefaultModes(); this.selectMode(editorView.mode); } } }, + addDefaultModes: function() { + var defaultModes = this.editorView.getDefaultModes(), + i, + mode; + for (i = 0; i < defaultModes.length; i++) { + mode = defaultModes[i]; + this.addModeButton(mode.id, mode.name); + } + }, + changeMode: function(event) { var parent = $(event.target.parentElement), mode = parent.data('mode'); @@ -107,7 +113,6 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal", this.editorView.save({ success: function() { self.hide(); - self.$el.html(""); if (refresh) { refresh(xblockInfo); } @@ -124,24 +129,11 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal", }, findXBlockInfo: function(xblockElement, defaultXBlockInfo) { - var xblockInfo = defaultXBlockInfo, - locator, - displayName, - category; + var xblockInfo = defaultXBlockInfo; if (xblockElement.length > 0) { - locator = xblockElement.data('locator'); - displayName = xblockElement.data('display-name'); - category = xblockElement.data('category'); - if (!displayName) { - displayName = category; - if (!category) { - displayName = gettext('Empty'); - } - } xblockInfo = new XBlockInfo({ - id: locator, - display_name: displayName, - category: category + id: xblockElement.data('locator'), + category: xblockElement.data('category') }); } return xblockInfo; diff --git a/cms/static/js/views/xblock_container.js b/cms/static/js/views/xblock_container.js index 2a7ee51338..af4886a77a 100644 --- a/cms/static/js/views/xblock_container.js +++ b/cms/static/js/views/xblock_container.js @@ -31,7 +31,7 @@ define(["jquery", "underscore", "js/views/baseview", "js/views/xblock", "js/view noContentElement.addClass('is-hidden'); xblockView.$el.addClass('is-hidden'); - // Add actions to any root buttons + // Add actions to any top level buttons, e.g. "Edit" of the container itself self.addButtonActions(this.$el); // Render the xblock diff --git a/cms/static/js/views/xblock_editor.js b/cms/static/js/views/xblock_editor.js index b8771b3801..88911b2510 100644 --- a/cms/static/js/views/xblock_editor.js +++ b/cms/static/js/views/xblock_editor.js @@ -38,6 +38,13 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification", "js } }, + getDefaultModes: function() { + return [ + { id: 'editor', name: gettext("Editor")}, + { id: 'settings', name: gettext("Settings")} + ]; + }, + hasCustomTabs: function() { return this.$('.editor-with-tabs').length > 0; }, diff --git a/cms/templates/js/mock/mock-container-view.underscore b/cms/templates/js/mock/mock-container-view.underscore index 5e8616f5c6..88b191cee3 100644 --- a/cms/templates/js/mock/mock-container-view.underscore +++ b/cms/templates/js/mock/mock-container-view.underscore @@ -7,7 +7,7 @@

- Unit 1 + Unit 1 Nested Vertical Test

@@ -25,7 +25,7 @@
-
+
diff --git a/cms/templates/js/mock/mock-container-xblock.underscore b/cms/templates/js/mock/mock-container-xblock.underscore index fbd2c7bd8e..558175c799 100644 --- a/cms/templates/js/mock/mock-container-xblock.underscore +++ b/cms/templates/js/mock/mock-container-xblock.underscore @@ -1,4 +1,4 @@ -
+