@graphistry/falcor
Version:
A JavaScript library for efficient data fetching.
1,057 lines (1,004 loc) • 49.4 kB
JavaScript
var $ref = require('@graphistry/falcor-json-graph').ref;
var $atom = require('@graphistry/falcor-json-graph').atom;
var Model = require('./../../../falcor.js').Model;
var FalcorJSON = require('./../../../falcor.js').FalcorJSON;
var Rx = require('rx');
var noOp = function() {};
var LocalDataSource = require('../../data/LocalDataSource');
var Observable = Rx.Observable;
var sinon = require('sinon');
var expect = require('chai').expect;
var strip = require('./../../cleanData').stripDerefAndVersionKeys;
var cacheGenerator = require('./../../CacheGenerator');
var jsonGraph = require('@graphistry/falcor-json-graph');
var toFlatBuffer = require('@graphistry/falcor-path-utils').toFlatBuffer;
var computeFlatBufferHash = require('@graphistry/falcor-path-utils').computeFlatBufferHash;
var M = function(m) {
return cacheGenerator(0, 1);
};
var Cache = function(c) {
return cacheGenerator(0, 40);
};
describe('DataSource and Cache', function() {
describe('Preload Functions', function() {
it('should get multiple arguments.', function(done) {
var model = new Model({cache: M(), source: new LocalDataSource(Cache())});
var onNext = sinon.spy();
var secondOnNext = sinon.spy();
toObservable(model.
preload(['videos', 0, 'title'], ['videos', 1, 'title'])).
doAction(onNext, noOp, function() {
expect(onNext.callCount).to.equal(0);
}).
defaultIfEmpty({}).
flatMap(function() {
return model.get(['videos', 0, 'title'], ['videos', 1, 'title']);
}).
doAction(secondOnNext, noOp, function() {
expect(secondOnNext.calledOnce).to.be.ok;
expect(strip(secondOnNext.getCall(0).args[0])).to.deep.equals({
json: {
videos: {
0: {
title: 'Video 0'
},
1: {
title: 'Video 1'
}
}
}
});
}).
subscribe(noOp, done, done);
});
it('should get a complex argument into a single arg.', function(done) {
var model = new Model({cache: M(), source: new LocalDataSource(Cache())});
var onNext = sinon.spy();
var secondOnNext = sinon.spy();
toObservable(model.
preload(['lolomo', 0, {to: 1}, 'item', 'title'])).
doAction(onNext).
doAction(noOp, noOp, function() {
expect(onNext.callCount).to.equal(0);
}).
defaultIfEmpty({}).
flatMap(function() {
return model.get(['lolomo', 0, {to: 1}, 'item', 'title']);
}).
doAction(secondOnNext, noOp, function() {
expect(secondOnNext.calledOnce).to.be.ok;
expect(strip(secondOnNext.getCall(0).args[0])).to.deep.equals({
json: {
lolomo: {
0: {
0: {
item: {
title: 'Video 0'
}
},
1: {
item: {
title: 'Video 1'
}
}
}
}
}
});
}).
subscribe(noOp, done, done);
});
});
describe('JSON', function() {
it('should ensure empty paths do not cause dataSource requests {from:1, to:0}', function(done) {
var onGet = sinon.spy();
var model = new Model({
cache: {
a: $ref(['c']),
c: {
0: $atom('hello')
}
},
source: {
get: onGet
}
});
var modelGet = model.get(['b', {to:0, from:1}]);
var onNext = sinon.spy();
toObservable(modelGet).
doAction(onNext, noOp, function() {
expect(onGet.callCount).to.equals(0);
expect(onNext.callCount).to.equals(0);
}).
subscribe(noOp, done, done);
});
it('should ensure empty paths do not cause dataSource requests [].', function(done) {
var onGet = sinon.spy();
var model = new Model({
cache: {
a: $ref(['c']),
c: {
0: $atom('hello')
}
},
source: {
get: onGet
}
});
var modelGet = model.get(['b', []]);
var onNext = sinon.spy();
toObservable(modelGet).
doAction(onNext, noOp, function() {
expect(onGet.callCount).to.equals(0);
expect(onNext.callCount).to.equals(0);
}).
subscribe(noOp, done, done);
});
it('should get multiple arguments into a single toJSON response.', function(done) {
var model = new Model({cache: M(), source: new LocalDataSource(Cache())});
var onNext = sinon.spy();
toObservable(model.
get(['lolomo', 0, 0, 'item', 'title'], ['lolomo', 0, 1, 'item', 'title'])).
doAction(onNext, noOp, function() {
expect(onNext.calledOnce).to.be.ok;
expect(strip(onNext.getCall(0).args[0])).to.deep.equals({
json: {
lolomo: {
0: {
0: {
item: {
title: 'Video 0'
}
},
1: {
item: {
title: 'Video 1'
}
}
}
}
}
});
}).
subscribe(noOp, done, done);
});
it('should get a complex argument into a single arg.', function(done) {
var model = new Model({cache: M(), source: new LocalDataSource(Cache())});
var onNext = sinon.spy();
toObservable(model.
get(['lolomo', 0, {to: 1}, 'item', 'title'])).
doAction(onNext, noOp, function() {
expect(onNext.calledOnce).to.be.ok;
expect(strip(onNext.getCall(0).args[0])).to.deep.equals({
json: {
lolomo: {
0: {
0: {
item: {
title: 'Video 0'
}
},
1: {
item: {
title: 'Video 1'
}
}
}
}
}
});
}).
subscribe(noOp, done, done);
});
it('should get a complex argument into a single arg and collect to max cache size.', function(done) {
var model = new Model({
cache: M(),
source: new LocalDataSource(Cache()),
maxSize: 0
});
var cache = model._root.cache;
var onNext = sinon.spy();
toObservable(model.
get(['lolomo', 0, {to: 1}, 'item', 'title'])).
doAction(onNext, noOp, function() {
expect(onNext.calledOnce).to.be.ok;
expect(strip(onNext.getCall(0).args[0])).to.deep.equals({
json: {
lolomo: {
0: {
0: {
item: {
title: 'Video 0'
}
},
1: {
item: {
title: 'Video 1'
}
}
}
}
}
});
}).
finally(function() {
expect(cache['$size']).to.equal(0);
done();
}).
subscribe(noOp, done, noOp);
});
it('should ensure that a response where only materialized atoms come through still onNexts a value if one is present in cache.', function(done) {
var model = new Model({
cache: {
paths: {
0: 'test',
1: 'test'
}
},
source: new LocalDataSource({
paths: {
2: $atom(undefined),
3: $atom(undefined)
}
}, {materialize: true})
});
var onNext = sinon.spy();
toObservable(model.
get(['paths', {to:3}])).
doAction(onNext, noOp, function() {
expect(onNext.calledOnce, 'onNext called').to.be.ok;
expect(strip(onNext.getCall(0).args[0])).to.deep.equals({
json: {
paths: {
0: 'test',
1: 'test'
}
}
});
}).
subscribe(noOp, done, done);
});
it('should go to the datasource when a branch is an atom of undefined.', function(done) {
var model = new Model({
materialized: true,
cache: {
paths: $atom(undefined)
},
source: new LocalDataSource({
paths: $atom(undefined)
}, { materialize: true })
});
var onNext = sinon.spy();
toObservable(model.
get(['paths', {to:3}])).
doAction(onNext, noOp, function() {
expect(onNext.calledOnce, 'onNext called').to.be.ok;
expect(strip(onNext.getCall(0).args[0])).to.deep.equals({
json: {
paths: {
0: undefined,
1: undefined,
2: undefined,
3: undefined
}
}
});
}).
subscribe(noOp, done, done);
});
});
describe('_toJSONG', function() {
it('should get multiple arguments into a single _toJSONG response.', function(done) {
var model = new Model({cache: M(), source: new LocalDataSource(Cache())});
var onNext = sinon.spy();
toObservable(model.
get(['lolomo', 0, 0, 'item', 'title'], ['lolomo', 0, 1, 'item', 'title']).
_toJSONG()).
doAction(onNext, noOp, function() {
expect(onNext.calledOnce).to.be.ok;
var out = strip(onNext.getCall(0).args[0]);
var expected = strip({
jsonGraph: cacheGenerator(0, 2),
paths: [
['lolomo', 0, {from: 0, to: 1}, 'item', 'title']
]
});
expect(out).to.deep.equals(expected);
}).
subscribe(noOp, done, done);
});
it('should get a complex argument into a single arg.', function(done) {
var model = new Model({cache: M(), source: new LocalDataSource(Cache())});
var onNext = sinon.spy();
toObservable(model.
get(['lolomo', 0, {to: 1}, 'item', 'title']).
_toJSONG()).
doAction(onNext, noOp, function() {
expect(onNext.calledOnce).to.be.ok;
var out = strip(onNext.getCall(0).args[0]);
var expected = strip({
jsonGraph: cacheGenerator(0, 2),
paths: [
['lolomo', 0, {from: 0, to: 1}, 'item', 'title']
]
});
expect(out).to.deep.equals(expected);
}).
subscribe(noOp, done, done);
});
});
describe('Progressively', function() {
it('should get multiple arguments with multiple trips to the dataSource into a single toJSON response.', function(done) {
var model = new Model({cache: M(), source: new LocalDataSource(Cache())});
var count = 0;
toObservable(model.
get(['lolomo', 0, 0, 'item', 'title'], ['lolomo', 0, 1, 'item', 'title']).
progressively()).
doAction(function(x) {
count++;
if (count === 1) {
expect(x.json.$__status).to.equal('pending');
expect(x.json.lolomo.$__status).to.equal('pending');
expect(x.json.lolomo[0].$__status).to.equal('pending');
expect(x.json.lolomo[0][0].$__status).to.equal('resolved');
expect(x.json.lolomo[0][0].item.$__status).to.equal('resolved');
expect(strip(x)).to.deep.equals({
json: {
lolomo: {
0: {
0: {
item: {
title: 'Video 0'
}
}
}
}
}
});
} else {
expect(x.json.$__status).to.equal('resolved');
expect(x.json.lolomo.$__status).to.equal('resolved');
expect(x.json.lolomo[0].$__status).to.equal('resolved');
expect(x.json.lolomo[0][0].$__status).to.equal('resolved');
expect(x.json.lolomo[0][1].$__status).to.equal('resolved');
expect(x.json.lolomo[0][0].item.$__status).to.equal('resolved');
expect(x.json.lolomo[0][1].item.$__status).to.equal('resolved');
expect(strip(x)).to.deep.equals({
json: {
lolomo: {
0: {
0: {
item: {
title: 'Video 0'
}
},
1: {
item: {
title: 'Video 1'
}
}
}
}
}
});
}
}, noOp, function() {
expect(count).to.equals(2);
}).
subscribe(noOp, done, done);
});
it('should get complex path with multiple trips to the dataSource into a single toJSON response.', function(done) {
var model = new Model({cache: M(), source: new LocalDataSource(Cache())});
var count = 0;
toObservable(model.
get(['lolomo', 0, {to: 1}, 'item', 'title']).
progressively()).
doAction(function(x) {
count++;
if (count === 1) {
expect(strip(x)).to.deep.equals({
json: {
lolomo: {
0: {
0: {
item: {
title: 'Video 0'
}
}
}
}
}
});
} else {
expect(strip(x)).to.deep.equals({
json: {
lolomo: {
0: {
0: {
item: {
title: 'Video 0'
}
},
1: {
item: {
title: 'Video 1'
}
}
}
}
}
});
}
}, noOp, function() {
expect(count).to.equals(2);
}).
subscribe(noOp, done, done);
});
it('should get different response objects with multiple trips to the dataSource.', function(done) {
var model = new Model({cache: M(), source: new LocalDataSource(Cache())});
var revisions = [];
toObservable(model.
get(['lolomo', 0, [0, 1], 'item', 'title']).
progressively()).
doAction(function(x) {
revisions.push(x);
}, noOp, function() {
expect(revisions.length).to.equals(2);
expect(revisions[1]).to.not.equal(revisions[0]);
expect(revisions[1].json.lolomo[0]).to.not.equal(revisions[0].json.lolomo[0]);
expect(revisions[1].json.lolomo[0][0]).to.equal(revisions[0].json.lolomo[0][0]);
expect(revisions[1].json.lolomo[0][1]).to.not.equal(revisions[0].json.lolomo[0][1]);
}).
subscribe(noOp, done, done);
});
});
describe('Recycle JSON', function() {
it('should get multiple arguments with multiple trips to the dataSource into a single toJSON response with recycleJSON true.', function(done) {
var model = new Model({cache: M(), source: new LocalDataSource(Cache()), recycleJSON: true});
var count = 0;
toObservable(model.
get(['lolomo', 0, 0, 'item', 'title'], ['lolomo', 0, 1, 'item', 'title'])).
doAction(function(x) {
count++;
expect(x instanceof FalcorJSON).to.equal(true);
expect(JSON.parse(x.toString(true, true))).to.deep.equals({
json: {
[f_meta_data]: {
'$code': '350990479',
[f_meta_status]: 'resolved'
},
lolomo: {
[f_meta_data]: {
'$code': '1437563678',
[f_meta_abs_path]: ['lolomos', '1234'],
[f_meta_status]: 'resolved'
},
0: {
[f_meta_data]: {
'$code': '2823858104',
[f_meta_abs_path]: ['lists', 'A'],
[f_meta_status]: 'resolved'
},
0: {
[f_meta_data]: {
'$code': '3681981706',
[f_meta_abs_path]: ['lists', 'A', '0'],
[f_meta_status]: 'resolved'
},
item: {
[f_meta_data]: {
'$code': '165499941',
[f_meta_abs_path]: ['videos', '0'],
[f_meta_status]: 'resolved'
},
title: 'Video 0'
}
},
1: {
[f_meta_data]: {
'$code': '3681981706',
[f_meta_abs_path]: ['lists', 'A', 1],
[f_meta_status]: 'resolved'
},
item: {
[f_meta_data]: {
'$code': '165499941',
[f_meta_abs_path]: ['videos', '1'],
[f_meta_status]: 'resolved'
},
title: 'Video 1'
}
}
}
}
}
});
}, noOp, function() {
expect(count).to.equals(1);
}).
subscribe(noOp, done, done);
});
it('should get a complex keyset where a reference is in the cache but the ref target is loaded from a trip to the dataSource into a single toJSON response.', function(done) {
var cache = cacheGenerator(0, 2);
delete cache.videos[1];
var model = new Model({
cache: cache,
recycleJSON: true,
source: new LocalDataSource(Cache())
});
var count = 0;
toObservable(model.
get(['lolomo', 0, 0, 'item', 'title'], ['lolomo', 0, 1, 'item', 'title'])).
doAction(function(x) {
count++;
expect(x instanceof FalcorJSON).to.equal(true);
expect(JSON.parse(x.toString(true, true))).to.deep.equals({
json: {
[f_meta_data]: {
'$code': '350990479',
[f_meta_status]: 'resolved'
},
lolomo: {
[f_meta_data]: {
'$code': '1437563678',
[f_meta_abs_path]: ['lolomos', '1234'],
[f_meta_status]: 'resolved'
},
0: {
[f_meta_data]: {
'$code': '2823858104',
[f_meta_abs_path]: ['lists', 'A'],
[f_meta_status]: 'resolved'
},
0: {
[f_meta_data]: {
'$code': '3681981706',
[f_meta_abs_path]: ['lists', 'A', '0'],
[f_meta_status]: 'resolved'
},
item: {
[f_meta_data]: {
'$code': '165499941',
[f_meta_abs_path]: ['videos', '0'],
[f_meta_status]: 'resolved'
},
title: 'Video 0'
}
},
1: {
[f_meta_data]: {
'$code': '3681981706',
[f_meta_abs_path]: ['lists', 'A', '1'],
[f_meta_status]: 'resolved'
},
item: {
[f_meta_data]: {
'$code': '165499941',
[f_meta_abs_path]: ['videos', 1],
[f_meta_status]: 'resolved'
},
title: 'Video 1'
}
}
}
}
}
});
}, noOp, function() {
expect(count).to.equals(1);
}).
subscribe(noOp, done, done);
});
describe('Progressively', function() {
it('should get multiple arguments with multiple trips to the dataSource into a single recycled JSON response.', function(done) {
var model = new Model({
cache: M(), recycleJSON: true,
source: new LocalDataSource(Cache(), { materialize: true }),
});
var count = 0;
model._root.cache.videos[0].undef = { $type: 'atom' };
model._source.model._root.cache.videos[1].undef = { $type: 'atom' };
toObservable(model.
get(['lolomo', 0, 0, 'item', ['title', 'undef']], ['lolomo', 0, 1, 'item', ['title', 'undef']]).
progressively()).
doAction(function(x) {
count++;
expect(x instanceof FalcorJSON).to.equal(true);
if (count === 1) {
expect(JSON.parse(x.toString(true, true))).to.deep.equals({
json: {
[f_meta_data]: {
'$code': '1378446605',
[f_meta_status]: 'pending'
},
lolomo: {
[f_meta_data]: {
'$code': '2056930616',
[f_meta_status]: 'pending',
[f_meta_abs_path]: ['lolomos', '1234'],
},
0: {
[f_meta_data]: {
'$code': '932389016',
[f_meta_status]: 'pending',
[f_meta_abs_path]: ['lists', 'A'],
},
0: {
[f_meta_data]: {
'$code': '901320925',
[f_meta_status]: 'resolved',
[f_meta_abs_path]: ['lists', 'A', '0'],
},
item: {
[f_meta_data]: {
'$code': '4113242691',
[f_meta_status]: 'resolved',
[f_meta_abs_path]: ['videos', '0'],
},
title: 'Video 0'
}
}
}
}
}
});
} else {
expect(JSON.parse(x.toString(true, true))).to.deep.equals({
json: {
[f_meta_data]: {
'$code': '974387055',
[f_meta_status]: 'resolved'
},
lolomo: {
[f_meta_data]: {
'$code': '3454200840',
[f_meta_status]: 'resolved',
[f_meta_abs_path]: ['lolomos', '1234'],
},
0: {
[f_meta_data]: {
'$code': '945722345',
[f_meta_status]: 'resolved',
[f_meta_abs_path]: ['lists', 'A'],
},
0: {
[f_meta_data]: {
'$code': '901320925',
[f_meta_status]: 'resolved',
[f_meta_abs_path]: ['lists', 'A', '0'],
},
item: {
[f_meta_data]: {
'$code': '4113242691',
[f_meta_status]: 'resolved',
[f_meta_abs_path]: ['videos', '0'],
},
title: 'Video 0'
}
},
1: {
[f_meta_data]: {
'$code': '901320925',
[f_meta_status]: 'resolved',
[f_meta_abs_path]: ['lists', 'A', 1],
},
item: {
[f_meta_data]: {
'$code': '4113242691',
[f_meta_status]: 'resolved',
[f_meta_abs_path]: ['videos', '1'],
},
title: 'Video 1'
}
}
}
}
}
});
}
}, noOp, function() {
expect(count).to.equals(2);
}).
subscribe(noOp, done, done);
});
it('should get complex path with multiple trips to the dataSource into a single toJSON response.', function(done) {
var model = new Model({cache: M(), source: new LocalDataSource(Cache()), recycleJSON: true});
var count = 0;
toObservable(model.
get(['lolomo', 0, {to: 1}, 'item', 'title']).
progressively()).
doAction(function(x) {
count++;
expect(x instanceof FalcorJSON).to.equal(true);
if (count === 1) {
expect(JSON.parse(x.toString(true, true))).to.deep.equals({
json: {
[f_meta_data]: {
'$code': '855611276',
[f_meta_status]: 'pending'
},
lolomo: {
[f_meta_data]: {
'$code': '587981637',
[f_meta_abs_path]: ['lolomos', '1234'],
[f_meta_status]: 'pending'
},
0: {
[f_meta_data]: {
'$code': '3715347144',
[f_meta_abs_path]: ['lists', 'A'],
[f_meta_status]: 'pending'
},
0: {
[f_meta_data]: {
'$code': '3681981706',
[f_meta_abs_path]: ['lists', 'A', '0'],
[f_meta_status]: 'resolved'
},
item: {
[f_meta_data]: {
'$code': '165499941',
[f_meta_abs_path]: ['videos', '0'],
[f_meta_status]: 'resolved'
},
title: 'Video 0'
}
}
}
}
}
});
} else {
expect(JSON.parse(x.toString(true, true))).to.deep.equals({
json: {
[f_meta_data]: {
'$code': '2988950702',
[f_meta_status]: 'resolved'
},
lolomo: {
[f_meta_data]: {
'$code': '2223524821',
[f_meta_abs_path]: ['lolomos', '1234'],
[f_meta_status]: 'resolved'
},
0: {
[f_meta_data]: {
'$code': '2400656047',
[f_meta_abs_path]: ['lists', 'A'],
[f_meta_status]: 'resolved'
},
0: {
[f_meta_data]: {
'$code': '3681981706',
[f_meta_abs_path]: ['lists', 'A', '0'],
[f_meta_status]: 'resolved'
},
item: {
[f_meta_data]: {
'$code': '165499941',
[f_meta_abs_path]: ['videos', '0'],
[f_meta_status]: 'resolved'
},
title: 'Video 0'
}
},
1: {
[f_meta_data]: {
'$code': '3681981706',
[f_meta_abs_path]: ['lists', 'A', 1],
[f_meta_status]: 'resolved'
},
item: {
[f_meta_data]: {
'$code': '165499941',
[f_meta_abs_path]: ['videos', '1'],
[f_meta_status]: 'resolved'
},
title: 'Video 1'
}
}
}
}
}
});
}
}, noOp, function() {
expect(count).to.equals(2);
}).
subscribe(noOp, done, done);
});
it('should get the same response object with multiple trips to the dataSource.', function(done) {
var model = new Model({cache: M(), source: new LocalDataSource(Cache()), recycleJSON: true});
var revisions = [];
toObservable(model.
get(['lolomo', 0, [0, 1], 'item', 'title']).
progressively()).
doAction(function(x) {
expect(x instanceof FalcorJSON).to.equal(true);
revisions.push(x);
}, noOp, function() {
expect(revisions.length).to.equals(2);
expect(revisions[1]).to.equal(revisions[0]);
expect(revisions[1].json.lolomo[0]).to.equal(revisions[0].json.lolomo[0]);
expect(revisions[1].json.lolomo[0][0]).to.equal(revisions[0].json.lolomo[0][0]);
expect(revisions[1].json.lolomo[0][1]).to.equal(revisions[0].json.lolomo[0][1]);
}).
subscribe(noOp, done, done);
});
});
});
describe('Error Selector (during merge)', function() {
function generateErrorSelectorSpy() {
return sinon.spy(function(path, atom) {
// Needs to be asserted before mutation.
expect(atom.$type).to.equal('error');
expect(atom.value).to.deep.equals({ message:'errormsg' });
atom.$custom = 'custom';
atom.value.customtype = 'customtype';
return atom;
});
}
function assertExpectedErrorPayload(e, expectedPath) {
var path = e.path;
var value = e.value;
// To avoid hardcoding/scrubbing $size, and other internals
expect(path).to.deep.equals(expectedPath);
expect(value.$type).to.equal('error');
expect(value.$custom).to.equal('custom');
expect(value.value).to.deep.equals({
message: 'errormsg',
customtype: 'customtype'
});
}
it('should get invoked with the right arguments for branches in cache', function(done) {
// Cache has [lolomo,0,0,item]
var testPath = ['lolomo',0,0,'item','errorPath'];
var modelCache = M();
var dataSourceCache = Cache();
// [lolomo,0,0,item]->[videos,0]
dataSourceCache.videos[0].errorPath = jsonGraph.error({ message:'errormsg' });
var onNextSpy = sinon.spy();
var onErrorSpy = sinon.spy();
var errorSelectorSpy = generateErrorSelectorSpy();
var model = new Model({
cache: modelCache,
source: new LocalDataSource(dataSourceCache),
errorSelector: errorSelectorSpy
});
toObservable(model.
boxValues().
get(testPath)).
doAction(onNextSpy, onErrorSpy, noOp).
doAction(null, function(e) {
expect(e.length).to.equal(1);
expect(onNextSpy.callCount).to.equal(0);
expect(onErrorSpy.callCount).to.equal(1);
expect(errorSelectorSpy.callCount).to.equal(1);
assertExpectedErrorPayload(e[0], ['videos', '0', 'errorPath']);
})
.subscribe(
done.bind(null, 'onNext should not have been called'),
function() { done() },
done.bind(null, 'onCompleted should not have been called')
);
});
it('should get invoked with the right arguments for branches not in cache', function(done) {
// Cache doesn't have [lolomo,1,0,item]
var testPath = ['lolomo',1,0,'item','errorPath'];
var modelCache = M();
var dataSourceCache = Cache();
// [lolomo,1,0,item]->[videos,10]
dataSourceCache.videos[10].errorPath = jsonGraph.error({ message:'errormsg' });
var onNextSpy = sinon.spy();
var onErrorSpy = sinon.spy();
var errorSelectorSpy = generateErrorSelectorSpy();
var model = new Model({
cache: modelCache,
source: new LocalDataSource(dataSourceCache),
errorSelector: errorSelectorSpy
});
toObservable(model.
boxValues().
get(testPath)).
doAction(onNextSpy, onErrorSpy, noOp).
doAction(null, function(e) {
expect(e.length).to.equal(1);
expect(onNextSpy.callCount).to.equal(0);
expect(onErrorSpy.callCount).to.equal(1);
expect(errorSelectorSpy.callCount).to.equal(1);
assertExpectedErrorPayload(e[0], ['videos', '10', 'errorPath']);
})
.subscribe(
done.bind(null, 'onNext should not have been called'),
function() { done() },
done.bind(null, 'onCompleted should not have been called')
);
});
it('should get invoked with the correct error paths for a keyset', function(done) {
var testPath = ['lolomo',[0,1],0,'item','errorPath'];
var modelCache = M();
var dataSourceCache = Cache();
dataSourceCache.videos[0].errorPath = jsonGraph.error({ message:'errormsg' });
dataSourceCache.videos[10].errorPath = jsonGraph.error({ message:'errormsg' });
var onNextSpy = sinon.spy();
var onErrorSpy = sinon.spy();
var errorSelectorSpy = generateErrorSelectorSpy();
var model = new Model({
cache: modelCache,
source: new LocalDataSource(dataSourceCache),
errorSelector: errorSelectorSpy
});
toObservable(model.
boxValues().
get(testPath)).
doAction(onNextSpy, onErrorSpy, noOp).
doAction(null, function(e) {
expect(e.length).to.equal(1);
expect(onNextSpy.callCount).to.equal(0);
expect(onErrorSpy.callCount).to.equal(1);
expect(errorSelectorSpy.callCount).to.equal(2);
assertExpectedErrorPayload(e[0], ['videos', '0', 'errorPath']);
assertExpectedErrorPayload(e[1], ['videos', '10', 'errorPath']);
})
.subscribe(
done.bind(null, 'onNext should not have been called'),
function() { done() },
done.bind(null, 'onCompleted should not have been called')
);
});
});
describe('Cached data with timestamp', function() {
var t0 = Date.parse('2000/01/01');
var t1 = t0 + 1;
function remoteData() {
return {
videos: {
1: {
bookmark: $atom('remote value', {$timestamp: t0})
},
2: {
previous: $ref(['videos', 1])
}
}
};
}
it('should not be replaced by data with an older timestamp', function(done) {
var cache = {
videos: {
1: {
bookmark: $atom('cached value', {$timestamp: t1})
}
}
};
var source = new LocalDataSource(remoteData());
var model = new Model({cache: cache, source: source});
model.getValue(['videos', 2, 'previous', 'bookmark']).
then(function(value) {
expect(value).to.equal('cached value');
done();
}).
catch(function(e) {
done(e);
});
});
it('when expired should be replaced by data with an older timestamp', function(done) {
var cache = {
videos: {
1: {
bookmark: $atom('cached value', {$timestamp: t1, $expires: t1})
}
}
};
var source = new LocalDataSource(remoteData());
var model = new Model({cache: cache, source: source});
model.getValue(['videos', 2, 'previous', 'bookmark']).
then(function(value) {
expect(value).to.equal('remote value');
done();
}).
catch(function(e) {
done(e);
});
});
});
});