// ========================================================================== // Project: SproutCore - JavaScript Application Framework // Copyright: ©2006-2011 Strobe Inc. and contributors. // ©2008-2011 Apple Inc. All rights reserved. // License: Licensed under MIT license (see license.js) // ========================================================================== // ======================================================================== // SC.Binding Tests // ======================================================================== /*globals module, test, ok, equals */

var FromObject, fromObject, midObject, toObject, binding, Bon1, bon2, first, second, third, binding1, binding2;

module(“basic object binding”, {

setup: function () {
  fromObject = SC.Object.create({ value: 'start' });
  midObject = SC.Object.create({ value: 'middle' });
  toObject = SC.Object.create({ value: 'end' });
  binding1 = SC.Binding.from("value", fromObject).to("value", midObject).connect();
  binding2 = SC.Binding.from("value", midObject).to("value", toObject).connect();
  SC.Binding.flushPendingChanges(); // actually sets up up the connection
},

teardown: function () {
  fromObject.destroy();
  midObject.destroy();
  toObject.destroy();
  fromObject = midObject = toObject = binding1 = binding2 = null;
}

});

test(“binding is connected”, function () {

equals(binding1.isConnected, YES, "binding1.isConnected");
equals(binding2.isConnected, YES, "binding2.isConnected");

});

test(“binding has actually been setup”, function () {

equals(binding1._connectionPending, NO, "binding1._connectionPending");
equals(binding2._connectionPending, NO, "binding2._connectionPending");

});

test(“binding should have synced on connect”, function () {

equals(toObject.get("value"), "start", "toObject.value should match fromObject.value");
equals(midObject.get("value"), "start", "midObject.value should match fromObject.value");

});

test(“changing fromObject should mark binding as dirty”, function () {

fromObject.set("value", "change");
ok(SC.Binding._changeQueue.contains(binding1), "the binding should be in the _changeQueue");
SC.Binding.flushPendingChanges();
ok(SC.Binding._changeQueue.contains(binding2), "the binding should be in the _changeQueue");

});

test(“fromObject change should propogate to toObject only after flush”, function () {

fromObject.set("value", "change");
equals(midObject.get("value"), "start");
equals(toObject.get("value"), "start");
SC.Binding.flushPendingChanges();
equals(midObject.get("value"), "change");
SC.Binding.flushPendingChanges();
equals(toObject.get("value"), "change");

});

test(“changing toObject should mark binding as dirty”, function () {

toObject.set("value", "change");
ok(SC.Binding._changeQueue.contains(binding2), "the binding should be in the _changeQueue");
SC.Binding.flushPendingChanges();
ok(SC.Binding._changeQueue.contains(binding1), "the binding should be in the _changeQueue");

});

test(“toObject change should propogate to fromObject only after flush”, function () {

toObject.set("value", "change");
equals(midObject.get("value"), "start");
equals(fromObject.get("value"), "start");
SC.Binding.flushPendingChanges();
equals(midObject.get("value"), "change");
SC.Binding.flushPendingChanges();
equals(fromObject.get("value"), "change");

});

test(“suspended observing during bindings”, function () {

// setup special binding
fromObject = SC.Object.create({
  value1: 'value1',
  value2: 'value2'
});

toObject = SC.Object.create({
  value1: 'value1',
  value2: 'value2',

  callCount: 0,

  observer: function () {
    equals(this.get('value1'), 'CHANGED', 'value1 when observer fires');
    equals(this.get('value2'), 'CHANGED', 'value2 when observer fires');
    this.callCount++;
  }.observes('value1', 'value2')
});

toObject.bind('value1', fromObject, 'value1');
toObject.bind('value2', fromObject, 'value2');

// change both value1 + value2, then  flush bindings.  observer should only
// fire after bindings are done flushing.
fromObject.set('value1', 'CHANGED').set('value2', 'CHANGED');
SC.Binding.flushPendingChanges();

equals(toObject.callCount, 2, 'should call observer twice');

});

test(“binding will disconnect”, function () {

binding1.disconnect();
equals(binding1.isConnected, NO, "binding1.isConnected");

});

test(“binding disconnection actually works”, function () {

binding1.disconnect();
fromObject.set('value', 'change');
SC.Binding.flushPendingChanges();
equals(midObject.get('value'), 'start');
SC.Binding.flushPendingChanges();
equals(toObject.get('value'), 'start');

binding1.connect();
SC.Binding.flushPendingChanges();
equals(midObject.get('value'), 'change');
SC.Binding.flushPendingChanges();
equals(toObject.get('value'), 'change');

});

