dgeni
Version:
Flexible JavaScript documentation generator used by both AngularJS and Angular
459 lines (392 loc) • 16.8 kB
text/typescript
const {expect, spy} = require('chai').use(require('chai-spies'));
import {Dgeni} from './Dgeni';
import {DocCollection} from './DocCollection';
describe('Dgeni', () => {
let dgeni, mockLogger;
beforeEach(() => {
mockLogger = spy.object(['error', 'warning', 'info', 'debug', 'silly']);
dgeni = new Dgeni();
const mockLoggerPackage = dgeni.package('mockLogger');
mockLoggerPackage.factory(function log() { return mockLogger; });
});
describe('constructor', () => {
it('should accept an array of packages to load', () => {
const package1 = new Dgeni.Package('package1');
const package2 = new Dgeni.Package('package2');
dgeni = new Dgeni([package1, package2]);
expect(dgeni.packages.package1).to.equal(package1);
expect(dgeni.packages.package2).to.equal(package2);
});
it('should complain if the packages parameter is not an array', () => {
expect(() => {
new Dgeni('bad-param' as any);
}).to.throw();
});
});
describe('package()', () => {
it('should add the package to the packages property', () => {
const testPackage = new Dgeni.Package('test-package');
dgeni.package(testPackage);
expect(dgeni.packages['test-package']).to.eql(testPackage);
});
it('should create a new package if passed a string', () => {
const newPackage = dgeni.package('test-package');
expect(Dgeni.Package.isPackage(newPackage)).to.equal(true);
});
it('should throw an error if the not passed an instance of Package or a string name', () => {
expect(() => {
dgeni.package({});
}).to.throw();
});
it('should complain if two packages have the same name', () => {
dgeni.package('test');
expect(() => {
dgeni.package('test');
}).to.throw();
});
it('should pass dependencies through to the new package', () => {
const newPackage = dgeni.package('test-package', ['dep1', 'dep2']);
expect(newPackage.dependencies).to.eql(['dep1', 'dep2']);
});
it('should load up package dependencies that are defined inline', function() {
const log = [];
const a = new Dgeni.Package('a').processor(function aProcessor() {
return { $process: (docs: DocCollection) => { log.push('a'); } };
});
const b = new Dgeni.Package('b', [a]);
dgeni.package(b);
expect(b.dependencies).to.eql([a]);
expect(b.namedDependencies).to.eql(['a']);
return dgeni.generate().then(() => {
expect(log).to.eql(['a']);
});
});
it('should not load a dependency that is already loaded', function() {
const log = [];
// Load package a1, with name 'a'
const a1 = new Dgeni.Package('a').processor({ name: 'a', $process: () => { log.push('a1'); } });
dgeni.package(a1);
// Load package b with inline depencency on a2, which also has name 'a'
// This second 'a' package (i.e. a2) should nt get loaded
const a2 = new Dgeni.Package('a').processor({ name: 'a', $process: () => { log.push('a2'); } });
const b = new Dgeni.Package('b', [a2]);
dgeni.package(b);
expect(b.dependencies).to.eql([a2]);
expect(b.namedDependencies).to.eql(['a']);
return dgeni.generate().then(() => {
expect(log).to.eql(['a1']);
});
});
it('should not modify the `dependencies` property of a package', () => {
const a = new Dgeni.Package('a').processor({ name: 'a', $process: () => { } });
const b = new Dgeni.Package('b', [a]).processor({ name: 'a', $process: () => { } });
dgeni.package(b);
expect(b.dependencies).to.eql([a]);
expect(b.namedDependencies).to.eql(['a']);
});
});
describe('configureInjector', () => {
it('should return the configured injector', () => {
const injector = dgeni.configureInjector();
expect(injector.get).to.be.a('function');
});
describe('services', () => {
it('should add some basic shared services to the injector', () => {
const injector = dgeni.configureInjector();
expect(injector.get('dgeni')).to.be.an('object');
expect(injector.get('log')).to.be.an('object');
expect(injector.get('log').debug).to.be.a('function');
expect(injector.get('getInjectables')).to.be.a('function');
});
it('should set stop on error defaults', () => {
let stopOnProcessingError;
dgeni.package('testPackage').config(function(dgeni) {
stopOnProcessingError = dgeni.stopOnProcessingError;
});
dgeni.configureInjector();
expect(stopOnProcessingError).to.equal(true);
});
it('should add services to the injector', () => {
const log = [];
dgeni.package('test-package')
.processor(function testProcessor(service1, service2) {
return {
$process: () => {
log.push(service1);
log.push(service2);
}
};
})
.factory(function service1() { return 'service1 value'; })
.factory(function service2(service1) { return service1 + ' service2 value'; });
const injector = dgeni.configureInjector();
injector.get('testProcessor').$process();
expect(log).to.eql(['service1 value', 'service1 value service2 value']);
});
it('should add services from packages in the correct package dependency order', () => {
const log = [];
dgeni.package('test1', ['test2']).factory(function testValue() { return 'test 1'; });
dgeni.package('test2').factory(function testValue() { return 'test 2'; });
dgeni.package('test4', ['test3'])
.processor(function test3Processor(testValue) {
return {
$process: () => {
log.push(testValue + '(overridden)'); }
};
});
dgeni.package('test3', ['test1', 'test2'])
.processor(function test3Processor(testValue) {
return {
$process: () => {
log.push(testValue); }
};
});
const injector = dgeni.configureInjector();
injector.get('test3Processor').$process();
expect(log).to.eql(['test 1(overridden)']);
});
});
describe('config blocks', () => {
it('should run the config functions in the correct package dependency order', () => {
const log = [];
function testProcessor() {
return {
$process() { log.push(this.testValue); }
};
}
dgeni.package('test')
.processor(testProcessor);
dgeni.package('test1', ['test2'])
.config(function(testProcessor) { testProcessor.testValue = 1; });
dgeni.package('test2', ['test'])
.config(function(testProcessor) { testProcessor.testValue = 2; });
const injector = dgeni.configureInjector();
injector.get('testProcessor').$process();
expect(log).to.eql([1]);
});
it('should provide config blocks with access to the injector', () => {
let localInjector;
dgeni.package('test')
.config(function(injector) {
localInjector = injector;
});
const injector = dgeni.configureInjector();
expect(injector).to.equal(localInjector);
});
});
describe('eventHandlers', () => {
it('should add eventHandlers in the correct package dependency order', () => {
function handler1() {}
function handler2() {}
function handler3() {}
function handler4() {}
dgeni.package('test1', ['test2']).eventHandler('testEvent', () => { return handler1; });
dgeni.package('test2')
.eventHandler('testEvent', () => { return handler2; })
.eventHandler('testEvent2', () => { return handler3; });
dgeni.package('test3', ['test1']).eventHandler('testEvent', () => { return handler4; });
dgeni.configureInjector();
expect(dgeni.handlerMap).to.have.property('testEvent').to.eql([handler2, handler1, handler4]);
expect(dgeni.handlerMap).to.have.property('testEvent2').to.eql([handler3]);
});
});
describe('legacy validation', () => {
it('should fail if processor has an invalid property', function(done) {
dgeni.package('test')
.processor(function testProcessor() {
return {
$validate: { x: { presence: true } }
};
});
dgeni.generate().catch(function(errors) {
expect(errors).to.eql([{ processor : 'testProcessor', package : 'test', errors : { x : [ 'X can\'t be blank' ] } }]);
done();
});
});
});
describe('processors', () => {
it('should order the processors by dependency', () => {
const log = [];
const a = { $runAfter: ['c'], $process: () => { log.push('a'); } };
const b = { $runAfter: ['c', 'e', 'a'], $process: () => { log.push('b'); } };
const c = { $runBefore: ['e'], $process: () => { log.push('c'); } };
const d = { $runAfter: ['a'], $process: () => { log.push('d'); } };
const e = { $runAfter: [], $process: () => { log.push('e'); } };
dgeni.package('test1')
.processor('a', a)
.processor('b', b)
.processor('c', c)
.processor('d', d)
.processor('e', e);
dgeni.configureInjector();
expect(dgeni.processors).to.eql([c, e, a, b, d]);
});
it('should ignore processors that have $enabled set to false', () => {
const a = { $process: () => {} };
const b = { $enabled: false, $process: () => {} };
const c = { $process: () => {} };
dgeni.package('test1')
.processor('a', a)
.processor('b', b)
.processor('c', c);
dgeni.configureInjector();
expect(dgeni.processors).to.eql([a, c]);
});
it('should allow config blocks to change $enabled on a processor', () => {
const log = [];
const a = { $process: () => { log.push('a'); } };
const b = { $enabled: false, $process: () => { log.push('b'); } };
const c = { $process: () => { log.push('c'); } };
dgeni.package('test1')
.processor('a', a)
.processor('b', b)
.processor('c', c)
.config(function(a, b) {
a.$enabled = false;
b.$enabled = true;
});
dgeni.configureInjector();
expect(dgeni.processors).to.eql([b, c]);
});
it('should throw an error if the $runAfter dependencies are invalid', () => {
dgeni.package('test')
.processor(function badRunAfterProcessor() { return { $runAfter: ['tags-processed'] }; });
expect(() => {
dgeni.configureInjector();
}).to.throw('Missing dependency: "tags-processed" on "badRunAfterProcessor"');
});
it('should throw an error if the $runBefore dependencies are invalid', () => {
dgeni.package('test')
.processor(function badRunBeforeProcessor() { return { $runBefore: ['tags-processed'] }; });
expect(() => {
dgeni.configureInjector();
}).to.throw('Missing dependency: "tags-processed" on "badRunBeforeProcessor"');
});
it('should throw an error if the processor dependencies are cyclic', () => {
dgeni.package('test')
.processor(function processor1() { return { $runBefore: ['processor2'] }; })
.processor(function processor2() { return { $runBefore: ['processor1'] }; });
expect(() => {
dgeni.configureInjector();
}).to.throw('Dependency Cycle Found: processor1 -> processor2 -> processor1');
});
it('should allow config blocks to change the order of the processors', function(done) {
const log = [];
dgeni.package('test')
.processor(function a() { return { $runBefore: ['b'], $process: () => { log.push('a' ); } }; })
.processor(function b() { return { $runBefore: ['c'], $process: () => { log.push('b' ); } }; })
.processor(function c() { return { $process: () => { log.push('c' ); } }; })
.config(function(a, b, c) {
b.$runBefore = [];
c.$runBefore = ['b'];
});
dgeni.generate([]).then(() => {
expect(log).to.eql(['a', 'c', 'b']);
done();
});
});
});
});
describe('triggerEvent()', () => {
it('should run all the specified event\'s handlers in the correct dependency order', function(done) {
const log = [];
const handler1 = () => { log.push('handler1'); };
const handler2 = () => { log.push('handler2'); };
const handler3 = () => { log.push('handler3'); };
const handler4 = () => { log.push('handler4'); };
dgeni.package('test1', ['test2']).eventHandler('testEvent', () => { return handler1; });
dgeni.package('test2')
.eventHandler('testEvent', () => { return handler2; })
.eventHandler('testEvent2', () => { return handler3; });
dgeni.package('test3', ['test1']).eventHandler('testEvent', () => { return handler4; });
dgeni.configureInjector();
dgeni.triggerEvent('testEvent').finally(() => {
expect(log).to.eql(['handler2', 'handler1', 'handler4']);
done();
});
});
it('should pass through the call arguments to the handler', function(done) {
const handler = spy('handler');
dgeni.package('test1', []).eventHandler('testEvent', () => { return handler; });
dgeni.configureInjector();
dgeni.triggerEvent('testEvent', 'arg1', 'arg2', 'arg3').finally(() => {
expect(handler).to.have.been.called.with('testEvent', 'arg1', 'arg2', 'arg3');
done();
});
});
it('should return a promise to event handler results', function(done) {
function handler1() { }
function handler2() { return true; }
function handler3() { return { message: 'info' }; }
function handler4() { return Promise.resolve(); }
function handler5() { return Promise.resolve(true); }
function handler6() { return Promise.resolve({ message: 'info async'}); }
dgeni.package('test1', [])
.eventHandler('testEvent', () => { return handler1; })
.eventHandler('testEvent', () => { return handler2; })
.eventHandler('testEvent', () => { return handler3; })
.eventHandler('testEvent', () => { return handler4; })
.eventHandler('testEvent', () => { return handler5; })
.eventHandler('testEvent', () => { return handler6; });
dgeni.configureInjector();
dgeni.triggerEvent('testEvent').then(function(results) {
expect(results).to.eql([undefined, true, {message: 'info'}, undefined, true, {message: 'info async'}]);
done();
});
});
});
describe('generate()', () => {
describe('bad-processor', () => {
let testPackage;
beforeEach(() => {
testPackage = dgeni.package('test')
.processor(function badProcessor() {
return {
$process: () => { throw new Error('processor failed'); }
};
});
});
describe('stopOnProcessingError', () => {
it('should fail if stopOnProcessingError is true and a processor throws an Error', function() {
return dgeni.generate()
.catch(function(e) {
expect(e).to.exist;
});
});
it('should not fail but log the error if stopOnProcessingError is false a processor throws an Error', function() {
let error;
testPackage
.config(function(dgeni) {
dgeni.stopOnProcessingError = false;
});
return dgeni.generate()
.catch(function(e) {
error = e;
})
.finally(() => {
expect(error).to.be.undefined;
expect(mockLogger.error).to.have.been.called();
});
});
it('should continue to process the subsequent processors after a bad-processor if stopOnProcessingError is false', function() {
let called = false;
testPackage
.config(function(dgeni) {
dgeni.stopOnProcessingError = false;
})
.processor(function checkProcessor() {
return {
$runAfter: ['badProcessor'],
$process: () => {
called = true;
}
};
});
return dgeni.generate().finally(() => {
expect(called).to.eql(true);
});
});
});
});
});
});