1 /* global $, _, CRM:true */
4 describe('crmCaseType', function() {
14 var defaultAssigneeDefaultValue
;
17 beforeEach(function() {
21 module('crmCaseType');
22 module('crmJsonComparator');
23 inject(function(crmJsonComparator
) {
24 crmJsonComparator
.register(jasmine
);
28 beforeEach(inject(function(_
$controller_
, _
$compile_
, _
$httpBackend_
, _
$q_
, _
$rootScope_
, _
$timeout_
) {
29 $controller
= _
$controller_
;
30 $compile
= _
$compile_
;
31 $httpBackend
= _
$httpBackend_
;
33 $rootScope
= _
$rootScope_
;
34 $timeout
= _
$timeout_
;
37 describe('CaseTypeCtrl', function() {
38 beforeEach(function () {
44 "option_group_id": "25",
57 "option_group_id": "25",
73 "option_group_id": "28",
87 "option_group_id": "28",
104 "option_group_id": "2",
105 "label": "ADC referral",
107 "name": "ADC referral",
118 "option_group_id": "2",
119 "label": "Add Client To Case",
121 "name": "Add Client To Case",
133 "option_group_id": "2",
134 "label": "Open Case",
144 "icon": "fa-folder-open-o"
148 "option_group_id": "2",
149 "label": "Medical evaluation",
151 "name": "Medical evaluation",
166 "name_a_b": "Benefits Specialist is",
167 "label_a_b": "Benefits Specialist is",
168 "name_b_a": "Benefits Specialist",
169 "label_b_a": "Benefits Specialist",
170 "description": "Benefits Specialist",
171 "contact_type_a": "Individual",
172 "contact_type_b": "Individual",
178 "name_a_b": "Case Coordinator is",
179 "label_a_b": "Case Coordinator is",
180 "name_b_a": "Case Coordinator",
181 "label_b_a": "Case Coordinator",
182 "description": "Case Coordinator",
183 "contact_type_a": "Individual",
184 "contact_type_b": "Individual",
190 "name_a_b": "Homeless Services Coordinator is",
191 "label_a_b": "Homeless Services Coordinator is",
192 "name_b_a": "Homeless Services Coordinator",
193 "label_b_a": "Homeless Services Coordinator",
194 "description": "Homeless Services Coordinator",
195 "contact_type_a": "Individual",
196 "contact_type_b": "Individual",
202 "name_a_b": "Spouse of",
203 "label_a_b": "Spouse of",
204 "name_b_a": "Spouse of",
205 "label_b_a": "Spouse of",
206 "description": "Spousal relationship.",
207 "contact_type_a": "Individual",
208 "contact_type_b": "Individual",
212 // include one where name is different from label
215 "name_a_b": "GA123ab",
216 "label_a_b": "Guardian Angel is",
217 "name_b_a": "GA123ba",
218 "label_b_a": "Guardian Angel for",
219 "description": "Guardian Angel.",
220 "contact_type_a": "Individual",
221 "contact_type_b": "Individual",
227 // Where is this used in the tests?
228 // It seems to be in the format for the activity assignee.
233 "value": "Benefits Specialist"
237 "value": "Benefits Specialist is"
241 "value": "Case Coordinator"
245 "value": "Case Coordinator is"
255 "name": "housing_support",
256 "title": "Housing Support",
257 "description": "Help homeless individuals obtain temporary and long-term housing",
265 {"name": "Open Case", "max_instances": "1"}
269 "name": "standard_timeline",
270 "label": "Standard Timeline",
275 "status": "Completed"
278 "name": "Medical evaluation",
279 "reference_activity": "Open Case",
280 "reference_offset": "1",
281 "reference_select": "newest"
288 "name": "Homeless Services Coordinator",
295 defaultAssigneeTypes
: {
299 "option_group_id": "152",
312 "option_group_id": "152",
313 "label": "By relationship to workflow client",
315 "name": "BY_RELATIONSHIP",
325 "option_group_id": "152",
326 "label": "Specific contact",
328 "name": "SPECIFIC_CONTACT",
338 "option_group_id": "152",
339 "label": "User creating the workflow",
341 "name": "USER_CREATING_THE_CASE",
352 defaultAssigneeDefaultValue
= _
.find(apiCalls
.defaultAssigneeTypes
.values
, { is_default
: '1' });
353 scope
= $rootScope
.$new();
354 ctrl
= $controller('CaseTypeCtrl', {$scope
: scope
, apiCalls
: apiCalls
});
357 it('should load activity statuses', function() {
358 expect(scope
.activityStatuses
).toEqualData(apiCalls
.actStatuses
.values
);
361 it('should load activity types', function() {
362 expect(scope
.activityTypes
['ADC referral']).toEqualData(apiCalls
.actTypes
.values
[0]);
365 it('should store the default assignee types', function() {
366 expect(scope
.defaultAssigneeTypes
).toBe(apiCalls
.defaultAssigneeTypes
.values
);
369 it('should store the default assignee types values indexed by name', function() {
370 var defaultAssigneeTypeValues
= _
.chain(apiCalls
.defaultAssigneeTypes
.values
)
371 .indexBy('name').mapValues('value').value();
373 expect(scope
.defaultAssigneeTypeValues
).toEqual(defaultAssigneeTypeValues
);
376 it('should store the default assignee relationship type options', function() {
377 var defaultRelationshipTypeOptions
= _
.transform(apiCalls
.relTypes
.values
, function(result
, relType
) {
378 var isBidirectionalRelationship
= relType
.label_a_b
=== relType
.label_b_a
;
381 label
: relType
.label_b_a
,
382 value
: relType
.id
+ '_a_b'
385 if (!isBidirectionalRelationship
) {
387 label
: relType
.label_a_b
,
388 value
: relType
.id
+ '_b_a'
393 expect(scope
.defaultRelationshipTypeOptions
).toEqual(defaultRelationshipTypeOptions
);
396 it('addActivitySet should add an activitySet to the case type', function() {
397 scope
.addActivitySet('timeline');
398 var activitySets
= scope
.caseType
.definition
.activitySets
;
399 var newSet
= activitySets
[activitySets
.length
- 1];
400 expect(newSet
.name
).toBe('timeline_1');
401 expect(newSet
.timeline
).toBe('1');
402 expect(newSet
.label
).toBe('Timeline');
405 it('addActivitySet handles second timeline correctly', function() {
406 scope
.addActivitySet('timeline');
407 scope
.addActivitySet('timeline');
408 var activitySets
= scope
.caseType
.definition
.activitySets
;
409 var newSet
= activitySets
[activitySets
.length
- 1];
410 expect(newSet
.name
).toBe('timeline_2');
411 expect(newSet
.timeline
).toBe('1');
412 expect(newSet
.label
).toBe('Timeline #2');
415 describe('when clearing the activity\'s default assignee type values', function() {
418 beforeEach(function() {
420 default_assignee_relationship
: 1,
421 default_assignee_contact
: 2
424 scope
.clearActivityDefaultAssigneeValues(activity
);
427 it('clears the default assignee relationship for the activity', function() {
428 expect(activity
.default_assignee_relationship
).toBe(null);
431 it('clears the default assignee contact for the activity', function() {
432 expect(activity
.default_assignee_contact
).toBe(null);
436 describe('when adding a new activity to a set', function() {
439 beforeEach(function() {
440 activitySet
= { activityTypes
: [] };
441 scope
.activityTypes
= { comment
: { label
: 'Add a new comment' } };
443 scope
.addActivity(activitySet
, 'comment');
446 it('adds a new Comment activity to the set', function() {
447 expect(activitySet
.activityTypes
[0]).toEqual({
449 label
: scope
.activityTypes
.comment
.label
,
451 reference_activity
: 'Open Case',
452 reference_offset
: '1',
453 reference_select
: 'newest',
454 default_assignee_type
: defaultAssigneeDefaultValue
.value
459 describe('when creating a new workflow', function() {
460 beforeEach(inject(function ($controller
) {
461 apiCalls
.caseType
= null;
463 ctrl
= $controller('CaseTypeCtrl', {$scope
: scope
, apiCalls
: apiCalls
});
466 it('sets default values for the case type title, name, and active status', function() {
467 expect(scope
.caseType
).toEqual(jasmine
.objectContaining({
474 it('adds an Open Case activty to the default activty set', function() {
475 expect(scope
.caseType
.definition
.activitySets
[0].activityTypes
).toEqual([{
479 default_assignee_type
: defaultAssigneeDefaultValue
.value
484 describe('when adding a role', function() {
485 beforeEach(inject(function ($controller
) {
486 ctrl
= $controller('CaseTypeCtrl', {$scope
: scope
, apiCalls
: apiCalls
});
489 it('updates the case roles', function() {
491 // This line sort of simulates selecting a relationship type from the
492 // dropdown. It doesn't test that clicking picks the right value to
493 // add, just that if it did then the function that gets called does
494 // the right thing with it.
495 // Note the value returned by the dropdown is "backwards", e.g.
496 // for the client perspective direction the dropdown returns the other
497 // direction, which is also what's stored in the xml.
498 scope
.addRole(scope
.caseType
.definition
.caseRoles
, 'Case Coordinator');
500 expect(scope
.caseType
.definition
.caseRoles
).toEqual(
503 name
: 'Homeless Services Coordinator',
506 displayLabel
: 'Homeless Services Coordinator is'
509 name
: 'Case Coordinator',
510 displayLabel
: 'Case Coordinator is'
516 it('updates case roles if choose non-client-perspective direction', function() {
517 // again, the dropdown returns the opposite direction
518 scope
.addRole(scope
.caseType
.definition
.caseRoles
, 'Homeless Services Coordinator is');
519 expect(scope
.caseType
.definition
.caseRoles
).toEqual(
522 name
: 'Homeless Services Coordinator',
525 displayLabel
: 'Homeless Services Coordinator is'
528 name
: 'Homeless Services Coordinator is',
529 displayLabel
: 'Homeless Services Coordinator'
535 it("doesn't add the same role twice", function() {
536 // This is in the mock casetype to start, so check if can add twice.
537 scope
.addRole(scope
.caseType
.definition
.caseRoles
, 'Homeless Services Coordinator');
538 expect(scope
.caseType
.definition
.caseRoles
).toEqual(
541 name
: 'Homeless Services Coordinator',
544 displayLabel
: 'Homeless Services Coordinator is'
551 describe('when adding a role on-the-fly', function() {
552 beforeEach(inject(function ($controller
) {
553 ctrl
= $controller('CaseTypeCtrl', {$scope
: scope
, apiCalls
: apiCalls
});
556 it('updates the case roles for unidirectional', function() {
557 // first simulate the ajax popup to create a new relationship type
560 "name_a_b": "Some New Type is",
561 "label_a_b": "Some New Type is",
562 "name_b_a": "Some New Type for",
563 "label_b_a": "Some New Type for",
564 "description": "Some New Type",
565 "contact_type_a": "Individual",
566 "contact_type_b": "Individual",
570 apiCalls
.relTypes
.values
.push(newType
);
572 // now let the real code do what it does with the new type
573 scope
.addRoleOnTheFly(scope
.caseType
.definition
.caseRoles
, newType
);
575 expect(scope
.caseType
.definition
.caseRoles
).toEqual(
578 name
: 'Homeless Services Coordinator',
581 displayLabel
: 'Homeless Services Coordinator is'
584 name
: 'Some New Type for',
585 displayLabel
: 'Some New Type is'
590 expect(scope
.relationshipTypeOptions
.slice(-2)).toEqual(
593 id
: 'Some New Type is',
594 text
: 'Some New Type for'
597 id
: 'Some New Type for',
598 text
: 'Some New Type is'
604 it('updates the case roles for bidirectional', function() {
605 // first simulate the ajax popup to create a new relationship type
608 "name_a_b": "Friend of",
609 "label_a_b": "Friend of",
610 "name_b_a": "Friend of",
611 "label_b_a": "Friend of",
612 "description": "Friend",
613 "contact_type_a": "Individual",
614 "contact_type_b": "Individual",
618 apiCalls
.relTypes
.values
.push(newType
);
620 // now let the real code do what it does with the new type
621 scope
.addRoleOnTheFly(scope
.caseType
.definition
.caseRoles
, newType
);
623 expect(scope
.caseType
.definition
.caseRoles
).toEqual(
626 name
: 'Homeless Services Coordinator',
629 displayLabel
: 'Homeless Services Coordinator is'
633 displayLabel
: 'Friend of'
638 expect(scope
.relationshipTypeOptions
.slice(-1)).toEqual(
647 // Check that it did NOT add two entries for this bidirectional type
648 expect(scope
.relationshipTypeOptions
.slice(-2,-1)).not
.toEqual(
660 describe('crmAddName', function () {
663 beforeEach(function() {
664 scope
= $rootScope
.$new();
665 scope
.activityTypeOptions
= [1, 2, 3];
666 element
= '<span crm-add-name crm-options="activityTypeOptions"></span>';
668 spyOn(CRM
.$.fn
, 'crmSelect2').and
.callThrough();
670 element
= $compile(element
)(scope
);
674 describe('when initialized', function () {
677 beforeEach (function () {
678 var dataFunction
= CRM
.$.fn
.crmSelect2
.calls
.argsFor(0)[0].data
;
679 returnValue
= dataFunction();
682 it('updates the UI with updated value of scope variable', function () {
683 expect(returnValue
).toEqual({ results
: scope
.activityTypeOptions
});
688 describe('crmEditableTabTitle', function () {
689 var element
, titleLabel
, penIcon
, saveButton
, cancelButton
;
691 beforeEach(function() {
692 scope
= $rootScope
.$new();
693 element
= '<div crm-editable-tab-title title="Click to edit">' +
694 '<span>{{ activitySet.label }}</span>' +
697 scope
.activitySet
= { label
: 'Title'};
698 element
= $compile(element
)(scope
);
700 titleLabel
= $(element
).find('span');
701 penIcon
= $(element
).find('i.fa-pencil');
702 saveButton
= $(element
).find('button[type=button]');
703 cancelButton
= $(element
).find('button[type=cancel]');
708 describe('when initialized', function () {
709 it('hides the save and cancel button', function () {
710 expect(saveButton
.parent().css('display') === 'none').toBe(true);
711 expect(cancelButton
.parent().css('display') === 'none').toBe(true);
715 describe('when clicked on title label', function () {
716 beforeEach(function () {
720 it('hides the pen icon', function () {
721 expect(penIcon
.css('display') === 'none').toBe(true);
724 it('shows the save button', function () {
725 expect(saveButton
.parent().css('display') !== 'none').toBe(true);
728 it('makes the title editable', function () {
729 expect(titleLabel
.attr('contenteditable')).toBe('true');
733 describe('when clicked outside of the editable area', function () {
734 beforeEach(function () {
736 titleLabel
.text('Updated Title');
742 it('shows the pen icon', function () {
743 expect(penIcon
.css('display') !== 'none').toBe(true);
746 it('hides the save and cancel button', function () {
747 expect(saveButton
.parent().css('display') === 'none').toBe(true);
748 expect(cancelButton
.parent().css('display') === 'none').toBe(true);
751 it('makes the title non editable', function () {
752 expect(titleLabel
.attr('contenteditable')).not
.toBe('true');
755 it('does not update the title in angular context', function () {
756 expect(scope
.activitySet
.label
).toBe('Title');
760 describe('when ESCAPE key is pressed while typing', function () {
761 beforeEach(function () {
762 var eventObj
= $.Event('keydown');
763 eventObj
.key
= 'Escape';
766 titleLabel
.text('Updated Title');
767 titleLabel
.trigger(eventObj
);
771 it('shows the pen icon', function () {
772 expect(penIcon
.css('display') !== 'none').toBe(true);
775 it('hides the save and cancel button', function () {
776 expect(saveButton
.parent().css('display') === 'none').toBe(true);
777 expect(cancelButton
.parent().css('display') === 'none').toBe(true);
780 it('makes the title non editable', function () {
781 expect(titleLabel
.attr('contenteditable')).not
.toBe('true');
784 it('does not update the title', function () {
785 expect(scope
.activitySet
.label
).toBe('Title');
789 describe('when ENTER key is pressed while typing', function () {
790 beforeEach(function () {
791 var eventObj
= $.Event('keydown');
792 eventObj
.key
= 'Enter';
795 titleLabel
.text('Updated Title');
796 titleLabel
.trigger(eventObj
);
800 it('shows the pen icon', function () {
801 expect(penIcon
.css('display') !== 'none').toBe(true);
804 it('hides the save and cancel button', function () {
805 expect(saveButton
.parent().css('display') === 'none').toBe(true);
806 expect(cancelButton
.parent().css('display') === 'none').toBe(true);
809 it('makes the title non editable', function () {
810 expect(titleLabel
.attr('contenteditable')).not
.toBe('true');
813 it('updates the title in angular context', function () {
814 expect(scope
.activitySet
.label
).toBe('Updated Title');
818 describe('when SAVE button is clicked', function () {
819 beforeEach(function () {
821 titleLabel
.text('Updated Title');
826 it('shows the pen icon', function () {
827 expect(penIcon
.css('display') !== 'none').toBe(true);
830 it('hides the save and cancel button', function () {
831 expect(saveButton
.parent().css('display') === 'none').toBe(true);
832 expect(cancelButton
.parent().css('display') === 'none').toBe(true);
835 it('makes the title non editable', function () {
836 expect(titleLabel
.attr('contenteditable')).not
.toBe('true');
839 it('updates the title in angular context', function () {
840 expect(scope
.activitySet
.label
).toBe('Updated Title');
844 describe('when CANCEL button is clicked', function () {
845 beforeEach(function () {
847 titleLabel
.text('Updated Title');
848 cancelButton
.click();
852 it('shows the pen icon', function () {
853 expect(penIcon
.css('display') !== 'none').toBe(true);
856 it('hides the save and cancel button', function () {
857 expect(saveButton
.parent().css('display') === 'none').toBe(true);
858 expect(cancelButton
.parent().css('display') === 'none').toBe(true);
861 it('makes the title non editable', function () {
862 expect(titleLabel
.attr('contenteditable')).not
.toBe('true');
865 it('does not update the title in angular context', function () {
866 expect(scope
.activitySet
.label
).toBe('Title');
871 describe('CaseTypeListCtrl', function() {
872 var caseTypes
, crmApiSpy
;
874 beforeEach(function() {
882 crmApiSpy
= jasmine
.createSpy('crmApi').and
.returnValue($q
.resolve());
883 scope
= $rootScope
.$new();
884 ctrl
= $controller('CaseTypeListCtrl', {
886 caseTypes
: caseTypes
,
891 it('should store an index of case types', function() {
892 expect(scope
.caseTypes
).toEqual(caseTypes
.values
);
895 describe('toggleCaseType', function() {
896 var caseType
= { id
: _
.uniqueId() };
898 describe('when the case is active', function() {
899 beforeEach(function() {
900 caseType
.is_active
= '1';
902 scope
.toggleCaseType(caseType
);
905 it('sets the case type as inactive', function() {
906 expect(crmApiSpy
).toHaveBeenCalledWith('CaseType', 'create', jasmine
.objectContaining({
913 describe('when the case is inactive', function() {
914 beforeEach(function() {
915 caseType
.is_active
= '0';
917 scope
.toggleCaseType(caseType
);
920 it('sets the case type as active', function() {
921 expect(crmApiSpy
).toHaveBeenCalledWith('CaseType', 'create', jasmine
.objectContaining({
929 describe('deleteCaseType', function() {
930 var caseType
= { id
: _
.uniqueId() };
932 beforeEach(function() {
933 crmApiSpy
.and
.returnValue($q
.resolve(caseType
));
934 scope
.caseTypes
[caseType
.id
] = caseType
;
936 scope
.deleteCaseType(caseType
);
940 describe('when the case type can be deleted', function() {
941 it('deletes the case from the api', function() {
942 expect(crmApiSpy
).toHaveBeenCalledWith('CaseType', 'delete', { id
: caseType
.id
}, jasmine
.any(Object
));
945 it('removes the case type from the list', function() {
946 expect(scope
.caseTypes
[caseType
.id
]).toBeUndefined();
950 describe('when the case type cannot be delted', function() {
951 var error
= { error_message
: 'Error Message' };
953 beforeEach(function() {
956 crmApiSpy
.and
.returnValue($q
.reject(error
));
957 scope
.caseTypes
[caseType
.id
] = caseType
;
960 scope
.deleteCaseType(caseType
);
963 errorHandler
= crmApiSpy
.calls
.mostRecent().args
[3].error
;
967 it('displays the error message', function() {
968 expect(CRM
.alert
).toHaveBeenCalledWith(error
.error_message
, 'Error', 'error');
972 describe('revertCaseType', function() {
979 describe('when reverting a case type', function() {
980 beforeEach(function() {
981 scope
.revertCaseType(caseType
);
984 it('resets the case type information using the api', function() {
985 expect(crmApiSpy
).toHaveBeenCalledWith('CaseType', 'create', jasmine
.objectContaining({