rpd
Version:
RPD is a minimal framework for building Node-Based User Interfaces, powered by Reactive Programming
1,194 lines (891 loc) • 41.1 kB
JavaScript
describe('registration: node type', function() {
it('could be registered with an empty object', function() {
expect(function() {
Rpd.nodetype('spec/foo', {});
}).not.toThrow();
});
it('creates specified inlets for the node instance', function() {
Rpd.nodetype('spec/foo', {
inlets: {
'a': { type: 'spec/any' },
'b': { type: 'spec/any' }
}
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
expect(updateSpy).toHaveBeenCalledWith(
jasmine.objectContaining(
{ type: 'node/add-inlet',
inlet: jasmine.objectContaining({
alias: 'a',
type: 'spec/any'
}) })
);
expect(updateSpy).toHaveBeenCalledWith(
jasmine.objectContaining(
{ type: 'node/add-inlet',
inlet: jasmine.objectContaining({
alias: 'b',
type: 'spec/any'
}) })
);
});
});
it('creates specified outlets for the node instance', function() {
Rpd.nodetype('spec/foo', {
outlets: {
'a': { type: 'spec/any' },
'b': { type: 'spec/any' }
}
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
expect(updateSpy).toHaveBeenCalledWith(
jasmine.objectContaining(
{ type: 'node/add-outlet',
outlet: jasmine.objectContaining({
alias: 'a',
type: 'spec/any'
}) })
);
expect(updateSpy).toHaveBeenCalledWith(
jasmine.objectContaining(
{ type: 'node/add-outlet',
outlet: jasmine.objectContaining({
alias: 'b',
type: 'spec/any'
}) })
);
});
});
it('informs it is ready when all inlets and outlets are ready', function() {
Rpd.nodetype('spec/foo', {
inlets: { 'a': { type: 'spec/any' },
'b': { type: 'spec/any' } },
outlets: { 'c': { type: 'spec/any' },
'd': { type: 'spec/any' } }
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
expect(updateSpy).toHaveBeenOrderlyCalledWith([
[ jasmine.objectContaining({ type: 'node/add-inlet' }) ],
[ jasmine.objectContaining({ type: 'node/add-outlet' }) ],
[ jasmine.objectContaining({ type: 'node/is-ready' }) ]
]);
});
});
it('informs it is ready only once', function() {
Rpd.nodetype('spec/foo', {
inlets: { 'a': { type: 'spec/any' } },
outlets: { 'b': { type: 'spec/any' } }
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
updateSpy.calls.reset();
node.addInlet('spec/any', 'foo');
expect(updateSpy).not.toHaveBeenCalledWith(
jasmine.objectContaining({ type: 'node/is-ready' }));
});
});
it('provides access to inlets or outlets defined in type', function() {
Rpd.nodetype('spec/foo', {
inlets: { 'a': { type: 'spec/any' },
'b': { type: 'spec/any' } },
outlets: { 'c': { type: 'spec/any' } }
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
expect(node.inlets['a']).toBeDefined();
expect(node.inlets['b']).toBeDefined();
expect(node.inlets['c']).not.toBeDefined();
expect(node.outlets['c']).toBeDefined();
});
});
describe('processing function', function() {
var processSpy;
beforeEach(function() { processSpy = jasmine.createSpy('process'); });
it('gets node instance as context of a call', function() {
Rpd.nodetype('spec/foo', {
inlets: { 'a': { type: 'spec/any' } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
processSpy.and.callFake(function() {
expect(this).toBe(node);
});
node.inlets['a'].receive(2);
expect(processSpy).toHaveBeenCalled();
});
});
it('is not called when inlets have no default values', function() {
Rpd.nodetype('spec/foo', {
inlets: { 'a': { type: 'spec/any' },
'b': { type: 'spec/any' } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
expect(processSpy).not.toHaveBeenCalled();
});
});
it('is still not called when inlets have no default values and there is an outlet', function() {
Rpd.nodetype('spec/foo', {
inlets: { 'a': { type: 'spec/any' },
'b': { type: 'spec/any' } },
outlets: { 'c': { type: 'spec/any' } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
expect(processSpy).not.toHaveBeenCalled();
});
});
it('is called once when single inlet has some default value', function() {
Rpd.nodetype('spec/foo', {
inlets: { 'a': { type: 'spec/any', 'default': 10 },
'b': { type: 'spec/any' } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
expect(processSpy).toHaveBeenCalledWith({ 'a': 10 }, jasmine.anything());
expect(processSpy).toHaveBeenCalledOnce();
});
});
it('is called for every inlet which have a default value', function() {
Rpd.nodetype('spec/foo', {
inlets: { 'a': { type: 'spec/any', 'default': 10 },
'b': { type: 'spec/any', 'default': 5 } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
var ensureExecuted = handleNextCalls(processSpy, [
function() { expect(processSpy).toHaveBeenCalledWith({ 'a': 10 }, jasmine.anything()); },
function() { expect(processSpy).toHaveBeenCalledWith({ 'a': 10, 'b': 5 }, jasmine.anything()); }
]);
var node = patch.addNode('spec/foo');
ensureExecuted();
});
});
it('is not affected with number of outlets', function() {
Rpd.nodetype('spec/foo', {
inlets: { 'a': { type: 'spec/any', 'default': 10 },
'b': { type: 'spec/any', 'default': 5 } },
outlets: { 'c': { type: 'spec/any' } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
expect(processSpy).toHaveBeenCalledTwice();
});
});
it('gets values from inlets even when there\'s no outlets', function() {
Rpd.nodetype('spec/foo', {
inlets: { 'a': { type: 'spec/any' },
'b': { type: 'spec/any' } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
node.inlets['a'].receive(2);
expect(processSpy).toHaveBeenCalledWith({ a: 2 }, jasmine.anything());
node.inlets['b'].receive('abc');
expect(processSpy).toHaveBeenCalledWith({ a: 2, b: 'abc' }, jasmine.anything());
});
});
it('does not uses default values for outlets', function() {
// throw an error if default value passed to outlet?
Rpd.nodetype('spec/foo', {
inlets: { 'a': { type: 'spec/any' },
'b': { type: 'spec/any' } },
outlets: { 'c': { type: 'spec/any', 'default': 17 } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
var outlet = node.outlets['c'];
expect(updateSpy).not.toHaveBeenCalledWith(
jasmine.objectContaining({
type: 'outlet/update',
outlet: outlet,
value: 17
})
);
expect(processSpy).not.toHaveBeenCalled();
});
});
it('called only when node is ready', function() {
var nodeReady = false;
Rpd.nodetype('spec/foo', {
inlets: { 'a': { type: 'spec/any', 'default': 5 },
'b': { type: 'spec/any', 'default': [ 2, 1 ] },
'c': { type: 'spec/any', 'default': {} },
'd': { type: 'spec/any', 'default': '' } },
outlets: { 'e': { type: 'spec/any' },
'f': { type: 'spec/any' } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
updateSpy.and.callFake(function(update) {
if (update.type === 'node/is-ready') {
expect(processSpy).not.toHaveBeenCalled();
nodeReady = true;
} else if (!nodeReady) {
expect(processSpy).not.toHaveBeenCalled();
}
}.bind(this));
var node = patch.addNode('spec/foo');
expect(updateSpy).toHaveBeenCalled();
expect(processSpy).toHaveBeenCalled();
});
});
it('when inlet is set to transfer some stream by default, gets values from this stream one by one', function() {
jasmine.clock().install();
var values = [ 'a', 'b', 'c' ];
var period = 30;
Rpd.nodetype('spec/foo', {
inlets: { 'char': { type: 'spec/any', 'default': Kefir.sequentially(period, values) } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
var ensureExecuted = handleNextCalls(processSpy, [
function() { expect(processSpy).toHaveBeenCalledWith({ char: values[0] }, jasmine.anything()); },
function() { expect(processSpy).toHaveBeenCalledWith({ char: values[1] }, jasmine.anything()); },
function() { expect(processSpy).toHaveBeenCalledWith({ char: values[2] }, jasmine.anything()); }
]);
var node = patch.addNode('spec/foo');
jasmine.clock().tick(period * (values.length + 1));
ensureExecuted();
});
jasmine.clock().uninstall();
});
it('when stream was sent to the inlet, still gets values one by one', function() {
jasmine.clock().install();
var values = [ 'a', 'b', 'c' ];
var period = 30;
Rpd.nodetype('spec/foo', {
inlets: { 'char': { type: 'spec/any' } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
var ensureExecuted = handleNextCalls(processSpy, [
function() { expect(processSpy).toHaveBeenCalledWith({ char: values[0] }, jasmine.anything()); },
function() { expect(processSpy).toHaveBeenCalledWith({ char: values[1] }, jasmine.anything()); },
function() { expect(processSpy).toHaveBeenCalledWith({ char: values[2] }, jasmine.anything()); }
]);
node.inlets['char'].stream(Kefir.sequentially(period, values));
jasmine.clock().tick(period * (values.length + 1));
ensureExecuted();
});
jasmine.clock().uninstall();
});
it('passes previous values with a call', function() {
Rpd.nodetype('spec/foo', {
inlets: { 'char': { type: 'spec/any', 'default': 'a' } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
node.inlets['char'].receive('b');
expect(processSpy).toHaveBeenCalledWith(
jasmine.objectContaining({ char: 'b' }),
jasmine.objectContaining({ char: 'a' })
);
node.inlets['char'].receive('c');
expect(processSpy).toHaveBeenCalledWith(
jasmine.objectContaining({ char: 'c' }),
jasmine.objectContaining({ char: 'b' })
);
});
});
it('still gets values from hidden inlets', function() {
Rpd.nodetype('spec/foo', {
inlets: { 'char': { type: 'spec/any',
hidden: true,
'default': 'a' } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
expect(processSpy).toHaveBeenCalledWith(
jasmine.objectContaining({ char: 'a' }),
jasmine.anything()
);
node.inlets['char'].receive('b');
expect(processSpy).toHaveBeenCalledWith(
jasmine.objectContaining({ char: 'b' }),
jasmine.anything()
);
});
});
it('still gets values from hidden inlets', function() {
Rpd.nodetype('spec/foo', {
inlets: { 'char': { type: 'spec/any',
hidden: true,
'default': 'a' } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
expect(node.inlets['char'].def.hidden).toBeTruthy();
expect(processSpy).toHaveBeenCalledWith(
jasmine.objectContaining({ char: 'a' }),
jasmine.anything()
);
node.inlets['char'].receive('b');
expect(processSpy).toHaveBeenCalledWith(
jasmine.objectContaining({ char: 'b' }),
jasmine.anything()
);
});
});
it('does not react if updated inlet was cold, but keeps its value for next update', function() {
Rpd.nodetype('spec/foo', {
inlets: { 'foo': { type: 'spec/any',
cold: true,
'default': 'a' },
'bar': { type: 'spec/any', cold: true },
'buz': { type: 'spec/any' } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
expect(processSpy).not.toHaveBeenCalledWith(
jasmine.objectContaining({ foo: 'a' })
);
node.inlets['foo'].receive('b');
expect(processSpy).not.toHaveBeenCalledWith(
jasmine.objectContaining({ foo: 'b' })
);
node.inlets['bar'].receive(12);
expect(processSpy).not.toHaveBeenCalledWith(
jasmine.objectContaining({ bar: 12 })
);
node.inlets['buz'].receive('jazz');
expect(processSpy).toHaveBeenCalledWith(
jasmine.objectContaining({ bar: 12, foo: 'b', buz: 'jazz' }),
jasmine.objectContaining({ foo: 'a' })
);
});
});
it('passes values to corresponding outlets based on default inlets values', function() {
processSpy.and.callFake(function(inlets) {
return { 'c': (inlets.a || 0) * (inlets.b || 1) };
});
Rpd.nodetype('spec/foo', {
inlets: { 'a': { type: 'spec/any', 'default': 7 },
'b': { type: 'spec/any', 'default': 2 } },
outlets: { 'c': { type: 'spec/any' } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
var outlet = node.outlets['c'];
expect(processSpy).toHaveBeenCalled();
expect(updateSpy).toHaveBeenCalledWith(
jasmine.objectContaining({
type: 'outlet/update',
outlet: outlet,
value: 7 * 1
})
);
expect(updateSpy).toHaveBeenCalledWith(
jasmine.objectContaining({
type: 'outlet/update',
outlet: outlet,
value: 2 * 7
})
);
});
});
it('passes single values to corresponding outlets, even in sequences', function() {
processSpy.and.callFake(function(inlets) {
return { 'c': inlets.a * (inlets.b || 1),
'd': (inlets.b || 0) * 3 };
});
Rpd.nodetype('spec/foo', {
inlets: { 'a': { type: 'spec/any' },
'b': { type: 'spec/any' } },
outlets: { 'c': { type: 'spec/any' },
'd': { type: 'spec/any' } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
node.inlets['a'].receive(7);
node.inlets['b'].receive(2);
node.inlets['b'].receive(6);
var outletC = node.outlets['c'];
var outletD = node.outlets['d'];
expect(processSpy).toHaveBeenCalled();
expect(updateSpy).toHaveBeenCalledWith(
jasmine.objectContaining({
type: 'outlet/update',
outlet: outletC,
value: 7 * 1
})
);
expect(updateSpy).toHaveBeenCalledWith(
jasmine.objectContaining({
type: 'outlet/update',
outlet: outletC,
value: 7 * 2
})
);
expect(updateSpy).toHaveBeenCalledWith(
jasmine.objectContaining({
type: 'outlet/update',
outlet: outletC,
value: 7 * 6
})
);
expect(updateSpy).toHaveBeenCalledWith(
jasmine.objectContaining({
type: 'outlet/update',
outlet: outletD,
value: 0 * 3
})
);
expect(updateSpy).toHaveBeenCalledWith(
jasmine.objectContaining({
type: 'outlet/update',
outlet: outletD,
value: 2 * 3
})
);
expect(updateSpy).toHaveBeenCalledWith(
jasmine.objectContaining({
type: 'outlet/update',
outlet: outletD,
value: 6 * 3
})
);
});
});
describe('streaming to outlets', function() {
beforeEach(function() {
jasmine.clock().install();
});
afterEach(function() {
jasmine.clock().uninstall();
});
it('passes streamed values to corresponding outlets', function() {
var values = [ 3, 14, 15, 92 ];
var period = 30;
processSpy.and.callFake(function(inlets) {
return { 'out': Kefir.sequentially(period, values)
.map(function(value) {
return (inlets.in * value);
}) };
});
Rpd.nodetype('spec/foo', {
inlets: { 'in': { type: 'spec/any' } },
outlets: { 'out': { type: 'spec/any' } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
node.inlets['in'].receive(7);
var outlet = node.outlets['out'];
expect(processSpy).toHaveBeenCalled();
jasmine.clock().tick(period * (values.length + 1));
for (var i = 0; i < values.length; i++) {
expect(updateSpy).toHaveBeenCalledWith(
jasmine.objectContaining({ type: 'outlet/update',
outlet: outlet,
value: values[i] * 7 }));
}
});
});
it('new calls to the process function properly create new streams', function() {
processSpy.and.callFake(function(inlets) {
return { 'out':
Kefir.interval(inlets.config.period, inlets.config.value)
.take(inlets.config.count)
};
});
Rpd.nodetype('spec/foo', {
inlets: { 'config': { type: 'spec/any' } },
outlets: { 'out': { type: 'spec/any' } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
node.inlets['config'].receive({ period: 20, count: 10, value: 3 });
updateSpy.calls.reset();
var outlet = node.outlets['out'];
jasmine.clock().tick(20 * 10 + 1);
expect(updateSpy).toHaveBeenCalledWith(
jasmine.objectContaining({ type: 'outlet/update',
outlet: outlet,
value: 3 }));
expect(updateSpy.calls.count()).toBe(10);
node.inlets['config'].receive({ period: 10, count: 5, value: 7 });
updateSpy.calls.reset();
jasmine.clock().tick(10 * 5 + 1);
expect(updateSpy).not.toHaveBeenCalledWith(
jasmine.objectContaining({ type: 'outlet/update',
outlet: outlet,
value: 3 }));
expect(updateSpy).toHaveBeenCalledWith(
jasmine.objectContaining({ type: 'outlet/update',
outlet: outlet,
value: 7 }));
expect(updateSpy.calls.count()).toBe(5);
});
});
it('users are able to turn off streams they send from outlets', function() {
var lastStream;
var firstTime = true;
var pool = Kefir.pool();
processSpy.and.callFake(function(inlets) {
if (lastStream) {
firstTime = false;
pool.unplug(lastStream);
}
lastStream = Kefir.interval(inlets.config.period, inlets.config.value);
pool.plug(lastStream);
//return { 'out': firstTime ? pool : Kefir.never() };
return firstTime ? { 'out': pool } : null;
});
Rpd.nodetype('spec/foo', {
inlets: { 'config': { type: 'spec/any' } },
outlets: { 'out': { type: 'spec/any' } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
var outlet = node.outlets['out'];
var updateEventStream = outlet.event['outlet/update'];
var outletUpdateSpy = jasmine.createSpy();
var value3spy = jasmine.createSpy('value-3');
var value7spy = jasmine.createSpy('value-7');
updateEventStream.onValue(outletUpdateSpy);
updateEventStream.filter(function(value) {
return (value === 3);
}).onValue(value3spy);
updateEventStream.filter(function(value) {
return (value === 7);
}).onValue(value7spy);
node.inlets['config'].receive({ period: 20, value: 3 });
jasmine.clock().tick(20 * 10 + 1);
expect(value3spy.calls.count()).toBe(10);
expect(value7spy).not.toHaveBeenCalled();
value3spy.calls.reset();
node.inlets['config'].receive({ period: 10, value: 7 });
jasmine.clock().tick(10 * 5 + 1);
pool.unplug(lastStream);
expect(value3spy).not.toHaveBeenCalled();
expect(value7spy.calls.count()).toBe(5);
expect(outletUpdateSpy.calls.count()).toBe(10 + 5);
});
});
});
it('passes outlet values to process event', function() {
processSpy.and.callFake(function(inlets) {
return { 'c': inlets.a * inlets.b,
'd': inlets.b - inlets.a };
});
Rpd.nodetype('spec/foo', {
inlets: { 'a': { type: 'spec/any' },
'b': { type: 'spec/any' } },
outlets: { 'c': { type: 'spec/any' },
'd': { type: 'spec/any' } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
node.inlets['a'].receive(7);
node.inlets['b'].receive(2);
var outletC = node.outlets['c'];
var outletD = node.outlets['d'];
expect(processSpy).toHaveBeenCalled();
expect(updateSpy).toHaveBeenCalledWith(
jasmine.objectContaining({
type: 'node/process',
inlets: { a: 7, b : 2 },
outlets: { c: 7 * 2, d: 2 - 7 }
})
);
});
});
it('if no outlet was updated, does not fire the update for this outlet', function() {
processSpy.and.callFake(function(inlets) {
return {};
});
Rpd.nodetype('spec/foo', {
inlets: { 'a': { type: 'spec/any', 'default': 4 },
'b': { type: 'spec/any' } },
outlets: { 'c': { type: 'spec/any' },
'd': { type: 'spec/any' } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
node.inlets['b'].receive(7);
expect(updateSpy).not.toHaveBeenCalledWith(
jasmine.anything(),
jasmine.objectContaining({ type: 'outlet/update' })
);
});
});
it('treats no return value as no update to any outlet', function() {
processSpy.and.callFake(function(inlets) { });
Rpd.nodetype('spec/foo', {
inlets: { 'a': { type: 'spec/any', 'default': 2 },
'b': { type: 'spec/any' } },
outlets: { 'c': { type: 'spec/any' },
'd': { type: 'spec/any' } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
node.inlets['b'].receive(12);
expect(updateSpy).not.toHaveBeenCalledWith(
jasmine.objectContaining({ type: 'outlet/update' })
);
expect(processSpy).toHaveBeenCalledWith(
jasmine.objectContaining({ a: 2 }),
jasmine.anything()
);
expect(processSpy).toHaveBeenCalledWith(
jasmine.objectContaining({ b: 12 }),
jasmine.anything()
);
});
});
it('processing function receives node instance as `this`', function() {
var nodeInSpy;
var processSpy = jasmine.createSpy('process').and.callFake(function() {
nodeInSpy = this;
return {};
});
Rpd.nodetype('spec/foo', {
inlets: { 'a': { type: 'spec/any' } },
process: processSpy
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
node.inlets['a'].receive({});
expect(processSpy).toHaveBeenCalled();
expect(nodeInSpy).toBe(node);
});
});
xdescribe('overriding channel type definition', function() {
xit('overriding inlet adapt function', function() {});
xit('overriding inlet allow function', function() {});
xit('overriding inlet accept function', function() {});
xit('overriding inlet show function', function() {});
xit('overriding inlet tune function', function() {});
xit('overriding outlet tune function', function() {});
xit('subscribing to inlet events', function() {});
xit('subscribing to outlet events', function() {});
});
it('one could add inlets or outlets from the inside', function() {
Rpd.nodetype('spec/foo', {
inlets: { 'a': { type: 'spec/any' } }, // force call to process function
process: processSpy.and.callFake(function(inlets) {
if (!inlets.bar) this.addInlet('spec/any', 'bar');
})
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
node.inlets['a'].receive(2); // force call to process function
expect(updateSpy).toHaveBeenCalledWith(
jasmine.objectContaining({
type: 'node/add-inlet',
inlet: jasmine.objectContaining({ alias: 'bar' })
})
);
expect(processSpy).toHaveBeenCalledOnce();
});
});
it('incoming values stream could be tuned', function() {
Rpd.nodetype('spec/foo', {
inlets: { 'a': { type: 'spec/any', 'default': -1 } },
process: processSpy,
tune: function(incoming) {
return incoming
.filter(function(update) {
return update.value !== 2;
})
.map(function(update) {
return { inlet: update.inlet,
value: update.value * 10 };
});
}
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
node.inlets['a'].receive(0);
expect(processSpy).toHaveBeenCalledWith(
jasmine.objectContaining({ a: 0 }),
jasmine.anything()
);
node.inlets['a'].receive(1);
expect(processSpy).toHaveBeenCalledWith(
jasmine.objectContaining({ a: 10 }),
jasmine.anything()
);
node.inlets['a'].receive(2);
expect(processSpy).not.toHaveBeenCalledWith(
jasmine.objectContaining({ a: 2 }),
jasmine.anything()
);
expect(processSpy).not.toHaveBeenCalledWith(
jasmine.objectContaining({ a: 20 }),
jasmine.anything()
);
node.inlets['a'].receive(3);
expect(processSpy).toHaveBeenCalledWith(
jasmine.objectContaining({ a: 30 }),
jasmine.anything()
);
});
});
it('tuning function receives node instance as `this`', function() {
var nodeInSpy;
var tuneSpy = jasmine.createSpy('tune').and.callFake(function(stream) {
nodeInSpy = this;
return stream;
});
Rpd.nodetype('spec/foo', {
inlets: { 'a': { type: 'spec/any' } },
process: function() {},
tune: tuneSpy
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
node.inlets['a'].receive({});
expect(tuneSpy).toHaveBeenCalled();
expect(nodeInSpy).toBe(node);
});
});
it('tuning function may delay updates', function() {
jasmine.clock().install();
Rpd.nodetype('spec/delay', {
inlets: { 'in': { type: 'spec/any' } },
outlets: { 'out': { type: 'spec/any' } },
tune: function(updates) {
return updates.delay(1000); // delays updates for one second
},
process: function(inlets) {
return { out: inlets.in };
}
});
withNewPatch(function(patch, updateSpy) {
var delayingNode = patch.addNode('spec/delay');
delayingNode.inlets['in'].receive(42);
expect(updateSpy).not.toHaveBeenCalledWith(
jasmine.objectContaining({
type: 'outlet/update'
})
);
jasmine.clock().tick(500);
expect(updateSpy).not.toHaveBeenCalledWith(
jasmine.objectContaining({
type: 'outlet/update'
})
);
jasmine.clock().tick(501);
expect(updateSpy).toHaveBeenCalledWith(
jasmine.objectContaining({
type: 'outlet/update',
outlet: delayingNode.outlets['out'],
value: 42
})
);
});
jasmine.clock().uninstall();
});
});
it('updates could be handled with custom handle function', function() {
var inletUpdateSpy = jasmine.createSpy('inlet-update');
var outletConnectSpy = jasmine.createSpy('outlet-connect');
Rpd.nodetype('spec/foo', {
inlets: { 'in': { type: 'spec/any' } },
outlets: { 'out': { type: 'spec/any' } },
handle: {
'inlet/update': inletUpdateSpy,
'outlet/connect': outletConnectSpy
}
});
withNewPatch(function(patch, updateSpy) {
var firstNode = patch.addNode('spec/foo');
var secondNode = patch.addNode('spec/foo');
var fromOutlet = firstNode.outlets['out'];
var toInlet = secondNode.inlets['in'];
toInlet.receive(12);
expect(inletUpdateSpy).toHaveBeenCalledWith(
jasmine.objectContaining({ type: 'inlet/update',
inlet: toInlet,
value: 12 }));
var link = fromOutlet.connect(toInlet);
expect(outletConnectSpy).toHaveBeenCalledWith(
jasmine.objectContaining({ type: 'outlet/connect',
link: link }));
});
});
it('default inlets and outlets could be prepared with custom prepare function', function() {
var prepareSpy = jasmine.createSpy('prepare');
Rpd.nodetype('spec/foo', {
inlets: { 'a': { type: 'spec/any' },
'b': { type: 'spec/any' } },
outlets: { 'c': { type: 'spec/any' } },
prepare: prepareSpy
});
withNewPatch(function(patch, updateSpy) {
updateSpy.and.callFake(function(update) {
if (update.type === 'node/is-ready') {
expect(prepareSpy).toHaveBeenCalled();
}
});
var node = patch.addNode('spec/foo');
expect(prepareSpy).toHaveBeenCalledWith(
jasmine.objectContaining({
a: node.inlets['a'],
b: node.inlets['b']
}),
jasmine.objectContaining({
c: node.outlets['c']
})
);
expect(prepareSpy).toHaveBeenCalledOnce();
});
});
it('preparing function receives node instance as `this`', function() {
var nodeInSpy;
var prepareSpy = jasmine.createSpy('tune').and.callFake(function() {
nodeInSpy = this;
});
Rpd.nodetype('spec/foo', {
inlets: { 'a': { type: 'spec/any' } },
prepare: prepareSpy
});
withNewPatch(function(patch, updateSpy) {
var node = patch.addNode('spec/foo');
expect(prepareSpy).toHaveBeenCalled();
expect(nodeInSpy).toBe(node);
});
});
it('could be specified as a single function which returns the defition and gets node instance', function() {
var definitionGenSpy = jasmine.createSpy('definition-generator')
.and.callFake(function(node) {
return { title: 'foo-' + node.id };
});
Rpd.nodetype('spec/foo', definitionGenSpy);
withNewPatch(function(patch, updateSpy) {
var firstNode = patch.addNode('spec/foo'),
secondNode = patch.addNode('spec/foo');
expect(definitionGenSpy).toHaveBeenCalled();
expect(definitionGenSpy).toHaveBeenCalledWith(firstNode);
expect(definitionGenSpy).toHaveBeenCalledWith(secondNode);
expect(firstNode.def.title).toBe('foo-' + firstNode.id);
expect(secondNode.def.title).toBe('foo-' + secondNode.id);
});
});
xit('it is possible to handle events from node definition');
xit('`node/process` event is not fired to node definition handler unless `process` callback is defined');
});