/**
 * @license
 * Copyright Google Inc. 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 {MockDirectory, setup} from '../aot/test_util';
import {compile, expectEmit} from './mock_compile';
/* These tests are codified version of the tests in compiler_canonical_spec.ts. Every
  * test in compiler_canonical_spec.ts should have a corresponding test here.
  */
describe('compiler compliance', () => {
  const angularFiles = setup({
    compileAngular: true,
    compileAnimations: false,
    compileCommon: true,
  });
  describe('elements', () => {
    it('should translate DOM structure', () => {
      const files = {
        app: {
          'spec.ts': `
              import {Component, NgModule} from '@angular/core';
              @Component({
                selector: 'my-component',
                template: \`
Hello World !
\`
              })
              export class MyComponent {}
              @NgModule({declarations: [MyComponent]})
              export class MyModule {}
          `
        }
      };
      // The factory should look like this:
      const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }';
      // The template should look like this (where IDENT is a wild card for an identifier):
      const template = `
        const $c1$ = ['class', 'my-app', 'title', 'Hello'];
        …
        template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
          if (cm) {
            $r3$.ɵE(0, 'div', $c1$);
            $r3$.ɵT(1, 'Hello ');
            $r3$.ɵE(2, 'b');
            $r3$.ɵT(3, 'World');
            $r3$.ɵe();
            $r3$.ɵT(4, '!');
            $r3$.ɵe();
          }
        }
      `;
      const result = compile(files, angularFiles);
      expectEmit(result.source, factory, 'Incorrect factory');
      expectEmit(result.source, template, 'Incorrect template');
    });
  });
  describe('components & directives', () => {
    it('should instantiate directives', () => {
      const files = {
        app: {
          'spec.ts': `
            import {Component, Directive, NgModule} from '@angular/core';
            @Component({selector: 'child', template: 'child-view'})
            export class ChildComponent {}
            @Directive({selector: '[some-directive]'})
            export class SomeDirective {}
            @Component({selector: 'my-component', template: ') { }
            }
            @Component({
              selector: 'my-component',
              template: ''
            })
            export class MyComponent {
              salutation = 'Hello';
            }
            @NgModule({declarations: [IfDirective, MyComponent]})
            export class MyModule {}
            `
        }
      };
      const IfDirectiveDefinition = `
        static ngDirectiveDef = $r3$.ɵdefineDirective({
          type: IfDirective,
          factory: function IfDirective_Factory() { return new IfDirective($r3$.ɵinjectTemplateRef()); }
        });`;
      const MyComponentDefinition = `
        const $c1$ = ['foo', ''];
        const $c2$ = [IfDirective];
        …
        static ngComponentDef = $r3$.ɵdefineComponent({
          type: MyComponent,
          tag: 'my-component',
          factory: function MyComponent_Factory() { return new MyComponent(); },
          template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
            if (cm) {
              $r3$.ɵE(0, 'ul', null, null, $c1$);
              $r3$.ɵC(2, $c2$, MyComponent_IfDirective_Template_2);
              $r3$.ɵe();
            }
            const $foo$ = $r3$.ɵld(1);
            IfDirective.ngDirectiveDef.h(3,2);
            $r3$.ɵcR(2);
            $r3$.ɵr(3,2);
            $r3$.ɵcr();
            function MyComponent_IfDirective_Template_2(ctx0: IDENT, cm: IDENT) {
              if (cm) {
                $r3$.ɵE(0, 'li');
                $r3$.ɵT(1);
                $r3$.ɵe();
              }
              $r3$.ɵt(1, $r3$.ɵi2('', ctx.salutation, ' ', $foo$, ''));
            }
          }
        });`;
      const result = compile(files, angularFiles);
      const source = result.source;
      expectEmit(source, IfDirectiveDefinition, 'Incorrect IfDirective.ngDirectiveDef');
      expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
    });
    describe('value composition', () => {
      it('should support array literals', () => {
        const files = {
          app: {
            'spec.ts': `
              import {Component, Input, NgModule} from '@angular/core';
              @Component({
                selector: 'my-comp',
                template: \`
                  {{ names[0] }}
                  {{ names[1] }}
                \`
              })
              export class MyComp {
                @Input() names: string[];
              }
              @Component({
                selector: 'my-app',
                template: \`
                
                 
              \`
              })
              export class MyApp {
                n0 = 'a';
                n1 = 'b';
                n2 = 'c';
                n3 = 'd';
                n4 = 'e';
                n5 = 'f';
                n6 = 'g';
                n7 = 'h';
                n8 = 'i';
              }
              @NgModule({declarations: [MyComp, MyApp]})
              export class MyModule {}
              `
          }
        };
        const MyAppDefinition = `
          const $e0_ff$ = ($v0$: $any$, $v1$: $any$, $v2$: $any$, $v3$: $any$, $v4$: $any$, $v5$: $any$, $v6$: $any$, $v7$: $any$, $v8$: $any$) => {
            return ['start-', $v0$, $v1$, $v2$, $v3$, $v4$, '-middle-', $v5$, $v6$, $v7$, $v8$, '-end'];
          }
          …
          static ngComponentDef = $r3$.ɵdefineComponent({
            type: MyApp,
            tag: 'my-app',
            factory: function MyApp_Factory() { return new MyApp(); },
            template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) {
              if (cm) {
                $r3$.ɵE(0, MyComp);
                $r3$.ɵe();
              }
              $r3$.ɵp(
                  0, 'names',
                  $r3$.ɵb($r3$.ɵfV($e0_ff$, ctx.n0, ctx.n1, ctx.n2, ctx.n3, ctx.n4, ctx.n5, ctx.n6, ctx.n7, ctx.n8)));
              MyComp.ngComponentDef.h(1, 0);
              $r3$.ɵr(1, 0);
            }
          });
        `;
        const result = compile(files, angularFiles);
        const source = result.source;
        expectEmit(source, MyAppDefinition, 'Invalid array binding');
      });
      it('should support object literals', () => {
        const files = {
          app: {
            'spec.ts': `
                import {Component, Input, NgModule} from '@angular/core';
                @Component({
                  selector: 'object-comp',
                  template: \`
                     {{ config['duration'] }} 
                     {{ config.animation }} 
                  \`
                })
                export class ObjectComp {
                  @Input() config: {[key: string]: any};
                }
                @Component({
                  selector: 'my-app',
                  template: \`
                   {{ config.animation }} 
                   {{config.actions[0].opacity }} 
                   {{config.actions[1].duration }} 
                \`
              })
              export class NestedComp {
                @Input() config: {[key: string]: any};
              }
              @Component({
                selector: 'my-app',
                template: \`
                
                 
              \`
              })
              export class MyApp {
                name = 'slide';
                duration = 100;
              }
              @NgModule({declarations: [NestedComp, MyApp]})
              export class MyModule {}
              `
          }
        };
        const MyAppDefinition = `
          const $c0$ = {opacity: 0, duration: 0};
          const $e0_ff$ = ($v$: any) => { return {opacity: 1, duration: $v$}; };
          const $e0_ff_1$ = ($v$: any) => { return [$c0$, $v$]; };
          const $e0_ff_2$ = ($v1$: any, $v2$: any) => { return {animation: $v1$, actions: $v2$}; };
          …
          static ngComponentDef = $r3$.ɵdefineComponent({
            type: MyApp,
            tag: 'my-app',
            factory: function MyApp_Factory() { return new MyApp(); },
            template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) {
              if (cm) {
                $r3$.ɵE(0, NestedComp);
                $r3$.ɵe();
              }
              $r3$.ɵp(
                  0, 'config',
                  $r3$.ɵb($r3$.ɵf2(
                      $e0_ff_2$, ctx.name, $r3$.ɵf1($e0_ff_1$, $r3$.ɵf1($e0_ff$, ctx.duration)))));
              NestedComp.ngComponentDef.h(1, 0);
              $r3$.ɵr(1, 0);
            }
          });
        `;
        const result = compile(files, angularFiles);
        const source = result.source;
        expectEmit(source, MyAppDefinition, 'Invalid array/object literal binding');
      });
    });
    it('should support content projection', () => {
      const files = {
        app: {
          'spec.ts': `
            import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
            @Component({selector: 'simple', template: '
'})
            export class SimpleComponent {}
            @Component({
              selector: 'complex',
              template: \`
                
                
\`
              })
            export class ComplexComponent { }
            @NgModule({declarations: [SimpleComponent, ComplexComponent]})
            export class MyModule {}
            @Component({
              selector: 'my-app',
              template: 'content  
              \`
            })
            export class ViewQueryComponent {
              @ViewChild(SomeDirective) someDir: SomeDirective;
            }
            @NgModule({declarations: [SomeDirective, ViewQueryComponent]})
            export class MyModule {}
          `
          }
        };
        const ViewQueryComponentDefinition = `
          const $e0_attrs$ = ['someDir',''];
          const $e1_dirs$ = [SomeDirective];
          …
          static ngComponentDef = $r3$.ɵdefineComponent({
            type: ViewQueryComponent,
            tag: 'view-query-component',
            factory: function ViewQueryComponent_Factory() { return new ViewQueryComponent(); },
            template: function ViewQueryComponent_Template(ctx: $ViewQueryComponent$, cm: $boolean$) {
              var $tmp$: $any$;
              if (cm) {
                $r3$.ɵQ(0, SomeDirective, true);
                $r3$.ɵE(1, 'div', $e0_attrs$, $e1_dirs$);
                $r3$.ɵe();
              }
              ($r3$.ɵqR(($tmp$ = $r3$.ɵld(0))) && (ctx.someDir = $tmp$.first));
              SomeDirective.ngDirectiveDef.h(2, 1);
              $r3$.ɵr(2, 1);
            }
          });`;
        const result = compile(files, angularFiles);
        const source = result.source;
        expectEmit(source, ViewQueryComponentDefinition, 'Invalid ViewQuery declaration');
      });
      it('should support content queries', () => {
        const files = {
          app: {
            ...directive,
            'spec.ts': `
            import {Component, ContentChild, NgModule} from '@angular/core';
            import {SomeDirective} from './some.directive';
            @Component({
              selector: 'content-query-component',
              template: \`
                
              \`
            })
            export class ContentQueryComponent {
              @ContentChild(SomeDirective) someDir: SomeDirective;
            }
            @Component({
              selector: 'my-app',
              template: \`
                
                  
                 
              \`
            })
            export class MyApp { }
            @NgModule({declarations: [SomeDirective, ContentQueryComponent, MyApp]})
            export class MyModule { }
            `
          }
        };
        const ContentQueryComponentDefinition = `
          static ngComponentDef = $r3$.ɵdefineComponent({
            type: ContentQueryComponent,
            tag: 'content-query-component',
            factory: function ContentQueryComponent_Factory() {
              return [new ContentQueryComponent(), $r3$.ɵQ(null, SomeDirective, true)];
            },
            hostBindings: function ContentQueryComponent_HostBindings(
                dirIndex: $number$, elIndex: $number$) {
              var $tmp$: $any$;
              ($r3$.ɵqR(($tmp$ = $r3$.ɵld(dirIndex)[1])) && ($r3$.ɵld(dirIndex)[0].someDir = $tmp$.first));
            },
            template: function ContentQueryComponent_Template(
                ctx: $ContentQueryComponent$, cm: $boolean$) {
              if (cm) {
                $r3$.ɵpD(0);
                $r3$.ɵE(1, 'div');
                $r3$.ɵP(2, 0);
                $r3$.ɵe();
              }
            }
          });`;
        const result = compile(files, angularFiles);
        const source = result.source;
        expectEmit(source, ContentQueryComponentDefinition, 'Invalid ContentQuery declaration');
      });
    });
    describe('pipes', () => {
      const files = {
        app: {
          'spec.ts': `
              import {Component, NgModule, Pipe, PipeTransform, OnDestroy} from '@angular/core';
              @Pipe({
                name: 'myPipe',
                pure: false
              })
              export class MyPipe implements PipeTransform,
                  OnDestroy {
                transform(value: any, ...args: any[]) { return value; }
                ngOnDestroy(): void {  }
              }
              @Pipe({
                name: 'myPurePipe',
                pure: true,
              })
              export class MyPurePipe implements PipeTransform {
                transform(value: any, ...args: any[]) { return value; }
              }
              @Component({selector: 'my-app', template: '{{name | myPipe:size | myPurePipe:size }}'})
              export class MyApp {
                name = 'World';
                size = 0;
              }
              @NgModule({declarations:[MyPipe, MyPurePipe, MyApp]})
              export class MyModule {}
          `
        }
      };
      it('should render pipes', () => {
        const MyPipeDefinition = `
            static ngPipeDef = $r3$.ɵdefinePipe(
                {type: MyPipe, factory: function MyPipe_Factory() { return new MyPipe(); }});
        `;
        const MyPurePipeDefinition = `
            static ngPipeDef = $r3$.ɵdefinePipe({
              type: MyPurePipe,
              factory: function MyPurePipe_Factory() { return new MyPurePipe(); },
              pure: true
            });`;
        const MyAppDefinition = `
            const $MyPurePipe_ngPipeDef$ = MyPurePipe.ngPipeDef;
            const $MyPipe_ngPipeDef$ = MyPipe.ngPipeDef;
            …
            static ngComponentDef = $r3$.ɵdefineComponent({
              type: MyApp,
              tag: 'my-app',
              factory: function MyApp_Factory() { return new MyApp(); },
              template: function MyApp_Template(ctx: IDENT, cm: IDENT) {
                if (cm) {
                  $r3$.ɵT(0);
                  $r3$.ɵPp(1, $MyPurePipe_ngPipeDef$, $MyPurePipe_ngPipeDef$.n());
                  $r3$.ɵPp(2, $MyPipe_ngPipeDef$, $MyPipe_ngPipeDef$.n());
                }
                $r3$.ɵt(0, $r3$.ɵi1('', $r3$.ɵpb2(1, $r3$.ɵpb2(2,ctx.name, ctx.size), ctx.size), ''));
              }
            });`;
        const result = compile(files, angularFiles);
        const source = result.source;
        expectEmit(source, MyPipeDefinition, 'Invalid pipe definition');
        expectEmit(source, MyPurePipeDefinition, 'Invalid pure pipe definition');
        expectEmit(source, MyAppDefinition, 'Invalid MyApp definition');
      });
    });
    it('local reference', () => {
      const files = {
        app: {
          'spec.ts': `
            import {Component, NgModule} from '@angular/core';
            @Component({selector: 'my-component', template: ') {}
              @Input() forOf: any[];
              ngOnChanges(simpleChanges: SimpleChanges) {
                if ('forOf' in simpleChanges) {
                  this.update();
                }
              }
              ngDoCheck(): void {
                const previous = this.previous;
                const current = this.forOf;
                if (!previous || previous.length != current.length ||
                    previous.some((value: any, index: number) => current[index] !== previous[index])) {
                  this.update();
                }
              }
              private update() {
                // TODO(chuckj): Not implemented yet
                // this.view.clear();
                if (this.forOf) {
                  const current = this.forOf;
                  for (let i = 0; i < current.length; i++) {
                    const context = {$implicit: current[i], index: i, even: i % 2 == 0, odd: i % 2 == 1};
                    // TODO(chuckj): Not implemented yet
                    // this.view.createEmbeddedView(this.template, context);
                  }
                  this.previous = [...this.forOf];
                }
              }
            }
          `
        }
      };
      it('should support a let variable and reference', () => {
        const files = {
          app: {
            ...shared,
            'spec.ts': `
              import {Component, NgModule} from '@angular/core';
              import {ForOfDirective} from './shared/for_of';
              @Component({
                selector: 'my-component',
                template: \`\`
              })
              export class MyComponent {
                items = [{name: 'one'}, {name: 'two'}];
              }
              @NgModule({
                declarations: [MyComponent, ForOfDirective]
              })
              export class MyModule {}
            `
          }
        };
        // TODO(chuckj): Enforce this when the directives are specified
        const ForDirectiveDefinition = `
          static ngDirectiveDef = $r3$.ɵdefineDirective({
            type: ForOfDirective,
            factory: function ForOfDirective_Factory() {
              return new ForOfDirective($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef());
            },
            features: [$r3$.ɵNgOnChangesFeature(NgForOf)],
            inputs: {forOf: 'forOf'}
          });
        `;
        const MyComponentDefinition = `
          const $c1$ = [ForOfDirective];
          …
          static ngComponentDef = $r3$.ɵdefineComponent({
            type: MyComponent,
            tag: 'my-component',
            factory: function MyComponent_Factory() { return new MyComponent(); },
            template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
              if (cm) {
                $r3$.ɵE(0, 'ul');
                $r3$.ɵC(1, $c1$, MyComponent_ForOfDirective_Template_1);
                $r3$.ɵe();
              }
              $r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
              ForOfDirective.ngDirectiveDef.h(2, 1);
              $r3$.ɵcR(1);
              $r3$.ɵr(2, 1);
              $r3$.ɵcr();
              function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
                if (cm) {
                  $r3$.ɵE(0, 'li');
                  $r3$.ɵT(1);
                  $r3$.ɵe();
                }
                const $item$ = ctx0.$implicit;
                $r3$.ɵt(1, $r3$.ɵi1('', $item$.name, ''));
              }
            }
          });
        `;
        const result = compile(files, angularFiles);
        const source = result.source;
        // TODO(chuckj): Enforce this when the directives are specified
        // expectEmit(source, ForDirectiveDefinition, 'Invalid directive definition');
        expectEmit(source, MyComponentDefinition, 'Invalid component definition');
      });
      it('should support accessing parent template variables', () => {
        const files = {
          app: {
            ...shared,
            'spec.ts': `
              import {Component, NgModule} from '@angular/core';
              import {ForOfDirective} from './shared/for_of';
              @Component({
                selector: 'my-component',
                template: \`
                
                  
                    {{item.name}}
                    
                      
                        {{item.name}}: {{info.description}}
                       
                     
                   
                 \`
              })
              export class MyComponent {
                items = [
                  {name: 'one', infos: [{description: '11'}, {description: '12'}]},
                  {name: 'two', infos: [{description: '21'}, {description: '22'}]}
                ];
              }
              @NgModule({
                declarations: [MyComponent, ForOfDirective]
              })
              export class MyModule {}
            `
          }
        };
        const MyComponentDefinition = `
          const $c1$ = [ForOfDirective];
          const $c2$ = ForOfDirective.ngDirectiveDef;
          …
          static ngComponentDef = $r3$.ɵdefineComponent({
            type: MyComponent,
            tag: 'my-component',
            factory: function MyComponent_Factory() { return new MyComponent(); },
            template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
              if (cm) {
                $r3$.ɵE(0, 'ul');
                $r3$.ɵC(1, $c1$, MyComponent_ForOfDirective_Template_1);
                $r3$.ɵe();
              }
              $r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
              $c2$.h(2,1);
              $r3$.ɵcR(1);
              $r3$.ɵr(2, 1);
              $r3$.ɵcr();
              function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
                if (cm) {
                  $r3$.ɵE(0, 'li');
                  $r3$.ɵE(1, 'div');
                  $r3$.ɵT(2);
                  $r3$.ɵe();
                  $r3$.ɵE(3, 'ul');
                  $r3$.ɵC(4, $c1$, MyComponent_ForOfDirective_ForOfDirective_Template_4);
                  $r3$.ɵe();
                  $r3$.ɵe();
                }
                const $item$ = ctx0.$implicit;
                $r3$.ɵp(4, 'forOf', $r3$.ɵb(IDENT.infos));
                $c2$.h(5,4);
                $r3$.ɵt(2, $r3$.ɵi1('', IDENT.name, ''));
                $r3$.ɵcR(4);
                $r3$.ɵr(5, 4);
                $r3$.ɵcr();
                function MyComponent_ForOfDirective_ForOfDirective_Template_4(
                    ctx1: IDENT, cm: IDENT) {
                  if (cm) {
                    $r3$.ɵE(0, 'li');
                    $r3$.ɵT(1);
                    $r3$.ɵe();
                  }
                  const $info$ = ctx1.$implicit;
                  $r3$.ɵt(1, $r3$.ɵi2(' ', $item$.name, ': ', $info$.description, ' '));
                }
              }
            }
          });`;
        const result = compile(files, angularFiles);
        const source = result.source;
        expectEmit(source, MyComponentDefinition, 'Invalid component definition');
      });
    });
  });
});