noflo
Version:
Flow-Based Programming environment for JavaScript
1,636 lines (1,469 loc) • 68.4 kB
text/coffeescript
if typeof process isnt 'undefined' and process.execPath and process.execPath.match /node|iojs/
chai = require 'chai' unless chai
noflo = require '../src/lib/NoFlo.coffee'
else
noflo = require 'noflo'
describe 'Component', ->
describe 'with required ports', ->
it 'should throw an error upon sending packet to an unattached required port', ->
s2 = new noflo.internalSocket.InternalSocket
c = new noflo.Component
outPorts:
required_port:
required: true
optional_port: {}
c.outPorts.optional_port.attach s2
chai.expect(-> c.outPorts.required_port.send('foo')).to.throw()
it 'should be cool with an attached port', ->
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 = ->
s1.send 'some-more-data'
s2.send 'some-data'
chai.expect(f).to.not.throw()
describe 'with component creation shorthand', ->
it 'should make component creation easy', (done) ->
c = new noflo.Component
inPorts:
in:
datatype: 'string'
required: true
process: (event, packet, component) ->
return unless event is 'data'
chai.expect(packet).to.equal 'some-data'
chai.expect(component).to.equal c
just_processor: (event, packet, component) ->
return unless event is 'data'
chai.expect(packet).to.equal 'some-data'
chai.expect(component).to.equal c
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'
s2.send 'some-data'
it 'should throw errors if there is no error port', (done) ->
c = new noflo.Component
inPorts:
in:
datatype: 'string'
required: true
process: (event, packet, component) ->
return unless event is 'data'
chai.expect(packet).to.equal 'some-data'
chai.expect(component).to.equal c
chai.expect(-> c.error(new Error)).to.throw Error
done()
s1 = new noflo.internalSocket.InternalSocket
c.inPorts.in.attach s1
c.inPorts.in.nodeInstance = c
s1.send 'some-data'
it 'should throw errors if there is a non-attached error port', (done) ->
c = new noflo.Component
inPorts:
in:
datatype: 'string'
required: true
process: (event, packet, component) ->
return unless event is 'data'
chai.expect(packet).to.equal 'some-data'
chai.expect(component).to.equal c
chai.expect(-> c.error(new Error)).to.throw Error
done()
outPorts:
error:
datatype: 'object'
required: true
s1 = new noflo.internalSocket.InternalSocket
c.inPorts.in.attach s1
c.inPorts.in.nodeInstance = c
s1.send 'some-data'
it 'should not throw errors if there is a non-required error port', (done) ->
c = new noflo.Component
inPorts:
in:
datatype: 'string'
required: true
process: (event, packet, component) ->
return unless event is 'data'
chai.expect(packet).to.equal 'some-data'
chai.expect(component).to.equal c
c.error new Error
done()
outPorts:
error:
required: no
s1 = new noflo.internalSocket.InternalSocket
c.inPorts.in.attach s1
c.inPorts.in.nodeInstance = c
s1.send 'some-data'
it 'should send errors if there is a connected error port', (done) ->
grps = []
c = new noflo.Component
inPorts:
in:
datatype: 'string'
required: true
process: (event, packet, component) ->
grps.push packet if event is 'begingroup'
return unless event is 'data'
chai.expect(packet).to.equal 'some-data'
chai.expect(component).to.equal c
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', (grp) ->
chai.expect(grp).to.equal groups.shift()
s2.on 'data', (err) ->
chai.expect(err).to.be.an.instanceOf Error
chai.expect(groups.length).to.equal 0
done()
c.inPorts.in.attach s1
c.outPorts.error.attach s2
c.inPorts.in.nodeInstance = c
s1.beginGroup 'foo'
s1.beginGroup 'bar'
s1.send 'some-data'
describe 'defining ports with invalid names', ->
it 'should throw an error with uppercase letters in inport', ->
shorthand = ->
c = new noflo.Component
inPorts:
fooPort: {}
chai.expect(shorthand).to.throw()
it 'should throw an error with uppercase letters in outport', ->
shorthand = ->
c = new noflo.Component
outPorts:
BarPort: {}
chai.expect(shorthand).to.throw()
it 'should throw an error with special characters in inport', ->
shorthand = ->
c = new noflo.Component
inPorts:
'$%^&*a': {}
chai.expect(shorthand).to.throw()
describe 'starting a component', ->
it 'should flag the component as started', (done) ->
c = new noflo.Component
inPorts:
in:
datatype: 'string'
required: true
i = new noflo.internalSocket.InternalSocket
c.inPorts.in.attach(i)
c.start (err) ->
return done err if err
chai.expect(c.started).to.equal(true)
chai.expect(c.isStarted()).to.equal(true)
done()
describe 'shutting down a component', ->
it 'should flag the component as not started', (done) ->
c = new noflo.Component
inPorts:
in:
datatype: 'string'
required: true
i = new noflo.internalSocket.InternalSocket
c.inPorts.in.attach(i)
c.start (err) ->
return done err if err
chai.expect(c.isStarted()).to.equal(true)
c.shutdown (err) ->
return done err if err
chai.expect(c.started).to.equal(false)
chai.expect(c.isStarted()).to.equal(false)
done()
describe 'with object-based IPs', ->
it 'should speak IP objects', (done) ->
c = new noflo.Component
inPorts:
in:
datatype: 'string'
handle: (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'
c.outPorts.out.data 'bar',
groups: ['foo']
outPorts:
out:
datatype: 'string'
s1 = new noflo.internalSocket.InternalSocket
s2 = new noflo.internalSocket.InternalSocket
s2.on 'ip', (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'
done()
c.inPorts.in.attach s1
c.outPorts.out.attach s2
s1.post new noflo.IP 'data', 'some-data',
groups: ['foo']
it 'should support substreams', (done) ->
c = new noflo.Component
inPorts:
tags:
datatype: 'string'
handle: (ip) ->
chai.expect(ip).to.be.an 'object'
switch ip.type
when 'openBracket'
c.str += "<#{ip.data}>"
c.level++
when 'data'
c.str += ip.data
when 'closeBracket'
c.str += "</#{ip.data}>"
c.level--
if c.level is 0
c.outPorts.html.data c.str
c.str = ''
outPorts:
html:
datatype: 'string'
c.str = ''
c.level = 0
d = new noflo.Component
inPorts:
bang:
datatype: 'bang'
handle: (ip) ->
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', (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>'
done()
d.inPorts.bang.attach s1
d.outPorts.tags.attach s2
c.inPorts.tags.attach s2
c.outPorts.html.attach s3
s1.post new noflo.IP 'data', 'start'
describe 'with process function', ->
c = null
sin1 = null
sin2 = null
sin3 = null
sout1 = null
sout2 = null
beforeEach (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
done()
it 'should trigger on IPs', (done) ->
hadIPs = []
c = new noflo.Component
inPorts:
foo: datatype: 'string'
bar: datatype: 'string'
outPorts:
baz: datatype: 'boolean'
process: (input, output) ->
hadIPs = []
hadIPs.push 'foo' if input.has 'foo'
hadIPs.push 'bar' if input.has 'bar'
output.sendDone baz: true
c.inPorts.foo.attach sin1
c.inPorts.bar.attach sin2
c.outPorts.baz.attach sout1
count = 0
sout1.on 'ip', (ip) ->
count++
if count is 1
chai.expect(hadIPs).to.eql ['foo']
if count is 2
chai.expect(hadIPs).to.eql ['foo', 'bar']
done()
sin1.post new noflo.IP 'data', 'first'
sin2.post new noflo.IP 'data', 'second'
it 'should trigger on IPs to addressable ports', (done) ->
receivedIndexes = []
c = new noflo.Component
inPorts:
foo:
datatype: 'string'
addressable: true
outPorts:
baz:
datatype: 'boolean'
process: (input, output) ->
# See what inbound connection indexes have data
indexesWithData = input.attached('foo').filter (idx) ->
input.hasData ['foo', idx]
return unless indexesWithData.length
# Read from the first of them
indexToUse = indexesWithData[0]
packet = input.get ['foo', indexToUse]
receivedIndexes.push
idx: indexToUse
payload: packet.data
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', (ip) ->
count++
if count is 1
chai.expect(receivedIndexes).to.eql [
idx: 1
payload: 'first'
]
if count is 2
chai.expect(receivedIndexes).to.eql [
idx: 1
payload: 'first'
,
idx: 0
payload: 'second'
]
done()
sin1.post new noflo.IP 'data', 'first'
sin2.post new noflo.IP 'data', 'second'
it 'should be able to send IPs to addressable connections', (done) ->
expected = [
data: 'first'
index: 1
,
data: 'second'
index: 0
]
c = new noflo.Component
inPorts:
foo:
datatype: 'string'
outPorts:
baz:
datatype: 'boolean'
addressable: true
process: (input, output) ->
return unless input.has 'foo'
packet = input.get 'foo'
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', (ip) ->
exp = expected.shift()
received =
data: ip.data
index: 1
chai.expect(received).to.eql exp
done() unless expected.length
sout2.on 'ip', (ip) ->
exp = expected.shift()
received =
data: ip.data
index: 0
chai.expect(received).to.eql exp
done() unless expected.length
sin1.post new noflo.IP 'data', 'first'
sin1.post new noflo.IP 'data', 'second'
it 'trying to send to addressable port without providing index should fail', (done) ->
c = new noflo.Component
inPorts:
foo:
datatype: 'string'
outPorts:
baz:
datatype: 'boolean'
addressable: true
process: (input, output) ->
return unless input.hasData 'foo'
packet = input.get 'foo'
noIndex = new noflo.IP 'data', packet.data
chai.expect(-> output.sendDone noIndex).to.throw Error
done()
c.inPorts.foo.attach sin1
c.outPorts.baz.attach sout1, 1
c.outPorts.baz.attach sout2, 0
sout1.on 'ip', (ip) ->
sout2.on 'ip', (ip) ->
sin1.post new noflo.IP 'data', 'first'
it 'should not be triggered by non-triggering ports', (done) ->
triggered = []
c = new noflo.Component
inPorts:
foo:
datatype: 'string'
triggering: false
bar: datatype: 'string'
outPorts:
baz: datatype: 'boolean'
process: (input, output) ->
triggered.push input.port.name
output.sendDone baz: true
c.inPorts.foo.attach sin1
c.inPorts.bar.attach sin2
c.outPorts.baz.attach sout1
count = 0
sout1.on 'ip', (ip) ->
count++
if count is 1
chai.expect(triggered).to.eql ['bar']
if count is 2
chai.expect(triggered).to.eql ['bar', 'bar']
done()
sin1.post new noflo.IP 'data', 'first'
sin2.post new noflo.IP 'data', 'second'
sin1.post new noflo.IP 'data', 'first'
sin2.post new noflo.IP 'data', 'second'
it 'should fetch undefined for premature data', (done) ->
c = new noflo.Component
inPorts:
foo:
datatype: 'string'
bar:
datatype: 'boolean'
triggering: false
control: true
baz:
datatype: 'string'
triggering: false
control: true
process: (input, output) ->
return unless input.has 'foo'
[foo, bar, baz] = input.getData 'foo', 'bar', 'baz'
chai.expect(foo).to.be.a 'string'
chai.expect(bar).to.be.undefined
chai.expect(baz).to.be.undefined
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
sin3.post new noflo.IP 'data', 'first'
it 'should receive and send complete noflo.IP objects', (done) ->
c = new noflo.Component
inPorts:
foo: datatype: 'string'
bar: datatype: 'string'
outPorts:
baz: datatype: 'object'
process: (input, output) ->
return unless input.has 'foo', 'bar'
[foo, bar] = input.get 'foo', 'bar'
baz =
foo: foo.data
bar: bar.data
groups: foo.groups
type: bar.type
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', (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']
done()
sin1.post new noflo.IP 'data', 'foo',
groups: ['foo']
sin2.post new noflo.IP 'data', 'bar',
groups: ['bar']
it 'should receive and send just IP data if wanted', (done) ->
c = new noflo.Component
inPorts:
foo: datatype: 'string'
bar: datatype: 'string'
outPorts:
baz: datatype: 'object'
process: (input, output) ->
return unless input.has 'foo', 'bar'
[foo, bar] = input.getData 'foo', 'bar'
baz =
foo: foo
bar: bar
output.sendDone
baz: baz
c.inPorts.foo.attach sin1
c.inPorts.bar.attach sin2
c.outPorts.baz.attach sout1
sout1.once 'ip', (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'
done()
sin1.post new noflo.IP 'data', 'foo',
groups: ['foo']
sin2.post new noflo.IP 'data', 'bar',
groups: ['bar']
it 'should receive IPs and be able to selectively find them', (done) ->
called = 0
c = new noflo.Component
inPorts:
foo: datatype: 'string'
bar: datatype: 'string'
outPorts:
baz: datatype: 'object'
process: (input, output) ->
validate = (ip) ->
called++
ip.type is 'data' and ip.data is 'hello'
unless input.has 'foo', 'bar', validate
return
foo = input.get 'foo'
while foo?.type isnt 'data'
foo = input.get 'foo'
bar = input.getData 'bar'
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', (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
done()
sin1.post new noflo.IP 'openBracket', 'a'
sin1.post new noflo.IP 'data', 'hello',
sin1.post new noflo.IP 'closeBracket', 'a'
shouldHaveSent = true
sin2.post new noflo.IP 'data', 'hello'
it 'should keep last value for controls', (done) ->
c = new noflo.Component
inPorts:
foo: datatype: 'string'
bar:
datatype: 'string'
control: true
outPorts:
baz: datatype: 'object'
process: (input, output) ->
return unless input.has 'foo', 'bar'
[foo, bar] = input.getData 'foo', 'bar'
baz =
foo: foo
bar: bar
output.sendDone
baz: baz
c.inPorts.foo.attach sin1
c.inPorts.bar.attach sin2
c.outPorts.baz.attach sout1
sout1.once 'ip', (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'
sout1.once 'ip', (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'
done()
sin1.post new noflo.IP 'data', 'foo'
sin2.post new noflo.IP 'data', 'bar'
sin1.post new noflo.IP 'data', 'boo'
it 'should keep last data-typed IP packet for controls', (done) ->
c = new noflo.Component
inPorts:
foo: datatype: 'string'
bar:
datatype: 'string'
control: true
outPorts:
baz: datatype: 'object'
process: (input, output) ->
return unless input.has 'foo', 'bar'
[foo, bar] = input.getData 'foo', 'bar'
baz =
foo: foo
bar: bar
output.sendDone
baz: baz
c.inPorts.foo.attach sin1
c.inPorts.bar.attach sin2
c.outPorts.baz.attach sout1
sout1.once 'ip', (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'
sout1.once 'ip', (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'
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'
sin1.post new noflo.IP 'data', 'boo'
it 'should isolate packets with different scopes', (done) ->
c = new noflo.Component
inPorts:
foo: datatype: 'string'
bar: datatype: 'string'
outPorts:
baz: datatype: 'string'
process: (input, output) ->
return unless input.has 'foo', 'bar'
[foo, bar] = input.getData 'foo', 'bar'
output.sendDone
baz: "#{foo} and #{bar}"
c.inPorts.foo.attach sin1
c.inPorts.bar.attach sin2
c.outPorts.baz.attach sout1
sout1.once 'ip', (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'
sout1.once 'ip', (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'
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'
sin1.post new noflo.IP 'data', 'Jane', scope: '2'
it 'should be able to change scope', (done) ->
c = new noflo.Component
inPorts:
foo: datatype: 'string'
outPorts:
baz: datatype: 'string'
process: (input, output) ->
foo = input.getData 'foo'
output.sendDone
baz: new noflo.IP 'data', foo, scope: 'baz'
c.inPorts.foo.attach sin1
c.outPorts.baz.attach sout1
sout1.once 'ip', (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'
done()
sin1.post new noflo.IP 'data', 'foo', scope: 'foo'
it 'should support integer scopes', (done) ->
c = new noflo.Component
inPorts:
foo: datatype: 'string'
bar: datatype: 'string'
outPorts:
baz: datatype: 'string'
process: (input, output) ->
return unless input.has 'foo', 'bar'
[foo, bar] = input.getData 'foo', 'bar'
output.sendDone
baz: "#{foo} and #{bar}"
c.inPorts.foo.attach sin1
c.inPorts.bar.attach sin2
c.outPorts.baz.attach sout1
sout1.once 'ip', (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'
sout1.once 'ip', (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'
sout1.once 'ip', (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'
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
sin2.post new noflo.IP 'data', 'Anna'
it 'should preserve order between input and output', (done) ->
c = new noflo.Component
inPorts:
msg: datatype: 'string'
delay: datatype: 'int'
outPorts:
out: datatype: 'object'
ordered: true
process: (input, output) ->
return unless input.has 'msg', 'delay'
[msg, delay] = input.getData 'msg', 'delay'
setTimeout ->
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', (ip) ->
chai.expect(ip.data).to.eql sample.shift()
done() if sample.length is 0
for ip in sample
sin1.post new noflo.IP 'data', ip.msg
sin2.post new noflo.IP 'data', ip.delay
it 'should ignore order between input and output', (done) ->
c = new noflo.Component
inPorts:
msg: datatype: 'string'
delay: datatype: 'int'
outPorts:
out: datatype: 'object'
ordered: false
process: (input, output) ->
return unless input.has 'msg', 'delay'
[msg, delay] = input.getData 'msg', 'delay'
setTimeout ->
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', (ip) ->
count++
switch count
when 1 then src = sample[1]
when 2 then src = sample[3]
when 3 then src = sample[2]
when 4 then src = sample[0]
chai.expect(ip.data).to.eql src
done() if count is 4
for ip in sample
sin1.post new noflo.IP 'data', ip.msg
sin2.post new noflo.IP 'data', ip.delay
it 'should throw errors if there is no error port', (done) ->
c = new noflo.Component
inPorts:
in:
datatype: 'string'
required: true
process: (input, output) ->
packet = input.get 'in'
chai.expect(packet.data).to.equal 'some-data'
chai.expect(-> output.done new Error 'Should fail').to.throw Error
done()
c.inPorts.in.attach sin1
sin1.post new noflo.IP 'data', 'some-data'
it 'should throw errors if there is a non-attached error port', (done) ->
c = new noflo.Component
inPorts:
in:
datatype: 'string'
required: true
outPorts:
error:
datatype: 'object'
required: true
process: (input, output) ->
packet = input.get 'in'
chai.expect(packet.data).to.equal 'some-data'
chai.expect(-> output.sendDone new Error 'Should fail').to.throw Error
done()
c.inPorts.in.attach sin1
sin1.post new noflo.IP 'data', 'some-data'
it 'should not throw errors if there is a non-required error port', (done) ->
c = new noflo.Component
inPorts:
in:
datatype: 'string'
required: true
outPorts:
error:
required: no
process: (input, output) ->
packet = input.get 'in'
chai.expect(packet.data).to.equal 'some-data'
output.sendDone new Error 'Should not fail'
done()
c.inPorts.in.attach sin1
sin1.post new noflo.IP 'data', 'some-data'
it 'should send out string other port if there is only one port aside from error', (done) ->
c = new noflo.Component
inPorts:
in:
datatype: 'all'
required: true
outPorts:
out:
required: true
error:
required: false
process: (input, output) ->
packet = input.get 'in'
output.sendDone 'some data'
sout1.on 'ip', (ip) ->
chai.expect(ip).to.be.an 'object'
chai.expect(ip.data).to.equal 'some data'
done()
c.inPorts.in.attach sin1
c.outPorts.out.attach sout1
sin1.post new noflo.IP 'data', 'first'
it 'should send object out other port if there is only one port aside from error', (done) ->
c = new noflo.Component
inPorts:
in:
datatype: 'all'
required: true
outPorts:
out:
required: true
error:
required: false
process: (input, output) ->
packet = input.get 'in'
output.sendDone some: 'data'
sout1.on 'ip', (ip) ->
chai.expect(ip).to.be.an 'object'
chai.expect(ip.data).to.eql some: 'data'
done()
c.inPorts.in.attach sin1
c.outPorts.out.attach sout1
sin1.post new noflo.IP 'data', 'first'
it 'should throw an error if sending without specifying a port and there are multiple ports', (done) ->
f = ->
c = new noflo.Component
inPorts:
in:
datatype: 'string'
required: true
outPorts:
out:
datatype: 'all'
eh:
required: no
process: (input, output) ->
output.sendDone 'test'
c.inPorts.in.attach sin1
sin1.post new noflo.IP 'data', 'some-data'
chai.expect(f).to.throw Error
done()
it 'should send errors if there is a connected error port', (done) ->
c = new noflo.Component
inPorts:
in:
datatype: 'string'
required: true
outPorts:
error:
datatype: 'object'
process: (input, output) ->
packet = input.get 'in'
chai.expect(packet.data).to.equal 'some-data'
chai.expect(packet.scope).to.equal 'some-scope'
output.sendDone new Error 'Should fail'
sout1.on 'ip', (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'
done()
c.inPorts.in.attach sin1
c.outPorts.error.attach sout1
sin1.post new noflo.IP 'data', 'some-data',
scope: 'some-scope'
it 'should send substreams with multiple errors per activation', (done) ->
c = new noflo.Component
inPorts:
in:
datatype: 'string'
required: true
outPorts:
error:
datatype: 'object'
process: (input, output) ->
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'
output.sendDone errors
expected = [
'<'
'One thing is invalid'
'Another thing is invalid'
'>'
]
actual = []
count = 0
sout1.on 'ip', (ip) ->
count++
chai.expect(ip).to.be.an 'object'
chai.expect(ip.scope).to.equal 'some-scope'
actual.push '<' if ip.type is 'openBracket'
actual.push '>' if ip.type is 'closeBracket'
if ip.type is 'data'
chai.expect(ip.data).to.be.an.instanceOf Error
actual.push ip.data.message
if count is 4
chai.expect(actual).to.eql expected
done()
c.inPorts.in.attach sin1
c.outPorts.error.attach sout1
sin1.post new noflo.IP 'data', 'some-data',
scope: 'some-scope'
it 'should forward brackets for map-style components', (done) ->
c = new noflo.Component
inPorts:
in:
datatype: 'string'
outPorts:
out:
datatype: 'string'
error:
datatype: 'object'
process: (input, output) ->
str = input.getData()
if typeof str isnt 'string'
return output.sendDone new Error 'Input is not string'
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', (ip) ->
data = switch ip.type
when 'openBracket' then '<'
when 'closeBracket' then '>'
else ip.data
chai.expect(data).to.equal source[count].toUpperCase()
count++
done() if count is 4
sout2.on 'ip', (ip) ->
return if ip.type isnt 'data'
console.log 'Unexpected error', ip
done ip.data
for data in source
switch data
when '<' then sin1.post new noflo.IP 'openBracket'
when '>' then sin1.post new noflo.IP 'closeBracket'
else sin1.post new noflo.IP 'data', data
it 'should forward brackets for map-style components with addressable outport', (done) ->
sent = false
c = new noflo.Component
inPorts:
in:
datatype: 'string'
outPorts:
out:
datatype: 'string'
addressable: true
process: (input, output) ->
return unless input.hasData()
string = input.getData()
idx = if sent then 0 else 1
sent = true
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', (ip) ->
switch ip.type
when 'openBracket'
received.push "1 < #{ip.data}"
when 'data'
received.push "1 DATA #{ip.data}"
when 'closeBracket'
received.push "1 > #{ip.data}"
return unless received.length is expected.length
chai.expect(received).to.eql expected
done()
sout2.on 'ip', (ip) ->
switch ip.type
when 'openBracket'
received.push "0 < #{ip.data}"
when 'data'
received.push "0 DATA #{ip.data}"
when 'closeBracket'
received.push "0 > #{ip.data}"
return unless received.length is expected.length
chai.expect(received).to.eql expected
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'
sin1.post new noflo.IP 'closeBracket', 'a'
it 'should forward brackets for async map-style components with addressable outport', (done) ->
sent = false
c = new noflo.Component
inPorts:
in:
datatype: 'string'
outPorts:
out:
datatype: 'string'
addressable: true
process: (input, output) ->
return unless input.hasData()
string = input.getData()
idx = if sent then 0 else 1
sent = true
setTimeout ->
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', (ip) ->
switch ip.type
when 'openBracket'
received.push "1 < #{ip.data}"
when 'data'
received.push "1 DATA #{ip.data}"
when 'closeBracket'
received.push "1 > #{ip.data}"
return unless received.length is expected.length
chai.expect(received).to.eql expected
done()
sout2.on 'ip', (ip) ->
switch ip.type
when 'openBracket'
received.push "0 < #{ip.data}"
when 'data'
received.push "0 DATA #{ip.data}"
when 'closeBracket'
received.push "0 > #{ip.data}"
return unless received.length is expected.length
chai.expect(received).to.eql expected
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'
sin1.post new noflo.IP 'closeBracket', 'a'
it 'should forward brackets for map-style components with addressable in/outports', (done) ->
c = new noflo.Component
inPorts:
in:
datatype: 'string'
addressable: true
outPorts:
out:
datatype: 'string'
addressable: true
process: (input, output) ->
indexesWithData = []
for idx in input.attached()
indexesWithData.push idx if input.hasData ['in', idx]
return unless indexesWithData.length
indexToUse = indexesWithData[0]
data = input.get ['in', indexToUse]
ip = new noflo.IP 'data', data.data
ip.index = indexToUse
output.sendDone ip
c.inPorts.in.attach sin1, 1
c.inPorts.in.attach sin2, 0
c.outPorts.out.attach sout1, 1
c.outPorts.out.attach sout2, 0
expected = [
'1 < a'
'1 < foo'
'1 DATA first'
'1 > foo'
'0 < bar'
'0 DATA second'
'0 > bar'
'1 > a'
]
received = []
sout1.on 'ip', (ip) ->
switch ip.type
when 'openBracket'
received.push "1 < #{ip.data}"
when 'data'
received.push "1 DATA #{ip.data}"
when 'closeBracket'
received.push "1 > #{ip.data}"
return unless received.length is expected.length
chai.expect(received).to.eql expected
done()
sout2.on 'ip', (ip) ->
switch ip.type
when 'openBracket'
received.push "0 < #{ip.data}"
when 'data'
received.push "0 DATA #{ip.data}"
when 'closeBracket'
received.push "0 > #{ip.data}"
return unless received.length is expected.length
return unless received.length is expected.length
chai.expect(received).to.eql expected
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'
sin2.post new noflo.IP 'openBracket', 'bar'
sin2.post new noflo.IP 'data', 'second'
sin2.post new noflo.IP 'closeBracket', 'bar'
sin1.post new noflo.IP 'closeBracket', 'a'
it 'should forward brackets for async map-style components with addressable in/outports', (done) ->
c = new noflo.Component
inPorts:
in:
datatype: 'string'
addressable: true
outPorts:
out:
datatype: 'string'
addressable: true
process: (input, output) ->
indexesWithData = []
for idx in input.attached()
indexesWithData.push idx if input.hasData ['in', idx]
return unless indexesWithData.length
data = input.get ['in', indexesWithData[0]]
setTimeout ->
ip = new noflo.IP 'data', data.data
ip.index = data.index
output.sendDone ip
, 1
c.inPorts.in.attach sin1, 1
c.inPorts.in.attach sin2, 0
c.outPorts.out.attach sout1, 1
c.outPorts.out.attach sout2, 0
expected = [
'1 < a'
'1 < foo'
'1 DATA first'
'1 > foo'
'0 < bar'
'0 DATA second'
'0 > bar'
'1 > a'
]
received = []
sout1.on 'ip', (ip) ->
switch ip.type
when 'openBracket'
received.push "1 < #{ip.data}"
when 'data'
received.push "1 DATA #{ip.data}"
when 'closeBracket'
received.push "1 > #{ip.data}"
return unless received.length is expected.length
chai.expect(received).to.eql expected
done()
sout2.on 'ip', (ip) ->
switch ip.type
when 'openBracket'
received.push "0 < #{ip.data}"
when 'data'
received.push "0 DATA #{ip.data}"
when 'closeBracket'
received.push "0 > #{ip.data}"
return unless received.length is expected.length
chai.expect(received).to.eql expected
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'
sin2.post new noflo.IP 'openBracket', 'bar'
sin2.post new noflo.IP 'data', 'second'
sin2.post new noflo.IP 'closeBracket', 'bar'
sin1.post new noflo.IP 'closeBracket', 'a'
it 'should forward brackets to error port in async components', (done) ->
c = new noflo.Component
inPorts:
in:
datatype: 'string'
outPorts:
out:
datatype: 'string'
error:
datatype: 'object'
process: (input, output) ->
str = input.getData()
setTimeout ->
if typeof str isnt 'string'
return output.sendDone new Error 'Input is not string'
output.pass str.toUpperCase()
, 10
c.inPorts.in.attach sin1
c.outPorts.out.attach sout1
c.outPorts.error.attach sout2
sout1.on 'ip', (ip) ->
# done new Error "Unexpected IP: #{ip.type} #{ip.data}"
count = 0
sout2.on 'ip', (ip) ->
count++
switch count
when 1
chai.expect(ip.type).to.equal 'openBracket'
when 2
chai.expect(ip.type).to.equal 'data'
chai.expect(ip.data).to.be.an 'error'
when 3
chai.expect(ip.type).to.equal 'closeBracket'
done() if count is 3
sin1.post new noflo.IP 'openBracket', 'foo'
sin1.post new noflo.IP 'data', { bar: 'baz' }
sin1.post new noflo.IP 'closeBracket', 'foo'
it 'should not forward brackets if error port is not connected', (done) ->
c = new noflo.Component
inPorts:
in:
datatype: 'string'
outPorts:
out:
datatype: 'string'
required: true
error:
datatype: 'object'
required: true
process: (input, output) ->
str = input.getData()
setTimeout ->
if typeof str isnt 'string'
return output.sendDone new Error 'Input is not string'
output.pass str.toUpperCase()
, 10
c.inPorts.in.attach sin1
c.outPorts.out.attach sout1
# c.outPorts.error.attach sout2
sout1.on 'ip', (ip) ->
done() if ip.type is 'closeBracket'
sout2.on 'ip', (ip) ->
done new Error "Unexpected error IP: #{ip.type} #{ip.data}"
chai.expect ->
sin1.post new noflo.IP 'openBracket', 'foo'
sin1.post new noflo.IP 'data', 'bar'
sin1.post new noflo.IP 'closeBracket', 'foo'
.to.not.throw()
it 'should support custom bracket forwarding mappings with auto-ordering', (done) ->
c = new noflo.Component
inPorts:
msg:
datatype: 'string'
delay:
datatype: 'int'
outPorts:
out:
datatype: 'string'
error:
datatype: 'object'
forwardBrackets:
msg: ['out', 'error']
delay: ['error']
process: (input, output) ->
return unless input.hasData 'msg', 'delay'
[msg, delay] = input.getData 'msg', 'delay'
if delay < 0
return output.sendDone new Error 'Delay is negative'
setTimeout ->
output.sendDone
out: { msg: msg, delay: delay }
, delay
c.inPorts.msg.attach sin1
c.inPorts.delay.attach sin2
c.outPorts.out.attach sout1
c.outPorts.error.attach sout2
sample = [
{ delay: 30, msg: "one" }
{ delay: 0, msg: "two" }
{ delay: 20, msg: "three" }
{ delay: 10, msg: "four" }
{ delay: -40, msg: 'five'}
]
count = 0
errCount = 0
sout1.on 'ip', (ip) ->
src = null
switch count
when 0
chai.expect(ip.type).to.equal 'openBracket'
chai.expect(ip.data).to.equal 'msg'
when 5
chai.expect(ip.type).to.equal 'closeBracket'
chai.expect(ip.data).to.equal 'msg'
else src = sample[count - 1]
chai.expect(ip.data).to.eql src if src
count++
# done() if count is 6
sout2.on 'ip', (ip) ->
switch errCount
when 0
chai.expect(ip.type).to.equal 'openBracket'
chai.expect(ip.data).to.equal 'msg'
when 1
chai.expect(ip.type).to.equal 'openBracket'
chai.expect(ip.data).to.equal 'delay'
when 2
chai.expect(ip.type).to.equal 'data'
chai.expect(ip.data).to.be.an.error
when 3
chai.expect(ip.type).to.equal 'closeBracket'
chai.expect(ip.data).to.equal 'delay'
when 4
chai.expect(ip.type).to.equal 'closeBracket'
chai.expect(ip.data).to.equal 'msg'
errCount++
done() if errCount is 5
sin1.post new noflo.IP 'openBracket', 'msg'
sin2.post new noflo.IP 'openBracket', 'delay'
for ip in sample
sin1.post new noflo.IP 'data', ip.msg
sin2.post new noflo.IP 'data', ip.delay
sin2.post new noflo.IP 'closeBracket', 'delay'
sin1.post new noflo.IP 'closeBracket', 'msg'
it 'should de-duplicate brackets when asynchronously forwarding from multiple inports', (done) ->
c = new noflo.Component
inPorts:
in1:
datatype: 'string'
in2:
datatype: 'string'
outPorts:
out:
datatype: 'string'
error:
datatype: 'object'
forwardBrackets:
in1: ['out', 'error']
in2: ['out', 'error']
process: (input, output) ->
return unless input.hasData 'in1', 'in2'
[one, two] = input.getData 'in1', 'in2'
setTimeout ->
output.sendDone
out: "#{one}:#{two}"
, 1
c.inPorts.in1.attach sin1
c.inPorts.in2.attach sin2
c.outPorts.out.attach sout1
c.outPorts.error.attach sout2
# Fail early on errors
sout2.on 'ip', (ip) ->
return unless ip.type is 'data'
done ip.data
expected = [
'< a'
'< b'
'DATA one:yksi'
'< c'
'DATA two:kaksi'
'> c'
'DATA three:kolme'
'> b'
'> a'
]
received = [
]
sout1.on 'ip', (ip) ->
switch ip.type
when 'openBracket'
received.push "< #{ip.data}"
when 'data'
received.push "DATA #{ip.data}"
when 'closeBracket'
received.push "> #{ip.data}"
return unless received.length is expected.length
chai.expect(received).to.eql expected