UNPKG

backbone-rel

Version:

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

438 lines (352 loc) 15.4 kB
define(function(require) { "use strict"; var _ = require("underscore"), Backbone = require("backbone-rel"); var A = Backbone.Model.extend({ embeddings: { embeddedModel: function() { return EmbeddedModel; }, embeddedCollection: function() { return EmbeddedCollection; }, embeddedCollectionOfA: function() { return EmbeddedCollectionOfA; } }, autoFetchRelated: false, url: function() { return this.id ? this.id.toString() : "/"; } }); var EmbeddedModel = Backbone.Model.extend({}); var EmbeddedCollection = Backbone.Collection.extend({ model: EmbeddedModel }); var EmbeddedCollectionOfA = Backbone.Collection.extend({ model: A }); describe("Model", function() { this.timeout(4000); // wait a max time of 4s for async requests var a; beforeEach(function() { a = new A({ id: 1, embeddedModel: { title: "embedded_1" } }); }); afterEach(function() { a = undefined; }); describe("#url()", function() { it("should use the parent object's URL and the embedding key to build a nested URL", function() { var parent = new A({ id: 1 }); parent.url = "/parent"; var embedded = new EmbeddedModel({ id: 1 }); parent.set("embeddedModel", embedded); expect(embedded.url()).to.equal("/parent/embeddedModel"); }); it("should throw an error when the parent object is still new", function() { var parent = new A({ embeddedModel: {} }); expect(parent.get("embeddedModel").url).to.throw(Error); }); }); describe("#destroy()", function() { it("should delete the embedded object in the parent model", function() { var aNew = new A({ embeddedModel: { title: "embedded_1" } }); aNew.get("embeddedModel").destroy(); expect(aNew.get("embeddedModel")).to.be.undefined; }); }); describe('#Events', function() { it("should trigger 'deepchange' on 'change' of one of the embedded objects", function() { var a = new A({ id: 1, embeddedModel: { title: "title" }}); var spy = { onDeepChange: function() {} }; sinon.spy(spy, "onDeepChange"); a.on("deepchange", spy.onDeepChange); a.get("embeddedModel").set("title", "title_changed"); expect(spy.onDeepChange).to.be.calledOnce; }); it("should trigger 'deepchange' on 'change' of an item when setting whole object structures", function() { var OuterEmbeddedModel = Backbone.Model.extend({ embeddings: { embeddedModel: EmbeddedModel } }); var o = new OuterEmbeddedModel({ embeddedModel: { id: 1, title: "embedded_1" } }); var spy = { onDeepChange: function() {} }; sinon.spy(spy, "onDeepChange"); o.on("deepchange", spy.onDeepChange); o.set({ embeddedModel: { id: 1, title: "embedded_1_changed" } }); expect(spy.onDeepChange).to.be.calledOnce; }); }); describe('#unset()', function() { it("should unset embedded objects", function() { var a = new A({ id: 1, embeddedCollection: [ { title: "embedded_1" } ] }); expect(a.get("embeddedCollection")).to.be.an.instanceof(EmbeddedCollection); a.unset("embeddedCollection"); expect(a.get("embeddedCollection")).to.be.undefined; }); }); describe('#toJSON()', function() { it("should nest embedded models' JSON structures if the embedding is declared in the `inlineJSON` property", function() { var AWithInlining = A.extend({ inlineJSON: [ "embeddedModel" ] }); var a = new A({ embeddedModel: { title: "embedded_1" } }); var aWithInlining = new AWithInlining({ embeddedModel: { title: "embedded_1" } }); var json = a.toJSON(); var jsonWithInlining = aWithInlining.toJSON(); expect(json).to.not.have.property("embeddedModel"); expect(jsonWithInlining).to.have.property("embeddedModel") .that.is.an("object") .with.property("title", "embedded_1"); }); it("should nest embedded models' JSON structures if the embedding is declared in the `inlineJSON` option", function() { var a = new A({ embeddedModel: { title: "embedded_1" } }); var json = a.toJSON({ inlineJSON: "embeddedModel" }); expect(json).to.have.property("embeddedModel") .that.is.an("object") .with.property("title", "embedded_1"); }); }); describe("#fetch", function() { it("should auto-fetch embeddings", function() { var e = new EmbeddedModel({ id: 1 }); e.url = FIXTURES_BASE + "embedded_model.json"; var AWithoutAutoFetch = A.extend({ autoFetchRelated: false }); var a = new A({ id: 1, embeddedModel: e }); a.autoFetchRelated = ["embeddedModel"]; sinon.spy(e, 'fetch'); var server = sinon.fakeServer.create(); a.fetch(); expect(e.fetch).to.have.been.calledOnce; //a.once("sync", function() { // expect(e.get("title")).to.equal("embedded_1"); // done(); //}); }); //it("should not auto-fetch embeddings that are side-loaded", function(done) { // var a = new A({ // id: 1, // embeddedModel: { // id: 1 // } // }); // a.autoFetchRelated = ["embeddedModel"]; // a.url = FIXTURES_BASE + "a_1_sideloading.json"; // var e = a.get("embeddedModel"); // sinon.spy(e, 'fetch'); // a.fetch(); // a.once("deepsync", function() { // expect(e.fetch).to.have.not.been.called; // expect(e.get("title")).to.equal("embedded_1"); // done(); // }); //}); }); describe("#save", function() { it("should send a PUT request for a new model with a parent", function() { var a = new A({ id: 1, embeddedModel: { title: "title" }}); var e = a.get("embeddedModel"); var server = sinon.fakeServer.create(); e.save(); expect(server.requests[0].method).to.equal('PUT'); }); it("should send a POST request for a new model in a collection", function() { var a = new A({ id: 1, embeddedCollection: [{ title: "title" }]}); var e = a.get("embeddedCollection").first(); var server = sinon.fakeServer.create(); e.save(); expect(server.requests[0].method).to.equal('POST'); }); }); describe('#parse', function () { it('should be called once for an embedding when the parent model is set with parse option', function () { var a = new A({ id: 1, embeddedModel: { title: "title" }}); var e = a.get("embeddedModel"); sinon.spy(e, "parse"); a.set({ id: 1, embeddedModel: { title: "title" }}, { parse: true }); expect(e.parse).to.have.been.calledOnce; }); it('should also be called just once when embedded in a collection and setting the parent model with parse option', function () { var a = new A({ id: 1, embeddedCollection: [ { id: 1, title: "embedded_1" } ] }); var e1 = a.get("embeddedCollection").get(1); sinon.spy(e1, "parse"); a.set({ id: 1, embeddedCollection: [ { id: 1, title: "embedded_1" } ] }, { parse: true }); expect(e1.parse).to.have.been.calledOnce; }); }); }); describe("Collection", function() { var a; beforeEach(function() { a = new A({ embeddedCollection: [ { id: 1, title: "embedded_1" }, { id: 2, title: "embedded_2" } ] }); }); describe("#url()", function() { it("should use the parent object's URL and the embedding key to build a nested URL", function() { var parent = new A({ id: 1 }); parent.url = "/parent"; var embedded = new EmbeddedCollection({ id: 1 }); parent.set("embeddedCollection", embedded); expect(embedded.url()).to.equal("/parent/embeddedCollection"); }); it("should throw an error when the parent object is still new", function() { var parent = new A({ embeddedCollection: [] }); expect(parent.get("embeddedCollection").url).to.throw(Error); }); }); describe("#set()", function() { it("should set embedded models passed as JSON objects", function() { var a = new A({ embeddedModel: { title: "embedded_1" } }); expect(a.get("embeddedModel")) .to.be.an.instanceof(EmbeddedModel); expect(a.get("embeddedModel").get("title")) .to.equal("embedded_1"); }); it("should set embedded collections passed as JSON arrays", function() { var a = new A({ embeddedCollection: [ { title: "embedded_1" }, { title: "embedded_2" } ] }); expect(a.get("embeddedCollection")).to.be.an.instanceof(EmbeddedCollection); expect(a.get("embeddedCollection").models).to.have.length(2); expect(a.get("embeddedCollection").models[0]).to.be.an.instanceof(EmbeddedModel); expect(a.get("embeddedCollection").models[0].get("title")).to.equal("embedded_1"); expect(a.get("embeddedCollection").models[1]).to.be.an.instanceof(EmbeddedModel); expect(a.get("embeddedCollection").models[1].get("title")).to.equal("embedded_2"); }); /*it("should merge attributes into existing models when setting a embedded collection", function() { var spy = { onAdd: function() {}, onRemove: function() {} }; sinon.spy(spy, 'onAdd'); sinon.spy(spy, 'onRemove'); a.get("embeddedCollection").on("add", spy.onAdd); a.get("embeddedCollection").on("remove", spy.onRemove); var embedded_2 = a.get("embeddedCollection").get(2); a.set("embeddedCollection", [{id:2, title: "embedded_2_upd"}, {id:3, title: "embedded_3"}]); expect(a.get("embeddedCollection")).to.have.length(2); expect(spy.onAdd).to.be.calledOnce; expect(spy.onRemove).to.be.calledOnce; expect(embedded_2.get("title")).to.equal("embedded_2_upd"); });*/ //it("should trigger 'deepchange' and 'deepchange:[attribute]' on its parent when items are added, removed, or changed", function() { // a.set("embeddedCollection", [{id: 1}, {id: 2}, {id:3}]); // var spy = { onChange: function() {}, onChangeEmbedded: function() {} }; // sinon.spy(spy, "onChange"); // sinon.spy(spy, "onChangeEmbedded"); // a.on("change", spy.onChange); // a.on("change:embeddedCollection", spy.onChangeEmbedded); // // a.set("embeddedCollection", [{id: 1}, {id: 2, title: "t2"}, {id:4}]); // expect(spy.onChange).to.be.calledOnce; // expect(spy.onChangeEmbedded).to.be.calledOnce; //}); }); describe("#fetch", function() { //it("should auto-fetch embeddings of all items", function(done) { // A.prototype.autoFetchRelated = ["embeddedModel"]; // // var a = new A({ // id: 1, // embeddedCollectionOfA: [ // { id: 1, embeddedModel: {} }, // { id: 2, embeddedModel: {} } // ] // }); // var e1 = a.get("embeddedCollectionOfA").get(1).get("embeddedModel"), // e2 = a.get("embeddedCollectionOfA").get(2).get("embeddedModel"); // // sinon.spy(e1, 'fetch'); // sinon.spy(e2, 'fetch'); // a.get("embeddedCollectionOfA").fetch(); // a.get("embeddedCollectionOfA").once("sync", function() { // _.defer(function() { // // }); // expect(e1.fetch).to.be.calledOnce; // expect(e2.fetch).to.be.calledOnce; // done(); // }, this); // // // A.prototype.autoFetchRelated = false; //}); }); }); });