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' });

        });

    })

});

