diff --git a/README.md b/README.md index ffb6a4a..b4112f9 100644 --- a/README.md +++ b/README.md @@ -201,3 +201,5 @@ Initial release! Pull requests are more than welcome - please add tests, which can be run by opening test/index.html. They can also be run from the command-line (requires [PhantomJS](http://phantomjs.org/)): $ ./run_tests.sh + +Please make sure the modifications pass [JSHint](http://jshint.com/). diff --git a/test/nested-model.js b/test/nested-model.js index 2306b7c..d7df932 100644 --- a/test/nested-model.js +++ b/test/nested-model.js @@ -378,6 +378,56 @@ $(document).ready(function() { sinon.assert.notCalled(changeNameMiddleInitial); }); + test("change event doesn't fire if validation fails on top level attribute", function() { + var change = sinon.spy(); + var changeName = sinon.spy(); + var changeNameFirst = sinon.spy(); + + doc.validate = function(attributes) { + if (attributes.gender.length > 1) { + return "Gender should be 'M' or 'F'"; + } + }; + + doc.bind('change', change); + doc.bind('change:name', changeName); + doc.bind('change:name.first', changeNameFirst); + + doc.set({'gender': 'Unknown'}); + + sinon.assert.notCalled(change); + sinon.assert.notCalled(changeName); + sinon.assert.notCalled(changeNameFirst); + }); + + test("change event doesn't fire if validation fails on deeply nested attribute", function() { + var change = sinon.spy(); + var changeName = sinon.spy(); + var changeNameMiddle = sinon.spy(); + var changeNameMiddleInitial = sinon.spy(); + + doc.validate = function(attributes) { + if (attributes.name.middle.initial.length > 1) { + return "Middle initial is too long"; + } + }; + + doc.bind('change', change); + doc.bind('change:name', changeName); + doc.bind('change:name.middle', changeNameMiddle); + doc.bind('change:name.middle.initial', changeNameMiddleInitial); + + doc.set({'name.middle': { + initial: 'ThisIsTooLong', + full: 'Lee' + }}); + + sinon.assert.notCalled(change); + sinon.assert.notCalled(changeName); + sinon.assert.notCalled(changeNameMiddle); + sinon.assert.notCalled(changeNameMiddleInitial); + }); + test("attribute change event receives new value", function() { doc.bind('change:name', function(model, newVal){ deepEqual(newVal, { @@ -617,12 +667,12 @@ $(document).ready(function() { test("#changedAttributes() should clear the nested attributes between change events with validation", function() { doc.validate = function(attributes) { - if (attributes.name.first.length > 15) { - return "First name is too long"; - } - }; + if (attributes.name.first.length > 15) { + return "First name is too long"; + } + }; - doc.set({'name.first': 'TooLongFirstName'}); + doc.set({'name.first': 'TooLongFirstName'}); doc.bind('change', function(){ deepEqual(this.changedAttributes(), { @@ -641,6 +691,7 @@ $(document).ready(function() { doc.set({'name.last': 'Dylan'}); }); + // ----- UNSET -------- test("#unset() top-level attribute", function() { @@ -733,6 +784,32 @@ $(document).ready(function() { equal(model, doc); }); + test("#add() on nested array fails if validation fails", function() { + var addAddresses = sinon.spy(); + + equal(doc.get('addresses').length, 2); + + doc.bind('add:addresses', addAddresses); + + doc.validate = function(attributes) { + for (var i = attributes.addresses.length - 1; i >= 0; i--) { + if (attributes.addresses[i].state.length > 2) { + return "Must use 2 letter state abbreviation"; + } + } + }; + + var attrs = { + city: 'Lincoln', + state: 'Nebraska' // Longer than 2 letters, validation should fail + }; + + doc.add('addresses', attrs); + + sinon.assert.notCalled(addAddresses); + equal(doc.get('addresses[2]'), undefined); + }); + // ----- REMOVE --------