diff --git a/src/wirecloud/commons/static/js/StyledElements/Container.js b/src/wirecloud/commons/static/js/StyledElements/Container.js index 3a77018aad..cd071832e2 100644 --- a/src/wirecloud/commons/static/js/StyledElements/Container.js +++ b/src/wirecloud/commons/static/js/StyledElements/Container.js @@ -83,92 +83,61 @@ }, /** + * Insert the `newElement` either to the end of this Container + * or after the `refElement` given. + * @since 0.5 + * + * @param {(StyledElements.StyledElement|Node|String)} newElement + * An element to insert into this Container. + * @param {(StyledElements.StyledElement|Node)} [refElement] + * Optional. An element after which `newElement` is inserted. + * + * @returns {StyledElements.Container} + * The instance on which the member is called. */ - remove: function remove(childElement) { - var index; - - if (childElement == null) { - return this.superMember(se.StyledElement, 'remove', childElement); - } - - if (childElement instanceof se.StyledElement) { - if ((index = this.children.indexOf(childElement)) === -1) { - return this; - } - - this.children.splice(index, 1); - childElement.parentElement = null; - - // Get the DOM element - childElement = childElement.get(); - } - - if (childElement.parentElement === this.get()) { - this.get().removeChild(childElement); - } - + appendChild: function appendChild(newElement, refElement) { + utils.appendChild(this, newElement, refElement).forEach(addChild.bind(this)); + orderbyIndex.call(this); return this; }, /** - * Insert an element at the end of this container or before the - * refElement if provided. + * Inserts the `newElement` to the beginning of this Container + * or before the `refElement` given. + * @since 0.7 + * + * @param {(StyledElements.StyledElement|Node|String)} newElement + * An element to insert into this Container. + * @param {(StyledElements.StyledElement|Node)} [refElement] + * Optional. An element before which `newElement` is inserted. * - * @since 0.5 - * @param {StyledElements.StyledElement|HTMLElement|String} newElement - * An element to insert into the wrapperElement. - * @param {StyledElements.StyledElement|HTMLElement} [refElement] - * Optional. An element after which newElement is inserted. * @returns {StyledElements.Container} * The instance on which the member is called. */ - appendChild: function appendChild(element, refElement) { - if (element instanceof StyledElements.StyledElement) { - element.insertInto(this.wrapperElement, refElement); - element.parentElement = this; - this.children.push(element); - - return this; - } - - if (typeof element === "string") { - element = document.createTextNode(element); - } - - if (refElement instanceof StyledElements.StyledElement) { - refElement = refElement.get(); - } - - if (refElement != null) { - this.wrapperElement.insertBefore(element, refElement); - } else { - this.wrapperElement.appendChild(element); - } - + prependChild: function prependChild(newElement, refElement) { + utils.prependChild(this, newElement, refElement).forEach(addChild.bind(this)); + orderbyIndex.call(this); return this; }, /** - * Inserts a new element to the beginning of this Container - * @since 0.6 + * Removes the `childElement` from this Container. + * @since 0.5 + * + * @param {(StyledElements.StyledElement|Node)} childElement + * An element to remove from this Container. * - * @param {StyledElement|HTMLElement|String} newElement - * An element to insert into the wrapperElement. - * @param {StyledElement|HTMLElement} [refElement] - * Optional. An element before which newElement is inserted. - * @returns {StyledElement} + * @returns {StyledElements.Container} * The instance on which the member is called. */ - prependChild: function prependChild(newElement, refElement) { - return this.appendChild(newElement, this.get().firstChild); - }, + removeChild: function removeChild(childElement) { + utils.removeChild(this, childElement); - removeChild: function removeChild(element) { - if (element == null) { - throw new TypeError('missing element parameter'); + if (childElement instanceof se.StyledElement) { + this.children.splice(this.children.indexOf(childElement), 1); } - return this.remove(element); + return this; }, repaint: function repaint(temporal) { @@ -260,4 +229,34 @@ useFullHeight: false }; + var addChild = function addChild(newElement) { + /* jshint validthis: true */ + + if (newElement instanceof se.StyledElement) { + var index = this.children.indexOf(newElement); + + if (index === -1) { + this.children.push(newElement); + } + } + }; + + var orderbyIndex = function orderbyIndex() { + /* jshint validthis: true */ + var children = []; + + Array.prototype.forEach.call(this.get().childNodes, function (childNode) { + var i, elementFound = false; + + for (i = 0; i < this.children.length && !elementFound; i++) { + if (this.children[i].get() === childNode) { + children.push(this.children.splice(i, 1)[0]); + elementFound = true; + } + } + }.bind(this)); + + this.children = children; + }; + })(StyledElements, StyledElements.Utils); diff --git a/src/wirecloud/commons/static/js/StyledElements/Fragment.js b/src/wirecloud/commons/static/js/StyledElements/Fragment.js index b969465f31..6d6c1c4d0b 100644 --- a/src/wirecloud/commons/static/js/StyledElements/Fragment.js +++ b/src/wirecloud/commons/static/js/StyledElements/Fragment.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2015 CoNWeT Lab., Universidad Politécnica de Madrid + * Copyright (c) 2012-2016 CoNWeT Lab., Universidad Politécnica de Madrid * * This file is part of Wirecloud Platform. * @@ -19,66 +19,124 @@ * */ + /*globals StyledElements */ -(function () { + +(function (se, utils) { "use strict"; + // ================================================================================== + // CLASS DEFINITION + // ================================================================================== + /** + * Creates a new instance of Fragment. + * @name StyledElements.Fragment + * @since 0.5 * + * @constructor + * @extends {StyledElements.StyledElement} + * + * @param {(Array|String|Node|StyledElement)} newElement + * An element or list of elements. + * */ - var Fragment = function Fragment(elements) { - var tmp_element; - - if (Array.isArray(elements)) { - this.elements = elements; - } else if (typeof elements === 'string') { - tmp_element = document.createElement('div'); - tmp_element.innerHTML = elements; - this.elements = Array.prototype.slice.call(tmp_element.childNodes); + se.Fragment = function Fragment(newElement) { + this.superClass(); + + /** + * The list of elements stored. + * @since 0.7 + * + * @memberof StyledElements.Fragment# + * @type {Array.<(Node|StyledElements.StyledElement)>} + */ + this.children = []; + + if (Array.isArray(newElement)) { + newElement.forEach(function (childElement) { + this.appendChild(childElement); + }.bind(this)); } else { - this.elements = []; + this.appendChild(newElement); } + + Object.defineProperties(this, { + elements: { + get: function get() {return this.children;} + } + }); }; - Fragment.prototype = new StyledElements.StyledElement(); - /* - * @override - */ - Fragment.prototype.insertInto = function insertInto(element, refElement) { - var i, currentElement; + // ================================================================================== + // PUBLIC MEMBERS + // ================================================================================== - if (refElement instanceof StyledElements.StyledElement) { - refElement = refElement.wrapperElement; - } + utils.inherit(se.Fragment, se.StyledElement, /** @lends StyledElements.Fragment.prototype */{ + + /** + * Insert the `newElement` to the end of this Fragment. + * @since 0.5 + * + * @param {(Node|String|StyledElements.StyledElement)} newElement + * An element to insert into this Fragment. + * + * @returns {StyledElements.Fragment} + * The instance on which the member is called. + */ + appendChild: function appendChild(newElement) { - for (i = 0; i < this.elements.length; i += 1) { - currentElement = this.elements[i]; - if (currentElement instanceof StyledElements.StyledElement) { - currentElement.insertInto(element, refElement); + if (newElement == null) { + return this; + } + + if (typeof newElement === 'string') { + this.children = this.children.concat(getChildrenFromText(newElement)); + } else if (newElement instanceof se.Fragment) { + this.children = this.children.concat(newElement.children); } else { - element.insertBefore(currentElement, refElement); + this.children.push(newElement); } - } - }; - Fragment.prototype.repaint = function repaint() { - var i; + return this; + }, - for (i = 0; i < this.elements.length; i++) { - if (typeof this.elements[i].repaint === 'function') { - this.elements[i].repaint(); - } + /* + * @override + */ + appendTo: function appendTo(parentElement, refElement) { + this.children.forEach(function (childElement) { + utils.appendChild(parentElement, childElement, refElement); + }); + return this; + }, + + /* + * @override + */ + repaint: function repaint() { + this.children.forEach(function (childElement) { + if (typeof childElement.repaint === 'function') { + childElement.repaint(); + } + }) + return this; } - return this; - }; - Fragment.prototype.appendChild = function appendChild(element) { - this.elements.push(element); + }); + + var getChildrenFromText = function getChildrenFromText(text) { + var targetElement, children = []; + + if (text.length) { + targetElement = document.createElement('div'); + targetElement.innerHTML = text; + children = Array.prototype.slice.call(targetElement.childNodes); + } - return this; + return children; }; - StyledElements.Fragment = Fragment; -})(); +})(StyledElements, StyledElements.Utils); diff --git a/src/wirecloud/commons/static/js/StyledElements/Panel.js b/src/wirecloud/commons/static/js/StyledElements/Panel.js index 11c02fda48..bfa702ad2c 100644 --- a/src/wirecloud/commons/static/js/StyledElements/Panel.js +++ b/src/wirecloud/commons/static/js/StyledElements/Panel.js @@ -71,8 +71,7 @@ } if (options.subtitle) { - this.subtitle = new se.Container({extraClass: "panel-subtitle"}); - this.heading.appendChild(this.subtitle.appendChild(options.subtitle)); + this.setSubtitle(options.subtitle); } if (!options.noBody) { @@ -141,6 +140,18 @@ this.heading.title.clear().appendChild(title); + return this; + }, + + setSubtitle: function setSubtitle(subtitle) { + + if (this.heading.subtitle == null) { + this.heading.subtitle = new se.Container({extraClass: "panel-subtitle"}); + this.heading.appendChild(this.heading.subtitle); + } + + this.heading.subtitle.clear().appendChild(subtitle); + return this; } diff --git a/src/wirecloud/commons/static/js/StyledElements/StyledElements.js b/src/wirecloud/commons/static/js/StyledElements/StyledElements.js index ea565418e8..ab650b14fe 100644 --- a/src/wirecloud/commons/static/js/StyledElements/StyledElements.js +++ b/src/wirecloud/commons/static/js/StyledElements/StyledElements.js @@ -120,16 +120,21 @@ }, /** - * Inserts this StyledElement to the end of the targetElement + * Inserts this StyledElement either to the end of the `parentElement` + * or after the `refElement` given. * @since 0.6 * - * @param {StyledElements.StyledElement|HTMLElement} targetElement - * An element to insert the wrapperElement. + * @param {(StyledElements.StyledElement|Node)} parentElement + * An element to be the parent of this StyledElement. + * @param {(StyledElements.StyledElement|Node)} [refElement] + * Optional. An element after which this StyledElement is inserted. + * * @returns {StyledElements.StyledElement} * The instance on which the member is called. */ - appendTo: function appendTo(targetElement) { - return this.insertInto(targetElement); + appendTo: function appendTo(parentElement, refElement) { + utils.appendChild(parentElement, this, refElement); + return this; }, /** @@ -204,18 +209,21 @@ }, /** - * Insert the wrapperElement to the beginning of the targetElement children. - * @param {StyledElements.StyledElement|HTMLElement} targetElement - * An element to insert the wrapperElement. + * Inserts this StyledElement either to the beginning of the `parentElement` + * or before the `refElement` given. + * @since 0.7 + * + * @param {(StyledElements.StyledElement|Node)} parentElement + * An element to be the parent of this StyledElement. + * @param {(StyledElements.StyledElement|Node)} [refElement] + * Optional. An element before which this StyledElement is inserted. + * * @returns {StyledElements.StyledElement} * The instance on which the member is called. */ - prependTo: function prependTo(targetElement) { - if (targetElement instanceof se.StyledElement) { - return targetElement.prependChild(this); - } else { - return this.insertInto(targetElement, targetElement.firstChild); - } + prependTo: function prependTo(parentElement, refElement) { + utils.prependChild(parentElement, this, refElement); + return this; }, /** @@ -227,12 +235,10 @@ */ remove: function remove() { - if (this.parentElement != null) { - this.parentElement.remove(this); + if (this.parentElement instanceof se.StyledElement && typeof this.parentElement.removeChild === 'function') { + this.parentElement.removeChild(this); } else { - if (this.get().parentElement != null) { - this.get().parentElement.removeChild(this.get()); - } + this.get().remove(); } return this; @@ -405,34 +411,19 @@ }, /** - * Inserts this StyledElement at the end of the given element. If - * the refElement parameter is used, then this StyledElement will be - * inserted before refElement. + * Inserts this StyledElement either at the end of the `parentElement` or + * before the `refElement` given. + * + * @param {(StyledElements.StyledElement|Node)} parentElement + * An element to be the parent of this StyledElement. + * @param {(StyledElements.StyledElement|Node)} [refElement] + * Optional. An element before which this StyledElement is inserted. * - * @param {Container|HTMLElement} element - * An element where this StyledElement will be inserted. - * @param {StyledElements.StyledElement|HTMLElement} [refElement] - * Optional. An element after which newElement is going to be - * inserted. * @returns {StyledElements.StyledElement} * The instance on which the member is called. */ - insertInto: function insertInto(element, refElement) { - if (element instanceof StyledElements.StyledElement) { - element = element.wrapperElement; - } - - if (refElement instanceof StyledElements.StyledElement) { - refElement = refElement.wrapperElement; - } - - if (refElement) { - element.insertBefore(this.wrapperElement, refElement); - } else { - element.appendChild(this.wrapperElement); - } - - return this; + insertInto: function insertInto(parentElement, refElement) { + return refElement != null ? this.prependTo(parentElement, refElement) : this.appendTo(parentElement); }, /** diff --git a/src/wirecloud/commons/static/js/StyledElements/Utils.js b/src/wirecloud/commons/static/js/StyledElements/Utils.js index 48e1534c93..d8cc9d61a4 100644 --- a/src/wirecloud/commons/static/js/StyledElements/Utils.js +++ b/src/wirecloud/commons/static/js/StyledElements/Utils.js @@ -1037,6 +1037,107 @@ if (window.StyledElements == null) { return key; }; + // ================================================================================== + // Node helpers + // ================================================================================== + + /** + * Insert the `newElement` either to the end of `parentElement` or after + * the `refElement` given. + * @since 0.7 + * + * @param {(StyledElements.StyledElement|Node)} parentElement + * An element to be the parent of the `newElement`. + * @param {(StyledElements.StyledElement|Node|String)} newElement + * An element to insert into the `parentElement`. + * @param {(StyledElements.StyledElement|Node)} [refElement] + * Optional. An element after which `newElement` is inserted. + * + * @returns {Array.<(StyledElements.StyledElement|Node)>} + * The new elements that were inserted. + */ + Utils.appendChild = function appendChild(parentElement, newElement, refElement) { + var parentNode = getNode(parentElement); + var refNode = getNode(refElement); + + if (refNode != null) { + refNode = refNode.nextSibling; + } + + if (typeof newElement === 'string') { + newElement = document.createTextNode(newElement); + } + + return getNodes(newElement).map(function (childElement) { + parentNode.insertBefore(getNode(childElement), refNode); + + if (parentElement instanceof StyledElements.StyledElement && childElement instanceof StyledElements.StyledElement) { + childElement.parentElement = parentElement; + } + + return childElement; + }); + }; + + /** + * Insert the `newElement` either to the beginning of `parentElement` or + * before the `refElement` given. + * @since 0.7 + * + * @param {(StyledElements.StyledElement|Node)} parentElement + * An element to be the parent of the `newElement`. + * @param {(StyledElements.StyledElement|Node|String)} newElement + * An element to insert into the `parentElement`. + * @param {(StyledElements.StyledElement|Node)} [refElement] + * Optional. An element before which `newElement` is inserted. + * + * @returns {Array.<(StyledElements.StyledElement|Node)>} + * The new elements that were inserted. + */ + Utils.prependChild = function prependChild(parentElement, newElement, refElement) { + var parentNode = getNode(parentElement); + var refNode = getNode(refElement); + + if (typeof newElement === 'string') { + newElement = document.createTextNode(newElement); + } + + return getNodes(newElement).map(function (childElement) { + parentNode.insertBefore(getNode(childElement), refNode == null ? parentNode.firstChild : refNode); + + if (parentElement instanceof StyledElements.StyledElement && childElement instanceof StyledElements.StyledElement) { + childElement.parentElement = parentElement; + } + + return childElement; + }); + }; + + /** + * Remove the `childElement` from the `parentElement`. + * @since 0.7 + * + * @param {(StyledElements.StyledElement|Node)} parentElement + * An element that is parent of `childElement`. + * @param {(StyledElements.StyledElement|Node)} childElement + * An element to remove from the `parentElement`. + */ + Utils.removeChild = function removeChild(parentElement, childElement) { + getNode(parentElement).removeChild(getNode(childElement)); + + if (parentElement instanceof StyledElements.StyledElement && childElement instanceof StyledElements.StyledElement) { + childElement.parentElement = null; + } + }; + + var getNode = function getNode(value) { + return value instanceof StyledElements.StyledElement ? value.get() : value; + }; + + var getNodes = function getNodes(value) { + return (value instanceof StyledElements.StyledElement && value.get() == null) ? value.children : [value]; + }; + StyledElements.Utils = Utils; })(); diff --git a/src/wirecloud/commons/test-data/Wirecloud_TestOperator_2.0.zip b/src/wirecloud/commons/test-data/Wirecloud_TestOperator_2.0.zip new file mode 100644 index 0000000000..b6b63cb0a3 Binary files /dev/null and b/src/wirecloud/commons/test-data/Wirecloud_TestOperator_2.0.zip differ diff --git a/src/wirecloud/commons/utils/remote.py b/src/wirecloud/commons/utils/remote.py index 565a31acdc..c55070ec8f 100644 --- a/src/wirecloud/commons/utils/remote.py +++ b/src/wirecloud/commons/utils/remote.py @@ -554,6 +554,17 @@ def set_value(self, value): return self +class ChoiceFieldTester(WebElementTester): + + @property + def options(self): + return Select(self.element).options + + def set_value(self, value): + Select(self.element).select_by_value(value) + return self + + class ModalTester(WebElementTester): @property @@ -570,12 +581,12 @@ def btn_cancel(self): def accept(self): self.btn_accept.click() - + WebDriverWait(self.testcase.driver, timeout=5).until(EC.staleness_of(self.element)) return self def cancel(self): self.btn_cancel.click() - + WebDriverWait(self.testcase.driver, timeout=5).until(EC.staleness_of(self.element)) return self def find_button(self, title): @@ -585,20 +596,32 @@ def find_button(self, title): return None +class AlertTester(WebElementTester): + + @property + def title(self): + return self.find_element(".wc-log-title").text + + class AlertModalTester(ModalTester): @property def count(self): return len(self.find_alerts()) - def find_alerts(self, state=None): - return self.body.find_elements_by_css_selector(".alert" if state is None else ".alert-%s" % (state,)) + def find_alerts(self, title=None, state=None): + css_selector = ".alert" if state is None else ".alert.alert-%s" % (state,) + elements = [AlertTester(self.testcase, e) for e in self.body.find_elements_by_css_selector(css_selector)] + return elements if title is None else [e for e in elements if e.title == title] class FormModalTester(ModalTester): def get_field(self, name): - return FieldTester(self.testcase, self.body.find_element_by_css_selector("[name='%s']" % (name,))) + field = self.body.find_element_by_css_selector("[name='%s']" % (name,)) + if field.tag_name == 'select': + return ChoiceFieldTester(self.testcase, field) + return FieldTester(self.testcase, field) class BaseComponentTester(WebElementTester): @@ -628,7 +651,7 @@ def rename(self, title): def show_logs(self): self.show_preferences().click_entry("Logs") - return AlertModalTester(self.testcase, self.testcase.wait_element_visible_by_css_selector(".logwindowmenu")) + return AlertModalTester(self.testcase, self.testcase.wait_element_visible_by_css_selector(".wc-component-logs-dialog")) def show_preferences(self): button = self.btn_preferences.click() @@ -638,6 +661,13 @@ def show_settings(self): self.show_preferences().click_entry("Settings") return FormModalTester(self.testcase, self.testcase.wait_element_visible_by_css_selector(".wc-component-preferences-dialog")) + def change_version(self, version): + self.show_preferences().click_entry("Upgrade/Downgrade") + modal = FormModalTester(self.testcase, self.testcase.wait_element_visible_by_css_selector(".wc-upgrade-component-dialog")) + modal.get_field('version').set_value(version) + modal.accept() + return self + class WiringComponentTester(BaseComponentTester): @@ -646,6 +676,10 @@ def state(self): label_element = self.find_element(".label") return "" if label_element is None else label_element.text + @property + def version(self): + return self.find_element(".component-version").text + def drag_and_drop(self, condition, target_element, x, y): ActionChains(self.testcase.driver).click_and_hold(self.element).perform() WebDriverWait(self.testcase.driver, timeout=5).until(condition) @@ -685,6 +719,9 @@ def create_component(self): return self.find_components()[-1] def find_component(self, id=None, title=None): + if id is not None and not isinstance(id, six.string_types): + id = "%s" % id + for component in self.find_components(): if (id is not None and id == component.id) or (title is not None and title == component.title): return component diff --git a/src/wirecloud/platform/static/js/wirecloud/ui/LogWindowMenu.js b/src/wirecloud/platform/static/js/wirecloud/ui/LogWindowMenu.js index 57c7d2680b..4a24aa0c53 100644 --- a/src/wirecloud/platform/static/js/wirecloud/ui/LogWindowMenu.js +++ b/src/wirecloud/platform/static/js/wirecloud/ui/LogWindowMenu.js @@ -40,16 +40,21 @@ }; var print_entry = function print_entry(entry) { - var entry_element, dateElement, expander; + var entry_element, dateElement, expander, titleElement; entry_element = document.createElement('div'); entry_element.className = 'fade alert ' + LEVEL_CLASS[entry.level - 1]; dateElement = document.createElement('strong'); + dateElement.className = "wc-log-date"; dateElement.textContent = entry.date.strftime('%x %X');//_('short_date'))); entry_element.appendChild(dateElement); - entry_element.appendChild(document.createTextNode(entry.msg)); + titleElement = document.createElement('span'); + titleElement.className = "wc-log-title"; + titleElement.appendChild(document.createTextNode(entry.msg)); + + entry_element.appendChild(titleElement); if (entry.details != null) { expander = new StyledElements.Expander({title: gettext('Details')}); diff --git a/src/wirecloud/platform/static/js/wirecloud/ui/UpgradeWindowMenu.js b/src/wirecloud/platform/static/js/wirecloud/ui/UpgradeWindowMenu.js index 3d0306e81e..14b3f287d6 100644 --- a/src/wirecloud/platform/static/js/wirecloud/ui/UpgradeWindowMenu.js +++ b/src/wirecloud/platform/static/js/wirecloud/ui/UpgradeWindowMenu.js @@ -104,7 +104,7 @@ return -version1.compareTo(version2); }); - this.version_selector = new StyledElements.Select(); + this.version_selector = new StyledElements.Select({'name': "version"}); this.version_selector.addEventListener('change', request_version.bind(this)); this.version_selector.addEntries(versions); @@ -122,6 +122,7 @@ this.acceptButton.addEventListener("click", function () { this.acceptButton.disable(); var new_version = this.version_selector.getValue(); + var old_version = this.component.meta.version; var new_component_id = [this.component.meta.vendor, this.component.meta.name, new_version].join('/'); var new_component = Wirecloud.LocalCatalogue.getResourceId(new_component_id); @@ -130,13 +131,13 @@ component.removeEventListener('upgradeerror', _onfailure_listener); var msg; - if (new_version.compareTo(this.component.meta.version) > 0) { - msg = utils.gettext('Upgrading to version %(version)s'); + if (new_version.compareTo(old_version) > 0) { + msg = utils.gettext("The %(type)s was upgraded to v%(version)s successfully."); } else { - msg = utils.gettext('Downgrading to version %(version)s'); + msg = utils.gettext("The %(type)s was downgraded to v%(version)s successfully."); } - msg = utils.interpolate(msg, {version: new_version}); - component.logManager.log(msg, Wirecloud.constants.LOGGING.INFO_MS); + msg = utils.interpolate(msg, {type: component.meta.type, version: new_version}); + component.logManager.log(msg, Wirecloud.constants.LOGGING.INFO_MSG); this._closeListener(); }.bind(this); @@ -154,7 +155,7 @@ // Cancel button this.cancelButton = new StyledElements.Button({ text: utils.gettext('Cancel'), - 'class': 'btn-primary' + 'class': 'btn-primary btn-cancel' }); this.cancelButton.addEventListener("click", this._closeListener); this.cancelButton.insertInto(this.windowBottom); diff --git a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor.js b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor.js index 17414cb4a3..7061c959a9 100644 --- a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor.js +++ b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor.js @@ -103,7 +103,8 @@ Wirecloud.ui = Wirecloud.ui || {}; component = new ns.WiringEditor.ComponentDraggable(wiringComponent, options); component - // TODO: .on('endpointadded', component_onendpointadded.bind(this)) + .on('endpointadded', component_onendpointadded.bind(this)) + .on('endpointremoved', component_onendpointremoved.bind(this)) .on('change', function () { this.behaviourEngine.updateComponent(component, component.toJSON()); }.bind(this)) @@ -594,6 +595,17 @@ Wirecloud.ui = Wirecloud.ui || {}; return this; }; + var component_onendpointadded = function component_onendpointadded(component, endpoint) { + /*jshint validthis:true */ + bindEndpoint.call(this, endpoint); + }; + + var component_onendpointremoved = function component_onendpointremoved(component, endpoint) { + /*jshint validthis:true */ + this.connectionEngine.removeEndpoint(endpoint); + this.suggestionManager.removeEndpoint(endpoint); + }; + var findEndpoint = function findEndpoint(type, bInfo, wiringEndpoint) { /*jshint validthis:true */ var component, endpoint; diff --git a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/BehaviourEngine.js b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/BehaviourEngine.js index 57315cfdf3..478813bf99 100644 --- a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/BehaviourEngine.js +++ b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/BehaviourEngine.js @@ -123,7 +123,7 @@ .setTitle(gettext("Disable")) .replaceIconClass('icon-lock', 'icon-unlock'); this.btnCreate.show(); - this.body.remove(this.disabledAlert); + this.body.removeChild(this.disabledAlert); this.wrapperElement.appendChild(this.btnGroupElement); } else { this.btnEnable @@ -439,7 +439,7 @@ this.emptyBehaviour(behaviour).activate(_behaviour); - this.body.remove(behaviour); + this.body.removeChild(behaviour); this.behaviours.splice(this.behaviours.indexOf(behaviour), 1); enableToRemoveBehaviour.call(this); @@ -763,7 +763,7 @@ dialog.setMsg(message); dialog.acceptHandler = function () { for (var i = this.behaviours.length - 1; i >= 0; i--) { - this.body.remove(this.behaviours[i]); + this.body.removeChild(this.behaviours[i]); } this.behaviours.length = 0; diff --git a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/Component.js b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/Component.js index ec7949341f..efbd5cbc4f 100644 --- a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/Component.js +++ b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/Component.js @@ -61,9 +61,8 @@ buttons: [this.btnPrefs] }); - this.subtitle.addClassName("component-version"); - this.heading.title.addClassName('text-truncate'); + this.heading.subtitle.addClassName("component-version"); this.label = document.createElement('span'); @@ -82,6 +81,11 @@ if (wiringComponent.volatile || !wiringComponent.hasEndpoints()) { this.disable(); } + + wiringComponent.on('upgraded', function (componentUpdated) { + this.setTitle(componentUpdated.title); + this.setSubtitle("v" + componentUpdated.meta.version.text); + }.bind(this)); }, inherit: se.Panel, diff --git a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ComponentDraggable.js b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ComponentDraggable.js index 01f592ceeb..7d7276b6d4 100644 --- a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ComponentDraggable.js +++ b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ComponentDraggable.js @@ -118,10 +118,18 @@ get: function get() {return this.endpoints.source.orderable || this.endpoints.target.orderable;} }, + sources: { + get: function get() {return this.endpoints.source.endpoints;} + }, + sourceList: { get: function get() {return this.endpoints.source.children;} }, + targets: { + get: function get() {return this.endpoints.target.endpoints;} + }, + targetList: { get: function get() {return this.endpoints.target.children;} }, @@ -162,6 +170,10 @@ } }); } + + this._endpoint_onconnectionadded_bound = endpoint_onconnectionadded.bind(this); + this._endpoint_onconnectionremoved_bound = endpoint_onconnectionremoved.bind(this); + appendEndpoints.call(this, 'source', wiringComponent.meta.outputList.map(function (data) {return wiringComponent.outputs[data.name];})); appendEndpoints.call(this, 'target', wiringComponent.meta.inputList.map(function (data) {return wiringComponent.inputs[data.name];})); @@ -180,12 +192,17 @@ this.position(options.position); + this._component_onupgrade_bound = component_onupgrade.bind(this); + this._component_onrename_bound = component_onrename.bind(this); + if (this.type == 'widget') { - wiringComponent.on('title_changed', component_onrename.bind(this)); + wiringComponent.on('title_changed', this._component_onrename_bound); } notifyErrors.call(this); makeDraggable.call(this); + + wiringComponent.on('upgraded', this._component_onupgrade_bound); }, inherit: se.Panel, @@ -302,10 +319,12 @@ return this; }, - appendEndpoint: function appendEndpoint(type, wiringEndpoint, options) { + appendEndpoint: function appendEndpoint(type, wiringEndpoint) { var endpoint = this.endpoints[type].appendEndpoint(wiringEndpoint); endpoint.on('connectionadded', endpoint_onconnectionadded.bind(this)); + endpoint.on('connectionremoved', endpoint_onconnectionremoved.bind(this)); + this.trigger('endpointadded', endpoint); return this; }, @@ -504,6 +523,11 @@ remove: function remove(childElement) { if (!arguments.length && !this.hasClassName('cloned')) { + this._component.off('upgraded', this._component_onupgrade_bound); + + if (this.type == 'widget') { + this._component.off('title_changed', this._component_onrename_bound); + } this.trigger('remove'); } @@ -545,7 +569,7 @@ // PRIVATE MEMBERS // ================================================================================== - var events = ['change', 'dragstart', 'drag', 'dragend', 'optremove', 'optremovecascade', 'optshare', 'remove', 'orderstart', 'orderend']; + var events = ['change', 'dragstart', 'drag', 'dragend', 'endpointadded', 'endpointremoved', 'optremove', 'optremovecascade', 'optshare', 'remove', 'orderstart', 'orderend']; var updateFlagRemoveAllowed = function updateFlagRemoveAllowed() { return this.removeAllowed ? this._showButtonRemove() : this._showButtonDelete(); @@ -555,6 +579,13 @@ endpoints.forEach(function (endpoint) { this.appendEndpoint(type, endpoint); + + if (this._missingEndpoints != null && endpoint.name in this._missingEndpoints[type]) { + this._missingEndpoints[type][endpoint.name].forEachConnection(function (connection) { + this.endpoints[type].endpoints[endpoint.name].appendConnection(connection, true); + }.bind(this)); + delete this._missingEndpoints[type][endpoint.name]; + } }, this); return this; @@ -598,6 +629,15 @@ }; + var endpoint_onconnectionremoved = function endpoint_onconnectionremoved(endpoint, connection) { + /* jshint validthis: true */ + if (endpoint.missing && !endpoint.hasConnections()) { + this.endpoints[endpoint.type].removeChild(endpoint); + this.trigger('endpointremoved', endpoint); + this.refresh(); + } + }; + var expandEndpoints = function expandEndpoints(collapsedWidth) { var offsetWidth; @@ -702,8 +742,67 @@ } }; + var cleanEndpoints = function cleanEndpoints() { + /* jshint validthis: true */ + var id; + + for (id in this.targets) { + cleanEndpoint.call(this, this.targets[id]); + } + + for (id in this.sources) { + cleanEndpoint.call(this, this.sources[id]); + } + }; + + var cleanEndpoint = function cleanEndpoint(endpoint) { + /* jshint validthis: true */ + + if (endpoint.hasConnections()) { + this._missingEndpoints[endpoint.type][endpoint.name] = endpoint; + } + + endpoint.off('connectionadded', this._endpoint_onconnectionadded_bound); + endpoint.off('connectionremoved', this._endpoint_onconnectionremoved_bound); + + this.endpoints[endpoint.type].removeChild(endpoint); + this.trigger('endpointremoved', endpoint); + }; + var component_onrename = function component_onrename(title) { this.setTitle(title).refresh(); }; + var component_onupgrade = function component_onupgrade(componentUpdated) { + /* jshint validthis: true */ + + this.setTitle(componentUpdated.title); + + this._missingEndpoints = {source: {}, target: {}}; + cleanEndpoints.call(this); + + appendEndpoints.call(this, 'source', componentUpdated.meta.outputList.map(function (data) {return componentUpdated.outputs[data.name];})); + appendEndpoints.call(this, 'target', componentUpdated.meta.inputList.map(function (data) {return componentUpdated.inputs[data.name];})); + + appendMissingEndpoints.call(this, componentUpdated, 'source', 'outputs'); + appendMissingEndpoints.call(this, componentUpdated, 'target', 'inputs'); + + delete this._missingEndpoints; + this.refresh(); + }; + + var appendMissingEndpoints = function appendMissingEndpoints(componentUpdated, type, namespace) { + /* jshint validthis: true */ + var name; + + for (name in componentUpdated[namespace]) { + if (name in this._missingEndpoints[type]) { + this.appendEndpoint(type, componentUpdated[namespace][name]); + this._missingEndpoints[type][name].forEachConnection(function (connection) { + this.endpoints[type].endpoints[name].appendConnection(connection, true); + }.bind(this)); + } + } + }; + })(Wirecloud.ui.WiringEditor, StyledElements, StyledElements.Utils); diff --git a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ComponentDraggablePrefs.js b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ComponentDraggablePrefs.js index 3ca30ba485..3be27f45cb 100644 --- a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ComponentDraggablePrefs.js +++ b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ComponentDraggablePrefs.js @@ -75,11 +75,15 @@ }.bind(this.component), canCollapseEndpoints), this._createMenuItem(item2.title, item2.icon, function () { if (this.orderingEndpoints) { - this.StopOrderingEndpoints(); + this.stopOrderingEndpoints(); } else { this.startOrderingEndpoints(); } }.bind(this.component), canOrderEndpoints), + this._createMenuItem(utils.gettext("Upgrade/Downgrade"), "retweet", function () { + var dialog = new Wirecloud.ui.UpgradeWindowMenu(this._component); + dialog.show(); + }.bind(this.component), canUpgrade), this._createMenuItem(gettext("Logs"), "tags", function () { this.showLogs(); }.bind(this.component)), @@ -109,6 +113,10 @@ return this.type == 'widget' && this._component.isAllowed('rename'); }; + var canUpgrade = function canUpgrade() { + return !this.background && this._component.isAllowed('upgrade') && Wirecloud.LocalCatalogue.hasAlternativeVersion(this._component.meta); + }; + var canCollapseEndpoints = function canCollapseEndpoints() { return this.hasEndpoints() && !this.background && !this.orderingEndpoints; }; diff --git a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ComponentPrefs.js b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ComponentPrefs.js index d7a73ddaee..8e6e4abe18 100644 --- a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ComponentPrefs.js +++ b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ComponentPrefs.js @@ -67,6 +67,10 @@ this._createMenuItem(gettext("Rename"), "pencil", function () { showRenameModal.call(this); }.bind(this), canRename), + this._createMenuItem(utils.gettext("Upgrade/Downgrade"), "retweet", function () { + var dialog = new Wirecloud.ui.UpgradeWindowMenu(this.component._component); + dialog.show(); + }.bind(this), canUpgrade), this._createMenuItem(gettext("Logs"), "tags", function () { this.component.showLogs(); }.bind(this)), @@ -88,6 +92,10 @@ return !this.component._component.volatile && this.component.type == 'widget'; }; + var canUpgrade = function canUpgrade() { + return this.component._component.isAllowed('upgrade') && Wirecloud.LocalCatalogue.hasAlternativeVersion(this.component._component.meta); + }; + var canShowSettings = function canShowSettings() { return this.component.hasSettings() && this.component._component.isAllowed('configure'); }; diff --git a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ComponentShowcase.js b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ComponentShowcase.js index da26e4d620..01cef64491 100644 --- a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ComponentShowcase.js +++ b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ComponentShowcase.js @@ -192,7 +192,7 @@ removeComponent: function removeComponent(type, element) { - this.components[type].container.remove(element); + this.components[type].container.removeChild(element); delete this.components[type].elements[element.getId()]; return this; diff --git a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/Connection.js b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/Connection.js index 9ec88bbbb2..68922eb89e 100644 --- a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/Connection.js +++ b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/Connection.js @@ -380,6 +380,16 @@ return this; }, + refreshEndpoint: function refreshEndpoint(endpoint) { + + if (this.established) { + this[endpoint.type].endpoint = endpoint; + this[endpoint.type].handle.endpoint = endpoint; + } + + return this; + }, + /** * @override */ diff --git a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ConnectionEngine.js b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ConnectionEngine.js index 460e626e5a..d9d1079af9 100644 --- a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ConnectionEngine.js +++ b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/ConnectionEngine.js @@ -141,8 +141,6 @@ .appendTo(this.connectionsElement); appendConnection.call(this, connection); - this.trigger('establish', connection); - connection.logManager.log(gettext("The connection was loaded successfully."), Wirecloud.constants.LOGGING.INFO_MSG); return this; }, @@ -271,7 +269,7 @@ target: 'source' }; - var appendConnection = function appendConnection(connection) { + var appendConnection = function appendConnection(connection, backupConnection) { this.connections.push(connection); connection.options.appendTo(this.optionsElement); @@ -281,7 +279,7 @@ .on('customizestart', connection_oncustomizestart.bind(this)) .on('remove', connection_onremove.bind(this)); - return this.trigger('establish', connection); + return this.trigger('establish', connection, backupConnection); }; var connection_onclick = function connection_onclick(connection) { @@ -396,9 +394,7 @@ this.temporalConnection .stickEndpoint(finalEndpoint) .createAndBind(false, this.wiringEngine); - appendConnection.call(this, this.temporalConnection); - this.trigger('establish', this.temporalConnection, this._connectionBackup); - this.temporalConnection.logManager.log(gettext("The connection was established successfully."), Wirecloud.constants.LOGGING.INFO_MSG); + appendConnection.call(this, this.temporalConnection, this._connectionBackup); break; case ns.ConnectionEngine.CONNECTION_DUPLICATE: this.trigger('duplicate', this.temporalInitialEndpoint.getConnectionTo(finalEndpoint)); diff --git a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/Endpoint.js b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/Endpoint.js index af72fcf3c8..9e1bb20baf 100644 --- a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/Endpoint.js +++ b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/Endpoint.js @@ -167,10 +167,14 @@ * @returns {Endpoint} * The instance on which the member is called. */ - appendConnection: function appendConnection(connection) { + appendConnection: function appendConnection(connection, updateEndpoint) { this.connections.push(connection); + if (!!updateEndpoint) { + connection.refreshEndpoint(this); + } + return this.trigger('connectionadded', connection); }, @@ -341,6 +345,7 @@ if (index !== -1) { this.connections.splice(index, 1); + this.trigger('connectionremoved', connection); } return this; @@ -380,7 +385,7 @@ // PRIVATE MEMBERS // ================================================================================== - var events = ['click', 'connectionadded', 'mousedown', 'mouseenter', 'mouseleave', 'mouseup']; + var events = ['click', 'connectionadded', 'connectionremoved', 'mousedown', 'mouseenter', 'mouseleave', 'mouseup']; var endpoint_onmousedown = function endpoint_onmousedown(event) { diff --git a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/EndpointGroup.js b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/EndpointGroup.js index 6d6cf960e4..d73bfae6da 100644 --- a/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/EndpointGroup.js +++ b/src/wirecloud/platform/static/js/wirecloud/ui/WiringEditor/EndpointGroup.js @@ -54,9 +54,14 @@ this.EndpointClass = EndpointClass; this.originalOrder = []; - this.modified = false; - Object.defineProperties(this, { + modified: { + get: function get() { + return this.canBeOrdered() && !equalsLists(this.originalOrder, this.children.map(function (endpoint) { + return endpoint.name; + })); + } + }, orderable: { get: function get() {return this.hasClassName('orderable');}, set: function set(value) {this.toggleClassName('orderable', value);} @@ -85,13 +90,24 @@ */ appendEndpoint: function appendEndpoint(wiringEndpoint) { var endpoint = new this.EndpointClass(wiringEndpoint, this.component); + var missingEndpoint = findFirstMissingEndpoint.call(this); + var i; - endpoint.index = this.children.length; + if (!wiringEndpoint.missing) { + this.originalOrder.push(endpoint.name); + } + + if (!wiringEndpoint.missing && missingEndpoint != null) { + this.prependChild(endpoint, missingEndpoint); + } else { + this.appendChild(endpoint); + } this.endpoints[endpoint.name] = endpoint; - this.appendChild(endpoint); - this.originalOrder.push(endpoint.name); + for (i = 0; i < this.children.length; i++) { + this.children[i].index = i; + } return endpoint; }, @@ -103,7 +119,7 @@ * [TODO: description] */ canBeOrdered: function canBeOrdered() { - return this.children.length > ns.EndpointGroup.MIN_LENGTH; + return this.children.length > ns.EndpointGroup.MIN_LENGTH && !hasMissingEndpoints.call(this); }, /** @@ -125,17 +141,7 @@ * The instance on which the member is called. */ refresh: function refresh() { - var currentOrder, name; - - if (!this.canBeOrdered()) { - return this; - } - - currentOrder = this.children.map(function (endpoint) { - return endpoint.name; - }); - - this.modified = !equalsLists(this.originalOrder, currentOrder); + var name; for (name in this.endpoints) { this.endpoints[name].refresh(); @@ -171,7 +177,23 @@ this.removeChild(endpoint).appendChild(endpoint); }, this); - this.modified = true; + return this; + }, + + removeChild: function removeChild(endpoint) { + var index = this.originalOrder.indexOf(endpoint.name); + var i; + + if (index !== -1) { + this.originalOrder.splice(index, 1); + } + + this.superMember(se.Container, 'removeChild', endpoint); + delete this.endpoints[endpoint.name]; + + for (i = 0; i < this.children.length; i++) { + this.children[i].index = i; + } return this; }, @@ -258,6 +280,24 @@ return list1.join() === list2.join(); }; + var hasMissingEndpoints = function hasMissingEndpoints() { + return this.children.some(function (endpoint) { + return endpoint.missing; + }); + }; + + var findFirstMissingEndpoint = function findFirstMissingEndpoint() { + var i, endpoint = null; + + for (i = 0; i < this.children.length && endpoint == null; i++) { + if (this.children[i].missing) { + endpoint = this.children[i]; + } + } + + return endpoint; + }; + var makeEndpointDraggable = function makeEndpointDraggable(endpoint) { endpoint.draggable = new Wirecloud.ui.Draggable(endpoint.get(), {group: this}, diff --git a/src/wirecloud/platform/static/js/wirecloud/wiring/Operator.js b/src/wirecloud/platform/static/js/wirecloud/wiring/Operator.js index 4beab0f678..61343a1aef 100644 --- a/src/wirecloud/platform/static/js/wirecloud/wiring/Operator.js +++ b/src/wirecloud/platform/static/js/wirecloud/wiring/Operator.js @@ -55,7 +55,7 @@ throw new TypeError('wiringEngine must be an instance of Wirecloud.Wiring'); } - this.superClass(['load', 'remove', 'unload', 'upgraded']); + this.superClass(['load', 'remove', 'unload', 'upgraded', 'upgradeerror']); businessInfo = utils.merge(ns.Operator.JSON_TEMPLATE, businessInfo); @@ -66,7 +66,8 @@ this.permissions = Wirecloud.Utils.merge({ 'close': true, 'configure': true, - 'rename': true + 'rename': true, + 'upgrade': true }, businessInfo.permissions); upgrade = function upgrade(new_meta) { diff --git a/src/wirecloud/platform/wiring/tests.py b/src/wirecloud/platform/wiring/tests.py index 3593c70ae0..cf21299605 100644 --- a/src/wirecloud/platform/wiring/tests.py +++ b/src/wirecloud/platform/wiring/tests.py @@ -32,9 +32,7 @@ from django.test import Client from mock import Mock, patch import selenium -from selenium.webdriver import ActionChains from selenium.webdriver.common.keys import Keys -from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait from wirecloud.commons.utils.remote import FormModalTester @@ -901,7 +899,7 @@ def test_operator_logging_support(self): operator.wait_to_be_loaded() modal = operator.show_logs() - self.assertEqual(len(modal.find_alerts('error')), 0) + self.assertEqual(len(modal.find_alerts(state='error')), 0) modal.accept() # Make test operator log some errors @@ -911,7 +909,7 @@ def test_operator_logging_support(self): # Check operator registered correctly the errors raised by the operator modal = operator.show_logs() - self.assertEqual(len(modal.find_alerts('error')), 2) + self.assertEqual(len(modal.find_alerts(state='error')), 2) modal.accept() with self.find_iwidget(title="Test 1"): @@ -925,7 +923,7 @@ def test_operator_logging_support(self): def _check_connection_errors(self, connection, count): modal = connection.show_logs() - self.assertEqual(len(modal.find_alerts('error')), count) + self.assertEqual(len(modal.find_alerts(state='error')), count) modal.accept() @uses_extra_resources(('Wirecloud_api-test_0.9.wgt',), shared=True) @@ -1117,6 +1115,103 @@ def test_remove_components_using_key_delete_when_behaviour_engine_is_enabled(sel self.assertTrue(wiring.find_draggable_component('operator', id=1).has_class('background')) self.assertTrue(wiring.find_draggable_component('widget', title="Test 1").has_class('background')) + @uses_extra_resources(('Wirecloud_TestOperator_2.0.zip',), shared=True) + def test_upgrade_operator(self): + self.login(username='user_with_workspaces') + + with self.wiring_view as wiring: + with wiring.component_sidebar as sidebar: + operator = sidebar.find_component('operator', "Wirecloud/TestOperator", id=0) + operator.change_version("2.0") + self.assertEqual(operator.version, "v2.0") + modal = operator.show_logs() + WebDriverWait(self.driver, timeout=5).until(lambda driver: len(modal.find_alerts(title="The operator was upgraded to v2.0 successfully.")) == 1) + modal.accept() + draggable_operator = wiring.find_draggable_component('operator', id=operator.id) + + self.assertEqual(len(draggable_operator.find_endpoints('target')), 2) + target = draggable_operator.find_endpoint('target', "input") + self.assertFalse(target.has_class('missing')) + self.assertTrue(len(target.find_connections()), 1) + + self.assertEqual(len(draggable_operator.find_endpoints('source')), 2) + source = draggable_operator.find_endpoint('source', "output") + self.assertTrue(source.has_class('missing')) + self.assertTrue(len(source.find_connections()), 1) + + self.assertEqual(len(wiring.find_connections(extra_class="missing")), 1) + connection = wiring.find_connections(extra_class="missing")[0] + self.assertEqual(connection.source_id, source.id) + + @uses_extra_resources(('Wirecloud_Test_3.0.wgt',), shared=True) + def test_upgrade_widget(self): + self.login(username='user_with_workspaces') + + with self.wiring_view as wiring: + with wiring.component_sidebar as sidebar: + widget = sidebar.find_component('widget', "Wirecloud/Test", title="Test 1") + widget.change_version("3.0") + self.assertEqual(widget.version, "v3.0") + modal = widget.show_logs() + WebDriverWait(self.driver, timeout=5).until(lambda driver: len(modal.find_alerts(title="The widget was upgraded to v3.0 successfully.")) == 1) + modal.accept() + draggable_widget = wiring.find_draggable_component('widget', id=widget.id) + + self.assertEqual(len(draggable_widget.find_endpoints('target')), 2) + target = draggable_widget.find_endpoint('target', "inputendpoint") + self.assertTrue(target.has_class('missing')) + self.assertTrue(len(target.find_connections()), 1) + + self.assertEqual(len(wiring.find_connections(extra_class="missing")), 1) + connection = wiring.find_connections(extra_class="missing")[0] + self.assertEqual(connection.target_id, target.id) + + self.assertEqual(len(draggable_widget.find_endpoints('source')), 1) + source = draggable_widget.find_endpoint('source', "outputendpoint") + self.assertFalse(source.has_class('missing')) + self.assertTrue(len(source.find_connections()), 1) + + @uses_extra_resources(('Wirecloud_Test_3.0.wgt',), shared=True) + def test_upgrade_and_downgrade_widget(self): + self.login(username='user_with_workspaces') + + with self.wiring_view as wiring: + draggable_widget = wiring.find_draggable_component('widget', title="Test 1") + draggable_widget.change_version("3.0") + draggable_widget.change_version("1.0") + modal = draggable_widget.show_logs() + WebDriverWait(self.driver, timeout=5).until(lambda driver: len(modal.find_alerts(title="The widget was upgraded to v1.0 successfully.")) == 1) + modal.accept() + + self.assertEqual(len(draggable_widget.find_endpoints('target')), 2) + target = draggable_widget.find_endpoint('target', "inputendpoint") + self.assertFalse(target.has_class('missing')) + self.assertTrue(len(target.find_connections()), 1) + + self.assertEqual(len(wiring.find_connections(extra_class="missing")), 0) + + @uses_extra_resources(('Wirecloud_Test_3.0.wgt',), shared=True) + def test_remove_missing_endpoint_with_no_connections(self): + self.login(username='user_with_workspaces') + + with self.wiring_view as wiring: + draggable_widget = wiring.find_draggable_component('widget', title="Test 1") + draggable_widget.change_version("3.0") + + connection = wiring.find_connections(extra_class="missing")[0] + connection.remove() + + self.assertIsNone(draggable_widget.find_endpoint('target', "inputendpoint")) + + @uses_extra_resources(('Wirecloud_Test_3.0.wgt',), shared=True) + def test_missing_endpoints_cannot_be_ordered(self): + self.login(username='user_with_workspaces') + + with self.wiring_view as wiring: + draggable_widget = wiring.find_draggable_component('widget', title="Test 1") + draggable_widget.change_version("3.0") + draggable_widget.show_preferences().check(must_be_disabled=("Order endpoints",)) + @wirecloud_selenium_test_case class ComponentMissingTestCase(WirecloudSeleniumTestCase): @@ -1539,7 +1634,7 @@ def check_input_endpoint_exceptions(self): operators = sidebar.find_components('operator', 'Wirecloud/TestOperator', state='in use') operator = wiring.find_draggable_component('operator', id=operators[0].id) modal = operator.show_logs() - self.assertEqual(len(modal.find_alerts('error')), 1) + self.assertEqual(len(modal.find_alerts(state='error')), 1) modal.accept() def test_input_endpoint_exceptions(self):