test(“binding destruction actually works”, function () {

binding1.destroy();
ok(binding1.isDestroyed, "binding marks itself as destroyed.");
ok(!binding1._fromTarget && !binding1._toTarget, "binding destruction removes binding targets.");

});

module(“bindings on classes”);

test(“should connect when multiple instances of class are created”, function () {

window.TestNamespace = {};
window.TestNamespace.stubController = SC.Object.create({
  name: 'How to Be Happy'
});

try {
  var myClass = SC.Object.extend({
    fooBinding: SC.Binding.from('TestNamespace.stubController.name')
  });

  var myFirstObj;

  SC.run(function () { myFirstObj = myClass.create(); });
  equals(myFirstObj.get('foo'), "How to Be Happy");

  var mySecondObj;
  SC.run(function () { mySecondObj = myClass.create(); });
  equals(mySecondObj.get('foo'), "How to Be Happy");

  SC.run(function () { myFirstObj.destroy(); });
  ok(myFirstObj.fooBinding.isDestroyed, "destroying an object destroys its class bindings.");

} finally {
  window.TestNamespace = undefined;
}

});

module(“one way binding”, {

setup: function () {
  fromObject = SC.Object.create({ value: 'start' });
  toObject = SC.Object.create({ value: 'end' });
  binding = SC.Binding.from("value", fromObject).to("value", toObject).oneWay().connect();
  SC.Binding.flushPendingChanges(); // actually sets up up the connection
}

});

test(“changing fromObject should mark binding as dirty”, function () {

fromObject.set("value", "change");
ok(SC.Binding._changeQueue.contains(binding), "the binding should be in the _changeQueue");

});

test(“fromObject change should propogate after flush”, function () {

fromObject.set("value", "change");
equals(toObject.get("value"), "start");
SC.Binding.flushPendingChanges();
equals(toObject.get("value"), "change");

});

test(“changing toObject should not make binding dirty”, function () {

toObject.set("value", "change");
ok(!SC.Binding._changeQueue.contains(binding), "the binding should not be in the _changeQueue");

});

test(“toObject change should NOT propogate”, function () {

toObject.set("value", "change");
equals(fromObject.get("value"), "start");
SC.Binding.flushPendingChanges();
equals(fromObject.get("value"), "start");

});

module(“chained binding”, {

setup: function () {
  first = SC.Object.create({ output: 'first' });

  second = SC.Object.create({
    input: 'second',
    output: 'second',

    inputDidChange: function () {
      this.set("output", this.get("input"));
    }.observes("input")
  });

  third = SC.Object.create({ input: "third" });

  binding1 = SC.Binding.from("output", first).to("input", second).connect();
  binding2 = SC.Binding.from("output", second).to("input", third).connect();
  SC.Binding.flushPendingChanges(); // actually sets up up the connection
}

});

test(“changing first output should propagate to third after flush”, function () {

first.set("output", "change");
equals("change", first.get("output"), "first.output");
ok("change" !== third.get("input"), "third.input");

var didChange = YES;
while (didChange) { didChange = SC.Binding.flushPendingChanges(); }

// bindings should not have bending changes
ok(!SC.Binding._changeQueue.contains(binding1), "the binding should not be in the _changeQueue");
ok(!SC.Binding._changeQueue.contains(binding2), "the binding should not be in the _changeQueue");

equals("change", first.get("output"), "first.output");
equals("change", second.get("input"), "second.input");
equals("change", second.get("output"), "second.output");
equals("change", third.get("input"), "third.input");

});

module(“Custom Binding”, {

setup: function () {
  Bon1 = SC.Object.extend({
    value1: "hi",
    value2: 83,
    array1: []
  });

  bon2 = SC.Object.create({
    val1: "hello",
    val2: 25,
    arr: [1, 2, 3, 4]
  });

  window.TestNamespace = {
    bon2: bon2,
    Bon1: Bon1
  };
},

teardown: function () {
  bon2.destroy();
}

});

