// ========================================================================== // 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 MyApp */ // test core array-mapping methods for RecordArray var store, storeKey, json, rec, storeKeys, recs, query, recsController, fooQuery, fooRecs, fooRecsController; module("SC.RecordArray core methods", { setup: function() { // setup dummy store store = SC.Store.create(); storeKey = SC.Record.storeKeyFor('foo'); json = { guid: "foo", foo: "foo" }; store.writeDataHash(storeKey, json, SC.Record.READY_CLEAN); // get record rec = store.materializeRecord(storeKey); equals(rec.get('foo'), 'foo', 'record should have json'); // get record array. query = SC.Query.create({ recordType: SC.Record }); recs = SC.RecordArray.create({ store: store, query: query }); recsController = SC.Object.create({ content: recs, bigCost: NO, veryExpensiveObserver: function() { this.set('bigCost', YES); }.observes('.content.[]') }); fooQuery = SC.Query.create({ recordType: SC.Record, conditions: "foo='foo'" }); fooRecs = SC.RecordArray.create({ store: store, query: fooQuery }); fooRecsController = SC.Object.create({ content: fooRecs, bigCost: NO, veryExpensiveObserver: function() { this.set('bigCost', YES); }.observes('.content.[]') }); } }); // .......................................................... // BASIC TESTS // test("should not initially populate storeKeys array until we flush()", function() { equals(recs.get('storeKeys'), null, 'should not have storeKeys yet'); recs.flush(); var storeKeys = recs.get('storeKeys'); same(storeKeys, [storeKey], 'after flush should have initial set of storeKeys'); }); test("length property should flush", function() { equals(recs.get('storeKeys'), null,' should not have storeKeys yet'); equals(recs.get('length'), 1, 'should have length 1 when called'); same(recs.get('storeKeys'), [storeKey], 'after flush should have initial set of storeKeys'); }); test("objectAt() should flush", function() { equals(recs.get('storeKeys'), null,' should not have storeKeys yet'); equals(recs.objectAt(0), rec, 'objectAt(0) should return record'); same(recs.get('storeKeys'), [storeKey], 'after flush should have initial set of storeKeys'); }); // .......................................................... // storeDidChangeStoreKeys() // test("calling storeDidChangeStoreKeys() with a matching recordType", function() { recs.flush(); // do initial setup var orig = recs.get('storeKeys').copy(); // do it this way instead of using store.createRecord() to isolate the // method call. storeKey = SC.Record.storeKeyFor("bar"); json = { guid: "bar", foo: "bar" }; store.writeDataHash(storeKey, json, SC.Record.READY_CLEAN); equals(recs.get('needsFlush'), NO, 'PRECOND - should not need flush'); same(recs.get('storeKeys'), orig, 'PRECOND - storeKeys should not have changed yet'); recs.storeDidChangeStoreKeys([storeKey], SC.Set.create().add(SC.Record)); orig.unshift(storeKey); // update - must be first b/c id.bar < id.foo equals(recs.get('needsFlush'), NO, 'should not need flush anymore'); same(recs.get('storeKeys'), orig, 'storeKeys should now be updated - rec1[%@]{%@} = %@, rec2[%@]{%@} = %@'.fmt( rec.get('id'), rec.get('storeKey'), rec, store.materializeRecord(storeKey).get('id'), storeKey, store.materializeRecord(storeKey))); }); test("calling storeDidChangeStoreKeys() with a non-matching recordType", function() { var Foo = SC.Record.extend(), Bar = SC.Record.extend(); storeKey = Foo.storeKeyFor('foo2'); json = { guid: "foo2" }; store.writeDataHash(storeKey, json, SC.Record.READY_CLEAN); query = SC.Query.create({ recordType: Foo }); recs = SC.RecordArray.create({ store: store, query: query }); equals(recs.get('length'), 1, 'should have a Foo record'); // now simulate adding a Bar record storeKey = Bar.storeKeyFor('bar'); json = { guid: "bar" }; store.writeDataHash(storeKey, json, SC.Record.READY_CLEAN); recs.storeDidChangeStoreKeys([storeKey], SC.Set.create().add(Bar)); equals(recs.get('needsFlush'), NO, 'should not have indicated it needed a flush'); }); test("calling storeDidChangeStoreKeys() to remove a record", function() { equals(recs.get('length'), 1, 'PRECOND - should have storeKey'); store.writeStatus(storeKey, SC.Record.DESTROYED_CLEAN); equals(recs.get('storeKeys').length, 1, 'should still have storeKey'); recs.storeDidChangeStoreKeys([storeKey], SC.Set.create().add(SC.Record)); equals(recs.get('length'), 0, 'should remove storeKey on flush()'); }); test("calling storeDidChangeStoreKeys() with a matching recordType should not unnecessarily call enumerableContentDidChange", function() { // do initial setup recs.flush(); fooRecs.flush(); recsController.set('bigCost', NO); fooRecsController.set('bigCost', NO); // do it this way instead of using store.createRecord() to isolate the // method call. storeKey = SC.Record.storeKeyFor("bar"); json = { guid: "bar", foo: "bar" }; store.writeDataHash(storeKey, json, SC.Record.READY_CLEAN); equals(recsController.get('bigCost'), NO, 'PRECOND - recsController should not have spent big cost'); equals(fooRecsController.get('bigCost'), NO, 'PRECOND - fooRecsController should not have spent big cost'); recs.storeDidChangeStoreKeys([storeKey], SC.Set.create().add(SC.Record)); fooRecs.storeDidChangeStoreKeys([storeKey], SC.Set.create().add(SC.Record)); equals(recsController.get('bigCost'), YES, 'recsController should have spent big cost'); equals(fooRecsController.get('bigCost'), NO, 'fooRecsController should not have spent big cost'); }); test("adding an array observer to a SC.RecordArray should cause the array to flush", function() { var callCount = 0; debugger; recs.addArrayObservers({ didChange: function() { callCount++; }, willChange: function() { } }); recs.get('length'); equals(callCount, 0, "does not cause array observers to be fired when getting length"); }); // .......................................................... // SPECIAL CASES // var json2, foo, bar ; module("SC.RecordArray core methods", { setup: function() { // setup dummy store store = SC.Store.create(); storeKey = SC.Record.storeKeyFor('foo'); json = { guid: "foo", name: "foo" }; store.writeDataHash(storeKey, json, SC.Record.READY_CLEAN); foo = store.materializeRecord(storeKey); equals(foo.get('name'), 'foo', 'record should have json'); storeKey = SC.Record.storeKeyFor('bar'); json2 = { guid: "bar", name: "bar" }; store.writeDataHash(storeKey, json2, SC.Record.READY_CLEAN); bar = store.materializeRecord(storeKey); equals(bar.get('name'), 'bar', 'record should have json'); // get record array. query = SC.Query.create({ recordType: SC.Record, orderBy: 'name' }); recs = store.find(query); } }); test("local query should notify changes", function() { // note: important to retrieve records from RecordArray first to prime // any cache same(recs.mapProperty('id'), ['bar', 'foo'], 'PRECOND - bar should appear before foo'); SC.stopIt = YES; SC.RunLoop.begin(); bar.set('name', 'zzbar'); SC.RunLoop.end(); // should resort record array same(recs.mapProperty('id'), ['foo', 'bar'], 'order of records should change'); });