marsdb
Version:
MarsDB is a lightweight client-side MongoDB-like database, Promise based, written in ES6
830 lines (743 loc) • 27.8 kB
JavaScript
import Collection from '../../lib/Collection';
import Cursor from '../../lib/Cursor';
import Random from '../../lib/Random';
import chai, {assert, expect} from 'chai';
import _ from 'lodash';
import sinon from 'sinon';
chai.use(require('chai-as-promised'));
chai.use(require('sinon-chai'));
chai.should();
describe('Cursor', () => {
let db;
beforeEach(function () {
db = new Collection('test');
return Promise.all([
db.insert({_id: '1', a: 'a', b: 1, c: 'some text 1', g: 'g1', f: 1, j: '2'}),
db.insert({_id: '2', a: 'b', b: 2, c: 'some text 2', g: 'g1', f: 10, j: '3'}),
db.insert({_id: '3', a: 'c', b: 3, c: 'some text 3', g: 'g1', f: 11, j: '4'}),
db.insert({_id: '4', a: 'd', b: 4, c: 'some text 4', g: 'g1', f: 12, j: '5'}),
db.insert({_id: '5', a: 'e', b: 5, c: 'some text 5', g: 'g2', d: 234, f: 2, j: '6'}),
db.insert({_id: '6', a: 'f', b: 6, c: 'some text 6', g: 'g2', f: 20, k: {a: 1}, j: ['7', '5']}),
db.insert({_id: '7', a: 'g', b: 7, c: 'some text 7', g: 'g2', f: 21, j: [{_id: '1'}, {_id: '2'}]}),
]);
});
describe('#exec', function () {
it('should create new execution on each call', function () {
const cursor = new Cursor(db);
cursor.find({b: {$gt: 4}}).skip(1).sort({b: 1});
const promise = cursor.exec();
cursor.exec().should.not.be.equal(promise);
return promise.then(() => {
const anotherPromise = cursor.exec()
anotherPromise.should.not.be.equal(promise);
return anotherPromise;
})
});
it('should clone docs by default', function () {
const cursor = new Cursor(db);
cursor.find({b: {$gt: 4}}).skip(1).sort({b: 1});
return cursor.exec().then((docs) => {
docs.should.have.length(2);
docs[0].k.a.should.be.equals(1);
docs[0].b.should.be.equals(6);
docs[0].b = 7;
docs[0].k.a = 2;
return cursor.exec().then((docs) => {
docs.should.have.length(2);
docs[0].k.a.should.be.equals(1);
docs[0].b.should.be.equals(6);
});
});
});
it('should NOT clone docs when `options.noClone` passed', function () {
const cursor = new Cursor(db, {}, {noClone: true});
cursor.find({b: {$gt: 4}}).skip(1).sort({b: 1});
return cursor.exec().then((docs) => {
docs.should.have.length(2);
docs[0].k.a.should.be.equals(1);
docs[0].b.should.be.equals(6);
docs[0].b = 7;
docs[0].k.a = 2;
return cursor.exec().then((docs) => {
docs.should.have.length(2);
docs[0].b.should.be.equals(7);
docs[0].k.a.should.be.equals(2);
});
});
});
it('should set latest result', function () {
const cursor = new Cursor(db, {}, {noClone: true});
cursor.find({b: {$gt: 4}}).skip(1).sort({b: 1});
expect(cursor._latestResult).to.be.null;
return cursor.exec().then((docs) => {
cursor._latestResult.should.have.length(2);
})
});
it('should emit `beforeExecute` event', function () {
const cb = sinon.spy();
const cursor = new Cursor(db, {}, {noClone: true});
cursor.on('beforeExecute', cb);
cursor.find({b: {$gt: 4}}).skip(1).sort({b: 1});
cursor.exec();
cb.should.have.callCount(1);
});
});
describe('#skip', function () {
it('should skip documents with sorting', function () {
const cursor = new Cursor(db);
cursor.find({b: {$gt: 4}}).skip(1).sort({b: 1});
return cursor.exec().then((docs) => {
docs.should.have.length(2);
docs[0].b.should.be.equals(6);
docs[1].b.should.be.equals(7);
});
});
it('should return empty with skip out of result', function () {
const cursor = new Cursor(db);
cursor.find({b: {$gt: 4}}).skip(10).sort({b: 1});
return cursor.exec().then((docs) => {
docs.should.have.length(0);
});
});
it('should throw an error with negative skip', function () {
const cursor = new Cursor(db);
(() => cursor.find({b: {$gt: 4}}).skip(-1)).should.throw(Error);
});
});
describe('#limit', function () {
it('should limit documents with sorting', function () {
const cursor = new Cursor(db);
cursor.find({b: {$gt: 4}}).limit(2).sort({b: 1});
return cursor.exec().then((docs) => {
docs.should.have.length(2);
docs[0].b.should.be.equals(5);
docs[1].b.should.be.equals(6);
});
});
it('should return not limites result with zero limit', function () {
const cursor = new Cursor(db);
cursor.find({b: {$gt: 4}}).limit(0).sort({b: 1});
return cursor.exec().then((docs) => {
docs.should.have.length(3);
});
});
it('should throw an error with negative skip', function () {
const cursor = new Cursor(db);
(() => cursor.find({b: {$gt: 4}}).limit(-1)).should.throw(Error);
});
});
describe('#ifNotEmpty', function () {
it('should stop pipeline processing if result is null', function (done) {
const cursor = new Cursor(db);
cursor.find({b: 6})
.ifNotEmpty().aggregate(docs => {
docs.should.have.length(1);
return null;
})
.ifNotEmpty().aggregate(docs => {
throw new Error('Docs is empty but aggregate is invked')
})
.exec()
.then(() => done());
});
it('should stop pipeline processing if result is undefined', function (done) {
const cursor = new Cursor(db);
cursor.find({b: 6})
.ifNotEmpty().aggregate(docs => {
docs.should.have.length(1);
return undefined;
})
.ifNotEmpty().aggregate(docs => {
throw new Error('Docs is empty but aggregate is invked')
})
.exec()
.then(() => done());
});
it('should stop pipeline processing if result is empty array', function (done) {
const cursor = new Cursor(db);
cursor.find({b: 6})
.ifNotEmpty().aggregate(docs => {
docs.should.have.length(1);
return [];
})
.ifNotEmpty().aggregate(docs => {
throw new Error('Docs is empty but aggregate is invked')
})
.exec()
.then(() => done());
});
});
describe('#find', function () {
it('should return all objects with empty query object', function () {
const cursor = new Cursor(db);
cursor.find({});
return cursor.exec().then((docs) => {
docs.should.have.length(7);
});
});
it('should return all objects with undefined query object', function () {
const cursor = new Cursor(db);
cursor.find();
return cursor.exec().then((docs) => {
docs.should.have.length(7);
});
});
it('should find with all available functions', function () {
const cursor = new Cursor(db);
cursor.find({
a: {$ne: 'd'},
b: {
$in: [3, 4, 5, 6],
$nin: [1, 2, 3],
$lt: 18,
$gt: 1,
$lte: 6,
$gte: 2,
},
d: {$exists: true},
c: {$regex: /5$/i}
});
return cursor.exec().then((docs) => {
docs.should.have.length(1);
docs[0].a.should.be.equals('e');
});
});
});
describe('#sort', function () {
it('should sort by number field asc', function () {
const cursor = new Cursor(db);
cursor.find().sort({f: 1});
return cursor.exec().then((docs) => {
docs.should.have.length(7);
docs[0].f.should.be.equals(1);
docs[1].f.should.be.equals(2);
docs[6].f.should.be.equals(21);
});
});
it('should sort by number field desc', function () {
const cursor = new Cursor(db);
cursor.find().sort({f: -1});
return cursor.exec().then((docs) => {
docs.should.have.length(7);
docs[0].f.should.be.equals(21);
docs[1].f.should.be.equals(20);
docs[5].f.should.be.equals(2);
docs[6].f.should.be.equals(1);
});
});
it('should sort by text field asc', function () {
const cursor = new Cursor(db);
cursor.find().sort({a: 1});
return cursor.exec().then((docs) => {
docs.should.have.length(7);
docs[0].a.should.be.equals('a');
docs[6].a.should.be.equals('g');
});
});
it('should sort by text field desc', function () {
const cursor = new Cursor(db);
cursor.find().sort({a: -1});
return cursor.exec().then((docs) => {
docs.should.have.length(7);
docs[0].a.should.be.equals('g');
docs[6].a.should.be.equals('a');
});
});
});
describe('#sortFunc', function () {
it('should sort with custom function', function () {
const cursor = new Cursor(db);
cursor.find().sortFunc((a, b) => b.b - a.b);
return cursor.exec().then((docs) => {
docs.should.have.length(7);
docs[0].b.should.be.equals(7);
docs[1].b.should.be.equals(6);
});
});
it('should sort after index sorting', function () {
const cursor = new Cursor(db);
cursor.find().sort({a: -1}).sortFunc((a, b) => a.b - b.b);
return cursor.exec().then((docs) => {
docs.should.have.length(7);
docs[0].b.should.be.equals(1);
docs[1].b.should.be.equals(2);
});
});
it('should throw an error if sorter is not a function', function () {
const cursor = new Cursor(db);
(() => cursor.sortFunc(123)).should.throw(Error);
});
});
describe('#filter', function () {
it('should filter by function', function () {
const cursor = new Cursor(db);
cursor.find().sort({b: 1}).filter(d => d.b === 3 || d.b === 7);
return cursor.exec().then((docs) => {
docs.should.have.length(2);
docs[0].b.should.be.equals(3);
docs[1].b.should.be.equals(7);
});
});
it('should throw an error if filter is not a function', function () {
const cursor = new Cursor(db);
(() => cursor.filter(123)).should.throw(Error);
});
});
describe('#map', function () {
it('should map by a function', function () {
const cursor = new Cursor(db);
cursor.find().sort({b: 1}).map(d => {
return {b: d.b * 2};
});
return cursor.exec().then((docs) => {
docs.should.have.length(7);
docs[0].should.not.have.ownProperty('a');
docs[1].should.not.have.ownProperty('a');
docs[0].b.should.be.equals(2);
docs[1].b.should.be.equals(4);
});
});
it('should throw an error if map is not a function', function () {
const cursor = new Cursor(db);
(() => cursor.map(123)).should.throw(Error);
});
});
describe('#reduce', function () {
it('should reduce by a function', function () {
const cursor = new Cursor(db);
cursor.find().sort({b: 1}).reduce((acum, val) => {
acum[val.g] += val.b;
return acum;
}, {g1: 0, g2: 0});
return cursor.exec().then((res) => {
res['g1'].should.be.equals(10);
res['g2'].should.be.equals(18);
});
});
it('should throw an error if reduce is not a function', function () {
const cursor = new Cursor(db);
(() => cursor.reduce(123)).should.throw(Error);
});
});
describe('#aggregate', function () {
it('should aggregate by a function', function () {
const cursor = new Cursor(db);
cursor.find().sort({b: 1}).aggregate(docs => {
return docs.length;
});
return cursor.exec().then((docs) => {
docs.should.be.equals(7);
});
});
it('should throw an error if aggregate is not a function', function () {
const cursor = new Cursor(db);
(() => cursor.aggregate(123)).should.throw(Error);
});
});
describe('#join', function () {
it('should use joinEach for an array', function () {
return db.find().join(d => {
d.should.be.an('object');
}).then((result) => {
result.should.be.an('array');
});
});
it('should use joinAll for single object', function () {
return db.findOne().join(d => {
d.should.be.an('object');
}).then((result) => {
result.should.be.an('object');
});
});
it('should joinObj for object value', function () {
sinon.spy(db, 'find');
sinon.spy(db, 'findOne');
return db.findOne('1').join({j: db}).then(res => {
db.find.should.be.calledOnce;
db.findOne.should.be.calledOnce;
res.j._id.should.be.equal('2');
});
});
it('should throw an error if join is not a function', function () {
const cursor = new Cursor(db);
(() => cursor.join(123)).should.throw(Error);
});
it('should execute cursor if not executed yet', function () {
const cursor = new Cursor(db);
const cursorJoined = new Cursor(db);
cursorJoined.exec = sinon.stub();
cursorJoined.exec.returns({ cursor: cursorJoined, then: (fn) => fn() });
return cursor.find().join(() => cursorJoined).exec().then(() => {
cursorJoined.exec.should.have.callCount(7);
})
});
it('should resolve array of cursors returned in join', function () {
const cursor = new Cursor(db);
const cursorJoined_1 = new Cursor(db);
cursorJoined_1.exec = sinon.stub();
cursorJoined_1.exec.returns({ cursor: cursorJoined_1, then: (fn) => fn() });
const cursorJoined_2 = new Cursor(db);
cursorJoined_2.exec = sinon.stub();
cursorJoined_2.exec.returns({ cursor: cursorJoined_2, then: (fn) => fn() });
return cursor.find().join(() => [
cursorJoined_1, cursorJoined_2
]).exec().then(() => {
cursorJoined_1.exec.should.have.callCount(7);
cursorJoined_2.exec.should.have.callCount(7);
})
});
});
describe('#joinObj', function () {
it('should join for all docs only by one query call', function () {
sinon.spy(db, 'find');
return db.find().sort(['_id']).joinObj({j: db}).then(res => {
db.find.should.be.calledTwice;
res[0].j._id.should.be.equal('2');
res[1].j._id.should.be.equal('3');
res[2].j._id.should.be.equal('4');
res[3].j._id.should.be.equal('5');
res[4].j._id.should.be.equal('6');
res[5].j.should.have.length(2);
res[5].j[0]._id.should.be.equal('7');
res[5].j[1]._id.should.be.equal('5');
expect(res[6].j).to.be.an('array');
});
});
it('should join for findOne', function () {
sinon.spy(db, 'find');
sinon.spy(db, 'findOne');
return db.findOne('1').joinObj({j: db}).then(res => {
db.find.should.be.calledOnce;
db.findOne.should.be.calledOnce;
res.j._id.should.be.equal('2');
});
});
it('should join for findOne with undefined result', function () {
sinon.spy(db, 'find');
sinon.spy(db, 'findOne');
return db.findOne('111').joinObj({j: db}).then(res => {
db.find.should.be.have.callCount(0);
db.findOne.should.be.calledOnce;
});
});
it('should join with nested object', function () {
sinon.spy(db, 'find');
return db.find().sort(['_id']).joinObj({'j._id': {model: db, joinPath: 'j.$'}}).then(res => {
db.find.should.be.calledTwice;
expect(res[0].j).to.be.equal('2');
expect(res[1].j).to.be.equal('3');
expect(res[2].j).to.be.equal('4');
expect(res[3].j).to.be.equal('5');
expect(res[4].j).to.be.equal('6');
expect(res[5].j).to.be.deep.equal(['7', '5']);
res[6].j.should.have.length(2);
res[6].j[0]._id.should.be.equal('1');
res[6].j[1]._id.should.be.equal('2');
});
});
it('should observe joined objects', function (done) {
return db.findOne('1').joinObj({ j: db }, { observe: true }).then(res => {
res.j._id.should.be.equal('2');
res.j.b.should.be.equal(2);
return db.update('2', {$set: {b: 22}}).then(() => {
return new Promise(resolve => {
setTimeout(() => {
res.j.b.should.be.equal(22);
done();
}, 100);
})
});
})
});
it('should NOT observe joins by default', function (done) {
return db.findOne('1').joinObj({ j: db }).then(res => {
res.j._id.should.be.equal('2');
res.j.b.should.be.equal(2);
return db.update('2', {$set: {b: 22}}).then(() => {
return new Promise(resolve => {
setTimeout(() => {
res.j.b.should.be.equal(2);
done();
}, 100);
})
});
})
});
});
describe('#joinEach', function () {
it('should join by function with promises', function () {
const cursor = new Cursor(db);
cursor.find().sort({b: 1}).joinEach(d => {
const cursor2 = new Cursor(db);
return cursor2.find({g: d.g}).exec().then((result) => {
d.groupObjs = result;
});
});
return cursor.exec().then((docs) => {
docs.should.have.length(7);
docs[0].groupObjs.should.have.length(4);
docs[1].groupObjs.should.have.length(4);
docs[6].groupObjs.should.have.length(3);
});
});
it('should join for findOne with undefined result', function () {
sinon.spy(db, 'find');
sinon.spy(db, 'findOne');
return db.findOne('111').joinEach(d => db.find()).then(res => {
db.find.should.be.have.callCount(0);
db.findOne.should.be.calledOnce;
});
});
it('should not change object in a storage', function () {
return db.find().sort({b: 1}).joinEach(d => {
return db.find({g: d.g}).then((result) => {
d.groupObjs = result;
});
}).then((docs) => {
docs.should.have.length(7);
docs[0].groupObjs.should.have.length(4);
docs[1].groupObjs.should.have.length(4);
docs[6].groupObjs.should.have.length(3);
return db.find().sort({b: 1});
}).then((docs) => {
docs.should.have.length(7);
docs[0].should.not.have.ownProperty('groupObjs');
});
});
it('should throw an error if join is not a function', function () {
const cursor = new Cursor(db);
(() => cursor.join(123)).should.throw(Error);
});
});
describe('#joinAll', function () {
it('should join by function with promises', function () {
return db.find({b: {$in: [1,2,3]}}).sort(['b']).joinAll(docs => {
docs.should.have.length(3);
return db.find({g: docs[0].g}).exec().then((result) => {
docs.forEach(d => d.groupObjs = result);
});
}).exec().then((docs) => {
docs.should.have.length(3);
docs[0].groupObjs.should.have.length(4);
docs[1].groupObjs.should.have.length(4);
docs[2].groupObjs.should.have.length(4);
});
});
it('should throw an error if join is not a function', function () {
const cursor = new Cursor(db);
(() => cursor.joinAll(123)).should.throw(Error);
});
});
describe('#project', function () {
let db2;
beforeEach(function () {
db2 = new Collection('another');
return Promise.all(_.times(30, function (i) {
return db2.insert({
something: Random.default().id(),
anything: {
foo: "bar",
cool: "hot"
},
nothing: i,
i: i
});
}));
});
it('should fetch with some projection', function () {
return db2.find({}).project({
'something': 1,
'anything.foo': 1
}).then(fetchResults => {
assert.isTrue(_.all(fetchResults, function (x) {
return x &&
x.something &&
x.anything &&
x.anything.foo &&
x.anything.foo === "bar" &&
!_.has(x, 'nothing') &&
!_.has(x.anything, 'cool');
}));
})
});
it('should exclude fields even fields used in a selector', function () {
return db2.find({
nothing: { $gte: 5 }
}).project({ nothing: 0 })
.then((fetchResults) => {
assert.isTrue(_.all(fetchResults, function (x) {
return x &&
x.something &&
x.anything &&
x.anything.foo === "bar" &&
x.anything.cool === "hot" &&
!_.has(x, 'nothing') &&
x.i &&
x.i >= 5;
}));
assert.isTrue(fetchResults.length === 25);
})
});
it('should sort based on excluded fields and use skip and limit well', function () {
return db2.find({}).skip(10).limit(10).sort({nothing: 1})
.project({ i: 1, something: 1}).then((fetchResults) => {
assert.isTrue(_.all(fetchResults, function (x) {
return x &&
x.something &&
x.i >= 10 && x.i < 20;
}));
_.each(fetchResults, function (x, i, arr) {
if (!i) return;
assert.isTrue(x.i === arr[i-1].i + 1);
});
});
});
it('should rise an exception if used unsupported operations', function () {
assert.throws(function () {
db2.find({}).project(function(){});
});
assert.throws(function () {
db2.find({}).project({ 'grades': 121 });
});
assert.throws(function () {
db2.find({}).project({ 'grades.$': 1 });
});
assert.throws(function () {
db2.find({}).project({ grades: { $elemMatch: { mean: 70 } } } );
});
assert.throws(function () {
db2.find({}).project({ grades: { $slice: [20, 10] } });
});
});
it('should properly project arrays', function () {
// Insert a test object with two set fields
return db2.remove({}, {multi: true}).then(() => {
return db2.insert({
setA: [{
fieldA: 42,
fieldB: 33
}, {
fieldA: "the good",
fieldB: "the bad",
fieldC: "the ugly"
}],
setB: [{
anotherA: { },
anotherB: "meh"
}, {
anotherA: 1234,
anotherB: 431
}]
}).then(() => {
var equalNonStrict = function (a, b, desc) {
assert.isTrue(_.isEqual(a, b), desc);
};
var testForProjection = function (projection, expected) {
return db2.find({}).project(projection).then((fetched) => {
equalNonStrict(fetched[0], expected, "failed sub-set projection: " +
JSON.stringify(projection));
});
};
return Promise.all([
testForProjection({ 'setA.fieldA': 1, 'setB.anotherB': 1, _id: 0 },
{
setA: [{ fieldA: 42 }, { fieldA: "the good" }],
setB: [{ anotherB: "meh" }, { anotherB: 431 }]
}),
testForProjection({ 'setA.fieldA': 0, 'setB.anotherA': 0, _id: 0 },
{
setA: [{fieldB:33}, {fieldB:"the bad",fieldC:"the ugly"}],
setB: [{ anotherB: "meh" }, { anotherB: 431 }]
}),
]).then(() => {
return db2.remove({});
}).then(() => {
return db2.insert({a:[[{b:1,c:2},{b:2,c:4}],{b:3,c:5},[{b:4, c:9}]]});
}).then(() => {
return Promise.all([
testForProjection({ 'a.b': 1, _id: 0 },
{a: [ [ { b: 1 }, { b: 2 } ], { b: 3 }, [ { b: 4 } ] ] }),
testForProjection({ 'a.b': 0, _id: 0 },
{a: [ [ { c: 2 }, { c: 4 } ], { c: 5 }, [ { c: 9 } ] ] }),
]);
});
});
});
});
});
describe('#_trackChildCursorPromise', function () {
it('should destroy only not used cursors', function () {
const cursor_3 = new Cursor(db).find({a: 'c'});
const cursor_3_1 = new Cursor(db).find({a: 'c'});
const cursor_2 = new Cursor(db).find({a: 'b'}).join(() => [cursor_3, cursor_3_1]);
const cursor_1 = new Cursor(db).find({a: 'a'}).join(() => cursor_2);
const cursor_1_1 = new Cursor(db).find({a: 'd'}).join(() => cursor_3);
return Promise.all([
cursor_1, cursor_1_1
]).then(() => {
cursor_1._childrenCursors.should.be.deep.equal({[cursor_2._id]: cursor_2});
cursor_1._parentCursors.should.be.deep.equal({});
cursor_2._childrenCursors.should.be.deep.equal({
[cursor_3._id]: cursor_3,
[cursor_3_1._id]: cursor_3_1,
});
cursor_2._parentCursors.should.be.deep.equal({[cursor_1._id]: cursor_1});
cursor_3._childrenCursors.should.be.deep.equal({});
cursor_3._parentCursors.should.be.deep.equal({
[cursor_1_1._id]: cursor_1_1,
[cursor_2._id]: cursor_2,
});
cursor_3_1._childrenCursors.should.be.deep.equal({});
cursor_3_1._parentCursors.should.be.deep.equal({
[cursor_2._id]: cursor_2,
});
cursor_1_1._parentCursors.should.be.deep.equal({});
cursor_1_1._childrenCursors.should.be.deep.equal({[cursor_3._id]: cursor_3});
let a = cursor_1.exec();
cursor_1._childrenCursors.should.be.deep.equal({});
cursor_1._parentCursors.should.be.deep.equal({});
cursor_2._childrenCursors.should.be.deep.equal({});
cursor_2._parentCursors.should.be.deep.equal({});
cursor_3._childrenCursors.should.be.deep.equal({});
cursor_3._parentCursors.should.be.deep.equal({
[cursor_1_1._id]: cursor_1_1,
});
cursor_3_1._childrenCursors.should.be.deep.equal({});
cursor_3_1._parentCursors.should.be.deep.equal({});
cursor_1_1._parentCursors.should.be.deep.equal({});
cursor_1_1._childrenCursors.should.be.deep.equal({[cursor_3._id]: cursor_3});
let b = cursor_1_1.exec();
cursor_1._childrenCursors.should.be.deep.equal({});
cursor_1._parentCursors.should.be.deep.equal({});
cursor_2._childrenCursors.should.be.deep.equal({});
cursor_2._parentCursors.should.be.deep.equal({});
cursor_3._childrenCursors.should.be.deep.equal({});
cursor_3._parentCursors.should.be.deep.equal({});
cursor_3_1._childrenCursors.should.be.deep.equal({});
cursor_3_1._parentCursors.should.be.deep.equal({});
cursor_1_1._parentCursors.should.be.deep.equal({});
cursor_1_1._childrenCursors.should.be.deep.equal({});
return Promise.all([a, b]);
}).then(() => {
cursor_1._childrenCursors.should.be.deep.equal({[cursor_2._id]: cursor_2});
cursor_1._parentCursors.should.be.deep.equal({});
cursor_2._childrenCursors.should.be.deep.equal({
[cursor_3._id]: cursor_3,
[cursor_3_1._id]: cursor_3_1,
});
cursor_2._parentCursors.should.be.deep.equal({[cursor_1._id]: cursor_1});
cursor_3._childrenCursors.should.be.deep.equal({});
cursor_3._parentCursors.should.be.deep.equal({
[cursor_1_1._id]: cursor_1_1,
[cursor_2._id]: cursor_2,
});
cursor_3_1._childrenCursors.should.be.deep.equal({});
cursor_3_1._parentCursors.should.be.deep.equal({
[cursor_2._id]: cursor_2,
});
cursor_1_1._parentCursors.should.be.deep.equal({});
cursor_1_1._childrenCursors.should.be.deep.equal({[cursor_3._id]: cursor_3});
});
});
});
});