/**
 * WatchJS Test
 * 
 */


// Force dirty checks 
// ------------------------
// WatchJS.useDirtyCheck = true;

QUnit.module("Watch object")
QUnit.config.testTimeout = 30000;
QUnit.test( "Observe changes on an object property", function( assert ) {
    assert.expect(6);
    var done1 = assert.async();
    var done2 = assert.async();
    var o = {
        name: 'Mary',
        email:'mary@domain.com'
    };
    
    WatchJS.watch(o,"name", function(prop, action, newvalue, oldvalue) {
        assert.equal( "name",prop, "name: property name matched" );
        assert.equal( "Paul",newvalue, "new value matched" );
        assert.equal( "Mary",oldvalue, "old value matched" );
        done1();
    });

    WatchJS.watch(o,"email", function(prop, action, newvalue, oldvalue) {
        assert.equal( "email",prop, "eamil: property name matched" );
        assert.equal( "paul@domain.com",newvalue, "new value matched" );
        assert.equal( "mary@domain.com",oldvalue, "old value matched" );
        done2();
    });
    
    o.name = 'Paul';
    o.email = 'paul@domain.com';
});

QUnit.test("Observe the changes of more than one object properties", function( assert ) {
    assert.expect(4);
    var done1 = assert.async();
    var done2 = assert.async();
    var obj = {
        attr1: 1,
        attr2: 2,
        attr3: 3
    };
    
    WatchJS.watch(obj, ["attr2", "attr3"], function(prop, action, newvalue, oldvalue){
        if (prop==='attr2') {
            assert.strictEqual(50, newvalue, "attr2: new value matched ");
            assert.strictEqual(2, oldvalue, "attr2: old value matched ");            
            done1();
        }
        else if (prop==='attr3') {
            assert.strictEqual(100, newvalue, "attr3: new value matched " );        
            assert.strictEqual(3, oldvalue, "attr3: old value matched " );        
            done2();
        }
        else {
            throw new Exception(prop+": Invalid property");
        }
    });
    
    obj.attr2 = 50;
    obj.attr3 = 100;
});

QUnit.test("Observe the changes of all properties of the object", function( assert ) {
    assert.expect(3);
    var done1 = assert.async();
    var obj = {
        attr1: 1,
        attr2: 2,
        attr3: 3
    };
    
    WatchJS.watch(obj, function(prop, action, newvalue, oldvalue){
        assert.equal( "attr3",prop, "attr3: name match" );
        assert.strictEqual(100, newvalue, "attr2: new value matched ");
        assert.strictEqual(3, oldvalue, "attr2: old value matched ");            
        done1();
    });
    
    obj.attr3 = 100;
});

QUnit.test("Add and remove watcher", function( assert ) {
    assert.expect(2);
    var done1 = assert.async();
    var done2 = assert.async();
    var obj = {
        attr1: 1,
        attr2: 2,
        attr3: 3
    };

    var callback = function(prop, action, newvalue, oldvalue){
        changeDetected++ ;
        assert.ok(changeDetected===1, "attr1: Watcher added");
        done1();
        
        if (changeDetected===1) {
            setTimeout(function() {
                assert.ok(changeDetected === 1, "attr1: Watcher removed");
                done2();
            },60);
            WatchJS.unwatch(obj,"attr1", callback);  // remove watcher from attr1  
            obj.attr1 = 125;
            
        }
    }
    
    var changeDetected = 0;
    WatchJS.watch(obj, "attr1", callback);
    
    obj.attr1 = 250;        
});


QUnit.test("Add and remove multiple watchers", function( assert ) {
    assert.expect(3);
    var done1 = assert.async();
    var done2 = assert.async();
    var done3 = assert.async();
    var obj = {
        attr1: 1,
        attr2: 2,
        attr3: 3
    };

    var callback1 = function(prop, action, newvalue, oldvalue){
        changeDetected++ ;
        assert.ok(changeDetected===1, "attr1: Watcher 1 added");
        done1();        
    }
    
    var callback2 = function(prop, action, newvalue, oldvalue){
        changeDetected++ ;
        assert.ok(changeDetected===2, "attr1: Watcher 2 added");
        done2();
        
        if (changeDetected===2) {
            setTimeout(function() {
                assert.ok(changeDetected === 2, "attr1: Watchers removed");
                done3();
            },100);            
            WatchJS.unwatch(obj,"attr1");    // remove all watchers of attr1
            obj.attr1 = 1245;
        }
    }    
    
    var changeDetected = 0;
    WatchJS.watch(obj, "attr1", callback1);
    WatchJS.watch(obj, "attr1", callback2);
    obj.attr1 = 10;
        
});

