~bac/juju-gui/trunkcopy

« back to all changes in this revision

Viewing changes to lib/yui/tests/app/tests/model-test.js

  • Committer: kapil.foss at gmail
  • Date: 2012-07-13 18:45:59 UTC
  • Revision ID: kapil.foss@gmail.com-20120713184559-2xl7be17egsrz0c9
reshape

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
YUI.add('model-test', function (Y) {
2
 
 
3
 
var ArrayAssert  = Y.ArrayAssert,
4
 
    Assert       = Y.Assert,
5
 
    ObjectAssert = Y.ObjectAssert,
6
 
 
7
 
    suite,
8
 
    modelSuite;
9
 
 
10
 
// -- Global Suite -------------------------------------------------------------
11
 
suite = Y.AppTestSuite || (Y.AppTestSuite = new Y.Test.Suite('App Framework'));
12
 
 
13
 
// -- Model Suite --------------------------------------------------------------
14
 
modelSuite = new Y.Test.Suite('Model');
15
 
 
16
 
// -- Model: Lifecycle ---------------------------------------------------------
17
 
modelSuite.add(new Y.Test.Case({
18
 
    name: 'Lifecycle',
19
 
 
20
 
    'destroy() should destroy the model instance': function () {
21
 
        var model = new Y.Model();
22
 
 
23
 
        model.sync = function () {
24
 
            Assert.fail('sync should not be called unless the model is being deleted');
25
 
        };
26
 
 
27
 
        Assert.isFalse(model.get('destroyed'));
28
 
        Assert.areSame(model, model.destroy(), 'destroy() should be chainable');
29
 
        Assert.isTrue(model.get('destroyed'));
30
 
    },
31
 
 
32
 
    'destroy() should call a callback if provided as the only arg': function () {
33
 
        var mock  = Y.Mock(),
34
 
            model = new Y.Model();
35
 
 
36
 
        Y.Mock.expect(mock, {
37
 
            method: 'callback',
38
 
            args  : []
39
 
        });
40
 
 
41
 
        model.destroy(mock.callback);
42
 
        Y.Mock.verify(mock);
43
 
    },
44
 
 
45
 
    'destroy() should call a callback if provided as the second arg': function () {
46
 
        var mock  = Y.Mock(),
47
 
            model = new Y.Model();
48
 
 
49
 
        Y.Mock.expect(mock, {
50
 
            method: 'callback',
51
 
            args  : []
52
 
        });
53
 
 
54
 
        model.destroy({}, mock.callback);
55
 
        Y.Mock.verify(mock);
56
 
    },
57
 
 
58
 
    'destroy() should delete the model if the `remove` option is truthy': function () {
59
 
        var calls   = 0,
60
 
            mock    = Y.Mock(),
61
 
            model   = new Y.Model();
62
 
 
63
 
        Y.Mock.expect(mock, {
64
 
            method: 'callback',
65
 
            args  : []
66
 
        });
67
 
 
68
 
        model.sync = function (action, options, callback) {
69
 
            calls += 1;
70
 
 
71
 
            Assert.areSame('delete', action, 'sync action should be "delete"');
72
 
            Assert.isObject(options, 'options should be an object');
73
 
            Assert.isTrue(options.remove, 'options.delete should be true');
74
 
            Assert.isFunction(callback, 'callback should be a function');
75
 
 
76
 
            callback();
77
 
        };
78
 
 
79
 
        model.destroy({remove: true}, mock.callback);
80
 
        Y.Mock.verify(mock);
81
 
    },
82
 
 
83
 
    'destroy() should remove the model from all lists': function () {
84
 
        var model     = new Y.Model(),
85
 
            listOne   = new Y.ModelList(),
86
 
            listTwo   = new Y.ModelList(),
87
 
            listThree = new Y.ModelList();
88
 
 
89
 
        listOne.add(model);
90
 
        listTwo.add(model);
91
 
        listThree.add(model);
92
 
 
93
 
        Assert.areSame(1, listOne.size(), 'model should be added to list one');
94
 
        Assert.areSame(1, listTwo.size(), 'model should be added to list two');
95
 
        Assert.areSame(1, listThree.size(), 'model should be added to list three');
96
 
 
97
 
        model.destroy();
98
 
 
99
 
        Assert.areSame(0, listOne.size(), 'model should be removed from list one');
100
 
        Assert.areSame(0, listTwo.size(), 'model should be removed from list two');
101
 
        Assert.areSame(0, listThree.size(), 'model should be removed from list three');
102
 
    }
103
 
}));
104
 
 
105
 
// -- Model: Attributes and Properties -----------------------------------------
106
 
modelSuite.add(new Y.Test.Case({
107
 
    name: 'Attributes and Properties',
108
 
 
109
 
    setUp: function () {
110
 
        this.TestModel = Y.Base.create('testModel', Y.Model, [], {
111
 
            idAttribute: 'customId'
112
 
        }, {
113
 
            ATTRS: {
114
 
                customId: {value: ''},
115
 
                foo: {value: ''}
116
 
            }
117
 
        });
118
 
    },
119
 
 
120
 
    tearDown: function () {
121
 
        delete this.TestModel;
122
 
    },
123
 
 
124
 
    'Attributes should be settable at instantiation time': function () {
125
 
        var model = new this.TestModel({foo: 'foo'});
126
 
        Assert.areSame('foo', model.get('foo'));
127
 
    },
128
 
 
129
 
    'Models should allow ad-hoc attributes': function () {
130
 
        var created = new Date(),
131
 
 
132
 
            model = new Y.Model({
133
 
                foo: 'foo',
134
 
                bar: {a: 'bar'},
135
 
                baz: ['baz'],
136
 
                quux: null,
137
 
                zero: 0,
138
 
                created: created
139
 
            });
140
 
 
141
 
        Assert.areSame('foo', model.get('foo'), 'ad-hoc foo attribute should be set');
142
 
        Assert.areSame('bar', model.get('bar.a'), 'ad-hoc bar attribute should be set');
143
 
        Assert.areSame('baz', model.get('baz')[0], 'ad-hoc baz attribute should be set');
144
 
        Assert.isNull(model.get('quux'), 'ad-hoc quux attribute should be set');
145
 
        Assert.areSame(0, model.get('zero'), 'ad-hoc zero attribute should be set');
146
 
        Assert.areSame(created, model.get('created'), 'ad-hoc created attribute should be set');
147
 
 
148
 
        ObjectAssert.ownsKeys(['foo', 'bar', 'baz', 'quux', 'zero', 'created'], model.getAttrs(), 'ad-hoc attributes should be returned by getAttrs()');
149
 
    },
150
 
 
151
 
    'Custom id attribute should be settable at instantiation time': function () {
152
 
        var model;
153
 
 
154
 
        // We need to set and get the id and customId attributes in various
155
 
        // orders to ensure there are no issues due to the attributes being
156
 
        // lazily added.
157
 
 
158
 
        model = new this.TestModel({customId: 'foo'});
159
 
        Assert.areSame('foo', model.get('customId'));
160
 
        Assert.areSame('foo', model.get('id'));
161
 
 
162
 
        model = new this.TestModel({customId: 'foo'});
163
 
        Assert.areSame('foo', model.get('id'));
164
 
        Assert.areSame('foo', model.get('customId'));
165
 
 
166
 
        model = new this.TestModel({id: 'foo'});
167
 
        Assert.areSame('foo', model.get('customId'));
168
 
        Assert.areSame('foo', model.get('id'));
169
 
 
170
 
        model = new this.TestModel({id: 'foo'});
171
 
        Assert.areSame('foo', model.get('id'));
172
 
        Assert.areSame('foo', model.get('customId'));
173
 
    },
174
 
 
175
 
    '`_isYUIModel` property should be true': function () {
176
 
        var model = new this.TestModel();
177
 
        Assert.isTrue(model._isYUIModel);
178
 
    },
179
 
 
180
 
    '`id` attribute should be an alias for the custom id attribute': function () {
181
 
        var calls = 0,
182
 
            model = new this.TestModel();
183
 
 
184
 
        model.on('change', function (e) {
185
 
            calls += 1;
186
 
 
187
 
            Assert.areSame('foo', e.changed.customId.newVal);
188
 
            Assert.areSame('foo', e.changed.id.newVal);
189
 
        });
190
 
 
191
 
        model.set('id', 'foo');
192
 
 
193
 
        Assert.areSame(1, calls);
194
 
    },
195
 
 
196
 
    '`changed` property should be a hash of attributes that have changed since last save() or load()': function () {
197
 
        var model = new this.TestModel();
198
 
 
199
 
        Assert.isObject(model.changed);
200
 
        ObjectAssert.ownsNoKeys(model.changed);
201
 
 
202
 
        model.set('foo', 'foo');
203
 
        Assert.areSame('foo', model.changed.foo);
204
 
 
205
 
        model.setAttrs({foo: 'bar', bar: 'baz'});
206
 
        ObjectAssert.areEqual({foo: 'bar', bar: 'baz'}, model.changed);
207
 
 
208
 
        model.save();
209
 
        ObjectAssert.ownsNoKeys(model.changed);
210
 
 
211
 
        model.set('foo', 'foo');
212
 
        model.load();
213
 
        ObjectAssert.ownsNoKeys(model.changed);
214
 
    },
215
 
 
216
 
    'clientId attribute should be automatically generated': function () {
217
 
        var model = new Y.Model();
218
 
 
219
 
        Assert.isString(model.get('clientId'));
220
 
        Assert.isTrue(!!model.get('clientId'));
221
 
    },
222
 
 
223
 
    '`lastChange` property should contain attributes that changed in the last `change` event': function () {
224
 
        var model = new this.TestModel();
225
 
 
226
 
        Assert.isObject(model.lastChange);
227
 
        ObjectAssert.ownsNoKeys(model.lastChange);
228
 
 
229
 
        model.set('foo', 'foo');
230
 
        Assert.areSame(1, Y.Object.size(model.lastChange));
231
 
        ObjectAssert.ownsKeys(['newVal', 'prevVal', 'src'], model.lastChange.foo);
232
 
        Assert.areSame('', model.lastChange.foo.prevVal);
233
 
        Assert.areSame('foo', model.lastChange.foo.newVal);
234
 
        Assert.isNull(model.lastChange.foo.src);
235
 
 
236
 
        model.set('bar', 'bar', {src: 'test'});
237
 
        Assert.areSame(1, Y.Object.size(model.lastChange));
238
 
        Assert.areSame('test', model.lastChange.bar.src);
239
 
 
240
 
        model.set('foo', 'bar', {silent: true});
241
 
        Assert.areSame(1, Y.Object.size(model.lastChange));
242
 
        Assert.areSame('bar', model.lastChange.foo.newVal);
243
 
    },
244
 
 
245
 
    '`lists` property should be an array of ModelList instances that contain this model': function () {
246
 
        var calls = 0,
247
 
            model = new this.TestModel(),
248
 
 
249
 
            lists = [
250
 
                new Y.ModelList({model: this.TestModel}),
251
 
                new Y.ModelList({model: this.TestModel})
252
 
            ];
253
 
 
254
 
        Assert.isArray(model.lists);
255
 
 
256
 
        function onChange() {
257
 
            calls += 1;
258
 
        }
259
 
 
260
 
        lists[0].on('*:change', onChange);
261
 
        lists[1].on('*:change', onChange);
262
 
 
263
 
        lists[0].add(model);
264
 
        lists[1].add(model);
265
 
 
266
 
        ArrayAssert.itemsAreSame(lists, model.lists);
267
 
 
268
 
        model.set('foo', 'foo');
269
 
 
270
 
        Assert.areSame(2, calls);
271
 
    }
272
 
}));
273
 
 
274
 
// -- Model: Methods -----------------------------------------------------------
275
 
modelSuite.add(new Y.Test.Case({
276
 
    name: 'Methods',
277
 
 
278
 
    setUp: function () {
279
 
        this.TestModel = Y.Base.create('testModel', Y.Model, [], {}, {
280
 
            ATTRS: {
281
 
                foo: {value: ''},
282
 
                bar: {value: ''}
283
 
            }
284
 
        });
285
 
    },
286
 
 
287
 
    tearDown: function () {
288
 
        delete this.TestModel;
289
 
    },
290
 
 
291
 
    'generateClientId() should generate a unique client id': function () {
292
 
        var model    = new this.TestModel(),
293
 
            firstId  = model.generateClientId(),
294
 
            secondId = model.generateClientId();
295
 
 
296
 
        Assert.isString(firstId);
297
 
        Assert.areNotSame(firstId, secondId);
298
 
        Assert.isTrue(firstId.indexOf(this.TestModel.NAME) === 0);
299
 
    },
300
 
 
301
 
    'getAsHTML() should return an HTML-escaped attribute value': function () {
302
 
        var value = '<div id="foo">hello!</div>',
303
 
            model = new this.TestModel({foo: value});
304
 
 
305
 
        Assert.areSame(Y.Escape.html(value), model.getAsHTML('foo'));
306
 
    },
307
 
 
308
 
    'getAsURL() should return a URL-encoded attribute value': function () {
309
 
        var value = 'foo & bar = baz',
310
 
            model = new this.TestModel({foo: value});
311
 
 
312
 
        Assert.areSame(encodeURIComponent(value), model.getAsURL('foo'));
313
 
    },
314
 
 
315
 
    'isModified() should return true if the model is new': function () {
316
 
        var model = new this.TestModel();
317
 
        Assert.isTrue(model.isModified());
318
 
 
319
 
        model = new this.TestModel({id: 'foo'});
320
 
        Assert.isFalse(model.isModified());
321
 
    },
322
 
 
323
 
    'isModified() should return true if the model has changed since it was last saved': function () {
324
 
        var model = new this.TestModel({id: 'foo'});
325
 
        Assert.isFalse(model.isModified());
326
 
 
327
 
        model.set('foo', 'bar');
328
 
        Assert.isTrue(model.isModified());
329
 
 
330
 
        model.save();
331
 
        Assert.isFalse(model.isModified());
332
 
    },
333
 
 
334
 
    'isNew() should return true if the model is new': function () {
335
 
        var model = new this.TestModel();
336
 
        Assert.isTrue(model.isNew());
337
 
 
338
 
        model = new this.TestModel({id: 'foo'});
339
 
        Assert.isFalse(model.isNew());
340
 
 
341
 
        model = new this.TestModel({id: 0});
342
 
        Assert.isFalse(model.isNew());
343
 
    },
344
 
 
345
 
    'load() should delegate to sync()': function () {
346
 
        var calls = 0,
347
 
            model = new this.TestModel(),
348
 
            opts  = {};
349
 
 
350
 
        model.sync = function (action, options, callback) {
351
 
            calls += 1;
352
 
 
353
 
            Assert.areSame('read', action);
354
 
            Assert.areSame(opts, options);
355
 
            Assert.isFunction(callback);
356
 
 
357
 
            callback();
358
 
        };
359
 
 
360
 
        model.load(opts);
361
 
        Assert.areSame(1, calls);
362
 
    },
363
 
 
364
 
    'load() should reset this.changed when loading succeeds': function () {
365
 
        var model = new this.TestModel();
366
 
 
367
 
        model.set('foo', 'bar');
368
 
        Assert.areSame(1, Y.Object.size(model.changed));
369
 
 
370
 
        model.load();
371
 
        Assert.areSame(0, Y.Object.size(model.changed));
372
 
    },
373
 
 
374
 
    'load() should be chainable and should call the callback if one was provided': function () {
375
 
        var calls = 0,
376
 
            model = new this.TestModel();
377
 
 
378
 
        Assert.areSame(model, model.load());
379
 
        Assert.areSame(model, model.load({}));
380
 
 
381
 
        Assert.areSame(model, model.load(function (err) {
382
 
            calls += 1;
383
 
            Assert.isUndefined(err);
384
 
        }));
385
 
 
386
 
        Assert.areSame(model, model.load({}, function () {
387
 
            calls += 1;
388
 
        }));
389
 
 
390
 
        Assert.areSame(2, calls);
391
 
    },
392
 
 
393
 
    'parse() should parse a JSON string and return an object': function () {
394
 
        var model    = new this.TestModel(),
395
 
            response = model.parse('{"foo": "bar"}');
396
 
 
397
 
        Assert.isObject(response);
398
 
        Assert.areSame('bar', response.foo);
399
 
    },
400
 
 
401
 
    'parse() should not try to parse non-strings': function () {
402
 
        var model  = new this.TestModel(),
403
 
            array  = ['foo', 'bar'],
404
 
            object = {foo: 'bar'};
405
 
 
406
 
        Assert.areSame(array, model.parse(array));
407
 
        Assert.areSame(object, model.parse(object));
408
 
    },
409
 
 
410
 
    'save() should delegate to sync()': function () {
411
 
        var calls = 0,
412
 
            model = new this.TestModel(),
413
 
            opts  = {};
414
 
 
415
 
        model.sync = function (action, options, callback) {
416
 
            calls += 1;
417
 
 
418
 
            Assert.areSame('create', action);
419
 
            Assert.areSame(opts, options);
420
 
            Assert.isFunction(callback);
421
 
 
422
 
            // Give the model an id so it will no longer be new.
423
 
            callback(null, {id: 'foo'});
424
 
        };
425
 
 
426
 
        model.save(opts);
427
 
 
428
 
        Assert.areSame('foo', model.get('id'), "model id should be updated after save");
429
 
 
430
 
        model.sync = function (action) {
431
 
            calls += 1;
432
 
            Assert.areSame('update', action);
433
 
        };
434
 
 
435
 
        model.save();
436
 
 
437
 
        Assert.areSame(2, calls);
438
 
    },
439
 
 
440
 
    'save() should reset this.changed when saving succeeds': function () {
441
 
        var model = new this.TestModel();
442
 
 
443
 
        model.set('foo', 'bar');
444
 
        Assert.areSame(1, Y.Object.size(model.changed));
445
 
 
446
 
        model.save();
447
 
        Assert.areSame(0, Y.Object.size(model.changed));
448
 
    },
449
 
 
450
 
    'save() should be chainable and should call the callback if one was provided': function () {
451
 
        var calls = 0,
452
 
            model = new this.TestModel();
453
 
 
454
 
        Assert.areSame(model, model.save());
455
 
        Assert.areSame(model, model.save({}));
456
 
 
457
 
        Assert.areSame(model, model.save(function (err) {
458
 
            calls += 1;
459
 
            Assert.isUndefined(err);
460
 
        }));
461
 
 
462
 
        Assert.areSame(model, model.save({}, function () {
463
 
            calls += 1;
464
 
        }));
465
 
 
466
 
        Assert.areSame(2, calls);
467
 
    },
468
 
 
469
 
    'set() should set the value of a single attribute': function () {
470
 
        var model = new this.TestModel();
471
 
 
472
 
        Assert.areSame('', model.get('foo'));
473
 
        Assert.areSame(model, model.set('foo', 'bar'), 'set() should be chainable');
474
 
        Assert.areSame('bar', model.get('foo'));
475
 
    },
476
 
 
477
 
    'setAttrs() should set the values of multiple attributes': function () {
478
 
        var model = new this.TestModel();
479
 
 
480
 
        Assert.areSame('', model.get('foo'));
481
 
        Assert.areSame('', model.get('bar'));
482
 
        Assert.areSame(model, model.setAttrs({foo: 'foo', bar: 'bar'}), 'setAttrs() should be chainable');
483
 
        Assert.areSame('foo', model.get('foo'));
484
 
        Assert.areSame('bar', model.get('bar'));
485
 
    },
486
 
 
487
 
    'sync() should just call the supplied callback by default': function () {
488
 
        var calls = 0,
489
 
            model = new this.TestModel();
490
 
 
491
 
        model.sync(function (err) {
492
 
            calls += 1;
493
 
            Assert.isUndefined(err);
494
 
        });
495
 
 
496
 
        Assert.areSame(1, calls);
497
 
    },
498
 
 
499
 
    "toJSON() should return a copy of the model's attributes, minus excluded ones": function () {
500
 
        var attrs = {id: 'id', foo: 'foo', bar: 'bar'},
501
 
            model = new this.TestModel(attrs),
502
 
            CustomTestModel, json;
503
 
 
504
 
        json = model.toJSON();
505
 
        Assert.areSame(3, Y.Object.size(json));
506
 
        ObjectAssert.ownsKeys(['id', 'foo', 'bar'], json);
507
 
        ObjectAssert.areEqual(attrs, json);
508
 
 
509
 
        // When there's a custom id attribute, the 'id' attribute should be
510
 
        // excluded.
511
 
        CustomTestModel = Y.Base.create('customTestModel', Y.Model, [], {
512
 
            idAttribute: 'customId'
513
 
        }, {
514
 
            ATTRS: {
515
 
                customId: {value: ''},
516
 
                foo     : {value: ''},
517
 
                bar     : {value: ''}
518
 
            }
519
 
        });
520
 
 
521
 
        attrs = {customId: 'id', foo: 'foo', bar: 'bar'};
522
 
        model = new CustomTestModel(attrs);
523
 
        json  = model.toJSON();
524
 
 
525
 
        Assert.areSame(3, Y.Object.size(json));
526
 
        ObjectAssert.ownsKeys(['customId', 'foo', 'bar'], json);
527
 
        ObjectAssert.areEqual(attrs, json);
528
 
    },
529
 
 
530
 
    'undo() should revert the previous change to the model': function () {
531
 
        var attrs = {id: 'id', foo: 'foo', bar: 'bar'},
532
 
            model = new this.TestModel(attrs);
533
 
 
534
 
        ObjectAssert.areEqual(attrs, model.toJSON());
535
 
 
536
 
        model.setAttrs({foo: 'moo', bar: 'quux'});
537
 
        ObjectAssert.areEqual({id: 'id', foo: 'moo', bar: 'quux'}, model.toJSON());
538
 
 
539
 
        Assert.areSame(model, model.undo(), 'undo() should be chainable');
540
 
        ObjectAssert.areEqual(attrs, model.toJSON());
541
 
    },
542
 
 
543
 
    'undo() should revert only the specified attributes when attributes are specified': function () {
544
 
        var model = new this.TestModel({id: 'id', foo: 'foo', bar: 'bar'});
545
 
 
546
 
        model.setAttrs({foo: 'moo', bar: 'quux'});
547
 
 
548
 
        model.undo(['foo']);
549
 
        ObjectAssert.areEqual({id: 'id', foo: 'foo', bar: 'quux'}, model.toJSON());
550
 
    },
551
 
 
552
 
    'undo() should pass options to setAttrs()': function () {
553
 
        var calls = 0,
554
 
            model = new this.TestModel({id: 'id', foo: 'foo', bar: 'bar'});
555
 
 
556
 
        model.setAttrs({foo: 'moo', bar: 'quux'});
557
 
 
558
 
        model.on('change', function (e) {
559
 
            calls += 1;
560
 
            Assert.areSame('test', e.changed.foo.src);
561
 
        });
562
 
 
563
 
        model.undo(null, {src: 'test'});
564
 
        Assert.areSame(1, calls);
565
 
    },
566
 
 
567
 
    'undo() should do nothing when there is no previous change to revert': function () {
568
 
        var model = new this.TestModel();
569
 
 
570
 
        model.on('change', function () {
571
 
            Assert.fail('`change` should not be called');
572
 
        });
573
 
 
574
 
        model.undo();
575
 
    },
576
 
 
577
 
    'validate() should only be called on save()': function () {
578
 
        var calls = 0,
579
 
            model = new this.TestModel();
580
 
 
581
 
        model.validate = function (attrs, callback) {
582
 
            calls += 1;
583
 
            Y.ObjectAssert.areEqual(model.toJSON(), attrs);
584
 
            callback();
585
 
        };
586
 
 
587
 
        model.set('foo', 'bar');
588
 
        model.set('foo', 'baz');
589
 
        model.save();
590
 
 
591
 
        Assert.areSame(1, calls);
592
 
    },
593
 
 
594
 
    'a validation failure should abort a save() call': function () {
595
 
        var calls         = 0,
596
 
            errors        = 0,
597
 
            model         = new this.TestModel(),
598
 
            saveCallbacks = 0;
599
 
 
600
 
        model.validate = function (attrs, callback) {
601
 
            calls += 1;
602
 
            callback('OMG invalid!');
603
 
        };
604
 
 
605
 
        model.sync = function () {
606
 
            Assert.fail('sync() should not be called on validation failure');
607
 
        };
608
 
 
609
 
        model.on('error', function (e) {
610
 
            errors += 1;
611
 
            Assert.areSame('OMG invalid!', e.error);
612
 
            Assert.areSame('validate', e.src);
613
 
        });
614
 
 
615
 
        model.save(function (err, res) {
616
 
            saveCallbacks += 1;
617
 
            Assert.areSame('OMG invalid!', err);
618
 
            Assert.isUndefined(res);
619
 
        });
620
 
 
621
 
        Assert.areSame(1, calls);
622
 
        Assert.areSame(1, saveCallbacks);
623
 
        Assert.areSame(1, errors);
624
 
    },
625
 
 
626
 
    'validate() should be backwards compatible with the 3.4.x synchronous style': function () {
627
 
        var errors = 0,
628
 
            saves  = 0,
629
 
            model  = new this.TestModel();
630
 
 
631
 
        model.on('error', function (e) {
632
 
            errors += 1;
633
 
 
634
 
        });
635
 
 
636
 
        model.on('save', function (e) {
637
 
            saves += 1;
638
 
        });
639
 
 
640
 
        model.validate = function (attrs) {
641
 
            if (attrs.foo !== 'bar') {
642
 
                return 'No no no!';
643
 
            }
644
 
        };
645
 
 
646
 
        model.set('foo', 'bar');
647
 
        model.save();
648
 
        Assert.areSame(0, errors);
649
 
        Assert.areSame(1, saves);
650
 
 
651
 
        model.set('foo', 'baz');
652
 
        model.save();
653
 
        Assert.areSame(1, errors);
654
 
        Assert.areSame(1, saves);
655
 
    }
656
 
}));
657
 
 
658
 
// -- Model: Events ------------------------------------------------------------
659
 
modelSuite.add(new Y.Test.Case({
660
 
    name: 'Events',
661
 
 
662
 
    setUp: function () {
663
 
        this.TestModel = Y.Base.create('testModel', Y.Model, [], {}, {
664
 
            ATTRS: {
665
 
                foo: {value: ''},
666
 
                bar: {value: ''},
667
 
                baz: {value: ''}
668
 
            }
669
 
        });
670
 
    },
671
 
 
672
 
    tearDown: function () {
673
 
        delete this.TestModel;
674
 
    },
675
 
 
676
 
    '`change` event should contain coalesced attribute changes': function () {
677
 
        var calls = 0,
678
 
            model = new this.TestModel();
679
 
 
680
 
        model.on('change', function (e) {
681
 
            calls += 1;
682
 
 
683
 
            ObjectAssert.ownsKeys(['foo', 'bar'], e.changed);
684
 
            Assert.areSame(2, Y.Object.size(e.changed));
685
 
            ObjectAssert.ownsKeys(['newVal', 'prevVal', 'src'], e.changed.foo);
686
 
            ObjectAssert.ownsKeys(['newVal', 'prevVal', 'src'], e.changed.bar);
687
 
            Assert.areSame('foo', e.changed.foo.newVal);
688
 
            Assert.areSame('', e.changed.foo.prevVal);
689
 
            Assert.areSame('bar', e.changed.bar.newVal);
690
 
            Assert.areSame('', e.changed.bar.prevVal);
691
 
            Assert.areSame('test', e.changed.foo.src);
692
 
            Assert.areSame('test', e.changed.bar.src);
693
 
        });
694
 
 
695
 
        model.setAttrs({
696
 
            foo: 'foo',
697
 
            bar: 'bar'
698
 
        }, {src: 'test'});
699
 
 
700
 
        Assert.areSame(1, calls);
701
 
    },
702
 
 
703
 
    '`change` event should not fire when the _silent_ option is truthy': function () {
704
 
        var model = new this.TestModel();
705
 
 
706
 
        model.on('change', function (e) {
707
 
            Assert.fail('`change` should not fire');
708
 
        });
709
 
 
710
 
        model.set('foo', 'bar', {silent: true});
711
 
        model.setAttrs({bar: 'baz'}, {silent: true});
712
 
    },
713
 
 
714
 
    '`change` event facade should contain options passed to set()/setAttrs()': function () {
715
 
        var calls = 0,
716
 
            model = new this.TestModel();
717
 
 
718
 
        model.on('change', function (e) {
719
 
            calls += 1;
720
 
 
721
 
            Assert.areSame(e.src, 'test');
722
 
            Assert.areSame(e.foo, 'bar');
723
 
        });
724
 
 
725
 
        model.setAttrs({
726
 
            foo: 'foo',
727
 
            bar: 'bar'
728
 
        }, {src: 'test', foo: 'bar'});
729
 
 
730
 
        model.set('foo', 'bar', {
731
 
            src: 'test',
732
 
            foo: 'bar'
733
 
        });
734
 
 
735
 
        Assert.areSame(2, calls);
736
 
    },
737
 
 
738
 
    '`error` event should fire when validation fails': function () {
739
 
        var calls = 0,
740
 
            model = new this.TestModel();
741
 
 
742
 
        model.validate = function (hash, callback) {
743
 
            callback('ERROR. ERROR. DOES NOT COMPUTE.');
744
 
        };
745
 
 
746
 
        model.on('error', function (e) {
747
 
            calls += 1;
748
 
 
749
 
            Assert.areSame('validate', e.src);
750
 
            ObjectAssert.ownsKey('foo', e.attributes);
751
 
            Assert.areSame('bar', e.attributes.foo);
752
 
            Assert.areSame('ERROR. ERROR. DOES NOT COMPUTE.', e.error);
753
 
        });
754
 
 
755
 
        model.set('foo', 'bar');
756
 
        model.save();
757
 
 
758
 
        Assert.areSame(1, calls);
759
 
    },
760
 
 
761
 
    '`error` event should fire when parsing fails': function () {
762
 
        var calls = 0,
763
 
            model = new this.TestModel();
764
 
 
765
 
        model.on('error', function (e) {
766
 
            calls += 1;
767
 
 
768
 
            Assert.areSame('parse', e.src);
769
 
            Y.assert(e.error instanceof Error);
770
 
            Assert.areSame('moo', e.response);
771
 
        });
772
 
 
773
 
        model.parse('moo');
774
 
 
775
 
        Assert.areSame(1, calls);
776
 
    },
777
 
 
778
 
    '`error` event should fire when a load operation fails': function () {
779
 
        var calls = 0,
780
 
            model = new this.TestModel();
781
 
 
782
 
        model.on('error', function (e) {
783
 
            calls += 1;
784
 
 
785
 
            Assert.areSame('load', e.src);
786
 
            Assert.areSame('foo', e.error);
787
 
            Assert.areSame('{"error": true}', e.response);
788
 
            Assert.isObject(e.options);
789
 
        });
790
 
 
791
 
        model.sync = function (action, options, callback) {
792
 
            callback('foo', '{"error": true}');
793
 
        };
794
 
 
795
 
        model.load();
796
 
 
797
 
        Assert.areSame(1, calls);
798
 
    },
799
 
 
800
 
    '`error` event should fire when a save operation fails': function () {
801
 
        var calls = 0,
802
 
            model = new this.TestModel();
803
 
 
804
 
        model.on('error', function (e) {
805
 
            calls += 1;
806
 
 
807
 
            Assert.areSame('save', e.src);
808
 
            Assert.areSame('foo', e.error);
809
 
            Assert.areSame('{"error": true}', e.response);
810
 
            Assert.isObject(e.options);
811
 
        });
812
 
 
813
 
        model.sync = function (action, options, callback) {
814
 
            callback('foo', '{"error": true}');
815
 
        };
816
 
 
817
 
        model.save();
818
 
 
819
 
        Assert.areSame(1, calls);
820
 
    },
821
 
 
822
 
    '`load` event should fire after a successful load operation': function () {
823
 
        var calls = 0,
824
 
            model = new this.TestModel();
825
 
 
826
 
        model.on('load', function (e) {
827
 
            calls += 1;
828
 
 
829
 
            Assert.areSame('{"foo": "bar"}', e.response);
830
 
            Assert.isObject(e.options);
831
 
            Assert.isObject(e.parsed);
832
 
            Assert.areSame('bar', e.parsed.foo);
833
 
            Assert.areSame('bar', model.get('foo'), 'load event should fire after attribute changes are applied');
834
 
        });
835
 
 
836
 
        model.sync = function (action, options, callback) {
837
 
            callback(null, '{"foo": "bar"}');
838
 
        };
839
 
 
840
 
        model.load(function () {
841
 
            Assert.areSame(1, calls, 'load event should fire before the callback runs');
842
 
        });
843
 
 
844
 
        Assert.areSame(1, calls, 'load event never fired');
845
 
    },
846
 
 
847
 
    '`save` event should fire after a successful save operation': function () {
848
 
        var calls = 0,
849
 
            model = new this.TestModel();
850
 
 
851
 
        model.on('save', function (e) {
852
 
            calls += 1;
853
 
 
854
 
            Assert.areSame('{"foo": "bar"}', e.response);
855
 
            Assert.isObject(e.options);
856
 
            Assert.isObject(e.parsed);
857
 
            Assert.areSame('bar', e.parsed.foo);
858
 
            Assert.areSame('bar', model.get('foo'), 'save event should fire after attribute changes are applied');
859
 
        });
860
 
 
861
 
        model.sync = function (action, options, callback) {
862
 
            callback(null, '{"foo": "bar"}');
863
 
        };
864
 
 
865
 
        model.save(function () {
866
 
            Assert.areSame(1, calls, 'save event should fire before the callback runs');
867
 
        });
868
 
 
869
 
        Assert.areSame(1, calls, 'save event never fired');
870
 
    }
871
 
}));
872
 
 
873
 
suite.add(modelSuite);
874
 
 
875
 
}, '@VERSION@', {
876
 
    requires: ['model', 'model-list', 'test']
877
 
});