// ========================================================================== // 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 with RecordAttribute var storeKeys, rec, rec2, bar, bar2 ;

module(“SC.RecordAttribute core methods”, {

setup: function() {

  MyApp = SC.Object.create({
    store: SC.Store.create()
  });

  MyApp.Foo = SC.Record.extend({

    // test toOne relationships
    relatedTo: SC.Record.toOne('MyApp.Foo'),

    // test non-isEditable toOne relationships
    readOnlyRelatedTo: SC.Record.toOne('MyApp.Bar', {
      isEditable: NO
    }),

    // test toOne relationship with computed type
    relatedToComputed: SC.Record.toOne(function() {
      // not using .get() to avoid another transform which will
      // trigger an infinite loop
      return (this.readAttribute('relatedToComputed').indexOf("foo")===0) ? MyApp.Foo : MyApp.Bar;
    }),

    bar: SC.Record.toOne('MyApp.Bar', { inverse: 'foo' }),

    barKeyed: SC.Record.toOne('MyApp.Bar', { key: 'barId' })

  });

  MyApp.Bar = SC.Record.extend({
    foo: SC.Record.toOne('MyApp.Foo', { inverse: 'bar', isMaster: NO })
  });

  SC.RunLoop.begin();
  storeKeys = MyApp.store.loadRecords(MyApp.Foo, [
    {
      guid: 'foo1',
      firstName: "John",
      lastName: "Doe",
      date: "2009-03-01T20:30-08:00",
      anArray: ['one', 'two', 'three'],
      anObject: { 'key1': 'value1', 'key2': 'value2' },
      bar: "bar1"
    },

    {
      guid: 'foo2',
      firstName: "Jane",
      lastName: "Doe",
      relatedTo: 'foo1',
      anArray: 'notAnArray',
      anObject: 'notAnObject',
      nonIsoDate: "2009/06/10 8:55:50 +0000"
    },

    {
      guid: 'foo3',
      firstName: "Alex",
      lastName: "Doe",
      relatedToComputed: 'bar1',
      anArray: ['one', 'two', 'three'],
      anObject: { 'key1': 'value1', 'key2': 'value2' },
      bar: "bar2"
    },

    {
      guid: 'foo4',
      firstName: 'Joe',
      lastName:  'Schmo',
      barId: 'bar1'
    },

    {
      guid: 'foo5',
      firstName: "Jane",
      lastName: "Doe",
      readOnlyRelatedTo: 'bar1'
    }

  ]);

  MyApp.store.loadRecords(MyApp.Bar, [
    { guid: 'bar1', city: "Chicago", foo: "foo1" },
    { guid: "bar2", city: "New York", foo: 'foo3' }
  ]);

  SC.RunLoop.end();

  rec = MyApp.store.find(MyApp.Foo, 'foo1');
  rec2 = MyApp.store.find(MyApp.Foo, 'foo2');

  bar = MyApp.store.find(MyApp.Bar, 'bar1');
  bar2 = MyApp.store.find(MyApp.Bar, 'bar2');

  equals(rec.storeKey, storeKeys[0], 'should find record');

}

});

// .….….….….….….….….….….….….….….. // READING //

test(“getting toOne relationship should map guid to a real record”, function() {

var rec2 = MyApp.store.find(MyApp.Foo, 'foo2');
equals(rec2.get('id'), 'foo2', 'precond - should find record 2');
equals(rec2.get('relatedTo'), rec, 'should get rec1 instance for rec2.relatedTo');

});

test(“getting toOne relationship from computed attribute should map guid to a real record”, function() {

var rec3 = MyApp.store.find(MyApp.Foo, 'foo3');
equals(rec3.get('id'), 'foo3', 'precond - should find record 3');
equals(rec3.get('relatedToComputed'), bar, 'should get bar1 instance for rec3.relatedToComputed');

});

test(“reading an inverse relationship”, function() {

equals(rec.get('bar'), bar, 'foo1.bar should == bar');
equals(bar.get('foo'), rec, 'bar.foo should == foo1');

});

test(“reading a keyed relationship”, function(){

var rec4 = MyApp.store.find(MyApp.Foo, 'foo4');
equals(rec4.get('barKeyed'), bar, 'foo4.barKeyed should == bar');

});

// .….….….….….….….….….….….….….….. // WRITING //

test(“writing to a to-one relationship should update set guid”, function() {

var rec2 = MyApp.store.find(MyApp.Foo, 'foo2');
equals(rec2.get('id'), 'foo2', 'precond - should find record 2');

equals(rec2.get('relatedTo'), rec, 'precond - should get rec1 instance for rec2.relatedTo');

rec2.set('relatedTo', rec2);

equals(rec2.readAttribute('relatedTo'), 'foo2', 'should write ID for set record to relatedTo attribute');

equals(rec2.get('relatedTo'), rec2, 'should get foo record that was just set');

});