QUnit.test("Suspend object watchers", function( assert ) {
    assert.expect(5);
    var done1 = assert.async();
    var done2 = assert.async();
    var done3 = assert.async();
    var done4 = assert.async();
    var obj = {
        attr1: 'value 1',
        attr2: 'value 2',
        attr3: 'value 3'
    };
    
    var obj2 = {
        attrA: 123,
        attrB: 456,
        attrC: 789
    };    
   
    WatchJS.watch(obj, 'attr1', function(prop, action, newvalue, oldvalue){
        assert.equal( "new value 1",newvalue, "attr1: changed" );
        obj.attr2 = "new value 2";
        
        // temporarily suspend all watchers on obj2
        WatchJS.suspend(obj2);
        obj2.attrA = 19;
        obj2.attrB = 712;
        obj2.attrC = 143;
        
        // set timeout to trigger changes to obj2 in next update cycle
        setTimeout(function(){ 
            obj2.attrA = 2192; // this change will trigger watchers;
        }, 200)
        
        done1();
    });

    WatchJS.watch(obj, 'attr2', function(prop, action, newvalue, oldvalue){
        WatchJS.suspend(obj,'attr1'); // temporarily suspend changes to attr1 to prevent inifite loop
        assert.equal( "new value 2",newvalue, "attr2: changed");
        obj.attr1 = 'new value 3'; // this change should  not trigger wattchers
        obj.attr3 = 'new value 4'; // this change should invoke watchers
        done2();
    });
    
    WatchJS.watch(obj, 'attr3', function(prop, action, newvalue, oldvalue){
        assert.equal( "new value 4",newvalue, "attr3: changed" );
        done3();
    });
    
    WatchJS.watch(obj2, function(prop, action, newvalue, oldvalue) {
        assert.equal(prop, 'attrA', prop+": changed" ); // only changes to attrA should trigger watcher 
        assert.equal(newvalue, 2192, "value set to " + newvalue); // only changes to attrA should trigger watcher 
        done4();
    });
    
    obj.attr1 = 'new value 1';
        
});


QUnit.test("Observe changes on all nested objects", function( assert ) {
    assert.expect(6);
    var done1 = assert.async();
    var done2 = assert.async();
    var obj = {
        attr1: 1,
        attr2: 2,
        object1:  {
            attrA: 3,
            attrB: 4,
            object2: {
                attrD: 123
            }
        }
    };
    
    WatchJS.watch(obj, function(prop, action, newvalue, oldvalue){
        if (obj.object1 === this) {
            assert.equal( "attrA",prop, "obj.object1.attrA: name matched" );
            assert.strictEqual(100, newvalue, "obj.object1.attrA: new value matched ");
            assert.strictEqual(3, oldvalue, "obj.object1.attrA: old value matched ");            
            done1();
        }
        else if (obj.object1.object2 === this) {
            assert.equal( "attrD",prop, "obj.object1.object2.attrD: name matched" );
            assert.strictEqual(240, newvalue, "obj.object1.object2.attrD: new value matched ");
            assert.strictEqual(123, oldvalue, "obj.object1.object2.attrD: old value matched ");            
            done2();
        }
    });
    
    obj.object1.attrA = 100;
    obj.object1.object2.attrD = 240;
});


QUnit.test("Observe changes on level 1 nested objects", function( assert ) {
    assert.expect(3);
    var done1 = assert.async();
    var obj = {
        attr1: 1,
        attr2: 2,
        object1:  {
            attrA: 3,
            attrB: 4,
            object2: {
                attrD: 123
            }
        }
    };
    
    var level = 1; // set watch level
    WatchJS.watch(obj, function(prop, action, newvalue, oldvalue){
        assert.equal( "attrA",prop, "obj.object1.attrA: name matched" );
        assert.strictEqual(100, newvalue, "obj.object1.attrA: new value matched ");
        assert.strictEqual(3, oldvalue, "obj.object1.attrA: old value matched ");            
        done1();
    }, level);
    
    obj.object1.attrA = 100;    // this should trigger the obj watchers
    obj.object1.object2.attrD = 240; // this will not triggers obj watchers
});