test(“Binding value1 such that it will receive only single values”, function () {

var bon1 = Bon1.create({
  value1Binding: SC.Binding.single("TestNamespace.bon2.val1"),
  array1Binding: SC.Binding.single("TestNamespace.bon2.arr")
});
SC.Binding.flushPendingChanges();
var a = [23, 31, 12, 21];
bon2.set("arr", a);
bon2.set("val1", "changed");
SC.Binding.flushPendingChanges();
equals(bon2.get("val1"), bon1.get("value1"));
equals("@@MULT@@", bon1.get("array1"));
bon1.destroy();

});

test(“Single binding using notEmpty function.”, function () {

var bond = Bon1.create({
  array1Binding: SC.Binding.single("TestNamespace.bon2.arr").notEmpty(null, '(EMPTY)')
});
SC.Binding.flushPendingChanges();
bon2.set("arr", []);
SC.Binding.flushPendingChanges();
equals("(EMPTY)", bond.get("array1"));

});

test(“Binding with transforms, function to check the type of value”, function () {

var jon = Bon1.create({
  value1Binding: SC.Binding.transform(function (val1) {
    return (SC.typeOf(val1) == SC.T_STRING) ? val1 : "";
  }).from("TestNamespace.bon2.val1")
});
SC.Binding.flushPendingChanges();
bon2.set("val1", "changed");
SC.Binding.flushPendingChanges();
equals(jon.get("value1"), bon2.get("val1"));

});

test(“Adding transform does not affect parent binding”, function () {

var A,
    a,
    b;

A = SC.Object.extend({
  isEnabledBindingDefault: SC.Binding.oneWay().bool()
});

b = SC.Object.create({
  isEnabled: YES
});

a = A.create();
a.bind('isEnabled', b, 'isEnabled').not();

ok(A.prototype.isEnabledBindingDefault._transforms !== a.bindings[0]._transforms, "transforms array not shared with parent binding");

});

test(“two bindings to the same value should sync in the order they are initialized”, function () {

SC.LOG_BINDINGS = YES;

SC.RunLoop.begin();

window.a = SC.Object.create({
  foo: "bar"
});

var a = window.a;

window.b = SC.Object.create({
  foo: "baz",
  fooBinding: "a.foo",

  C: SC.Object.extend({
    foo: "bee",
    fooBinding: "*owner.foo"
  }),

  init: function () {
    sc_super();
    this.set('c', this.C.create({ owner: this }));
  }

});

var b = window.b;

SC.LOG_BINDINGS = NO;

SC.RunLoop.end();

equals(a.get('foo'), "bar", 'a.foo should not change');
equals(b.get('foo'), "bar", 'a.foo should propogate up to b.foo');
equals(b.c.get('foo'), "bar", 'a.foo should propogate up to b.c.foo');

window.a = window.b = null;

});

var BindableObject; module(“Binding transforms”, {

setup: function () {
  BindableObject = SC.Object.extend({
    booleanValue: NO,
    numericValue: 42,
    stringValue: 'forty-two',
    arrayValue: [4, 2],
    undefinedValue: undefined,
    nullValue: null
  });
  // temporarily set up two source objects in the SC namespace so we can
  // use property paths to access them
  SC.testControllerA = BindableObject.create();
  SC.testControllerB = BindableObject.create();
},

teardown: function () {
  SC.testControllerA.destroy();
  delete SC.testControllerA;
  SC.testControllerB.destroy();
  delete SC.testControllerB;
}

});

test('Binding sync when only transformed value has changed', function () {

var toObject;
SC.run(function () {
  toObject = SC.Object.create({
    transformedValue: null,
    transformedValueBinding: SC.Binding.oneWay('SC.testControllerA.undefinedValue').transform(function (value, binding) {
      if (value === undefined) {
        return 'VALUE IS UNDEFINED';
      } else {
        return value;
      }
    })
  });
});

equals(toObject.get('transformedValue'), 'VALUE IS UNDEFINED', 'value is undefined, so bound value should be');

});