test(“writing to a to-one computed relationship should update set guid”, function() {

var rec3 = MyApp.store.find(MyApp.Foo, 'foo3');
equals(rec3.get('id'), 'foo3', 'precond - should find record 2');
equals(rec3.get('relatedToComputed'), bar, 'precond - should get bar1 instance for rec3.relatedToComputed');

rec3.set('relatedToComputed', rec);
equals(rec3.readAttribute('relatedToComputed'), 'foo1', 'should write ID for set record to relatedTo attribute');
equals(rec2.get('relatedTo'), rec, 'should get foo record that was just set');

});

test(“clearing a toOne relationship”, function() {

ok(rec2.get('relatedTo') !== null, 'precond - rec.relatedTo should have a value');

rec2.set('relatedTo', null);
equals(rec2.get('relatedTo'), null, 'rec.relatedTo should be null');
equals(rec2.readAttribute('relatedTo'), null, 'rec.relatedTo attribute should be null');

});

test(“clearing a toOne relationship with an inverse - foo isMaster”, function() {

equals(rec.get('bar'), bar, 'precond - foo1.bar should eq bar');
equals(bar.get('foo'), rec, 'precond - bar.foo should eq foo1');

equals(rec.get('status'), SC.Record.READY_CLEAN, 'precond - foo1.status should be READY_CLEAN');
equals(bar.get('status'), SC.Record.READY_CLEAN, 'precond - bar1.status should be READY_CLEAN');

rec.set('bar', null);

equals(rec.get('bar'), null, 'foo1.bar should be null after change');
equals(bar.get('foo'), null, 'bar.foo should also be null after change');

equals(rec.get('status'), SC.Record.READY_DIRTY, 'foo1.status should be READY_DIRTY');
equals(bar.get('status'), SC.Record.READY_CLEAN, 'bar1.status should be READY_CLEAN');

});

test(“modifying a toOne relationship with an inverse from null”, function() {

equals(rec.get('bar'), bar, 'precond - foo1.bar should eq bar');
equals(bar.get('foo'), rec, 'precond - bar.foo should eq foo1');
equals(rec2.get('bar'), null, 'precond - foo2.bar should eq null');

[rec, rec2, bar].forEach(function(r) {
  equals(r.get('status'), SC.Record.READY_CLEAN, 'precond - %@.status should be READY_CLEAN'.fmt(r.get('id')));
}, this);

bar.set('foo', rec2);

equals(rec.get('bar'), null, 'foo1.bar should be null after change');
equals(bar.get('foo'), rec2, 'bar.foo should eq foo2 after change');
equals(rec2.get('bar'), bar, 'foo2.bar should eq bar after change');

equals(rec.get('status'), SC.Record.READY_DIRTY, 'foo1.status should be READY_DIRTY');
equals(rec2.get('status'), SC.Record.READY_DIRTY, 'foo1.status should be READY_DIRTY');
equals(bar.get('status'), SC.Record.READY_CLEAN, 'bar1.status should be READY_CLEAN');

});

test(“modifying a toOne relationship with an inverse from other”, function() {

var foo1 = rec,
    foo3 = MyApp.store.find(MyApp.Foo, 'foo3'),
    bar1 = bar;

equals(foo1.get('bar'), bar1, 'precond - foo1.bar should eq bar1');
equals(bar1.get('foo'), foo1, 'precond - bar.foo should eq foo1');

equals(foo3.get('bar'), bar2, 'precond - foo3.bar should eq bar2');
equals(bar2.get('foo'), foo3, 'precond - bar2.foo should eq foo3');

[foo1, foo3, bar1, bar2].forEach(function(r) {
  equals(r.get('status'), SC.Record.READY_CLEAN, 'precond - %@.status should be READY_CLEAN'.fmt(r.get('id')));
}, this);

bar1.set('foo', foo3);

equals(foo1.get('bar'), null, 'foo1.bar should be null after change');
equals(bar1.get('foo'), foo3, 'bar.foo should eq foo3 after change');

equals(foo3.get('bar'), bar1, 'foo3.bar should be bar after change');
equals(bar2.get('foo'), null, 'bar2.foo should eq null after change');

equals(foo1.get('status'), SC.Record.READY_DIRTY, 'foo1.status should be READY_DIRTY');
equals(foo3.get('status'), SC.Record.READY_DIRTY, 'foo3.status should be READY_DIRTY');

equals(bar1.get('status'), SC.Record.READY_CLEAN, 'bar1.status should be READY_CLEAN');
equals(bar2.get('status'), SC.Record.READY_CLEAN, 'bar2.status should be READY_CLEAN');

});