QUnit.test("Observe new object properties", function( assert ) {
    assert.expect(4);
    var done1 = assert.async();
    var obj = {
        attr1: 1,
        attr2: 2,
        attr3: 3
    };
    
    var observeNewProp = true; // observer new and deleted props
    WatchJS.watch(obj, function(prop, action, newvalue, oldvalue){
        
        var oldObj = {
            attr1: 1,
            attr2: 2,
            attr3: 3
        };
        
        var diff =  {
            added: ['attrA'],
            removed: []
        }
        assert.equal( "root" ,prop, "root property matched" );
        assert.equal( "differentattr" ,action, "action matched" );
        assert.deepEqual( diff ,newvalue, "New property attrA added" );
        assert.deepEqual(oldObj, oldvalue, "Old value matched ");
        done1();
    },undefined,observeNewProp);
    
    obj.attrA = 100;    // new property
});


QUnit.test("Observe deleted object properties", function( assert ) {
    assert.expect(6);
    var done1 = assert.async();
    var done2 = assert.async();
    var obj = {
        attr1: 1,
        attr2: 2,
        attr3: 3
    };
    
    var observeNewProp = true; // observer new and deleted props
    WatchJS.watch(obj, function(prop, action, newvalue, oldvalue){
        
        var oldObj = {
            attr1: 1,
            attr2: 2,
            attr3: 3
        };
        
        var diff =  {
            added: [],
            removed: ['attr1']
        }
        
        if (action==='differentattr') {
            assert.equal( "root" ,prop, " root property matched" );
            assert.deepEqual( diff ,newvalue, "New property attrA added" );
            assert.deepEqual(oldObj, oldvalue, "Old value matched ");
            done1();
        }
        else {
            assert.equal( "attr1" ,prop, "attr1 property matched" );
            assert.strictEqual( undefined ,newvalue, "attr1 property updated" );
            assert.strictEqual( 1, oldvalue, "Old value matched " );
            done2();
        }
    },undefined,observeNewProp);
    
    delete obj.attr1;    // delete property
});

QUnit.test("Invoke object property watchers", function( assert ) {
    assert.expect(4);
    var done1 = assert.async();
            
    var obj = {
        attr1: 1,
        attr2: 2,
        attr3: 3
    };    
    
    WatchJS.watch(obj, function(prop, action, newvalue, oldvalue){
        assert.equal( "attr2",prop, "attr2: name matched" );
        assert.equal( "set",action, "action matched" );
        assert.strictEqual(20, newvalue, "attr2: new value matched ");
        assert.strictEqual(2, oldvalue, "attr2: old value matched ");            
        done1();
    });
    
    // invoke watchers
    var newval = 20, oldval = obj.attr2;
    WatchJS.callWatchers(obj,'attr2', 'set', newval, oldval);
});    

// -- array test -------

QUnit.module("Watch array")
QUnit.test("Observe array push changes", function( assert ) {
    assert.expect(8);
    var done1 = assert.async();
    var done2 = assert.async();
            
    var list = [1,2,3,4];
    var obj = { 
        items: [1,2,3,4] 
    }
    
    var fn = function(prop, action, newvalue, oldvalue){
        assert.equal( "4",prop, "index of new item matched" );
        assert.equal( "push",action, "action matched" );
        assert.strictEqual(5, newvalue, "new value matched ");
        assert.strictEqual(undefined, oldvalue, "old value matched ");            
        if (this===obj.items) {
            done2();
        } 
        else {
            done1();
        }
    };
    
    WatchJS.watch(list, fn);
    WatchJS.watch(obj,'items', fn); 
        
    list.push(5);
    obj.items.push(5);
}); 

QUnit.test("Observe array pop changes", function( assert ) {
    assert.expect(8);
    var done1 = assert.async();
    var done2 = assert.async();
            
    var list = [1,2,3,4]
    var obj = { 
        items: [1,2,3,4] 
    }
    
    var fn = function(prop, action, newvalue, oldvalue){
        assert.equal( "3",prop, "index of removed item matched" );
        assert.equal( "pop",action, "action matched" );
        assert.strictEqual(undefined, newvalue, "new value matched ");
        assert.strictEqual(4, oldvalue, "old value matched ");            
        if (this===obj.items) {
            done2();
        } 
        else {
            done1();
        }
    };
    
    WatchJS.watch(list, fn);
    WatchJS.watch(obj,'items', fn); 
    
    list.pop();
    obj.items.pop();
});

