import {loadComponentProvider, loadComponent, setModuleLoader} from 'angular2less';
import {DirectiveMetadata, ComponentMetadata, PipeMetadata, Pipe, Component} from 'angular2/core';

describe('Dynamic component loader', function() {
  let loadModule;

  beforeEach(function() {
    loadModule
    setModuleLoader
  })

  it('allows to change module loader', function() {
    let path = 'core/app';
    let loadModule = createAndSetModuleLoader({ 'core/app': {} });

    loadComponent(path);

    expect(loadModule).to.have.been.called.with(path);
  });

  it('throws exception if more than one directive or component is found in the module', function() {
    createAndSetModuleLoader({
      'core/app': {
        OneComponent: function() {},
        AnotherComponent: function() {}
      }
    });

    return expect(loadComponent('core/app')).eventually.to.be.rejected;
  });

  describe('when module contain a class which name ends with "CustomAttribute"', function() {
    let klass, metadata;

    beforeEach(function() {
      klass = function NewHrefCustomAttribute() {}
      createAndSetModuleLoader({
        'core/app': { NewCustomAttribute: klass }
      });

      return loadComponent('core/app').then(function() {
        metadata = Reflect.getMetadata('annotations', klass)[0];
      });
    });

    it('adds Directive metadata to the class', function() {
      expect(metadata).to.be.instanceof(DirectiveMetadata);
    });

    it('sets "selector" metadata property to "[dasherizedClassName]" without "CustomAttribute" suffix', function() {
      expect(metadata.selector).to.equal('[new-href]');
    });

    it('does not load template', function() {
      expect(metadata.template).to.be.undefined;
    });
  });

  describe('when module contain a class which name does not end with "CustomElement"', function() {
    let klass, metadata, loadModule;

    beforeEach(function() {
      klass = function NewElement() {}
      loadModule = createAndSetModuleLoader({
        'core/app': {
          NewElement: klass,
          CustomPipe: function CustomPipe() {},
          WithMetadataPipe: Pipe({ name: 'another' })(function WithMetadataPipe() {})
        },
        'core/app.html': `
          <require from="core/pipes"></require>
          <require from="core/app.css"></require>
          <require from="core/component"></require>
          <h1>template</h1>
        `,
        'core/pipes': {
          MyPipe: function MyPipe() {},
          AnotherPipe: function AnotherPipe() {}
        },
        'core/app.css': 'a { color: red }',
        'core/component': {
          Component: Component({ template: 'component' })(function AnotherComponent() {})
        }
      });

      return loadComponent('core/app').then(function() {
        metadata = Reflect.getMetadata('annotations', klass)[0];
      });
    });

    it('adds Component metadata to the class', function() {
      expect(metadata).to.be.instanceof(ComponentMetadata);
    });

    it('sets "selector" metadata property to dasherized class name without "CustomElement" suffix', function() {
      expect(metadata.selector).to.equal('new-element');
    });

    it('sets "exportAs" metadata property', function() {
      expect(metadata.exportAs).to.equal('newElement');
    });

    it('requests template in the same filename and suffix ".html"', function() {
      expect(loadModule).to.have.been.called.with('core/app.html');
    });

    it('defines "template" metadata property', function() {
      let template = loadModule.schema['core/app.html'].replace(/\s*<require[^>]+><\/require>\s*/g, '');

      expect(metadata.template.trim()).to.equal(template.trim());
    });

    it('adds pipes from the same module into metadata', function() {
      expect(metadata.pipes).to.contain(loadModule.schema['core/app'].CustomPipe);
      expect(metadata.pipes).to.contain(loadModule.schema['core/app'].WithMetadataPipe);
    });

    it('adds metadata to pipes if it does not exists', function() {
      let module = loadModule.schema['core/app'];
      let customPipeMetadata = Reflect.getMetadata('annotations', module.CustomPipe)[0];
      let withMetadataPipeMetadata = Reflect.getMetadata('annotations', module.WithMetadataPipe)[0];

      expect(customPipeMetadata.name).to.equal('custom');
      expect(withMetadataPipeMetadata.name).to.equal('another');
    });

    it('adds pipes specified in "require[from]" element', function() {
      let module = loadModule.schema['core/pipes'];

      expect(metadata.pipes).to.contain(module.MyPipe);
      expect(metadata.pipes).to.contain(module.AnotherPipe);
    });

    it('adds directives specified in "require[from]" element', function() {
      expect(metadata.directives).to.contain(loadModule.schema['core/component'].Component);
    });

    it('adds styles specified in "require[from]" element', function() {
      expect(metadata.styles).to.contain(loadModule.schema['core/app.css']);
    });
  });

  function createModuleLoader(schema) {
    return function(path) {
      return new Promise(function(resolve, reject) {
        if (schema[path]) {
          return resolve(schema[path]);
        }

        reject(new Error(`Module "${path}" is not found.`));
      });
    };
  }

  function createAndSetModuleLoader(schema) {
    let loadModule = spy(createModuleLoader(schema || {}));

    loadModule.schema = schema;
    setModuleLoader(loadModule);

    return loadModule;
  }
});
