describe("Ext.Util", function() {
    describe("Ext.callback", function() {
        var spy;

        beforeEach(function() {
            spy = jasmine.createSpy();
        });

        it('should not fail if given a null callback', function () {
            expect(function() {
                Ext.callback(null);
            }).not.toThrow();
        });

        it('should not fail if given an undefined callback', function () {
            expect(function() {
                Ext.callback(undefined);
            }).not.toThrow();
        });

        it('should not fail if given an invalid callback', function () {
            expect(function() {
                Ext.callback(42);
            }).not.toThrow();
        });

        it("should pass arguments to the callback function", function() {
            Ext.callback(spy, fakeScope, [1, 2, 3, 4, 6]);
            expect(spy).toHaveBeenCalledWith(1, 2, 3, 4,6);
        });

        it("should be able to defer function call", function() {
            runs(function() {
                Ext.callback(spy, undefined, [1, 2, 3, 4, 6], 1);
                expect(spy).not.toHaveBeenCalled();
            });
            waitsFor(function() {
                return spy.callCount > 0;
            }, "deferred callback never called");
            runs(function() {
                expect(spy).toHaveBeenCalledWith(1, 2, 3, 4, 6);
            });

        });

        it("should return the return value of the given function", function () {
            var x = 0,
                fn = function (y) {
                    return x = this.z * 10 + y;
                },
                y = Ext.callback(fn, {z: 42}, [7]);

            expect(x).toBe(427);
            expect(y).toBe(427);
        });
        
        describe("scoping", function() {
            describe("with a function", function() {
                describe("scope 'this'", function() {
                    it("should resolve the scope to the defaultScope", function() {
                        Ext.callback(spy, 'this', undefined, undefined, undefined, fakeScope);
                        expect(spy.mostRecentCall.object).toBe(fakeScope);
                    });

                    it("should resolve the scope to the caller", function() {
                        Ext.callback(spy, 'this', undefined, undefined, fakeScope);
                        expect(spy.mostRecentCall.object).toBe(fakeScope);
                    });

                    it("should prefer the defaultScope to the caller", function() {
                        Ext.callback(spy, 'this', undefined, undefined, {}, fakeScope);
                        expect(spy.mostRecentCall.object).toBe(fakeScope);
                    });

                    it("should fallback to global scope if no caller is passed", function() {
                        Ext.callback(spy, 'this');
                        expect(spy.mostRecentCall.object).toBe(Ext.global);
                    });
                });

                describe("scope 'controller'", function() {
                    it("should resolve the scope to the defaultScope", function() {
                        Ext.callback(spy, 'controller', undefined, undefined, undefined, fakeScope);
                        expect(spy.mostRecentCall.object).toBe(fakeScope);
                    });

                    it("should resolve the scope to the caller", function() {
                        Ext.callback(spy, 'controller', undefined, undefined, fakeScope);
                        expect(spy.mostRecentCall.object).toBe(fakeScope);
                    });

                    it("should prefer the defaultScope to the caller", function() {
                        Ext.callback(spy, 'controller', undefined, undefined, {}, fakeScope);
                        expect(spy.mostRecentCall.object).toBe(fakeScope);
                    });

                    it("should fallback to global scope if no caller is passed", function() {
                        Ext.callback(spy, 'controller');
                        expect(spy.mostRecentCall.object).toBe(Ext.global);
                    });
                });

                it("should execute the passed function in the specified scope", function() {
                    Ext.callback(spy, fakeScope);
                    expect(spy.mostRecentCall.object).toBe(fakeScope);
                });
                
                it("should default the scope to Ext.global", function() {
                    Ext.callback(spy);
                    expect(spy.mostRecentCall.object).toBe(Ext.global);
                });
            });
            
            describe("with a string", function() {
                var scopeInfo;
                
                beforeEach(function() {
                    scopeInfo = {
                        foo: function() {
                            
                        }
                    };
                    spyOn(scopeInfo, 'foo');
                });
                
                describe("without caller", function() {
                    it("should throw if no scope is passed", function() {
                        expect(function() {
                            Ext.callback('foo');
                        }).toThrow();
                    });
                    
                    it("should throw if the method cannot be found on the passed scope", function() {
                        expect(function() {
                            Ext.callback('foo', {});
                        }).toThrow();
                    });

                    // Can't resolve the string scope without a caller
                    it("should throw if passed 'this' as scope", function() {
                        expect(function() {
                            Ext.callback('foo', 'this');
                        }).toThrow();
                    });

                    it("should throw if passed 'controller' as scope", function() {
                        expect(function() {
                            Ext.callback('foo', 'controller');
                        }).toThrow();
                    });
                    
                    it("should call the resolved method on the passed scope", function() {
                        Ext.callback('foo', scopeInfo);
                        expect(scopeInfo.foo).toHaveBeenCalled();
                        expect(scopeInfo.foo.mostRecentCall.object).toBe(scopeInfo);
                    });
                    
                    it("should retain scope on defer", function() {
                        runs(function() {
                            Ext.callback('foo', scopeInfo, undefined, 1);
                            expect(scopeInfo.foo).not.toHaveBeenCalled();
                        });
                        waitsFor(function() {
                            return scopeInfo.foo.callCount > 0;
                        }, "deferred callback never called");
                        runs(function() {
                            expect(scopeInfo.foo).toHaveBeenCalled();
                            expect(scopeInfo.foo.mostRecentCall.object).toBe(scopeInfo);
                        });
                    });
                });
                
                describe("with caller", function() {
                    var theScope, caller;
                    beforeEach(function() {
                        theScope = {
                            foo: function() {
                                
                            }
                        };
                        caller = {
                            resolveListenerScope: function() {
                                return theScope;
                            }
                        };
                        spyOn(theScope, 'foo');
                    });
                    
                    describe("object scope", function() {
                        it("should favour a passed scope", function() {
                            Ext.callback('foo', scopeInfo, undefined, undefined, caller);
                            expect(scopeInfo.foo).toHaveBeenCalled();
                            expect(scopeInfo.foo.mostRecentCall.object).toBe(scopeInfo);
                            expect(theScope.foo).not.toHaveBeenCalled();
                        });
                    
                        it("should throw if the method cannot be found on the passed caller", function() {
                            expect(function() {
                                Ext.callback('fake', undefined, undefined, undefined, caller);
                            }).toThrow();
                        });
                    
                        it("should call the resolved method on the passed scope", function() {
                            Ext.callback('foo', undefined, undefined, undefined, caller);
                            expect(theScope.foo).toHaveBeenCalled();
                            expect(theScope.foo.mostRecentCall.object).toBe(caller.resolveListenerScope());
                        });
                    
                        it("should retain scope on defer", function() {
                            runs(function() {
                                Ext.callback('foo', undefined, undefined, 1, caller);
                                expect(theScope.foo).not.toHaveBeenCalled();
                            });
                            waitsFor(function() {
                                return theScope.foo.callCount > 0;
                            }, "deferred callback never called");
                            runs(function() {
                                expect(theScope.foo).toHaveBeenCalled();
                                expect(theScope.foo.mostRecentCall.object).toBe(caller.resolveListenerScope());
                            });
                        });
                    });

                    describe("scope: 'this'", function() {
                        it("should call resolveListenerScope on the caller", function() {
                            spyOn(caller, 'resolveListenerScope').andCallThrough();
                            Ext.callback('foo', 'this', undefined, undefined, caller);
                            expect(caller.resolveListenerScope).toHaveBeenCalledWith('this');
                            expect(theScope.foo).toHaveBeenCalled();
                            expect(theScope.foo.mostRecentCall.object).toBe(theScope);
                        });

                        it("should throw if the method cannot be found on the passed caller", function() {
                            expect(function() {
                                Ext.callback('fake', 'this', undefined, undefined, caller);
                            }).toThrow();
                        });

                        it("should retain scope on defer", function() {
                            runs(function() {
                                Ext.callback('foo', 'this', undefined, 1, caller);
                                expect(theScope.foo).not.toHaveBeenCalled();
                            });
                            waitsFor(function() {
                                return theScope.foo.callCount > 0;
                            }, "deferred callback never called");
                            runs(function() {
                                expect(theScope.foo).toHaveBeenCalled();
                                expect(theScope.foo.mostRecentCall.object).toBe(theScope);
                            });
                        });
                    });

                    describe("scope: 'controller'", function() {
                        it("should call resolveListenerScope on the caller", function() {
                            spyOn(caller, 'resolveListenerScope').andCallThrough();
                            Ext.callback('foo', 'controller', undefined, undefined, caller);
                            expect(caller.resolveListenerScope).toHaveBeenCalledWith('controller');
                            expect(theScope.foo).toHaveBeenCalled();
                            expect(theScope.foo.mostRecentCall.object).toBe(theScope);
                        });

                        it("should throw if the method cannot be found on the passed caller", function() {
                            expect(function() {
                                Ext.callback('fake', 'controller', undefined, undefined, caller);
                            }).toThrow();
                        });

                        it("should retain scope on defer", function() {
                            runs(function() {
                                Ext.callback('foo', 'controller', undefined, 1, caller);
                                expect(theScope.foo).not.toHaveBeenCalled();
                            });
                            waitsFor(function() {
                                return theScope.foo.callCount > 0;
                            }, "deferred callback never called");
                            runs(function() {
                                expect(theScope.foo).toHaveBeenCalled();
                                expect(theScope.foo.mostRecentCall.object).toBe(theScope);
                            });
                        });
                    });
                });
            });
        });
    }); // Ext.callback

    describe('copyToIf String[]', function () {
        var dest;
        var source = { a: 1, b: 'x', c: 42 };

        beforeEach(function () {
            dest = { a: 427 };
        });

        it('should leave existing properties alone', function () {
            Ext.copyToIf(dest, source, ['a']);
            expect(dest).toEqual({ a: 427 });
        });

        it('should add new properties', function () {
            Ext.copyToIf(dest, source, ['a','b']);
            expect(dest).toEqual({ a: 427, b: 'x' });
        });
    });

    describe('copyToIf String', function () {
        var dest;
        var source = { a: 1, b: 'x', c: 42 };

        beforeEach(function () {
            dest = { a: 427 };
        });

        it('should leave existing properties alone', function () {
            Ext.copyToIf(dest, source, 'a');
            expect(dest).toEqual({ a: 427 });
        });

        it('should add new properties', function () {
            Ext.copyToIf(dest, source, 'a,b');
            expect(dest).toEqual({ a: 427, b: 'x' });
        });
    })
});