test(“modifying a keyed toOne relationship”, function(){

var rec4 = MyApp.store.find(MyApp.Foo, 'foo4');

rec4.set('barKeyed', bar2);

equals(rec4.get('barKeyed'), bar2, 'foo4.barKeyed should == bar2');
equals(rec4.readAttribute('barId'), 'bar2', 'should write ID for set record to barId attribute');

});

test(“isEditable NO should not allow editing”, function() {

var bar1 = MyApp.store.find(MyApp.Bar, 'bar1');
var bar2 = MyApp.store.find(MyApp.Bar, 'bar2');
var rec5 = MyApp.store.find(MyApp.Foo, 'foo5');

equals(rec5.get('readOnlyRelatedTo'), bar1, 'precond - should find bar1');
equals(rec5.get('status'), SC.Record.READY_CLEAN, 'precond - foo5 should be READY_CLEAN');

rec5.set('readOnlyRelatedTo', bar2);

equals(rec5.get('readOnlyRelatedTo'), bar1, 'should still find bar1 after setting');
equals(rec5.get('status'), SC.Record.READY_CLEAN, 'foo5 status is still READY_CLEAN');

});

test(“isEditable NO should not fire property change observer”, function() {

var bar1 = MyApp.store.find(MyApp.Bar, 'bar1');
var bar2 = MyApp.store.find(MyApp.Bar, 'bar2');
var rec5 = MyApp.store.find(MyApp.Foo, 'foo5');

equals(rec5.get('readOnlyRelatedTo'), bar1, 'precond - should find bar1');

var readOnlyWasModified = NO;
var modifierListener = function() {
  readOnlyWasModified = YES;
};
rec5.addObserver('readOnlyRelatedTo', modifierListener);

rec5.set('readOnlyRelatedTo', bar2);

equals(readOnlyWasModified, NO, 'property change observer should not have fired');

rec5.removeObserver('readOnlyRelatedTo', modifierListener);

});

test(“adding toOne pointing to non existing class should throw error”, function() {

var message;
try {
  MyApp.InvalidModel = SC.Record.extend({
    foo: SC.Record.toOne(MyApp.DoesNotExist)
  });
} catch (x) {
  message = x.message;
}

same(message, 'Attempted to create toOne attribute with undefined recordType. Did you forget to sc_require a dependency?');

});

test(“Adding an unsaved record should throw an Error”, function() {

var bar1 = MyApp.store.find(MyApp.Bar, 'bar1'),
  foo = MyApp.store.createRecord(MyApp.Foo, {});

try {
  bar1.set('foo', foo);
  ok(false, "Attempting to assign an unsaved record resulted in an error.");
} catch (x) {
  ok(true, "Attempting to assign an unsaved record resulted in an error.");
}

});

test(“adding toMany pointing to non existing class should throw error”, function() {

var message;
try {
  MyApp.InvalidModel = SC.Record.extend({
    foo: SC.Record.toMany(MyApp.DoesNotExist)
  });
} catch (x) {
  message = x.message;
}

same(message, 'Attempted to create toMany attribute with undefined recordType. Did you forget to sc_require a dependency?');

});

module(“modifying a keyed toOne relationship via the inverse”, {

setup: function() {
  MyApp = SC.Object.create({ store: SC.Store.create() });

  MyApp.Foo = SC.Record.extend({
    bar: SC.Record.toOne('MyApp.Bar', {
      isMaster: YES, inverse: 'foo'
    })
  });

  MyApp.Bar = SC.Record.extend({
    foo: SC.Record.toOne('MyApp.Foo', {
      isMaster: NO, key: 'foo_id', inverse: 'bar'
    })
  });
}

});

test(“creating an association”, function() {

var foo1, bar1;

MyApp.store.loadRecords(MyApp.Foo, [{guid: 'foo1', bar: null}]);

foo1 = MyApp.store.find(MyApp.Foo, 'foo1');
bar1 = MyApp.store.createRecord(MyApp.Bar, {guid: 'bar1'});

foo1.set('bar', bar1);

equals(bar1.get('foo'), foo1, 'bar1.foo relationship should be established');
equals(bar1.get('attributes').foo_id, 'foo1', 'correct key should be set in attributes');

});

test(“destroying an association”, function() {

var foo1, bar1;

MyApp.store.loadRecords(MyApp.Foo, [{guid: 'foo1', bar: 'bar1'}]);
MyApp.store.loadRecords(MyApp.Bar, [{guid: 'bar1', foo_id: 'foo1'}]);

foo1 = MyApp.store.find(MyApp.Foo, 'foo1');
bar1 = MyApp.store.find(MyApp.Bar, 'bar1');

equals(foo1.get('bar'), bar1, 'foo1.bar relationship should be established');
foo1.set('bar', null);
equals(bar1.get('foo'), null, 'bar.foo relationship should be destroyed');
equals(bar1.get('attributes').foo_id, null, 'correct key should be set in attributes');

});