3a21b2ec by Adam Heath

Add 100% test coverage, and fix the few bugs that were found.

1 parent 1b9359a4
...@@ -22,7 +22,7 @@ define( ...@@ -22,7 +22,7 @@ define(
22 var validation = _.extend({}, oldValidation); 22 var validation = _.extend({}, oldValidation);
23 var found; 23 var found;
24 var f = function(value) { 24 var f = function(value) {
25 if (value === validateNestedValue) { 25 if (typeof value === 'object' && value.fn === validateNestedValue) {
26 found = true; 26 found = true;
27 } 27 }
28 }; 28 };
...@@ -44,7 +44,7 @@ define( ...@@ -44,7 +44,7 @@ define(
44 found = false; 44 found = false;
45 _.each(validators, f); 45 _.each(validators, f);
46 if (!found) { 46 if (!found) {
47 validators.push(validateNestedValue); 47 validators.push({fn: validateNestedValue});
48 } 48 }
49 } 49 }
50 } 50 }
...@@ -57,21 +57,21 @@ define( ...@@ -57,21 +57,21 @@ define(
57 modelClass.prototype[methodName] = function() { 57 modelClass.prototype[methodName] = function() {
58 var oldValidation = updateValidation(this); 58 var oldValidation = updateValidation(this);
59 try { 59 try {
60 if (originalMethod) {
61 return originalMethod.apply(this, arguments); 60 return originalMethod.apply(this, arguments);
62 } else {
63 return modelClass.__super__[methodName].apply(this, arguments);
64 }
65 } finally { 61 } finally {
66 this.validation = oldValidation; 62 this.validation = oldValidation;
67 } 63 }
68 }; 64 };
65 return modelClass;
69 } 66 }
70 67
71 function wrapSetFunction(modelClass) { 68 function wrapSetFunction(modelClass) {
72 var originalMethod = modelClass.prototype.set; 69 var originalMethod = modelClass.prototype.set;
73 modelClass.prototype.set = function(key, val, options) { 70 modelClass.prototype.set = function(key, val, options) {
74 var attr, attrs, curVal, nestedOptions, newVal; 71 var attr, attrs, curVal, nestedOptions, newVal;
72 if (typeof(key) === 'undefined') {
73 return this;
74 }
75 if (key === null) { 75 if (key === null) {
76 return this; 76 return this;
77 } 77 }
...@@ -93,23 +93,15 @@ define( ...@@ -93,23 +93,15 @@ define(
93 } 93 }
94 } 94 }
95 } 95 }
96 if (originalMethod) {
97 return originalMethod.call(this, attrs, options); 96 return originalMethod.call(this, attrs, options);
98 } else {
99 return modelClass.__super__.set.call(this, attrs, options);
100 }
101 }; 97 };
98 return modelClass;
102 } 99 }
103 100
104 function wrapToJSONFunction(modelClass) { 101 function wrapToJSONFunction(modelClass) {
105 var originalMethod = modelClass.prototype.toJSON; 102 var originalMethod = modelClass.prototype.toJSON;
106 modelClass.prototype.toJSON = function(options) { 103 modelClass.prototype.toJSON = function(options) {
107 var result; 104 var result = originalMethod.apply(this, arguments);
108 if (originalMethod) {
109 result = originalMethod.apply(this, arguments);
110 } else {
111 result = modelClass.__super__.toJSON.apply(this, arguments);
112 }
113 if (options && options.deep) { 105 if (options && options.deep) {
114 _.each(result, function(value, key) { 106 _.each(result, function(value, key) {
115 if (value instanceof Backbone.Model) { 107 if (value instanceof Backbone.Model) {
...@@ -119,6 +111,7 @@ define( ...@@ -119,6 +111,7 @@ define(
119 } 111 }
120 return result; 112 return result;
121 }; 113 };
114 return modelClass;
122 } 115 }
123 116
124 var NestedModels = { 117 var NestedModels = {
...@@ -128,12 +121,17 @@ define( ...@@ -128,12 +121,17 @@ define(
128 wrapToJSONFunction: wrapToJSONFunction, 121 wrapToJSONFunction: wrapToJSONFunction,
129 wrapValidationFunction: wrapValidationFunction, 122 wrapValidationFunction: wrapValidationFunction,
130 123
124 wrapValidationFunctions: function(modelClass) {
125 modelClass = wrapValidationFunction(modelClass, 'isValid');
126 modelClass = wrapValidationFunction(modelClass, 'validate');
127 modelClass = wrapValidationFunction(modelClass, 'preValidate');
128 return modelClass;
129 },
130
131 mixin: function(modelClass) { 131 mixin: function(modelClass) {
132 wrapSetFunction(modelClass); 132 modelClass = NestedModels.wrapSetFunction(modelClass);
133 wrapToJSONFunction(modelClass); 133 modelClass = NestedModels.wrapToJSONFunction(modelClass);
134 wrapValidationFunction(modelClass, 'isValid'); 134 modelClass = NestedModels.wrapValidationFunctions(modelClass);
135 wrapValidationFunction(modelClass, 'validate');
136 wrapValidationFunction(modelClass, 'preValidate');
137 return modelClass; 135 return modelClass;
138 }, 136 },
139 }; 137 };
......
1 define(function(require) {
2 'use strict';
3
4 var NestedModels = require('backbone-nested-models');
5 var _ = require('underscore');
6 var Backbone = require('backbone');
7 require('backbone-validation');
8
9 describe('NestedModels', function() {
10 it('exists', function() {
11 expect(NestedModels).toBeTruthy();
12 });
13 });
14 describe('NestedModels', function() {
15 var Top, TopWithMethods, Nested;
16 beforeEach(function() {
17 Nested = Backbone.Model.extend({
18 defaults: {
19 address1: null,
20 countryGeoId: null,
21 },
22 validation: {
23 address1: [
24 {msg: 'ADDRESS1 is REQUIRED', required: true},
25 ],
26 countryGeoId: [
27 {msg: 'countryGeoId is not in the given set', oneOf: ['USA', 'CAN', 'RUS']},
28 ],
29 },
30 });
31 Top = Backbone.Model.extend({
32 defaults: function() {
33 return {
34 nested: new Nested(),
35 name: null,
36 };
37 },
38 validation: {
39 name: [
40 {msg: 'name IS required', required: true},
41 ],
42 },
43 });
44 _.extend(Backbone.Model.prototype, Backbone.Validation.mixin);
45 function incrCount(self, name) {
46 if (!self.counts) {
47 self.counts = {};
48 }
49 if (!self.counts[name]) {
50 self.counts[name] = 0;
51 }
52 self.counts[name]++;
53 }
54 TopWithMethods = Backbone.Model.extend({
55 defaults: function() {
56 return {
57 nested: new Nested(),
58 name: null,
59 };
60 },
61 validation: {
62 name: [
63 {msg: 'name IS required', required: true},
64 ],
65 },
66 set: function() {
67 incrCount(this, 'set');
68 return TopWithMethods.__super__.set.apply(this, arguments);
69 },
70 toJSON: function() {
71 incrCount(this, 'toJSON');
72 return TopWithMethods.__super__.toJSON.apply(this, arguments);
73 },
74 validate: function() {
75 incrCount(this, 'validate');
76 return TopWithMethods.__super__.validate.apply(this, arguments);
77 },
78 preValidate: function() {
79 incrCount(this, 'preValidate');
80 return TopWithMethods.__super__.preValidate.apply(this, arguments);
81 },
82 isValid: function() {
83 incrCount(this, 'isValid');
84 return TopWithMethods.__super__.isValid.apply(this, arguments);
85 },
86 });
87 });
88 function testSet(Top, checkCounts) {
89 function extractCids(top) {
90 return {
91 top: top.cid,
92 nested: top.get('nested').cid,
93 };
94 }
95
96 var result, top;
97 top = new Top();
98 var cidsOriginal = extractCids(top);
99 expect(cidsOriginal.top).not.toEqual(cidsOriginal.nested);
100 if (checkCounts) {
101 expect(top.counts).toEqual({set: 1});
102 } else {
103 expect(top.counts).toBeUndefined();
104 }
105
106 result = top.set();
107 expect(result).toBe(top);
108 var cidsNoargs = extractCids(top);
109 expect(cidsOriginal.top).toEqual(cidsNoargs.top);
110 expect(cidsOriginal.nested).toEqual(cidsNoargs.nested);
111 if (checkCounts) {
112 expect(top.counts).toEqual({set: 1});
113 } else {
114 expect(top.counts).toBeUndefined();
115 }
116
117 result = top.set(null);
118 expect(result).toBe(top);
119 var cidsNullkey = extractCids(top);
120 expect(cidsOriginal.top).toEqual(cidsNullkey.top);
121 expect(cidsOriginal.nested).toEqual(cidsNullkey.nested);
122 if (checkCounts) {
123 expect(top.counts).toEqual({set: 1});
124 } else {
125 expect(top.counts).toBeUndefined();
126 }
127
128 result = top.set({name: 'Name', nested: new Nested({address1: '1234 Main St.'})});
129 expect(result).toBe(top);
130 var cidsNomerge = extractCids(top);
131 expect(cidsOriginal.top).toEqual(cidsNomerge.top);
132 expect(cidsOriginal.nested).not.toEqual(cidsNomerge.nested);
133 expect(top.get('name')).toEqual('Name');
134 expect(top.get('nested').get('address1')).toEqual('1234 Main St.');
135 if (checkCounts) {
136 expect(top.counts).toEqual({set: 2});
137 } else {
138 expect(top.counts).toBeUndefined();
139 }
140
141 result = top.set({name: 'NAME', nested: new Nested({address1: '4321 Main St.'})}, {merge: true});
142 expect(result).toBe(top);
143 var cidsMerge = extractCids(top);
144 expect(cidsNomerge.top).toEqual(cidsMerge.top);
145 expect(cidsNomerge.nested).toEqual(cidsMerge.nested);
146 expect(top.get('name')).toEqual('NAME');
147 expect(top.get('nested').get('address1')).toEqual('4321 Main St.');
148 if (checkCounts) {
149 expect(top.counts).toEqual({set: 3});
150 } else {
151 expect(top.counts).toBeUndefined();
152 }
153
154 result = top.set('name', 'value');
155 expect(result).toBe(top);
156 var cidsSetname = extractCids(top);
157 expect(cidsNomerge.top).toEqual(cidsSetname.top);
158 expect(cidsNomerge.nested).toEqual(cidsSetname.nested);
159 expect(top.get('name')).toEqual('value');
160 expect(top.get('nested').get('address1')).toEqual('4321 Main St.');
161 if (checkCounts) {
162 expect(top.counts).toEqual({set: 4});
163 } else {
164 expect(top.counts).toBeUndefined();
165 }
166
167 result = top.set('nested', new Nested({address1: '1234 Main St.'}));
168 expect(result).toBe(top);
169 var cidsSetnested = extractCids(top);
170 expect(cidsSetname.top).toEqual(cidsSetnested.top);
171 expect(cidsSetname.nested).not.toEqual(cidsSetnested.nested);
172 expect(top.get('name')).toEqual('value');
173 expect(top.get('nested').get('address1')).toEqual('1234 Main St.');
174 if (checkCounts) {
175 expect(top.counts).toEqual({set: 5});
176 } else {
177 expect(top.counts).toBeUndefined();
178 }
179
180 result = top.set('nested', new Nested({address1: '4321 Main St.'}), {merge: true});
181 expect(result).toBe(top);
182 var cidsSetnestedmerge = extractCids(top);
183 expect(cidsSetnested.top).toEqual(cidsSetnestedmerge.top);
184 expect(cidsSetnested.nested).toEqual(cidsSetnestedmerge.nested);
185 expect(top.get('name')).toEqual('value');
186 expect(top.get('nested').get('address1')).toEqual('4321 Main St.');
187 if (checkCounts) {
188 expect(top.counts).toEqual({set: 6});
189 } else {
190 expect(top.counts).toBeUndefined();
191 }
192 }
193 it('set-mixin-plain', function() {
194 testSet.call(this, NestedModels.mixin(Top), false);
195 });
196 it('set-mixin-methods', function() {
197 testSet.call(this, NestedModels.mixin(TopWithMethods), true);
198 });
199 it('set-wrap-plain', function() {
200 expect(NestedModels.wrapSetFunction(Top)).toBe(Top);
201 testSet.call(this, Top, false);
202 });
203 it('set-wrap-methods', function() {
204 expect(NestedModels.wrapSetFunction(TopWithMethods)).toBe(TopWithMethods);
205 testSet.call(this, TopWithMethods, true);
206 });
207 function testToJSON(Top, checkCounts) {
208 var result, top, rKeys;
209 top = new Top();
210 if (checkCounts) {
211 expect(top.counts).toEqual({set: 1});
212 } else {
213 expect(top.counts).toBeUndefined();
214 }
215
216 result = top.toJSON();
217 rKeys = _.keys(result).sort();
218 expect(rKeys).toEqual(['name', 'nested']);
219 expect(result.name).toBeNull();
220 expect(result.nested).not.toBeNull();
221 expect(result.nested).toEqual(jasmine.any(Backbone.Model));
222 if (checkCounts) {
223 expect(top.counts).toEqual({set: 1, toJSON: 1});
224 } else {
225 expect(top.counts).toBeUndefined();
226 }
227
228 result = top.toJSON({deep: true});
229 rKeys = _.keys(result).sort();
230 expect(rKeys).toEqual(['name', 'nested']);
231 expect(result.name).toBeNull();
232 expect(result.nested).not.toBeNull();
233 expect(result.nested).not.toEqual(jasmine.any(Backbone.Model));
234 if (checkCounts) {
235 expect(top.counts).toEqual({set: 1, toJSON: 2});
236 } else {
237 expect(top.counts).toBeUndefined();
238 }
239 }
240 it('toJSON-mixin-plain', function() {
241 testToJSON.call(this, NestedModels.mixin(Top), false);
242 });
243 it('toJSON-mixin-methods', function() {
244 testToJSON.call(this, NestedModels.mixin(TopWithMethods), true);
245 });
246 it('toJSON-wrap-plain', function() {
247 expect(NestedModels.wrapToJSONFunction(Top)).toBe(Top);
248 testToJSON.call(this, Top, false);
249 });
250 it('toJSON-wrap-methods', function() {
251 expect(NestedModels.wrapToJSONFunction(TopWithMethods)).toBe(TopWithMethods);
252 testToJSON.call(this, TopWithMethods, true);
253 });
254 function testValidation(Top, checkCounts) {
255 var result, top, rKeys;
256 top = new Top();
257 if (checkCounts) {
258 expect(top.counts).toEqual({set: 1});
259 } else {
260 expect(top.counts).toBeUndefined();
261 }
262
263 expect(top.isValid()).toBeUndefined();
264 result = top.validate();
265 expect(result).not.toBeNull();
266 rKeys = _.keys(result).sort();
267 expect(rKeys).toEqual(['name', 'nested']);
268 expect(result.name).toEqual(jasmine.any(String));
269 expect(result.nested).toEqual(jasmine.any(String));
270 expect(top.isValid()).toBe(false);
271 if (checkCounts) {
272 expect(top.counts).toEqual({set: 1, isValid: 2, validate: 1});
273 } else {
274 expect(top.counts).toBeUndefined();
275 }
276
277 top.validation.nested = [
278 {fn: function() { return true; }, msg: 'return-true'},
279 ];
280 result = top.validate();
281 expect(result).not.toBeNull();
282 rKeys = _.keys(result).sort();
283 expect(rKeys).toEqual(['name', 'nested']);
284 expect(result.name).toEqual('name IS required');
285 expect(result.nested).toEqual('return-true');
286 if (checkCounts) {
287 expect(top.counts).toEqual({set: 1, isValid: 2, validate: 2});
288 } else {
289 expect(top.counts).toBeUndefined();
290 }
291
292 top.validation.nested = [
293 {fn: function() { return false; }, msg: 'return-false'},
294 ];
295 result = top.validate();
296 expect(result).not.toBeNull();
297 rKeys = _.keys(result).sort();
298 expect(rKeys).toEqual(['name']);
299 expect(result.name).toEqual('name IS required');
300 if (checkCounts) {
301 expect(top.counts).toEqual({set: 1, isValid: 2, validate: 3});
302 } else {
303 expect(top.counts).toBeUndefined();
304 }
305
306 top.validation.nested = [
307 {fn: function() { return null; }, msg: 'return-null'},
308 ];
309 result = top.validate();
310 expect(result).not.toBeNull();
311 rKeys = _.keys(result).sort();
312 expect(rKeys).toEqual(['name', 'nested']);
313 expect(result.name).toEqual('name IS required');
314 expect(result.nested).toEqual(jasmine.any(String));
315 if (checkCounts) {
316 expect(top.counts).toEqual({set: 1, isValid: 2, validate: 4});
317 } else {
318 expect(top.counts).toBeUndefined();
319 }
320
321 top.get('nested').set('address1', '1234 Main St.');
322 result = top.validate();
323 expect(result).not.toBeNull();
324 rKeys = _.keys(result).sort();
325 expect(rKeys).toEqual(['name', 'nested']);
326 expect(result.name).toEqual('name IS required');
327 expect(result.nested).toEqual(jasmine.any(String));
328 if (checkCounts) {
329 expect(top.counts).toEqual({set: 1, isValid: 2, validate: 5});
330 } else {
331 expect(top.counts).toBeUndefined();
332 }
333
334 top.get('nested').set('countryGeoId', 'foobar');
335 result = top.validate();
336 expect(result).not.toBeNull();
337 rKeys = _.keys(result).sort();
338 expect(rKeys).toEqual(['name', 'nested']);
339 expect(result.name).toEqual('name IS required');
340 expect(result.nested).toEqual(jasmine.any(String));
341 if (checkCounts) {
342 expect(top.counts).toEqual({set: 1, isValid: 2, validate: 6});
343 } else {
344 expect(top.counts).toBeUndefined();
345 }
346
347 top.get('nested').set('countryGeoId', 'USA');
348 result = top.validate();
349 expect(result).not.toBeNull();
350 rKeys = _.keys(result).sort();
351 expect(rKeys).toEqual(['name']);
352 expect(result.name).toEqual('name IS required');
353 if (checkCounts) {
354 expect(top.counts).toEqual({set: 1, isValid: 2, validate: 7});
355 } else {
356 expect(top.counts).toBeUndefined();
357 }
358
359 top.get('nested').clear();
360 top.validation.nested = {fn: NestedModels.validateNestedValue, msg: 'installed-directly'};
361 result = top.validate();
362 expect(result).not.toBeNull();
363 rKeys = _.keys(result).sort();
364 expect(rKeys).toEqual(['name', 'nested']);
365 expect(result.name).toEqual('name IS required');
366 expect(result.nested).toEqual('installed-directly');
367 if (checkCounts) {
368 expect(top.counts).toEqual({set: 1, isValid: 2, validate: 8});
369 } else {
370 expect(top.counts).toBeUndefined();
371 }
372
373 result = top.preValidate('name', null);
374 expect(result).toEqual(jasmine.any(String));
375 expect(top.get('name')).toBeNull();
376 if (checkCounts) {
377 expect(top.counts).toEqual({set: 1, isValid: 2, validate: 8, preValidate: 1});
378 } else {
379 expect(top.counts).toBeUndefined();
380 }
381
382 result = top.preValidate('name', 'NAME');
383 expect(result).toEqual('');
384 expect(top.get('name')).toBeNull();
385 if (checkCounts) {
386 expect(top.counts).toEqual({set: 1, isValid: 2, validate: 8, preValidate: 2});
387 } else {
388 expect(top.counts).toBeUndefined();
389 }
390 }
391 it('validation-mixin-plain', function() {
392 testValidation.call(this, NestedModels.mixin(Top), false);
393 });
394 it('validation-mixin-methods', function() {
395 testValidation.call(this, NestedModels.mixin(TopWithMethods), true);
396 });
397 it('validation-wrap-plain', function() {
398 expect(NestedModels.wrapValidationFunctions(Top)).toBe(Top);
399 testValidation.call(this, Top, false);
400 });
401 it('validation-mixin-methods', function() {
402 expect(NestedModels.wrapValidationFunctions(TopWithMethods)).toBe(TopWithMethods);
403 testValidation.call(this, TopWithMethods, true);
404 });
405 });
406 });