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",
229 "name": "housing_support",
230 "title": "Housing Support",
231 "description": "Help homeless individuals obtain temporary and long-term housing",
239 {"name": "Open Case", "max_instances": "1"}
243 "name": "standard_timeline",
244 "label": "Standard Timeline",
249 "status": "Completed"
252 "name": "Medical evaluation",
253 "reference_activity": "Open Case",
254 "reference_offset": "1",
255 "reference_select": "newest"
262 "name": "Homeless Services Coordinator",
269 defaultAssigneeTypes
: {
273 "option_group_id": "152",
286 "option_group_id": "152",
287 "label": "By relationship to workflow client",
289 "name": "BY_RELATIONSHIP",
299 "option_group_id": "152",
300 "label": "Specific contact",
302 "name": "SPECIFIC_CONTACT",
312 "option_group_id": "152",
313 "label": "User creating the workflow",
315 "name": "USER_CREATING_THE_CASE",
326 defaultAssigneeDefaultValue
= _
.find(apiCalls
.defaultAssigneeTypes
.values
, { is_default
: '1' });
327 scope
= $rootScope
.$new();
328 ctrl
= $controller('CaseTypeCtrl', {$scope
: scope
, apiCalls
: apiCalls
});
331 it('should load activity statuses', function() {
332 expect(scope
.activityStatuses
).toEqualData(apiCalls
.actStatuses
.values
);
335 it('should load activity types', function() {
336 expect(scope
.activityTypes
['ADC referral']).toEqualData(apiCalls
.actTypes
.values
[0]);
339 it('should store the default assignee types', function() {
340 expect(scope
.defaultAssigneeTypes
).toBe(apiCalls
.defaultAssigneeTypes
.values
);
343 it('should store the default assignee types values indexed by name', function() {
344 var defaultAssigneeTypeValues
= _
.chain(apiCalls
.defaultAssigneeTypes
.values
)
345 .indexBy('name').mapValues('value').value();
347 expect(scope
.defaultAssigneeTypeValues
).toEqual(defaultAssigneeTypeValues
);
350 it('should compute the relationship type options', function() {
351 var expectedRelationshipTypeOptions
= [
354 "text": "Benefits Specialist",
355 "xmlName": "Benefits Specialist is"
359 "text": "Benefits Specialist is",
360 "xmlName": "Benefits Specialist"
364 "text": "Case Coordinator",
365 "xmlName": "Case Coordinator is"
369 "text": "Case Coordinator is",
370 "xmlName": "Case Coordinator"
374 "text": "Homeless Services Coordinator",
375 "xmlName": "Homeless Services Coordinator is"
379 "text": "Homeless Services Coordinator is",
380 "xmlName": "Homeless Services Coordinator"
382 // Bidirectional, so should only have the one direction in the list
386 "xmlName": "Spouse of"
388 // include one where name is different from label
391 "text": "Guardian Angel for",
392 "xmlName": "GA123ab",
396 "text": "Guardian Angel is",
397 "xmlName": "GA123ba",
401 expect(scope
.relationshipTypeOptions
).toEqual(expectedRelationshipTypeOptions
);
404 it('addActivitySet should add an activitySet to the case type', function() {
405 scope
.addActivitySet('timeline');
406 var activitySets
= scope
.caseType
.definition
.activitySets
;
407 var newSet
= activitySets
[activitySets
.length
- 1];
408 expect(newSet
.name
).toBe('timeline_1');
409 expect(newSet
.timeline
).toBe('1');
410 expect(newSet
.label
).toBe('Timeline');
413 it('addActivitySet handles second timeline correctly', function() {
414 scope
.addActivitySet('timeline');
415 scope
.addActivitySet('timeline');
416 var activitySets
= scope
.caseType
.definition
.activitySets
;
417 var newSet
= activitySets
[activitySets
.length
- 1];
418 expect(newSet
.name
).toBe('timeline_2');
419 expect(newSet
.timeline
).toBe('1');
420 expect(newSet
.label
).toBe('Timeline #2');
423 describe('when clearing the activity\'s default assignee type values', function() {
426 beforeEach(function() {
428 default_assignee_relationship
: 1,
429 default_assignee_contact
: 2
432 scope
.clearActivityDefaultAssigneeValues(activity
);
435 it('clears the default assignee relationship for the activity', function() {
436 expect(activity
.default_assignee_relationship
).toBe(null);
439 it('clears the default assignee contact for the activity', function() {
440 expect(activity
.default_assignee_contact
).toBe(null);
444 describe('when adding a new activity to a set', function() {
447 beforeEach(function() {
448 activitySet
= { activityTypes
: [] };
449 scope
.activityTypes
= { comment
: { label
: 'Add a new comment' } };
451 scope
.addActivity(activitySet
, 'comment');
454 it('adds a new Comment activity to the set', function() {
455 expect(activitySet
.activityTypes
[0]).toEqual({
457 label
: scope
.activityTypes
.comment
.label
,
459 reference_activity
: 'Open Case',
460 reference_offset
: '1',
461 reference_select
: 'newest',
462 default_assignee_type
: defaultAssigneeDefaultValue
.value
467 describe('when creating a new workflow', function() {
468 beforeEach(inject(function ($controller
) {
469 apiCalls
.caseType
= null;
471 ctrl
= $controller('CaseTypeCtrl', {$scope
: scope
, apiCalls
: apiCalls
});
474 it('sets default values for the case type title, name, and active status', function() {
475 expect(scope
.caseType
).toEqual(jasmine
.objectContaining({
482 it('adds an Open Case activty to the default activty set', function() {
483 expect(scope
.caseType
.definition
.activitySets
[0].activityTypes
).toEqual([{
487 default_assignee_type
: defaultAssigneeDefaultValue
.value
492 describe('when adding a role', function() {
493 beforeEach(inject(function ($controller
) {
494 ctrl
= $controller('CaseTypeCtrl', {$scope
: scope
, apiCalls
: apiCalls
});
497 it('updates the case roles', function() {
499 // This line sort of simulates selecting a relationship type from the
500 // dropdown. It doesn't test that clicking picks the right value to
501 // add, just that if it did then the function that gets called does
502 // the right thing with it.
503 // Note the value returned by the dropdown is "backwards" to match
504 // historically what has been stored in the xml as name, e.g.
505 // for the client perspective direction the dropdown returns the other
507 // So 9_b_a is "Case Coordinator", which means that they actually
508 // selected "Case Coordinator is".
509 scope
.addRole(scope
.caseType
.definition
.caseRoles
, '9_b_a');
511 expect(scope
.caseType
.definition
.caseRoles
).toEqual(
514 name
: 'Homeless Services Coordinator',
517 displayLabel
: 'Homeless Services Coordinator is'
520 name
: 'Case Coordinator',
521 displayLabel
: 'Case Coordinator is'
527 it('updates case roles when name and label are different', function() {
528 scope
.addRole(scope
.caseType
.definition
.caseRoles
, '27_b_a');
529 expect(scope
.caseType
.definition
.caseRoles
).toEqual(
532 name
: 'Homeless Services Coordinator',
535 displayLabel
: 'Homeless Services Coordinator is'
539 displayLabel
: 'Guardian Angel is'
545 it('updates case roles if choose non-client-perspective direction', function() {
546 // again, the dropdown returns the opposite direction
547 scope
.addRole(scope
.caseType
.definition
.caseRoles
, '10_a_b');
548 expect(scope
.caseType
.definition
.caseRoles
).toEqual(
551 name
: 'Homeless Services Coordinator',
554 displayLabel
: 'Homeless Services Coordinator is'
557 name
: 'Homeless Services Coordinator is',
558 displayLabel
: 'Homeless Services Coordinator'
564 it('updates case roles non-client-perspective direction name/label different', function() {
565 scope
.addRole(scope
.caseType
.definition
.caseRoles
, '27_a_b');
566 expect(scope
.caseType
.definition
.caseRoles
).toEqual(
569 name
: 'Homeless Services Coordinator',
572 displayLabel
: 'Homeless Services Coordinator is'
576 displayLabel
: 'Guardian Angel for'
582 it("doesn't add the same role twice", function() {
583 // This is in the mock casetype to start, so check if can add twice.
584 scope
.addRole(scope
.caseType
.definition
.caseRoles
, '10_b_a');
585 expect(scope
.caseType
.definition
.caseRoles
).toEqual(
588 name
: 'Homeless Services Coordinator',
591 displayLabel
: 'Homeless Services Coordinator is'
596 // Try with name and label different. We already tested one time in
597 // another test so just go straight to twice.
598 scope
.addRole(scope
.caseType
.definition
.caseRoles
, '27_b_a');
599 scope
.addRole(scope
.caseType
.definition
.caseRoles
, '27_b_a');
600 expect(scope
.caseType
.definition
.caseRoles
).toEqual(
603 name
: 'Homeless Services Coordinator',
606 displayLabel
: 'Homeless Services Coordinator is'
610 displayLabel
: 'Guardian Angel is'
617 describe('when adding a role on-the-fly', function() {
618 beforeEach(inject(function ($controller
) {
619 ctrl
= $controller('CaseTypeCtrl', {$scope
: scope
, apiCalls
: apiCalls
});
622 it('updates the case roles for unidirectional', function() {
623 // first simulate the ajax popup to create a new relationship type
626 "name_a_b": "Some New Type is",
627 "label_a_b": "Some New Type is",
628 "name_b_a": "Some New Type for",
629 "label_b_a": "Some New Type for",
630 "description": "Some New Type",
631 "contact_type_a": "Individual",
632 "contact_type_b": "Individual",
637 // now let the real code do what it does with the new type
638 scope
.addRoleOnTheFly(scope
.caseType
.definition
.caseRoles
, newType
);
640 expect(scope
.caseType
.definition
.caseRoles
).toEqual(
643 name
: 'Homeless Services Coordinator',
646 displayLabel
: 'Homeless Services Coordinator is'
649 name
: 'Some New Type for',
650 displayLabel
: 'Some New Type is'
655 expect(scope
.relationshipTypeOptions
.slice(-2)).toEqual(
658 xmlName
: 'Some New Type is',
660 text
: 'Some New Type for'
663 xmlName
: 'Some New Type for',
665 text
: 'Some New Type is'
671 it('updates the case roles for bidirectional', function() {
672 // first simulate the ajax popup to create a new relationship type
675 "name_a_b": "Friend of",
676 "label_a_b": "Friend of",
677 "name_b_a": "Friend of",
678 "label_b_a": "Friend of",
679 "description": "Friend",
680 "contact_type_a": "Individual",
681 "contact_type_b": "Individual",
686 // now let the real code do what it does with the new type
687 scope
.addRoleOnTheFly(scope
.caseType
.definition
.caseRoles
, newType
);
689 expect(scope
.caseType
.definition
.caseRoles
).toEqual(
692 name
: 'Homeless Services Coordinator',
695 displayLabel
: 'Homeless Services Coordinator is'
699 displayLabel
: 'Friend of'
704 expect(scope
.relationshipTypeOptions
.slice(-1)).toEqual(
707 xmlName
: 'Friend of',
714 // Check that it did NOT add two entries for this bidirectional type
715 expect(scope
.relationshipTypeOptions
.slice(-2,-1)).not
.toEqual(
718 xmlName
: 'Friend of',
728 describe('crmAddName', function () {
731 beforeEach(function() {
732 scope
= $rootScope
.$new();
733 scope
.activityTypeOptions
= [1, 2, 3];
734 element
= '<span crm-add-name crm-options="activityTypeOptions"></span>';
736 spyOn(CRM
.$.fn
, 'crmSelect2').and
.callThrough();
738 element
= $compile(element
)(scope
);
742 describe('when initialized', function () {
745 beforeEach (function () {
746 var dataFunction
= CRM
.$.fn
.crmSelect2
.calls
.argsFor(0)[0].data
;
747 returnValue
= dataFunction();
750 it('updates the UI with updated value of scope variable', function () {
751 expect(returnValue
).toEqual({ results
: scope
.activityTypeOptions
});
756 describe('crmEditableTabTitle', function () {
757 var element
, titleLabel
, penIcon
, saveButton
, cancelButton
;
759 beforeEach(function() {
760 scope
= $rootScope
.$new();
761 element
= '<div crm-editable-tab-title title="Click to edit">' +
762 '<span>{{ activitySet.label }}</span>' +
765 scope
.activitySet
= { label
: 'Title'};
766 element
= $compile(element
)(scope
);
768 titleLabel
= $(element
).find('span');
769 penIcon
= $(element
).find('i.fa-pencil');
770 saveButton
= $(element
).find('button[type=button]');
771 cancelButton
= $(element
).find('button[type=cancel]');
776 describe('when initialized', function () {
777 it('hides the save and cancel button', function () {
778 expect(saveButton
.parent().css('display') === 'none').toBe(true);
779 expect(cancelButton
.parent().css('display') === 'none').toBe(true);
783 describe('when clicked on title label', function () {
784 beforeEach(function () {
788 it('hides the pen icon', function () {
789 expect(penIcon
.css('display') === 'none').toBe(true);
792 it('shows the save button', function () {
793 expect(saveButton
.parent().css('display') !== 'none').toBe(true);
796 it('makes the title editable', function () {
797 expect(titleLabel
.attr('contenteditable')).toBe('true');
801 describe('when clicked outside of the editable area', function () {
802 beforeEach(function () {
804 titleLabel
.text('Updated Title');
810 it('shows the pen icon', function () {
811 expect(penIcon
.css('display') !== 'none').toBe(true);
814 it('hides the save and cancel button', function () {
815 expect(saveButton
.parent().css('display') === 'none').toBe(true);
816 expect(cancelButton
.parent().css('display') === 'none').toBe(true);
819 it('makes the title non editable', function () {
820 expect(titleLabel
.attr('contenteditable')).not
.toBe('true');
823 it('does not update the title in angular context', function () {
824 expect(scope
.activitySet
.label
).toBe('Title');
828 describe('when ESCAPE key is pressed while typing', function () {
829 beforeEach(function () {
830 var eventObj
= $.Event('keydown');
831 eventObj
.key
= 'Escape';
834 titleLabel
.text('Updated Title');
835 titleLabel
.trigger(eventObj
);
839 it('shows the pen icon', function () {
840 expect(penIcon
.css('display') !== 'none').toBe(true);
843 it('hides the save and cancel button', function () {
844 expect(saveButton
.parent().css('display') === 'none').toBe(true);
845 expect(cancelButton
.parent().css('display') === 'none').toBe(true);
848 it('makes the title non editable', function () {
849 expect(titleLabel
.attr('contenteditable')).not
.toBe('true');
852 it('does not update the title', function () {
853 expect(scope
.activitySet
.label
).toBe('Title');
857 describe('when ENTER key is pressed while typing', function () {
858 beforeEach(function () {
859 var eventObj
= $.Event('keydown');
860 eventObj
.key
= 'Enter';
863 titleLabel
.text('Updated Title');
864 titleLabel
.trigger(eventObj
);
868 it('shows the pen icon', function () {
869 expect(penIcon
.css('display') !== 'none').toBe(true);
872 it('hides the save and cancel button', function () {
873 expect(saveButton
.parent().css('display') === 'none').toBe(true);
874 expect(cancelButton
.parent().css('display') === 'none').toBe(true);
877 it('makes the title non editable', function () {
878 expect(titleLabel
.attr('contenteditable')).not
.toBe('true');
881 it('updates the title in angular context', function () {
882 expect(scope
.activitySet
.label
).toBe('Updated Title');
886 describe('when SAVE button is clicked', function () {
887 beforeEach(function () {
889 titleLabel
.text('Updated Title');
894 it('shows the pen icon', function () {
895 expect(penIcon
.css('display') !== 'none').toBe(true);
898 it('hides the save and cancel button', function () {
899 expect(saveButton
.parent().css('display') === 'none').toBe(true);
900 expect(cancelButton
.parent().css('display') === 'none').toBe(true);
903 it('makes the title non editable', function () {
904 expect(titleLabel
.attr('contenteditable')).not
.toBe('true');
907 it('updates the title in angular context', function () {
908 expect(scope
.activitySet
.label
).toBe('Updated Title');
912 describe('when CANCEL button is clicked', function () {
913 beforeEach(function () {
915 titleLabel
.text('Updated Title');
916 cancelButton
.click();
920 it('shows the pen icon', function () {
921 expect(penIcon
.css('display') !== 'none').toBe(true);
924 it('hides the save and cancel button', function () {
925 expect(saveButton
.parent().css('display') === 'none').toBe(true);
926 expect(cancelButton
.parent().css('display') === 'none').toBe(true);
929 it('makes the title non editable', function () {
930 expect(titleLabel
.attr('contenteditable')).not
.toBe('true');
933 it('does not update the title in angular context', function () {
934 expect(scope
.activitySet
.label
).toBe('Title');
939 describe('CaseTypeListCtrl', function() {
940 var caseTypes
, crmApiSpy
;
942 beforeEach(function() {
950 crmApiSpy
= jasmine
.createSpy('crmApi').and
.returnValue($q
.resolve());
951 scope
= $rootScope
.$new();
952 ctrl
= $controller('CaseTypeListCtrl', {
954 caseTypes
: caseTypes
,
959 it('should store an index of case types', function() {
960 expect(scope
.caseTypes
).toEqual(caseTypes
.values
);
963 describe('toggleCaseType', function() {
964 var caseType
= { id
: _
.uniqueId() };
966 describe('when the case is active', function() {
967 beforeEach(function() {
968 caseType
.is_active
= '1';
970 scope
.toggleCaseType(caseType
);
973 it('sets the case type as inactive', function() {
974 expect(crmApiSpy
).toHaveBeenCalledWith('CaseType', 'create', jasmine
.objectContaining({
981 describe('when the case is inactive', function() {
982 beforeEach(function() {
983 caseType
.is_active
= '0';
985 scope
.toggleCaseType(caseType
);
988 it('sets the case type as active', function() {
989 expect(crmApiSpy
).toHaveBeenCalledWith('CaseType', 'create', jasmine
.objectContaining({
997 describe('deleteCaseType', function() {
998 var caseType
= { id
: _
.uniqueId() };
1000 beforeEach(function() {
1001 crmApiSpy
.and
.returnValue($q
.resolve(caseType
));
1002 scope
.caseTypes
[caseType
.id
] = caseType
;
1004 scope
.deleteCaseType(caseType
);
1008 describe('when the case type can be deleted', function() {
1009 it('deletes the case from the api', function() {
1010 expect(crmApiSpy
).toHaveBeenCalledWith('CaseType', 'delete', { id
: caseType
.id
}, jasmine
.any(Object
));
1013 it('removes the case type from the list', function() {
1014 expect(scope
.caseTypes
[caseType
.id
]).toBeUndefined();
1018 describe('when the case type cannot be delted', function() {
1019 var error
= { error_message
: 'Error Message' };
1021 beforeEach(function() {
1024 crmApiSpy
.and
.returnValue($q
.reject(error
));
1025 scope
.caseTypes
[caseType
.id
] = caseType
;
1027 spyOn(CRM
, 'alert');
1028 scope
.deleteCaseType(caseType
);
1031 errorHandler
= crmApiSpy
.calls
.mostRecent().args
[3].error
;
1032 errorHandler(error
);
1035 it('displays the error message', function() {
1036 expect(CRM
.alert
).toHaveBeenCalledWith(error
.error_message
, 'Error', 'error');
1040 describe('revertCaseType', function() {
1047 describe('when reverting a case type', function() {
1048 beforeEach(function() {
1049 scope
.revertCaseType(caseType
);
1052 it('resets the case type information using the api', function() {
1053 expect(crmApiSpy
).toHaveBeenCalledWith('CaseType', 'create', jasmine
.objectContaining({