backbone-rel
Version:
Relationships between Backbone models in the flavor of MongoDB's document references and embeddings
927 lines (748 loc) • 34.2 kB
JavaScript
define(function(require) {
"use strict";
var _ = require("underscore"),
Backbone = require("backbone-rel");
/**
* Factory method for a callback for synchronizing sub-goals
* After the returned callback has been called 'divideBy' times
* the allDoneCb is called.
*/
var doneDivision = function(divideBy, allDoneCb) {
var done = 0;
var oneDoneCb = function(err) {
if(err) {
allDoneCb(err);
} else {
done++;
if(done==divideBy) allDoneCb();
if(done>divideBy)
throw new Error("done callback was called too many times (" + divideBy+" calls expected, got " + done +" call)");
}
};
return oneDoneCb;
};
var A = Backbone.Model.extend({
references: {
oneC: function() { return C; }
},
embeddings:{
embeddedModel: function() { return EmbeddedModel; },
embeddedCollection: function() { return EmbeddedCollection; }
},
autoFetchRelated: [ "oneC" ],
url: function() { return FIXTURES_BASE + "a_" + this.id + ".json"; } // override for using fixtures
});
var ACollection = Backbone.Collection.extend({
model: A,
url: FIXTURES_BASE + "a_collection.json"
});
var B = Backbone.Model.extend({
references: {
oneA : A,
manyAs : ACollection,
oneC : function() { return C; }
},
defaults: {
manyAs: []
},
autoFetchRelated: [ "oneA", "manyAs", "oneC" ],
url: function() { return FIXTURES_BASE + "b_" + this.id + ".json"; } // override for using fixtures
});
var C = Backbone.Model.extend({
references: {
oneB : B
},
autoFetchRelated: [ "oneB" ],
url: function() { return FIXTURES_BASE + "c_" + this.id + ".json"; } // override for using fixtures
});
var EmbeddedModel = Backbone.Model.extend({
references: {
oneA: A
}
});
var EmbeddedCollection = Backbone.Collection.extend({
model: EmbeddedModel
});
describe("Model", function() {
this.timeout(4000); // wait a max time of 4s for async requests
it("should be initializable", function() {
expect(Backbone.Model).to.exist;
expect(new Backbone.Model()).to.be.an.instanceof(Backbone.Model);
});
it("should be possible to define defaults for referenced objects", function() {
var b = new B();
expect(b.get("manyAs"))
.to.be.an.instanceof(ACollection);
});
it("should correctly resolve the reference model class when a resolve function is supplied", function() {
var a = new A({oneCId: 1});
expect(a.get("oneC"))
.to.be.an.instanceof(C);
});
it("should be possible to define custom attribute name patterns for ID references", function() {
var MyModel = Backbone.Model.extend({
references: {
oneA: A
},
referenceAttributeName: function(refKey) {
return "idFor_" + refKey;
}
});
var m = new MyModel({ oneA: new A({ id: "a1" })});
expect(m.get("idFor_oneA")).to.equal("a1");
expect(m.get("oneAId")).to.not.exist;
var json = m.toJSON();
expect(json.idFor_oneA).to.equal("a1");
expect(json.oneAId).to.not.exist;
});
it("should be possible to use the reference name as the attribute name for the ID reference", function() {
var MyModel = Backbone.Model.extend({
references: {
oneA: A
},
referenceAttributeName: function(refKey) {
return refKey;
}
});
var m = new MyModel({ oneA: "a1" });
expect(m.get("oneA")).to.be.an.instanceof(A);
expect(m.get("oneA").id).to.equal("a1");
m.set("oneA", new A({ id: "a2" }));
var json = m.toJSON()
expect(json.oneA).to.equal("a2");
});
describe("#constructor", function() {
it("should auto-fetch embeddings", function() {
var e = new EmbeddedModel({ id: 1 });
e.url = FIXTURES_BASE + "embedded_model.json";
sinon.spy(e, 'fetch');
var AWithEmbeddedAutoFetch = A.extend({
autoFetchRelated: true
});
var server = sinon.fakeServer.create();
var a = new AWithEmbeddedAutoFetch({
id: 1
});
expect(a.get("embeddedModel")).to.exist;
expect(a.get("embeddedModel").isSyncing).to.be.true;
server.restore();
});
});
describe('#set()', function() {
it("should be possible to set a referenced object directly", function() {
var a = new A({id:1});
var b1 = new B({id:1}),
b2 = new B({id:2, oneA: a});
b1.set("oneA", a);
expect(b1.get("oneA")).to.equal(a);
expect(b2.get("oneA")).to.equal(a);
});
it("should be possible to set a referenced object by ID", function() {
var b = new B({id:1});
b.set("oneA", 2);
expect(b.get("oneA"))
.to.be.an.instanceof(A);
expect(b.get("oneA"))
.to.have.property("id", 2);
});
it("should be possible to set a collection of referenced objects", function() {
var aCollection = new ACollection([
new A({id:1}), new A({id:2}), new A({id:3})
]);
var b1 = new B({id:1}),
b2 = new B({id:2, manyAs: aCollection});
b1.set("manyAs", aCollection);
expect(b1.get("manyAs")).to.equal(aCollection);
expect(b2.get("manyAs")).to.equal(aCollection);
});
it("should set the referenced object ID reference in the attributes", function() {
var a = new A({id:1});
var b = new B({id:1});
b.set("oneA", a);
b.set("manyAs", new ACollection([a]));
expect(b.get("oneAId")).to.equal(1);
expect(b.get("manyAIds")).to.have.length.of(1);
expect(b.get("manyAIds")[0]).to.equal(1);
});
it("should trigger 'change:{referenceKey}' when the ID to the referenced object changed", function() {
var a1 = new A({id:1});
var b = new B({id:1, oneA: a1});
var spy = {
onChange: function() {}
};
sinon.spy(spy, "onChange");
b.on("change:oneA", spy.onChange);
b.set("oneA", { id: 2 });
expect(spy.onChange).to.have.been.calledOnce;
});
it("should update the ID reference when a new referenced object receives an ID", function() {
var a1 = new A({title:"a_1"});
var b = new B({id:1, oneA: a1});
expect(b.get("oneAId")).to.be.undefined;
a1.set("id", 1); // create a1 on the server, server assigns an ID
expect(b.get("oneAId")).to.equal(1);
expect(b.toJSON()).to.have.property("oneAId", 1);
});
it("should update the ID reference array when a new referenced item receives an ID", function() {
var a1 = new A({title:"a_1"}),
a2 = new A({id: 2, title:"a_2"});
var b = new B({id:1, manyAs: [a1, a2]});
expect(b.get("manyAIds")).to.deep.equal([2]);
a1.set("id", 1); // create a1 on the server, server assigns an ID
expect(b.get("manyAIds")).to.deep.equal([1, 2]);
expect(b.toJSON().manyAIds).to.deep.equal([1, 2]);
});
it("should fetch the new referenced object for to-one relations", function(done) {
var b = new B({id: 1});
b.fetch();
b.once("sync", function() {
expect(b.get("oneA"))
.to.be.an.instanceof(A);
b.get("oneA").once("sync", function() {
expect(b.get("oneA").get("title")).to.equal("a1");
done();
});
});
});
it("should fetch the new referenced objects for to-many relations", function(done) {
var b = new B({id: 1});
b.fetch();
b.once("sync", function() {
expect(b.get("manyAs"))
.to.be.an.instanceof(ACollection);
expect(b.get("manyAs").models).to.have.length(3);
var oneDone = doneDivision(b.get("manyAs").models.length, done);
_.each(b.get("manyAs").models, function(a) {
a.once("sync", function() {
expect(a.get("title")).to.equal("a"+a.id);
oneDone();
});
});
});
});
it("should merge side-loaded attribute data into existing referenced objects", function(done) {
var a = new A({id:1});
var b = new B({id: 1, manyAs: [a]});
b.url = FIXTURES_BASE + "b_1_sideloading.json";
b.on("error", function(model, resp, opts) { done(new Error("fetch failed: " + resp.status)); });
b.fetch();
b.once("deepsync", function() {
expect(b.get("manyAs").models).to.have.length(3);
expect(b.get("manyAs").models[0]).to.equal(a);
expect(a.get("title")).to.equal("a1_s");
done();
});
});
it("should support side-loading of referenced objects", function(done) {
var b = new B();
b.url = FIXTURES_BASE + "b_1_sideloading.json";
b.fetch();
b.once("sync", function() {
expect(b.get("oneA"))
.to.be.an.instanceof(A);
expect(b.get("oneA").get("title")).to.equal("a1_s");
expect(b.get("manyAs"))
.to.be.an.instanceof(ACollection);
expect(b.get("manyAs").models).to.have.length(3);
_.each(b.get("manyAs").models, function(a) {
expect(a.get("title")).to.equal("a"+a.id+"_s");
});
done();
});
});
it("should clear current attributes that are not present in the set attrs if the `clear` option is set", function() {
var a1 = new A({
id: 1,
newAttr: "new_attr"
});
var idChanged = sinon.spy();
var newAttrChanged = sinon.spy();
var titleChanged = sinon.spy();
a1.on("change:id", idChanged);
a1.on("change:newAttr", newAttrChanged);
a1.on("change:title", titleChanged);
a1.set({ id: 1, title: "a1" }, { clear: true });
expect(a1.get("newAttr")).to.not.exist;
expect(a1.get("title")).to.equal("a1");
expect(idChanged).to.have.not.been.called;
expect(titleChanged).to.have.been.calledOnce;
expect(newAttrChanged).to.have.been.calledOnce;
});
it("should clear current references that are not present in the set attrs if the `clear` option is set", function() {
var a1 = new A({ id: 1, title: "a1" });
var b1 = new B({ id: 1, oneA: a1, manyAIds: [1, 2, 3] });
b1.set("oneAId", 2, { clear: true });
expect(b1.get("oneA")).to.exist;
expect(b1.get("manyAs").length).to.equal(0);
expect(a1.get("title")).to.equal("a1");
b1.set({ id: 1 }, { clear: true });
expect(b1.get("oneA")).to.not.exist;
});
it("should clear current embeddings that are not present in the set attrs if the `clear` option is set", function() {
var e1 = new EmbeddedModel({ id: 1, title: "e1" });
var eC = new EmbeddedCollection([{id:2}, {id:3}]);
var a1 = new A({ id: 1, embeddedModel: e1, embeddedCollection: eC });
a1.set({ id: 1 }, { clear: true });
expect(a1.get("embeddedModel")).to.not.exist;
expect(e1.get("title")).to.equal("e1");
expect(a1.get("embeddedCollection")).to.not.exist;
});
it("should restore its default value when clearing an attribute", function() {
var ModelWithDefaults = Backbone.Model.extend({
references: {
oneA: A,
manyAs: ACollection
},
embeddings: {
embeddedModel: EmbeddedModel,
embeddedCollection: EmbeddedCollection
},
defaults: {
defAttr: "def",
oneA: new A({id: 1}),
manyAs: [],
embeddedModel: { id: 1 },
embeddedCollection: [ {id: 1} ]
}
});
var m = new ModelWithDefaults({
oneAId: 2,
manyAIds: [1, 2],
defAttr: "changed",
embeddedModel: null
});
m.set({ newAttr: "new" }, {clear: true});
expect(m.get("defAttr")).to.equal("def");
expect(m.get("oneA").id).to.equal(1);
expect(m.get("manyAs").length).to.equal(0);
expect(m.get("embeddedModel").id).to.equal(1);
expect(m.get("embeddedCollection").length).to.equal(1);
});
it("should leave referenced objects untouched when clearing", function() {
var a1 = new A({ id: 1, title: "a1" });
var b1 = new B({ id: 1, oneA: a1 });
b1.set({ id: 1, oneA: {} }, { clear: true });
expect(b1.get("oneA").get("title")).to.not.exist;
expect(a1.get("title")).to.equal("a1");
});
// This most probably is not desirable... Or may be it is?
it("should leave embeddded objects untouched when clearing", function() {
var e1 = new EmbeddedModel({ id: 1, title: "e1" });
var eC = new EmbeddedCollection([{id:2}, {id:3}]);
var a1 = new A({ id: 1, embeddedModel: e1, embeddedCollection: eC });
a1.set({ id: 1, embeddedModel: {}, embeddedCollection: [] }, { clear: true });
expect(a1.get("embeddedModel").get("title")).to.not.exist;
expect(e1.get("title")).to.equal("e1");
expect(a1.get("embeddedCollection").models).to.be.empty;
expect(eC.length).to.equal(2);
});
});
describe('#save()', function() {
it("should support creating referenced objects by inlining their JSON representation when saving a model", function() {
var server = sinon.fakeServer.create();
var b = new B({
title: "new_b",
oneA: {
title: "new_a"
}
});
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",
description: "desc"
}
})
);
expect(b.get("oneAId")).to.equal(2);
var a = b.get("oneA");
expect(a.id).to.equal(2);
expect(a.get("description")).to.equal("desc");
server.restore();
});
});
describe('#parse', function () {
it('should be called once for a to-one reference with side-loading when the parent model is set with parse option', function () {
var b = new B({
oneA: {
title: "new_a"
}
});
var oneA = b.get("oneA");
sinon.spy(oneA, "parse");
b.set({
oneA: {
title: "new_a"
}
}, { parse: true });
expect(oneA.parse).to.have.been.calledOnce;
});
it('should also be called only once for each item in a to-many side-loading with parse option', function () {
var b1 = new B({id:1, manyAs: [ { id: 1 } ]});
var a1 = b1.get("manyAs").get(1);
sinon.spy(a1, "parse");
b1.set({ manyAs: [ { id: 1 } ] }, { parse: true });
expect(a1.parse).to.have.been.calledOnce;
});
});
describe('#unset()', function() {
it("should unset referenced objects", function() {
var a = new A({id:1});
var b = new B({id:1, oneA: a});
expect(b.get("oneA")).to.equal(a);
b.unset("oneA");
expect(b.get("oneA")).to.be.undefined;
expect(b.get("oneAId")).to.be.undefined;
});
});
describe('#toJSON()', function() {
it("should return a JSON hash with an ID reference to the referenced object", function() {
var a = new A({id:1});
var b = new B({id:1, oneA: a});
var json = b.toJSON();
expect(json).to.not.have.property("oneA");
expect(json).to.have.property("oneAId", 1);
});
it("should return a JSON hash with an array of ID references to the referenced objects", function() {
var aCollection = new ACollection([
new A({id:1}), new A({id:2}), new A({id:3})
]);
var b = new B({id:1, manyAs: aCollection});
var json = b.toJSON();
expect(json).to.not.have.property("manyAs");
expect(json).to.have.property("manyAIds")
.that.is.an("array")
.with.length.of(3);
expect(json.manyAIds).to.include.members([1, 2, 3]);
});
});
describe('#fetchRelated', function() {
it("should fetch all related objects for the passed relation keys", function() {
var eM = new EmbeddedModel({id:1}),
c = new C({id: 2});
var AWithoutAutoFetch = A.extend({
autoFetchRelated: false
});
var a = new AWithoutAutoFetch({
id: 1,
oneC: c,
embeddedModel: eM
});
sinon.spy(c, "fetch");
sinon.spy(eM, "fetch");
var server = sinon.fakeServer.create();
a.fetchRelated(["oneC", "embeddedModel", "embeddedCollection"]);
expect(c.fetch).to.have.been.calledOnce;
expect(eM.fetch).to.have.been.calledOnce;
// embeddings will be inititalized, if they have not been set before
expect(a.get("embeddedCollection")).to.exist;
expect(a.get("embeddedCollection").isSyncing).to.be.true;
server.restore();
});
it("should trigger 'deepsync' when all related objects have been synced", function(done) {
var eM = new EmbeddedModel({id:1}),
eC = new EmbeddedCollection(),
c = new C({id: 2});
var a = new A({
id: 1,
oneC: c
});
a.fetchRelated(["oneC"]);
a.once("deepsync", function() {
done();
});
});
it("should fetch all those related objects that have not been synced before, if no keys are given", function() {
var eM = new EmbeddedModel({id:1}),
c = new C({id: 2});
var AWithCollectionAutoFetch = A.extend({
autoFetchRelated: ["embeddedCollection"]
});
var server = sinon.fakeServer.create();
var a = new AWithCollectionAutoFetch({
id: 1,
oneC: c,
embeddedModel: eM
});
var eC = a.get("embeddedCollection");
expect(eC).to.exist;
expect(eC.isSyncing).to.be.true;
// respond to a_1
server.requests[0].respond(200, { "Content-Type": "application/json" },
JSON.stringify({})
);
sinon.spy(c, "fetch");
sinon.spy(eM, "fetch");
sinon.spy(eC, "fetch");
a.fetchRelated();
expect(c.fetch).to.have.been.calledOnce;
expect(eM.fetch).to.have.been.calledOnce;
expect(eC.fetch).to.have.not.been.called;
server.restore();
});
//it("should fetch embedded objects straight away, but referenced objects only after the model is finished syncing", function(done) {
//
//});
});
describe('#destroy', function() {
it("should unset all references from other models to the the destroyed model", function(done) {
var b = new B({ id: 1 });
b.fetch();
b.once("deepsync", function() {
var a = b.get("oneA");
a.destroy();
expect(b.get("oneA")).to.be.undefined;
expect(b.get("oneAId")).to.be.undefined;
done();
});
});
});
describe('#previous', function() {
it("should return the previous related object, when called with a relationship key", function() {
var c1 = new C({ id: 1 });
var c2 = new C({ id: 2 });
var e1 = new EmbeddedModel({ id: 1 });
var e2 = new EmbeddedModel({ id: 2 });
var a = new A({
oneC: c1,
embeddedModel: e1
});
a.on("change", function() {
expect(a.get("oneC")).to.equal(c2);
expect(a.previous("oneC")).to.equal(c1);
expect(a.get("embeddedModel")).to.equal(e2);
expect(a.previous("embeddedModel")).to.equal(e1);
});
a.set({
oneC: c2,
embeddedModel: e2
});
});
});
describe('Events', function() {
it("should trigger change:referenceId when the ID ref changes", function(done) {
var a1 = new A({id:1});
var b = new B({
id: 5,
oneA: a1
});
b.once("change:oneAId", function() {
expect(b.get("oneAId")).to.equal(2);
done();
});
a1.set("id", 2);
});
it("should trigger change:referenceIds when one ID in the ref array changes", function(done) {
var a1 = new A({id: 1}),
a2 = new A({id: 2});
var b = new B({
id: 5,
manyAs: [a1, a2]
});
b.once("change:manyAIds", function() {
expect(b.get("manyAIds")).to.deep.equal([3,2]);
done();
});
a1.set("id", 3);
});
it("should trigger change:referenceIds when an item is add to the reference", function(done) {
var a1 = new A({id: 1}),
a2 = new A({id: 2});
var b = new B({
id: 5,
manyAs: [a1]
});
b.once("change:manyAIds", function() {
expect(b.get("manyAIds")).to.deep.equal([1,2]);
done();
});
b.get("manyAs").add(a2);
});
it("should trigger change:referenceIds when an item is removed from the reference", function(done) {
var a1 = new A({id: 1}),
a2 = new A({id: 2});
var b = new B({
id: 5,
manyAs: [a1, a2]
});
b.once("change:manyAIds", function() {
expect(b.get("manyAIds")).to.deep.equal([1]);
done();
});
b.get("manyAs").remove(2);
});
it("should trigger 'deepsync' when all referenced objects have been synced", function(done) {
var b = new B({id: 1});
b.fetch();
b.once("deepsync", function() {
expect(b.get("oneA").get("title")).to.equal("a1");
_.each(b.get("manyAs").models, function(a) {
expect(a.get("title")).to.equal("a"+a.id);
});
done();
});
});
it("should also trigger 'deepsync' in the case that all referenced objects are side-loaded", function(done) {
var b = new B();
b.url = FIXTURES_BASE + "b_1_sideloading.json";
b.fetch();
b.once("deepsync", function() {
done();
});
});
it("should trigger 'deepsync' on 'sync' when there are no related objects to be fetched", function(done) {
var c = new C({id: 2});
c.autoFetchRelated = false;
c.fetch();
c.once("deepsync", function() {
expect(c.isSynced).to.be.true;
done();
});
});
it("should trigger 'deepchange' on 'change' of one the referenced objects", function() {
var a1 = new A({ id: 1});
var b1 = new B({ id: 1, oneA: a1 });
var spy = {
onDeepChangeB: function() {}
};
sinon.spy(spy, "onDeepChangeB");
b1.on("deepchange", spy.onDeepChangeB);
a1.set("title", "a1_changed");
expect(spy.onDeepChangeB).to.be.calledOnce;
});
it("should trigger 'deepchange' events when referenced collections are changed", function() {
var b1 = new B({ id: 1, manyAs: [{id:1}, {id:2}] });
var spy = {
onDeepChange: function() {}
};
sinon.spy(spy, "onDeepChange");
b1.on("deepchange", spy.onDeepChange);
b1.get("manyAs").push(new A({id: 3}));
expect(spy.onDeepChange).to.be.calledOnce;
});
it("should propagate 'deepchange' events from referenced models", function() {
var a1 = new A({
id: 1,
oneC: new C({
id: 1,
oneB: new B({id: 1})
})
});
var spy = {
onDeepChange: function() {}
};
sinon.spy(spy, "onDeepChange");
a1.on("deepchange", spy.onDeepChange);
a1.get("oneC").get("oneB").set("title", "b1_changed");
expect(spy.onDeepChange).to.be.calledOnce;
});
it("should propagate 'deepchange' events from referenced collections", function() {
var a1 = new A({ id: 1});
var b1 = new B({ id: 1, manyAs: [a1] });
var spy = {
onDeepChange: function() {}
};
sinon.spy(spy, "onDeepChange");
b1.on("deepchange", spy.onDeepChange);
a1.set("title", "a1_changed");
expect(spy.onDeepChange).to.be.calledOnce;
});
it("should consolidate 'deepchange' events when when those are propagated through multiple paths in the object graph", function() {
var a1 = new A({ id: 1});
var b1 = new B({ id: 1, oneA: a1, manyAs: [a1] });
var spy = {
onDeepChange: function() {}
};
sinon.spy(spy, "onDeepChange");
b1.on("deepchange", spy.onDeepChange);
a1.set("title", "a1_changed");
expect(spy.onDeepChange).to.be.calledOnce;
});
it("should handle 'deepchange' events that are propagated through circular paths in the object graph", function() {
var b1 = new B({ id: 1 });
var c1 = new C({ id: 1, oneB: b1});
b1.set("oneC", c1);
var spy = {
onDeepChangeB: function() {},
onDeepChangeC: function() {}
};
sinon.spy(spy, "onDeepChangeB");
sinon.spy(spy, "onDeepChangeC");
b1.on("deepchange", spy.onDeepChangeB);
c1.on("deepchange", spy.onDeepChangeC);
c1.set("title", "c1_changed");
expect(spy.onDeepChangeB).to.be.calledOnce;
expect(spy.onDeepChangeC).to.be.calledOnce;
});
it("should propagate the 'error' event when the automatic fetch of referenced objects fails", function() {
var b = new B({});
var server = sinon.fakeServer.create();
b.set("oneAId", "invalid_id");
b.on("error", function(model, resp, opts) {
expect(model).to.be.an.instanceof(A);
expect(resp.status).to.equal(404);
});
server.requests[0].respond(404, {}, "");
server.restore();
});
it("should propagate the 'error' event when parsing of fetched data for referenced objects fails", function(done) {
var b = new B({});
b.set("oneAId", "badjson");
b.on("error", function(model, resp, opts) {
expect(model).to.be.an.instanceof(A);
done();
});
});
});
});
describe("Collection", function() {
this.timeout(4000); // wait a max time of 4s for async requests
describe("#sync()", function() {
it("should set the isSynced flag on each item model that has been added during the sync", function(done) {
var a5 = new A({id:5});
var col = new ACollection([a5]);
col.fetch();
col.once("sync", function() {
expect(col.get(1)).to.have.property("isSynced", true);
expect(a5).to.not.have.property("isSynced");
done();
});
});
it("should set the isSynced flag on each item model that has been added during the sync when resetting", function(done) {
var col = new ACollection();
col.fetch({reset: true});
col.once("sync", function() {
expect(col.get(1)).to.have.property("isSynced", true);
done();
});
});
it("should set the isSynced flag on each item model before the 'add' event is triggered", function(done) {
var col = new ACollection();
col.fetch();
col.once("add", function(model) {
expect(model).to.have.property("isSynced", true);
done();
});
});
it("should trigger 'deepsync' also when there are no related objects to be fetched", function(done) {
var CNoAutoFetch = C.extend({ autoFetchRelated: false });
var CCollection = Backbone.Collection.extend({
model: CNoAutoFetch,
url: FIXTURES_BASE + "a_collection.json"
});
var col = new CCollection();
col.fetch();
col.once("deepsync", function() {
expect(col.isSynced).to.be.true;
done();
});
});
});
});
});