UNPKG

grunt-doctrine

Version:

Grunt plugin to convert doctrine xml annotations into backbone models and collections. Useful in conjunction with doctrine apigility. Includes option to scaffold an entire BBB application

1,153 lines (930 loc) 38.3 kB
describe('Backbone.fetchCache', function() { var model, errorModel, collection, errorCollection, server, modelResponse, errorModelResponse, collectionResponse; var originalPriorityFn = Backbone.fetchCache.priorityFn; // Async spec waitsFor helpers function promiseComplete(promise) { return function() { return promise.state() !== 'pending'; }; } function promiseNotified(promise) { var notified = false; promise.progress(function() { notified = true; }); return function() { return notified; }; } beforeEach(function() { model = new Backbone.Model(); model.url = '/model-cache-test'; errorModel = new Backbone.Model(); errorModel.url = '/model-error-cache-test'; collection = new Backbone.Collection(); collection.url = '/collection-cache-test'; errorCollection = new Backbone.Collection(); errorCollection.url = '/collection-error-cache-test'; // Mock xhr resposes server = sinon.fakeServer.create(); modelResponse = { sausages: 'bacon' }; collectionResponse = [{ sausages: 'bacon' }, { rice: 'peas' }]; server.respondWith('GET', model.url, [ 200, { 'Content-Type': 'application/json' }, JSON.stringify(modelResponse) ]); server.respondWith('GET', collection.url, [ 200, { 'Content-Type': 'application/json' }, JSON.stringify(collectionResponse) ]); server.respondWith('GET', errorModel.url, [ 500, { 'Content-Type': 'test/html' }, 'Server Error' ]); server.respondWith('GET', errorCollection.url, [ 500, { 'Content-Type': 'test/html' }, 'Server Error' ]); }); afterEach(function() { Backbone.fetchCache._cache = {}; localStorage.clear('backboneCache'); Backbone.fetchCache.priorityFn = originalPriorityFn; server.restore(); }); describe('AMD', function() { it('defines an AMD module if supported', function() { var s = document.createElement('script'); s.src = '/backbone.fetch-cache.js'; window.define = jasmine.createSpy('AMD define'); window.define.amd = true; s.onload = function(){ expect(window.define).toHaveBeenCalled(); }; document.body.appendChild(s); }); }); describe('.getCacheKey', function() { it('uses options url with priority if set', function() { expect(Backbone.fetchCache.getCacheKey(model, {url: '/options-test-url'})) .toEqual('/options-test-url'); }); it('uses model url if options url is not set', function() { expect(Backbone.fetchCache.getCacheKey(model, {})) .toEqual(model.url); }); it('generates a standard querystring', function() { var value = { 'string': 'stringValue', 'float': 2.1, 'bool':false }; var expected = model.url + '?string=stringValue&float=2.1&bool=false'; expect(Backbone.fetchCache.getCacheKey(model, {data: value})) .toEqual(expected); }); it('generates a standard querystring if data is a string', function() { var value = 'test'; var expected = model.url + '?test'; expect(Backbone.fetchCache.getCacheKey(model, {data: value})) .toEqual(expected); }); }); describe('.setCache', function() { var opts; beforeEach(function() { opts = { cache: true }; }); it('noops if the instance does not have a url', function() { model.url = null; Backbone.fetchCache.setCache(model, opts, modelResponse); expect(Backbone.fetchCache._cache[model.url]).toBeUndefined(); }); it('noops unless cache: true is passed', function() { Backbone.fetchCache.setCache(model, null, modelResponse); expect(Backbone.fetchCache._cache[model.url]).toBeUndefined(); }); it('keys cache items by the getCacheKey method',function() { spyOn(Backbone.fetchCache, 'getCacheKey').andReturn('someCacheKey'); Backbone.fetchCache.setCache(model, opts, modelResponse); expect(Backbone.fetchCache._cache['someCacheKey'].value) .toEqual(modelResponse); }); it('calls setLocalStorage', function() { spyOn(Backbone.fetchCache, 'setLocalStorage'); Backbone.fetchCache.setCache(model, opts); expect(Backbone.fetchCache.setLocalStorage).toHaveBeenCalled(); }); describe('with prefill: true option', function() { beforeEach(function() { opts = { prefill: true }; }); it('caches even if cache: true is not passed', function() { var cacheKey = Backbone.fetchCache.getCacheKey(model, opts); Backbone.fetchCache.setCache(model, opts, modelResponse); expect(Backbone.fetchCache._cache[cacheKey].value) .toEqual(modelResponse); }); it('does not cache if cache: false is passed', function() { var cacheKey = Backbone.fetchCache.getCacheKey(model, opts); opts.cache = false; Backbone.fetchCache.setCache(model, this.opts, modelResponse); expect(Backbone.fetchCache._cache[cacheKey]).toBeUndefined(); }); }); describe('cache expiry', function() { var cacheKey; beforeEach(function() { this.clock = sinon.useFakeTimers(); cacheKey = Backbone.fetchCache.getCacheKey(model, this.opts); }); afterEach(function() { this.clock.restore(); }); it('sets default expiry times for cache keys', function() { Backbone.fetchCache.setCache(model, { cache: true }, modelResponse); expect(Backbone.fetchCache._cache[cacheKey].expires) .toEqual((new Date()).getTime() + (5* 60 * 1000)); }); it('sets expiry times for cache keys', function() { var opts = { cache: true, expires: 1000 }; Backbone.fetchCache.setCache(model, opts, modelResponse); expect(Backbone.fetchCache._cache[cacheKey].expires) .toEqual((new Date()).getTime() + (opts.expires * 1000)); }); it('is not set if expires: false is set', function() { var opts = { cache: true, expires: false }; Backbone.fetchCache.setCache(model, opts, modelResponse); expect(Backbone.fetchCache._cache[cacheKey].expires) .toEqual(false); }); }); }); describe('.clearItem', function() { beforeEach(function() { Backbone.fetchCache._cache = { '/item/1': { foo: 'bar' }, '/item/2': { beep: 'boop' } }; }); it('deletes a single item from the cache', function() { Backbone.fetchCache.clearItem('/item/1'); expect(Backbone.fetchCache._cache['/item/1']).toBeUndefined(); expect(Backbone.fetchCache._cache['/item/2']).toEqual({ beep: 'boop' }); }); it('updates localStorage', function() { spyOn(Backbone.fetchCache, 'setLocalStorage'); Backbone.fetchCache.clearItem('/item/1'); expect(Backbone.fetchCache.setLocalStorage).toHaveBeenCalled(); }); }); describe('.setLocalStorage', function() { it('puts the cache into localStorage', function() { var cache = Backbone.fetchCache._cache = { '/url1': { expires: false, value: { bacon: 'sandwich' } }, '/url2': { expires: false, value: { egg: 'roll' } } }; Backbone.fetchCache.setLocalStorage(); expect(localStorage.getItem('backboneCache')).toEqual(JSON.stringify(cache)); }); it('does not put the cache into localStorage if localStorage is false', function() { var cache = Backbone.fetchCache._cache = { '/url1': { expires: false, value: { bacon: 'sandwich' } }, '/url2': { expires: false, value: { egg: 'roll' } } }; Backbone.fetchCache.localStorage = false; Backbone.fetchCache.setLocalStorage(); expect(localStorage.getItem('backboneCache')).toEqual(); Backbone.fetchCache.localStorage = true; // restore }); }); describe('.getLocalStorage', function() { it('primes the cache from localStorage', function() { var cache = { '/url1': { expires: false, value: { bacon: 'sandwich' } }, '/url2': { expires: false, value: { egg: 'roll' } } }; localStorage.setItem('backboneCache', JSON.stringify(cache)); Backbone.fetchCache.getLocalStorage(); expect(Backbone.fetchCache._cache).toEqual(cache); }); it('always primes the cache with an object', function() { Backbone.fetchCache.getLocalStorage(); expect(Backbone.fetchCache._cache).toEqual({}); }); }); describe('Automatic expiry when quota is met', function() { describe('.prioritize', function() { it('prioritizes older results by default', function() { var cache = { '/url1': { expires: 1000, value: { bacon: 'sandwich' } }, '/url2': { expires: 1500, value: { egg: 'roll' } } }; localStorage.setItem('backboneCache', JSON.stringify(cache)); Backbone.fetchCache.getLocalStorage(); expect(Backbone.fetchCache._prioritize()).toBeDefined(); expect(Backbone.fetchCache._prioritize()).toEqual('/url1'); }); }); describe('.priorityFn', function() { it('should take a custom priorityFn sorting function', function() { var cache = { '/url1': { expires: 1000, value: { bacon: 'sandwich' } }, '/url2': { expires: 1500, value: { egg: 'roll' } } }; localStorage.setItem('backboneCache', JSON.stringify(cache)); Backbone.fetchCache.getLocalStorage(); Backbone.fetchCache.priorityFn = function(a, b) { return b.expires - a.expires; }; expect(Backbone.fetchCache._prioritize()).toEqual('/url2'); }); }); describe('.deleteCacheWithPriority', function() { it('calls deleteCacheWithPriority if a QUOTA_EXCEEDED_ERR is thrown', function() { function QuotaError(message) { this.code = 22; } QuotaError.prototype = new Error(); spyOn(localStorage, 'setItem').andThrow(new QuotaError()); spyOn(Backbone.fetchCache, '_deleteCacheWithPriority'); Backbone.fetchCache._cache = { '/url1': { expires: 1000, value: { bacon: 'sandwich' } }, '/url2': { expires: 1500, value: { egg: 'roll' } } }; Backbone.fetchCache.setLocalStorage(); expect(Backbone.fetchCache._deleteCacheWithPriority).toHaveBeenCalled(); }); it('calls deleteCacheWithPriority if a QUOTA_EXCEEDED_ERR is thrown in IE', function() { function IEQuotaError(message) { this.number = 22; } IEQuotaError.prototype = new Error(); spyOn(localStorage, 'setItem').andThrow(new IEQuotaError()); spyOn(Backbone.fetchCache, '_deleteCacheWithPriority'); Backbone.fetchCache._cache = { '/url1': { expires: 1000, value: { bacon: 'sandwich' } }, '/url2': { expires: 1500, value: { egg: 'roll' } } }; Backbone.fetchCache.setLocalStorage(); expect(Backbone.fetchCache._deleteCacheWithPriority).toHaveBeenCalled(); }); it('calls deleteCacheWithPriority if a QUOTA_EXCEEDED_ERR is thrown in Firefox', function() { function FFQuotaError(message) { this.message = 22; } FFQuotaError.prototype = new Error(); spyOn(localStorage, 'setItem').andThrow(new FFQuotaError()); spyOn(Backbone.fetchCache, '_deleteCacheWithPriority'); Backbone.fetchCache._cache = { '/url1': { expires: 1000, value: { bacon: 'sandwich' } }, '/url2': { expires: 1500, value: { egg: 'roll' } } }; Backbone.fetchCache.setLocalStorage(); expect(Backbone.fetchCache._deleteCacheWithPriority).toHaveBeenCalled(); }); it('should delete a cached item according to what is returned by priorityFn', function() { var cache = { '/url1': { expires: 1000, value: { bacon: 'sandwich' } }, '/url2': { expires: 1500, value: { egg: 'roll' } } }; localStorage.setItem('backboneCache', JSON.stringify(cache)); Backbone.fetchCache.getLocalStorage(); Backbone.fetchCache._deleteCacheWithPriority(); expect(Backbone.fetchCache._cache) .toEqual({'/url2': { expires: 1500, value: { egg: 'roll' } }}); }); }); }); describe('Backbone.Model', function() { describe('#fetch', function() { var cacheData; beforeEach(function() { cacheData = { cheese: 'pickle' }; }); it('saves returned attributes to the cache', function() { var cacheKey = Backbone.fetchCache.getCacheKey(model); model.fetch({ cache: true }); server.respond(); expect(Backbone.fetchCache._cache[cacheKey].value) .toEqual(model.toJSON()); }); it('passes the instance and options through to setCache', function() { var opts = { banana: 'bread' }; spyOn(Backbone.fetchCache, 'setCache'); model.fetch(opts); server.respond(); expect(Backbone.fetchCache.setCache.calls[0].args[0]).toEqual(model); expect(Backbone.fetchCache.setCache.calls[0].args[1]).toEqual(opts); }); it('sets data from the cache if cache: true is set', function() { Backbone.fetchCache._cache[model.url] = { value: cacheData, expires: (new Date()).getTime() + (5* 60 * 1000) }; waitsFor(promiseComplete(model.fetch({ cache: true }))); runs(function() { expect(model.toJSON()).toEqual(cacheData); }); }); it('does not set cache data if cache: true is not set', function() { Backbone.fetchCache._cache[model.url] = { value: cacheData, expires: (new Date()).getTime() + (5* 60 * 1000) }; waitsFor(promiseComplete(model.fetch())); server.respond(); runs(function() { expect(model.toJSON()).not.toEqual(cacheData); expect(model.toJSON()).toEqual(modelResponse); }); }); it('does not set cache data if the cache item is stale', function() { Backbone.fetchCache._cache[model.url] = { value: cacheData, expires: (new Date()).getTime() - (5* 60 * 1000) }; waitsFor(promiseComplete(model.fetch())); server.respond(); runs(function() { expect(model.toJSON()).not.toEqual(cacheData); expect(model.toJSON()).toEqual(modelResponse); }); }); it('sets cache data if the item has expires: false', function() { var cacheData = { cheese: 'pickle' }; Backbone.fetchCache._cache[model.url] = { value: cacheData, expires: false }; waitsFor(promiseComplete(model.fetch({ cache: true }))); runs(function() { expect(model.toJSON()).toEqual(cacheData); }); }); it('returns an unfulfilled promise', function() { var promise = model.fetch(); expect(promise).toBeAPromise(); expect(promise).toBeUnresolved(); }); describe('on AJAX error', function() { it('rejects the promise', function() { var promise = errorModel.fetch(); waitsFor(promiseComplete(promise)); server.respond(); runs(function() { expect(Backbone.fetchCache._cache[errorModel.url]).toBeUndefined(); expect(promise.state()).toBe('rejected'); }); }); it('calls the error callback', function() { var spy = jasmine.createSpy('error'); var promise = errorModel.fetch({ error: spy }); waitsFor(promiseComplete(promise)); server.respond(); runs(function() { expect(Backbone.fetchCache._cache[errorModel.url]).toBeUndefined(); expect(spy).toHaveBeenCalled(); }); }); }); describe('on a cache hit', function() { var promise, successSpy, cacheData; beforeEach(function() { cacheData = { cheese: 'pickle' }; successSpy = jasmine.createSpy('fetch success'); Backbone.fetchCache._cache[model.url] = { value: cacheData, expires: (new Date()).getTime() + (5* 60 * 1000), success: successSpy }; promise = model.fetch({ cache: true }); }); it('returns an unfulfilled promise', function() { expect(promise).toBeAPromise(); expect(promise).toBeUnresolved(); }); it('fulfills the promise asynchronously', function() { waitsFor(promiseComplete(promise)); runs(function() { expect(promise).toBeResolved(); }); }); it('resolves the promise with the model instance', function() { promise.done(successSpy); waitsFor(promiseComplete(promise)); runs(function() { expect(successSpy).toHaveBeenCalledWith(model); }); }); it('calls the success callback with the model instance, response data and fetch options', function() { var options = { cache: true, success: successSpy }; waitsFor(promiseComplete(model.fetch(options))); runs(function() { expect(successSpy).toHaveBeenCalledWith(model, cacheData, options); }); }); }); describe('with prefill: true option', function() { var cacheData; beforeEach(function(){ cacheData = { cheese: 'pickle' }; Backbone.fetchCache._cache[model.url] = { value: cacheData, expires: (new Date()).getTime() + (5* 60 * 1000) }; }); it('sets cache data and then makes an xhr request', function() { waitsFor(promiseNotified(model.fetch({ prefill: true }))); runs(function() { expect(model.toJSON()).toEqual(cacheData); server.respond(); for (var key in modelResponse) { if (modelResponse.hasOwnProperty(key)) { expect(model.toJSON()[key]).toEqual(modelResponse[key]); } } }); }); it('calls the prefillSuccess and success callbacks in order', function() { var prefillSuccess = jasmine.createSpy('prefillSuccess'), success = jasmine.createSpy('success'); var promise = model.fetch({ prefill: true, success: success, prefillSuccess: prefillSuccess }); waitsFor(promiseNotified(promise)); runs(function() { expect(prefillSuccess.calls[0].args[0]).toEqual(model); expect(prefillSuccess.calls[0].args[1]).toEqual(model.attributes); expect(success).not.toHaveBeenCalled(); server.respond(); expect(success.calls[0].args[0]).toEqual(model); expect(success.calls[0].args[1]).toEqual(modelResponse); }); }); it('triggers sync on prefill success and success', function() { var prefillSuccess = jasmine.createSpy('prefillSuccess'), success = jasmine.createSpy('success'), sync = jasmine.createSpy('sync'), cachesync = jasmine.createSpy('cachesync'); model.bind('sync', sync); model.bind('cachesync', cachesync); var promise = model.fetch({ prefill: true, success: success, prefillSuccess: prefillSuccess }); waitsFor(promiseNotified(promise)); runs(function() { expect(sync).toHaveBeenCalled(); expect(sync.callCount).toEqual(1); expect(cachesync).toHaveBeenCalled(); expect(cachesync.callCount).toEqual(1); server.respond(); // Ensure cachesync was not called on this second go around expect(cachesync.callCount).toEqual(1); expect(sync).toHaveBeenCalled(); expect(sync.callCount).toEqual(2); expect(success.calls[0].args[0]).toEqual(model); expect(success.calls[0].args[1]).toEqual(modelResponse); }); }); it('triggers progress on the promise on cache hit', function() { var progress = jasmine.createSpy('progress'); var promise = model.fetch({ prefill: true }).progress(progress); waitsFor(promiseNotified(promise)); runs(function() { expect(progress).toHaveBeenCalledWith(model); }); }); it('fulfills the promise on AJAX success', function() { var success = jasmine.createSpy('success'); model.fetch({ prefill: true }).done(success); server.respond(); expect(success.calls[0].args[0]).toEqual(model); expect(success.calls[0].args[1]).toEqual(modelResponse); }); }); describe('with async: false option', function() { it('resolves synchronously', function() { Backbone.fetchCache._cache[model.url] = { value: cacheData, expires: (new Date()).getTime() + (5* 60 * 1000) }; model.fetch({ cache: true, async: false }); expect(model.toJSON()).toEqual(cacheData); }); }); describe('with caching disabled', function(){ beforeEach(function(){ Backbone.fetchCache.enabled = false; }); afterEach(function(){ Backbone.fetchCache.enabled = true; }); it("doesn't set cache", function(){ var cacheKey = Backbone.fetchCache.getCacheKey(model); model.fetch({ cache: true }); server.respond(); expect(Backbone.fetchCache._cache[cacheKey]) .toBeUndefined(); }); it("doesn't use cache", function(){ Backbone.fetchCache._cache[model.url] = { value: cacheData, expires: (new Date()).getTime() + (5* 60 * 1000) }; waitsFor(promiseComplete(model.fetch({cache: true}))); server.respond(); runs(function() { expect(model.toJSON()).not.toEqual(cacheData); expect(model.toJSON()).toEqual(modelResponse); }); }); }); }); describe('#sync', function() { describe('using model instance url', function () { var cacheData; beforeEach(function() { var cache = {}; cacheData = { some: 'data' }; cache[model.url] = cacheData; localStorage.setItem('backboneCache', JSON.stringify(cache)); Backbone.fetchCache.getLocalStorage(); }); it('clears the cache for the model on create', function() { model.sync('create', model, {}); expect(Backbone.fetchCache._cache[model.url]).toBeUndefined(); }); it('clears the cache for the model on update', function() { model.sync('update', model, {}); expect(Backbone.fetchCache._cache[model.url]).toBeUndefined(); }); it('clears the cache for the model on patch', function() { model.sync('create', model, {}); expect(Backbone.fetchCache._cache[model.url]).toBeUndefined(); }); it('clears the cache for the model on delete', function() { model.sync('create', model, {}); expect(Backbone.fetchCache._cache[model.url]).toBeUndefined(); }); it('does not clear the cache for the model on read', function() { model.sync('read', model, {}); expect(Backbone.fetchCache._cache[model.url]).toEqual(cacheData); }); }); describe('using options-given url', function() { var cacheData; var optionsGiven; beforeEach(function() { optionsGiven = { 'url' : '/given-url' }; var cache = {}; cacheData = { some: 'data' }; cache[optionsGiven.url] = cacheData; localStorage.setItem('backboneCache', JSON.stringify(cache)); Backbone.fetchCache.getLocalStorage(); }); it('clears the cache for the model on create', function() { model.sync('create', model, optionsGiven); expect(Backbone.fetchCache._cache[optionsGiven.url]).toBeUndefined(); }); it('clears the cache for the model on update', function() { model.sync('update', model, optionsGiven); expect(Backbone.fetchCache._cache[optionsGiven.url]).toBeUndefined(); }); it('clears the cache for the model on patch', function() { model.sync('create', model, optionsGiven); expect(Backbone.fetchCache._cache[optionsGiven.url]).toBeUndefined(); }); it('clears the cache for the model on delete', function() { model.sync('create', model, optionsGiven); expect(Backbone.fetchCache._cache[optionsGiven.url]).toBeUndefined(); }); it('does not clear the cache for the model on read', function() { model.sync('read', model, optionsGiven); expect(Backbone.fetchCache._cache[optionsGiven.url]).toEqual(cacheData); }); }); it('calls super', function() { spyOn(Backbone.fetchCache._superMethods, 'modelSync'); model.sync('create', model, {}); expect(Backbone.fetchCache._superMethods.modelSync).toHaveBeenCalled(); }); it('returns the result of calling super', function() { expect(model.sync('create', model, {})).toBeAPromise(); }); describe('with an associated collection', function() { beforeEach(function() { var cache = {}; model.collection = collection; cache[Backbone.fetchCache.getCacheKey(collection)] = [{ some: 'data' }]; localStorage.setItem('backboneCache', JSON.stringify(cache)); Backbone.fetchCache.getLocalStorage(); }); it('clears the cache for the collection', function() { var cacheKey = Backbone.fetchCache.getCacheKey(collection); model.sync('create', model, {}); expect(Backbone.fetchCache._cache[cacheKey]).toBeUndefined(); }); }); }); }); describe('Backbone.Collection', function() { describe('#fetch', function() { var cacheData; beforeEach(function() { cacheData = [{ cheese: 'pickle' }, { salt: 'vinegar' }]; }); it('returns an unfulfilled promise', function() { var promise = collection.fetch(); expect(promise).toBeAPromise(); expect(promise).toBeUnresolved(); }); it('saves returned attributes to the cache', function() { collection.fetch({ cache: true }); server.respond(); expect(Backbone.fetchCache._cache[collection.url].value) .toEqual(collection.toJSON()); }); it('passes the instance and options through to setCache', function() { var opts = { banana: 'bread' }; spyOn(Backbone.fetchCache, 'setCache'); collection.fetch(opts); server.respond(); expect(Backbone.fetchCache.setCache.calls[0].args[0]).toEqual(collection); expect(Backbone.fetchCache.setCache.calls[0].args[1]).toEqual(opts); }); it('sets data cache data if cache: true is set', function() { Backbone.fetchCache._cache[collection.url] = { value: cacheData, expires: (new Date()).getTime() + (5* 60 * 1000) }; waitsFor(promiseComplete(collection.fetch({ cache: true }))); runs(function() { expect(collection.toJSON()).toEqual(cacheData); }); }); it('does not set cache data if cache: true is not set', function() { Backbone.fetchCache._cache[collection.url] = { value: cacheData, expires: (new Date()).getTime() + (5* 60 * 1000) }; collection.fetch(); server.respond(); expect(collection.toJSON()).not.toEqual(cacheData); expect(collection.toJSON()).toEqual(collectionResponse); }); it('does not set cache data if the cache item is stale', function() { Backbone.fetchCache._cache[collection.url] = { value: cacheData, expires: (new Date()).getTime() - (5* 60 * 1000) }; collection.fetch(); server.respond(); expect(collection.toJSON()).not.toEqual(cacheData); expect(collection.toJSON()).toEqual(collectionResponse); }); it('sets cache data if the item has expires: false', function() { Backbone.fetchCache._cache[collection.url] = { value: cacheData, expires: false }; waitsFor(promiseComplete(collection.fetch({ cache: true }))); runs(function() { expect(collection.toJSON()).toEqual(cacheData); }); }); describe('on AJAX error', function() { it('rejects the promise', function() { var promise = errorCollection.fetch(); waitsFor(promiseComplete(promise)); server.respond(); runs(function() { expect(Backbone.fetchCache._cache[errorCollection.url]).toBeUndefined(); expect(promise.state()).toBe('rejected'); }); }); it('calls the error callback', function() { var spy = jasmine.createSpy('error'); var promise = errorCollection.fetch({ error: spy }); waitsFor(promiseComplete(promise)); server.respond(); runs(function() { expect(Backbone.fetchCache._cache[errorCollection.url]).toBeUndefined(); expect(spy).toHaveBeenCalled(); }); }); }); describe('on cache hit', function() { it('returns an unfulfilled promise', function() { var promise = collection.fetch({ cache: true }); expect(promise).toBeAPromise(); expect(promise).toBeUnresolved(); }); it('calls set according to options', function() { var options = { cache: true, remove: false }; spyOn(collection, 'set'); Backbone.fetchCache._cache[collection.url] = { value: cacheData, expires: (new Date()).getTime() + (5* 60 * 1000) }; waitsFor(promiseComplete(collection.fetch(options))); runs(function() { expect(collection.set).toHaveBeenCalledWith(cacheData, options); }); }); it('calls reset according to options', function() { var options = { cache: true, reset: true }; spyOn(collection, 'reset'); Backbone.fetchCache._cache[collection.url] = { value: cacheData, expires: (new Date()).getTime() + (5* 60 * 1000) }; waitsFor(promiseComplete(collection.fetch(options))); runs(function() { expect(collection.reset).toHaveBeenCalledWith(cacheData, options); }); }); it('fulfills the promise with the collection instance', function() { var spy = jasmine.createSpy('success'); Backbone.fetchCache._cache[collection.url] = { value: cacheData, expires: (new Date()).getTime() + (5* 60 * 1000) }; var promise = collection.fetch({ cache: true }).done(spy); waitsFor(promiseComplete(promise)); runs(function() { expect(spy).toHaveBeenCalledWith(collection); }); }); it('calls success callback with the collection instance, response data and fetch options', function() { var success = jasmine.createSpy('success'); var options = { cache: true, success: success }; Backbone.fetchCache._cache[collection.url] = { value: cacheData, expires: (new Date()).getTime() + (5* 60 * 1000) }; waitsFor(promiseComplete(collection.fetch(options))); runs(function() { expect(success).toHaveBeenCalledWith(collection, cacheData, options); }); }); }); describe('with prefill: true option', function() { beforeEach(function(){ Backbone.fetchCache._cache[collection.url] = { value: cacheData, expires: (new Date()).getTime() + (5* 60 * 1000) }; }); it('sets cache data and makes an xhr request', function() { waitsFor(promiseNotified(collection.fetch({ prefill: true }))); runs(function() { expect(collection.toJSON()).toEqual(cacheData); server.respond(); expect(collection.toJSON()).toEqual(collectionResponse); }); }); it('calls the prefillSuccess and success callbacks in order', function() { var prefillSuccess = jasmine.createSpy('prefillSuccess'), success = jasmine.createSpy('success'); waitsFor(promiseNotified(collection.fetch({ prefill: true, success: success, prefillSuccess: prefillSuccess }))); runs(function() { expect(prefillSuccess.calls[0].args[0]).toEqual(collection); expect(prefillSuccess.calls[0].args[1]).toEqual(collection.attributes); expect(success).not.toHaveBeenCalled(); server.respond(); expect(success.calls[0].args[0]).toEqual(collection); expect(success.calls[0].args[1]).toEqual(collectionResponse); }); }); it('triggers sync on prefill success and success', function() { var prefillSuccess = jasmine.createSpy('prefillSuccess'), success = jasmine.createSpy('success'), sync = jasmine.createSpy('sync'), cachesync = jasmine.createSpy('cachesync'); collection.bind('sync', sync); collection.bind('cachesync', cachesync); waitsFor(promiseNotified(collection.fetch({ prefill: true, success: success, prefillSuccess: prefillSuccess }))); runs(function() { expect(sync).toHaveBeenCalled(); expect(sync.callCount).toEqual(1); expect(cachesync).toHaveBeenCalled(); expect(cachesync.callCount).toEqual(1); server.respond(); // Ensure cachesync was not called on this second go around expect(cachesync.callCount).toEqual(1); expect(sync).toHaveBeenCalled(); expect(sync.callCount).toEqual(2); expect(success.calls[0].args[0]).toEqual(collection); expect(success.calls[0].args[1]).toEqual(collectionResponse); }); }); it('triggers progress on the promise on cache hit', function() { var progress = jasmine.createSpy('progeress'); var promise = collection.fetch({ prefill: true }).progress(progress); waitsFor(promiseNotified(promise)); runs(function() { expect(progress).toHaveBeenCalledWith(collection); }); }); it('fulfills the promise on AJAX success', function() { var success = jasmine.createSpy('success'); collection.fetch({ prefill: true }).done(success); server.respond(); expect(success.calls[0].args[0]).toEqual(collection); expect(success.calls[0].args[1]).toEqual(collectionResponse); }); }); describe('with caching disabled', function(){ beforeEach(function(){ Backbone.fetchCache.enabled = false; }); afterEach(function(){ Backbone.fetchCache.enabled = true; }); it("doesn't set cache", function(){ collection.fetch({ cache: true }); server.respond(); expect(Backbone.fetchCache._cache[collection.url]) .toBeUndefined(); }); it("doesn't use cache", function(){ Backbone.fetchCache._cache[collection.url] = { value: cacheData, expires: (new Date()).getTime() - (5* 60 * 1000) }; collection.fetch({cache: true}); server.respond(); expect(collection.toJSON()).not.toEqual(cacheData); expect(collection.toJSON()).toEqual(collectionResponse); }); }); }); }); describe('Prefix', function() { beforeEach(function() { Backbone.fetchCache.getLocalStorageKey = function() { return 'backboneCache_user1'; }; }); it('get prefix', function() { expect(Backbone.fetchCache.getLocalStorageKey()).toEqual('backboneCache_user1'); }); it('set localStorage', function() { var cache = Backbone.fetchCache._cache = { '/url1': { expires: false, value: { bacon: 'sandwich' } }, '/url2': { expires: false, value: { egg: 'roll' } } }; Backbone.fetchCache.setLocalStorage(); expect(localStorage.getItem('backboneCache_user1')).toEqual(JSON.stringify(cache)); }); it('get localStorage', function() { var cache = { '/url1': { expires: false, value: { bacon: 'sandwich' } }, '/url2': { expires: false, value: { egg: 'roll' } } }; localStorage.setItem('backboneCache_user1', JSON.stringify(cache)); Backbone.fetchCache.getLocalStorage(); expect(Backbone.fetchCache._cache).toEqual(cache); }); it('get localStorage for several users', function() { Backbone.fetchCache.getLocalStorageKey = function() { return 'backboneCache_user2'; }; var cache = Backbone.fetchCache._cache = { '/url1': { expires: true, value: { vegetables: 'tomato' } }, '/url2': { expires: true, value: { egg: 'roll' } } }; localStorage.setItem('backboneCache_user1', ''); Backbone.fetchCache.setLocalStorage(); expect(localStorage.getItem('backboneCache_user2')).toEqual(JSON.stringify(cache)); expect(localStorage.getItem('backboneCache_user1')).toEqual(''); }); }); });