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