test(“the integer transform”, function() {

var toObject;

SC.run(function () {
  toObject = SC.Object.create({
    aNumber: null,
    aNumberBinding: SC.Binding.oneWay('SC.testControllerA.nullValue').integer(10)
  });
});

equals(toObject.get('aNumber'), 0, "Value is null, so bound value should should be");

SC.run(function () {
  SC.testControllerA.set('nullValue', '123abc');
});

equals(toObject.get('aNumber'), window.parseInt('123abc', 10), "Value is String, so bound value should be");

SC.run(function () {
  SC.testControllerA.set('nullValue', 'abc123');
});

equals(toObject.get('aNumber'), 0, "Value is non-parseable String, so bound value should be");

SC.run(function () {
  SC.testControllerA.set('nullValue', NaN);
});

equals(toObject.get('aNumber'), 0, "Value is NaN, so bound value should be");

SC.run(function () {
  SC.testControllerA.set('nullValue', Infinity);
});

equals(toObject.get('aNumber'), Infinity, "Value is Infinity, so bound value should be");

SC.run(function () {
  SC.testControllerA.set('nullValue', true);
});

equals(toObject.get('aNumber'), 1, "Value is Boolean true, so bound value should be");

SC.run(function () {
  SC.testControllerA.set('nullValue', false);
});

equals(toObject.get('aNumber'), 0, "Value is Boolean false, so bound value should be");

SC.run(function () {
  SC.testControllerA.set('nullValue', {});
});

equals(toObject.get('aNumber'), 0, "Value is Object, so bound value should be");

SC.run(function () {
  SC.testControllerA.set('nullValue', SC.Object.create({}));
});

equals(toObject.get('aNumber'), 0, "Value is SC.Object instance, so bound value should be");

});

test(“the string transform”, function() {

var toObject;

SC.run(function () {
  toObject = SC.Object.create({
    aString: null,
    aStringBinding: SC.Binding.oneWay('SC.testControllerA.nullValue').string()
  });
});

equals(toObject.get('aString'), '', "Value is null, so bound value should should be");

SC.run(function () {
  SC.testControllerA.set('nullValue', 1);
});

equals(toObject.get('aString'), '1', "Value is Number, so bound value should be");

SC.run(function () {
  SC.testControllerA.set('nullValue', true);
});

equals(toObject.get('aString'), 'true', "Value is Boolean true, so bound value should be");

SC.run(function () {
  SC.testControllerA.set('nullValue', false);
});

equals(toObject.get('aString'), 'false', "Value is Boolean false, so bound value should be");

SC.run(function () {
  SC.testControllerA.set('nullValue', {});
});

equals(toObject.get('aString'), {}.toString(), "Value is Object, so bound value should be");

SC.run(function () {
  SC.testControllerA.set('nullValue', SC.Object.create({ toString: function () { return 'An SC.Object'; }}));
});

equals(toObject.get('aString'), 'An SC.Object', "Value is SC.Object instance, so bound value should be");

});

test(“the equalTo transform”, function() {

SC.RunLoop.begin();
var toObject = SC.Object.create({
  isFortyTwo: null,
  isFortyTwoBinding: SC.Binding.oneWay('SC.testControllerA.numericValue').equalTo(42)
});
SC.RunLoop.end();

equals(toObject.get('isFortyTwo'), true, "Value is equal to 42, so bound value should should be");

SC.RunLoop.begin();
SC.testControllerA.set('numericValue', 45);
SC.RunLoop.end();

equals(toObject.get('isFortyTwo'), false, "Value is no longer 42, so bound value should be");

});

module(“Binding transform: `and`”, {

setup: function () {
  // temporarily set up two source objects in the SC namespace so we can
  // use property paths to access them
  SC.testControllerA = SC.Object.create({ value: NO });
  SC.testControllerB = SC.Object.create({ value: NO });
  SC.testControllerC = SC.Object.create({ value: NO });

  toObject = SC.Object.create({
    value: null,
    valueBinding: SC.Binding.and('SC.testControllerA.value', 'SC.testControllerB.value', 'SC.testControllerC.value'),
    localValue1: NO,
    localValue2: NO,
    localValue3: NO,
    boundLocalValue: NO,
    boundLocalValueBinding: SC.Binding.and('.localValue1', '.localValue2', '.localValue3')
  });
},

teardown: function () {
  SC.testControllerA.destroy();
  delete SC.testControllerA;
  SC.testControllerB.destroy();
  delete SC.testControllerB;
  SC.testControllerC.destroy();
  delete SC.testControllerC;
}

});

test(“bound value should be YES if all sources are YES”, function () {

SC.RunLoop.begin();
SC.testControllerA.set('value', YES);
SC.testControllerB.set('value', YES);
SC.testControllerC.set('value', YES);
SC.RunLoop.end();

SC.Binding.flushPendingChanges();
equals(toObject.get('value'), YES, 'Bound value');

SC.RunLoop.begin();
toObject.set('localValue1', YES);
toObject.set('localValue2', YES);
toObject.set('localValue3', YES);
SC.RunLoop.end();

SC.Binding.flushPendingChanges();
equals(toObject.get('boundLocalValue'), YES, 'Local bound value');

});

