noflo
Version:
Flow-Based Programming environment for JavaScript
345 lines (319 loc) • 13.4 kB
text/coffeescript
if typeof process isnt 'undefined' and process.execPath and process.execPath.match /node|iojs/
chai = require 'chai' unless chai
loader = require '../src/lib/nodejs/ComponentLoader.coffee'
component = require '../src/lib/Component.coffee'
port = require '../src/lib/Port.coffee'
platform = require '../src/lib/Platform.coffee'
path = require 'path'
root = path.resolve __dirname, '../'
shippingLanguage = 'coffeescript'
urlPrefix = './'
else
loader = require 'noflo/src/lib/ComponentLoader.js'
component = require 'noflo/src/lib/Component.js'
platform = require 'noflo/src/lib/Platform.js'
root = 'noflo'
shippingLanguage = 'javascript'
urlPrefix = '/'
class Split extends component.Component
constructor: ->
=
in: new noflo.Port
=
out: new noflo.ArrayPort
.in.on 'connect', (data) =>
.out.connect()
.in.on 'data', (data) =>
.out.send data
.in.on 'disconnect', =>
.out.disconnect()
Split.getComponent = -> new Split
Merge = ->
inst = new component.Component
inst.inPorts.add 'in', (event, payload, instance) ->
method = event
method = 'send' if event is 'data'
instance.outPorts[method] 'out', payload
inst.outPorts.add 'out'
inst
describe 'ComponentLoader with no external packages installed', ->
l = new loader.ComponentLoader root
it 'should initially know of no components', ->
chai.expect(l.components).to.be.null
it 'should not initially require revalidation', ->
chai.expect(l.revalidate).to.be.false
it 'should not initially be ready', ->
chai.expect(l.ready).to.be.false
it 'should not initially be processing', ->
chai.expect(l.processing).to.be.false
it 'should not have any packages in the checked list', ->
chai.expect(l.checked).to.be.empty
describe 'normalizing names', ->
it 'should return simple module names as-is', ->
normalized = l.getModulePrefix 'foo'
chai.expect(normalized).to.equal 'foo'
it 'should return empty for NoFlo core', ->
normalized = l.getModulePrefix 'noflo'
chai.expect(normalized).to.equal ''
it 'should strip noflo-', ->
normalized = l.getModulePrefix 'noflo-image'
chai.expect(normalized).to.equal 'image'
it 'should strip NPM scopes', ->
normalized = l.getModulePrefix '@noflo/foo'
chai.expect(normalized).to.equal 'foo'
it 'should strip NPM scopes and noflo-', ->
normalized = l.getModulePrefix '@noflo/noflo-image'
chai.expect(normalized).to.equal 'image'
it 'should be able to read a list of components', (done) ->
60 * 1000
ready = false
l.once 'ready', ->
ready = true
chai.expect(l.ready).to.equal true
l.listComponents (err, components) ->
return done err if err
chai.expect(l.processing).to.equal false
chai.expect(l.components).not.to.be.empty
chai.expect(components).to.equal l.components
chai.expect(l.ready).to.equal true
chai.expect(ready).to.equal true
done()
chai.expect(l.processing).to.equal true
describe 'after listing components', ->
it 'should have the Graph component registered', ->
chai.expect(l.components.Graph).not.to.be.empty
describe 'loading the Graph component', ->
instance = null
it 'should be able to load the component', (done) ->
l.load 'Graph', (err, inst) ->
return done err if err
chai.expect(inst).to.be.an 'object'
chai.expect(inst.componentName).to.equal 'Graph'
instance = inst
done()
it 'should contain input ports', ->
chai.expect(instance.inPorts).to.be.an 'object'
chai.expect(instance.inPorts.graph).to.be.an 'object'
it 'should have "on" method on the input port', ->
chai.expect(instance.inPorts.graph.on).to.be.a 'function'
it 'it should know that Graph is a subgraph', ->
chai.expect(instance.isSubgraph()).to.equal true
it 'should know the description for the Graph', ->
chai.expect(instance.description).to.be.a 'string'
it 'should be able to provide an icon for the Graph', ->
chai.expect(instance.getIcon()).to.be.a 'string'
chai.expect(instance.getIcon()).to.equal 'sitemap'
# describe 'loading a subgraph', ->
# l = new loader.ComponentLoader root
# file = "#{urlPrefix}spec/fixtures/subgraph.fbp"
# it 'should remove `graph` and `start` ports', (done) ->
# l.listComponents (components) ->
# l.components.Merge = Merge
# l.components.Subgraph = file
# l.components.Split = Split
# l.load 'Subgraph', (inst) ->
# chai.expect(inst).to.be.an 'object'
# inst.once 'ready', ->
# chai.expect(inst.inPorts.ports).not.to.have.keys ['graph','start']
# chai.expect(inst.inPorts.ports).to.have.keys ['in']
# chai.expect(inst.outPorts.ports).to.have.keys ['out']
# done()
# it 'should not automatically start the subgraph if there is no `start` port', (done) ->
# l.listComponents (components) ->
# l.components.Merge = Merge
# l.components.Subgraph = file
# l.components.Split = Split
# l.load 'Subgraph', (inst) ->
# chai.expect(inst).to.be.an 'object'
# inst.once 'ready', ->
# chai.expect(inst.started).to.equal(false)
# done()
describe 'loading the Graph component', ->
instance = null
it 'should be able to load the component', (done) ->
l.load 'Graph', (err, graph) ->
return done err if err
chai.expect(graph).to.be.an 'object'
instance = graph
done()
it 'should have a reference to the Component Loader\'s baseDir', ->
chai.expect(instance.baseDir).to.equal l.baseDir
describe 'loading a component', ->
it 'should return an error on an invalid component type', (done) ->
l.components['InvalidComponent'] = true
l.load 'InvalidComponent', (err, c) ->
chai.expect(err).to.be.instanceOf Error
chai.expect(err.message).to.equal 'Invalid type boolean for component InvalidComponent.'
done()
describe 'register a component at runtime', ->
class Split extends component.Component
constructor: ->
=
in: new port.Port
=
out: new port.Port
Split.getComponent = -> new Split
instance = null
l.libraryIcons.foo = 'star'
it 'should be available in the components list', ->
l.registerComponent 'foo', 'Split', Split
chai.expect(l.components).to.contain.keys ['foo/Split', 'Graph']
it 'should be able to load the component', (done) ->
l.load 'foo/Split', (err, split) ->
return done err if err
chai.expect(split).to.be.an 'object'
instance = split
done()
it 'should have the correct ports', ->
chai.expect(instance.inPorts).to.have.keys ['in']
chai.expect(instance.outPorts).to.have.keys ['out']
it 'should have inherited its icon from the library', ->
chai.expect(instance.getIcon()).to.equal 'star'
it 'should emit an event on icon change', (done) ->
instance.once 'icon', (newIcon) ->
chai.expect(newIcon).to.equal 'smile'
done()
instance.setIcon 'smile'
it 'new instances should still contain the original icon', (done) ->
l.load 'foo/Split', (err, split) ->
return done err if err
chai.expect(split).to.be.an 'object'
chai.expect(split.getIcon()).to.equal 'star'
done()
it 'after setting an icon for the Component class, new instances should have that', (done) ->
Split::icon = 'trophy'
l.load 'foo/Split', (err, split) ->
return done err if err
chai.expect(split).to.be.an 'object'
chai.expect(split.getIcon()).to.equal 'trophy'
done()
it 'should not affect the original instance', ->
chai.expect(instance.getIcon()).to.equal 'smile'
describe 'reading sources', ->
it 'should be able to provide source code for a component', (done) ->
l.getSource 'Graph', (err, component) ->
return done err if err
chai.expect(component).to.be.an 'object'
chai.expect(component.code).to.be.a 'string'
chai.expect(component.code.indexOf('noflo.Component')).to.not.equal -1
chai.expect(component.code.indexOf('exports.getComponent')).to.not.equal -1
chai.expect(component.name).to.equal 'Graph'
chai.expect(component.library).to.equal ''
chai.expect(component.language).to.equal shippingLanguage
done()
it 'should return an error for missing components', (done) ->
l.getSource 'foo/BarBaz', (err, src) ->
chai.expect(err).to.be.an 'error'
done()
it 'should return an error for non-file components', (done) ->
l.getSource 'foo/Split', (err, src) ->
chai.expect(err).to.be.an 'error'
done()
describe 'writing sources', ->
describe 'with working code', ->
workingSource = """
var noflo = require('noflo');
exports.getComponent = function() {
var c = new noflo.Component();
c.inPorts.add('in', function(packet, outPorts) {
if (packet.event !== 'data') {
return;
}
// Do something with the packet, then
c.outPorts.out.send(packet.data);
});
c.outPorts.add('out');
return c;
};"""
it 'should be able to set the source', (done) ->
10000
unless platform.isBrowser()
workingSource = workingSource.replace "'noflo'", "'../src/lib/NoFlo'"
l.setSource 'foo', 'RepeatData', workingSource, 'js', (err) ->
return done err if err
done()
it 'should be a loadable component', (done) ->
l.load 'foo/RepeatData', (err, inst) ->
return done err if err
chai.expect(inst).to.be.an 'object'
chai.expect(inst.inPorts).to.contain.keys ['in']
chai.expect(inst.outPorts).to.contain.keys ['out']
done()
describe 'with non-working code', ->
nonWorkingSource = """
var noflo = require('noflo');
var notFound = require('./this_file_does_not_exist.js');
exports.getComponent = function() {
var c = new noflo.Component();
c.inPorts.add('in', function(packet, outPorts) {
if (packet.event !== 'data') {
return;
}
// Do something with the packet, then
c.outPorts.out.send(packet.data);
});
c.outPorts.add('out');
return c;
};"""
it 'should be able to set the source', (done) ->
unless platform.isBrowser()
nonWorkingSource = nonWorkingSource.replace "'noflo'", "'../src/lib/NoFlo'"
l.setSource 'foo', 'NotWorking', nonWorkingSource, 'js', (err) ->
chai.expect(err).to.be.an 'error'
done()
it 'should not be a loadable component', (done) ->
l.load 'foo/NotWorking', (err, inst) ->
chai.expect(err).to.be.an 'error'
chai.expect(inst).to.be.an 'undefined'
done()
describe 'ComponentLoader with a fixture project', ->
l = null
before ->
return if platform.isBrowser()
it 'should be possible to instantiate', ->
l = new loader.ComponentLoader path.resolve __dirname, 'fixtures/componentloader'
it 'should initially know of no components', ->
chai.expect(l.components).to.be.a 'null'
it 'should not initially be ready', ->
chai.expect(l.ready).to.be.false
it 'should be able to read a list of components', (done) ->
ready = false
l.once 'ready', ->
chai.expect(l.ready).to.equal true
ready = l.ready
l.listComponents (err, components) ->
return done err if err
chai.expect(l.processing).to.equal false
chai.expect(l.components).not.to.be.empty
chai.expect(components).to.equal l.components
chai.expect(l.ready).to.equal true
chai.expect(ready).to.equal true
done()
chai.expect(l.processing).to.equal true
it 'should be able to load a local component', (done) ->
l.load 'componentloader/Output', (err, instance) ->
chai.expect(err).to.be.a 'null'
chai.expect(instance.description).to.equal 'Output stuff'
chai.expect(instance.icon).to.equal 'cloud'
done()
it 'should be able to load a component from a dependency', (done) ->
l.load 'example/Forward', (err, instance) ->
chai.expect(err).to.be.a 'null'
chai.expect(instance.description).to.equal 'Forward stuff'
chai.expect(instance.icon).to.equal 'car'
done()
it 'should be able to load a dynamically registered component from a dependency', (done) ->
l.load 'example/Hello', (err, instance) ->
chai.expect(err).to.be.a 'null'
chai.expect(instance.description).to.equal 'Hello stuff'
chai.expect(instance.icon).to.equal 'bicycle'
done()
it 'should be able to load core Graph component', (done) ->
l.load 'Graph', (err, instance) ->
chai.expect(err).to.be.a 'null'
chai.expect(instance.icon).to.equal 'sitemap'
done()
it 'should fail loading a missing component', (done) ->
l.load 'componentloader/Missing', (err, instance) ->
chai.expect(err).to.be.an 'error'
done()