2 * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 describe('Directive: dashboard', function () {
21 var scope, element, childScope, DashboardState, mockModal, modalOptions, $compile, $q, mockLog;
24 beforeEach(function () {
25 angular.module('ui.sortable', []);
28 // load the directive's module
29 beforeEach(module('ui.dashboard', function($provide) {
31 open: function(options) {
32 modalOptions = options;
40 $provide.value('$uibModal', mockModal);
41 $provide.value('$log', mockLog);
44 beforeEach(inject(function (_$compile_, $rootScope, _DashboardState_, _$q_) {
46 scope = $rootScope.$new();
47 $compile = _$compile_;
48 DashboardState = _DashboardState_;
52 var widgetDefinitions = [
55 template: '<div class="wt-one-value">{{2 + 2}}</div>'
59 template: '<span class="wt-two-value">{{value}}</span>'
62 var defaultWidgets = _.clone(widgetDefinitions);
63 scope.dashboardOptions = {
65 widgetDefinitions: widgetDefinitions,
66 defaultWidgets: defaultWidgets,
68 testProperty: 'foobar'
74 element = $compile('<div dashboard="dashboardOptions"></div>')(scope);
76 childScope = element.scope();
79 it('should have toolbar', function () {
80 var toolbar = element.find('.btn-toolbar');
81 expect(toolbar.length).toEqual(1);
84 it('should have UI.Sortable directive', function () {
85 var widgetArea = element.find('.dashboard-widget-area');
86 expect(widgetArea.attr('ui-sortable')).toBeDefined();
89 it('should render widgets', function () {
90 var widgets = element.find('.widget');
91 expect(widgets.length).toEqual(2);
94 it('should evaluate widget expressions', function () {
95 var divWidget = element.find('.wt-one-value');
96 expect(divWidget.html()).toEqual('4');
99 it('should evaluate scope expressions', function () {
100 var spanWidget = element.find('.wt-two-value');
101 expect(spanWidget.html()).toEqual('10');
104 it('should fill options with defaults', function() {
105 expect(scope.dashboardOptions.stringifyStorage).toEqual(true);
108 it('should not overwrite specified options with defaults', inject(function($compile) {
109 scope.dashboardOptions.stringifyStorage = false;
110 element = $compile('<div dashboard="dashboardOptions"></div>')(scope);
111 $compile(element)(scope);
113 expect(scope.dashboardOptions.stringifyStorage).toEqual(false);
116 it('should be able to use a different dashboard template', inject(function($compile, $templateCache) {
118 'myCustomTemplate.html',
120 '<div ui-sortable="sortableOptions" ng-model="widgets">' +
121 '<div ng-repeat="widget in widgets" ng-style="widget.style" class="widget-container custom-widget" widget>' +
122 '<h3 class="widget-header">' +
124 '<span ng-click="removeWidget(widget);" class="glyphicon glyphicon-remove icon-erase" ng-if="!options.hideWidgetClose"></span>' +
125 '<span ng-click="openWidgetSettings(widget);" class="glyphicon glyphicon-cog icon-settings" ng-if="!options.hideWidgetSettings"></span>' +
127 '<div class="widget-content"></div>' +
128 '<div class="widget-ew-resizer" ng-mousedown="grabResizer($event)"></div>' +
133 var customElement = $compile('<div dashboard="dashboardOptions" template-url="myCustomTemplate.html"></div>')(scope);
135 expect(customElement.find('.custom-widget').length).toEqual(2);
138 it('should set scope.widgets to an empty array if no defaultWidgets are specified', inject(function($compile) {
139 delete scope.dashboardOptions.defaultWidgets;
140 var element2 = $compile('<div dashboard="dashboardOptions"></div>')(scope);
142 var childScope2 = element2.scope();
143 expect(childScope2.widgets instanceof Array).toEqual(true);
146 it('should set options.unsavedChangeCount to 0 upon load', function() {
147 expect(scope.dashboardOptions.unsavedChangeCount).toEqual(0);
150 it('should not call saveDashboard on load', inject(function($compile) {
151 spyOn(DashboardState.prototype, 'save');
152 var s = scope.$new();
153 element = $compile('<div dashboard="dashboardOptions"></div>')(s);
155 expect(DashboardState.prototype.save).not.toHaveBeenCalled();
158 describe('the sortableOptions', function() {
160 it('should exist', function() {
161 expect(typeof childScope.sortableOptions).toEqual('object');
164 it('should be possible to be extendable from the dashboardOptions', function() {
165 expect(childScope.sortableOptions.testProperty).toEqual('foobar');
168 it('should have a stop function that calls $scope.saveDashboard', function() {
169 expect(typeof childScope.sortableOptions.stop).toEqual('function');
170 spyOn(childScope, 'saveDashboard');
171 childScope.sortableOptions.stop();
172 expect(childScope.saveDashboard).toHaveBeenCalled();
176 describe('the addWidget function', function() {
178 var widgetCreated, widgetPassed, widgetDefault;
180 beforeEach(function() {
181 childScope.widgets.push = function(w) {
186 it('should be a function', function() {
187 expect(typeof childScope.addWidget).toEqual('function');
190 it('should throw if no default widgetDefinition was found', function() {
191 spyOn(childScope.widgetDefs, 'getByName').and.returnValue(false);
193 childScope.addWidget({ name: 'notReal' });
195 expect(fn).toThrow();
198 it('should look to the passed widgetToInstantiate object for the title before anything else', function() {
199 spyOn(childScope.widgetDefs, 'getByName').and.returnValue({ title: 'defaultTitle', name: 'A' });
200 childScope.addWidget({ title: 'highestPrecedence', name: 'A' });
201 expect(widgetCreated.title).toEqual('highestPrecedence');
204 it('should use the defaultWidget\'s title second', function() {
205 spyOn(childScope.widgetDefs, 'getByName').and.returnValue({ title: 'defaultTitle', name: 'A' });
206 childScope.addWidget({ name: 'A' });
207 expect(widgetCreated.title).toEqual('defaultTitle');
210 it('should call the saveDashboard method (internal)', function() {
211 spyOn(childScope.widgetDefs, 'getByName').and.returnValue({ title: 'defaultTitle', name: 'A' });
212 spyOn(childScope, 'saveDashboard');
213 childScope.addWidget({ name: 'A' });
214 expect(childScope.saveDashboard).toHaveBeenCalled();
217 it('should support passing just the widget name as a string', function() {
218 spyOn(childScope.widgetDefs, 'getByName').and.returnValue({ title: 'defaultTitle', name: 'A' });
219 childScope.addWidget('A');
220 expect(childScope.widgetDefs.getByName).toHaveBeenCalledWith('A');
221 expect(widgetCreated.title).toEqual('defaultTitle');
224 describe('@awashbrook Test Case', function() {
225 beforeEach(function() {
226 spyOn(childScope.widgetDefs, 'getByName').and.returnValue(widgetDefault = {
227 "name": "nvLineChartAlpha",
228 "directive": "nvd3-line-chart",
229 "dataAttrName": "data",
235 "xAxisTickFormat": "xAxisTickFormat()",
237 "useInteractiveGuideline": true,
240 "noData": "No data for YOU!",
241 "color": "colorFunction()",
244 "dataModelOptions": {
254 childScope.addWidget(widgetPassed = {
256 "name": "nvLineChartAlpha",
260 "dataModelOptions": {
264 "randomWalk(\"random Andy 1\")",
265 "randomWalk(\"random walk 2\")",
266 "randomWalk(\"random walk 3\")"
274 "xAxisTickFormat": "xAxisTickFormat()",
275 "interactive": false,
276 "useInteractiveGuideline": true,
279 "noData": "No data for YOU!",
280 "color": "colorFunction()",
287 it('should keep overrides from widgetPassed', function() {
288 expect(widgetCreated.attrs.interactive).toEqual(widgetPassed.attrs.interactive);
291 it('should fill in default attrs', function() {
292 expect(widgetCreated.attrs.isArea).toEqual(widgetDefault.attrs.isArea);
295 it('should override deep options in dataModelOptions', function() {
296 expect(widgetCreated.dataModelOptions.params.from).toEqual(widgetPassed.dataModelOptions.params.from);
299 it('should fill in deep default attrs', function() {
300 expect(widgetCreated.dataModelOptions.params.until).toEqual(widgetDefault.dataModelOptions.params.until);
304 describe('the doNotSave parameter', function() {
306 it('should prevent save from being called if set to true', function() {
307 spyOn(childScope.widgetDefs, 'getByName').and.returnValue({ title: 'defaultTitle', name: 'A' });
308 spyOn(childScope, 'saveDashboard');
309 childScope.addWidget({ name: 'A' }, true);
310 expect(childScope.saveDashboard).not.toHaveBeenCalled();
317 describe('the removeWidget function', function() {
319 it('should be a function', function() {
320 expect(typeof childScope.removeWidget).toEqual('function');
323 it('should remove the provided widget from childScope.widgets array', function() {
324 var startingLength = childScope.widgets.length;
325 var expectedLength = startingLength - 1;
327 var widgetToRemove = childScope.widgets[0];
328 childScope.removeWidget(widgetToRemove);
330 expect(childScope.widgets.length).toEqual(expectedLength);
331 expect(childScope.widgets.indexOf(widgetToRemove)).toEqual(-1);
334 it('should call saveDashboard', function() {
335 spyOn(childScope, 'saveDashboard');
336 var widgetToRemove = childScope.widgets[0];
337 childScope.removeWidget(widgetToRemove);
338 expect(childScope.saveDashboard).toHaveBeenCalled();
343 describe('the saveDashboard function', function() {
345 it('should be attached to the options object after initialization', function() {
346 expect(typeof scope.dashboardOptions.saveDashboard).toEqual('function');
347 expect(scope.dashboardOptions.saveDashboard === childScope.externalSaveDashboard).toEqual(true);
350 it('should call scope.dashboardState.save when called internally if explicitSave is falsey', function() {
351 spyOn(childScope.dashboardState, 'save').and.returnValue(true);
352 childScope.saveDashboard();
353 expect(childScope.dashboardState.save).toHaveBeenCalled();
356 it('should not call scope.dashboardState.save when called internally if explicitSave is truthy', function() {
357 scope.dashboardOptions.explicitSave = true;
358 spyOn(childScope.dashboardState, 'save').and.returnValue(true);
359 childScope.saveDashboard();
360 expect(childScope.dashboardState.save).not.toHaveBeenCalled();
363 it('should call scope.dashboardState.save when called externally, no matter what explicitSave value is', function() {
364 spyOn(childScope.dashboardState, 'save').and.returnValue(true);
366 scope.dashboardOptions.explicitSave = false;
367 scope.dashboardOptions.saveDashboard();
368 expect(childScope.dashboardState.save.calls.count()).toEqual(1);
370 scope.dashboardOptions.explicitSave = true;
371 scope.dashboardOptions.saveDashboard();
372 expect(childScope.dashboardState.save.calls.count()).toEqual(2);
375 it('should keep a count of unsaved changes as unsavedChangeCount', function() {
376 scope.dashboardOptions.explicitSave = true;
377 spyOn(childScope.dashboardState, 'save').and.returnValue(true);
378 childScope.saveDashboard();
379 expect(scope.dashboardOptions.unsavedChangeCount).toEqual(1);
380 childScope.saveDashboard();
381 childScope.saveDashboard();
382 expect(scope.dashboardOptions.unsavedChangeCount).toEqual(3);
385 it('should reset the cound of unsaved changes if a successful force save occurs', function() {
386 scope.dashboardOptions.explicitSave = true;
387 spyOn(childScope.dashboardState, 'save').and.returnValue(true);
389 childScope.saveDashboard();
390 childScope.saveDashboard();
391 childScope.saveDashboard();
393 childScope.saveDashboard(true);
395 expect(scope.dashboardOptions.unsavedChangeCount).toEqual(0);
400 describe('the loadWidgets function', function() {
402 it('should be a function', function() {
403 expect(typeof childScope.loadWidgets).toEqual('function');
406 it('should set savedWidgetDefs on scope as passed array', function() {
408 childScope.loadWidgets(widgets);
409 expect(childScope.savedWidgetDefs === widgets).toEqual(true);
412 it('should call clear on the scope with true as the only argument', function() {
413 spyOn(childScope, 'clear');
414 childScope.loadWidgets([]);
415 expect(childScope.clear).toHaveBeenCalled();
416 expect(childScope.clear.calls.argsFor(0)).toEqual([true]);
419 it('should call addWidget for each widget in the array', function() {
420 spyOn(childScope, 'addWidget').and.returnValue(null);
421 var widgets = [{},{},{}];
422 childScope.loadWidgets(widgets);
423 expect(childScope.addWidget.calls.count()).toEqual(3);
426 it('should call addWidget for each widget with true as the second parameter (doNotSave)', function() {
427 spyOn(childScope, 'addWidget').and.returnValue(null);
428 var widgets = [{},{},{}];
429 childScope.loadWidgets(widgets);
430 expect(childScope.addWidget.calls.argsFor(0)).toEqual( [ widgets[0], true] );
431 expect(childScope.addWidget.calls.argsFor(1)).toEqual( [ widgets[1], true] );
432 expect(childScope.addWidget.calls.argsFor(2)).toEqual( [ widgets[2], true] );
437 describe('the clear function', function() {
439 it('should set the scope to an empty array', function() {
441 expect(childScope.widgets).toEqual([]);
444 it('should not call saveDashboard if first arg is true', function() {
445 spyOn(childScope, 'saveDashboard');
446 childScope.clear(true);
447 expect(childScope.saveDashboard).not.toHaveBeenCalled();
450 it('should call saveDashboard if first arg is not true', function() {
451 spyOn(childScope, 'saveDashboard');
453 expect(childScope.saveDashboard).toHaveBeenCalled();
458 describe('the openWidgetSettings function', function() {
460 it('should be a function', function() {
461 expect(typeof childScope.openWidgetSettings).toEqual('function');
464 it('should call $uibModal.open with default options', function() {
466 spyOn(mockModal, 'open').and.returnValue({
467 result: { then: function(fn) {} }
469 childScope.openWidgetSettings(widget);
470 expect(mockModal.open).toHaveBeenCalled();
473 it('should have widget in the resolve object', function() {
475 var dfr = $q.defer();
476 spyOn(mockModal, 'open').and.callFake(function(options) {
477 modalOptions = options;
482 childScope.openWidgetSettings(widget);
483 expect(modalOptions.resolve.widget() === widget).toEqual(true);
486 it('should set the templateUrl in modal options to the default ("app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/widget-settings-template.html")', function() {
488 var dfr = $q.defer();
489 spyOn(mockModal, 'open').and.callFake(function(options) {
490 modalOptions = options;
495 childScope.openWidgetSettings(widget);
496 expect(modalOptions.templateUrl).toEqual('app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/widget-settings-template.html');
499 it('should set the templateUrl in modal options to scope.options.settingsModalOptions.templateUrl', function() {
501 scope.dashboardOptions.settingsModalOptions = {
502 templateUrl: other = 'some/other/url.html'
505 var dfr = $q.defer();
506 spyOn(mockModal, 'open').and.callFake(function(options) {
507 modalOptions = options;
512 childScope.openWidgetSettings(widget);
513 expect(modalOptions.templateUrl).toEqual(other);
516 it('should set the templateUrl in modal options to widget.settingsModalOptions.templateUrl, if present', function() {
519 settingsModalOptions: {
520 templateUrl: expected = 'specific/template.html'
523 var dfr = $q.defer();
524 spyOn(mockModal, 'open').and.callFake(function(options) {
525 modalOptions = options;
530 childScope.openWidgetSettings(widget);
531 expect(modalOptions.templateUrl).toEqual(expected);
534 it('should set the controller in modal options to the default ("WidgetSettingsCtrl")', function() {
536 var dfr = $q.defer();
537 spyOn(mockModal, 'open').and.callFake(function(options) {
538 modalOptions = options;
543 childScope.openWidgetSettings(widget);
544 expect(modalOptions.controller).toEqual('WidgetSettingsCtrl');
547 it('should set the controller in modal options to the default ("WidgetSettingsCtrl"), even when settingsModalOptions is supplied in options', inject(function($rootScope) {
549 scope = $rootScope.$new();
552 var widgetDefinitions = [
555 template: '<div class="wt-one-value">{{2 + 2}}</div>'
559 template: '<span class="wt-two-value">{{value}}</span>'
562 var defaultWidgets = _.clone(widgetDefinitions);
563 scope.dashboardOptions = {
565 widgetDefinitions: widgetDefinitions,
566 defaultWidgets: defaultWidgets,
568 testProperty: 'foobar'
570 settingsModalOptions: {
576 var dfr = $q.defer();
577 spyOn(mockModal, 'open').and.callFake(function(options) {
578 modalOptions = options;
585 element = $compile('<div dashboard="dashboardOptions"></div>')(scope);
587 childScope = element.scope();
589 childScope.openWidgetSettings({});
590 expect(modalOptions.controller).toEqual('WidgetSettingsCtrl');
594 it('should set the controller in modal options to the default ("WidgetSettingsCtrl"), even when settingsModalOptions is supplied in widget', inject(function($rootScope) {
596 scope = $rootScope.$new();
599 var widgetDefinitions = [
602 template: '<div class="wt-one-value">{{2 + 2}}</div>'
606 template: '<span class="wt-two-value">{{value}}</span>'
609 var defaultWidgets = _.clone(widgetDefinitions);
610 scope.dashboardOptions = {
612 widgetDefinitions: widgetDefinitions,
613 defaultWidgets: defaultWidgets,
615 testProperty: 'foobar'
617 settingsModalOptions: {
623 var dfr = $q.defer();
624 spyOn(mockModal, 'open').and.callFake(function(options) {
625 modalOptions = options;
632 element = $compile('<div dashboard="dashboardOptions"></div>')(scope);
634 childScope = element.scope();
636 childScope.openWidgetSettings({
637 settingsModalOptions: {
638 templateUrl: 'custom/widget/template.html'
641 expect(modalOptions.controller).toEqual('WidgetSettingsCtrl');
642 expect(modalOptions.backdrop).toEqual(false);
643 expect(modalOptions.templateUrl).toEqual('custom/widget/template.html');
647 it('should set the controller to scope.options.settingsModalOptions.controller if provided', function() {
648 scope.dashboardOptions.settingsModalOptions = {};
649 var expected = scope.dashboardOptions.settingsModalOptions.controller = 'MyCustomCtrl';
651 var dfr = $q.defer();
652 spyOn(mockModal, 'open').and.callFake(function(options) {
653 modalOptions = options;
658 childScope.openWidgetSettings(widget);
659 expect(modalOptions.controller).toEqual(expected);
662 it('should set the controller to widget.settingsModalOptions.controller if provided', function() {
665 settingsModalOptions: {
666 controller: expected = 'MyWidgetCtrl'
669 var dfr = $q.defer();
670 spyOn(mockModal, 'open').and.callFake(function(options) {
671 modalOptions = options;
676 childScope.openWidgetSettings(widget);
677 expect(modalOptions.controller).toEqual(expected);
680 it('should pass in other modal options set in scope.options.settingsModalOptions', function() {
681 scope.dashboardOptions.settingsModalOptions = {
683 windowClass: 'my-extra-class'
686 var dfr = $q.defer();
687 spyOn(mockModal, 'open').and.callFake(function(options) {
688 modalOptions = options;
693 childScope.openWidgetSettings(widget);
694 expect(modalOptions.keyboard).toEqual(false);
695 expect(modalOptions.windowClass).toEqual('my-extra-class');
698 it('should pass in other modal options set in widget.settingsModalOptions', function() {
699 scope.dashboardOptions.settingsModalOptions = {
701 windowClass: 'my-extra-class'
704 settingsModalOptions: {
709 var dfr = $q.defer();
710 spyOn(mockModal, 'open').and.callFake(function(options) {
711 modalOptions = options;
716 childScope.openWidgetSettings(widget);
717 expect(modalOptions.keyboard).toEqual(true);
718 expect(modalOptions.size).toEqual('sm');
719 expect(modalOptions.windowClass).toEqual('my-extra-class');
722 it('should emit a "widgetChanged" event on the childScope when the modal promise is called', function(done) {
725 var dfr = $q.defer();
726 spyOn(mockModal, 'open').and.callFake(function(options) {
727 modalOptions = options;
732 spyOn(childScope.options, 'onSettingsClose');
733 childScope.openWidgetSettings(widget);
734 childScope.$on('widgetChanged', done);
735 dfr.resolve(result, widget);
736 childScope.$digest();
739 it('should call scope.options.onSettingsClose when the modal promise is resolved by default', function() {
742 var dfr = $q.defer();
743 spyOn(mockModal, 'open').and.callFake(function(options) {
744 modalOptions = options;
749 spyOn(childScope.options, 'onSettingsClose');
750 childScope.openWidgetSettings(widget);
752 childScope.$digest();
753 expect(scope.dashboardOptions.onSettingsClose).toHaveBeenCalledWith(result, widget, childScope);
756 it('should call scope.options.onSettingsDismiss when the modal promise is rejected by default', function() {
759 var dfr = $q.defer();
760 spyOn(mockModal, 'open').and.callFake(function(options) {
761 modalOptions = options;
766 spyOn(childScope.options, 'onSettingsDismiss');
767 childScope.openWidgetSettings(widget);
768 dfr.reject('Testing failure');
769 childScope.$digest();
770 expect(scope.dashboardOptions.onSettingsDismiss).toHaveBeenCalledWith('Testing failure', childScope);
773 it('should call widget.onSettingsClose if provided when the modal promise is resolved', function() {
775 onSettingsClose: function(result, widget, scope) {
780 var dfr = $q.defer();
781 spyOn(widget, 'onSettingsClose');
782 spyOn(mockModal, 'open').and.callFake(function(options) {
783 modalOptions = options;
788 spyOn(childScope.options, 'onSettingsClose');
789 childScope.openWidgetSettings(widget);
791 childScope.$digest();
792 expect(scope.dashboardOptions.onSettingsClose).not.toHaveBeenCalled();
793 expect(widget.onSettingsClose).toHaveBeenCalledWith(result, widget, childScope);
796 it('should call widget.onSettingsDismiss if provided when the modal promise is rejected', function() {
798 onSettingsDismiss: function(result, widget, scope) {
803 var dfr = $q.defer();
804 spyOn(widget, 'onSettingsDismiss');
805 spyOn(mockModal, 'open').and.callFake(function(options) {
806 modalOptions = options;
811 spyOn(childScope.options, 'onSettingsDismiss');
812 childScope.openWidgetSettings(widget);
813 dfr.reject('Testing failure');
814 childScope.$digest();
815 expect(scope.dashboardOptions.onSettingsDismiss).not.toHaveBeenCalled();
816 expect(widget.onSettingsDismiss).toHaveBeenCalledWith('Testing failure', childScope);
821 describe('the default onSettingsClose callback', function() {
825 beforeEach(function() {
826 onSettingsClose = childScope.options.onSettingsClose;
829 it('should exist', function() {
830 expect(typeof onSettingsClose).toEqual('function');
833 it('should deep extend widget with result', function() {
846 onSettingsClose(result, widget, {});
847 expect(widget).toEqual({
858 describe('the default onSettingsDismiss callback', function() {
860 var onSettingsDismiss;
862 beforeEach(function() {
863 onSettingsDismiss = childScope.options.onSettingsDismiss;
866 it('should exist', function() {
867 expect(typeof onSettingsDismiss).toEqual('function');
870 it('should call $log.info with the reason', function() {
871 spyOn(mockLog, 'info');
872 onSettingsDismiss('dismiss reason');
873 expect(mockLog.info).toHaveBeenCalled();