test(“toObject.value should be NO if either source is NO”, function () {

SC.RunLoop.begin();
SC.testControllerA.set('value', YES);
SC.testControllerB.set('value', NO);
SC.testControllerC.set('value', YES);
SC.RunLoop.end();

SC.Binding.flushPendingChanges();
equals(toObject.get('value'), NO, 'Bound value on YES/NO/YES');

SC.RunLoop.begin();
SC.testControllerA.set('value', YES);
SC.testControllerB.set('value', YES);
SC.testControllerC.set('value', YES);
SC.RunLoop.end();

SC.Binding.flushPendingChanges();
equals(toObject.get('value'), YES, 'Bound value on YES/YES/YES');

SC.RunLoop.begin();
SC.testControllerA.set('value', NO);
SC.testControllerB.set('value', YES);
SC.testControllerC.set('value', NO);
SC.RunLoop.end();

SC.Binding.flushPendingChanges();
equals(toObject.get('value'), NO, 'Bound value on NO/YES/NO');

SC.RunLoop.begin();
toObject.set('localValue1', YES);
toObject.set('localValue2', NO);
toObject.set('localValue3', YES);
SC.RunLoop.end();

SC.Binding.flushPendingChanges();
equals(toObject.get('boundLocalValue'), NO, 'Local bound value on YES/NO/YES');

SC.RunLoop.begin();
toObject.set('localValue1', YES);
toObject.set('localValue2', YES);
toObject.set('localValue3', YES);
SC.RunLoop.end();

SC.Binding.flushPendingChanges();
equals(toObject.get('boundLocalValue'), YES, 'Local bound value on YES/YES/YES');

SC.RunLoop.begin();
toObject.set('localValue1', NO);
toObject.set('localValue2', YES);
toObject.set('localValue2', NO);
SC.RunLoop.end();

SC.Binding.flushPendingChanges();
equals(toObject.get('boundLocalValue'), NO, 'Local bound value on NO/YES/NO');

});

test(“remote paths work when binding is defined on a class”, function() {

// This tests the solution to a bug which was hooking all instances of a class's `and` binding
// up through the same internal object, which would be destroyed the first time any instance
// was destroyed.

var ToObject = SC.Object.extend({
  value: null,
  valueBinding: SC.Binding.and('SC.testControllerA.value', 'SC.testControllerB.value')
});

var toObject1, toObject2;
SC.run(function() {
  toObject1 = ToObject.create();
  toObject2 = ToObject.create();
});

ok(!toObject1.get('value') && !toObject2.get('value'), "PRELIM: instances' initial values are correct.");

SC.run(function() {
  SC.testControllerA.set('value', YES);
  SC.testControllerB.set('value', YES);
});

ok(toObject1.get('value') && toObject2.get('value'), "PRELIM: instances' values update correctly.");

SC.run(function() {
  toObject1.destroy();
  SC.testControllerB.set('value', NO);
});

ok(!toObject2.get('value'), "Second instance updates correctly after first instance is destroyed.");

// Cleanup.
toObject2.destroy();

});

test(“local paths work when binding is defined on a class”, function() {

// This tests the solution to a bug which was hooking all instances of a class's `and` binding
// up through the same internal object, which would cause multiple instances to cross-polinate.

var ToObject = SC.Object.extend({
  localValue1: NO,
  localValue2: NO,
  value: NO,
  valueBinding: SC.Binding.and('.localValue1', '.localValue2')
});
var toObject1, toObject2;
SC.run(function() {
  toObject1 = ToObject.create();
  toObject2 = ToObject.create();
});

ok(!toObject1.get('value') && !toObject2.get('value'), "PRELIM: instances' initial values are correct.");

SC.run(function() {
  toObject1.set('localValue1', YES).set('localValue2', YES);
});

ok(toObject1.get('value'), "First instance updates correctly when its own values are changed.");
ok(!toObject2.get('value'), "Second instance does not update when first instance's values are changed.");

// Cleanup.
toObject1.destroy();
toObject2.destroy();

});