QUnit.test("Observe array shift changes", function( assert ) {
    assert.expect(8);
    var done1 = assert.async();
    var done2 = assert.async();
    
    var list = [1,2,3,4]
    var obj = { 
        items: [1,2,3,4] 
    }
    
    var fn = function(prop, action, newvalue, oldvalue){
        assert.equal( "0",prop, "index of removed item matched" );
        assert.equal( "shift",action, "action matched" );
        assert.strictEqual(undefined, newvalue, "new value matched ");
        assert.strictEqual(1, oldvalue, "old value matched ");            
        if (this===obj.items) {
            done2();
        } 
        else {
            done1();
        }
    };
    
    WatchJS.watch(list, fn);
    WatchJS.watch(obj,'items', fn); 
    
    
    list.shift();
    obj.items.shift();
});

QUnit.test("Observe array unshift changes", function( assert ) {
    assert.expect(8);
    var done1 = assert.async();
    var done2 = assert.async();
            
    var list = [1,2,3,4]
    var obj = { 
        items: [1,2,3,4] 
    }
    
    var fn = function(prop, action, newvalue, oldvalue){
        assert.equal( "0",prop, "index of new item matched" );
        assert.equal( "unshift",action, "action matched" );
        assert.strictEqual(12, newvalue, "new value matched ");
        assert.strictEqual(undefined, oldvalue, "old value matched ");            
        if (this===obj.items) {
            done2();
        } 
        else {
            done1();
        }
    };
    
    WatchJS.watch(list, fn);
    WatchJS.watch(obj,'items', fn); 
    
    list.unshift(12);
    obj.items.unshift(12);
});

QUnit.test("Observe array sort changes", function( assert ) {
    assert.expect(8);
    var done1 = assert.async();
    var done2 = assert.async();
            
    var list = [5,8,1,3]
    var obj = { 
        items: [5,8,1,3]
    }
    
    var fn = function(prop, action, newvalue, oldvalue){
        assert.equal( "0",prop, "index matched" );
        assert.equal( "sort",action, "action matched" );
        assert.deepEqual(newvalue, [1,3,5,8], "new value matched ");
        assert.strictEqual(oldvalue, undefined, "old value matched ");            
        if (this===obj.items) {
            done2();
        } 
        else {
            done1();
        }
    };
    
    WatchJS.watch(list, fn);
    WatchJS.watch(obj,'items', fn); 
    
    list.sort();
    obj.items.sort();
});

QUnit.test("Observe array reverse changes", function( assert ) {
    assert.expect(8);
    var done1 = assert.async();
    var done2 = assert.async();
            
    var list = [1,2,3,4,5]
    var obj = { 
        items: [1,2,3,4,5]
    }
    
    var fn = function(prop, action, newvalue, oldvalue){
        assert.equal( "0",prop, "index  matched" );
        assert.equal( "reverse",action, "action matched" );
        assert.deepEqual(newvalue, [5,4,3,2,1], "new value matched ");
        assert.deepEqual(oldvalue, undefined, "old value matched ");            
        if (this===obj.items) {
            done2();
        } 
        else {
            done1();
        }
    };
    
    WatchJS.watch(list, fn);
    WatchJS.watch(obj,'items', fn); 
    
    list.reverse();
    obj.items.reverse();
});

QUnit.test("Observe array splice changes - add items", function( assert ) {
    assert.expect(10);
    var done1 = assert.async();
    var done2 = assert.async();
            
    var list = [1,2,3,4,5]
    var obj = { 
        items: [1,2,3,4,5]
    }
    var expectedResult = [1,6,8,9,2,3,4,5];
    
    var fn = function(prop, action, newvalue, oldvalue){
        assert.equal( "1",prop, "start index of spliced items matched" );
        assert.equal( "splice",action, "action matched" );
        assert.deepEqual(newvalue, [6,8,9], "new value matched");
        assert.deepEqual(oldvalue, [], "old value matched");            
        assert.deepEqual(this, expectedResult, "result matched");            
        if (this===obj.items) {
            done2();
        } 
        else {
            done1();
        }
    };
    
    WatchJS.watch(list, fn);
    WatchJS.watch(obj,'items', fn); 
    
    list.splice(1,0,6,8,9);
    obj.items.splice(1,0,6,8,9);
});

