conditions
Version:
A javascript configuration engine
240 lines (225 loc) • 10.3 kB
JavaScript
describe('configuration parser', function () {
/* jshint maxlen: 200 */
'use strict';
var parse = require('../src/parser.js'),
fs = require('fs');
beforeEach(function () {
delete require.cache[require.resolve('../src/parser.js')];
parse = require('../src/parser.js');
});
describe('checks', function() {
it('should ensure the supplied data is a string', function () {
expect(parse.bind(null, { })).to.throw(/must.*string/i);
expect(parse.bind(null, true)).to.throw(/must.*string/i);
expect(parse.bind(null, function () { })).to.throw(/must.*string/i);
expect(parse.bind(null, 123)).to.throw(/must.*string/i);
});
it('should return an empty object when the supplied str is empty', function() {
expect(parse('')).to.be.an('object');
});
it('should ensure the supplied config file defines an object or array at its root', function () {
expect(parse.bind(null, data('invalid'))).to.throw(/object.*array/i);
});
it('should not allow calls to functions which are illegal', function () {
expect(parse.bind(null, data('illegal'))).to.throw(/illegal/i);
});
it('should prevent illegal expression types from being used', function () {
expect(parse.bind(null, data('illegalexpression'))).to.throw(/illegal/i);
});
});
describe('root', function () {
it('should allow an object to be defined at the root, and should return an object when parsed', function () {
var val = parse(data('object'));
expect(val).to.be.an('object');
});
it('should allow an array to be defined at the root, and should return an array when parsed', function () {
var val = parse(data('array'));
expect(val).to.be.an('array');
});
});
describe('literals', function () {
var config;
beforeEach(function () {
config = parse(data('literal'));
});
it('should return a string when a string property is defined', function () {
expect(config.str).to.equal('foo bar');
});
it('should return a number when a number property is defined', function () {
expect(config.num).to.equal(10);
});
it('should return a boolean when a boolean property is defined', function () {
expect(config.bool).to.equal(true);
});
it('should return undefined when a undefined property is defined', function () {
expect(config.undefined).to.equal(undefined);
});
it('should return null when null is defined', function () {
expect(config.nul).to.equal(null);
});
it('should return a regular expression when a regular expression is defined', function () {
expect(config.rex).to.be.a('regexp');
expect(config.rex.source).to.equal('abc');
expect(config.rex.global).to.equal(true);
expect(config.rex.multiline).to.equal(false);
expect(config.rex.ignoreCase).to.equal(true);
});
});
describe('interpolation', function () {
var config;
beforeEach(function () {
config = parse(data('interpolation'));
});
it('should return the value with the variables interpolated into the string', function () {
expect(config.value).to.equal('hello cruel world!');
});
it('should return a new value if any of the variables have changed', function () {
expect(config.value).to.equal('hello cruel world!');
config.type = 'good';
expect(config.value).to.equal('hello good world!');
});
});
describe('objects', function () {
it('should parse sub objects in the configuration', function () {
var val = parse(data('object'));
expect(val).to.be.an('object');
expect(val.sub).to.be.an('object');
expect(val.sub.foo).to.equal('bar');
});
it('should parse sub arrays in the configuration', function () {
var val = parse(data('object'));
expect(val).to.be.an('object');
expect(val.sub).to.be.an('object');
expect(val.sub.baz).to.be.an('array');
expect(val.sub.baz[0]).to.equal(1);
expect(val.sub.baz[1]).to.equal(2);
expect(val.sub.baz[2]).to.equal(3);
});
});
describe('sequence', function () {
var val = parse(data('sequence'));
expect(val).to.be.an('array');
expect(val.length).to.equal(3);
expect(val[0]).to.equal('foo');
expect(val[1]).to.equal('bar');
expect(val[2]).to.equal('baz');
});
describe('expressions', function () {
var val, env = { env: 'foo bar' };
beforeEach(function () {
val = parse(data('expressions'), { environment: env });
});
it('should return the value of the expression', function () {
expect(val.exp1).to.equal(20);
expect(val.exp2).to.equal('foo bar');
expect(val.exp3).to.equal('foo bar');
});
it('should allow expressions to reference object by their id', function () {
expect(val.sub.exp4).to.equal(30);
});
it('should allow configuration values on the current object to be referenced by name', function () {
expect(val.exp6).to.equal(30);
});
it('should treat literal strings inside template literals as a normal string instead of expression', function () {
expect(val.literal).to.equal('template-txt');
const desc = Object.getOwnPropertyDescriptor(val, 'literal');
expect(desc.value).to.equal('template-txt');
});
it('should allow configuration values on the current object to be through this', function () {
expect(val.exp7).to.equal('foo bar baz');
});
it('should allow a value to be set to override expression values', function () {
val = parse(data('array-expression'), { });
const test = Symbol('test');
val.hello.world[test] = 'foo bar'; // For coverage
expect(val.hello.world[0]).to.equal(1);
val.hello.world[0] = 'testing';
expect(val.hello.world[0]).to.equal('testing');
expect(val.hello.world[1]).to.equal(2);
val.hello.world[1] = 'testing';
expect(val.hello.world[1]).to.equal('testing');
});
it('should use values from options.environment to the parse function in expressions', function () {
expect(val.exp8).to.equal('foo bar baz');
});
it('should not allow new expressions', function () {
expect(parse.bind(null, '{ foo: new Date() }')).to.throw(/not.*supported/i);
});
it('should not throw an error if an undefined identier us used with typeof', function () {
const config = parse('{ foo: typeof dontexist }');
expect(config.foo).to.equal('undefined');
});
it('should not allow new expressions', function () {
expect(parse.bind(null, '{ foo: new Date() }')).to.throw(/not.*supported/i);
});
it('should not allow assignment expressions', function () {
expect(parse.bind(null, '{ foo: Date = 10 }')).to.throw(/not.*supported/i);
});
it('should parse constant expressions to normal values', function () {
expect(val.constant).to.equal(2500);
val.constant = 100;
expect(val.constant).to.equal(100);
});
it('should allow expressions to be used in arrays', function () {
val = parse(data('array-expression'), { });
expect(val.hello.world).to.be.an('array');
expect(val.hello.world.length).to.equal(3);
expect(val.hello.world[0]).to.equal(1);
expect(val.hello.world[1]).to.equal(2);
expect(val.hello.world[2]).to.equal(3);
val.foo.bar = 5;
expect(val.hello.world[0]).to.equal(1);
expect(val.hello.world[1]).to.equal(5);
expect(val.hello.world[2]).to.equal(3);
});
it('should allow options.custom to return a function which will become the expression', function () {
const text = 'foo bar baz';
const options = {
environment: env,
custom: function () {
return () => text;
}
}
const val = parse('{ foo: bar + baz }', options);
expect(val.foo).to.equal(text);
});
});
describe('errors', function() {
it('should report the line and column of an error when one occurs', function () {
var d = data('syntaxerror'), cfg;
expect(parse.bind(null, d)).to.throw(/line.*6/i);
var env = {}, err;
Object.defineProperty(env, 'env', {
enumerable: true,
configurable: true,
get: function () {
if (err) {
throw new Error('fake');
}
}
});
d = data('expressions');
cfg = parse(d, { environment: env });
err = true;
expect(function () { return cfg.exp8; }).to.throw();
});
it('should report the line and column when an error occurs in an expression', function () {
var val = parse(data('error'));
expect(get).to.throw(/line.*column/i);
function get() {
return val.invalid;
}
});
it('should report the context from options if one was supplued', function () {
var val = parse(data('error'), { context: 'fakefile' });
expect(get).to.throw(/fakefile/i);
function get() {
return val.invalid;
}
});
});
/** Reads the contents from the specified data file */
function data(file) {
return fs.readFileSync('./spec/data/data.' + file + '.config', { encoding: 'utf8' });
}
});