3310cad94bc2e6b8587baf16232a212e130c3e72
[portal/sdk.git] /
1 'use strict';
2
3 describe('Factory: LayoutStorage', function () {
4
5   // mock UI Sortable
6   beforeEach(function () {
7     angular.module('ui.sortable', []);
8   });
9
10   // load the service's module
11   beforeEach(module('ui.dashboard'));
12
13   // instantiate service
14   var LayoutStorage;
15   beforeEach(inject(function (_LayoutStorage_) {
16     LayoutStorage = _LayoutStorage_;
17   }));
18
19   describe('the constructor', function() {
20     
21     var storage, options;
22
23     beforeEach(function() {
24       options = {
25         storageId: 'testingStorage',
26         storage: {
27           setItem: function(key, value) {
28
29           },
30           getItem: function(key) {
31
32           },
33           removeItem: function(key) {
34
35           }
36         },
37         storageHash: 'ds5f9d1f',
38         stringifyStorage: true,
39         widgetDefinitions: [
40
41         ],
42         defaultLayouts: [
43           {title: 'something'},
44           {title: 'something'},
45           {title: 'something'}
46         ],
47         widgetButtons: false,
48         explicitSave: false,
49         settingsModalOptions: {},
50         onSettingsClose: function() {
51
52         },
53         onSettingsDismiss: function() {
54
55         }
56       };
57       storage = new LayoutStorage(options);
58     });
59
60     it('should provide an empty implementation of storage if it is not provided', function() {
61       delete options.storage;
62       var stateless = new LayoutStorage(options);
63       var noop = stateless.storage;
64       angular.forEach(['setItem', 'getItem', 'removeItem'], function(method) {
65         expect(typeof noop[method]).toEqual('function');
66         expect(noop[method]).not.toThrow();
67         noop[method]();
68       });
69     });
70
71     it('should set a subset of the options directly on the LayoutStorage instance itself', function() {
72       var properties = {
73         id: 'storageId',
74         storage: 'storage',
75         storageHash: 'storageHash',
76         stringifyStorage: 'stringifyStorage',
77         widgetDefinitions: 'widgetDefinitions',
78         defaultLayouts: 'defaultLayouts',
79         widgetButtons: 'widgetButtons',
80         explicitSave: 'explicitSave',
81         settingsModalOptions: 'settingsModalOptions',
82         onSettingsClose: 'onSettingsClose',
83         onSettingsDismiss: 'onSettingsDismiss'
84       };
85
86       angular.forEach(properties, function(val, key) {
87         expect( storage[key] ).toEqual( options[val] );
88       });
89
90     });
91
92     it('should set stringify as true by default', function() {
93       delete options.stringifyStorage;
94       storage = new LayoutStorage(options);
95       expect(storage.stringifyStorage).toEqual(true);
96     });
97
98     it('should allow stringify to be overridden by option', function() {
99       options.stringifyStorage = false;
100       storage = new LayoutStorage(options);
101       expect(storage.stringifyStorage).toEqual(false);
102     });
103
104     it('should create a layouts array and states object', function() {
105       expect(storage.layouts instanceof Array).toEqual(true);
106       expect(typeof storage.states).toEqual('object');
107     });
108
109     it('should call load', function() {
110       spyOn(LayoutStorage.prototype, 'load');
111       storage = new LayoutStorage(options);
112       expect(LayoutStorage.prototype.load).toHaveBeenCalled();
113     });
114
115   });
116
117   describe('the load method', function() {
118
119     var options, storage;
120
121     beforeEach(function() {
122       options = {
123         storageId: 'testingStorage',
124         storage: {
125           setItem: function(key, value) {
126
127           },
128           getItem: function(key) {
129
130           },
131           removeItem: function(key) {
132
133           }
134         },
135         storageHash: 'ds5f9d1f',
136         stringifyStorage: true,
137         widgetDefinitions: [
138
139         ],
140         defaultLayouts: [
141           {title: 'something'},
142           {title: 'something'},
143           {title: 'something'}
144         ],
145         widgetButtons: false,
146         explicitSave: false
147       }
148       storage = new LayoutStorage(options);
149     });
150
151     it('should use the default layouts if no stored info was found', function() {
152       expect(storage.layouts.length).toEqual(options.defaultLayouts.length);
153     });
154
155     it('should clone default layouts rather than use them directly', function() {
156       expect(storage.layouts.indexOf(options.defaultLayouts[0])).toEqual(-1);
157     });
158
159     it('should use the result from getItem for layouts.', function() {
160       spyOn(options.storage, 'getItem').and.returnValue(JSON.stringify({
161         storageHash: 'ds5f9d1f',
162         layouts: [
163           { id: 0, title: 'title', defaultWidgets: [], active: true },
164           { id: 1, title: 'title2', defaultWidgets: [], active: false },
165           { id: 2, title: 'title3', defaultWidgets: [], active: false },
166           { id: 3, title: 'custom', defaultWidgets: [], active: false }
167         ],
168         states: {
169           0: {},
170           1: {},
171           2: {}
172         }
173       }));
174       storage.load();
175       expect(storage.layouts.map(function(l) {return l.title})).toEqual(['title', 'title2', 'title3', 'custom']);
176     });
177
178     it('should NOT use result from getItem for layouts if the storageHash doesnt match', function() {
179       spyOn(options.storage, 'getItem').and.returnValue(JSON.stringify({
180         storageHash: 'alskdjf02iej',
181         layouts: [
182           { id: 0, title: 'title', defaultWidgets: [], active: true },
183           { id: 1, title: 'title2', defaultWidgets: [], active: false },
184           { id: 2, title: 'title3', defaultWidgets: [], active: false },
185           { id: 3, title: 'custom', defaultWidgets: [], active: false }
186         ],
187         states: {
188           0: {},
189           1: {},
190           2: {}
191         }
192       }));
193       storage.load();
194       expect(storage.layouts.map(function(l) {return l.title})).toEqual(['something', 'something', 'something']);
195     });
196
197     it('should be able to handle async loading via promise', inject(function($rootScope,$q) {
198       var deferred = $q.defer();
199       spyOn(options.storage, 'getItem').and.returnValue(deferred.promise);
200       storage.load();
201       expect(storage.layouts).toEqual([]);
202       deferred.resolve(JSON.stringify({
203         storageHash: 'ds5f9d1f',
204         layouts: [
205           { id: 0, title: 'title', defaultWidgets: [], active: true },
206           { id: 1, title: 'title2', defaultWidgets: [], active: false },
207           { id: 2, title: 'title3', defaultWidgets: [], active: false },
208           { id: 3, title: 'custom', defaultWidgets: [], active: false }
209         ],
210         states: {
211           0: {},
212           1: {},
213           2: {}
214         }
215       }));
216       $rootScope.$apply();
217       expect(storage.layouts.map(function(l) {return l.title})).toEqual(['title', 'title2', 'title3', 'custom']);
218     }));
219
220     it('should load defaults if the deferred is rejected', inject(function($rootScope,$q) {
221       var deferred = $q.defer();
222       spyOn(options.storage, 'getItem').and.returnValue(deferred.promise);
223       storage.load();
224       deferred.reject();
225       $rootScope.$apply();
226       expect(storage.layouts.map(function(l) {return l.title})).toEqual(['something', 'something', 'something']);
227     }));
228
229     it('should load defaults if the json is malformed', inject(function($rootScope,$q) {
230       var deferred = $q.defer();
231       spyOn(options.storage, 'getItem').and.returnValue(deferred.promise);
232       storage.load();
233       expect(storage.layouts).toEqual([]);
234       deferred.resolve(JSON.stringify({
235         storageHash: 'ds5f9d1f',
236         layouts: [
237           { id: 0, title: 'title', defaultWidgets: [], active: true },
238           { id: 1, title: 'title2', defaultWidgets: [], active: false },
239           { id: 2, title: 'title3', defaultWidgets: [], active: false },
240           { id: 3, title: 'custom', defaultWidgets: [], active: false }
241         ],
242         states: {
243           0: {},
244           1: {},
245           2: {}
246         }
247       }).replace('{','{{'));
248       $rootScope.$apply();
249       expect(storage.layouts.map(function(l) {return l.title})).toEqual(['something', 'something', 'something']);
250     }));
251
252     it('should not try to JSON.parse the result if stringifyStorage is false.', function() {
253       options.stringifyStorage = false;
254       storage = new LayoutStorage(options);
255       spyOn(options.storage, 'getItem').and.returnValue({
256         storageHash: 'ds5f9d1f',
257         layouts: [
258           { id: 0, title: 'title', defaultWidgets: [], active: true },
259           { id: 1, title: 'title2', defaultWidgets: [], active: false },
260           { id: 2, title: 'title3', defaultWidgets: [], active: false },
261           { id: 3, title: 'custom', defaultWidgets: [], active: false }
262         ],
263         states: {
264           0: {},
265           1: {},
266           2: {}
267         }
268       });
269       storage.load();
270       expect(storage.layouts.map(function(l) {return l.title})).toEqual(['title', 'title2', 'title3', 'custom']);
271     });
272
273   });
274
275   describe('the add method', function() {
276     
277     var storage, options;
278
279     beforeEach(function() {
280       options = {
281         storageId: 'testingStorage',
282         storage: {
283           setItem: function(key, value) {
284
285           },
286           getItem: function(key) {
287
288           },
289           removeItem: function(key) {
290
291           }
292         },
293         storageHash: 'ds5f9d1f',
294         stringifyStorage: true,
295         widgetDefinitions: [
296
297         ],
298         defaultLayouts: [],
299         widgetButtons: false,
300         explicitSave: false
301       }
302
303       spyOn(LayoutStorage.prototype, 'load' );
304
305       storage = new LayoutStorage(options);
306
307     });
308
309     it('should add to storage.layouts', function() {
310       var newLayout = { title: 'my-layout' };
311       storage.add(newLayout);
312       expect(storage.layouts[0]).toEqual(newLayout);
313     });
314
315     it('should be able to take an array of new layouts', function() {
316       var newLayouts = [ { title: 'my-layout' }, { title: 'my-layout-2' } ];
317       storage.add(newLayouts);
318       expect(storage.layouts.length).toEqual(2);
319       expect(storage.layouts.indexOf(newLayouts[0])).not.toEqual(-1);
320       expect(storage.layouts.indexOf(newLayouts[1])).not.toEqual(-1);
321     });
322
323     it('should look for defaultWidgets on storage options if not supplied on layout definition', function() {
324       options.defaultWidgets = [{name: 'a'}, {name: 'b'}, {name: 'c'}];
325       storage = new LayoutStorage(options);
326
327       var newLayouts = [ { title: 'my-layout', defaultWidgets: [] }, { title: 'my-layout-2' } ];
328       storage.add(newLayouts);
329       expect(newLayouts[0].dashboard.defaultWidgets === newLayouts[0].defaultWidgets).toEqual(true);
330       expect(newLayouts[1].dashboard.defaultWidgets === options.defaultWidgets).toEqual(true);
331     });
332
333     it('should use defaultWidgets if supplied in the layout definition', function() {
334       options.defaultWidgets = [{name: 'a'}, {name: 'b'}, {name: 'c'}];
335       storage = new LayoutStorage(options);
336
337       var newLayouts = [ { title: 'my-layout', defaultWidgets: [] }, { title: 'my-layout-2' } ];
338       storage.add(newLayouts);
339       expect(newLayouts[0].dashboard.defaultWidgets).toEqual([]);
340       expect(newLayouts[1].dashboard.defaultWidgets).toEqual(options.defaultWidgets);
341     });
342
343     it('should look for widgetDefinitions on storage options if not supplied on layout definition', function() {
344       options.widgetDefinitions = [{name: 'a'}, {name: 'b'}, {name: 'c'}];
345       storage = new LayoutStorage(options);
346
347       var newLayouts = [ { title: 'my-layout', widgetDefinitions: [] }, { title: 'my-layout-2' } ];
348       storage.add(newLayouts);
349       expect(newLayouts[0].dashboard.widgetDefinitions === newLayouts[0].widgetDefinitions).toEqual(true);
350       expect(newLayouts[1].dashboard.widgetDefinitions === options.widgetDefinitions).toEqual(true);
351     });
352
353     it('should use widgetDefinitions if supplied in the layout definition', function() {
354       options.widgetDefinitions = [{name: 'a'}, {name: 'b'}, {name: 'c'}];
355       storage = new LayoutStorage(options);
356
357       var newLayouts = [ { title: 'my-layout', widgetDefinitions: [] }, { title: 'my-layout-2' } ];
358       storage.add(newLayouts);
359       expect(newLayouts[0].dashboard.widgetDefinitions).toEqual([]);
360       expect(newLayouts[1].dashboard.widgetDefinitions).toEqual(options.widgetDefinitions);
361     });
362
363   });
364
365   describe('the remove method', function() {
366
367     var storage, options;
368
369     beforeEach(function() {
370       options = {
371         storageId: 'testingStorage',
372         storageHash: 'ds5f9d1f',
373         stringifyStorage: true,
374         widgetDefinitions: [
375           { name: 'A' },
376           { name: 'B' },
377           { name: 'C' }
378         ],
379         defaultLayouts: [
380           { title: '1' },
381           { title: '2', active: true },
382           { title: '3' }
383         ],
384         widgetButtons: false,
385         explicitSave: false
386       }
387
388       storage = new LayoutStorage(options);
389     });
390
391     it('should remove the supplied layout', function() {
392       var layout = storage.layouts[1];
393       storage.remove(layout);
394       expect(storage.layouts.indexOf(layout)).toEqual(-1);
395     });
396
397     it('should delete the state', function() {
398       var layout = storage.layouts[1];
399       storage.setItem(layout.id, {});
400       storage.remove(layout);
401       expect(storage.states[layout.id]).toBeUndefined();
402     });
403
404     it('should do nothing if layout is not in layouts', function() {
405       var layout = {};
406       var before = storage.layouts.length;
407       storage.remove(layout);
408       var after = storage.layouts.length;
409       expect(before).toEqual(after);
410     });
411
412     it('should set another dashboard to active if the layout removed was active', function() {
413       var layout = storage.layouts[1];
414       storage.remove(layout);
415       expect(storage.layouts[0].active || storage.layouts[1].active).toEqual(true);
416     });
417
418     it('should set the layout at index 0 to active if the removed layout was 0', function() {
419       storage.layouts[1].active = false;
420       storage.layouts[0].active = true;
421       storage.remove(storage.layouts[0]);
422       expect(storage.layouts[0].active).toEqual(true);
423     });
424
425     it('should not change the active layout if it was not the one that got removed', function() {
426       var active = storage.layouts[1];
427       var layout = storage.layouts[0];
428       storage.remove(layout);
429       expect(active.active).toEqual(true);
430     });
431
432   });
433
434   describe('the save method', function() {
435
436     var options, storage;
437
438     beforeEach(function() {
439       options = {
440         storageId: 'testingStorage',
441         storage: {
442           setItem: function(key, value) {
443
444           },
445           getItem: function(key) {
446
447           },
448           removeItem: function(key) {
449
450           }
451         },
452         storageHash: 'ds5f9d1f',
453         stringifyStorage: true,
454         widgetDefinitions: [
455
456         ],
457         defaultLayouts: [
458           {title: 'something'},
459           {title: 'something'},
460           {title: 'something'}
461         ],
462         widgetButtons: false,
463         explicitSave: false
464       }
465       storage = new LayoutStorage(options);
466     });
467     
468     it('should call options.storage.setItem with a stringified object', function() {
469       spyOn(options.storage, 'setItem' );
470       storage.save();
471       expect(options.storage.setItem).toHaveBeenCalled();
472       expect(options.storage.setItem.calls.argsFor(0)[0]).toEqual(storage.id);
473       expect(typeof options.storage.setItem.calls.argsFor(0)[1]).toEqual('string');
474       expect(function(){
475         JSON.parse(options.storage.setItem.calls.argsFor(0)[1]);
476       }).not.toThrow();
477     });
478
479     it('should save an object that has layouts, states, and storageHash', function() {
480       spyOn(options.storage, 'setItem' );
481       storage.save();
482       var obj = JSON.parse(options.storage.setItem.calls.argsFor(0)[1]);
483       expect(obj.hasOwnProperty('layouts')).toEqual(true);
484       expect(obj.layouts instanceof Array).toEqual(true);
485       expect(obj.hasOwnProperty('states')).toEqual(true);
486       expect(typeof obj.states).toEqual('object');
487       expect(obj.hasOwnProperty('storageHash')).toEqual(true);
488       expect(typeof obj.storageHash).toEqual('string');
489     });
490
491     it('should call options.storage.setItem with an object when stringifyStorage is false', function() {
492       options.stringifyStorage = false;
493       storage = new LayoutStorage(options);
494       spyOn(options.storage, 'setItem' );
495       storage.save();
496       expect(options.storage.setItem).toHaveBeenCalled();
497       expect(options.storage.setItem.calls.argsFor(0)[0]).toEqual(storage.id);
498       expect(typeof options.storage.setItem.calls.argsFor(0)[1]).toEqual('object');
499     });
500
501   });
502
503   describe('the setItem method', function() {
504     
505     var options, storage;
506
507     beforeEach(function() {
508       options = {
509         storageId: 'testingStorage',
510         storage: {
511           setItem: function(key, value) {
512
513           },
514           getItem: function(key) {
515
516           },
517           removeItem: function(key) {
518
519           }
520         },
521         storageHash: 'ds5f9d1f',
522         stringifyStorage: true,
523         widgetDefinitions: [
524
525         ],
526         defaultLayouts: [
527           {title: 'something'},
528           {title: 'something'},
529           {title: 'something'}
530         ],
531         widgetButtons: false,
532         explicitSave: false
533       }
534       storage = new LayoutStorage(options);
535     });
536
537     it('should set storage.states[id] to the second argument', function() {
538       var state = { some: 'thing'};
539       storage.setItem('id', state);
540       expect(storage.states.id).toEqual(state);
541     });
542
543     it('should call save', function() {
544       spyOn(storage, 'save');
545       var state = { some: 'thing'};
546       storage.setItem('id', state);
547       expect(storage.save).toHaveBeenCalled();
548     });
549
550   });
551
552   describe('the getItem method', function() {
553
554     var options, storage;
555
556     beforeEach(function() {
557       options = {
558         storageId: 'testingStorage',
559         storage: {
560           setItem: function(key, value) {
561
562           },
563           getItem: function(key) {
564
565           },
566           removeItem: function(key) {
567
568           }
569         },
570         storageHash: 'ds5f9d1f',
571         stringifyStorage: true,
572         widgetDefinitions: [
573
574         ],
575         defaultLayouts: [
576           {title: 'something'},
577           {title: 'something'},
578           {title: 'something'}
579         ],
580         widgetButtons: false,
581         explicitSave: false
582       }
583       storage = new LayoutStorage(options);
584     });
585     
586     it('should return states[id]', function() {
587       storage.states['myId'] = {};
588       var result = storage.getItem('myId');
589       expect(result === storage.states['myId']).toEqual(true);
590     });
591
592   });
593
594   describe('the getActiveLayout method', function() {
595     var options, storage;
596
597     beforeEach(function() {
598       options = {
599         storageId: 'testingStorage',
600         storage: {
601           setItem: function(key, value) {
602
603           },
604           getItem: function(key) {
605
606           },
607           removeItem: function(key) {
608
609           }
610         },
611         storageHash: 'ds5f9d1f',
612         stringifyStorage: true,
613         widgetDefinitions: [
614
615         ],
616         defaultLayouts: [
617           {title: 'i am active', active: true},
618           {title: 'i am not'},
619           {title: 'me neither'}
620         ],
621         widgetButtons: false,
622         explicitSave: false
623       }
624       storage = new LayoutStorage(options);
625     });
626
627     it('should return the layout with active:true', function() {
628       var layout = storage.getActiveLayout();
629       expect(layout.title).toEqual('i am active');
630     });
631
632     it('should return false if no layout is active', function() {
633       var layout = storage.getActiveLayout();
634       layout.active = false;
635       var result = storage.getActiveLayout();
636       expect(result).toEqual(false);
637     });
638
639   });
640
641   describe('the removeItem', function() {
642     
643     var options, storage;
644
645     beforeEach(function() {
646       options = {
647         storageId: 'testingStorage',
648         storage: {
649           setItem: function(key, value) {
650
651           },
652           getItem: function(key) {
653
654           },
655           removeItem: function(key) {
656
657           }
658         },
659         storageHash: 'ds5f9d1f',
660         stringifyStorage: true,
661         widgetDefinitions: [
662
663         ],
664         defaultLayouts: [
665           {title: 'i am active', active: true},
666           {title: 'i am not'},
667           {title: 'me neither'}
668         ],
669         widgetButtons: false,
670         explicitSave: false
671       }
672       storage = new LayoutStorage(options);
673     });
674
675     it('should remove states[id]', function() {
676       var state = {};
677       storage.setItem('1', state);
678       storage.removeItem('1');
679       expect(storage.states['1']).toBeUndefined();
680     });
681
682     it('should call save', function() {
683       spyOn(storage, 'save');
684       var state = {};
685       storage.setItem('1', state);
686       storage.removeItem('1');
687       expect(storage.save).toHaveBeenCalled();
688     });
689
690   });
691
692 });