CRM-15855 - Add crmAutosave module
[civicrm-core.git] / tests / karma / unit / crmAutosaveSpec.js
1 describe('crmAutosave', function() {
2
3 function using(name, values, func) {
4 for (var i = 0, count = values.length; i < count; i++) {
5 if (Object.prototype.toString.call(values[i]) !== '[object Array]') {
6 values[i] = [values[i]];
7 }
8 func.apply(this, values[i]);
9 jasmine.currentEnv_.currentSpec.description += ' (with "' + name + '" using ' + values[i].join(', ') + ')';
10 }
11 }
12
13 beforeEach(function() {
14 module('crmUtil');
15 module('crmAutosave');
16 });
17
18 describe('Autosave directive', function() {
19 var $compile,
20 $rootScope,
21 $interval,
22 $timeout,
23 fakeCtrl,
24 model,
25 element;
26
27 beforeEach(inject(function(_$compile_, _$rootScope_, _$interval_, _$timeout_, _$q_) {
28 // The injector unwraps the underscores (_) from around the parameter names when matching
29 $compile = _$compile_;
30 $rootScope = _$rootScope_;
31 $interval = _$interval_;
32 $timeout = _$timeout_;
33
34 $rootScope.fakeCtrl = fakeCtrl = {
35 doSave: function() {
36 },
37 doSaveWithPromise: function() {
38 var dfr = _$q_.defer();
39 $timeout(function() {
40 dfr.resolve();
41 }, 25);
42 return dfr.promise;
43 },
44 doSaveSlowly: function() {
45 var dfr = _$q_.defer();
46 fakeCtrl.savingSlowly = true;
47 $timeout(function() {
48 fakeCtrl.savingSlowly = false;
49 dfr.resolve();
50 }, 1000);
51 return dfr.promise;
52 }
53 };
54 spyOn(fakeCtrl, 'doSave').and.callThrough();
55 spyOn(fakeCtrl, 'doSaveWithPromise').and.callThrough();
56 spyOn(fakeCtrl, 'doSaveSlowly').and.callThrough();
57
58 $rootScope.model = model = {
59 fieldA: 'alpha',
60 fieldB: 'beta'
61 };
62 }));
63
64 // Fake wait - advance the interval & timeout
65 // @param int msec Total time to advance the clock
66 // @param int steps Number of times to issue flush()
67 // Higher values provide a more realistic simulation
68 // but can be a bit slower.
69 function wait(msec, steps) {
70 if (!steps) steps = 4;
71 for (var i = 0; i < steps; i++) {
72 $interval.flush(msec/steps);
73 $timeout.flush(msec/steps);
74 }
75 }
76
77 // TODO: Test: If the save function throws an error, or if its promise returns an error, reset form as dirty.
78
79 var fakeSaves = {
80 "fakeCtrl.doSave()": 'doSave',
81 "fakeCtrl.doSaveWithPromise()": 'doSaveWithPromise'
82 };
83 angular.forEach(fakeSaves, function(saveFunc, saveFuncExpr) {
84 it('calls ' + saveFuncExpr + ' twice over the course of three changes', function() {
85 element = $compile('<form name="myForm" crm-autosave="' + saveFuncExpr + '" crm-autosave-model="model" crm-autosave-interval="{poll: 25, save: 50}"><input class="fieldA" ng-model="model.fieldA"><input class="fieldB" ng-model="model.fieldB"></form>')($rootScope);
86 $rootScope.$digest();
87
88 // check that we load pristine data and don't do any saving
89 wait(100);
90 expect(element.find('.fieldA').val()).toBe('alpha');
91 expect(element.find('.fieldB').val()).toBe('beta');
92 expect($rootScope.myForm.$pristine).toBe(true);
93 expect(fakeCtrl[saveFunc].calls.count()).toBe(0);
94
95 // first round of changes
96 element.find('.fieldA').val('eh?').trigger('change');
97 $rootScope.$digest();
98 element.find('.fieldB').val('bee').trigger('change');
99 $rootScope.$digest();
100 expect(model.fieldA).toBe('eh?');
101 expect(model.fieldB).toBe('bee');
102 expect($rootScope.myForm.$pristine).toBe(false);
103 expect(fakeCtrl[saveFunc].calls.count()).toBe(0);
104
105 // first autosave
106 wait(100);
107 expect($rootScope.myForm.$pristine).toBe(true);
108 expect(fakeCtrl[saveFunc].calls.count()).toBe(1);
109
110 // a little stretch of time with nothing happening
111 wait(100);
112 expect(fakeCtrl[saveFunc].calls.count()).toBe(1);
113
114 // second round of changes
115 element.find('.fieldA').val('ah').trigger('change');
116 $rootScope.$digest();
117 expect(model.fieldA).toBe('ah');
118
119 // second autosave
120 expect($rootScope.myForm.$pristine).toBe(false);
121 expect(fakeCtrl[saveFunc].calls.count()).toBe(1);
122 wait(100);
123 expect(fakeCtrl[saveFunc].calls.count()).toBe(2);
124 expect($rootScope.myForm.$pristine).toBe(true);
125
126 // a little stretch of time with nothing happening
127 wait(100);
128 expect(fakeCtrl[saveFunc].calls.count()).toBe(2);
129 });
130 });
131
132 it('does not save an invalid form', function() {
133 element = $compile('<form name="myForm" crm-autosave="fakeCtrl.doSave()" crm-autosave-model="model" crm-autosave-interval="{poll: 25, save: 50}"><input class="fieldA" ng-model="model.fieldA"><input class="fieldB" required ng-model="model.fieldB"></form>')($rootScope);
134 $rootScope.$digest();
135
136 // check that we load pristine data and don't do any saving
137 wait(100);
138 expect(element.find('.fieldA').val()).toBe('alpha');
139 expect(element.find('.fieldB').val()).toBe('beta');
140 expect($rootScope.myForm.$pristine).toBe(true);
141 expect(fakeCtrl.doSave.calls.count()).toBe(0);
142
143 // first round of changes - fieldB is invalid
144 element.find('.fieldA').val('eh?').trigger('change');
145 $rootScope.$digest();
146 element.find('.fieldB').val('').trigger('change');
147 $rootScope.$digest();
148 expect(model.fieldA).toBe('eh?');
149 expect(model.fieldB).toBeFalsy();
150 expect($rootScope.myForm.$pristine).toBe(false);
151 expect(fakeCtrl.doSave.calls.count()).toBe(0);
152
153 // first autosave declines to run
154 wait(100);
155 expect($rootScope.myForm.$pristine).toBe(false);
156 expect(fakeCtrl.doSave.calls.count()).toBe(0);
157
158 // second round of changes
159 element.find('.fieldB').val('bee').trigger('change');
160 $rootScope.$digest();
161 expect(model.fieldB).toBe('bee');
162
163 // second autosave
164 expect($rootScope.myForm.$pristine).toBe(false);
165 expect(fakeCtrl.doSave.calls.count()).toBe(0);
166 wait(100);
167 expect(fakeCtrl.doSave.calls.count()).toBe(1);
168 expect($rootScope.myForm.$pristine).toBe(true);
169
170 // a little stretch of time with nothing happening
171 wait(100);
172 expect(fakeCtrl.doSave.calls.count()).toBe(1);
173 });
174
175 it('defers saving new changes when a save is already pending', function() {
176 element = $compile('<form name="myForm" crm-autosave="fakeCtrl.doSaveSlowly()" crm-autosave-model="model" crm-autosave-interval="{poll: 25, save: 50}"><input class="fieldA" ng-model="model.fieldA"><input class="fieldB" ng-model="model.fieldB"></form>')($rootScope);
177 $rootScope.$digest();
178
179 // check that we load pristine data and don't do any saving
180 wait(100);
181 expect(element.find('.fieldA').val()).toBe('alpha');
182 expect(element.find('.fieldB').val()).toBe('beta');
183 expect($rootScope.myForm.$pristine).toBe(true);
184 expect(fakeCtrl.doSaveSlowly.calls.count()).toBe(0);
185
186 // first round of changes
187 element.find('.fieldA').val('eh?').trigger('change');
188 $rootScope.$digest();
189 expect(model.fieldA).toBe('eh?');
190 expect($rootScope.myForm.$pristine).toBe(false);
191 expect(fakeCtrl.doSaveSlowly.calls.count()).toBe(0);
192
193 // first autosave starts
194 wait(100);
195 expect(fakeCtrl.savingSlowly).toBe(true);
196 expect(fakeCtrl.doSaveSlowly.calls.count()).toBe(1);
197
198 // second round of changes; doesn't save yet
199 element.find('.fieldA').val('aleph').trigger('change');
200 $rootScope.$digest();
201 expect(model.fieldA).toBe('aleph');
202 expect(fakeCtrl.savingSlowly).toBe(true);
203 expect(fakeCtrl.doSaveSlowly.calls.count()).toBe(1);
204 wait(100);
205 expect(fakeCtrl.doSaveSlowly.calls.count()).toBe(1);
206
207 // second autosave starts and finishes
208 wait(2500, 5);
209 expect(fakeCtrl.savingSlowly).toBe(false);
210 expect(fakeCtrl.doSaveSlowly.calls.count()).toBe(2);
211 });
212 });
213 });