Home Manual Reference Source Test

src/Logger.spec.js

import Emitter from 'events';
import { Transform } from 'stream';
import expect from 'unexpected';
import { spy } from 'sinon';
import Logger from '../../src/Logger';

class NoopTransformStream extends Transform {

  _transform(chunk, enc, callback) {
    callback(null, chunk);
  }

}

/** @test {Logger} */
describe('Logger', function() {
  /** @test {Logger#constructor} */
  describe('#constructor', function() {
    it('should return an emitter', function() {
      expect(new Logger(), 'to be an', Emitter);
    });

    it('should handle types option', function() {
      const types = ['first', 'second'];
      expect((new Logger({ types }))._types, 'to be', types);
    });

    it('should register log methods for all types', function() {
      const logger = new Logger();

      Logger.defaultTypes.forEach(type => {
        expect(logger[type], 'to be defined');
        expect(logger[type], 'to be a', 'function');
      });
    });

    it('should register levels for all types', function() {
      const logger = new Logger();

      Logger.defaultTypes.forEach(type => {
        expect(logger[`LEVEL_${type.toUpperCase()}`], 'to be defined');
        expect(logger[`LEVEL_${type.toUpperCase()}`], 'to be a', 'number');
      });
    });

    it('should store scope array', function() {
      const scope = ['scope', 'another'];
      const logger = new Logger({ scope });

      expect(logger.scope, 'to equal', scope);
    });

    it('should store single scope', function() {
      const scope = 'scope';
      const logger = new Logger({ scope });

      expect(logger.scope, 'to equal', [scope]);
    });

    it('should set no scope if omitted', function() {
      const logger = new Logger({ scope: false });

      expect(logger.scope, 'to equal', []);
    });

    it('should store timestamp option', function() {
      const logger = new Logger({ timestamp: false });

      expect(logger._printTimestamp, 'to equal', false);
    });
  });

  /** @test {Logger#createChild} */
  describe('#createChild', function() {
    it('should create a new logger instance', function() {
      const parent = new Logger();
      const child = parent.createChild('child');

      expect(child, 'to be a', Logger);
    });

    it('should forward options', function() {
      const types = ['a', 'b'];
      const timestamp = false;
      const parent = new Logger({ types, timestamp });
      const child = parent.createChild('child');

      expect(child._types, 'to equal', parent._types);
      expect(child._printTimestamp, 'to equal', parent._printTimestamp);
    });

    it('should forward events to original instance', function() {
      const parent = new Logger();
      const child = parent.createChild('child');

      const messageReceived = new Promise(resolve => {
        parent.on('info', resolve);
      });

      child.info('Test message');

      return expect(messageReceived, 'when fulfilled', 'to contain', 'Test message');
    });

    it('should prefix messages with name', function() {
      const parent = new Logger();
      const child = parent.createChild('child');

      const messageReceived = new Promise(resolve => {
        parent.on('info', resolve);
      });

      child.info('Test message');

      return expect(messageReceived, 'when fulfilled', 'to contain', 'child');
    });
  });

  /** @test {Logger#[type]} */
  describe('#[type]', function() {
    it('should emit `type` events', function() {
      const logger = new Logger();

      return Promise.all(
        Logger.defaultTypes.map(type => new Promise((resolve, reject) => {
          logger.on(type, msg => {
            try {
              resolve(expect(msg, 'to contain', type));
            } catch (e) {
              reject(e);
            }
          });
          logger[type](`${type} message`);
        }))
      );
    });

    it('should print timestamps by default', function() {
      const logger = new Logger({});
      const messageReceived = new Promise(resolve => {
        logger.on('info', resolve);
      });

      logger.info('Test message');

      return expect(messageReceived, 'when fulfilled', 'to match', /[0-9]{2}:[0-9]{2}:[0-9]{2}/);
    });

    it('should print scope', function() {
      const scope = 'scope';
      const logger = new Logger({ scope });

      const messageReceived = new Promise(resolve => {
        logger.on('info', resolve);
      });

      logger.info('Test message');

      return expect(messageReceived, 'when fulfilled', 'to contain', 'scope');
    });

    it('should not print timestamps if created with `timestamp: false`', function() {
      const logger = new Logger({ timestamp: false });
      const messageReceived = new Promise(resolve => {
        logger.on('info', resolve);
      });

      logger.info('Test message');

      return expect(messageReceived, 'when fulfilled', 'to match', /Test message\r?\n/);
    });
  });

  /** @test {Logger#prefix} */
  describe('#prefix', function() {
    it('should return a timestamp', function() {
      expect((new Logger()).prefix[0], 'to match', /\[.*[0-9]{2}:[0-9]{2}:[0-9]{2}.*\]/);
    });
  });

  /** @test {Logger#_format} */
  describe('#_format', function() {
    it('should join string arguments', function() {
      expect((new Logger())._format(['first', 'second']), 'to contain', 'first second');
    });

    it('should add a newline', function() {
      expect((new Logger())._format(['first', 'second']), 'to end with', '\n');
    });
  });

  /** @test {Logger#pipe} */
  describe('#pipe', function() {
    it('should write logs to stream', function(done) {
      const stream = new NoopTransformStream();

      const logger = new Logger()
        .pipe(stream);

      stream.on('data', msg => {
        expect(msg.toString(), 'to contain', 'Test');
        done();
      });

      logger.info('Test');
    });

    it('should remove listeners on close', function() {
      const stream = new NoopTransformStream();
      const logger = new Logger()
        .pipe(stream);

      stream.end();

      Logger.defaultTypes.forEach(type => {
        expect(logger.listenerCount(type), 'to be', 0);
      });
    });

    it('should handle logLevel option', function() {
      const onData = spy();
      const stream = new NoopTransformStream()
        .on('data', onData);
      const logger = new Logger()
        .pipe(stream, { logLevel: 2 });

      logger.info('Test info');
      logger.warn('Test warning');

      expect(onData.calledOnce, 'to be', true);
      expect(onData.lastCall.args[0].toString(), 'to contain', 'Test warning');
    });
  });
});