/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */

import {DirectiveResolver, ResourceLoader} from '@angular/compiler';
import {Compiler, Component, Injector, NgModule, NgModuleFactory, ɵstringify as stringify} from '@angular/core';
import {fakeAsync, inject, TestBed, tick, waitForAsync} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';

import {MockDirectiveResolver} from '../testing';

import {SpyResourceLoader} from './spies';

@Component({selector: 'child-cmp'})
class ChildComp {
}

@Component({selector: 'some-cmp', template: 'someComp'})
class SomeComp {
}

@Component({selector: 'some-cmp', templateUrl: './someTpl'})
class SomeCompWithUrlTemplate {
}

{
  describe('RuntimeCompiler', () => {
    describe('compilerComponentSync', () => {
      describe('never resolving loader', () => {
        class StubResourceLoader {
          get(url: string) {
            return new Promise(() => {});
          }
        }

        beforeEach(() => {
          TestBed.configureCompiler(
              {providers: [{provide: ResourceLoader, useClass: StubResourceLoader, deps: []}]});
        });

        it('should throw when using a templateUrl that has not been compiled before',
           waitForAsync(() => {
             TestBed.configureTestingModule({declarations: [SomeCompWithUrlTemplate]});
             TestBed.compileComponents().then(() => {
               expect(() => TestBed.createComponent(SomeCompWithUrlTemplate))
                   .toThrowError(`Can't compile synchronously as ${
                       stringify(SomeCompWithUrlTemplate)} is still being loaded!`);
             });
           }));

        it('should throw when using a templateUrl in a nested component that has not been compiled before',
           () => {
             TestBed.configureTestingModule({declarations: [SomeComp, ChildComp]});
             TestBed.overrideComponent(ChildComp, {set: {templateUrl: '/someTpl.html'}});
             TestBed.overrideComponent(SomeComp, {set: {template: '<child-cmp></child-cmp>'}});
             TestBed.compileComponents().then(() => {
               expect(() => TestBed.createComponent(SomeComp))
                   .toThrowError(`Can't compile synchronously as ${
                       stringify(ChildComp)} is still being loaded!`);
             });
           });
      });

      describe('resolving loader', () => {
        class StubResourceLoader {
          get(url: string) {
            return Promise.resolve('hello');
          }
        }

        beforeEach(() => {
          TestBed.configureCompiler(
              {providers: [{provide: ResourceLoader, useClass: StubResourceLoader, deps: []}]});
        });

        it('should allow to use templateUrl components that have been loaded before',
           waitForAsync(() => {
             TestBed.configureTestingModule({declarations: [SomeCompWithUrlTemplate]});
             TestBed.compileComponents().then(() => {
               const fixture = TestBed.createComponent(SomeCompWithUrlTemplate);
               expect(fixture.nativeElement).toHaveText('hello');
             });
           }));
      });
    });
  });

  describe('RuntimeCompiler', () => {
    let compiler: Compiler;
    let resourceLoader: SpyResourceLoader;
    let dirResolver: MockDirectiveResolver;
    let injector: Injector;

    beforeEach(() => {
      TestBed.configureCompiler({providers: [SpyResourceLoader.PROVIDE]});
    });

    beforeEach(fakeAsync(inject(
        [Compiler, ResourceLoader, DirectiveResolver, Injector],
        (_compiler: Compiler, _resourceLoader: SpyResourceLoader,
         _dirResolver: MockDirectiveResolver, _injector: Injector) => {
          compiler = _compiler;
          resourceLoader = _resourceLoader;
          dirResolver = _dirResolver;
          injector = _injector;
        })));

    describe('compileModuleAsync', () => {
      it('should allow to use templateUrl components', fakeAsync(() => {
           @NgModule({
             declarations: [SomeCompWithUrlTemplate],
             entryComponents: [SomeCompWithUrlTemplate]
           })
           class SomeModule {
           }

           resourceLoader.spy('get').and.callFake(() => Promise.resolve('hello'));
           let ngModuleFactory: NgModuleFactory<any> = undefined!;
           compiler.compileModuleAsync(SomeModule).then((f) => ngModuleFactory = f);
           tick();
           expect(ngModuleFactory.moduleType).toBe(SomeModule);
         }));
    });

    describe('compileModuleSync', () => {
      it('should throw when using a templateUrl that has not been compiled before', () => {
        @NgModule(
            {declarations: [SomeCompWithUrlTemplate], entryComponents: [SomeCompWithUrlTemplate]})
        class SomeModule {
        }

        resourceLoader.spy('get').and.callFake(() => Promise.resolve(''));
        expect(() => compiler.compileModuleSync(SomeModule))
            .toThrowError(`Can't compile synchronously as ${
                stringify(SomeCompWithUrlTemplate)} is still being loaded!`);
      });

      it('should throw when using a templateUrl in a nested component that has not been compiled before',
         () => {
           @NgModule({declarations: [SomeComp, ChildComp], entryComponents: [SomeComp]})
           class SomeModule {
           }

           resourceLoader.spy('get').and.callFake(() => Promise.resolve(''));
           dirResolver.setDirective(SomeComp, new Component({selector: 'some-cmp', template: ''}));
           dirResolver.setDirective(
               ChildComp, new Component({selector: 'child-cmp', templateUrl: '/someTpl.html'}));
           expect(() => compiler.compileModuleSync(SomeModule))
               .toThrowError(
                   `Can't compile synchronously as ${stringify(ChildComp)} is still being loaded!`);
         });

      it('should allow to use templateUrl components that have been loaded before',
         fakeAsync(() => {
           @NgModule({
             declarations: [SomeCompWithUrlTemplate],
             entryComponents: [SomeCompWithUrlTemplate]
           })
           class SomeModule {
           }

           resourceLoader.spy('get').and.callFake(() => Promise.resolve('hello'));
           compiler.compileModuleAsync(SomeModule);
           tick();

           const ngModuleFactory = compiler.compileModuleSync(SomeModule);
           expect(ngModuleFactory).toBeTruthy();
         }));
    });
  });
}