module(“OR binding”, {

setup: function () {
  // temporarily set up two source objects in the SC namespace so we can
  // use property paths to access them
  SC.testControllerA = SC.Object.create({ value: NO });
  SC.testControllerB = SC.Object.create({ value: null });

  toObject = SC.Object.create({
    value: null,
    valueBinding: SC.Binding.or('SC.testControllerA.value', 'SC.testControllerB.value'),
    localValue1: NO,
    localValue2: NO,
    boundLocalValue: NO,
    boundLocalValueBinding: SC.Binding.or('.localValue1', '.localValue2')
  });
},

teardown: function () {
  SC.testControllerA.destroy();
  SC.testControllerB.destroy();
}

});

test(“toObject.value should be first value if first value is truthy”, function () {

SC.RunLoop.begin();
SC.testControllerA.set('value', 'first value');
SC.testControllerB.set('value', 'second value');
SC.RunLoop.end();

SC.Binding.flushPendingChanges();
equals(toObject.get('value'), 'first value', 'Bound value on truthy first value');

SC.RunLoop.begin();
toObject.set('localValue1', 'first value');
toObject.set('localValue2', 'second value');
SC.RunLoop.end();

SC.Binding.flushPendingChanges();
equals(toObject.get('boundLocalValue'), 'first value', 'Locally bound value on truthy first value');

});

test(“toObject.value should be second value if first is falsy”, function () {

SC.RunLoop.begin();
SC.testControllerA.set('value', NO);
SC.testControllerB.set('value', 'second value');
SC.RunLoop.end();

SC.Binding.flushPendingChanges();
equals(toObject.get('value'), 'second value', 'Bound value on falsy first value');

SC.RunLoop.begin();
toObject.set('localValue1', NO);
toObject.set('localValue2', 'second value');
SC.RunLoop.end();

SC.Binding.flushPendingChanges();
equals(toObject.get('boundLocalValue'), 'second value', 'Locally bound value on falsy first value');

});

test(“remote paths work when binding is defined on a class”, function() {

// This tests the solution to a bug which was hooking all instances of a class's `or` binding
// up through the same internal object, which would be destroyed the first time any instance
// was destroyed.

var ToObject = SC.Object.extend({
  value: null,
  valueBinding: SC.Binding.or('SC.testControllerA.value', 'SC.testControllerB.value')
});

var toObject1, toObject2;
SC.run(function() {
  toObject1 = ToObject.create();
  toObject2 = ToObject.create();
});

ok(!toObject1.get('value') && !toObject2.get('value'), "PRELIM: instances' initial values are correct.");

SC.run(function() {
  SC.testControllerB.set('value', YES);
});

ok(toObject1.get('value') && toObject2.get('value'), "PRELIM: instances' values update correctly.");

SC.run(function() {
  toObject1.destroy();
  SC.testControllerB.set('value', NO);
});

ok(!toObject2.get('value'), "Second instance updates correctly after first instance is destroyed.");

// Cleanup.
toObject2.destroy();

});

test(“local paths work when binding is defined on a class”, function() {

// This tests the solution to a bug which was hooking all instances of a class's `or` binding
// up through the same internal object, which would cause multiple instances to cross-polinate.

var ToObject = SC.Object.extend({
  localValue1: NO,
  localValue2: NO,
  value: NO,
  valueBinding: SC.Binding.or('.localValue1', '.localValue2')
});
var toObject1, toObject2;
SC.run(function() {
  toObject1 = ToObject.create();
  toObject2 = ToObject.create();
});

ok(!toObject1.get('value') && !toObject2.get('value'), "PRELIM: instances' initial values are correct.");

SC.run(function() {
  toObject1.set('localValue1', YES);
});

ok(toObject1.get('value'), "First instance updates correctly when its own values are changed.");
ok(!toObject2.get('value'), "Second instance does not update when first instance's values are changed.");

// Cleanup.
toObject1.destroy();
toObject2.destroy();

});

