UNPKG

noflo

Version:

Flow-Based Programming environment for JavaScript

1,720 lines (1,718 loc) 88.8 kB
var chai, noflo; if (typeof process !== 'undefined' && process.execPath && process.execPath.match(/node|iojs/)) { if (!chai) { chai = require('chai'); } noflo = require('../src/lib/NoFlo.coffee'); } else { noflo = require('noflo'); } describe('Component', function() { describe('with required ports', function() { it('should throw an error upon sending packet to an unattached required port', function() { var c, s2; s2 = new noflo.internalSocket.InternalSocket; c = new noflo.Component({ outPorts: { required_port: { required: true }, optional_port: {} } }); c.outPorts.optional_port.attach(s2); return chai.expect(function() { return c.outPorts.required_port.send('foo'); }).to["throw"](); }); return it('should be cool with an attached port', function() { var c, f, s1, s2; s1 = new noflo.internalSocket.InternalSocket; s2 = new noflo.internalSocket.InternalSocket; c = new noflo.Component({ inPorts: { required_port: { required: true }, optional_port: {} } }); c.inPorts.required_port.attach(s1); c.inPorts.optional_port.attach(s2); f = function() { s1.send('some-more-data'); return s2.send('some-data'); }; return chai.expect(f).to.not["throw"](); }); }); describe('with component creation shorthand', function() { it('should make component creation easy', function(done) { var c, s1, s2; c = new noflo.Component({ inPorts: { "in": { datatype: 'string', required: true, process: function(event, packet, component) { if (event !== 'data') { return; } chai.expect(packet).to.equal('some-data'); return chai.expect(component).to.equal(c); } }, just_processor: function(event, packet, component) { if (event !== 'data') { return; } chai.expect(packet).to.equal('some-data'); chai.expect(component).to.equal(c); return done(); } } }); s1 = new noflo.internalSocket.InternalSocket; c.inPorts["in"].attach(s1); c.inPorts["in"].nodeInstance = c; s2 = new noflo.internalSocket.InternalSocket; c.inPorts.just_processor.attach(s1); c.inPorts.just_processor.nodeInstance = c; s1.send('some-data'); return s2.send('some-data'); }); it('should throw errors if there is no error port', function(done) { var c, s1; c = new noflo.Component({ inPorts: { "in": { datatype: 'string', required: true, process: function(event, packet, component) { if (event !== 'data') { return; } chai.expect(packet).to.equal('some-data'); chai.expect(component).to.equal(c); chai.expect(function() { return c.error(new Error); }).to["throw"](Error); return done(); } } } }); s1 = new noflo.internalSocket.InternalSocket; c.inPorts["in"].attach(s1); c.inPorts["in"].nodeInstance = c; return s1.send('some-data'); }); it('should throw errors if there is a non-attached error port', function(done) { var c, s1; c = new noflo.Component({ inPorts: { "in": { datatype: 'string', required: true, process: function(event, packet, component) { if (event !== 'data') { return; } chai.expect(packet).to.equal('some-data'); chai.expect(component).to.equal(c); chai.expect(function() { return c.error(new Error); }).to["throw"](Error); return done(); } } }, outPorts: { error: { datatype: 'object', required: true } } }); s1 = new noflo.internalSocket.InternalSocket; c.inPorts["in"].attach(s1); c.inPorts["in"].nodeInstance = c; return s1.send('some-data'); }); it('should not throw errors if there is a non-required error port', function(done) { var c, s1; c = new noflo.Component({ inPorts: { "in": { datatype: 'string', required: true, process: function(event, packet, component) { if (event !== 'data') { return; } chai.expect(packet).to.equal('some-data'); chai.expect(component).to.equal(c); c.error(new Error); return done(); } } }, outPorts: { error: { required: false } } }); s1 = new noflo.internalSocket.InternalSocket; c.inPorts["in"].attach(s1); c.inPorts["in"].nodeInstance = c; return s1.send('some-data'); }); return it('should send errors if there is a connected error port', function(done) { var c, groups, grps, s1, s2; grps = []; c = new noflo.Component({ inPorts: { "in": { datatype: 'string', required: true, process: function(event, packet, component) { if (event === 'begingroup') { grps.push(packet); } if (event !== 'data') { return; } chai.expect(packet).to.equal('some-data'); chai.expect(component).to.equal(c); return c.error(new Error, grps); } } }, outPorts: { error: { datatype: 'object' } } }); s1 = new noflo.internalSocket.InternalSocket; s2 = new noflo.internalSocket.InternalSocket; groups = ['foo', 'bar']; s2.on('begingroup', function(grp) { return chai.expect(grp).to.equal(groups.shift()); }); s2.on('data', function(err) { chai.expect(err).to.be.an.instanceOf(Error); chai.expect(groups.length).to.equal(0); return done(); }); c.inPorts["in"].attach(s1); c.outPorts.error.attach(s2); c.inPorts["in"].nodeInstance = c; s1.beginGroup('foo'); s1.beginGroup('bar'); return s1.send('some-data'); }); }); describe('defining ports with invalid names', function() { it('should throw an error with uppercase letters in inport', function() { var shorthand; shorthand = function() { var c; return c = new noflo.Component({ inPorts: { fooPort: {} } }); }; return chai.expect(shorthand).to["throw"](); }); it('should throw an error with uppercase letters in outport', function() { var shorthand; shorthand = function() { var c; return c = new noflo.Component({ outPorts: { BarPort: {} } }); }; return chai.expect(shorthand).to["throw"](); }); return it('should throw an error with special characters in inport', function() { var shorthand; shorthand = function() { var c; return c = new noflo.Component({ inPorts: { '$%^&*a': {} } }); }; return chai.expect(shorthand).to["throw"](); }); }); describe('starting a component', function() { return it('should flag the component as started', function(done) { var c, i; c = new noflo.Component({ inPorts: { "in": { datatype: 'string', required: true } } }); i = new noflo.internalSocket.InternalSocket; c.inPorts["in"].attach(i); return c.start(function(err) { if (err) { return done(err); } chai.expect(c.started).to.equal(true); chai.expect(c.isStarted()).to.equal(true); return done(); }); }); }); describe('shutting down a component', function() { return it('should flag the component as not started', function(done) { var c, i; c = new noflo.Component({ inPorts: { "in": { datatype: 'string', required: true } } }); i = new noflo.internalSocket.InternalSocket; c.inPorts["in"].attach(i); return c.start(function(err) { if (err) { return done(err); } chai.expect(c.isStarted()).to.equal(true); return c.shutdown(function(err) { if (err) { return done(err); } chai.expect(c.started).to.equal(false); chai.expect(c.isStarted()).to.equal(false); return done(); }); }); }); }); describe('with object-based IPs', function() { it('should speak IP objects', function(done) { var c, s1, s2; c = new noflo.Component({ inPorts: { "in": { datatype: 'string', handle: function(ip, component) { chai.expect(ip).to.be.an('object'); chai.expect(ip.type).to.equal('data'); chai.expect(ip.groups).to.be.an('array'); chai.expect(ip.groups).to.eql(['foo']); chai.expect(ip.data).to.be.a('string'); chai.expect(ip.data).to.equal('some-data'); return c.outPorts.out.data('bar', { groups: ['foo'] }); } } }, outPorts: { out: { datatype: 'string' } } }); s1 = new noflo.internalSocket.InternalSocket; s2 = new noflo.internalSocket.InternalSocket; s2.on('ip', function(ip) { chai.expect(ip).to.be.an('object'); chai.expect(ip.type).to.equal('data'); chai.expect(ip.groups).to.be.an('array'); chai.expect(ip.groups).to.eql(['foo']); chai.expect(ip.data).to.be.a('string'); chai.expect(ip.data).to.equal('bar'); return done(); }); c.inPorts["in"].attach(s1); c.outPorts.out.attach(s2); return s1.post(new noflo.IP('data', 'some-data', { groups: ['foo'] })); }); return it('should support substreams', function(done) { var c, d, s1, s2, s3; c = new noflo.Component({ inPorts: { tags: { datatype: 'string', handle: function(ip) { chai.expect(ip).to.be.an('object'); switch (ip.type) { case 'openBracket': c.str += "<" + ip.data + ">"; return c.level++; case 'data': return c.str += ip.data; case 'closeBracket': c.str += "</" + ip.data + ">"; c.level--; if (c.level === 0) { c.outPorts.html.data(c.str); return c.str = ''; } } } } }, outPorts: { html: { datatype: 'string' } } }); c.str = ''; c.level = 0; d = new noflo.Component({ inPorts: { bang: { datatype: 'bang', handle: function(ip) { return d.outPorts.tags.openBracket('p').openBracket('em').data('Hello').closeBracket('em').data(', ').openBracket('strong').data('World!').closeBracket('strong').closeBracket('p'); } } }, outPorts: { tags: { datatype: 'string' } } }); s1 = new noflo.internalSocket.InternalSocket; s2 = new noflo.internalSocket.InternalSocket; s3 = new noflo.internalSocket.InternalSocket; s3.on('ip', function(ip) { chai.expect(ip).to.be.an('object'); chai.expect(ip.type).to.equal('data'); chai.expect(ip.data).to.equal('<p><em>Hello</em>, <strong>World!</strong></p>'); return done(); }); d.inPorts.bang.attach(s1); d.outPorts.tags.attach(s2); c.inPorts.tags.attach(s2); c.outPorts.html.attach(s3); return s1.post(new noflo.IP('data', 'start')); }); }); describe('with process function', function() { var c, sin1, sin2, sin3, sout1, sout2; c = null; sin1 = null; sin2 = null; sin3 = null; sout1 = null; sout2 = null; beforeEach(function(done) { sin1 = new noflo.internalSocket.InternalSocket; sin2 = new noflo.internalSocket.InternalSocket; sin3 = new noflo.internalSocket.InternalSocket; sout1 = new noflo.internalSocket.InternalSocket; sout2 = new noflo.internalSocket.InternalSocket; return done(); }); it('should trigger on IPs', function(done) { var count, hadIPs; hadIPs = []; c = new noflo.Component({ inPorts: { foo: { datatype: 'string' }, bar: { datatype: 'string' } }, outPorts: { baz: { datatype: 'boolean' } }, process: function(input, output) { hadIPs = []; if (input.has('foo')) { hadIPs.push('foo'); } if (input.has('bar')) { hadIPs.push('bar'); } return output.sendDone({ baz: true }); } }); c.inPorts.foo.attach(sin1); c.inPorts.bar.attach(sin2); c.outPorts.baz.attach(sout1); count = 0; sout1.on('ip', function(ip) { count++; if (count === 1) { chai.expect(hadIPs).to.eql(['foo']); } if (count === 2) { chai.expect(hadIPs).to.eql(['foo', 'bar']); return done(); } }); sin1.post(new noflo.IP('data', 'first')); return sin2.post(new noflo.IP('data', 'second')); }); it('should trigger on IPs to addressable ports', function(done) { var count, receivedIndexes; receivedIndexes = []; c = new noflo.Component({ inPorts: { foo: { datatype: 'string', addressable: true } }, outPorts: { baz: { datatype: 'boolean' } }, process: function(input, output) { var indexToUse, indexesWithData, packet; indexesWithData = input.attached('foo').filter(function(idx) { return input.hasData(['foo', idx]); }); if (!indexesWithData.length) { return; } indexToUse = indexesWithData[0]; packet = input.get(['foo', indexToUse]); receivedIndexes.push({ idx: indexToUse, payload: packet.data }); return output.sendDone({ baz: true }); } }); c.inPorts.foo.attach(sin1, 1); c.inPorts.foo.attach(sin2, 0); c.outPorts.baz.attach(sout1); count = 0; sout1.on('ip', function(ip) { count++; if (count === 1) { chai.expect(receivedIndexes).to.eql([ { idx: 1, payload: 'first' } ]); } if (count === 2) { chai.expect(receivedIndexes).to.eql([ { idx: 1, payload: 'first' }, { idx: 0, payload: 'second' } ]); return done(); } }); sin1.post(new noflo.IP('data', 'first')); return sin2.post(new noflo.IP('data', 'second')); }); it('should be able to send IPs to addressable connections', function(done) { var expected; expected = [ { data: 'first', index: 1 }, { data: 'second', index: 0 } ]; c = new noflo.Component({ inPorts: { foo: { datatype: 'string' } }, outPorts: { baz: { datatype: 'boolean', addressable: true } }, process: function(input, output) { var packet; if (!input.has('foo')) { return; } packet = input.get('foo'); return output.sendDone(new noflo.IP('data', packet.data, { index: expected.length - 1 })); } }); c.inPorts.foo.attach(sin1); c.outPorts.baz.attach(sout1, 1); c.outPorts.baz.attach(sout2, 0); sout1.on('ip', function(ip) { var exp, received; exp = expected.shift(); received = { data: ip.data, index: 1 }; chai.expect(received).to.eql(exp); if (!expected.length) { return done(); } }); sout2.on('ip', function(ip) { var exp, received; exp = expected.shift(); received = { data: ip.data, index: 0 }; chai.expect(received).to.eql(exp); if (!expected.length) { return done(); } }); sin1.post(new noflo.IP('data', 'first')); return sin1.post(new noflo.IP('data', 'second')); }); it('trying to send to addressable port without providing index should fail', function(done) { c = new noflo.Component({ inPorts: { foo: { datatype: 'string' } }, outPorts: { baz: { datatype: 'boolean', addressable: true } }, process: function(input, output) { var noIndex, packet; if (!input.hasData('foo')) { return; } packet = input.get('foo'); noIndex = new noflo.IP('data', packet.data); chai.expect(function() { return output.sendDone(noIndex); }).to["throw"](Error); return done(); } }); c.inPorts.foo.attach(sin1); c.outPorts.baz.attach(sout1, 1); c.outPorts.baz.attach(sout2, 0); sout1.on('ip', function(ip) {}); sout2.on('ip', function(ip) {}); return sin1.post(new noflo.IP('data', 'first')); }); it('should not be triggered by non-triggering ports', function(done) { var count, triggered; triggered = []; c = new noflo.Component({ inPorts: { foo: { datatype: 'string', triggering: false }, bar: { datatype: 'string' } }, outPorts: { baz: { datatype: 'boolean' } }, process: function(input, output) { triggered.push(input.port.name); return output.sendDone({ baz: true }); } }); c.inPorts.foo.attach(sin1); c.inPorts.bar.attach(sin2); c.outPorts.baz.attach(sout1); count = 0; sout1.on('ip', function(ip) { count++; if (count === 1) { chai.expect(triggered).to.eql(['bar']); } if (count === 2) { chai.expect(triggered).to.eql(['bar', 'bar']); return done(); } }); sin1.post(new noflo.IP('data', 'first')); sin2.post(new noflo.IP('data', 'second')); sin1.post(new noflo.IP('data', 'first')); return sin2.post(new noflo.IP('data', 'second')); }); it('should fetch undefined for premature data', function(done) { c = new noflo.Component({ inPorts: { foo: { datatype: 'string' }, bar: { datatype: 'boolean', triggering: false, control: true }, baz: { datatype: 'string', triggering: false, control: true } }, process: function(input, output) { var bar, baz, foo, ref; if (!input.has('foo')) { return; } ref = input.getData('foo', 'bar', 'baz'), foo = ref[0], bar = ref[1], baz = ref[2]; chai.expect(foo).to.be.a('string'); chai.expect(bar).to.be.undefined; chai.expect(baz).to.be.undefined; return done(); } }); c.inPorts.foo.attach(sin1); c.inPorts.bar.attach(sin2); c.inPorts.baz.attach(sin3); sin1.post(new noflo.IP('data', 'AZ')); sin2.post(new noflo.IP('data', true)); return sin3.post(new noflo.IP('data', 'first')); }); it('should receive and send complete noflo.IP objects', function(done) { c = new noflo.Component({ inPorts: { foo: { datatype: 'string' }, bar: { datatype: 'string' } }, outPorts: { baz: { datatype: 'object' } }, process: function(input, output) { var bar, baz, foo, ref; if (!input.has('foo', 'bar')) { return; } ref = input.get('foo', 'bar'), foo = ref[0], bar = ref[1]; baz = { foo: foo.data, bar: bar.data, groups: foo.groups, type: bar.type }; return output.sendDone({ baz: new noflo.IP('data', baz, { groups: ['baz'] }) }); } }); c.inPorts.foo.attach(sin1); c.inPorts.bar.attach(sin2); c.outPorts.baz.attach(sout1); sout1.once('ip', function(ip) { chai.expect(ip).to.be.an('object'); chai.expect(ip.type).to.equal('data'); chai.expect(ip.data.foo).to.equal('foo'); chai.expect(ip.data.bar).to.equal('bar'); chai.expect(ip.data.groups).to.eql(['foo']); chai.expect(ip.data.type).to.equal('data'); chai.expect(ip.groups).to.eql(['baz']); return done(); }); sin1.post(new noflo.IP('data', 'foo', { groups: ['foo'] })); return sin2.post(new noflo.IP('data', 'bar', { groups: ['bar'] })); }); it('should receive and send just IP data if wanted', function(done) { c = new noflo.Component({ inPorts: { foo: { datatype: 'string' }, bar: { datatype: 'string' } }, outPorts: { baz: { datatype: 'object' } }, process: function(input, output) { var bar, baz, foo, ref; if (!input.has('foo', 'bar')) { return; } ref = input.getData('foo', 'bar'), foo = ref[0], bar = ref[1]; baz = { foo: foo, bar: bar }; return output.sendDone({ baz: baz }); } }); c.inPorts.foo.attach(sin1); c.inPorts.bar.attach(sin2); c.outPorts.baz.attach(sout1); sout1.once('ip', function(ip) { chai.expect(ip).to.be.an('object'); chai.expect(ip.type).to.equal('data'); chai.expect(ip.data.foo).to.equal('foo'); chai.expect(ip.data.bar).to.equal('bar'); return done(); }); sin1.post(new noflo.IP('data', 'foo', { groups: ['foo'] })); return sin2.post(new noflo.IP('data', 'bar', { groups: ['bar'] })); }); it('should receive IPs and be able to selectively find them', function(done) { var called, shouldHaveSent; called = 0; c = new noflo.Component({ inPorts: { foo: { datatype: 'string' }, bar: { datatype: 'string' } }, outPorts: { baz: { datatype: 'object' } }, process: function(input, output) { var bar, foo, validate; validate = function(ip) { called++; return ip.type === 'data' && ip.data === 'hello'; }; if (!input.has('foo', 'bar', validate)) { return; } foo = input.get('foo'); while ((foo != null ? foo.type : void 0) !== 'data') { foo = input.get('foo'); } bar = input.getData('bar'); return output.sendDone({ baz: foo.data + ":" + bar }); } }); c.inPorts.foo.attach(sin1); c.inPorts.bar.attach(sin2); c.outPorts.baz.attach(sout1); shouldHaveSent = false; sout1.on('ip', function(ip) { chai.expect(shouldHaveSent, 'Should not sent before its time').to.equal(true); chai.expect(ip).to.be.an('object'); chai.expect(ip.type).to.equal('data'); chai.expect(ip.data).to.equal('hello:hello'); chai.expect(called).to.equal(10); return done(); }); sin1.post(new noflo.IP('openBracket', 'a')); sin1.post(new noflo.IP('data', 'hello', sin1.post(new noflo.IP('closeBracket', 'a')))); shouldHaveSent = true; return sin2.post(new noflo.IP('data', 'hello')); }); it('should keep last value for controls', function(done) { c = new noflo.Component({ inPorts: { foo: { datatype: 'string' }, bar: { datatype: 'string', control: true } }, outPorts: { baz: { datatype: 'object' } }, process: function(input, output) { var bar, baz, foo, ref; if (!input.has('foo', 'bar')) { return; } ref = input.getData('foo', 'bar'), foo = ref[0], bar = ref[1]; baz = { foo: foo, bar: bar }; return output.sendDone({ baz: baz }); } }); c.inPorts.foo.attach(sin1); c.inPorts.bar.attach(sin2); c.outPorts.baz.attach(sout1); sout1.once('ip', function(ip) { chai.expect(ip).to.be.an('object'); chai.expect(ip.type).to.equal('data'); chai.expect(ip.data.foo).to.equal('foo'); chai.expect(ip.data.bar).to.equal('bar'); return sout1.once('ip', function(ip) { chai.expect(ip).to.be.an('object'); chai.expect(ip.type).to.equal('data'); chai.expect(ip.data.foo).to.equal('boo'); chai.expect(ip.data.bar).to.equal('bar'); return done(); }); }); sin1.post(new noflo.IP('data', 'foo')); sin2.post(new noflo.IP('data', 'bar')); return sin1.post(new noflo.IP('data', 'boo')); }); it('should keep last data-typed IP packet for controls', function(done) { c = new noflo.Component({ inPorts: { foo: { datatype: 'string' }, bar: { datatype: 'string', control: true } }, outPorts: { baz: { datatype: 'object' } }, process: function(input, output) { var bar, baz, foo, ref; if (!input.has('foo', 'bar')) { return; } ref = input.getData('foo', 'bar'), foo = ref[0], bar = ref[1]; baz = { foo: foo, bar: bar }; return output.sendDone({ baz: baz }); } }); c.inPorts.foo.attach(sin1); c.inPorts.bar.attach(sin2); c.outPorts.baz.attach(sout1); sout1.once('ip', function(ip) { chai.expect(ip).to.be.an('object'); chai.expect(ip.type).to.equal('data'); chai.expect(ip.data.foo).to.equal('foo'); chai.expect(ip.data.bar).to.equal('bar'); return sout1.once('ip', function(ip) { chai.expect(ip).to.be.an('object'); chai.expect(ip.type).to.equal('data'); chai.expect(ip.data.foo).to.equal('boo'); chai.expect(ip.data.bar).to.equal('bar'); return done(); }); }); sin1.post(new noflo.IP('data', 'foo')); sin2.post(new noflo.IP('openBracket')); sin2.post(new noflo.IP('data', 'bar')); sin2.post(new noflo.IP('closeBracket')); return sin1.post(new noflo.IP('data', 'boo')); }); it('should isolate packets with different scopes', function(done) { c = new noflo.Component({ inPorts: { foo: { datatype: 'string' }, bar: { datatype: 'string' } }, outPorts: { baz: { datatype: 'string' } }, process: function(input, output) { var bar, foo, ref; if (!input.has('foo', 'bar')) { return; } ref = input.getData('foo', 'bar'), foo = ref[0], bar = ref[1]; return output.sendDone({ baz: foo + " and " + bar }); } }); c.inPorts.foo.attach(sin1); c.inPorts.bar.attach(sin2); c.outPorts.baz.attach(sout1); sout1.once('ip', function(ip) { chai.expect(ip).to.be.an('object'); chai.expect(ip.type).to.equal('data'); chai.expect(ip.scope).to.equal('1'); chai.expect(ip.data).to.equal('Josh and Laura'); return sout1.once('ip', function(ip) { chai.expect(ip).to.be.an('object'); chai.expect(ip.type).to.equal('data'); chai.expect(ip.scope).to.equal('2'); chai.expect(ip.data).to.equal('Jane and Luke'); return done(); }); }); sin1.post(new noflo.IP('data', 'Josh', { scope: '1' })); sin2.post(new noflo.IP('data', 'Luke', { scope: '2' })); sin2.post(new noflo.IP('data', 'Laura', { scope: '1' })); return sin1.post(new noflo.IP('data', 'Jane', { scope: '2' })); }); it('should be able to change scope', function(done) { c = new noflo.Component({ inPorts: { foo: { datatype: 'string' } }, outPorts: { baz: { datatype: 'string' } }, process: function(input, output) { var foo; foo = input.getData('foo'); return output.sendDone({ baz: new noflo.IP('data', foo, { scope: 'baz' }) }); } }); c.inPorts.foo.attach(sin1); c.outPorts.baz.attach(sout1); sout1.once('ip', function(ip) { chai.expect(ip).to.be.an('object'); chai.expect(ip.type).to.equal('data'); chai.expect(ip.scope).to.equal('baz'); chai.expect(ip.data).to.equal('foo'); return done(); }); return sin1.post(new noflo.IP('data', 'foo', { scope: 'foo' })); }); it('should support integer scopes', function(done) { c = new noflo.Component({ inPorts: { foo: { datatype: 'string' }, bar: { datatype: 'string' } }, outPorts: { baz: { datatype: 'string' } }, process: function(input, output) { var bar, foo, ref; if (!input.has('foo', 'bar')) { return; } ref = input.getData('foo', 'bar'), foo = ref[0], bar = ref[1]; return output.sendDone({ baz: foo + " and " + bar }); } }); c.inPorts.foo.attach(sin1); c.inPorts.bar.attach(sin2); c.outPorts.baz.attach(sout1); sout1.once('ip', function(ip) { chai.expect(ip).to.be.an('object'); chai.expect(ip.type).to.equal('data'); chai.expect(ip.scope).to.equal(1); chai.expect(ip.data).to.equal('Josh and Laura'); return sout1.once('ip', function(ip) { chai.expect(ip).to.be.an('object'); chai.expect(ip.type).to.equal('data'); chai.expect(ip.scope).to.equal(0); chai.expect(ip.data).to.equal('Jane and Luke'); return sout1.once('ip', function(ip) { chai.expect(ip).to.be.an('object'); chai.expect(ip.type).to.equal('data'); chai.expect(ip.scope).to.be["null"]; chai.expect(ip.data).to.equal('Tom and Anna'); return done(); }); }); }); sin1.post(new noflo.IP('data', 'Tom')); sin1.post(new noflo.IP('data', 'Josh', { scope: 1 })); sin2.post(new noflo.IP('data', 'Luke', { scope: 0 })); sin2.post(new noflo.IP('data', 'Laura', { scope: 1 })); sin1.post(new noflo.IP('data', 'Jane', { scope: 0 })); return sin2.post(new noflo.IP('data', 'Anna')); }); it('should preserve order between input and output', function(done) { var ip, j, len, results, sample; c = new noflo.Component({ inPorts: { msg: { datatype: 'string' }, delay: { datatype: 'int' } }, outPorts: { out: { datatype: 'object' } }, ordered: true, process: function(input, output) { var delay, msg, ref; if (!input.has('msg', 'delay')) { return; } ref = input.getData('msg', 'delay'), msg = ref[0], delay = ref[1]; return setTimeout(function() { return output.sendDone({ out: { msg: msg, delay: delay } }); }, delay); } }); c.inPorts.msg.attach(sin1); c.inPorts.delay.attach(sin2); c.outPorts.out.attach(sout1); sample = [ { delay: 30, msg: "one" }, { delay: 0, msg: "two" }, { delay: 20, msg: "three" }, { delay: 10, msg: "four" } ]; sout1.on('ip', function(ip) { chai.expect(ip.data).to.eql(sample.shift()); if (sample.length === 0) { return done(); } }); results = []; for (j = 0, len = sample.length; j < len; j++) { ip = sample[j]; sin1.post(new noflo.IP('data', ip.msg)); results.push(sin2.post(new noflo.IP('data', ip.delay))); } return results; }); it('should ignore order between input and output', function(done) { var count, ip, j, len, results, sample; c = new noflo.Component({ inPorts: { msg: { datatype: 'string' }, delay: { datatype: 'int' } }, outPorts: { out: { datatype: 'object' } }, ordered: false, process: function(input, output) { var delay, msg, ref; if (!input.has('msg', 'delay')) { return; } ref = input.getData('msg', 'delay'), msg = ref[0], delay = ref[1]; return setTimeout(function() { return output.sendDone({ out: { msg: msg, delay: delay } }); }, delay); } }); c.inPorts.msg.attach(sin1); c.inPorts.delay.attach(sin2); c.outPorts.out.attach(sout1); sample = [ { delay: 30, msg: "one" }, { delay: 0, msg: "two" }, { delay: 20, msg: "three" }, { delay: 10, msg: "four" } ]; count = 0; sout1.on('ip', function(ip) { var src; count++; switch (count) { case 1: src = sample[1]; break; case 2: src = sample[3]; break; case 3: src = sample[2]; break; case 4: src = sample[0]; } chai.expect(ip.data).to.eql(src); if (count === 4) { return done(); } }); results = []; for (j = 0, len = sample.length; j < len; j++) { ip = sample[j]; sin1.post(new noflo.IP('data', ip.msg)); results.push(sin2.post(new noflo.IP('data', ip.delay))); } return results; }); it('should throw errors if there is no error port', function(done) { c = new noflo.Component({ inPorts: { "in": { datatype: 'string', required: true } }, process: function(input, output) { var packet; packet = input.get('in'); chai.expect(packet.data).to.equal('some-data'); chai.expect(function() { return output.done(new Error('Should fail')); }).to["throw"](Error); return done(); } }); c.inPorts["in"].attach(sin1); return sin1.post(new noflo.IP('data', 'some-data')); }); it('should throw errors if there is a non-attached error port', function(done) { c = new noflo.Component({ inPorts: { "in": { datatype: 'string', required: true } }, outPorts: { error: { datatype: 'object', required: true } }, process: function(input, output) { var packet; packet = input.get('in'); chai.expect(packet.data).to.equal('some-data'); chai.expect(function() { return output.sendDone(new Error('Should fail')); }).to["throw"](Error); return done(); } }); c.inPorts["in"].attach(sin1); return sin1.post(new noflo.IP('data', 'some-data')); }); it('should not throw errors if there is a non-required error port', function(done) { c = new noflo.Component({ inPorts: { "in": { datatype: 'string', required: true } }, outPorts: { error: { required: false } }, process: function(input, output) { var packet; packet = input.get('in'); chai.expect(packet.data).to.equal('some-data'); output.sendDone(new Error('Should not fail')); return done(); } }); c.inPorts["in"].attach(sin1); return sin1.post(new noflo.IP('data', 'some-data')); }); it('should send out string other port if there is only one port aside from error', function(done) { c = new noflo.Component({ inPorts: { "in": { datatype: 'all', required: true } }, outPorts: { out: { required: true }, error: { required: false } }, process: function(input, output) { var packet; packet = input.get('in'); return output.sendDone('some data'); } }); sout1.on('ip', function(ip) { chai.expect(ip).to.be.an('object'); chai.expect(ip.data).to.equal('some data'); return done(); }); c.inPorts["in"].attach(sin1); c.outPorts.out.attach(sout1); return sin1.post(new noflo.IP('data', 'first')); }); it('should send object out other port if there is only one port aside from error', function(done) { c = new noflo.Component({ inPorts: { "in": { datatype: 'all', required: true } }, outPorts: { out: { required: true }, error: { required: false } }, process: function(input, output) { var packet; packet = input.get('in'); return output.sendDone({ some: 'data' }); } }); sout1.on('ip', function(ip) { chai.expect(ip).to.be.an('object'); chai.expect(ip.data).to.eql({ some: 'data' }); return done(); }); c.inPorts["in"].attach(sin1); c.outPorts.out.attach(sout1); return sin1.post(new noflo.IP('data', 'first')); }); it('should throw an error if sending without specifying a port and there are multiple ports', function(done) { var f; f = function() { c = new noflo.Component({ inPorts: { "in": { datatype: 'string', required: true } }, outPorts: { out: { datatype: 'all' }, eh: { required: false } }, process: function(input, output) { return output.sendDone('test'); } }); c.inPorts["in"].attach(sin1); return sin1.post(new noflo.IP('data', 'some-data')); }; chai.expect(f).to["throw"](Error); return done(); }); it('should send errors if there is a connected error port', function(done) { c = new noflo.Component({ inPorts: { "in": { datatype: 'string', required: true } }, outPorts: { error: { datatype: 'object' } }, process: function(input, output) { var packet; packet = input.get('in'); chai.expect(packet.data).to.equal('some-data'); chai.expect(packet.scope).to.equal('some-scope'); return output.sendDone(new Error('Should fail')); } }); sout1.on('ip', function(ip) { chai.expect(ip).to.be.an('object'); chai.expect(ip.data).to.be.an.instanceOf(Error); chai.expect(ip.scope).to.equal('some-scope'); return done(); }); c.inPorts["in"].attach(sin1); c.outPorts.error.attach(sout1); return sin1.post(new noflo.IP('data', 'some-data', { scope: 'some-scope' })); }); it('should send substreams with multiple errors per activation', function(done) { var actual, count, expected; c = new noflo.Component({ inPorts: { "in": { datatype: 'string', required: true } }, outPorts: { error: { datatype: 'object' } }, process: function(input, output) { var errors, packet; packet = input.get('in'); chai.expect(packet.data).to.equal('some-data'); chai.expect(packet.scope).to.equal('some-scope'); errors = []; errors.push(new Error('One thing is invalid')); errors.push(new Error('Another thing is invalid')); return output.sendDone(errors); } }); expected = ['<', 'One thing is invalid', 'Another thing is invalid', '>']; actual = []; count = 0; sout1.on('ip', function(ip) { count++; chai.expect(ip).to.be.an('object'); chai.expect(ip.scope).to.equal('some-scope'); if (ip.type === 'openBracket') { actual.push('<'); } if (ip.type === 'closeBracket') { actual.push('>'); } if (ip.type === 'data') { chai.expect(ip.data).to.be.an.instanceOf(Error); actual.push(ip.data.message); } if (count === 4) { chai.expect(actual).to.eql(expected); return done(); } }); c.inPorts["in"].attach(sin1); c.outPorts.error.attach(sout1); return sin1.post(new noflo.IP('data', 'some-data', { scope: 'some-scope' })); }); it('should forward brackets for map-style components', function(done) { var actual, count, data, j, len, results, source; c = new noflo.Component({ inPorts: { "in": { datatype: 'string' } }, outPorts: { out: { datatype: 'string' }, error: { datatype: 'object' } }, process: function(input, output) { var str; str = input.getData(); if (typeof str !== 'string') { return output.sendDone(new Error('Input is not string')); } return output.pass(str.toUpperCase()); } }); c.inPorts["in"].attach(sin1); c.outPorts.out.attach(sout1); c.outPorts.error.attach(sout2); source = ['<', 'foo', 'bar', '>']; actual = []; count = 0; sout1.on('ip', function(ip) { var data; data = (function() { switch (ip.type) { case 'openBracket': return '<'; case 'closeBracket': return '>'; default: return ip.data; } })(); chai.expect(data).to.equal(source[count].toUpperCase()); count++; if (count === 4) { return done(); } }); sout2.on('ip', function(ip) { if (ip.type !== 'data') { return; } console.log('Unexpected error', ip); return done(ip.data); }); results = []; for (j = 0, len = source.length; j < len; j++) { data = source[j]; switch (data) { case '<': results.push(sin1.post(new noflo.IP('openBracket'))); break; case '>': results.push(sin1.post(new noflo.IP('closeBracket'))); break; default: results.push(sin1.post(new noflo.IP('data', data))); } } return results; }); it('should forward brackets for map-style components with addressable outport', function(done) { var expected, received, sent; sent = false; c = new noflo.Component({ inPorts: { "in": { datatype: 'string' } }, outPorts: { out: { datatype: 'string', addressable: true } }, process: function(input, output) { var idx, string; if (!input.hasData()) { return; } string = input.getData(); idx = sent ? 0 : 1; sent = true; return output.sendDone(new noflo.IP('data', string, { index: idx })); } }); c.inPorts["in"].attach(sin1); c.outPorts.out.attach(sout1, 1); c.outPorts.out.attach(sout2, 0); expected = ['1 < a', '1 < foo', '1 DATA first', '1 > foo', '0 < a', '0 < bar', '0 DATA second', '0 > bar', '0 > a', '1 > a']; received = []; sout1.on('ip', function(ip) { switch (ip.type) { case 'openBracket': received.push("1 < " + ip.data); break; case 'data': received.push("1 DATA " + ip.data); break; case 'closeBracket': received.push("1 > " + ip.data); } if (received.length !== expected.length) { return; } chai.expect(received).to.eql(expected); return done(); }); sout2.on('ip', function(ip) { switch (ip.type) { case 'openBracket': received.push("0 < " + ip.data); break; case 'data': received.push("0 DATA " + ip.data); break; case 'closeBracket': received.push("0 > " + ip.data); } if (received.length !== expected.length) { return; } chai.expect(received).to.eql(expected); return done(); }); sin1.post(new noflo.IP('openBracket', 'a')); sin1.post(new noflo.IP('openBracket', 'foo')); sin1.post(new noflo.IP('data', 'first')); sin1.post(new noflo.IP('closeBracket', 'foo')); sin1.post(new noflo.IP('openBracket', 'bar')); sin1.post(new noflo.IP('data', 'second')); sin1.post(new noflo.IP('closeBracket', 'bar')); return sin1.post(new noflo.IP('closeBracket', 'a')); }); it('should forward brackets for async map-style components with addressable outport', function(done) { var expected, received, sent; sent = false; c = new noflo.Component({ inPorts: { "in": { datatype: 'string' } }, outPorts: { out: { datatype: 'string', addressable: true } }, process: function(input, output) { var idx, string; if (!input.hasData()) { return; } string = input.getData(); idx = sent ? 0 : 1; sent = true; return setTimeout(function() { return output.sendDone(new noflo.IP('data', string, { index: idx })); }, 1); } }); c.inPorts["in"].attach(sin1); c.outPorts.out.attach(sout1, 1); c.outPorts.out.attach(sout2, 0); expected = ['1 < a', '1 < foo', '1 DATA first', '1 > foo', '0 < a', '0 < bar', '0 DATA second', '0 > bar', '0 > a', '1 > a']; received = []; sout1.on('ip', function(ip) { switch (ip.type) { case 'openBracket': received.push("1 < " + ip.data); break; case 'data': received.push("1 DATA " + ip.data); break; case 'close