describe("Ext.Function", function() { var _setTimeout, _clearTimeout, timeouts, timeoutIds, clearedTimeoutIds, runAfterInvocation = function(spyedFunction, callback, invocationCount) { invocationCount = invocationCount || 1; waitsFor(function() { return spyedFunction.calls.length >= invocationCount; }); runs(callback); }, mockTimeout = function() { timeouts = []; timeoutIds = []; clearedTimeoutIds = []; _setTimeout = window.setTimeout; window.setTimeout = function(fn, timeout) { timeouts.push(timeout); var timeoutId = _setTimeout.apply(this, arguments); timeoutIds.push(timeoutId); return timeoutId; }; _clearTimeout = window.clearTimeout; window.clearTimeout = function(timeoutId) { clearedTimeoutIds.push(timeoutId); _clearTimeout.apply(this, arguments); }; }, unmockTimeout = function() { timeouts = undefined; timeoutIds = undefined; clearedTimeoutIds = undefined; window.setTimeout = _setTimeout; window.clearTimeout = _clearTimeout; }; describe("bind", function() { var fn, bind; beforeEach(function() { fn = jasmine.createSpy("bindSpy"); }); it("should return a function if a function is passed as first argument", function() { bind = Ext.Function.bind(fn, this); expect(typeof bind === "function").toBe(true); }); it("should use the correct scope", function() { bind = Ext.Function.bind(fn, fakeScope); bind(); expect(fn.calls[0].object).toBe(fakeScope); }); it("should call the first function when it is executed", function() { bind = Ext.Function.bind(fn, this); bind(); expect(fn).toHaveBeenCalled(); }); describe("argument passing", function() { it("should use default args if none are passed", function() { bind = Ext.Function.bind(fn, this, ['a', 'b']); bind(); expect(fn).toHaveBeenCalledWith('a', 'b'); }); it("should use passed args if they are present", function() { bind = Ext.Function.bind(fn, this); bind('c', 'd'); expect(fn).toHaveBeenCalledWith('c', 'd'); }); it("should append args", function() { bind = Ext.Function.bind(fn, this, ['a', 'b'], true); bind('c', 'd'); expect(fn).toHaveBeenCalledWith('c', 'd', 'a', 'b'); }); it("should append args at the given index", function() { bind = Ext.Function.bind(fn, this, ['a', 'b'], 0); bind('c', 'd'); expect(fn).toHaveBeenCalledWith('a', 'b', 'c', 'd'); }); }); }); describe("pass", function() { it("should pass the specified array of arguments as the first arguments to the given function", function() { var fn = jasmine.createSpy(), args = [0, 1, 2], callback = Ext.Function.pass(fn, args); callback(3, 4, 5); expect(fn).toHaveBeenCalledWith(0, 1, 2, 3, 4, 5); }); it("should pass the specified string argument as the first argument to the given function", function() { var fn = jasmine.createSpy(), args = 'a', callback = Ext.Function.pass(fn, args); callback('b', 'c'); expect(fn).toHaveBeenCalledWith('a', 'b', 'c'); }); it("should pass the specified numeric argument as the first argument to the given function", function() { var fn = jasmine.createSpy(), args = 0, callback = Ext.Function.pass(fn, args); callback(1); expect(fn).toHaveBeenCalledWith(0, 1); }); it("should pass the specified 'arguments' argument as the first argument to the given funciton", function() { var testFunction = function () { var fn = jasmine.createSpy(), args = arguments, callback = Ext.Function.pass(fn, args); callback(3, 4, 5); expect(fn).toHaveBeenCalledWith(0, 1, 2, 3, 4, 5); }; testFunction(0, 1, 2); }); it("should discard the argument if it's undefined", function() { var fn = jasmine.createSpy(), args = undefined, callback = Ext.Function.pass(fn, args); callback(1); expect(fn).toHaveBeenCalledWith(1); }); it("should use 'this' as default scope", function() { var foo = 'a', fn = jasmine.createSpy().andCallFake(function() { foo = this.foo; }), callback = Ext.Function.pass(fn, 'c'); callback('d'); expect(fn).toHaveBeenCalledWith('c', 'd'); expect(foo).toBeUndefined(); }); it("should override 'this' with the specified scope", function() { var foo = 'a', scope = { foo: 'b' }, fn = jasmine.createSpy().andCallFake(function() { foo = this.foo; }), callback = Ext.Function.pass(fn, 'c', scope); callback('d'); expect(fn).toHaveBeenCalledWith('c', 'd'); expect(foo).toBe('b'); }); }); describe("clone", function() { it("should clone the given function", function() { var fn = jasmine.createSpy().andCallFake(function(arg) { return 'bar'; }), clonedFn = Ext.Function.clone(fn), result = clonedFn('foo'); expect(result).toBe('bar'); expect(fn).toHaveBeenCalledWith('foo'); }); }); describe("createInterceptor", function() { var interceptor, interceptorFn, interceptedFn, interceptorIsRunFirst, interceptedIsRunAfter; beforeEach(function() { interceptorIsRunFirst = false; interceptedIsRunAfter = false; interceptorFn = jasmine.createSpy("interceptorSpy").andCallFake(function() { interceptorIsRunFirst = true; }); interceptedFn = jasmine.createSpy("interceptedSpy").andCallFake(function() { interceptedIsRunAfter = interceptorIsRunFirst; }); }); describe("if no function is passed", function() { it("should return the same function", function() { expect(Ext.Function.createInterceptor(interceptedFn)).toEqual(interceptedFn); }); }); describe("if a function is passed", function() { beforeEach(function() { interceptor = Ext.Function.createInterceptor(interceptedFn, interceptorFn, fakeScope); interceptor(); }); it("should return a new function", function() { expect(typeof interceptor === "function").toBe(true); expect(interceptor).not.toEqual(interceptedFn); }); it("should set the correct scope for the interceptor function", function() { expect(interceptorFn.calls[0].object).toBe(fakeScope); }); it("should call the interceptor function first", function() { expect(interceptedIsRunAfter).toBe(true); }); }); describe("if the interceptor function returns false", function() { it("should not execute the original function", function() { interceptor = Ext.Function.createInterceptor(interceptedFn, function() { return false; }); interceptor(); expect(interceptedFn).not.toHaveBeenCalled(); }); }); }); describe("createDelayed", function() { it("should create bind to the given function to be called after x milliseconds", function() { mockTimeout(); var fn = jasmine.createSpy(), delayedFn = Ext.Function.createDelayed(fn, 2); delayedFn('foo'); expect(timeouts.shift()).toBe(2); expect(fn).not.toHaveBeenCalled(); runAfterInvocation(fn, function() { expect(fn).toHaveBeenCalledWith('foo'); }); unmockTimeout(); }); it("should use the specified scope as 'this'", function() { var scope = { x: 'foo' }, fn = jasmine.createSpy().andCallFake(function() { this.x = 'bar' }), delayedFn = Ext.Function.createDelayed(fn, 2, scope); delayedFn(); expect(fn).not.toHaveBeenCalled(); expect(scope.x).toBe('foo'); runAfterInvocation(fn, function() { expect(scope.x).toBe('bar'); }); }); it("should override the call arguments with the specified arguments", function() { var scope = {}, args = [0, 1, 2], fn = jasmine.createSpy(), delayedFn = Ext.Function.createDelayed(fn, 2, scope, args); delayedFn(3, 4, 5); expect(fn).not.toHaveBeenCalled(); runAfterInvocation(fn, function() { expect(fn).toHaveBeenCalledWith(0, 1, 2); }); }); it("should append the specified arguments to the call arguments when appendArgs is true", function() { var scope = {}, args = [0, 1, 2], fn = jasmine.createSpy(), delayedFn = Ext.Function.createDelayed(fn, 2, scope, args, true); delayedFn(3, 4, 5); expect(fn).not.toHaveBeenCalled(); runAfterInvocation(fn, function() { expect(fn).toHaveBeenCalledWith(3, 4, 5, 0, 1, 2); }); }); it("should insert the specified arguments into the call arguments at the position specified by appendArgs", function() { var scope = {}, args = [0, 1, 2], fn = jasmine.createSpy(), delayedFn = Ext.Function.createDelayed(fn, 2, scope, args, 2); delayedFn(3, 4, 5); expect(fn).not.toHaveBeenCalled(); runAfterInvocation(fn, function() { expect(fn).toHaveBeenCalledWith(3, 4, 0, 1, 2, 5); }); }); }); describe("defer", function() { var fn; beforeEach(function(){ fn = jasmine.createSpy("deferSpy"); }); it("should execute the function after the specified number of milliseconds", function() { Ext.defer(fn, 10); waitsFor(function(){ return fn.calls.length === 1; }, "fn was never called"); runs(function() { expect(fn).toHaveBeenCalled(); }); }); it("should execute the function directly if the specified number of milliseconds is <= 0", function() { Ext.defer(fn, 0); expect(fn).toHaveBeenCalled(); }); it("should set the correct scope", function() { Ext.defer(fn, 10, fakeScope); waitsFor(function(){ return fn.calls.length === 1; }, "fn was never called"); runs(function() { expect(fn.calls[0].object).toBe(fakeScope); }); }); it("should pass the correct arguments", function() { Ext.defer(fn, 10, this, [1, 2, 3]); waitsFor(function(){ return fn.calls.length === 1; }, "fn was never called"); runs(function() { expect(fn).toHaveBeenCalledWith(1,2,3); }); }); it("should return a timeout number", function() { expect(typeof Ext.defer(function() {}, 10) === 'number').toBe(true); }); }); describe("createSequence", function() { var sequence, newFn, origFn, origFnIsRunFirst, newFnIsRunAfter; beforeEach(function() { origFnIsRunFirst = false; newFnIsRunAfter = false; origFn = jasmine.createSpy("interceptedSpy").andCallFake(function() { origFnIsRunFirst = true; }); newFn = jasmine.createSpy("sequenceSpy").andCallFake(function() { newFnIsRunAfter = origFnIsRunFirst; }); }); describe("if no function is passed", function() { it("should return the same function", function() { expect(Ext.Function.createSequence(origFn)).toEqual(origFn); }); }); describe("if a function is passed", function() { beforeEach(function() { sequence = Ext.Function.createSequence(origFn, newFn, fakeScope); sequence(); }); it("should return a new function", function() { expect(typeof sequence === "function").toBe(true); expect(sequence).not.toEqual(origFn); }); it("should set the correct scope for the sequence function", function() { expect(newFn.calls[0].object).toBe(fakeScope); }); it("should call the sequence function first", function() { expect(newFnIsRunAfter).toBe(true); }); }); }); describe("createBuffered", function() { it("should prevent the execution of multiple calls of the buffered function within the timeout period", function() { mockTimeout(); var fn = jasmine.createSpy(), bufferedFn = Ext.Function.createBuffered(fn, 2); bufferedFn(); expect(timeouts.shift()).toBe(2); bufferedFn(); expect(clearedTimeoutIds.shift()).toBe(timeoutIds.shift()); expect(timeouts.shift()).toBe(2); expect(fn).not.toHaveBeenCalled(); runAfterInvocation(fn, function() { expect(fn.calls.length).toBe(1); }); unmockTimeout(); }); it("should use the specified scope as 'this'", function() { var scope = { x: 1 }, fn = jasmine.createSpy().andCallFake(function() { this.x++; }), bufferedFn = Ext.Function.createBuffered(fn, 20, scope); bufferedFn(); expect(scope.x).toBe(1); bufferedFn(); runAfterInvocation(fn, function() { expect(scope.x).toBe(2); }); }); it("should override the call arguments with the specified ones", function() { var scope = {}, args = ['bar1', 'bar2'], fn = jasmine.createSpy(), bufferedFn = Ext.Function.createBuffered(fn, 20, scope, args); bufferedFn('foo1', 'foo2'); expect(fn).not.toHaveBeenCalled(); runAfterInvocation(fn, function() { expect(fn).toHaveBeenCalledWith('bar1', 'bar2'); }); }); }); describe("createThrottled", function() { it("should execute only once per each specified time interval", function() { mockTimeout(); var fn = jasmine.createSpy(), throttledFn = Ext.Function.createThrottled(fn, 10); expect(fn).not.toHaveBeenCalled(); throttledFn(); expect(clearedTimeoutIds.shift()).toBeUndefined(); expect(fn.calls.length).toBe(1); throttledFn(); expect(timeouts.shift()).not.toBeGreaterThan(10); expect(clearedTimeoutIds.shift()).toBeUndefined(); throttledFn(); expect(timeouts.shift()).not.toBeGreaterThan(10); expect(clearedTimeoutIds.shift()).toBe(timeoutIds.shift()); throttledFn(); expect(timeouts.shift()).not.toBeGreaterThan(10); expect(clearedTimeoutIds.shift()).toBe(timeoutIds.shift()); expect(fn.calls.length).toBe(1); runAfterInvocation(fn, function() { expect(fn.calls.length).toEqual(2); throttledFn(); // elapsed may have been exceeded here, so this call may execute immediately expect(fn.calls.length).not.toBeLessThan(2); expect(fn.calls.length).not.toBeGreaterThan(3); }, 2); unmockTimeout(); }); it("should use the specified scope as 'this'", function() { var scope = {}, fn = jasmine.createSpy().andCallFake(function(value) { this.x = value; }), throttledFn = Ext.Function.createThrottled(fn, 10, scope); throttledFn('foo'); throttledFn('bar'); throttledFn('baz'); throttledFn('qux'); expect(fn).toHaveBeenCalledWith('foo'); expect(scope.x).toBe('foo'); expect(fn.calls.length).toBe(1); }); }); describe("interceptAfter", function() { it("should execute interceptor after each method call", function() { var monologue = { phrases: [], addPhrase: function(phrase) { this.phrases.push(phrase) } }, addMeToo = jasmine.createSpy().andCallFake(function(phrase) { this.phrases.push(phrase + ' too'); }); Ext.Function.interceptAfter(monologue, 'addPhrase', addMeToo); monologue.addPhrase('I like you'); monologue.addPhrase('I love you'); expect(monologue.phrases).toEqual(['I like you', 'I like you too', 'I love you', 'I love you too']); expect(addMeToo).toHaveBeenCalledWith('I like you'); expect(addMeToo).toHaveBeenCalledWith('I love you'); }); it("should execute interceptor after each method call with the specified scope as 'this'", function() { var monologue = { phrases: [], addPhrase: function(phrase) { this.phrases.push(phrase) } }, transcription = { phrases: [] }, transcriptPhrase = jasmine.createSpy().andCallFake(function(phrase) { this.phrases.push("He said: " + phrase); }); Ext.Function.interceptAfter(monologue, 'addPhrase', transcriptPhrase, transcription); monologue.addPhrase('I like you'); monologue.addPhrase('I love you'); expect(monologue.phrases).toEqual(['I like you', 'I love you']); expect(transcription.phrases).toEqual(['He said: I like you', 'He said: I love you']); expect(transcriptPhrase).toHaveBeenCalledWith('I like you'); expect(transcriptPhrase).toHaveBeenCalledWith('I love you'); }); }); });