// ========================================================================== // Project: SproutCore
- JavaScript Application Framework // Copyright: ©2006-2011 Apple Inc. and contributors. // License: Licensed under MIT license (see license.js) // ========================================================================== /*globals module, ok, equals, same, test */
// NOTE: The test below are based on the Data Hashes state chart. This models // the “commit” event in the NestedStore portion of the diagram.
var parent, store, child, storeKey, json, args; module(“SC.NestedStore#commitChanges”, {
setup: function() { SC.RunLoop.begin(); parent = SC.Store.create(); json = { string: "string", number: 23, bool: YES }; args = []; storeKey = SC.Store.generateStoreKey(); store = parent.chain(); // create nested store child = store.chain(); // test multiple levels deep // override commitChangesFromNestedStore() so we can ensure it is called // save call history for later evaluation parent.commitChangesFromNestedStore = child.commitChangesFromNestedStore = store.commitChangesFromNestedStore = function(store, changes, force) { args.push({ target: this, store: store, changes: changes, force: force }); }; SC.RunLoop.end(); }
});
// .….….….….….….….….….….….….….….. // BASIC STATE TRANSITIONS //
function testStateTransition(shouldIncludeStoreKey, shouldCallParent) {
// attempt to commit equals(store.commitChanges(), store, 'should return receiver'); // verify result equals(store.storeKeyEditState(storeKey), SC.Store.INHERITED, 'data edit state'); if (shouldCallParent === NO) { ok(!args || args.length===0, 'should not call commitChangesFromNestedStore'); } else { equals(args.length, 1, 'should have called commitChangesFromNestedStore'); var opts = args[0] || {}; // avoid exceptions equals(opts.target, parent, 'should have called on parent store'); // verify if changes passed to callback included storeKey var changes = opts.changes; var didInclude = changes && changes.contains(storeKey); if (shouldIncludeStoreKey) { ok(didInclude, 'passed set of changes should include storeKey'); } else { ok(!didInclude, 'passed set of changes should NOT include storeKey'); } } equals(store.get('hasChanges'), NO, 'hasChanges should be cleared'); ok(!store.chainedChanges || store.chainedChanges.length===0, 'should have empty chainedChanges set');
}
test(“state = INHERITED”, function() {
// write in some data to parent parent.writeDataHash(storeKey, json); // check preconditions equals(store.storeKeyEditState(storeKey), SC.Store.INHERITED, 'precond - data edit state'); testStateTransition(NO, NO);
});
test(“state = LOCKED”, function() {
// write in some data to parent parent.writeDataHash(storeKey, json); parent.editables = null ; // manually force to lock state store.readDataHash(storeKey); // check preconditions equals(store.storeKeyEditState(storeKey), SC.Store.LOCKED, 'precond - data edit state'); ok(!store.chainedChanges || !store.chainedChanges.contains(storeKey), 'locked record should not be in chainedChanges set'); testStateTransition(NO, NO);
});
test(“state = EDITABLE”, function() {
// write in some data to parent store.writeDataHash(storeKey, json); store.dataHashDidChange(storeKey); // check preconditions equals(store.storeKeyEditState(storeKey), SC.Store.EDITABLE, 'precond - data edit state'); ok(store.chainedChanges && store.chainedChanges.contains(storeKey), 'editable record should be in chainedChanges set'); testStateTransition(YES, YES);
});
// .….….….….….….….….….….….….….….. // SPECIAL CASES //
test(“commiting a changed record should immediately notify outstanding records in parent store”, function() {
var Rec = SC.Record.extend({ fooCnt: 0, fooDidChange: function() { this.fooCnt++; }.observes('foo'), statusCnt: 0, statusDidChange: function() { this.statusCnt++; }.observes('status'), reset: function() { this.fooCnt = this.statusCnt = 0; }, equals: function(fooCnt, statusCnt, str) { if (!str) str = '' ; equals(this.get('fooCnt'), fooCnt, str + ':fooCnt'); equals(this.get('statusCnt'), statusCnt, str + ':statusCnt'); } }); SC.RunLoop.begin(); var store = SC.Store.create(); var prec = store.createRecord(Rec, { foo: "bar", guid: 1 }); var child = store.chain(); var crec = child.find(Rec, prec.get('id')); // check assumptions ok(!!crec, 'prerec - should find child record'); equals(crec.get('foo'), 'bar', 'prerec - child record should have foo'); // modify child record - should not modify parent prec.reset(); crec.set('foo', 'baz'); equals(prec.get('foo'), 'bar', 'should not modify parent before commit'); prec.equals(0,0, 'before commitChanges'); // commit changes - note: still inside runloop child.commitChanges(); equals(prec.get('foo'), 'baz', 'should push data to parent'); prec.equals(1,1, 'after commitChanges'); // should notify immediately SC.RunLoop.end(); // should not notify again after runloop - nothing to do prec.equals(1,1,'after runloop ends - should not notify again');
});
test(“Changes to relationships should propagate to the parent store.”, function() {
var MyApp = window.MyApp = SC.Object.create({ store: SC.Store.create() }); MyApp.Rec = SC.Record.extend({ relatedChild: SC.Record.toOne('MyApp.RelatedRec', { inverse: 'relatedParent' }), relatedChildren: SC.Record.toMany('MyApp.RelatedRecs', { inverse: 'relatedParent' }) }); MyApp.RelatedRec = SC.Record.extend({ relatedParent: SC.Record.toOne('MyApp.Rec', { inverse: 'relatedChild', isMaster: NO }) }); MyApp.RelatedRecs = SC.Record.extend({ relatedParent: SC.Record.toOne('MyApp.Rec', { inverse: 'relatedChildren', isMaster: NO }) }); SC.RunLoop.begin(); MyApp.store.loadRecord(MyApp.RelatedRec, { guid: 2, relatedParent: 1}); MyApp.store.loadRecord(MyApp.RelatedRecs, { guid: 3, relatedParent: 1 }); MyApp.store.loadRecord(MyApp.RelatedRecs, { guid: 4, relatedParent: 1 }); MyApp.store.loadRecord(MyApp.Rec, { guid: 1, relatedChild: 2, relatedChildren: [3,4] }); var primaryRec = MyApp.store.find(MyApp.Rec, 1); var primaryRelatedRec = MyApp.store.find(MyApp.RelatedRec, 2); var primaryRelatedRecs1 = MyApp.store.find(MyApp.RelatedRecs, 3); var primaryRelatedRecs2 = MyApp.store.find(MyApp.RelatedRecs, 4); var nestedStore = MyApp.store.chain(); var nestedRec = nestedStore.find(MyApp.Rec, primaryRec.get('id')); var nestedRelatedRec = nestedStore.find(MyApp.RelatedRec, primaryRelatedRec.get('id')); var nestedRelatedRecs1 = nestedStore.find(MyApp.RelatedRecs, primaryRelatedRecs1.get('id')); var nestedRelatedRecs2 = nestedStore.find(MyApp.RelatedRecs, primaryRelatedRecs2.get('id')); // check assumptions ok(!!nestedRec, 'Prior to nested changes should find primaryRec in nested store'); ok(!!nestedRelatedRec, 'Prior to nested changes should find nestedRelatedRec in nested store'); ok(!!nestedRelatedRecs1, 'Prior to nested changes should find nestedRelatedRecs1 in nested store'); ok(!!nestedRelatedRecs2, 'Prior to nested changes should find nestedRelatedRecs2 in nested store'); equals(primaryRec.get('relatedChild'), primaryRelatedRec, 'Prior to changes primaryRec relatedChild should be'); equals(primaryRelatedRec.get('relatedParent'), primaryRec, 'Prior to changes primaryRelatedRec relatedParent should be'); equals(primaryRelatedRecs1.get('relatedParent'), primaryRec, 'Prior to changes primaryRelatedRecs1 relatedParent should be'); equals(primaryRelatedRecs2.get('relatedParent'), primaryRec, 'Prior to changes primaryRelatedRecs2 relatedParent should be'); equals(primaryRec.get('status'), SC.Record.READY_CLEAN, 'Prior to changes primaryRec status should be READY_CLEAN'); equals(primaryRelatedRec.get('status'), SC.Record.READY_CLEAN, 'Prior to changes primaryRelatedRec status should be READY_CLEAN'); equals(primaryRelatedRecs1.get('status'), SC.Record.READY_CLEAN, 'Prior to changes primaryRelatedRecs1 status should be READY_CLEAN'); equals(primaryRelatedRecs2.get('status'), SC.Record.READY_CLEAN, 'Prior to changes primaryRelatedRecs2 status should be READY_CLEAN'); nestedRec.set('relatedChild', null); nestedRelatedRecs2.set('relatedParent', null); nestedRec.get('relatedChildren').popObject(); // Modifying nested store record relationships should not modify primary store record relationships equals(primaryRec.get('relatedChild'), primaryRelatedRec, 'After nested changes primaryRec relatedChild should be'); equals(primaryRelatedRec.get('relatedParent'), primaryRec, 'After nested changes primaryRelatedRec relatedParent should be'); equals(primaryRelatedRecs1.get('relatedParent'), primaryRec, 'After nested changes primaryRelatedRecs1 relatedParent should be'); equals(primaryRelatedRecs2.get('relatedParent'), primaryRec, 'After nested changes primaryRelatedRecs2 relatedParent should be'); equals(primaryRec.get('status'), SC.Record.READY_CLEAN, 'After nested changes primaryRec status should be READY_CLEAN'); equals(primaryRelatedRec.get('status'), SC.Record.READY_CLEAN, 'After nested changes primaryRelatedRec status should be READY_CLEAN'); equals(primaryRelatedRecs1.get('status'), SC.Record.READY_CLEAN, 'After nested changes primaryRelatedRecs1 status should be READY_CLEAN'); equals(primaryRelatedRecs2.get('status'), SC.Record.READY_CLEAN, 'After nested changes primaryRelatedRecs2 status should be READY_CLEAN'); equals(nestedRec.get('relatedChild'), null, 'After nested changes nestedRec relatedChild should be'); equals(nestedRelatedRec.get('relatedParent'), null, 'After nested changes nestedRelatedRec relatedParent should be'); equals(nestedRelatedRecs1.get('relatedParent'), null, 'After nested changes nestedRelatedRecs1 relatedParent should be'); equals(nestedRelatedRecs2.get('relatedParent'), null, 'After nested changes nestedRelatedRecs2 relatedParent should be'); equals(nestedRec.get('status'), SC.Record.READY_DIRTY, 'After nested changes relatedChild status should be READY_DIRTY'); equals(nestedRelatedRec.get('status'), SC.Record.READY_CLEAN, 'After nested changes nestedRelatedRec status should be READY_CLEAN'); equals(nestedRelatedRecs1.get('status'), SC.Record.READY_CLEAN, 'After nested changes nestedRelatedRecs1 status should be READY_CLEAN'); equals(nestedRelatedRecs2.get('status'), SC.Record.READY_CLEAN, 'After nested changes nestedRelatedRecs2 status should be READY_CLEAN'); // commit changes - note: still inside runloop nestedStore.commitChanges(); equals(primaryRec.get('relatedChild'), null, 'After commit changes primaryRec relatedChild should be'); equals(primaryRelatedRec.get('relatedParent'), null, 'After commit changes primaryRelatedRec relatedParent should be'); equals(primaryRelatedRecs1.get('relatedParent'), null, 'After commit changes primaryRelatedRecs1 relatedParent should be'); equals(primaryRelatedRecs2.get('relatedParent'), null, 'After commit changes primaryRelatedRecs2 relatedParent should be'); equals(primaryRec.get('status'), SC.Record.READY_DIRTY, 'After commit changes primaryRec status should be READY_DIRTY'); equals(primaryRelatedRec.get('status'), SC.Record.READY_CLEAN, 'After commit changes primaryRelatedRec status should be READY_CLEAN'); equals(primaryRelatedRecs1.get('status'), SC.Record.READY_CLEAN, 'After commit changes primaryRelatedRecs1 status should be READY_CLEAN'); equals(primaryRelatedRecs2.get('status'), SC.Record.READY_CLEAN, 'After commit changes primaryRelatedRecs2 status should be READY_CLEAN'); SC.RunLoop.end(); delete window.MyApp;
});