// ========================================================================== // 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, rec3, bar, MyApp;

module(“SC.RecordAttribute core methods”, {

setup: function() {

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

  // stick it to the window object so that objectForPropertyPath works
  window.MyApp = MyApp;

  MyApp.Foo = SC.Record.extend({

    // test simple reading of a pass-through prop
    firstName: SC.Record.attr(String),

    // test mapping to another internal key
    otherName: SC.Record.attr(String, { key: "firstName" }),

    // test mapping Date
    date: SC.Record.attr(Date),
    nonIsoDate: SC.Record.attr(Date, { useIsoDate: false }),

    // test SC.DateTimes
    dateTime: SC.Record.attr(SC.DateTime),

    // test Array
    anArray: SC.Record.attr(Array),

    // test Object
    anObject: SC.Record.attr(Object),

    // test Number
    aNumber: SC.Record.attr(Number),

    // used to test default value
    defaultValue: SC.Record.attr(String, {
      defaultValue: "default"
    }),

    // used to test default value
    defaultComputedValue: SC.Record.attr(Number, {
      defaultValue: function() {
        return Math.floor(Math.random()*3+1);
      }
    }),

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

    // 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;
    }),

    // test readONly
    readOnly: SC.Record.attr(String, { isEditable: NO })

  });

  MyApp.Bar = SC.Record.extend({
    parent: SC.Record.toOne('MyApp.Foo', { aggregate: YES }),
    relatedMany: SC.Record.toMany('MyApp.Foo', { aggregate: YES })
  });

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

    {
      guid: 'foo2',
      firstName: "Jane",
      lastName: "Doe",
      relatedTo: 'foo1',
      relatedToAggregate: 'bar1',
      dateTime: "2009-03-01T20:30:25Z",
      anArray: 'notAnArray',
      anObject: 'notAnObject',
      aNumber: '123',
      nonIsoDate: "2009/06/10 8:55:50 +0000"
    },

    {
      guid: 'foo3',
      firstName: "Alex",
      lastName: "Doe",
      relatedToComputed: 'bar1',
      dateTime: SC.DateTime.create(1235939425000),
      anArray: ['one', 'two', 'three'],
      anObject: { 'key1': 'value1', 'key2': 'value2' },
      aNumber: '123'
    }

  ]);

  MyApp.store.loadRecords(MyApp.Bar, [
    { guid: 'bar1', city: "Chicago", parent: 'foo2', relatedMany: ['foo1', 'foo2'] }
  ]);

  SC.RunLoop.end();

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

  bar = MyApp.store.find(MyApp.Bar, 'bar1');
  equals(rec.storeKey, storeKeys[0], 'should find record');

},

teardown: function () {
  MyApp.store.destroy();
  window.MyApp = storeKeys = rec = rec2 = rec3 = bar = null;
}

});

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

test(“pass-through should return builtin value” ,function() {

equals(rec.get('firstName'), 'John', 'reading prop should get attr value');

});

test(“returns default value if underlying value is empty”, function() {

equals(rec.get('defaultValue'), 'default', 'reading prop should return default value');

});

test(“naming a key should read alternate attribute”, function() {

equals(rec.get('otherName'), 'John', 'reading prop otherName should get attr from firstName');

});

test(“getting a number”, function() {

equals((typeof rec.get('aNumber')), 'number', 'reading prop aNumber should get attr as number');

});

test(“getting an array and object”, function() {

equals(rec.get('anArray').length, 3, 'reading prop anArray should get attr as array');
equals((typeof rec.get('anObject')), 'object', 'reading prop anObject should get attr as object');

});

test(“getting an array and object attributes where underlying value is not”, function() {

equals(rec2.get('anArray').length, 0, 'reading prop anArray should return empty array');
equals((typeof rec2.get('anObject')), 'object', 'reading prop anObject should return empty object');

});

test(“reading date should parse ISO date”, function() {

var d = new Date(1235968200000); // should be proper date
equals(rec.get('date').toString(), d.toString(), 'should have matched date');

});

test(“reading dateTime should parse ISO date”, function() {

var ms = 1235939425000;
equals(rec.getPath('dateTime.milliseconds'), ms, 'should have parsed Date properly');
equals(rec2.getPath('dateTime.milliseconds'), ms, 'should have parsed String properly');
equals(rec3.getPath('dateTime.milliseconds'), ms, 'should have parsed SC.DateTime properly');

});

