const { EventBus } = require('@jsdoc/util');
const { LEVELS, Logger } = require('../../../lib/logger');

const ARGUMENT_ERROR = 'ArgumentError';
const TYPE_ERROR = 'TypeError';

describe('@jsdoc/cli/lib/logger', () => {
  describe('Logger', () => {
    let bus;
    let logger;

    beforeEach(() => {
      bus = new EventBus('loggerTest', {
        _console: console,
        cache: false,
      });
      logger = new Logger({ emitter: bus });

      ['debug', 'error', 'info', 'warn'].forEach((func) => spyOn(console, func));
    });

    it('exports a Logger constructor', () => {
      expect(() => new Logger({ emitter: bus })).not.toThrow();
    });

    it('exports a LEVELS enum', () => {
      expect(LEVELS).toBeNonEmptyObject();
    });

    describe('constructor', () => {
      it('throws on invalid input', () => {
        expect(() => new Logger()).toThrowErrorOfType(ARGUMENT_ERROR);
      });

      it('accepts a valid emitter', () => {
        expect(() => new Logger({ emitter: bus })).not.toThrow();
      });

      it('throws on an invalid emitter', () => {
        expect(() => new Logger({ emitter: {} })).toThrowErrorOfType(ARGUMENT_ERROR);
      });

      it('accepts a valid level', () => {
        expect(
          () =>
            new Logger({
              emitter: bus,
              level: LEVELS.VERBOSE,
            })
        ).not.toThrow();
      });

      it('throws on an invalid level', () => {
        expect(
          () =>
            new Logger({
              emitter: bus,
              level: LEVELS.VERBOSE + 1,
            })
        ).toThrowErrorOfType(TYPE_ERROR);
      });
    });

    describe('events', () => {
      it('passes all event arguments through', () => {
        const args = ['My name is %s %s %s', 'foo', 'bar', 'baz'];
        const eventType = 'logger:info';

        logger.level = LEVELS.VERBOSE;
        bus.emit(eventType, ...args);

        expect(console.info).toHaveBeenCalledWith(...args);
      });

      it('logs logger:fatal events by default', () => {
        bus.emit('logger:fatal');

        expect(console.error).toHaveBeenCalled();
      });

      it('does not log logger:fatal events when level is SILENT', () => {
        logger.level = LEVELS.SILENT;
        bus.emit('logger:fatal');

        expect(console.error).not.toHaveBeenCalled();
      });

      it('logs logger:error events by default', () => {
        bus.emit('logger:error');

        expect(console.error).toHaveBeenCalled();
      });

      it('does not log logger:error events when level is FATAL', () => {
        logger.level = LEVELS.FATAL;
        bus.emit('logger:error');

        expect(console.error).not.toHaveBeenCalled();
      });

      it('logs logger:warn events by default', () => {
        bus.emit('logger:warn');

        expect(console.warn).toHaveBeenCalled();
      });

      it('does not log logger:warn events when level is ERROR', () => {
        logger.level = LEVELS.ERROR;
        bus.emit('logger:warn');

        expect(console.warn).not.toHaveBeenCalled();
      });

      it('does not log logger:info events by default', () => {
        bus.emit('logger:info');

        expect(console.info).not.toHaveBeenCalled();
      });

      it('logs logger:info events when level is INFO', () => {
        logger.level = LEVELS.INFO;
        bus.emit('logger:info');

        expect(console.info).toHaveBeenCalled();
      });

      it('does not log logger:debug events by default', () => {
        bus.emit('logger:debug');

        expect(console.debug).not.toHaveBeenCalled();
      });

      it('logs logger:debug events when level is DEBUG', () => {
        logger.level = LEVELS.DEBUG;
        bus.emit('logger:debug');

        expect(console.debug).toHaveBeenCalled();
      });

      it('does not log logger:verbose events by default', () => {
        bus.emit('logger:verbose');

        expect(console.debug).not.toHaveBeenCalled();
      });

      it('logs logger:verbose events when level is VERBOSE', () => {
        logger.level = LEVELS.VERBOSE;
        bus.emit('logger:verbose');

        expect(console.debug).toHaveBeenCalled();
      });
    });

    describe('level', () => {
      it('contains the current log level', () => {
        expect(logger.level).toBe(LEVELS.WARN);
      });

      it('throws when set to an invalid value', () => {
        expect(() => {
          logger.level = LEVELS.VERBOSE + 1;
        }).toThrowErrorOfType(TYPE_ERROR);
      });

      // The `events` tests set this property to valid values, so no need to test that
      // behavior again here.
    });
  });

  describe('LEVELS', () => {
    it('has a numeric SILENT property', () => {
      expect(LEVELS.SILENT).toBeWholeNumber();
    });

    it('has a numeric FATAL property', () => {
      expect(LEVELS.FATAL).toBeWholeNumber();
    });

    it('has a numeric ERROR property', () => {
      expect(LEVELS.ERROR).toBeWholeNumber();
    });

    it('has a numeric WARN property', () => {
      expect(LEVELS.WARN).toBeWholeNumber();
    });

    it('has a numeric INFO property', () => {
      expect(LEVELS.INFO).toBeWholeNumber();
    });

    it('has a numeric DEBUG property', () => {
      expect(LEVELS.DEBUG).toBeWholeNumber();
    });

    it('has a numeric VERBOSE property', () => {
      expect(LEVELS.VERBOSE).toBeWholeNumber();
    });

    it('orders the log levels correctly', () => {
      expect(LEVELS.SILENT).toBeLessThan(LEVELS.FATAL);
      expect(LEVELS.FATAL).toBeLessThan(LEVELS.ERROR);
      expect(LEVELS.ERROR).toBeLessThan(LEVELS.WARN);
      expect(LEVELS.WARN).toBeLessThan(LEVELS.INFO);
      expect(LEVELS.INFO).toBeLessThan(LEVELS.DEBUG);
      expect(LEVELS.DEBUG).toBeLessThan(LEVELS.VERBOSE);
    });
  });
});
