import * as BaseLib from 'src/lib/base';

describe('src/lib/base', () => {
  describe('SubscribableMixin', () => {
    const TestClass = class extends BaseLib.SubscribableMixin(BaseLib.HomeHubBaseClass) {
    };

    describe('constructor', () => {
      it('initializes the instance', () => {
        // Given
        const mixin = new TestClass();

        // Then
        expect(mixin.subscribers).toEqual([]);
      });
    });

    describe('notify', () => {
      it('notifies the subscribers', () => {
        // Given
        const mixin = new TestClass();

        const mockSubscriber = jasmine.createSpy();
        mixin.subscribers.push(mockSubscriber);
        mixin.subscribers.push(null);

        // When
        mixin.notify('test');

        // Then
        expect(mockSubscriber).toHaveBeenCalledWith('test', {});
      });

      it('allows sending a notification with user info', () => {
        // Given
        const mixin = new TestClass();

        const mockSubscriber = jasmine.createSpy();
        mixin.subscribers.push(mockSubscriber);

        // When
        mixin.notify('test', {reset: true});

        // Then
        expect(mockSubscriber).toHaveBeenCalledWith('test', {reset: true});
      });
    });

    describe('unsubscriberFactory', () => {
      it('returns an unsubscriber function', () => {
        // Given
        const mixin = new TestClass();

        // When
        const result = mixin.unsubscriberFactory(0);

        // Then
        expect(result).toBeInstanceOf(Function);
      });

      it('configures the unsubscriber function to remove the bound subscriber', () => {
        // Given
        const mixin = new TestClass();

        const mockSubscriber1 = jasmine.createSpy();
        mixin.subscribers.push(mockSubscriber1);

        const mockSubscriber2 = jasmine.createSpy();
        mixin.subscribers.push(mockSubscriber2);

        const mockSubscriber3 = jasmine.createSpy();
        mixin.subscribers.push(mockSubscriber3);

        const unsubscriber = mixin.unsubscriberFactory(1);

        // When
        unsubscriber();

        // Then
        expect(mixin.subscribers.length).toEqual(3);
        expect(mixin.subscribers[0]).toBe(mockSubscriber1);
        expect(mixin.subscribers[1]).toBe(null);
        expect(mixin.subscribers[2]).toBe(mockSubscriber3);
      });
    });

    describe('subscribe', () => {
      it('adds a subscriber and returns its unsubscriber', () => {
        // Given
        const mixin = new TestClass();

        const mockUnsubscriber = jasmine.createSpy();
        spyOn(mixin, 'unsubscriberFactory').and.returnValue = mockUnsubscriber;

        const mockSubscriber = jasmine.createSpy();

        // When
        mixin.subscribe(mockSubscriber);

        // Then
        expect(mixin.subscribers.length).toEqual(1);
        expect(mixin.subscribers[0]).toBe(mockSubscriber);
        expect(mixin.unsubscriberFactory).toHaveBeenCalledWith(0);
      });
    });
  });

  describe('EventSouceMixin', () => {
    const Base = BaseLib.EventSourceMixin(BaseLib.HomeHubBaseClass, ['start']);
    const TestClass = class extends Base {
    };

    describe('constructor', () => {
      it('initializes the instance', () => {
        // Given
        const mixin = new TestClass();

        // Then
        expect(mixin.eventListeners).toEqual({start: []});
      });
    });

    describe('addEventListener', () => {
      let mockListener = null;

      beforeEach(() => {
        mockListener = jasmine.createSpy();
      });

      it('ignores an uknown event', () => {
        // Given
        const mixin = new TestClass();

        // When
        mixin.addEventListener('testing', mockListener);

        // Then
        expect(mixin.eventListeners.testing).not.toBeDefined();
      });

      it('adds a listener for an event', () => {
        // Given
        const mixin = new TestClass();

        // When
        mixin.addEventListener('start', mockListener);

        // Then
        expect(mixin.eventListeners.start.length).toEqual(1);
        expect(mixin.eventListeners.start[0]).toBe(mockListener);
      });
    });

    describe('addEventListener', () => {
      let mockListener = null;

      beforeEach(() => {
        mockListener = jasmine.createSpy();
      });

      it('ignores an uknown event', () => {
        // Given
        const mixin = new TestClass();
        mixin.addEventListener('start', mockListener);

        // When
        mixin.removeEventListener('testing', mockListener);

        // Then
        expect(mixin.eventListeners.testing).not.toBeDefined();
      });

      it('ignores an unknown listener', () => {
        // Given
        const mixin = new TestClass();
        mixin.addEventListener('start', mockListener);

        const mockListener2 = jasmine.createSpy();

        // When
        mixin.removeEventListener('start', mockListener2);

        // Then
        expect(mixin.eventListeners.start.length).toEqual(1);
        expect(mixin.eventListeners.start[0]).toBe(mockListener);
      });

      it('removes a listener for an event', () => {
        // Given
        const mixin = new TestClass();
        mixin.addEventListener('start', mockListener);

        const mockListener2 = jasmine.createSpy();
        mixin.addEventListener('start', mockListener2);

        const mockListener3 = jasmine.createSpy();
        mixin.addEventListener('start', mockListener3);

        // When
        mixin.removeEventListener('start', mockListener2);

        // Then
        expect(mixin.eventListeners.start.length).toEqual(3);
        expect(mixin.eventListeners.start[0]).toBe(mockListener);
        expect(mixin.eventListeners.start[1]).toBe(null);
        expect(mixin.eventListeners.start[2]).toBe(mockListener3);
      });
    });

    describe('fireEvent', () => {
      let mockListener = null;

      beforeEach(() => {
        mockListener = jasmine.createSpy();
      });

      it('ignores an uknown event', () => {
        // Given
        const mixin = new TestClass();
        mixin.addEventListener('start', mockListener);

        // When
        mixin.fireEvent('testing', mockListener);

        // Then
        expect(mockListener).not.toHaveBeenCalled();
      });

      it('calls event listeners for an event', () => {
        // Given
        const mixin = new TestClass();
        mixin.addEventListener('start', mockListener);

        // When
        mixin.fireEvent('start', mockListener);

        // Then
        expect(mockListener).toHaveBeenCalledWith(mixin);
      });
    });
  });
});
