UNPKG

backbone-rel

Version:

Relationships between Backbone models in the flavor of MongoDB's document references and embeddings

283 lines (231 loc) 10.2 kB
define(function(require) { "use strict"; var Backbone = require("backbone-rel"); require("backbone-uniquemodel"); // Attaches UniqueModel to Backbone global var flushModelCache = function(modelName) { var cache = Backbone.UniqueModel.getModelCache(modelName); for (var id in cache.instances) { delete cache.instances[id]; } }; describe("UniqueModel(Model)", function() { this.timeout(4000); // wait a max time of 4s for async requests var A = Backbone.UniqueModel(Backbone.Model.extend({ url: function() { return FIXTURES_BASE + "a_" + this.id + ".json"; } }), "A"); var ACollection = Backbone.Collection.extend({ model: A, url: FIXTURES_BASE + "a_collection.json" }); var B = Backbone.UniqueModel(Backbone.Model.extend({ references: { oneA : A, manyAs : ACollection, oneC : function() { return C; }, manyCs : function() { return CCollection; } }, url: function() { return FIXTURES_BASE + "b_" + this.id + ".json"; } }), "B"); var C = Backbone.UniqueModel(Backbone.Model.extend({ references: { oneB : B, children: function() { return CCollection; }, parent : function() { return C; } }, url: function() { return FIXTURES_BASE + "c_" + this.id + ".json"; } }), "C"); var CCollection = Backbone.Collection.extend({ model: C }); afterEach(function() { // flush uniquemodel model caches so that tests are kept isolated from each other flushModelCache("A"); flushModelCache("B"); flushModelCache("C"); }); describe('#fetch()', function() { it("should be ensured that referenced model instances are unique", function() { var server = sinon.fakeServer.create(); var a = new A({id: 1}); var b = new B({id: 1}); b.fetch(); server.requests[0].respond(200, { "Content-Type": "application/json" }, JSON.stringify({ oneAId: 1 }) ); expect(b.get("oneA")).to.equal(a); server.restore(); }); it("should automatically sync the unique referenced model instance", function() { var server = sinon.fakeServer.create(); var a = new A({id: 1}); var b = new B({id: 1}); b.fetch(); sinon.spy(a, "fetch"); server.requests[0].respond(200, { "Content-Type": "application/json" }, JSON.stringify({ oneAId: 1 }) ); expect(a.fetch).to.have.been.calledOnce; server.restore(); }); it("should correctly handle circular to-one relations", function() { var server = sinon.fakeServer.create(); var b = new B({id: 2}); b.on("error", function(model, resp, opts) { done(new Error("fetch failed: " + resp.status)); }); b.fetch(); server.requests[0].respond(200, { "Content-Type": "application/json" }, JSON.stringify({ id: 2, oneCId: 3 }) ); // c_3 is auto-fetched server.requests[1].respond(200, { "Content-Type": "application/json" }, JSON.stringify({ id: 3, title: "c3", oneBId: 2 // circular reference }) ); expect(b.get("oneC")) .to.be.an.instanceof(C); expect(b.get("oneC").get("title")) .to.equal("c3"); expect(b.get("oneC").get("oneB")) .to.equal(b); server.restore(); }); it("should correctly handle circular to-many relations", function(done) { var b = new B({id: 2}); b.on("error", function(model, resp, opts) { done(new Error("fetch failed: " + resp.status)); }); b.fetch(); b.once("deepsync", function() { expect(b.get("oneC")) .to.be.an.instanceof(C); expect(b.get("oneC").get("title")) .to.equal("c3"); expect(b.get("oneC").get("oneB")) .to.equal(b); done(); }); }); it("should correctly handle self-referential relations", function(done) { var c = new C({id: 1}); c.on("error", function(model, resp, opts) { done(new Error("fetch failed: " + resp.status)); }); c.fetch(); c.once("deepsync", function() { expect(c.get("children")) .to.be.an.instanceof(CCollection); expect(c.get("children").models) .to.have.length(2); // c is a child of itself expect(c.get("children").models[0]) .to.equal(c); // c is also its own parent expect(c.get("parent")) .to.equal(c); done(); }); }); it("should not fetch referenced models that have been fetched before", function(done) { var a = new A({id:1}); a.fetch(); a.once("sync", function() { sinon.spy(a, 'fetch'); var b = new B({id:1, oneAId: 1}); expect(a.fetch).to.have.not.been.called; done(); }); }); it("should not fetch referenced models that have been fetched before as part of a collection", function(done) { var aCollection = new ACollection(); aCollection.fetch(); aCollection.once("sync", function() { var a = aCollection.get(1); sinon.spy(a, 'fetch'); var b = new B({id:1, oneAId: 1}); expect(a.fetch).to.have.not.been.called; done(); }); }); it("should not fetch side-loaded referenced models that are also referenced from other referenced models", function(done) { var b = new B({id:3}); var c1 = new C({id:1}), c2 = new C({id:2}); sinon.spy(b, 'fetch'); sinon.spy(c1, 'fetch'); b.url = FIXTURES_BASE + "b_3_sideloading.json"; b.fetch(); b.once("deepsync", function() { expect(b.fetch).to.have.been.calledOnce; expect(c1.fetch).to.not.have.been.called; expect(b.isSyncing).to.be.undefined; expect(c1.isSyncing).to.be.undefined; done(); }); }); it("should fetch every referenced models only once even it is referenced multiple times in deeply referenced models", function(done) { var b = new B({id:3}); var c1 = new C({id:1}), c2 = new C({id:2}); sinon.spy(c2, 'fetch'); b.url = FIXTURES_BASE + "b_3_sideloading.json"; b.fetch(); b.once("deepsync", function() { expect(c2.fetch).to.have.been.calledOnce; expect(c2.isSyncing).to.be.undefined; done(); }); }); it("should not fetch side-loaded referenced models that are referenced from other models in circular relations", function(done) { var b = new B({id:4}); var c1 = new C({id:1}), c2 = new C({id:2}); sinon.spy(b, 'fetch'); sinon.spy(c1, 'fetch'); sinon.spy(c2, 'fetch'); b.url = FIXTURES_BASE + "b_4_sideloading.json"; b.fetch(); b.once("deepsync", function() { expect(b.fetch).to.have.been.calledOnce; expect(c1.fetch).to.not.have.been.called; expect(c2.fetch).to.have.been.calledOnce; expect(b.isSyncing).to.be.undefined; expect(c1.isSyncing).to.be.undefined; expect(c2.isSyncing).to.be.undefined; done(); }); }); }); describe('#save()', function() { it("should support creating referenced objects by inlining their JSON representation and preserve uniques", function() { var server = sinon.fakeServer.create(); var b = new B({ title: "new_b", oneA: { title: "new_a" } }); var a = b.get("oneA"); b.save(null, { inlineJSON: "oneA" }); server.requests[0].respond(200, { "Content-Type": "application/json" }, JSON.stringify({ id: 1, title: "new_b", oneA: { id: 2, title: "new_a" } }) ); expect(b.get("oneA")).to.equal(a); server.restore(); }); }); }); });