import { UtilRegistry } from './util-registry';
import { Utility } from '../../core/utility';

describe('UtilRegistry', () => {
  let utilRegistry;

  beforeEach(() => {
    utilRegistry = new UtilRegistry();
  });

  it('should create', () => {
    expect(utilRegistry).toBeTruthy();
  });

  describe('register()', () => {
    it('should allow to add utilities', () => {
      let foundUtil = utilRegistry.find(TestUtil1.name);
      expect(foundUtil).toBeFalsy();

      utilRegistry.register(TestUtil1);

      foundUtil = utilRegistry.find(TestUtil1.name);
      expect(foundUtil).toEqual(TestUtil1);
    });
  });

  describe('deregister()', () => {
    it('should remove util', () => {
      // register util
      utilRegistry.register(TestUtil1);
      let foundUtil = utilRegistry.find(TestUtil1.name);
      expect(foundUtil).toBeTruthy();

      // deregister util
      utilRegistry.deregister(TestUtil1.name);
      foundUtil = utilRegistry.find(TestUtil1.name);
      expect(foundUtil).toBeFalsy();
    });

    it('should destroy util instances if requested', () => {
      pending('TBD');
    });
  });

  describe('setup()', () => {

    it('should catch errors thrown by the utility', () => {
      expect(() => {
        utilRegistry.setup(ThrowingUtil);
      }).not.toThrow();
    });

    describe('scope has no matching elements', () => {
      it('should not construct an instance', () => {
        const scope = document.createElement('div');
        const instances = utilRegistry.setup(TestUtil1, scope);
        expect(instances.length).toBe(0);
      });

      it('should use fallback scope', () => {
        const instances = utilRegistry.setup(TestUtil1);
        expect(instances.length).toBe(0);
      });
    });

    describe('scope has matching elements', () => {
      let testScope;
      let testElement1;
      let testElement2;

      beforeEach(() => {
        testScope = document.createElement('div');
        testElement1 = document.createElement('div');
        testElement2 = document.createElement('div');
        testElement1.classList.add('util1');
        testElement2.classList.add('util1');
        testScope.appendChild(testElement1);
        testScope.appendChild(testElement2);
      });

      it('should construct a utility instance', () => {
        const setupUtilities = utilRegistry.setup(TestUtil1, testScope);
        expect(setupUtilities).toBeTruthy();
        expect(setupUtilities[0]).toBeTruthy();
      });

      it('should construct an instance for each matching element', () => {
        const setupUtilities = utilRegistry.setup(TestUtil1, testScope);
        expect(setupUtilities).toBeTruthy();
        expect(setupUtilities[0].element).toBe(testElement1);
        expect(setupUtilities[1].element).toBe(testElement2);
      });

      it('should pass the app instance', () => {
        const fakeApp = { };
        utilRegistry.setApp(fakeApp);

        const setupUtilities = utilRegistry.setup(TestUtil1, testScope);
        expect(setupUtilities).toBeTruthy();
        setupUtilities.forEach((setupUtility) => {
          expect(setupUtility).toBeTruthy();
          expect(setupUtility.instance).toBeTruthy();
        });
        expect(setupUtilities[0].instance.app).toBe(fakeApp);
        expect(setupUtilities[1].instance.app).toBe(fakeApp);
      });
    });
  });

  describe('initAll()', () => {
    it('should setup all the utilities', () => {
      spyOn(utilRegistry, 'setup');
      utilRegistry.register(TestUtil1);
      utilRegistry.register(TestUtil2);
      utilRegistry.register(TestUtil3);
      utilRegistry.initAll();

      expect(utilRegistry.setup.calls.count()).toBe(3);
      expect(utilRegistry.setup.calls.argsFor(0)).toEqual([TestUtil1, document.body]);
      expect(utilRegistry.setup.calls.argsFor(1)).toEqual([TestUtil2, document.body]);
      expect(utilRegistry.setup.calls.argsFor(2)).toEqual([TestUtil3, document.body]);
    });

    it('should pass the given scope', () => {
      spyOn(utilRegistry, 'setup');
      utilRegistry.register(TestUtil1);
      const scope = document.createElement('div');
      utilRegistry.initAll(scope);

      expect(utilRegistry.setup).toHaveBeenCalledWith(TestUtil1, scope);
    });

    describe('starts startable util instances', () => {
      let testScope,
          testElement1,
          testElement2,
          testElement3,
          testElement4;

      beforeEach(() => {
        testScope    = document.createElement('div');

        testElement1 = document.createElement('div');
        testElement2 = document.createElement('div');
        testElement3 = document.createElement('div');
        testElement4 = document.createElement('div');

        testElement1.classList.add('util1');
        testElement2.id = 'util2';
        testElement3.classList.add('util3');
        testElement4.classList.add('util3');

        testScope.appendChild(testElement1);
        testScope.appendChild(testElement2);
        testScope.appendChild(testElement3);
        testScope.appendChild(testElement4);
      });

      it('should start instances that provide a start function', () => {
        utilRegistry.register(TestUtil3);
        const initializedInstances = utilRegistry.initAll(testScope);

        expect(initializedInstances.length).toBe(2);
      });

      it('should not start instances that do not provide a start function', () => {
        utilRegistry.register(TestUtil1);
        utilRegistry.register(TestUtil2);
        const startedInstances = utilRegistry.initAll(testScope);
        
        expect(startedInstances.length).toBe(0);
      });
    });
  });
});

// test utilities
@Utility({ selector: '.util1' })
class TestUtil1 {
  constructor(element, app) {
    this.element = element;
    this.app = app;
  }
}

@Utility({ selector: '#util2' })
class TestUtil2 { }

@Utility({ selector: '.util3' })
class TestUtil3 {
  constructor() {}
  start() {}
}

@Utility({ selector: '#throws' })
class ThrowingUtil {
  constructor() {
    throw new Error();
  }
 }