QUnit.test("Observe array splice changes - remove items", function( assert ) {
    assert.expect(10);
    var done1 = assert.async();
    var done2 = assert.async();
            
    var list = [1,2,3,4,5]
    var obj = { 
        items: [1,2,3,4,5]
    }
    var expectedResult = [1,4,5];
    
    var fn = function(prop, action, newvalue, oldvalue){
        assert.equal( "1",prop, "start index of spliced items matched" );
        assert.equal( "splice",action, "action matched" );
        assert.deepEqual(newvalue, [], "new value matched");
        assert.deepEqual(oldvalue, [2,3], "old value matched");            
        assert.deepEqual(this, expectedResult, "result matched");            
        if (this===obj.items) {
            done2();
        } 
        else {
            done1();
        }
    };
    
    WatchJS.watch(list, fn);
    WatchJS.watch(obj,'items', fn); 
    
    list.splice(1,2);
    obj.items.splice(1,2);
});

QUnit.test("Observe array splice changes - add andremove items", function( assert ) {
    assert.expect(10);
    var done1 = assert.async();
    var done2 = assert.async();
            
    var list = [1,2,3,4,5,6]
    var obj = { 
        items: [1,2,3,4,5,6]
    }
    var expectedResult = [1,2,9,0,4,'A',2,6];
    
    var fn = function(prop, action, newvalue, oldvalue){
        assert.equal( "2",prop, "start index of spliced items matched" );
        assert.equal( "splice",action, "action matched" );
        assert.deepEqual(newvalue, [9,0,4,'A',2], "new value matched");
        assert.deepEqual(oldvalue, [3,4,5], "old value matched");            
        assert.deepEqual(this, expectedResult, "result matched");            
        if (this===obj.items) {
            done2();
        } 
        else {
            done1();
        }
    };
    
    WatchJS.watch(list, fn);
    WatchJS.watch(obj,'items', fn); 
    
    list.splice(2,3,9,0,4,'A',2);
    obj.items.splice(2,3,9,0,4,'A',2);
});


QUnit.test("Observe array splice changes - add andremove items", function( assert ) {
    assert.expect(10);
    var done1 = assert.async();
    var done2 = assert.async();
            
    var list = [1,2,3,4,5,6]
    var obj = { 
        items: [1,2,3,4,5,6]
    }
    var expectedResult = [1,2,9,0,4,'A',2,6];
    
    var fn = function(prop, action, newvalue, oldvalue){
        assert.equal( "2",prop, "start index of spliced items matched" );
        assert.equal( "splice",action, "action matched" );
        assert.deepEqual(newvalue, [9,0,4,'A',2], "new value matched");
        assert.deepEqual(oldvalue, [3,4,5], "old value matched");            
        assert.deepEqual(this, expectedResult, "result matched");            
        if (this===obj.items) {
            done2();
        } 
        else {
            done1();
        }
    };
    
    WatchJS.watch(list, fn);
    WatchJS.watch(obj,'items', fn); 
    
    list.splice(2,3,9,0,4,'A',2);
    obj.items.splice(2,3,9,0,4,'A',2);
});


// -- WatchJS.onChange test ---

QUnit.module("onChange")
QUnit.test("Track object changes", function( assert ) {
    assert.expect(3);
    var done1 = assert.async();
    var done2 = assert.async();
    var done3 = assert.async();
            
    var obj = { 
        attr1:  23,
        attr2:  40,
        color:  'green',
        items: [1,2,3,4,5,6]
    }
    var expectedChange1 = {
        type: 'update',
        value : obj,        
        splices: null
    };
    
    var expectedChange2 = {
        type: 'update',
        value : obj.items,        
        splices: [
            {
                index: 2,
                deleteCount: 3,
                addedCount: 5,
                added: [9,0,4,'A',2],
                deleted: [3,4,5]
            }
        ]
    };    
    
    WatchJS.onChange(obj,function(change){
        assert.deepEqual(change, expectedChange1, "change result matched");            
        done1();
    });
    
    WatchJS.onChange(obj,'color',function(change){
        assert.equal(change.value, 'red', "color property change result matched");            
        done2();
    });
    WatchJS.onChange(obj,'items',function(change){
        assert.deepEqual(change, expectedChange2, "items array change result matched");            
        done3();
    });
        
    obj.attr2 = 124;
    obj.color = 'red';
    obj.items.splice(2,3,9,0,4,'A',2);
});



