Merge pull request #11840 from mfb/smtp-reconnect
[civicrm-core.git] / tests / karma / unit / crmCaseTypeSpec.js
1 /* global $, _, CRM:true */
2 'use strict';
3
4 describe('crmCaseType', function() {
5 var $controller;
6 var $compile;
7 var $httpBackend;
8 var $q;
9 var $rootScope;
10 var $timeout;
11 var apiCalls;
12 var ctrl;
13 var compile;
14 var scope;
15
16 beforeEach(function() {
17 CRM.resourceUrls = {
18 'civicrm': ''
19 };
20 // CRM_Case_XMLProcessor::REL_TYPE_CNAME
21 CRM.crmCaseType = {
22 'REL_TYPE_CNAME': 'label_b_a'
23 };
24 module('crmCaseType');
25 module('crmJsonComparator');
26 inject(function(crmJsonComparator) {
27 crmJsonComparator.register(jasmine);
28 });
29 });
30
31 beforeEach(inject(function(_$controller_, _$compile_, _$httpBackend_, _$q_, _$rootScope_, _$timeout_) {
32 $controller = _$controller_;
33 $compile = _$compile_;
34 $httpBackend = _$httpBackend_;
35 $q = _$q_;
36 $rootScope = _$rootScope_;
37 $timeout = _$timeout_;
38 }));
39
40 describe('CaseTypeCtrl', function() {
41 beforeEach(function () {
42 apiCalls = {
43 actStatuses: {
44 values: [
45 {
46 "id": "272",
47 "option_group_id": "25",
48 "label": "Scheduled",
49 "value": "1",
50 "name": "Scheduled",
51 "filter": "0",
52 "is_default": "1",
53 "weight": "1",
54 "is_optgroup": "0",
55 "is_reserved": "1",
56 "is_active": "1"
57 },
58 {
59 "id": "273",
60 "option_group_id": "25",
61 "label": "Completed",
62 "value": "2",
63 "name": "Completed",
64 "filter": "0",
65 "weight": "2",
66 "is_optgroup": "0",
67 "is_reserved": "1",
68 "is_active": "1"
69 }
70 ]
71 },
72 caseStatuses: {
73 values: [
74 {
75 "id": "290",
76 "option_group_id": "28",
77 "label": "Ongoing",
78 "value": "1",
79 "name": "Open",
80 "grouping": "Opened",
81 "filter": "0",
82 "is_default": "1",
83 "weight": "1",
84 "is_optgroup": "0",
85 "is_reserved": "1",
86 "is_active": "1"
87 },
88 {
89 "id": "291",
90 "option_group_id": "28",
91 "label": "Resolved",
92 "value": "2",
93 "name": "Closed",
94 "grouping": "Closed",
95 "filter": "0",
96 "weight": "2",
97 "is_optgroup": "0",
98 "is_reserved": "1",
99 "is_active": "1"
100 }
101 ]
102 },
103 actTypes: {
104 values: [
105 {
106 "id": "784",
107 "option_group_id": "2",
108 "label": "ADC referral",
109 "value": "62",
110 "name": "ADC referral",
111 "filter": "0",
112 "is_default": "0",
113 "weight": "64",
114 "is_optgroup": "0",
115 "is_reserved": "0",
116 "is_active": "1",
117 "component_id": "7"
118 },
119 {
120 "id": "32",
121 "option_group_id": "2",
122 "label": "Add Client To Case",
123 "value": "27",
124 "name": "Add Client To Case",
125 "filter": "0",
126 "is_default": "0",
127 "weight": "26",
128 "description": "",
129 "is_optgroup": "0",
130 "is_reserved": "1",
131 "is_active": "1",
132 "component_id": "7"
133 },
134 {
135 "id": "18",
136 "option_group_id": "2",
137 "label": "Open Case",
138 "value": "13",
139 "name": "Open Case",
140 "filter": "0",
141 "is_default": "0",
142 "weight": "13",
143 "is_optgroup": "0",
144 "is_reserved": "1",
145 "is_active": "1",
146 "component_id": "7",
147 "icon": "fa-folder-open-o"
148 },
149 {
150 "id": "857",
151 "option_group_id": "2",
152 "label": "Medical evaluation",
153 "value": "55",
154 "name": "Medical evaluation",
155 "filter": "0",
156 "is_default": "0",
157 "weight": "56",
158 "is_optgroup": "0",
159 "is_reserved": "0",
160 "is_active": "1",
161 "component_id": "7"
162 },
163 ]
164 },
165 relTypes: {
166 values: [
167 {
168 "id": "14",
169 "name_a_b": "Benefits Specialist is",
170 "label_a_b": "Benefits Specialist is",
171 "name_b_a": "Benefits Specialist",
172 "label_b_a": "Benefits Specialist",
173 "description": "Benefits Specialist",
174 "contact_type_a": "Individual",
175 "contact_type_b": "Individual",
176 "is_reserved": "0",
177 "is_active": "1"
178 },
179 {
180 "id": "9",
181 "name_a_b": "Case Coordinator is",
182 "label_a_b": "Case Coordinator is",
183 "name_b_a": "Case Coordinator",
184 "label_b_a": "Case Coordinator",
185 "description": "Case Coordinator",
186 "contact_type_a": "Individual",
187 "contact_type_b": "Individual",
188 "is_reserved": "0",
189 "is_active": "1"
190 }
191 ]
192 },
193 caseType: {
194 "id": "1",
195 "name": "housing_support",
196 "title": "Housing Support",
197 "description": "Help homeless individuals obtain temporary and long-term housing",
198 "is_active": "1",
199 "is_reserved": "0",
200 "weight": "1",
201 "is_forkable": "1",
202 "is_forked": "",
203 "definition": {
204 "activityTypes": [
205 {"name": "Open Case", "max_instances": "1"}
206 ],
207 "activitySets": [
208 {
209 "name": "standard_timeline",
210 "label": "Standard Timeline",
211 "timeline": "1",
212 "activityTypes": [
213 {
214 "name": "Open Case",
215 "status": "Completed"
216 },
217 {
218 "name": "Medical evaluation",
219 "reference_activity": "Open Case",
220 "reference_offset": "1",
221 "reference_select": "newest"
222 }
223 ]
224 }
225 ],
226 "caseRoles": [
227 {
228 "name": "Homeless Services Coordinator",
229 "creator": "1",
230 "manager": "1"
231 }
232 ]
233 }
234 }
235 };
236 scope = $rootScope.$new();
237 ctrl = $controller('CaseTypeCtrl', {$scope: scope, apiCalls: apiCalls});
238 });
239
240 it('should load activity statuses', function() {
241 expect(scope.activityStatuses).toEqualData(apiCalls.actStatuses.values);
242 });
243
244 it('should load activity types', function() {
245 expect(scope.activityTypes['ADC referral']).toEqualData(apiCalls.actTypes.values[0]);
246 });
247
248 it('addActivitySet should add an activitySet to the case type', function() {
249 scope.addActivitySet('timeline');
250 var activitySets = scope.caseType.definition.activitySets;
251 var newSet = activitySets[activitySets.length - 1];
252 expect(newSet.name).toBe('timeline_1');
253 expect(newSet.timeline).toBe('1');
254 expect(newSet.label).toBe('Timeline');
255 });
256
257 it('addActivitySet handles second timeline correctly', function() {
258 scope.addActivitySet('timeline');
259 scope.addActivitySet('timeline');
260 var activitySets = scope.caseType.definition.activitySets;
261 var newSet = activitySets[activitySets.length - 1];
262 expect(newSet.name).toBe('timeline_2');
263 expect(newSet.timeline).toBe('1');
264 expect(newSet.label).toBe('Timeline #2');
265 });
266 });
267
268 describe('crmAddName', function () {
269 var element;
270
271 beforeEach(function() {
272 scope = $rootScope.$new();
273 scope.activityTypeOptions = [1, 2, 3];
274 element = '<span crm-add-name crm-options="activityTypeOptions"></span>';
275
276 spyOn(CRM.$.fn, 'crmSelect2').and.callThrough();
277
278 element = $compile(element)(scope);
279 scope.$digest();
280 });
281
282 describe('when initialized', function () {
283 var returnValue;
284
285 beforeEach (function () {
286 var dataFunction = CRM.$.fn.crmSelect2.calls.argsFor(0)[0].data;
287 returnValue = dataFunction();
288 });
289
290 it('updates the UI with updated value of scope variable', function () {
291 expect(returnValue).toEqual({ results: scope.activityTypeOptions });
292 });
293 });
294 });
295
296 describe('crmEditableTabTitle', function () {
297 var element, titleLabel, penIcon, saveButton, cancelButton;
298
299 beforeEach(function() {
300 scope = $rootScope.$new();
301 element = '<div crm-editable-tab-title title="Click to edit">' +
302 '<span>{{ activitySet.label }}</span>' +
303 '</div>';
304
305 scope.activitySet = { label: 'Title'};
306 element = $compile(element)(scope);
307
308 titleLabel = $(element).find('span');
309 penIcon = $(element).find('i.fa-pencil');
310 saveButton = $(element).find('button[type=button]');
311 cancelButton = $(element).find('button[type=cancel]');
312
313 scope.$digest();
314 });
315
316 describe('when initialized', function () {
317 it('hides the save and cancel button', function () {
318 expect(saveButton.parent().css('display') === 'none').toBe(true);
319 expect(cancelButton.parent().css('display') === 'none').toBe(true);
320 });
321 });
322
323 describe('when clicked on title label', function () {
324 beforeEach(function () {
325 titleLabel.click();
326 });
327
328 it('hides the pen icon', function () {
329 expect(penIcon.css('display') === 'none').toBe(true);
330 });
331
332 it('shows the save button', function () {
333 expect(saveButton.parent().css('display') !== 'none').toBe(true);
334 });
335
336 it('makes the title editable', function () {
337 expect(titleLabel.attr('contenteditable')).toBe('true');
338 });
339 });
340
341 describe('when clicked outside of the editable area', function () {
342 beforeEach(function () {
343 titleLabel.click();
344 titleLabel.text('Updated Title');
345 titleLabel.blur();
346 $timeout.flush();
347 scope.$digest();
348 });
349
350 it('shows the pen icon', function () {
351 expect(penIcon.css('display') !== 'none').toBe(true);
352 });
353
354 it('hides the save and cancel button', function () {
355 expect(saveButton.parent().css('display') === 'none').toBe(true);
356 expect(cancelButton.parent().css('display') === 'none').toBe(true);
357 });
358
359 it('makes the title non editable', function () {
360 expect(titleLabel.attr('contenteditable')).not.toBe('true');
361 });
362
363 it('does not update the title in angular context', function () {
364 expect(scope.activitySet.label).toBe('Title');
365 });
366 });
367
368 describe('when ESCAPE key is pressed while typing', function () {
369 beforeEach(function () {
370 var eventObj = $.Event('keydown');
371 eventObj.key = 'Escape';
372
373 titleLabel.click();
374 titleLabel.text('Updated Title');
375 titleLabel.trigger(eventObj);
376 scope.$digest();
377 });
378
379 it('shows the pen icon', function () {
380 expect(penIcon.css('display') !== 'none').toBe(true);
381 });
382
383 it('hides the save and cancel button', function () {
384 expect(saveButton.parent().css('display') === 'none').toBe(true);
385 expect(cancelButton.parent().css('display') === 'none').toBe(true);
386 });
387
388 it('makes the title non editable', function () {
389 expect(titleLabel.attr('contenteditable')).not.toBe('true');
390 });
391
392 it('does not update the title', function () {
393 expect(scope.activitySet.label).toBe('Title');
394 });
395 });
396
397 describe('when ENTER key is pressed while typing', function () {
398 beforeEach(function () {
399 var eventObj = $.Event('keydown');
400 eventObj.key = 'Enter';
401
402 titleLabel.click();
403 titleLabel.text('Updated Title');
404 titleLabel.trigger(eventObj);
405 scope.$digest();
406 });
407
408 it('shows the pen icon', function () {
409 expect(penIcon.css('display') !== 'none').toBe(true);
410 });
411
412 it('hides the save and cancel button', function () {
413 expect(saveButton.parent().css('display') === 'none').toBe(true);
414 expect(cancelButton.parent().css('display') === 'none').toBe(true);
415 });
416
417 it('makes the title non editable', function () {
418 expect(titleLabel.attr('contenteditable')).not.toBe('true');
419 });
420
421 it('updates the title in angular context', function () {
422 expect(scope.activitySet.label).toBe('Updated Title');
423 });
424 });
425
426 describe('when SAVE button is clicked', function () {
427 beforeEach(function () {
428 titleLabel.click();
429 titleLabel.text('Updated Title');
430 saveButton.click();
431 scope.$digest();
432 });
433
434 it('shows the pen icon', function () {
435 expect(penIcon.css('display') !== 'none').toBe(true);
436 });
437
438 it('hides the save and cancel button', function () {
439 expect(saveButton.parent().css('display') === 'none').toBe(true);
440 expect(cancelButton.parent().css('display') === 'none').toBe(true);
441 });
442
443 it('makes the title non editable', function () {
444 expect(titleLabel.attr('contenteditable')).not.toBe('true');
445 });
446
447 it('updates the title in angular context', function () {
448 expect(scope.activitySet.label).toBe('Updated Title');
449 });
450 });
451
452 describe('when CANCEL button is clicked', function () {
453 beforeEach(function () {
454 titleLabel.click();
455 titleLabel.text('Updated Title');
456 cancelButton.click();
457 scope.$digest();
458 });
459
460 it('shows the pen icon', function () {
461 expect(penIcon.css('display') !== 'none').toBe(true);
462 });
463
464 it('hides the save and cancel button', function () {
465 expect(saveButton.parent().css('display') === 'none').toBe(true);
466 expect(cancelButton.parent().css('display') === 'none').toBe(true);
467 });
468
469 it('makes the title non editable', function () {
470 expect(titleLabel.attr('contenteditable')).not.toBe('true');
471 });
472
473 it('does not update the title in angular context', function () {
474 expect(scope.activitySet.label).toBe('Title');
475 });
476 });
477 });
478
479 describe('CaseTypeListCtrl', function() {
480 var caseTypes, crmApiSpy;
481
482 beforeEach(function() {
483 caseTypes = {
484 values: {
485 1: { id: 1 },
486 2: { id: 2 },
487 3: { id: 3 }
488 }
489 };
490 crmApiSpy = jasmine.createSpy('crmApi').and.returnValue($q.resolve());
491 scope = $rootScope.$new();
492 ctrl = $controller('CaseTypeListCtrl', {
493 $scope: scope,
494 caseTypes: caseTypes,
495 crmApi: crmApiSpy
496 });
497 });
498
499 it('should store an index of case types', function() {
500 expect(scope.caseTypes).toEqual(caseTypes.values);
501 });
502
503 describe('toggleCaseType', function() {
504 var caseType = { id: _.uniqueId() };
505
506 describe('when the case is active', function() {
507 beforeEach(function() {
508 caseType.is_active = '1';
509
510 scope.toggleCaseType(caseType);
511 });
512
513 it('sets the case type as inactive', function() {
514 expect(crmApiSpy).toHaveBeenCalledWith('CaseType', 'create', jasmine.objectContaining({
515 id: caseType.id,
516 is_active: '0'
517 }), true);
518 });
519 });
520
521 describe('when the case is inactive', function() {
522 beforeEach(function() {
523 caseType.is_active = '0';
524
525 scope.toggleCaseType(caseType);
526 });
527
528 it('sets the case type as active', function() {
529 expect(crmApiSpy).toHaveBeenCalledWith('CaseType', 'create', jasmine.objectContaining({
530 id: caseType.id,
531 is_active: '1'
532 }), true);
533 });
534 });
535 });
536
537 describe('deleteCaseType', function() {
538 var caseType = { id: _.uniqueId() };
539
540 beforeEach(function() {
541 crmApiSpy.and.returnValue($q.resolve(caseType));
542 scope.caseTypes[caseType.id] = caseType;
543
544 scope.deleteCaseType(caseType);
545 scope.$digest();
546 });
547
548 describe('when the case type can be deleted', function() {
549 it('deletes the case from the api', function() {
550 expect(crmApiSpy).toHaveBeenCalledWith('CaseType', 'delete', { id: caseType.id }, jasmine.any(Object));
551 });
552
553 it('removes the case type from the list', function() {
554 expect(scope.caseTypes[caseType.id]).toBeUndefined();
555 });
556 });
557
558 describe('when the case type cannot be delted', function() {
559 var error = { error_message: 'Error Message' };
560
561 beforeEach(function() {
562 var errorHandler;
563
564 crmApiSpy.and.returnValue($q.reject(error));
565 scope.caseTypes[caseType.id] = caseType;
566
567 spyOn(CRM, 'alert');
568 scope.deleteCaseType(caseType);
569 scope.$digest();
570
571 errorHandler = crmApiSpy.calls.mostRecent().args[3].error;
572 errorHandler(error);
573 });
574
575 it('displays the error message', function() {
576 expect(CRM.alert).toHaveBeenCalledWith(error.error_message, 'Error', 'error');
577 });
578 });
579
580 describe('revertCaseType', function() {
581 var caseType = {
582 id: _.uniqueId(),
583 definition: {},
584 is_forked: '1'
585 };
586
587 describe('when reverting a case type', function() {
588 beforeEach(function() {
589 scope.revertCaseType(caseType);
590 });
591
592 it('resets the case type information using the api', function() {
593 expect(crmApiSpy).toHaveBeenCalledWith('CaseType', 'create', jasmine.objectContaining({
594 id: caseType.id,
595 definition: 'null',
596 is_forked: '0'
597 }), true);
598 });
599 });
600 });
601 });
602 });
603 });