diff --git a/src/GruntFile.js b/src/GruntFile.js index 1c911043c2..72a5735a7f 100644 --- a/src/GruntFile.js +++ b/src/GruntFile.js @@ -130,6 +130,7 @@ var WirecloudFiles = [ 'wirecloud/platform/static/js/wirecloud/ui/WiringEditor/Endpoint.js', 'wirecloud/platform/static/js/wirecloud/ui/WiringEditor/KeywordSuggestion.js', 'wirecloud/platform/static/js/wirecloud/ui/WorkspaceView.js', + 'wirecloud/platform/static/js/wirecloud/ui/WorkspaceTabView.js', 'wirecloud/platform/static/js/wirecloud/ui/SharingWindowMenu.js', 'wirecloud/platform/static/js/wirecloud/wiring/Endpoint.js', 'wirecloud/platform/static/js/wirecloud/wiring/EndpointTypeError.js', diff --git a/src/js_tests/wirecloud/bootstrap.js b/src/js_tests/wirecloud/bootstrap.js index 6fe26ef3c7..6a018bc6a5 100644 --- a/src/js_tests/wirecloud/bootstrap.js +++ b/src/js_tests/wirecloud/bootstrap.js @@ -17,6 +17,7 @@ const Wirecloud = { 'wirecloud/component_sidebar': ' ', 'wirecloud/wiring/behaviour_sidebar': '
', 'wirecloud/wiring/footer': ' ', + 'wirecloud/workspace/empty_tab_message': '
', 'wirecloud/workspace/sharing_user': '
', 'wirecloud/workspace/visibility_option': '
' } diff --git a/src/js_tests/wirecloud/ui/WorkspaceTabViewSpec.js b/src/js_tests/wirecloud/ui/WorkspaceTabViewSpec.js new file mode 100644 index 0000000000..2256c98f58 --- /dev/null +++ b/src/js_tests/wirecloud/ui/WorkspaceTabViewSpec.js @@ -0,0 +1,505 @@ +/* + * Copyright (c) 2018 Future Internet Consulting and Development Solutions S.L. + * + * This file is part of Wirecloud Platform. + * + * Wirecloud Platform is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Wirecloud is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Wirecloud Platform. If not, see + * . + * + */ + +/* globals StyledElements, Wirecloud */ + + +(function (ns, utils) { + + "use strict"; + + const callEventListener = function callEventListener(instance, event) { + var largs = Array.prototype.slice.call(arguments, 2); + largs.unshift(instance); + instance.addEventListener.calls.allArgs().some(function (args) { + if (args[0] === event) { + args[1].apply(instance, largs); + return true; + } + }); + }; + + const create_tab = function create_tab(options) { + return utils.merge({ + addEventListener: jasmine.createSpy("addEventListener"), + createWidget: jasmine.createSpy("createWidget"), + id: "8", + preferences: { + get: jasmine.createSpy("get"), + addEventListener: jasmine.createSpy("addEventListener") + }, + title: "Tab Title", + widgets: [] + }, options); + }; + + const create_workspace = function create_workspace(options) { + let workspace = utils.merge({ + id: "1", + owner: "user", + name: "empty", + title: "Dashboard Title", + tabs: [ + { + id: "8", + widgets: [], + name: "tab", + title: "Tab", + initial: true + } + ], + operators: [], + preferences: {}, + createTab: jasmine.createSpy("createTab"), + isAllowed: jasmine.createSpy("isAllowed").and.returnValue(false) + }, options); + + workspace.addEventListener = jasmine.createSpy("addEventListener"); + workspace.removeEventListener = jasmine.createSpy("removeEventListener"); + + return { + buildAddWidgetButton: jasmine.createSpy("buildAddWidgetButton").and.returnValue(), + model: workspace + }; + }; + + describe("WorkspaceTabView", () => { + + const notebook = new StyledElements.Notebook(); + + beforeAll(() => { + // TODO + ns.WidgetView = jasmine.createSpy("WidgetView").and.callFake(function (tab, options) { + this.id = options.id; + this.load = jasmine.createSpy("load"); + }); + ns.WorkspaceTabViewDragboard = jasmine.createSpy("WorkspaceTabViewDragboard").and.callFake(function () { + this.freeLayout = { + adaptHeight: jasmine.createSpy("adaptHeight").and.returnValue({inLU: 0}), + adaptWidth: jasmine.createSpy("adaptWidth").and.returnValue({inLU: 0}), + adaptColumnOffset: jasmine.createSpy("adaptColumnOffset").and.returnValue({inLU: 0}), + adaptRowOffset: jasmine.createSpy("adaptRowOffset").and.returnValue({inLU: 0}) + }; + this.baseLayout = { + adaptHeight: jasmine.createSpy("adaptHeight").and.returnValue({inLU: 0}), + adaptWidth: jasmine.createSpy("adaptWidth").and.returnValue({inLU: 0}), + adaptColumnOffset: jasmine.createSpy("adaptColumnOffset").and.returnValue({inLU: 0}), + adaptRowOffset: jasmine.createSpy("adaptRowOffset").and.returnValue({inLU: 0}) + }; + this.paint = jasmine.createSpy("paint"); + this._notifyWindowResizeEvent = jasmine.createSpy("_notifyWindowResizeEvent"); + this._updateBaseLayout = jasmine.createSpy("_updateBaseLayout"); + }); + ns.WorkspaceTabViewMenuItems = jasmine.createSpy("WorkspaceTabViewMenuItems"); + utils.inherit(ns.WorkspaceTabViewMenuItems, StyledElements.DynamicMenuItems); + Wirecloud.TutorialCatalogue = { + buildTutorialReferences: jasmine.createSpy("buildTutorialReferences") + }; + }); + + afterAll(() => { + // TODO + ns.WidgetView = null; + ns.WorkspaceTabViewDragboard = null; + Wirecloud.TutorialCatalogue = null; + }); + + describe("new WorkspaceTabView(id, notebook, options)", () => { + + /* + it("should require the id parameter", () => { + expect(() => { + new ns.WorkspaceTabView(); + }).toThrowError(TypeError); + }); + */ + + it("should load empty tabs", () => { + let workspace = create_workspace(); + let model = create_tab(); + let tab = new ns.WorkspaceTabView("1", notebook, { + model: model, + workspace: workspace + }); + + expect(tab.title).toEqual("Tab Title"); + expect(tab.widgets).toEqual([]); + expect(tab.widgetsById).toEqual({}); + }); + + it("should load tabs with widgets", () => { + let workspace = create_workspace(); + let model = create_tab({ + widgets: [{id: "9"}, {id: "5"}] + }); + let tab = new ns.WorkspaceTabView("1", notebook, { + model: model, + workspace: workspace + }); + + expect(tab.title).toEqual("Tab Title"); + expect(tab.widgets).toEqual([ + jasmine.any(ns.WidgetView), + jasmine.any(ns.WidgetView) + ]); + expect(tab.widgetsById).toEqual({ + "5": jasmine.any(ns.WidgetView), + "9": jasmine.any(ns.WidgetView) + }); + }); + + it("should load editable tabs", () => { + let workspace = create_workspace(); + workspace.model.isAllowed.and.returnValue(true); + let model = create_tab({ + widgets: [{id: "9"}, {id: "5"}] + }); + ns.WorkspaceTabViewMenuItems.calls.reset(); + let tab = new ns.WorkspaceTabView("1", notebook, { + model: model, + workspace: workspace + }); + + expect(ns.WorkspaceTabViewMenuItems).toHaveBeenCalledWith(tab); + }); + + }); + + describe("createWidget(resource[, options])", () => { + + it("should commit changes by default", (done) => { + let workspace = create_workspace(); + let model = create_tab(); + let tab = new ns.WorkspaceTabView("1", notebook, { + model: model, + workspace: workspace + }); + let widgetmodel = {id: 80}; + model.createWidget.and.returnValue(Promise.resolve(widgetmodel)); + let widget = {id: 80}; + spyOn(tab, "findWidget").and.callFake((id) => { + expect(id).toBe(80); + return widget; + }); + // TODO this make sense? + tab.dragboard.freeLayout.adaptHeight.and.returnValue({inLU: "invalid"}); + tab.dragboard.freeLayout.adaptWidth.and.returnValue({inLU: 40}); + tab.dragboard.freeLayout.columns = 20; + // END TODO + + let p = tab.createWidget( + { + default_height: "120px", + default_width: "33%" + }, + { + layout: 1, + top: 2, + left: 3, + height: "invalid", + width: 40 + } + ); + + p.then((created_widget) => { + expect(created_widget).toBe(widget); + done(); + }); + }); + + it("should allow commit false", () => { + let workspace = create_workspace(); + let model = create_tab(); + let tab = new ns.WorkspaceTabView("1", notebook, { + model: model, + workspace: workspace + }); + let widgetmodel = {id: 80}; + model.createWidget.and.returnValue(widgetmodel); + let widget = {id: 80}; + spyOn(tab, "findWidget").and.callFake((id) => { + expect(id).toBe(80); + return widget; + }); + + let created_widget = tab.createWidget( + { + default_height: "120px", + default_width: "33%" + }, { + commit: false + } + ); + + expect(created_widget).toBe(widget); + }); + + it("should honour initiallayout preference", (done) => { + let workspace = create_workspace(); + let model = create_tab(); + model.preferences.get.and.returnValue("Free"); + let tab = new ns.WorkspaceTabView("1", notebook, { + model: model, + workspace: workspace + }); + let widgetmodel = {id: 80}; + model.createWidget.and.returnValue(Promise.resolve(widgetmodel)); + let widget = {id: 80}; + spyOn(tab, "findWidget").and.callFake((id) => { + expect(id).toBe(80); + return widget; + }); + tab.dragboard.freeLayout._searchFreeSpace = jasmine.createSpy("_searchFreeSpace").and.returnValue({x: 1, y: 2}); + + let p = tab.createWidget({ + default_height: "120px", + default_width: "33%" + }); + + p.then((created_widget) => { + expect(created_widget).toBe(widget); + done(); + }); + }); + + }); + + describe("highlight()", () => { + + it("should add the highlight CSS class", () => { + let workspace = create_workspace(); + let model = create_tab(); + let tab = new ns.WorkspaceTabView("1", notebook, { + model: model, + workspace: workspace + }); + + expect(tab.highlight()).toBe(tab); + }); + + }); + + describe("show()", () => { + + it("should load all the widgets from the tab", () => { + let workspace = create_workspace(); + let model = create_tab({ + widgets: [{}, {}] + }); + let tab = new ns.WorkspaceTabView("1", notebook, { + model: model, + workspace: workspace + }); + + expect(tab.show()).toBe(tab); + expect(tab.widgets[0].load).toHaveBeenCalled(); + expect(tab.widgets[1].load).toHaveBeenCalled(); + }); + + }); + + describe("showSettings()", () => { + + it("should display the settings dialog", () => { + var show; + // TODO + ns.PreferencesWindowMenu = jasmine.createSpy("PreferencesWindowMenu").and.callFake(function () { + this.show = show = jasmine.createSpy("show"); + }); + let workspace = create_workspace(); + let model = create_tab({ + widgets: [{}, {}] + }); + let tab = new ns.WorkspaceTabView("1", notebook, { + model: model, + workspace: workspace + }); + + expect(tab.showSettings()).toBe(tab); + + expect(ns.PreferencesWindowMenu).toHaveBeenCalledWith('tab', model.preferences); + expect(show).toHaveBeenCalledWith(); + }); + + }); + + describe("tab events", () => { + + var workspace, model, tab; + + beforeEach(() => { + workspace = create_workspace(); + model = create_tab({}); + tab = new ns.WorkspaceTabView("1", notebook, { + model: model, + workspace: workspace + }); + }); + + it("should ignore preferences changes not related to the baselayout", () => { + callEventListener(model.preferences, "pre-commit", {"other": 5}); + + expect(tab.dragboard._updateBaseLayout).not.toHaveBeenCalled(); + }); + + it("should handle changes to the baselayout preference", () => { + callEventListener(model.preferences, "pre-commit", {"baselayout": 5}); + + expect(tab.dragboard._updateBaseLayout).toHaveBeenCalledWith(); + }); + + it("should handle title changes", () => { + spyOn(StyledElements.Tab.prototype, "rename"); + let newtitle = "NewTitle"; + model.title = newtitle; + + callEventListener(model, "change", ['title']); + + expect(StyledElements.Tab.prototype.rename).toHaveBeenCalledWith(newtitle); + }); + + it("should handle name changes (visible tab)", () => { + spyOn(StyledElements.Tab.prototype, "rename"); + spyOn(Wirecloud.HistoryManager, "getCurrentState").and.returnValue({ + workspace_owner: "owner", + workspace_name: "dashboard", + tab: "oldname" + }); + spyOn(Wirecloud.HistoryManager, "replaceState"); + let newname = "new-name"; + model.name = newname; + tab.hidden = false; + + callEventListener(model, "change", ['name']); + + expect(StyledElements.Tab.prototype.rename).not.toHaveBeenCalled(); + expect(Wirecloud.HistoryManager.replaceState).toHaveBeenCalledWith({ + workspace_owner: "owner", + workspace_name: "dashboard", + tab: newname + }); + }); + + it("should handle name changes (hidden tab)", () => { + spyOn(StyledElements.Tab.prototype, "rename"); + spyOn(Wirecloud.HistoryManager, "getCurrentState"); + spyOn(Wirecloud.HistoryManager, "replaceState"); + let newname = "new-name"; + model.name = newname; + tab.hidden = true; + + callEventListener(model, "change", ['name']); + + expect(StyledElements.Tab.prototype.rename).not.toHaveBeenCalled(); + expect(Wirecloud.HistoryManager.replaceState).not.toHaveBeenCalled(); + }); + + it("should handle remove events", () => { + spyOn(StyledElements.Tab.prototype, "close"); + + callEventListener(model, "remove"); + + expect(StyledElements.Tab.prototype.close).toHaveBeenCalledWith(); + }); + + it("should handle createwidget events (visible tab)", () => { + let workspace = create_workspace(); + let model = create_tab(); + let tab = new ns.WorkspaceTabView("1", notebook, { + model: model, + workspace: workspace + }); + let widgetmodel = {id: "20"}; + tab.hidden = false; + + callEventListener(model, "createwidget", widgetmodel); + + expect(tab.widgets).toEqual([ + jasmine.any(ns.WidgetView) + ]); + expect(tab.widgetsById).toEqual({ + "20": jasmine.any(ns.WidgetView) + }); + expect(tab.widgets[0].load).toHaveBeenCalledWith(); + }); + + it("should handle createwidget events (hidden tab)", () => { + let workspace = create_workspace(); + let model = create_tab(); + let tab = new ns.WorkspaceTabView("1", notebook, { + model: model, + workspace: workspace + }); + let widgetmodel = {id: "20"}; + tab.hidden = true; + + callEventListener(model, "createwidget", widgetmodel); + + expect(tab.widgets).toEqual([ + jasmine.any(ns.WidgetView) + ]); + expect(tab.widgetsById).toEqual({ + "20": jasmine.any(ns.WidgetView) + }); + expect(tab.widgets[0].load).not.toHaveBeenCalled(); + }); + + it("should handle removewidget events", () => { + let workspace = create_workspace(); + let model = create_tab({ + widgets: [{id: "9"}, {id: "5"}] + }); + let tab = new ns.WorkspaceTabView("1", notebook, { + model: model, + workspace: workspace + }); + let widget = tab.findWidget("5"); + + callEventListener(model, "removewidget", widget); + + expect(tab.widgets).toEqual([ + jasmine.any(ns.WidgetView) + ]); + expect(tab.widgetsById).toEqual({ + "9": jasmine.any(ns.WidgetView) + }); + }); + + }); + + describe("unhighlight()", () => { + + it("should remove the highlight CSS class", () => { + let workspace = create_workspace(); + let model = create_tab(); + let tab = new ns.WorkspaceTabView("1", notebook, { + model: model, + workspace: workspace + }); + + expect(tab.unhighlight()).toBe(tab); + }); + + }); + + }); + +})(Wirecloud.ui, StyledElements.Utils); diff --git a/src/wirecloud/platform/static/js/wirecloud/ui/WorkspaceTabView.js b/src/wirecloud/platform/static/js/wirecloud/ui/WorkspaceTabView.js index a1dad64534..ee2b971021 100644 --- a/src/wirecloud/platform/static/js/wirecloud/ui/WorkspaceTabView.js +++ b/src/wirecloud/platform/static/js/wirecloud/ui/WorkspaceTabView.js @@ -122,7 +122,7 @@ this.wrapperElement.classList.add("wc-workspace-tab-content"); this.wrapperElement.setAttribute('data-id', this.id); - if (!this.workspace.model.restricted) { + if (this.workspace.model.isAllowed("edit")) { var button = new se.PopupButton({ title: utils.gettext("Preferences"), class: 'icon-tab-menu', @@ -198,14 +198,11 @@ return this.findWidget(this.model.createWidget(resource, options).id); } - return new Promise(function (resolve, reject) { - - this.model.createWidget(resource, options).then(function (model) { - resolve(this.findWidget(model.id)); - }.bind(this), function (reason) { - reject(reason); - }); - }.bind(this)); + return this.model.createWidget(resource, options).then( + (model) => { + return Promise.resolve(this.findWidget(model.id)); + } + ); }, /** @@ -213,6 +210,7 @@ */ highlight: function () { this.tabElement.classList.add("highlight"); + return this; }, /** @@ -224,54 +222,16 @@ return this.widgetsById[id]; }, - /** - * @returns {Promise} - */ - remove: function remove() { - - if (privates.get(this).widgets.length) { - var dialog = new Wirecloud.ui.AlertWindowMenu(utils.gettext("The tab's widgets will also be removed. Would you like to continue?")); - dialog.setHandler(() => { - _remove.call(this); - }).show(); - } else { - _remove.call(this); - } - - return this; - }, - - /** - * Renames this tab - * - * @param {String} name - * - * @returns {Promise} - */ - rename: function rename(name) { - return this.model.rename(name).catch((reason) => { - this.logManager.log(reason); - return Promise.reject(reason); - }); - }, - repaint: function repaint() { this.dragboard.paint(); this.dragboard._notifyWindowResizeEvent(); return this; }, - /** - * @returns {Promise} - */ - setInitial: function setInitial() { - return this.model.setInitial(); - }, - show: function show() { se.Tab.prototype.show.call(this); - this.widgets.forEach(function (widget) { + privates.get(this).widgets.forEach(function (widget) { widget.load(); }); @@ -285,6 +245,7 @@ unhighlight: function unhighlight() { this.tabElement.classList.remove("highlight"); + return this; } }); @@ -305,10 +266,6 @@ return widget; }; - var _remove = function _remove() { - this.model.remove(); - }; - var clean_number = function clean_number(value, min, max) { if (typeof value !== 'number' || value < min) { diff --git a/src/wirecloud/platform/static/js/wirecloud/ui/WorkspaceTabViewMenuItems.js b/src/wirecloud/platform/static/js/wirecloud/ui/WorkspaceTabViewMenuItems.js index 2cb43f72ec..f4544f1491 100644 --- a/src/wirecloud/platform/static/js/wirecloud/ui/WorkspaceTabViewMenuItems.js +++ b/src/wirecloud/platform/static/js/wirecloud/ui/WorkspaceTabViewMenuItems.js @@ -54,15 +54,15 @@ items = []; - item = new se.MenuItem(utils.gettext("Rename"), function () { - (new Wirecloud.ui.RenameWindowMenu(this, utils.gettext('Rename Workspace Tab'))).show(); - }.bind(this.tab)); + item = new se.MenuItem(utils.gettext("Rename"), () => { + (new Wirecloud.ui.RenameWindowMenu(this.tab.model, utils.gettext('Rename Workspace Tab'))).show(); + }); item.addIconClass("fa fa-pencil"); items.push(item); item = new se.MenuItem(utils.gettext("Set as initial"), function () { - this.setInitial(); - }.bind(this.tab)); + this.tab.model.setInitial(); + }); item.addIconClass("fa fa-home"); item.setDisabled(this.tab.model.initial); items.push(item); @@ -73,9 +73,16 @@ item.addIconClass("fa fa-cog"); items.push(item); - item = new se.MenuItem(utils.gettext("Remove"), function () { - this.remove(); - }.bind(this.tab)); + item = new se.MenuItem(utils.gettext("Remove"), () => { + if (this.tab.widgets.length) { + var dialog = new Wirecloud.ui.AlertWindowMenu(utils.gettext("The tab's widgets will also be removed. Would you like to continue?")); + dialog.setHandler(() => { + this.tab.model.remove(); + }).show(); + } else { + this.tab.model.remove(); + } + }); item.addIconClass("fa fa-trash"); item.setDisabled(!this.tab.model.isAllowed('remove')); items.push(item);