module(“Binding transform: `mix`”, {

setup: function () {
  // temporarily set up two source objects in the SC namespace so we can
  // use property paths to access them
  SC.testControllerA = SC.Object.create({ value: 0 });
  SC.testControllerB = SC.Object.create({ value: 1 });
  SC.testControllerC = SC.Object.create({ value: 2 });

  toObject = SC.Object.create({
    value: null,
    valueBinding: SC.Binding.mix('SC.testControllerA.value', 'SC.testControllerB.value', 'SC.testControllerC.value',
                                 function(v1,v2,v3) {
                                   return v1+'-'+v2+'-'+v3;
                                 } ),
    localValue1: 1,
    localValue2: 2,
    localValue3: 3,
    boundLocalValue: NO,
    boundLocalValueBinding: SC.Binding.mix('.localValue1', '.localValue2', '.localValue3',
                                           function(v1,v2,v3) {
                                             return v1+'+'+v2+'+'+v3;
                                           } )
  });
},

teardown: function () {
  SC.testControllerA.destroy();
  delete SC.testControllerA;
  SC.testControllerB.destroy();
  delete SC.testControllerB;
  SC.testControllerC.destroy();
  delete SC.testControllerC;
}

});

test(“bound value should be calculated correctly”, function () {

SC.RunLoop.begin();
SC.testControllerA.set('value', 0);
SC.testControllerB.set('value', 10);
SC.testControllerC.set('value', 20);
SC.RunLoop.end();

SC.Binding.flushPendingChanges();
equals(toObject.get('value'), '0-10-20', 'Bound value');

SC.RunLoop.begin();
toObject.set('localValue1', 0);
toObject.set('localValue2', 10);
toObject.set('localValue3', 20);
SC.RunLoop.end();

SC.Binding.flushPendingChanges();
equals(toObject.get('boundLocalValue'), '0+10+20', 'Local bound value');

});

module(“Binding with '[]'”, {

setup: function () {
  fromObject = SC.Object.create({ value: [] });
  toObject = SC.Object.create({ value: '' });
  binding = SC.Binding.transform(function (v) {
    return v ? v.join(',') : '';
  }).from("value.[]", fromObject).to("value", toObject).connect();
}

});

test(“Binding refreshes after a couple of items have been pushed in the array”, function () {

fromObject.get('value').pushObjects(['foo', 'bar']);
SC.Binding.flushPendingChanges();
equals(toObject.get('value'), 'foo,bar');

});

module(“propertyNameBinding with longhand”, {

setup: function () {
  TestNamespace = {
    fromObject: SC.Object.create({
      value: "originalValue"
    }),
    toObject: SC.Object.create({
      valueBinding: SC.Binding.from('TestNamespace.fromObject.value'),
      localValue: "originalLocal",
      relativeBinding: SC.Binding.from('.localValue')
    })
  };
},
teardown: function () {
  TestNamespace.fromObject.destroy();
  TestNamespace.toObject.destroy();
  TestNamespace = null;
}

});

test(“works with full path”, function () {

SC.RunLoop.begin();
TestNamespace.fromObject.set('value', "updatedValue");
SC.RunLoop.end();

equals(TestNamespace.toObject.get('value'), "updatedValue");

SC.RunLoop.begin();
TestNamespace.fromObject.set('value', "newerValue");
SC.RunLoop.end();

equals(TestNamespace.toObject.get('value'), "newerValue");

});

test(“works with local path”, function () {

SC.RunLoop.begin();
TestNamespace.toObject.set('localValue', "updatedValue");
SC.RunLoop.end();

equals(TestNamespace.toObject.get('relative'), "updatedValue");

SC.RunLoop.begin();
TestNamespace.toObject.set('localValue', "newerValue");
SC.RunLoop.end();

equals(TestNamespace.toObject.get('relative'), "newerValue");

});

module(“Overriding binding in subclass”, {

setup: function() {
  FromObject = SC.Object.extend({
    localValue1: 'hello',
    localValue2: 'world',
    value: null,
    valueBinding: SC.Binding.oneWay('.localValue1')
  });
},
teardown: function() {
  FromObject = null;
}

});

test(“Bindings override in subclasses.”, function() {

SC.LOG_DUPLICATE_BINDINGS = NO; // clean consoles

SC.RunLoop.begin();
fromObject = FromObject.create();
SC.RunLoop.end();

equals(fromObject.get('value'), 'hello', "PRELIM: Superclass binding gives value of");

fromObject.destroy();

SC.RunLoop.begin();
fromObject = FromObject.create({
  valueBinding: SC.Binding.oneWay('.localValue2')
});
SC.RunLoop.end();

ok(fromObject._bindings.length === 1, "Duplicate bindings are not created.");

equals(fromObject.get('value'), 'world', "Superclass binding should have been overridden in the subclass, giving value a value of");

fromObject.destroy();

SC.LOG_DUPLICATE_BINDINGS = YES;

});