summaryrefslogtreecommitdiffstats
path: root/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models
diff options
context:
space:
mode:
Diffstat (limited to 'ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models')
-rw-r--r--ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/DashboardState.js180
-rw-r--r--ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/LayoutStorage.js253
-rw-r--r--ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/LayoutStorage.spec.js692
-rw-r--r--ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetDataModel.js45
-rw-r--r--ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetDefCollection.js56
-rw-r--r--ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetModel.js112
-rw-r--r--ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetModel.spec.js156
7 files changed, 1494 insertions, 0 deletions
diff --git a/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/DashboardState.js b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/DashboardState.js
new file mode 100644
index 00000000..67948ead
--- /dev/null
+++ b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/DashboardState.js
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+angular.module('ui.dashboard')
+ .factory('DashboardState', ['$log', '$q', function ($log, $q) {
+ function DashboardState(storage, id, hash, widgetDefinitions, stringify) {
+ this.storage = storage;
+ this.id = id;
+ this.hash = hash;
+ this.widgetDefinitions = widgetDefinitions;
+ this.stringify = stringify;
+ }
+
+ DashboardState.prototype = {
+ /**
+ * Takes array of widget instance objects, serializes,
+ * and saves state.
+ *
+ * @param {Array} widgets scope.widgets from dashboard directive
+ * @return {Boolean} true on success, false on failure
+ */
+ save: function (widgets) {
+
+ if (!this.storage) {
+ return true;
+ }
+
+ var serialized = _.map(widgets, function (widget) {
+ return widget.serialize();
+ });
+
+ var item = { widgets: serialized, hash: this.hash };
+
+ if (this.stringify) {
+ item = JSON.stringify(item);
+ }
+
+ this.storage.setItem(this.id, item);
+ return true;
+ },
+
+ /**
+ * Loads dashboard state from the storage object.
+ * Can handle a synchronous response or a promise.
+ *
+ * @return {Array|Promise} Array of widget definitions or a promise
+ */
+ load: function () {
+
+ if (!this.storage) {
+ return null;
+ }
+
+ var serialized;
+
+ // try loading storage item
+ serialized = this.storage.getItem( this.id );
+
+ if (serialized) {
+ // check for promise
+ if (angular.isObject(serialized) && angular.isFunction(serialized.then)) {
+ return this._handleAsyncLoad(serialized);
+ }
+ // otherwise handle synchronous load
+ return this._handleSyncLoad(serialized);
+ } else {
+ return null;
+ }
+ },
+
+ _handleSyncLoad: function(serialized) {
+
+ var deserialized, result = [];
+
+ if (!serialized) {
+ return null;
+ }
+
+ if (this.stringify) {
+ try { // to deserialize the string
+
+ deserialized = JSON.parse(serialized);
+
+ } catch (e) {
+
+ // bad JSON, log a warning and return
+ $log.warn('Serialized dashboard state was malformed and could not be parsed: ', serialized);
+ return null;
+
+ }
+ }
+ else {
+ deserialized = serialized;
+ }
+
+ // check hash against current hash
+ if (deserialized.hash !== this.hash) {
+
+ $log.info('Serialized dashboard from storage was stale (old hash: ' + deserialized.hash + ', new hash: ' + this.hash + ')');
+ this.storage.removeItem(this.id);
+ return null;
+
+ }
+
+ // Cache widgets
+ var savedWidgetDefs = deserialized.widgets;
+
+ // instantiate widgets from stored data
+ for (var i = 0; i < savedWidgetDefs.length; i++) {
+
+ // deserialized object
+ var savedWidgetDef = savedWidgetDefs[i];
+
+ // widget definition to use
+ var widgetDefinition = this.widgetDefinitions.getByName(savedWidgetDef.name);
+
+ // check for no widget
+ if (!widgetDefinition) {
+ // no widget definition found, remove and return false
+ $log.warn('Widget with name "' + savedWidgetDef.name + '" was not found in given widget definition objects');
+ continue;
+ }
+
+ // check widget-specific storageHash
+ if (widgetDefinition.hasOwnProperty('storageHash') && widgetDefinition.storageHash !== savedWidgetDef.storageHash) {
+ // widget definition was found, but storageHash was stale, removing storage
+ $log.info('Widget Definition Object with name "' + savedWidgetDef.name + '" was found ' +
+ 'but the storageHash property on the widget definition is different from that on the ' +
+ 'serialized widget loaded from storage. hash from storage: "' + savedWidgetDef.storageHash + '"' +
+ ', hash from WDO: "' + widgetDefinition.storageHash + '"');
+ continue;
+ }
+
+ // push instantiated widget to result array
+ result.push(savedWidgetDef);
+ }
+
+ return result;
+ },
+
+ _handleAsyncLoad: function(promise) {
+ var self = this;
+ var deferred = $q.defer();
+ promise.then(
+ // success
+ function(res) {
+ var result = self._handleSyncLoad(res);
+ if (result) {
+ deferred.resolve(result);
+ } else {
+ deferred.reject(result);
+ }
+ },
+ // failure
+ function(res) {
+ deferred.reject(res);
+ }
+ );
+
+ return deferred.promise;
+ }
+
+ };
+ return DashboardState;
+ }]); \ No newline at end of file
diff --git a/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/LayoutStorage.js b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/LayoutStorage.js
new file mode 100644
index 00000000..3685fd3f
--- /dev/null
+++ b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/LayoutStorage.js
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+angular.module('ui.dashboard')
+ .factory('LayoutStorage', function() {
+
+ var noopStorage = {
+ setItem: function() {
+
+ },
+ getItem: function() {
+
+ },
+ removeItem: function() {
+
+ }
+ };
+
+
+
+ function LayoutStorage(options) {
+
+ var defaults = {
+ storage: noopStorage,
+ storageHash: '',
+ stringifyStorage: true
+ };
+
+ angular.extend(defaults, options);
+ angular.extend(options, defaults);
+
+ this.id = options.storageId;
+ this.storage = options.storage;
+ this.storageHash = options.storageHash;
+ this.stringifyStorage = options.stringifyStorage;
+ this.widgetDefinitions = options.widgetDefinitions;
+ this.defaultLayouts = options.defaultLayouts;
+ this.lockDefaultLayouts = options.lockDefaultLayouts;
+ this.widgetButtons = options.widgetButtons;
+ this.explicitSave = options.explicitSave;
+ this.defaultWidgets = options.defaultWidgets;
+ this.settingsModalOptions = options.settingsModalOptions;
+ this.onSettingsClose = options.onSettingsClose;
+ this.onSettingsDismiss = options.onSettingsDismiss;
+ this.options = options;
+ this.options.unsavedChangeCount = 0;
+
+ this.layouts = [];
+ this.states = {};
+ this.load();
+ this._ensureActiveLayout();
+ }
+
+ LayoutStorage.prototype = {
+
+ add: function(layouts) {
+ if (!angular.isArray(layouts)) {
+ layouts = [layouts];
+ }
+ var self = this;
+ angular.forEach(layouts, function(layout) {
+ layout.dashboard = layout.dashboard || {};
+ layout.dashboard.storage = self;
+ layout.dashboard.storageId = layout.id = self._getLayoutId.call(self,layout);
+ layout.dashboard.widgetDefinitions = layout.widgetDefinitions || self.widgetDefinitions;
+ layout.dashboard.stringifyStorage = false;
+ layout.dashboard.defaultWidgets = layout.defaultWidgets || self.defaultWidgets;
+ layout.dashboard.widgetButtons = self.widgetButtons;
+ layout.dashboard.explicitSave = self.explicitSave;
+ layout.dashboard.settingsModalOptions = self.settingsModalOptions;
+ layout.dashboard.onSettingsClose = self.onSettingsClose;
+ layout.dashboard.onSettingsDismiss = self.onSettingsDismiss;
+ self.layouts.push(layout);
+ });
+ },
+
+ remove: function(layout) {
+ var index = this.layouts.indexOf(layout);
+ if (index >= 0) {
+ this.layouts.splice(index, 1);
+ delete this.states[layout.id];
+
+ // check for active
+ if (layout.active && this.layouts.length) {
+ var nextActive = index > 0 ? index - 1 : 0;
+ this.layouts[nextActive].active = true;
+ }
+ }
+ },
+
+ save: function() {
+
+ var state = {
+ layouts: this._serializeLayouts(),
+ states: this.states,
+ storageHash: this.storageHash
+ };
+
+ if (this.stringifyStorage) {
+ state = JSON.stringify(state);
+ }
+
+ this.storage.setItem(this.id, state);
+ this.options.unsavedChangeCount = 0;
+ },
+
+ load: function() {
+
+ var serialized = this.storage.getItem(this.id);
+
+ this.clear();
+
+ if (serialized) {
+ // check for promise
+ if (angular.isObject(serialized) && angular.isFunction(serialized.then)) {
+ this._handleAsyncLoad(serialized);
+ } else {
+ this._handleSyncLoad(serialized);
+ }
+ } else {
+ this._addDefaultLayouts();
+ }
+ },
+
+ clear: function() {
+ this.layouts = [];
+ this.states = {};
+ },
+
+ setItem: function(id, value) {
+ this.states[id] = value;
+ this.save();
+ },
+
+ getItem: function(id) {
+ return this.states[id];
+ },
+
+ removeItem: function(id) {
+ delete this.states[id];
+ this.save();
+ },
+
+ getActiveLayout: function() {
+ var len = this.layouts.length;
+ for (var i = 0; i < len; i++) {
+ var layout = this.layouts[i];
+ if (layout.active) {
+ return layout;
+ }
+ }
+ return false;
+ },
+
+ _addDefaultLayouts: function() {
+ var self = this;
+ var defaults = this.lockDefaultLayouts ? { locked: true } : {};
+ angular.forEach(this.defaultLayouts, function(layout) {
+ self.add(angular.extend(_.clone(defaults), layout));
+ });
+ },
+
+ _serializeLayouts: function() {
+ var result = [];
+ angular.forEach(this.layouts, function(l) {
+ result.push({
+ title: l.title,
+ id: l.id,
+ active: l.active,
+ locked: l.locked,
+ defaultWidgets: l.dashboard.defaultWidgets
+ });
+ });
+ return result;
+ },
+
+ _handleSyncLoad: function(serialized) {
+
+ var deserialized;
+
+ if (this.stringifyStorage) {
+ try {
+
+ deserialized = JSON.parse(serialized);
+
+ } catch (e) {
+ this._addDefaultLayouts();
+ return;
+ }
+ } else {
+
+ deserialized = serialized;
+
+ }
+
+ if (this.storageHash !== deserialized.storageHash) {
+ this._addDefaultLayouts();
+ return;
+ }
+ this.states = deserialized.states;
+ this.add(deserialized.layouts);
+ },
+
+ _handleAsyncLoad: function(promise) {
+ var self = this;
+ promise.then(
+ angular.bind(self, this._handleSyncLoad),
+ angular.bind(self, this._addDefaultLayouts)
+ );
+ },
+
+ _ensureActiveLayout: function() {
+ for (var i = 0; i < this.layouts.length; i++) {
+ var layout = this.layouts[i];
+ if (layout.active) {
+ return;
+ }
+ }
+ if (this.layouts[0]) {
+ this.layouts[0].active = true;
+ }
+ },
+
+ _getLayoutId: function(layout) {
+ if (layout.id) {
+ return layout.id;
+ }
+ var max = 0;
+ for (var i = 0; i < this.layouts.length; i++) {
+ var id = this.layouts[i].id;
+ max = Math.max(max, id * 1);
+ }
+ return max + 1;
+ }
+
+ };
+ return LayoutStorage;
+ }); \ No newline at end of file
diff --git a/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/LayoutStorage.spec.js b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/LayoutStorage.spec.js
new file mode 100644
index 00000000..3310cad9
--- /dev/null
+++ b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/LayoutStorage.spec.js
@@ -0,0 +1,692 @@
+'use strict';
+
+describe('Factory: LayoutStorage', function () {
+
+ // mock UI Sortable
+ beforeEach(function () {
+ angular.module('ui.sortable', []);
+ });
+
+ // load the service's module
+ beforeEach(module('ui.dashboard'));
+
+ // instantiate service
+ var LayoutStorage;
+ beforeEach(inject(function (_LayoutStorage_) {
+ LayoutStorage = _LayoutStorage_;
+ }));
+
+ describe('the constructor', function() {
+
+ var storage, options;
+
+ beforeEach(function() {
+ options = {
+ storageId: 'testingStorage',
+ storage: {
+ setItem: function(key, value) {
+
+ },
+ getItem: function(key) {
+
+ },
+ removeItem: function(key) {
+
+ }
+ },
+ storageHash: 'ds5f9d1f',
+ stringifyStorage: true,
+ widgetDefinitions: [
+
+ ],
+ defaultLayouts: [
+ {title: 'something'},
+ {title: 'something'},
+ {title: 'something'}
+ ],
+ widgetButtons: false,
+ explicitSave: false,
+ settingsModalOptions: {},
+ onSettingsClose: function() {
+
+ },
+ onSettingsDismiss: function() {
+
+ }
+ };
+ storage = new LayoutStorage(options);
+ });
+
+ it('should provide an empty implementation of storage if it is not provided', function() {
+ delete options.storage;
+ var stateless = new LayoutStorage(options);
+ var noop = stateless.storage;
+ angular.forEach(['setItem', 'getItem', 'removeItem'], function(method) {
+ expect(typeof noop[method]).toEqual('function');
+ expect(noop[method]).not.toThrow();
+ noop[method]();
+ });
+ });
+
+ it('should set a subset of the options directly on the LayoutStorage instance itself', function() {
+ var properties = {
+ id: 'storageId',
+ storage: 'storage',
+ storageHash: 'storageHash',
+ stringifyStorage: 'stringifyStorage',
+ widgetDefinitions: 'widgetDefinitions',
+ defaultLayouts: 'defaultLayouts',
+ widgetButtons: 'widgetButtons',
+ explicitSave: 'explicitSave',
+ settingsModalOptions: 'settingsModalOptions',
+ onSettingsClose: 'onSettingsClose',
+ onSettingsDismiss: 'onSettingsDismiss'
+ };
+
+ angular.forEach(properties, function(val, key) {
+ expect( storage[key] ).toEqual( options[val] );
+ });
+
+ });
+
+ it('should set stringify as true by default', function() {
+ delete options.stringifyStorage;
+ storage = new LayoutStorage(options);
+ expect(storage.stringifyStorage).toEqual(true);
+ });
+
+ it('should allow stringify to be overridden by option', function() {
+ options.stringifyStorage = false;
+ storage = new LayoutStorage(options);
+ expect(storage.stringifyStorage).toEqual(false);
+ });
+
+ it('should create a layouts array and states object', function() {
+ expect(storage.layouts instanceof Array).toEqual(true);
+ expect(typeof storage.states).toEqual('object');
+ });
+
+ it('should call load', function() {
+ spyOn(LayoutStorage.prototype, 'load');
+ storage = new LayoutStorage(options);
+ expect(LayoutStorage.prototype.load).toHaveBeenCalled();
+ });
+
+ });
+
+ describe('the load method', function() {
+
+ var options, storage;
+
+ beforeEach(function() {
+ options = {
+ storageId: 'testingStorage',
+ storage: {
+ setItem: function(key, value) {
+
+ },
+ getItem: function(key) {
+
+ },
+ removeItem: function(key) {
+
+ }
+ },
+ storageHash: 'ds5f9d1f',
+ stringifyStorage: true,
+ widgetDefinitions: [
+
+ ],
+ defaultLayouts: [
+ {title: 'something'},
+ {title: 'something'},
+ {title: 'something'}
+ ],
+ widgetButtons: false,
+ explicitSave: false
+ }
+ storage = new LayoutStorage(options);
+ });
+
+ it('should use the default layouts if no stored info was found', function() {
+ expect(storage.layouts.length).toEqual(options.defaultLayouts.length);
+ });
+
+ it('should clone default layouts rather than use them directly', function() {
+ expect(storage.layouts.indexOf(options.defaultLayouts[0])).toEqual(-1);
+ });
+
+ it('should use the result from getItem for layouts.', function() {
+ spyOn(options.storage, 'getItem').and.returnValue(JSON.stringify({
+ storageHash: 'ds5f9d1f',
+ layouts: [
+ { id: 0, title: 'title', defaultWidgets: [], active: true },
+ { id: 1, title: 'title2', defaultWidgets: [], active: false },
+ { id: 2, title: 'title3', defaultWidgets: [], active: false },
+ { id: 3, title: 'custom', defaultWidgets: [], active: false }
+ ],
+ states: {
+ 0: {},
+ 1: {},
+ 2: {}
+ }
+ }));
+ storage.load();
+ expect(storage.layouts.map(function(l) {return l.title})).toEqual(['title', 'title2', 'title3', 'custom']);
+ });
+
+ it('should NOT use result from getItem for layouts if the storageHash doesnt match', function() {
+ spyOn(options.storage, 'getItem').and.returnValue(JSON.stringify({
+ storageHash: 'alskdjf02iej',
+ layouts: [
+ { id: 0, title: 'title', defaultWidgets: [], active: true },
+ { id: 1, title: 'title2', defaultWidgets: [], active: false },
+ { id: 2, title: 'title3', defaultWidgets: [], active: false },
+ { id: 3, title: 'custom', defaultWidgets: [], active: false }
+ ],
+ states: {
+ 0: {},
+ 1: {},
+ 2: {}
+ }
+ }));
+ storage.load();
+ expect(storage.layouts.map(function(l) {return l.title})).toEqual(['something', 'something', 'something']);
+ });
+
+ it('should be able to handle async loading via promise', inject(function($rootScope,$q) {
+ var deferred = $q.defer();
+ spyOn(options.storage, 'getItem').and.returnValue(deferred.promise);
+ storage.load();
+ expect(storage.layouts).toEqual([]);
+ deferred.resolve(JSON.stringify({
+ storageHash: 'ds5f9d1f',
+ layouts: [
+ { id: 0, title: 'title', defaultWidgets: [], active: true },
+ { id: 1, title: 'title2', defaultWidgets: [], active: false },
+ { id: 2, title: 'title3', defaultWidgets: [], active: false },
+ { id: 3, title: 'custom', defaultWidgets: [], active: false }
+ ],
+ states: {
+ 0: {},
+ 1: {},
+ 2: {}
+ }
+ }));
+ $rootScope.$apply();
+ expect(storage.layouts.map(function(l) {return l.title})).toEqual(['title', 'title2', 'title3', 'custom']);
+ }));
+
+ it('should load defaults if the deferred is rejected', inject(function($rootScope,$q) {
+ var deferred = $q.defer();
+ spyOn(options.storage, 'getItem').and.returnValue(deferred.promise);
+ storage.load();
+ deferred.reject();
+ $rootScope.$apply();
+ expect(storage.layouts.map(function(l) {return l.title})).toEqual(['something', 'something', 'something']);
+ }));
+
+ it('should load defaults if the json is malformed', inject(function($rootScope,$q) {
+ var deferred = $q.defer();
+ spyOn(options.storage, 'getItem').and.returnValue(deferred.promise);
+ storage.load();
+ expect(storage.layouts).toEqual([]);
+ deferred.resolve(JSON.stringify({
+ storageHash: 'ds5f9d1f',
+ layouts: [
+ { id: 0, title: 'title', defaultWidgets: [], active: true },
+ { id: 1, title: 'title2', defaultWidgets: [], active: false },
+ { id: 2, title: 'title3', defaultWidgets: [], active: false },
+ { id: 3, title: 'custom', defaultWidgets: [], active: false }
+ ],
+ states: {
+ 0: {},
+ 1: {},
+ 2: {}
+ }
+ }).replace('{','{{'));
+ $rootScope.$apply();
+ expect(storage.layouts.map(function(l) {return l.title})).toEqual(['something', 'something', 'something']);
+ }));
+
+ it('should not try to JSON.parse the result if stringifyStorage is false.', function() {
+ options.stringifyStorage = false;
+ storage = new LayoutStorage(options);
+ spyOn(options.storage, 'getItem').and.returnValue({
+ storageHash: 'ds5f9d1f',
+ layouts: [
+ { id: 0, title: 'title', defaultWidgets: [], active: true },
+ { id: 1, title: 'title2', defaultWidgets: [], active: false },
+ { id: 2, title: 'title3', defaultWidgets: [], active: false },
+ { id: 3, title: 'custom', defaultWidgets: [], active: false }
+ ],
+ states: {
+ 0: {},
+ 1: {},
+ 2: {}
+ }
+ });
+ storage.load();
+ expect(storage.layouts.map(function(l) {return l.title})).toEqual(['title', 'title2', 'title3', 'custom']);
+ });
+
+ });
+
+ describe('the add method', function() {
+
+ var storage, options;
+
+ beforeEach(function() {
+ options = {
+ storageId: 'testingStorage',
+ storage: {
+ setItem: function(key, value) {
+
+ },
+ getItem: function(key) {
+
+ },
+ removeItem: function(key) {
+
+ }
+ },
+ storageHash: 'ds5f9d1f',
+ stringifyStorage: true,
+ widgetDefinitions: [
+
+ ],
+ defaultLayouts: [],
+ widgetButtons: false,
+ explicitSave: false
+ }
+
+ spyOn(LayoutStorage.prototype, 'load' );
+
+ storage = new LayoutStorage(options);
+
+ });
+
+ it('should add to storage.layouts', function() {
+ var newLayout = { title: 'my-layout' };
+ storage.add(newLayout);
+ expect(storage.layouts[0]).toEqual(newLayout);
+ });
+
+ it('should be able to take an array of new layouts', function() {
+ var newLayouts = [ { title: 'my-layout' }, { title: 'my-layout-2' } ];
+ storage.add(newLayouts);
+ expect(storage.layouts.length).toEqual(2);
+ expect(storage.layouts.indexOf(newLayouts[0])).not.toEqual(-1);
+ expect(storage.layouts.indexOf(newLayouts[1])).not.toEqual(-1);
+ });
+
+ it('should look for defaultWidgets on storage options if not supplied on layout definition', function() {
+ options.defaultWidgets = [{name: 'a'}, {name: 'b'}, {name: 'c'}];
+ storage = new LayoutStorage(options);
+
+ var newLayouts = [ { title: 'my-layout', defaultWidgets: [] }, { title: 'my-layout-2' } ];
+ storage.add(newLayouts);
+ expect(newLayouts[0].dashboard.defaultWidgets === newLayouts[0].defaultWidgets).toEqual(true);
+ expect(newLayouts[1].dashboard.defaultWidgets === options.defaultWidgets).toEqual(true);
+ });
+
+ it('should use defaultWidgets if supplied in the layout definition', function() {
+ options.defaultWidgets = [{name: 'a'}, {name: 'b'}, {name: 'c'}];
+ storage = new LayoutStorage(options);
+
+ var newLayouts = [ { title: 'my-layout', defaultWidgets: [] }, { title: 'my-layout-2' } ];
+ storage.add(newLayouts);
+ expect(newLayouts[0].dashboard.defaultWidgets).toEqual([]);
+ expect(newLayouts[1].dashboard.defaultWidgets).toEqual(options.defaultWidgets);
+ });
+
+ it('should look for widgetDefinitions on storage options if not supplied on layout definition', function() {
+ options.widgetDefinitions = [{name: 'a'}, {name: 'b'}, {name: 'c'}];
+ storage = new LayoutStorage(options);
+
+ var newLayouts = [ { title: 'my-layout', widgetDefinitions: [] }, { title: 'my-layout-2' } ];
+ storage.add(newLayouts);
+ expect(newLayouts[0].dashboard.widgetDefinitions === newLayouts[0].widgetDefinitions).toEqual(true);
+ expect(newLayouts[1].dashboard.widgetDefinitions === options.widgetDefinitions).toEqual(true);
+ });
+
+ it('should use widgetDefinitions if supplied in the layout definition', function() {
+ options.widgetDefinitions = [{name: 'a'}, {name: 'b'}, {name: 'c'}];
+ storage = new LayoutStorage(options);
+
+ var newLayouts = [ { title: 'my-layout', widgetDefinitions: [] }, { title: 'my-layout-2' } ];
+ storage.add(newLayouts);
+ expect(newLayouts[0].dashboard.widgetDefinitions).toEqual([]);
+ expect(newLayouts[1].dashboard.widgetDefinitions).toEqual(options.widgetDefinitions);
+ });
+
+ });
+
+ describe('the remove method', function() {
+
+ var storage, options;
+
+ beforeEach(function() {
+ options = {
+ storageId: 'testingStorage',
+ storageHash: 'ds5f9d1f',
+ stringifyStorage: true,
+ widgetDefinitions: [
+ { name: 'A' },
+ { name: 'B' },
+ { name: 'C' }
+ ],
+ defaultLayouts: [
+ { title: '1' },
+ { title: '2', active: true },
+ { title: '3' }
+ ],
+ widgetButtons: false,
+ explicitSave: false
+ }
+
+ storage = new LayoutStorage(options);
+ });
+
+ it('should remove the supplied layout', function() {
+ var layout = storage.layouts[1];
+ storage.remove(layout);
+ expect(storage.layouts.indexOf(layout)).toEqual(-1);
+ });
+
+ it('should delete the state', function() {
+ var layout = storage.layouts[1];
+ storage.setItem(layout.id, {});
+ storage.remove(layout);
+ expect(storage.states[layout.id]).toBeUndefined();
+ });
+
+ it('should do nothing if layout is not in layouts', function() {
+ var layout = {};
+ var before = storage.layouts.length;
+ storage.remove(layout);
+ var after = storage.layouts.length;
+ expect(before).toEqual(after);
+ });
+
+ it('should set another dashboard to active if the layout removed was active', function() {
+ var layout = storage.layouts[1];
+ storage.remove(layout);
+ expect(storage.layouts[0].active || storage.layouts[1].active).toEqual(true);
+ });
+
+ it('should set the layout at index 0 to active if the removed layout was 0', function() {
+ storage.layouts[1].active = false;
+ storage.layouts[0].active = true;
+ storage.remove(storage.layouts[0]);
+ expect(storage.layouts[0].active).toEqual(true);
+ });
+
+ it('should not change the active layout if it was not the one that got removed', function() {
+ var active = storage.layouts[1];
+ var layout = storage.layouts[0];
+ storage.remove(layout);
+ expect(active.active).toEqual(true);
+ });
+
+ });
+
+ describe('the save method', function() {
+
+ var options, storage;
+
+ beforeEach(function() {
+ options = {
+ storageId: 'testingStorage',
+ storage: {
+ setItem: function(key, value) {
+
+ },
+ getItem: function(key) {
+
+ },
+ removeItem: function(key) {
+
+ }
+ },
+ storageHash: 'ds5f9d1f',
+ stringifyStorage: true,
+ widgetDefinitions: [
+
+ ],
+ defaultLayouts: [
+ {title: 'something'},
+ {title: 'something'},
+ {title: 'something'}
+ ],
+ widgetButtons: false,
+ explicitSave: false
+ }
+ storage = new LayoutStorage(options);
+ });
+
+ it('should call options.storage.setItem with a stringified object', function() {
+ spyOn(options.storage, 'setItem' );
+ storage.save();
+ expect(options.storage.setItem).toHaveBeenCalled();
+ expect(options.storage.setItem.calls.argsFor(0)[0]).toEqual(storage.id);
+ expect(typeof options.storage.setItem.calls.argsFor(0)[1]).toEqual('string');
+ expect(function(){
+ JSON.parse(options.storage.setItem.calls.argsFor(0)[1]);
+ }).not.toThrow();
+ });
+
+ it('should save an object that has layouts, states, and storageHash', function() {
+ spyOn(options.storage, 'setItem' );
+ storage.save();
+ var obj = JSON.parse(options.storage.setItem.calls.argsFor(0)[1]);
+ expect(obj.hasOwnProperty('layouts')).toEqual(true);
+ expect(obj.layouts instanceof Array).toEqual(true);
+ expect(obj.hasOwnProperty('states')).toEqual(true);
+ expect(typeof obj.states).toEqual('object');
+ expect(obj.hasOwnProperty('storageHash')).toEqual(true);
+ expect(typeof obj.storageHash).toEqual('string');
+ });
+
+ it('should call options.storage.setItem with an object when stringifyStorage is false', function() {
+ options.stringifyStorage = false;
+ storage = new LayoutStorage(options);
+ spyOn(options.storage, 'setItem' );
+ storage.save();
+ expect(options.storage.setItem).toHaveBeenCalled();
+ expect(options.storage.setItem.calls.argsFor(0)[0]).toEqual(storage.id);
+ expect(typeof options.storage.setItem.calls.argsFor(0)[1]).toEqual('object');
+ });
+
+ });
+
+ describe('the setItem method', function() {
+
+ var options, storage;
+
+ beforeEach(function() {
+ options = {
+ storageId: 'testingStorage',
+ storage: {
+ setItem: function(key, value) {
+
+ },
+ getItem: function(key) {
+
+ },
+ removeItem: function(key) {
+
+ }
+ },
+ storageHash: 'ds5f9d1f',
+ stringifyStorage: true,
+ widgetDefinitions: [
+
+ ],
+ defaultLayouts: [
+ {title: 'something'},
+ {title: 'something'},
+ {title: 'something'}
+ ],
+ widgetButtons: false,
+ explicitSave: false
+ }
+ storage = new LayoutStorage(options);
+ });
+
+ it('should set storage.states[id] to the second argument', function() {
+ var state = { some: 'thing'};
+ storage.setItem('id', state);
+ expect(storage.states.id).toEqual(state);
+ });
+
+ it('should call save', function() {
+ spyOn(storage, 'save');
+ var state = { some: 'thing'};
+ storage.setItem('id', state);
+ expect(storage.save).toHaveBeenCalled();
+ });
+
+ });
+
+ describe('the getItem method', function() {
+
+ var options, storage;
+
+ beforeEach(function() {
+ options = {
+ storageId: 'testingStorage',
+ storage: {
+ setItem: function(key, value) {
+
+ },
+ getItem: function(key) {
+
+ },
+ removeItem: function(key) {
+
+ }
+ },
+ storageHash: 'ds5f9d1f',
+ stringifyStorage: true,
+ widgetDefinitions: [
+
+ ],
+ defaultLayouts: [
+ {title: 'something'},
+ {title: 'something'},
+ {title: 'something'}
+ ],
+ widgetButtons: false,
+ explicitSave: false
+ }
+ storage = new LayoutStorage(options);
+ });
+
+ it('should return states[id]', function() {
+ storage.states['myId'] = {};
+ var result = storage.getItem('myId');
+ expect(result === storage.states['myId']).toEqual(true);
+ });
+
+ });
+
+ describe('the getActiveLayout method', function() {
+ var options, storage;
+
+ beforeEach(function() {
+ options = {
+ storageId: 'testingStorage',
+ storage: {
+ setItem: function(key, value) {
+
+ },
+ getItem: function(key) {
+
+ },
+ removeItem: function(key) {
+
+ }
+ },
+ storageHash: 'ds5f9d1f',
+ stringifyStorage: true,
+ widgetDefinitions: [
+
+ ],
+ defaultLayouts: [
+ {title: 'i am active', active: true},
+ {title: 'i am not'},
+ {title: 'me neither'}
+ ],
+ widgetButtons: false,
+ explicitSave: false
+ }
+ storage = new LayoutStorage(options);
+ });
+
+ it('should return the layout with active:true', function() {
+ var layout = storage.getActiveLayout();
+ expect(layout.title).toEqual('i am active');
+ });
+
+ it('should return false if no layout is active', function() {
+ var layout = storage.getActiveLayout();
+ layout.active = false;
+ var result = storage.getActiveLayout();
+ expect(result).toEqual(false);
+ });
+
+ });
+
+ describe('the removeItem', function() {
+
+ var options, storage;
+
+ beforeEach(function() {
+ options = {
+ storageId: 'testingStorage',
+ storage: {
+ setItem: function(key, value) {
+
+ },
+ getItem: function(key) {
+
+ },
+ removeItem: function(key) {
+
+ }
+ },
+ storageHash: 'ds5f9d1f',
+ stringifyStorage: true,
+ widgetDefinitions: [
+
+ ],
+ defaultLayouts: [
+ {title: 'i am active', active: true},
+ {title: 'i am not'},
+ {title: 'me neither'}
+ ],
+ widgetButtons: false,
+ explicitSave: false
+ }
+ storage = new LayoutStorage(options);
+ });
+
+ it('should remove states[id]', function() {
+ var state = {};
+ storage.setItem('1', state);
+ storage.removeItem('1');
+ expect(storage.states['1']).toBeUndefined();
+ });
+
+ it('should call save', function() {
+ spyOn(storage, 'save');
+ var state = {};
+ storage.setItem('1', state);
+ storage.removeItem('1');
+ expect(storage.save).toHaveBeenCalled();
+ });
+
+ });
+
+}); \ No newline at end of file
diff --git a/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetDataModel.js b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetDataModel.js
new file mode 100644
index 00000000..547f2e96
--- /dev/null
+++ b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetDataModel.js
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+angular.module('ui.dashboard')
+ .factory('WidgetDataModel', function () {
+ function WidgetDataModel() {
+ }
+
+ WidgetDataModel.prototype = {
+ setup: function (widget, scope) {
+ this.dataAttrName = widget.dataAttrName;
+ this.dataModelOptions = widget.dataModelOptions;
+ this.widgetScope = scope;
+ },
+
+ updateScope: function (data) {
+ this.widgetScope.widgetData = data;
+ },
+
+ init: function () {
+ // to be overridden by subclasses
+ },
+
+ destroy: function () {
+ // to be overridden by subclasses
+ }
+ };
+
+ return WidgetDataModel;
+ }); \ No newline at end of file
diff --git a/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetDefCollection.js b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetDefCollection.js
new file mode 100644
index 00000000..27765440
--- /dev/null
+++ b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetDefCollection.js
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+angular.module('ui.dashboard')
+ .factory('WidgetDefCollection', function () {
+
+ function convertToDefinition(d) {
+ if (typeof d === 'function') {
+ return new d();
+ }
+ return d;
+ }
+
+ function WidgetDefCollection(widgetDefs) {
+
+ widgetDefs = widgetDefs.map(convertToDefinition);
+
+ this.push.apply(this, widgetDefs);
+
+ // build (name -> widget definition) map for widget lookup by name
+ var map = {};
+ _.each(widgetDefs, function (widgetDef) {
+ map[widgetDef.name] = widgetDef;
+ });
+ this.map = map;
+ }
+
+ WidgetDefCollection.prototype = Object.create(Array.prototype);
+
+ WidgetDefCollection.prototype.getByName = function (name) {
+ return this.map[name];
+ };
+
+ WidgetDefCollection.prototype.add = function(def) {
+ def = convertToDefinition(def);
+ this.push(def);
+ this.map[def.name] = def;
+ };
+
+ return WidgetDefCollection;
+ });
diff --git a/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetModel.js b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetModel.js
new file mode 100644
index 00000000..c378d3b6
--- /dev/null
+++ b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetModel.js
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+angular.module('ui.dashboard')
+ .factory('WidgetModel', function ($log) {
+
+ function defaults() {
+ return {
+ title: 'Widget',
+ style: {},
+ size: {},
+ enableVerticalResize: true,
+ containerStyle: { width: '33%' }, // default width
+ contentStyle: {}
+ };
+ };
+
+ // constructor for widget model instances
+ function WidgetModel(widgetDefinition, overrides) {
+
+ // Extend this with the widget definition object with overrides merged in (deep extended).
+ angular.extend(this, defaults(), _.merge(angular.copy(widgetDefinition), overrides));
+
+ this.updateContainerStyle(this.style);
+
+ if (!this.templateUrl && !this.template && !this.directive) {
+ this.directive = widgetDefinition.name;
+ }
+
+ if (this.size && _.has(this.size, 'height')) {
+ this.setHeight(this.size.height);
+ }
+
+ if (this.style && _.has(this.style, 'width')) { //TODO deprecate style attribute
+ this.setWidth(this.style.width);
+ }
+
+ if (this.size && _.has(this.size, 'width')) {
+ this.setWidth(this.size.width);
+ }
+ }
+
+ WidgetModel.prototype = {
+ // sets the width (and widthUnits)
+ setWidth: function (width, units) {
+ width = width.toString();
+ units = units || width.replace(/^[-\.\d]+/, '') || '%';
+
+ this.widthUnits = units;
+ width = parseFloat(width);
+
+ // check with min width if set, unit refer to width's unit
+ if (this.size && _.has(this.size, 'minWidth')) {
+ width = _.max([parseFloat(this.size.minWidth), width]);
+ }
+
+ if (width < 0 || isNaN(width)) {
+ $log.warn('malhar-angular-dashboard: setWidth was called when width was ' + width);
+ return false;
+ }
+
+ if (units === '%') {
+ width = Math.min(100, width);
+ width = Math.max(0, width);
+ }
+
+ this.containerStyle.width = width + '' + units;
+
+ this.updateSize(this.containerStyle);
+
+ return true;
+ },
+
+ setHeight: function (height) {
+ this.contentStyle.height = height;
+ this.updateSize(this.contentStyle);
+ },
+
+ setStyle: function (style) {
+ this.style = style;
+ this.updateContainerStyle(style);
+ },
+
+ updateSize: function (size) {
+ angular.extend(this.size, size);
+ },
+
+ updateContainerStyle: function (style) {
+ angular.extend(this.containerStyle, style);
+ },
+ serialize: function() {
+ return _.pick(this, ['title', 'name', 'report_id', 'hideGrid', 'showChart' ,'rcloud_url','reportData','style', 'size', 'dataModelOptions', 'attrs', 'storageHash']);
+ }
+ };
+
+ return WidgetModel;
+ }); \ No newline at end of file
diff --git a/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetModel.spec.js b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetModel.spec.js
new file mode 100644
index 00000000..151e560a
--- /dev/null
+++ b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetModel.spec.js
@@ -0,0 +1,156 @@
+'use strict';
+
+describe('Factory: WidgetModel', function () {
+
+ // load the service's module
+ beforeEach(module('ui.dashboard'));
+
+ // instantiate service
+ var WidgetModel;
+ beforeEach(inject(function (_WidgetModel_) {
+ WidgetModel = _WidgetModel_;
+ }));
+
+ it('should be a function', function() {
+ expect(typeof WidgetModel).toEqual('function');
+ });
+
+ describe('the constructor', function() {
+ var m, Class, Class2, overrides;
+
+ beforeEach(function() {
+ Class = {
+ name: 'TestWidget',
+ attrs: {},
+ dataAttrName: 'attr-name',
+ dataModelType: function TestType() {},
+ dataModelOptions: {},
+ style: { width: '10em' },
+ settingsModalOptions: {},
+ onSettingsClose: function() {},
+ onSettingsDismiss: function() {},
+ funkyChicken: {
+ cool: false,
+ fun: true
+ }
+ };
+
+ Class2 = {
+ name: 'TestWidget2',
+ attrs: {},
+ dataAttrName: 'attr-name',
+ dataModelType: function TestType() {},
+ dataModelOptions: {},
+ style: { width: '10em' },
+ templateUrl: 'my/url.html',
+ template: '<div>some template</div>'
+ };
+
+ overrides = {
+ style: {
+ width: '15em'
+ }
+ };
+ spyOn(WidgetModel.prototype, 'setWidth');
+ m = new WidgetModel(Class, overrides);
+ });
+
+ it('should copy class defaults, so that changes on an instance do not change the Class', function() {
+ m.style.width = '20em';
+ expect(Class.style.width).toEqual('10em');
+ });
+
+ it('should call setWidth', function() {
+ expect(WidgetModel.prototype.setWidth).toHaveBeenCalled();
+ });
+
+ it('should take overrides as precedent over Class defaults', function() {
+ expect(m.style.width).toEqual('15em');
+ });
+
+ it('should copy arbitrary data from the widget definition', function() {
+ expect(m.funkyChicken.cool).toEqual(false);
+ expect(m.funkyChicken.fun).toEqual(true);
+ expect(m.funkyChicken===Class.funkyChicken).toEqual(false);
+ });
+
+ it('should set templateUrl if and only if it is present on Class', function() {
+ var m2 = new WidgetModel(Class2, overrides);
+ expect(m2.templateUrl).toEqual('my/url.html');
+ });
+
+ it('should set template if and only if it is present on Class', function() {
+ delete Class2.templateUrl;
+ var m2 = new WidgetModel(Class2, overrides);
+ expect(m2.template).toEqual('<div>some template</div>');
+ });
+
+ it('should look for directive if neither templateUrl nor template is found on Class', function() {
+ delete Class2.templateUrl;
+ delete Class2.template;
+ Class2.directive = 'ng-bind';
+ var m2 = new WidgetModel(Class2, overrides);
+ expect(m2.directive).toEqual('ng-bind');
+ });
+
+ it('should set the name as directive if templateUrl, template, and directive are not defined', function() {
+ delete Class2.templateUrl;
+ delete Class2.template;
+ var m2 = new WidgetModel(Class2, overrides);
+ expect(m2.directive).toEqual('TestWidget2');
+ });
+
+ it('should not require overrides', function() {
+ var fn = function() {
+ var m2 = new WidgetModel(Class);
+ }
+ expect(fn).not.toThrow();
+ });
+
+ it('should copy references to settingsModalOptions, onSettingsClose, onSettingsDismiss', function() {
+ var m = new WidgetModel(Class);
+ expect(m.settingsModalOptions).toEqual(Class.settingsModalOptions);
+ expect(m.onSettingsClose).toEqual(Class.onSettingsClose);
+ expect(m.onSettingsDismiss).toEqual(Class.onSettingsDismiss);
+ });
+
+ });
+
+ describe('setWidth method', function() {
+
+ var context, setWidth;
+
+ beforeEach(function() {
+ context = new WidgetModel({});
+ setWidth = WidgetModel.prototype.setWidth;
+ });
+
+ it('should take one argument as a string with units', function() {
+ setWidth.call(context, '100px');
+ expect(context.containerStyle.width).toEqual('100px');
+ });
+
+ it('should take two args as a number and string as units', function() {
+ setWidth.call(context, 100, 'px');
+ expect(context.containerStyle.width).toEqual('100px');
+ });
+
+ it('should return false and not set anything if width is less than 0', function() {
+ var result = setWidth.call(context, -100, 'em');
+ expect(result).toEqual(false);
+ expect(context.containerStyle.width).not.toEqual('-100em');
+ });
+
+ it('should assume % if no unit is given', function() {
+ setWidth.call(context, 50);
+ expect(context.containerStyle.width).toEqual('50%');
+ });
+
+ it('should force greater than 0% and less than or equal 100%', function() {
+ setWidth.call(context, '110%');
+ expect(context.containerStyle.width).toEqual('100%');
+ });
+
+ });
+
+}); \ No newline at end of file