blob: fc56429436398c73c1dfa586c4af78fc734c15d4 [file] [log] [blame]
Matteo Scandolo686547a2017-08-08 13:05:25 -07001
2/*
3 * Copyright 2017-present Open Networking Foundation
4
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8
9 * http://www.apache.org/licenses/LICENSE-2.0
10
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
Matteo Scandoloa5d03d52016-07-21 11:35:46 -070019/**
20 * © OpenCORD
21 *
22 * Created by teone on 3/24/16.
23 */
24
25(function () {
26 'use strict';
27
28 let scope, element, isolatedScope, rootScope, compile, filter;
29 const compileElement = () => {
30
31 if(!scope){
32 scope = rootScope.$new();
33 }
34
35 element = angular.element('<xos-table config="config" data="data"></xos-table>');
36 compile(element)(scope);
37 scope.$digest();
38 isolatedScope = element.isolateScope().vm;
39 };
40
41
42 describe('The xos.helper module', function(){
43 describe('The xos-table component', () => {
44
45 beforeEach(module('xos.helpers'));
46
47 beforeEach(inject(function ($compile, $rootScope, $filter) {
48 compile = $compile;
49 rootScope = $rootScope;
50 filter = $filter;
51 }));
52
53 it('should throw an error if no config is specified', () => {
54 function errorFunctionWrapper(){
55 compileElement();
56 }
57 expect(errorFunctionWrapper).toThrow(new Error('[xosTable] Please provide a configuration via the "config" attribute'));
58 });
59
60 it('should throw an error if no config columns are specified', () => {
61 function errorFunctionWrapper(){
62 // setup the parent scope
63 scope = rootScope.$new();
64 scope.config = 'green';
65 compileElement();
66 }
67 expect(errorFunctionWrapper).toThrow(new Error('[xosTable] Please provide a columns list in the configuration'));
68 });
69
70 describe('when basically configured', function() {
71
72 beforeEach(inject(function ($compile, $rootScope) {
73
74 scope = $rootScope.$new();
75
76 scope.config = {
77 columns: [
78 {
79 label: 'Label 1',
80 prop: 'label-1'
81 },
82 {
83 label: 'Label 2',
84 prop: 'label-2'
85 }
86 ]
87 };
88
89 scope.data = [
90 {
91 'label-1': 'Sample 1.1',
92 'label-2': 'Sample 1.2'
93 },
94 {
95 'label-1': 'Sample 2.1',
96 'label-2': 'Sample 2.2'
97 }
98 ]
99
100 element = angular.element('<xos-table config="config" data="data"></xos-table>');
101 $compile(element)(scope);
102 scope.$digest();
103 isolatedScope = element.isolateScope().vm;
104 }));
105
106 it('should contain 2 columns', function() {
107 const th = element[0].getElementsByTagName('th');
108 expect(th.length).toEqual(2);
109 expect(isolatedScope.columns.length).toEqual(2);
110 });
111
112 it('should contain 3 rows', function() {
113 const tr = element[0].getElementsByTagName('tr');
114 expect(tr.length).toEqual(3);
115 });
116
117 it('should render labels', () => {
118 let label1 = $(element).find('thead tr th')[0]
119 let label2 = $(element).find('thead tr th')[1]
120 expect($(label1).text().trim()).toEqual('Label 1');
121 expect($(label2).text().trim()).toEqual('Label 2');
122 });
123
124 describe('when no data are provided', () => {
125 beforeEach(() => {
126 isolatedScope.data = [];
127 scope.$digest();
128 });
129 it('should render an alert', () => {
130 let alert = element[0].getElementsByClassName('alert');
131 let table = element[0].getElementsByTagName('table');
132 expect(alert.length).toEqual(1);
133 expect(table.length).toEqual(1);
134 });
135 });
136
137 describe('when a field type is provided', () => {
138 describe('and is boolean', () => {
139 beforeEach(() => {
140 scope.config = {
141 columns: [
142 {
143 label: 'Label 1',
144 prop: 'label-1',
145 type: 'boolean'
146 },
147 {
148 label: 'Label 2',
149 prop: 'label-2',
150 type: 'boolean'
151 }
152 ]
153 };
154 scope.data = [
155 {
156 'label-1': true,
157 'label-2': 1
158 },
159 {
160 'label-1': false,
161 'label-2': 0
162 }
163 ];
164 compileElement();
165 });
166
167 it('should render an incon', () => {
168 let td1 = $(element).find('tbody tr:first-child td')[0];
169 let td2 = $(element).find('tbody tr:last-child td')[0];
170 expect($(td1).find('i')).toHaveClass('glyphicon-ok');
171 expect($(td2).find('i')).toHaveClass('glyphicon-remove');
172 });
173
174 describe('with field filters', () => {
175 beforeEach(() => {
176 scope.config.filter = 'field';
177 compileElement();
178 });
179
180 it('should render a dropdown for filtering', () => {
181 let td1 = $(element).find('table tbody tr td')[0];
182 expect(td1).toContainElement('select');
183 expect(td1).not.toContainElement('input');
184 });
185
186 it('should correctly filter results', () => {
187 isolatedScope.query = {
188 'label-1': false
189 };
190 scope.$digest();
191 expect(isolatedScope.query['label-1']).toBeFalsy();
192 const tr = $(element).find('tbody:last-child > tr');
193 const icon = $(tr[0]).find('td i');
194 expect(tr.length).toEqual(1);
195 expect(icon).toHaveClass('glyphicon-remove');
196 });
197
198 it('should correctly filter results if the field is in the form of 0|1', () => {
199 isolatedScope.query = {
200 'label-2': false
201 };
202 scope.$digest();
203 expect(isolatedScope.query['label-1']).toBeFalsy();
204 const tr = $(element).find('tbody:last-child > tr');
205 const icon = $(tr[0]).find('td i');
206 expect(tr.length).toEqual(1);
207 expect(icon).toHaveClass('glyphicon-remove');
208 });
209 });
210 });
211
212 describe('and is date', () => {
213 beforeEach(() => {
214 scope.config = {
215 columns: [
216 {
217 label: 'Label 1',
218 prop: 'label-1',
219 type: 'date'
220 }
221 ]
222 };
223 scope.data = [
224 {
225 'label-1': '2015-02-17T22:06:38.059000Z'
226 }
227 ];
228 compileElement();
229 });
230
231 it('should render an formatted date', () => {
232 let td1 = $(element).find('tbody tr:first-child td')[0];
233 const expectedDate = filter('date')(scope.data[0]['label-1'], 'H:mm MMM d, yyyy');
234 expect($(td1).text().trim()).toEqual(expectedDate);
235 });
236 });
237
238 describe('and is array', () => {
239 beforeEach(() => {
240 scope.data = [
241 {categories: ['Film', 'Music']}
242 ];
243 scope.config = {
244 filter: 'field',
245 columns: [
246 {
247 label: 'Categories',
248 prop: 'categories',
249 type: 'array'
250 }
251 ]
252 }
253 compileElement();
254 });
255 it('should render a comma separated list', () => {
256 let td1 = $(element).find('tbody:last-child tr:first-child')[0];
257 expect($(td1).text().trim()).toEqual('Film, Music');
258 });
259
260 it('should not render the filter field', () => {
261 let filter = $(element).find('tbody tr td')[0];
262 expect($(filter)).not.toContainElement('input');
263 });
264 });
265
266 describe('and is object', () => {
267 beforeEach(() => {
268 scope.data = [
269 {
270 attributes: {
271 age: 20,
272 height: 50
273 }
274 }
275 ];
276 scope.config = {
277 filter: 'field',
278 columns: [
279 {
280 label: 'Categories',
281 prop: 'attributes',
282 type: 'object'
283 }
284 ]
285 }
286 compileElement();
287 });
288 it('should render a list of key-values', () => {
289 let td = $(element).find('tbody:last-child tr:first-child')[0];
290 let ageLabel = $(td).find('dl dt')[0];
291 let ageValue = $(td).find('dl dd')[0];
292 let heightLabel = $(td).find('dl dt')[1];
293 let heightValue = $(td).find('dl dd')[1];
294 expect($(ageLabel).text().trim()).toEqual('age');
295 expect($(ageValue).text().trim()).toEqual('20');
296 expect($(heightLabel).text().trim()).toEqual('height');
297 expect($(heightValue).text().trim()).toEqual('50');
298 });
299
300 it('should not render the filter field', () => {
301 let filter = $(element).find('tbody tr td')[0];
302 expect($(filter)).not.toContainElement('input');
303 });
304 });
305
306 describe('and is custom', () => {
307
308 let formatterFn = jasmine.createSpy('formatter').and.returnValue('Formatted Content');
309
310 beforeEach(() => {
311 scope.data = [
312 {categories: ['Film', 'Music']}
313 ];
314 scope.config = {
315 filter: 'field',
316 columns: [
317 {
318 label: 'Categories',
319 prop: 'categories',
320 type: 'custom',
321 formatter: formatterFn
322 }
323 ]
324 }
325 compileElement();
326 });
327
328 it('should check for a formatter property', () => {
329 function errorFunctionWrapper(){
330 // setup the parent scope
331 scope = rootScope.$new();
332 scope.config = {
333 columns: [
334 {
335 label: 'Categories',
336 prop: 'categories',
337 type: 'custom'
338 }
339 ]
340 };
341 compileElement();
342 }
343 expect(errorFunctionWrapper).toThrow(new Error('[xosTable] You have provided a custom field type, a formatter function should provided too.'));
344 });
345
346 it('should check that the formatter property is a function', () => {
347 function errorFunctionWrapper(){
348 // setup the parent scope
349 scope = rootScope.$new();
350 scope.config = {
351 columns: [
352 {
353 label: 'Categories',
354 prop: 'categories',
355 type: 'custom',
356 formatter: 'formatter'
357 }
358 ]
359 };
360 compileElement();
361 }
362 expect(errorFunctionWrapper).toThrow(new Error('[xosTable] You have provided a custom field type, a formatter function should provided too.'));
363 });
364
365 it('should format data using the formatter property', () => {
366 let td1 = $(element).find('tbody:last-child tr:first-child')[0];
367 expect($(td1).text().trim()).toEqual('Formatted Content');
368 // the custom formatted should receive the entire object, otherwise is not so custom
369 expect(formatterFn).toHaveBeenCalledWith({categories: ['Film', 'Music']});
370 });
371
372 it('should not render the filter field', () => {
373 // displayed value is different from model val, filter would not work
374 let filter = $(element).find('tbody tr td')[0];
375 expect($(filter)).not.toContainElement('input');
376 });
377 });
378
379 describe('and is icon', () => {
380
381 beforeEach(() => {
382 scope.config = {
383 columns: [
384 {
385 label: 'Label 1',
386 prop: 'label-1',
387 type: 'icon',
388 formatter: item => {
389 switch (item['label-1']){
390 case 1:
391 return 'ok';
392 case 2:
393 return 'remove';
394 case 3:
395 return 'plus'
396 }
397 }
398 }
399 ]
400 };
401 scope.data = [
402 {
403 'label-1': 1
404 },
405 {
406 'label-1': 2
407 },
408 {
409 'label-1': 3
410 }
411 ];
412 compileElement();
413 });
414
415 it('should render a custom icon', () => {
416 let td1 = $(element).find('tbody tr:first-child td')[0];
417 let td2 = $(element).find('tbody tr:nth-child(2) td')[0];
418 let td3 = $(element).find('tbody tr:last-child td')[0];
419 expect($(td1).find('i')).toHaveClass('glyphicon-ok');
420 expect($(td2).find('i')).toHaveClass('glyphicon-remove');
421 expect($(td3).find('i')).toHaveClass('glyphicon-plus');
422 });
423 });
424 });
425
426 describe('when a link property is provided', () => {
427 beforeEach(() => {
428 scope.data = [
429 {
430 id: 1}
431 ];
432 scope.config = {
433 columns: [
434 {
435 label: 'Id',
436 prop: 'id',
437 link: (item) => {
438 return `/link/${item.id}`;
439 }
440 }
441 ]
442 }
443 compileElement();
444 });
445
446 it('should check that the link property is a function', () => {
447 function errorFunctionWrapper(){
448 // setup the parent scope
449 scope = rootScope.$new();
450 scope.config = {
451 columns: [
452 {
453 label: 'Categories',
454 prop: 'categories',
455 link: 'custom'
456 }
457 ]
458 };
459 compileElement();
460 }
461 expect(errorFunctionWrapper).toThrow(new Error('[xosTable] The link property should be a function.'));
462 });
463
464 it('should render a link with the correct url', () => {
465 let link = $(element).find('tbody tr:first-child td a')[0];
466 expect($(link).attr('href')).toEqual('/link/1');
467 });
468 });
469
470 describe('when actions are passed', () => {
471
472 let cb = jasmine.createSpy('callback')
473
474 beforeEach(() => {
475 isolatedScope.config.actions = [
476 {
477 label: 'delete',
478 icon: 'remove',
479 cb: cb,
480 color: 'red'
481 }
482 ];
483 scope.$digest();
484 });
485
486 it('should have 3 columns', () => {
487 const th = element[0].getElementsByTagName('th');
488 expect(th.length).toEqual(3);
489 expect(isolatedScope.columns.length).toEqual(2);
490 });
491
492 it('when clicking on action should invoke callback', () => {
493 const link = element[0].getElementsByTagName('a')[0];
494 link.click();
495 expect(cb).toHaveBeenCalledWith(scope.data[0]);
496 });
497 });
498
499 describe('when filter is fulltext', () => {
500 beforeEach(() => {
501 isolatedScope.config.filter = 'fulltext';
502 scope.$digest();
503 });
504
505 it('should render a text field', () => {
506 const textField = element[0].getElementsByTagName('input');
507 expect(textField.length).toEqual(1);
508 });
509
510 describe('and a value is enterd', () => {
511 beforeEach(() => {
512 isolatedScope.query = '2.2';
513 scope.$digest();
514 });
515
516 it('should contain 2 rows', function() {
517 const tr = element[0].getElementsByTagName('tr');
518 expect(tr.length).toEqual(2);
519 });
520 });
521 });
522
523 describe('when filter is field', () => {
524 beforeEach(() => {
525 isolatedScope.config.filter = 'field';
526 scope.$digest();
527 });
528
529 it('should render a text field for each column', () => {
530 const textField = element[0].getElementsByTagName('input');
531 expect(textField.length).toEqual(2);
532 });
533
534 describe('and a value is enterd', () => {
535 beforeEach(() => {
536 isolatedScope.query = {'label-1': '2.1'};
537 scope.$digest();
538 });
539
540 it('should contain 3 rows', function() {
541 const tr = element[0].getElementsByTagName('tr');
542 expect(tr.length).toEqual(3);
543 });
544 });
545 });
546
547 describe('when order is true', () => {
548 beforeEach(() => {
549 isolatedScope.config.order = true;
550 scope.$digest();
551 });
552
553 it('should render a arrows beside', () => {
554 const arrows = element[0].getElementsByTagName('i');
555 expect(arrows.length).toEqual(4);
556 });
557
558 describe('and a default ordering is passed', () => {
559
560 beforeEach(() => {
561 scope.config.order = {
562 field: 'label-1',
563 reverse: true
564 };
565 compileElement();
566 });
567
568 it('should orderBy the default order', () => {
569 const tr = $(element).find('tr');
570 expect($(tr[1]).text()).toContain('Sample 2.2');
571 expect($(tr[2]).text()).toContain('Sample 1.1');
572 });
573 });
574
575 describe('and an order is set', () => {
576 beforeEach(() => {
577 isolatedScope.orderBy = 'label-1';
578 isolatedScope.reverse = true;
579 scope.$digest();
580 });
581
582 it('should orderBy', function() {
583 // console.log($(element).find('table'));
584 const tr = $(element).find('tr');
585 expect($(tr[1]).text()).toContain('Sample 2.2');
586 expect($(tr[2]).text()).toContain('Sample 1.1');
587 });
588 });
589 });
590 });
591 });
592 });
593})();
594