describe('Transport', function() { beforeEach(function() { jasmine.Ajax.useMock(); jasmine.Clock.useMock(); this.transport = new Transport(); }); afterEach(function() { // run twice to flush out on-deck requests $.each(ajaxRequests, drop); $.each(ajaxRequests, drop); clearAjaxRequests(); Transport.resetCache(); function drop(i, req) { req.readyState !== 4 && req.response(fixtures.ajaxResps.ok); } }); it('should use jQuery.ajax as the default transport mechanism', function() { var req, resp = fixtures.ajaxResps.ok, spy = jasmine.createSpy(); this.transport.get('/test', spy); req = mostRecentAjaxRequest(); req.response(resp); expect(req.url).toBe('/test'); expect(spy).toHaveBeenCalledWith(null, resp.parsed); }); it('should allow the transport mechanism to be configured', function() { var resp = fixtures.ajaxResps.ok, cbSpy = jasmine.createSpy(), sendSpy = jasmine.createSpy().andCallFake(send); this.transport = new Transport({ transport: sendSpy }); this.transport.get('/test', cbSpy); jasmine.Clock.tick(0); expect(cbSpy).toHaveBeenCalledWith(null, resp.parsed); expect(sendSpy).toHaveBeenCalledWith( '/test', {}, jasmine.any(Function), jasmine.any(Function) ); // send must be async function send(url, o, onSuccess, onError) { onSuccess(resp.parsed); } }); it('should respect maxPendingRequests configuration', function() { for (var i = 0; i < 10; i++) { this.transport.get('/test' + i, $.noop); } expect(ajaxRequests.length).toBe(6); }); it('should support rate limiting', function() { this.transport = new Transport({ rateLimiter: rateLimiter }); for (var i = 0; i < 5; i++) { this.transport.get('/test' + i, $.noop); } jasmine.Clock.tick(100); expect(ajaxRequests.length).toBe(1); function rateLimiter(fn) { return _.debounce(fn, 20); } }); it('should cache most recent requests', function() { var spy1 = jasmine.createSpy(), spy2 = jasmine.createSpy(); this.transport.get('/test1', $.noop); mostRecentAjaxRequest().response(fixtures.ajaxResps.ok); this.transport.get('/test2', $.noop); mostRecentAjaxRequest().response(fixtures.ajaxResps.ok1); expect(ajaxRequests.length).toBe(2); this.transport.get('/test1', spy1); this.transport.get('/test2', spy2); jasmine.Clock.tick(0); // no ajax requests were made on subsequent requests expect(ajaxRequests.length).toBe(2); expect(spy1).toHaveBeenCalledWith(null, fixtures.ajaxResps.ok.parsed); expect(spy2).toHaveBeenCalledWith(null, fixtures.ajaxResps.ok1.parsed); }); it('should not cache requests if cache option is false', function() { this.transport = new Transport({ cache: false }); this.transport.get('/test1', $.noop); mostRecentAjaxRequest().response(fixtures.ajaxResps.ok); this.transport.get('/test1', $.noop); mostRecentAjaxRequest().response(fixtures.ajaxResps.ok); expect(ajaxRequests.length).toBe(2); }); it('should prevent dog pile', function() { var spy1 = jasmine.createSpy(), spy2 = jasmine.createSpy(); this.transport.get('/test1', spy1); this.transport.get('/test1', spy2); mostRecentAjaxRequest().response(fixtures.ajaxResps.ok); expect(ajaxRequests.length).toBe(1); waitsFor(function() { return spy1.callCount && spy2.callCount; }); runs(function() { expect(spy1).toHaveBeenCalledWith(null, fixtures.ajaxResps.ok.parsed); expect(spy2).toHaveBeenCalledWith(null, fixtures.ajaxResps.ok.parsed); }); }); it('should always make a request for the last call to #get', function() { var spy = jasmine.createSpy(); for (var i = 0; i < 6; i++) { this.transport.get('/test' + i, $.noop); } this.transport.get('/test' + i, spy); expect(ajaxRequests.length).toBe(6); _.each(ajaxRequests, function(req) { req.response(fixtures.ajaxResps.ok); }); expect(ajaxRequests.length).toBe(7); mostRecentAjaxRequest().response(fixtures.ajaxResps.ok); expect(spy).toHaveBeenCalled(); }); it('should invoke the callback with err set to true on failure', function() { var req, resp = fixtures.ajaxResps.err, spy = jasmine.createSpy(); this.transport.get('/test', spy); req = mostRecentAjaxRequest(); req.response(resp); expect(req.url).toBe('/test'); expect(spy).toHaveBeenCalledWith(true); }); it('should not send cancelled requests', function() { this.transport = new Transport({ rateLimiter: rateLimiter }); this.transport.get('/test', $.noop); this.transport.cancel(); jasmine.Clock.tick(100); expect(ajaxRequests.length).toBe(0); function rateLimiter(fn) { return _.debounce(fn, 20); } }); it('should not send outdated requests', function() { this.transport = new Transport({ rateLimiter: rateLimiter }); // warm cache this.transport.get('/test1', $.noop); jasmine.Clock.tick(100); mostRecentAjaxRequest().response(fixtures.ajaxResps.ok); expect(mostRecentAjaxRequest().url).toBe('/test1'); expect(ajaxRequests.length).toBe(1); // within the same rate-limit cycle, request test2 and test1. test2 becomes // outdated after test1 is requested and no request is sent for test1 // because it's a cache hit this.transport.get('/test2', $.noop); this.transport.get('/test1', $.noop); jasmine.Clock.tick(100); expect(ajaxRequests.length).toBe(1); function rateLimiter(fn) { return _.debounce(fn, 20); } }); });