test(“reading date should parse non-ISO date”, function() {

var d = new Date(1244624150000);
equals(rec2.get('nonIsoDate').toString(), d.toString(), 'should have matched date');

});

test(“reading no date should produce null”, function() {

var d = new Date(1235968200000); // should be proper date
equals(rec2.get('date'), null, 'should have yielded null');

});

test(“reading computed default value”, function() {

var value = rec.get('defaultComputedValue');
var validValues = [1,2,3,4];
ok(validValues.indexOf(value)!==-1, 'should have a value from 1 through 4');

});

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

test(“writing pass-through should simply set value”, function() {

rec.set("firstName", "Foo");
equals(rec.readAttribute("firstName"), "Foo", "should write string");

rec.set("firstName", 23);
equals(rec.readAttribute("firstName"), 23, "should write number");

rec.set("firstName", YES);
equals(rec.readAttribute("firstName"), YES, "should write bool");

});

test(“writing when isEditable is NO should ignore”, function() {

var v = rec.get('readOnly');
rec.set('readOnly', 'NEW VALUE');
equals(rec.get('readOnly'), v, 'read only value should not change');

});

test(“writing a value should override default value”, function() {

equals(rec.get('defaultValue'), 'default', 'precond - returns default');
rec.set('defaultValue', 'not-default');
equals(rec.get('defaultValue'), 'not-default', 'newly written value should replace default value');

});

test(“writing a string to a number attribute should store a number” ,function() {

equals(rec.set('aNumber', "456"), rec, 'returns receiver');
equals(rec.get('aNumber'), 456, 'should have new value');
equals(typeof rec.get('aNumber'), 'number', 'new value should be a number');

});

test(“writing a date should generate an ISO date” ,function() {

var date = new Date(1238650083966);

// Work with timezones
var utcDate = new Date(Number(date) + (date.getTimezoneOffset() * 60000)); // Adjust for timezone offset
utcDate.getTimezoneOffset = function(){ return 0; }; // Hack the offset to respond 0

equals(rec.set('date', utcDate), rec, 'returns receiver');
equals(rec.readAttribute('date'), '2009-04-02T05:28:03Z', 'should have time in ISO format');

});

test(“writing an attribute should make relationship aggregate dirty” ,function() {

equals(bar.get('status'), SC.Record.READY_CLEAN, "precond - bar should be READY_CLEAN");
equals(rec2.get('status'), SC.Record.READY_CLEAN, "precond - rec2 should be READY_CLEAN");

bar.set('city', 'Oslo');
bar.get('store').flush();

equals(rec2.get('status'), SC.Record.READY_DIRTY, "foo2 should be READY_DIRTY");

});

test(“writing an attribute should make many relationship aggregate dirty” ,function() {

equals(bar.get('status'), SC.Record.READY_CLEAN, "precond - bar should be READY_CLEAN");
equals(rec2.get('status'), SC.Record.READY_CLEAN, "precond - rec2 should be READY_CLEAN");

SC.run(function () {
  bar.set('city', 'Oslo');
  bar.get('store').flush();
});

equals(rec.get('status'), SC.Record.READY_DIRTY, "foo1 should be READY_DIRTY");
equals(rec2.get('status'), SC.Record.READY_DIRTY, "foo2 should be READY_DIRTY");

});

test(“writing an attribute should make many relationship aggregate dirty and add the aggregate to the store” ,function() {

equals(bar.get('status'), SC.Record.READY_CLEAN, "precond - bar should be READY_CLEAN");
equals(rec2.get('status'), SC.Record.READY_CLEAN, "precond - rec2 should be READY_CLEAN");

bar.set('city', 'Oslo');

var store = bar.get('store');
ok(store.changelog.contains(rec.get('storeKey')), "foo1 should be in the store's changelog");
ok(store.changelog.contains(rec2.get('storeKey')), "foo2 should be in the store's changelog");

});

test(“adding attribute with non existing class should throw error”, function() {

MyApp.InvalidModel = SC.Record.extend({
  foo: SC.Record.attr("SomethingSomethingSomething")
});

var message;
try {
  MyApp.InvalidModel.prototype.foo.typeClass();
} catch (x) {
  message = x;
}

same(message, 'SomethingSomethingSomething could not be found');

});