test/MacroTests.js
'use strict';
var assert = require('assert');
var Macro = require('../lib/Macro');
var Context = require('../lib/Context');
var EventBus = require('../lib/EventBus');
var Dictionary = require('../lib/Dictionary');
var $ = require('../lib/Array');
var fn = require('../lib/fn');
describe('Macro', function () {
it('should interpret a synchronous step synchronously', function () {
var execution = new Execution();
new Macro('Easy', parsed_signature(/Easy as (\d), (\d), (\d)/), execution.fn, { a: 1 }).interpret('Easy as 1, 2, 3', new Context({ b: 2 }));
assert.ok(execution.executed, 'The step was not executed');
assert.equal(execution.args.length, 3);
assert.deepEqual(execution.args, [1, 2, 3]);
assert.deepEqual(execution.ctx, { a: 1, b: 2, step: 'Easy as 1, 2, 3' });
});
it('should tolerate too many step arguments for synchronous steps', function () {
var execution = new Execution();
new Macro('Easy', parsed_signature(/Easy as 1, 2, 3/), execution.fn, { a: 1 }).interpret('Easy as 1, 2, 3', new Context({ b: 2 }));
assert.ok(execution.executed, 'The step was not executed');
assert.equal(execution.args.length, 0);
});
it('should tolerate too few step arguments for synchronous steps', function () {
var execution = new Execution();
new Macro('Easy', parsed_signature(/Easy as (\d), (\d), (\d), (\d)/), execution.fn, { a: 1 }).interpret('Easy as 1, 2, 3, 4', new Context({ b: 2 }));
assert.ok(execution.executed, 'The step was not executed');
assert.equal(execution.args.length, 4);
});
it('should interpret a synchronous step asynchronously', function (done) {
var execution = new Execution();
new Macro('Easy', parsed_signature(/Easy as (\d), (\d), (\d)/), execution.fn, { a: 1 }).interpret('Easy as 1, 2, 3', new Context({ b: 2 }), function () {
assert.ok(execution.executed, 'The step was not executed');
assert.equal(execution.args.length, 3);
assert.deepEqual(execution.args, [1, 2, 3]);
assert.deepEqual(execution.ctx, { a: 1, b: 2, step: 'Easy as 1, 2, 3' });
done();
});
});
it('should interpret an asynchronous step', function (done) {
var execution = new Execution();
new Macro('Easy', parsed_signature(/Easy as (\d), (\d), (\d)/), execution.afn, { a: 1 }).interpret('Easy as 1, 2, 3', new Context({ b: 2 }), function () {
assert.ok(execution.executed, 'The step was not executed');
assert.equal(execution.args.length, 4);
assert.deepEqual(execution.args.splice(0, 3), [1, 2, 3]);
assert.deepEqual(execution.ctx, { a: 1, b: 2, step: 'Easy as 1, 2, 3' });
done();
});
});
it('should fail when too few step arguments for asynchronous steps', function (done) {
var execution = new Execution();
new Macro('Easy', parsed_signature(/Easy as (\d), (\d), 3/), execution.afn, { a: 1 }).interpret('Easy as 1, 2, 3', new Context({ b: 2 }), function (err) {
assert.ok(err);
done();
});
});
it('should fail when too many step arguments for asynchronous steps', function (done) {
var execution = new Execution();
new Macro('Easy', parsed_signature(/Easy as (\d), (\d), (\d), (\d)/), execution.afn, { a: 1 }).interpret('Easy as 1, 2, 3, 4', new Context({ b: 2 }), function (err) {
assert.ok(err);
done();
});
});
it('should support variadic async functions', function (done) {
var execution = new Execution();
new Macro('Easy', parsed_signature(/Easy as (\d), (\d), (\d), (\d)/), execution.vafn, { a: 1 }, undefined, { mode: 'async' }).interpret('Easy as 1, 2, 3, 4', new Context({ b: 2 }), function () {
assert.ok(execution.executed, 'The step was not executed');
assert.equal(execution.args.length, 5);
assert.deepEqual(execution.args.splice(0, 4), [1, 2, 3, 4]);
assert.deepEqual(execution.ctx, { a: 1, b: 2, step: 'Easy as 1, 2, 3, 4' });
done();
});
});
it('should execute a promisified step', function (done) {
var execution = new Execution();
new Macro('Easy', parsed_signature(/Easy as (\d), (\d), (\d)/), execution.promise, { a: 1 }).interpret('Easy as 1, 2, 3', new Context({ b: 2 }), function () {
assert.ok(execution.executed, 'The step was not executed');
assert.equal(execution.args.length, 3);
assert.deepEqual(execution.args, [1, 2, 3]);
assert.deepEqual(execution.ctx, { a: 1, b: 2, step: 'Easy as 1, 2, 3' });
done();
});
});
it('should include step name in the context', function () {
var execution = new Execution();
new Macro('Easy', parsed_signature(/Easy as (\d), (\d), (\d)/), execution.fn, { a: 1 }).interpret('Easy as 1, 2, 3', new Context({ b: 2 }), fn.noop);
assert.equal(execution.ctx.step, 'Easy as 1, 2, 3');
});
it('should not override step name in the context if explicitly set', function () {
var execution = new Execution();
new Macro('Easy', parsed_signature(/Easy as (\d), (\d), (\d)/), execution.fn, { a: 1 }).interpret('Easy as 1, 2, 3', new Context({ b: 2, step: 'Do not override' }), fn.noop);
assert.equal(execution.ctx.step, 'Do not override');
});
it('should provide a signature that can be used to compare levenshtein distance', function () {
$([/the quick brown fox/, /the quick.* brown.* fox/, /the quick(.*) brown(?:.*) fox/, /the quick[xyz] brown[^xyz] fox/, /the quick{0,1} brown{1} fox/, /the quick\d brown\W fox/]).each(function (pattern) {
assert.equal(new Macro('Quick brown fox', parsed_signature(pattern)).levenshtein_signature(), 'the quick brown fox');
});
});
it('should default to a no operation function', function (done) {
new Macro('blah $a', parsed_signature(/blah (.*)/)).interpret('blah 1', {}, function () {
done();
});
});
it('should notify listeners of execute events', function (done) {
var listener = new Listener();
EventBus.instance().on(/EXECUTE/, listener.listen);
new Macro('Easy', parsed_signature(/Easy as (\d), (\d), (\d)/), fn.noop, { a: 1 }).interpret('Easy as 1, 2, 3', { b: 2 });
assert.equal(1, listener.events.length);
var event = listener.events[0];
assert.equal(event.name, EventBus.ON_EXECUTE);
assert.equal(event.data.step, 'Easy as 1, 2, 3');
assert.deepEqual(event.data.ctx, { a: 1, b: 2, step: 'Easy as 1, 2, 3' });
assert.equal(event.data.pattern, '/Easy as (\\d), (\\d), (\\d)/');
done();
});
it('should notify listeners of define events', function (done) {
var listener = new Listener();
EventBus.instance().on(/DEFINE/, listener.listen);
new Macro('Easy', parsed_signature(/Easy as (\d), (\d), (\d)/), fn.noop, { a: 1 }).interpret('Easy as 1, 2, 3', { b: 2 });
assert.equal(1, listener.events.length);
var event = listener.events[0];
assert.equal(event.name, EventBus.ON_DEFINE);
assert.equal(event.data.pattern, '/Easy as (\\d), (\\d), (\\d)/');
done();
});
it('should interpret a multiline', function () {
var execution = new Execution();
new Macro('Easy', parsed_signature(/Easy as ([^\u0000]*)/), execution.fn, { a: 1 }).interpret('Easy as 1\n2\n3', new Context({ b: 2 }), fn.noop);
assert.ok(execution.executed, 'The step was not executed');
assert.deepEqual(execution.args.splice(0, 1), ['1\n2\n3']);
assert.deepEqual(execution.ctx, { a: 1, b: 2, step: 'Easy as 1\n2\n3' });
});
it('should convert parameters', function () {
var execution = new Execution();
new Macro(
'Easy',
{
pattern: /Easy as (\d), (\d), (\d)/,
converters: [
function (value, cb) {
cb(null, value * 2);
},
function (value, cb) {
cb(null, value * 3);
},
function (value, cb) {
cb(null, value * 4);
},
],
},
execution.fn,
{ a: 1 }
).interpret('Easy as 1, 2, 3', fn.noop);
assert.ok(execution.executed, 'The step was not executed');
assert.deepEqual(execution.args.splice(0, 3), [2, 6, 12]);
});
it('should convert parameters with multi-arg converters', function () {
var execution = new Execution();
new Macro(
'Easy',
{
pattern: /Easy as (\d), (\d), (\d), (\d)/,
converters: [
function (value, cb) {
cb(null, value * 2);
},
function (value1, value2, cb) {
cb(null, parseInt(value1) + parseInt(value2));
},
function (value, cb) {
cb(null, value * 3);
},
],
},
execution.fn,
{ a: 1 }
).interpret('Easy as 1, 2, 3, 4', fn.noop);
assert.ok(execution.executed, 'The step was not executed');
assert.deepEqual(execution.args.splice(0, 3), [2, 5, 12]);
});
it('should convert parameters with multi-result converters', function () {
var execution = new Execution();
new Macro(
'Easy',
{
pattern: /Easy as (\d), (\d), (\d), (\d)/,
converters: [
function (value, cb) {
cb(null, value * 2);
},
function (value1, value2, cb) {
cb(null, parseInt(value1), parseInt(value2), parseInt(value1));
},
function (value, cb) {
cb(null, value * 3);
},
],
},
execution.fn,
{ a: 1 }
).interpret('Easy as 1, 2, 3, 4', fn.noop);
assert.ok(execution.executed, 'The step was not executed');
assert.deepEqual(execution.args.splice(0, 5), [2, 2, 3, 2, 12]);
});
it('should yield errors when called asynchronously', function () {
new Macro('Easy', parsed_signature(/Easy as (\d), (\d), (\d)/), function (a, b, c, cb) {
throw new Error('Oh Noes!');
}).interpret('Easy as 1, 2, 3', {}, function (err) {
assert.ok(err);
assert.equal(err.message, 'Oh Noes!');
});
});
it('should throw errors when called synchronously', function () {
assert.throws(function () {
new Macro('Easy', parsed_signature(/Easy as (\d), (\d), (\d)/), function (a, b, c) {
throw new Error('Oh Noes!');
}).interpret('Easy as 1, 2, 3', {});
}, /Oh Noes!/);
});
function parsed_signature(pattern) {
return new Dictionary().define('foo', pattern).expand('$foo');
}
function Execution() {
this.executed = false;
this.args = undefined;
this.ctx = undefined;
var _this = this;
this.fn = function (a, b, c) {
_this.executed = true;
_this.captureArguments(arguments);
_this.ctx = this;
};
this.afn = function (a, b, c, next) {
_this.executed = true;
_this.captureArguments(arguments);
_this.ctx = this;
next();
};
this.vafn = function () {
_this.executed = true;
_this.captureArguments(arguments);
_this.ctx = this;
arguments[arguments.length - 1]();
};
this.promise = function (a, b, c) {
_this.executed = true;
_this.captureArguments(arguments);
_this.ctx = this;
return {
then: function (cb) {
cb();
return {
catch: function (cb) {
_this.caught = true;
},
};
},
};
};
this.captureArguments = function (args) {
_this.args = this.toArray(args);
};
this.toArray = function (obj) {
return [].slice.call(obj, 0);
};
}
function Listener() {
var _this = this;
this.events = [];
this.listen = function (event) {
_this.events.push(event);
};
}
});