From 35bf95281f2b3e4cdae545f6bcffb0638b4a0465 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Tue, 25 Sep 2018 15:44:53 -0700 Subject: [PATCH] test(ivy): implement tests for template type-checking (#26203) This commit builds on the NgtscTestEnvironment helper work before and introduces template_typecheck_spec.ts, which contains compiler tests for template type-checking. PR Close #26203 --- .../test/ngtsc/fake_core/index.ts | 2 + .../test/ngtsc/template_typecheck_spec.ts | 172 ++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts diff --git a/packages/compiler-cli/test/ngtsc/fake_core/index.ts b/packages/compiler-cli/test/ngtsc/fake_core/index.ts index 209d2f93e1..ea784dec32 100644 --- a/packages/compiler-cli/test/ngtsc/fake_core/index.ts +++ b/packages/compiler-cli/test/ngtsc/fake_core/index.ts @@ -39,6 +39,8 @@ export const ContentChild = callablePropDecorator(); export const ContentChildren = callablePropDecorator(); export const HostBinding = callablePropDecorator(); export const HostListener = callablePropDecorator(); +export const Input = callablePropDecorator(); +export const Output = callablePropDecorator(); export const ViewChild = callablePropDecorator(); export const ViewChildren = callablePropDecorator(); diff --git a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts new file mode 100644 index 0000000000..fde7bd21f2 --- /dev/null +++ b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts @@ -0,0 +1,172 @@ +/** + * @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 {NgtscTestEnvironment} from './env'; + +function setupCommon(env: NgtscTestEnvironment): void { + env.write('node_modules/@angular/common/index.d.ts', ` +import * as i0 from '@angular/core'; + +export declare class NgForOfContext { + $implicit: T; + ngForOf: T[]; + index: number; + count: number; + readonly first: boolean; + readonly last: boolean; + readonly even: boolean; + readonly odd: boolean; +} + +export declare class NgForOf { + ngForOf: T[]; + static ngTemplateContextGuard(dir: NgForOf, ctx: any): ctx is NgForOfContext; + static ngDirectiveDef: i0.ɵDirectiveDefWithMeta, '[ngFor][ngForOf]', never, {'ngForOf': 'ngForOf'}, {}, never>; +} + +export declare class NgIf { + ngIf: any; + static ngTemplateGuard_ngIf(dir: NgIf, expr: E): expr is NonNullable + static ngDirectiveDef: i0.ɵDirectiveDefWithMeta, '[ngIf]', never, {'ngIf': 'ngIf'}, {}, never>; +} + +export declare class CommonModule { + static ngModuleDef: i0.ɵNgModuleDefWithMeta; +} +`); +} + +describe('ngtsc type checking', () => { + if (!NgtscTestEnvironment.supported) { + // These tests should be excluded from the non-Bazel build. + return; + } + + let env !: NgtscTestEnvironment; + + beforeEach(() => { + env = NgtscTestEnvironment.setup(); + env.tsconfig({fullTemplateTypeCheck: true}); + setupCommon(env); + }); + + it('should check a simple component', () => { + env.write('test.ts', ` + import {Component, NgModule} from '@angular/core'; + + @Component({ + selector: 'test', + template: 'I am a simple template with no type info', + }) + class TestCmp {} + + @NgModule({ + declarations: [TestCmp], + }) + class Module {} + `); + + env.driveMain(); + }); + + it('should check basic usage of NgIf', () => { + env.write('test.ts', ` + import {CommonModule} from '@angular/common'; + import {Component, NgModule} from '@angular/core'; + + @Component({ + selector: 'test', + template: '
{{user.name}}
', + }) + class TestCmp { + user: {name: string}|null; + } + + @NgModule({ + declarations: [TestCmp], + imports: [CommonModule], + }) + class Module {} + `); + + env.driveMain(); + }); + + it('should check basic usage of NgFor', () => { + env.write('test.ts', ` + import {CommonModule} from '@angular/common'; + import {Component, NgModule} from '@angular/core'; + + @Component({ + selector: 'test', + template: '
{{user.name}}
', + }) + class TestCmp { + users: {name: string}[]; + } + + @NgModule({ + declarations: [TestCmp], + imports: [CommonModule], + }) + class Module {} + `); + + env.driveMain(); + }); + + it('should report an error inside the NgFor template', () => { + env.write('test.ts', ` + import {CommonModule} from '@angular/common'; + import {Component, NgModule} from '@angular/core'; + + @Component({ + selector: 'test', + template: '
{{user.does_not_exist}}
', + }) + class TestCmp { + users: {name: string}[]; + } + + @NgModule({ + declarations: [TestCmp], + imports: [CommonModule], + }) + class Module {} + `); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText).toContain('does_not_exist'); + }); + + it('should constrain types using type parameter bounds', () => { + env.write('test.ts', ` + import {CommonModule} from '@angular/common'; + import {Component, Input, NgModule} from '@angular/core'; + + @Component({ + selector: 'test', + template: '
{{user.does_not_exist}}
', + }) + class TestCmp { + @Input() users: T[]; + } + + @NgModule({ + declarations: [TestCmp], + imports: [CommonModule], + }) + class Module {} + `); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText).toContain('does_not_exist'); + }); +});