ci: remove old compliance tests (#41556)

Now that we can run the new compliance tests on Windows,
we can delete the old ones, simplifying and speeding up our CI.

PR Close #41556
This commit is contained in:
Pete Bacon Darwin 2021-04-10 17:35:56 +01:00 committed by Zach Arend
parent e16d234709
commit 4810f5c819
18 changed files with 0 additions and 13840 deletions

View File

@ -1,36 +0,0 @@
load("//tools:defaults.bzl", "jasmine_node_test", "ts_library")
ts_library(
name = "test_lib",
testonly = True,
srcs = glob(
["**/*.ts"],
),
visibility = [
":__subpackages__",
],
deps = [
"//packages:types",
"//packages/compiler",
"//packages/compiler-cli",
"//packages/compiler-cli/src/ngtsc/file_system",
"//packages/compiler-cli/test/compliance_old/mock_compile",
"//packages/compiler/test:test_utils",
"@npm//typescript",
],
)
jasmine_node_test(
name = "compliance_old",
bootstrap = ["//tools/testing:node_no_angular_es5"],
data = [
"//packages/compiler-cli/src/ngtsc/testing/fake_core:npm_package",
],
shard_count = 2,
tags = [
"ivy-only",
],
deps = [
":test_lib",
],
)

View File

@ -1,5 +0,0 @@
Tests in this directory should be run with:
```
yarn bazel test --config=ivy packages/compiler-cli/test/compliance:compliance
```

View File

@ -1,19 +0,0 @@
load("//tools:defaults.bzl", "ts_library")
ts_library(
name = "mock_compile",
testonly = True,
srcs = ["index.ts"],
visibility = [
"//packages/compiler-cli/test/compliance_old:__subpackages__",
],
deps = [
"//packages:types",
"//packages/compiler",
"//packages/compiler-cli",
"//packages/compiler-cli/src/ngtsc/core:api",
"//packages/compiler-cli/src/ngtsc/file_system",
"//packages/compiler/test:test_utils",
"@npm//typescript",
],
)

View File

@ -1,249 +0,0 @@
/**
* @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 {escapeRegExp} from '@angular/compiler/src/util';
import {arrayToMockDir, MockCompilerHost, MockData, MockDirectory, toMockFileArray} from '@angular/compiler/test/aot/test_util';
import * as ts from 'typescript';
import {NgCompilerOptions} from '../../../src/ngtsc/core/api';
import {NodeJSFileSystem, setFileSystem} from '../../../src/ngtsc/file_system';
import {NgtscProgram} from '../../../src/ngtsc/program';
const IDENTIFIER = /[A-Za-z_$ɵ][A-Za-z0-9_$]*/;
const OPERATOR =
/!|\?|%|\*|\/|\^|&&?|\|\|?|\(|\)|\{|\}|\[|\]|:|;|<=?|>=?|={1,3}|!==?|=>|\+\+?|--?|@|,|\.|\.\.\.|`|\\'/;
const STRING = /'(\\'|[^'])*'|"(\\"|[^"])*"/;
const BACKTICK_STRING = /\\`(([\s\S]*?)(\$\{[^}]*?\})?)*?[^\\]\\`/;
const BACKTICK_INTERPOLATION = /(\$\{[^}]*\})/;
const NUMBER = /\d+/;
const ELLIPSIS = '…';
const TOKEN = new RegExp(
`\\s*((${IDENTIFIER.source})|(${BACKTICK_STRING.source})|(${OPERATOR.source})|(${
STRING.source})|${NUMBER.source}|${ELLIPSIS})\\s*`,
'y');
type Piece = string|RegExp;
const SKIP = /(?:.|\n|\r)*/;
const ERROR_CONTEXT_WIDTH = 30;
// Transform the expected output to set of tokens
function tokenize(text: string): Piece[] {
// TOKEN.lastIndex is stateful so we cache the `lastIndex` and restore it at the end of the call.
const lastIndex = TOKEN.lastIndex;
TOKEN.lastIndex = 0;
let match: RegExpMatchArray|null;
let tokenizedTextEnd = 0;
const pieces: Piece[] = [];
while ((match = TOKEN.exec(text)) !== null) {
const [fullMatch, token] = match;
if (token === 'IDENT') {
pieces.push(IDENTIFIER);
} else if (token === ELLIPSIS) {
pieces.push(SKIP);
} else if (match = BACKTICK_STRING.exec(token)) {
pieces.push(...tokenizeBackTickString(token));
} else {
pieces.push(token);
}
tokenizedTextEnd += fullMatch.length;
}
if (pieces.length === 0 || tokenizedTextEnd < text.length) {
// The new token that could not be found is located after the
// last tokenized character.
const from = tokenizedTextEnd;
const to = from + ERROR_CONTEXT_WIDTH;
throw Error(
`Invalid test, no token found for "${text[tokenizedTextEnd]}" ` +
`(context = '${text.substr(from, to)}...'`);
}
// Reset the lastIndex in case we are in a recursive `tokenize()` call.
TOKEN.lastIndex = lastIndex;
return pieces;
}
/**
* Back-ticks are escaped as "\`" so we must strip the backslashes.
* Also the string will likely contain interpolations and if an interpolation holds an
* identifier we will need to match that later. So tokenize the interpolation too!
*/
function tokenizeBackTickString(str: string): Piece[] {
const pieces: Piece[] = ['`'];
// Unescape backticks that are inside the backtick string
// (we had to double escape them in the test string so they didn't look like string markers)
str = str.replace(/\\\\\\`/g, '\\`');
const backTickPieces = str.slice(2, -2).split(BACKTICK_INTERPOLATION);
backTickPieces.forEach((backTickPiece) => {
if (BACKTICK_INTERPOLATION.test(backTickPiece)) {
// An interpolation so tokenize this expression
pieces.push(...tokenize(backTickPiece));
} else {
// Not an interpolation so just add it as a piece
pieces.push(backTickPiece);
}
});
pieces.push('`');
return pieces;
}
export function expectEmit(
source: string, expected: string, description: string,
assertIdentifiers?: {[name: string]: RegExp}) {
// turns `// ...` into `…`
// remove `// TODO` comment lines
expected = expected.replace(/\/\/\s*\.\.\./g, ELLIPSIS).replace(/\/\/\s*TODO.*?\n/g, '');
const pieces = tokenize(expected);
const {regexp, groups} = buildMatcher(pieces);
const matches = source.match(regexp);
if (matches === null) {
let last: number = 0;
for (let i = 1; i < pieces.length; i++) {
const {regexp} = buildMatcher(pieces.slice(0, i));
const m = source.match(regexp);
const expectedPiece = pieces[i - 1] == IDENTIFIER ? '<IDENT>' : pieces[i - 1];
if (!m) {
// display at most `contextLength` characters of the line preceding the error location
const contextLength = 50;
const fullContext = source.substring(source.lastIndexOf('\n', last) + 1, last);
const context = fullContext.length > contextLength ?
`...${fullContext.substr(-contextLength)}` :
fullContext;
fail(`${description}: Failed to find "${expectedPiece}" after "${context}" in:\n'${
source.substr(0, last)}[<---HERE expected "${expectedPiece}"]${source.substr(last)}'`);
return;
} else {
last = (m.index || 0) + m[0].length;
}
}
fail(
`Test helper failure: Expected expression failed but the reporting logic could not find where it failed in: ${
source}`);
} else {
if (assertIdentifiers) {
// It might be possible to add the constraints in the original regexp (see `buildMatcher`)
// by transforming the assertion regexps when using anchoring, grouping, back references,
// flags, ...
//
// Checking identifiers after they have matched allows for a simple and flexible
// implementation.
// The overall performance are not impacted when `assertIdentifiers` is empty.
const ids = Object.keys(assertIdentifiers);
for (let i = 0; i < ids.length; i++) {
const id = ids[i];
if (groups.has(id)) {
const name = matches[groups.get(id) as number];
const regexp = assertIdentifiers[id];
if (!regexp.test(name)) {
throw Error(`${description}: The matching identifier "${id}" is "${
name}" which doesn't match ${regexp}`);
}
}
}
}
}
}
const IDENT_LIKE = /^[a-z][A-Z]/;
const MATCHING_IDENT = /^\$.*\$$/;
/*
* Builds a regexp that matches the given `pieces`
*
* It returns:
* - the `regexp` to be used to match the generated code,
* - the `groups` which maps `$...$` identifier to their position in the regexp matches.
*/
function buildMatcher(pieces: (string|RegExp)[]): {regexp: RegExp, groups: Map<string, number>} {
const results: string[] = [];
let first = true;
let group = 0;
const groups = new Map<string, number>();
for (const piece of pieces) {
if (!first)
results.push(`\\s${typeof piece === 'string' && IDENT_LIKE.test(piece) ? '+' : '*'}`);
first = false;
if (typeof piece === 'string') {
if (MATCHING_IDENT.test(piece)) {
const matchGroup = groups.get(piece);
if (!matchGroup) {
results.push('(' + IDENTIFIER.source + ')');
const newGroup = ++group;
groups.set(piece, newGroup);
} else {
results.push(`\\${matchGroup}`);
}
} else {
results.push(escapeRegExp(piece));
}
} else {
results.push('(?:' + piece.source + ')');
}
}
return {
regexp: new RegExp(results.join('')),
groups,
};
}
export function compileFiles(
data: MockDirectory, angularFiles: MockData, options: NgCompilerOptions = {}): {
fileName: string; source: string,
}[] {
setFileSystem(new NodeJSFileSystem());
const testFiles = toMockFileArray(data);
const scripts = testFiles.map(entry => entry.fileName);
const angularFilesArray = toMockFileArray(angularFiles);
const files = arrayToMockDir([...testFiles, ...angularFilesArray]);
const mockCompilerHost = new MockCompilerHost(scripts, files);
const program = new NgtscProgram(
scripts, {
target: ts.ScriptTarget.ES2015,
module: ts.ModuleKind.ES2015,
moduleResolution: ts.ModuleResolutionKind.NodeJs,
enableI18nLegacyMessageIdFormat: false,
...options,
},
mockCompilerHost);
program.emit();
return scripts.map(script => {
const fileName = script.replace(/\.ts$/, '.js');
const source = mockCompilerHost.readFile(fileName);
return {fileName, source};
});
}
function doCompile(data: MockDirectory, angularFiles: MockData, options: NgCompilerOptions = {}): {
source: string,
} {
const scripts = compileFiles(data, angularFiles, options);
const source = scripts.map(script => script.source).join('\n');
return {source};
}
export type CompileFn = typeof doCompile;
/**
* The actual compile function that will be used to compile the test code.
* This can be updated by a test bootstrap script to provide an alternative compile function.
*/
export let compile: CompileFn = doCompile;
/**
* Update the `compile` exported function to use a new implementation.
*/
export function setCompileFn(compileFn: CompileFn) {
compile = compileFn;
}

View File

@ -1,245 +0,0 @@
/**
* @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 {setup} from '@angular/compiler/test/aot/test_util';
import {compile, expectEmit} from './mock_compile';
describe('mock_compiler', () => {
// This produces a MockDirectory of the file needed to compile an Angular application.
// This setup is performed in a beforeAll which populates the map returned.
const angularFiles = setup({
compileAngular: false,
compileFakeCore: true,
compileAnimations: false,
});
describe('compiling', () => {
// To use compile you need to supply the files in a MockDirectory that can be merged
// with a set of "environment" files such as the angular files.
it('should be able to compile a simple application', () => {
const files = {
app: {
'hello.component.ts': `
import {Component, Input} from '@angular/core';
@Component({template: 'Hello {{name}}!'})
export class HelloComponent {
@Input() name: string = 'world';
}
`,
'hello.module.ts': `
import {NgModule} from '@angular/core';
import {HelloComponent} from './hello.component';
@NgModule({declarations: [HelloComponent]})
export class HelloModule {}
`
}
};
const result = compile(files, angularFiles);
// result.source contains just the emitted factory declarations regardless of the original
// module.
expect(result.source).toContain('Hello');
});
});
describe('expecting emitted output', () => {
it('should be able to find a simple expression in the output', () => {
const files = {
app: {
'hello.component.ts': `
import {Component, Input} from '@angular/core';
@Component({template: 'Hello {{name}}! Your name as {{name.length}} characters'})
export class HelloComponent {
@Input() name: string = 'world';
}
`,
'hello.module.ts': `
import {NgModule} from '@angular/core';
import {HelloComponent} from './hello.component';
@NgModule({declarations: [HelloComponent]})
export class HelloModule {}
`
}
};
const result = compile(files, angularFiles);
// The expression can expected directly.
expectEmit(result.source, 'name.length', 'name length expression not found');
// Whitespace is not significant
expectEmit(
result.source, 'name \n\n . \n length',
'name length expression not found (whitespace)');
});
it('should throw if the expected output contains unknown characters', () => {
const files = {
app: {
'test.ts': `ɵsayHello();`,
}
};
const result = compile(files, angularFiles);
expect(() => {
expectEmit(result.source, `ΔsayHello();`, 'Output does not match.');
}).toThrowError(/Invalid test, no token found for "Δ"/);
});
it('should be able to properly handle string literals with escaped quote', () => {
const files = {
app: {
'test.ts': String.raw`const identifier = "\"quoted\"";`,
}
};
const result = compile(files, angularFiles);
expect(() => {
expectEmit(result.source, String.raw`const $a$ = "\"quoted\"";`, 'Output does not match.');
}).not.toThrow();
});
});
it('should be able to skip untested regions (… and // ...)', () => {
const files = {
app: {
'hello.component.ts': `
import {Component, Input} from '@angular/core';
@Component({template: 'Hello {{name}}! Your name as {{name.length}} characters'})
export class HelloComponent {
@Input() name: string = 'world';
}
`,
'hello.module.ts': `
import {NgModule} from '@angular/core';
import {HelloComponent} from './hello.component';
@NgModule({declarations: [HelloComponent]})
export class HelloModule {}
`
}
};
const result = compile(files, angularFiles);
// The special character … means anything can be generated between the two sections allowing
// skipping sections of the output that are not under test. The ellipsis unicode char (…) is
// used instead of '...' because '...' is legal JavaScript (the spread operator) and might
// need to be tested. `// ...` could also be used in place of `…`.
expectEmit(result.source, 'ctx.name … ctx.name.length', 'could not find correct length access');
expectEmit(
result.source, 'ctx.name // ... ctx.name.length', 'could not find correct length access');
});
it('should be able to skip TODO comments (// TODO)', () => {
const files = {
app: {
'hello.component.ts': `
import {Component, Input} from '@angular/core';
@Component({template: 'Hello!'})
export class HelloComponent { }
`,
'hello.module.ts': `
import {NgModule} from '@angular/core';
import {HelloComponent} from './hello.component';
@NgModule({declarations: [HelloComponent]})
export class HelloModule {}
`
}
};
const result = compile(files, angularFiles);
expectEmit(
result.source, `
// TODO: this comment should not be taken into account
$r3$.ɵɵtext(0, "Hello!");
// TODO: this comment should not be taken into account
`,
'todo comments should be ignored');
});
it('should be able to enforce consistent identifiers', () => {
const files = {
app: {
'hello.component.ts': `
import {Component, Input} from '@angular/core';
@Component({template: 'Hello {{name}}! Your name as {{name.length}} characters'})
export class HelloComponent {
@Input() name: string = 'world';
}
`,
'hello.module.ts': `
import {NgModule} from '@angular/core';
import {HelloComponent} from './hello.component';
@NgModule({declarations: [HelloComponent]})
export class HelloModule {}
`
}
};
const result = compile(files, angularFiles);
// IDENT can be used a wild card for any identifier
expectEmit(result.source, 'IDENT.name', 'could not find context access');
// $<ident>$ can be used as a wild-card but all the content matched by the identifiers must
// match each other.
// This is useful if the code generator is free to invent a name but should use the name
// consistently.
expectEmit(
result.source, '$ctx$.$name$ … $ctx$.$name$.length',
'could not find correct length access');
});
it('should be able to enforce that identifiers match a regexp', () => {
const files = {
app: {
'hello.component.ts': `
import {Component, Input} from '@angular/core';
@Component({template: 'Hello {{name}}! Your name as {{name.length}} characters'})
export class HelloComponent {
@Input() name: string = 'world';
}
`,
'hello.module.ts': `
import {NgModule} from '@angular/core';
import {HelloComponent} from './hello.component';
@NgModule({declarations: [HelloComponent]})
export class HelloModule {}
`
}
};
const result = compile(files, angularFiles);
// Pass: `$n$` ends with `ME` in the generated code
expectEmit(result.source, '$ctx$.$n$ … $ctx$.$n$.length', 'Match names', {'$n$': /ME$/i});
// Fail: `$n$` does not match `/(not)_(\1)/` in the generated code
expect(() => {
expectEmit(
result.source, '$ctx$.$n$ … $ctx$.$n$.length', 'Match names', {'$n$': /(not)_(\1)/});
}).toThrowError(/"\$n\$" is "name" which doesn't match \/\(not\)_\(\\1\)\//);
});
});

View File

@ -1,41 +0,0 @@
load("//tools:defaults.bzl", "jasmine_node_test", "ts_library")
ts_library(
name = "prelink_bootstrap",
testonly = True,
srcs = ["bootstrap.ts"],
deps = [
"//packages:types",
"//packages/compiler-cli/linker",
"//packages/compiler-cli/linker/babel",
"//packages/compiler-cli/src/ngtsc/file_system/testing",
"//packages/compiler-cli/src/ngtsc/logging/testing",
"//packages/compiler-cli/test/compliance_old/mock_compile",
"@npm//@babel/core",
"@npm//@babel/generator",
"@npm//@babel/template",
"@npm//@babel/types",
"@npm//@types/babel__core",
"@npm//@types/babel__generator",
"@npm//@types/babel__template",
"@npm//typescript",
],
)
jasmine_node_test(
name = "prelink",
bootstrap = [
"//tools/testing:node_no_angular_es5",
":prelink_bootstrap_es5",
],
data = [
"//packages/compiler-cli/src/ngtsc/testing/fake_core:npm_package",
],
shard_count = 4,
tags = [
"ivy-only",
],
deps = [
"//packages/compiler-cli/test/compliance_old:test_lib",
],
)

View File

@ -1,79 +0,0 @@
/**
* @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 {PluginObj, transformSync} from '@babel/core';
import * as ts from 'typescript';
import {needsLinking} from '../../../linker';
import {createEs2015LinkerPlugin} from '../../../linker/babel';
import {MockFileSystemNative} from '../../../src/ngtsc/file_system/testing';
import {MockLogger} from '../../../src/ngtsc/logging/testing';
import {compileFiles, CompileFn, setCompileFn} from '../mock_compile';
/**
* A function to compile the given code in two steps:
*
* - first compile the code in partial mode
* - then compile the partially compiled code using the linker
*
* This should produce the same output as the full AOT compilation
*/
const linkedCompile: CompileFn = (data, angularFiles, options) => {
if (options !== undefined && options.target !== undefined &&
options.target < ts.ScriptTarget.ES2015) {
pending('ES5 is not supported in the partial compilation tests');
throw new Error('ES5 is not supported in the partial compilation tests');
}
const compiledFiles = compileFiles(data, angularFiles, {...options, compilationMode: 'partial'});
const fileSystem = new MockFileSystemNative();
const logger = new MockLogger();
const linkerPlugin = createEs2015LinkerPlugin({
fileSystem,
logger,
// enableI18nLegacyMessageIdFormat defaults to false in `compileFiles`.
enableI18nLegacyMessageIdFormat: false,
...options,
});
const source =
compiledFiles
.map(file => applyLinker({path: file.fileName, source: file.source}, linkerPlugin))
.join('\n');
return {source};
};
/**
* Runs the provided code through the Babel linker plugin, if the file has the .js extension.
*
* @param file The absolute file path and its source to be transformed using the linker.
* @param linkerPlugin The linker plugin to apply.
* @returns The file's source content, which has been transformed using the linker if necessary.
*/
function applyLinker(file: {path: string; source: string}, linkerPlugin: PluginObj): string {
if (!file.path.endsWith('.js') || !needsLinking(file.path, file.source)) {
return file.source;
}
const result = transformSync(file.source, {
filename: file.path,
plugins: [linkerPlugin],
parserOpts: {sourceType: 'unambiguous'},
});
if (result === null) {
throw fail('Babel transform did not have output');
}
if (result.code == null) {
throw fail('Babel transform result does not have any code');
}
return result.code;
}
// Update the function that will do the compiling with this specialised version that
// runs the prelink and postlink parts of AOT compilation, to check it produces the
// same result as a normal full AOT compile.
setCompileFn(linkedCompile);

View File

@ -1,398 +0,0 @@
/**
* @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 {MockDirectory, setup} from '@angular/compiler/test/aot/test_util';
import {compile, expectEmit} from './mock_compile';
describe('compiler compliance: dependency injection', () => {
const angularFiles = setup({
compileAngular: false,
compileFakeCore: true,
compileAnimations: false,
});
it('should create factory methods', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule, Injectable, Attribute, Host, SkipSelf, Self, Optional} from '@angular/core';
@Injectable()
export class MyService {}
function dynamicAttrName() {
return 'the-attr';
}
@Component({
selector: 'my-component',
template: \`\`
})
export class MyComponent {
constructor(
@Attribute('name') name:string,
@Attribute(dynamicAttrName()) other: string,
s1: MyService,
@Host() s2: MyService,
@Self() s4: MyService,
@SkipSelf() s3: MyService,
@Optional() s5: MyService,
@Self() @Optional() s6: MyService,
) {}
}
@NgModule({declarations: [MyComponent], providers: [MyService]})
export class MyModule {}
`
}
};
const factory = `
MyComponent.ɵfac = function MyComponent_Factory(t) {
return new (t || MyComponent)(
$r3$.ɵɵinjectAttribute('name'),
$r3$.ɵɵinjectAttribute(dynamicAttrName()),
$r3$.ɵɵdirectiveInject(MyService),
$r3$.ɵɵdirectiveInject(MyService, 1),
$r3$.ɵɵdirectiveInject(MyService, 2),
$r3$.ɵɵdirectiveInject(MyService, 4),
$r3$.ɵɵdirectiveInject(MyService, 8),
$r3$.ɵɵdirectiveInject(MyService, 10)
);
}`;
const result = compile(files, angularFiles);
expectEmit(result.source, factory, 'Incorrect factory');
});
it('should create a factory definition for an injectable', () => {
const files = {
app: {
'spec.ts': `
import {Injectable} from '@angular/core';
class MyDependency {}
@Injectable()
export class MyService {
constructor(dep: MyDependency) {}
}
`
}
};
const factory = `
MyService.ɵfac = function MyService_Factory(t) {
return new (t || MyService)($r3$.ɵɵinject(MyDependency));
}`;
const def = `
MyService.ɵprov = /*@__PURE__*/ $r3$.ɵɵdefineInjectable({
token: MyService,
factory: MyService.ɵfac
});
`;
const result = compile(files, angularFiles);
expectEmit(result.source, factory, 'Incorrect factory definition');
expectEmit(result.source, def, 'Incorrect injectable definition');
});
it('should create a factory definition for an injectable with an overloaded constructor', () => {
const files = {
app: {
'spec.ts': `
import {Injectable, Optional} from '@angular/core';
class MyDependency {}
class MyOptionalDependency {}
@Injectable()
export class MyService {
constructor(dep: MyDependency);
constructor(dep: MyDependency, @Optional() optionalDep?: MyOptionalDependency) {}
}
`
}
};
const factory = `
MyService.ɵfac = function MyService_Factory(t) {
return new (t || MyService)($r3$.ɵɵinject(MyDependency), $r3$.ɵɵinject(MyOptionalDependency, 8));
}`;
const def = `
MyService.ɵprov = /*@__PURE__*/ $r3$.ɵɵdefineInjectable({
token: MyService,
factory: MyService.ɵfac
});
`;
const result = compile(files, angularFiles);
expectEmit(result.source, factory, 'Incorrect factory definition');
expectEmit(result.source, def, 'Incorrect injectable definition');
});
it('should create a single factory def if the class has more than one decorator', () => {
const files = {
app: {
'spec.ts': `
import {Injectable, Pipe} from '@angular/core';
@Injectable()
@Pipe({name: 'my-pipe'})
export class MyPipe {
}
`
}
};
const result = compile(files, angularFiles).source;
const matches = result.match(/MyPipe\.ɵfac = function MyPipe_Factory/g);
expect(matches ? matches.length : 0).toBe(1);
});
it('should delegate directly to the alternate factory when setting `useFactory` without `deps`',
() => {
const files = {
app: {
'spec.ts': `
import {Injectable} from '@angular/core';
class MyAlternateService {}
function alternateFactory() {
return new MyAlternateService();
}
@Injectable({
useFactory: alternateFactory
})
export class MyService {
}
`
}
};
const def = `
MyService.ɵprov = /*@__PURE__*/ $r3$.ɵɵdefineInjectable({
token: MyService,
factory: function() {
return alternateFactory();
}
});
`;
const result = compile(files, angularFiles);
expectEmit(result.source, def, 'Incorrect injectable definition');
});
it('should not delegate directly to the alternate factory when setting `useFactory` with `deps`',
() => {
const files = {
app: {
'spec.ts': `
import {Injectable} from '@angular/core';
class SomeDep {}
class MyAlternateService {}
@Injectable({
useFactory: () => new MyAlternateFactory(),
deps: [SomeDep]
})
export class MyService {
}
`
}
};
const def = `
MyService.ɵprov = /*@__PURE__*/ $r3$.ɵɵdefineInjectable({
token: MyService,
factory: function MyService_Factory(t) {
let r = null;
if (t) {
r = new t();
} else {
r = (() => new MyAlternateFactory())($r3$.ɵɵinject(SomeDep));
}
return r;
}
});
`;
const result = compile(files, angularFiles);
expectEmit(result.source, def, 'Incorrect injectable definition');
});
it('should delegate directly to the alternate class factory when setting `useClass` without `deps`',
() => {
const files = {
app: {
'spec.ts': `
import {Injectable} from '@angular/core';
@Injectable()
class MyAlternateService {}
@Injectable({
useClass: MyAlternateService
})
export class MyService {
}
`
}
};
const factory = `
MyService.ɵprov = /*@__PURE__*/ $r3$.ɵɵdefineInjectable({
token: MyService,
factory: function(t) {
return MyAlternateService.ɵfac(t);
}
});
`;
const result = compile(files, angularFiles);
expectEmit(result.source, factory, 'Incorrect factory definition');
});
it('should not delegate directly to the alternate class when setting `useClass` with `deps`',
() => {
const files = {
app: {
'spec.ts': `
import {Injectable} from '@angular/core';
class SomeDep {}
@Injectable()
class MyAlternateService {}
@Injectable({
useClass: MyAlternateService,
deps: [SomeDep]
})
export class MyService {
}
`
}
};
const factory = `
MyService.ɵprov = /*@__PURE__*/ $r3$.ɵɵdefineInjectable({
token: MyService,
factory: function MyService_Factory(t) {
let r = null;
if (t) {
r = new t();
} else {
r = new MyAlternateService($r3$.ɵɵinject(SomeDep));
}
return r;
}
});
`;
const result = compile(files, angularFiles);
expectEmit(result.source, factory, 'Incorrect factory definition');
});
it('should unwrap forward refs when delegating to a different class', () => {
const files = {
app: {
'spec.ts': `
import {Injectable, forwardRef} from '@angular/core';
@Injectable({providedIn: 'root', useClass: forwardRef(() => SomeProviderImpl)})
abstract class SomeProvider {
}
@Injectable()
class SomeProviderImpl extends SomeProvider {
}
`
}
};
const factory = `
SomeProvider.ɵprov = /*@__PURE__*/ $r3$.ɵɵdefineInjectable({
token: SomeProvider,
factory: function(t) {
return SomeProviderImpl.ɵfac(t);
},
providedIn: 'root'
});
`;
const result = compile(files, angularFiles);
expectEmit(result.source, factory, 'Incorrect factory definition');
});
it('should have the pipe factory take precedence over the injectable factory, if a class has multiple decorators',
() => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule, Pipe, PipeTransform, Injectable} from '@angular/core';
@Injectable()
class Service {}
@Injectable()
@Pipe({name: 'myPipe'})
export class MyPipe implements PipeTransform {
constructor(service: Service) {}
transform(value: any, ...args: any[]) { return value; }
}
@Pipe({name: 'myOtherPipe'})
@Injectable()
export class MyOtherPipe implements PipeTransform {
constructor(service: Service) {}
transform(value: any, ...args: any[]) { return value; }
}
@Component({
selector: 'my-app',
template: '{{0 | myPipe | myOtherPipe}}'
})
export class MyApp {}
@NgModule({declarations: [MyPipe, MyOtherPipe, MyApp], declarations: [Service]})
export class MyModule {}
`
}
};
const result = compile(files, angularFiles);
const source = result.source;
// The prov definition must be last so MyPipe.fac is defined
const MyPipeDefs = `
MyPipe.ɵfac = function MyPipe_Factory(t) { return new (t || MyPipe)(i0.ɵɵdirectiveInject(Service, 16)); };
MyPipe.ɵpipe = /*@__PURE__*/ i0.ɵɵdefinePipe({ name: "myPipe", type: MyPipe, pure: true });
MyPipe.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: MyPipe, factory: MyPipe.ɵfac });
`;
// The prov definition must be last so MyOtherPipe.fac is defined
const MyOtherPipeDefs = `
MyOtherPipe.ɵfac = function MyOtherPipe_Factory(t) { return new (t || MyOtherPipe)($r3$.ɵɵdirectiveInject(Service, 16)); };
MyOtherPipe.ɵpipe = /*@__PURE__*/ i0.ɵɵdefinePipe({ name: "myOtherPipe", type: MyOtherPipe, pure: true });
MyOtherPipe.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: MyOtherPipe, factory: MyOtherPipe.ɵfac });
`;
expectEmit(source, MyPipeDefs, 'Invalid pipe factory function');
expectEmit(source, MyOtherPipeDefs, 'Invalid pipe factory function');
expect(source.match(/MyPipe\.ɵfac =/g)!.length).toBe(1);
expect(source.match(/MyOtherPipe\.ɵfac =/g)!.length).toBe(1);
});
});

View File

@ -1,410 +0,0 @@
/**
* @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 {AttributeMarker} from '@angular/compiler/src/core';
import {setup} from '@angular/compiler/test/aot/test_util';
import {compile, expectEmit} from './mock_compile';
describe('compiler compliance: directives', () => {
const angularFiles = setup({
compileAngular: false,
compileAnimations: false,
compileFakeCore: true,
});
describe('matching', () => {
it('should not match directives on i18n attribute', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule} from '@angular/core';
@Directive({selector: '[i18n]'})
export class I18nDirective {}
@Component({selector: 'my-component', template: '<div i18n></div>'})
export class MyComponent {}
@NgModule({declarations: [I18nDirective, MyComponent]})
export class MyModule{}`
}
};
// MyComponent definition should be:
const MyComponentDefinition = `
MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
type: MyComponent,
selectors: [["my-component"]],
decls: 1,
vars: 0,
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵelement(0, "div");
}
},
encapsulation: 2
});
`;
const MyComponentFactory = `
MyComponent.ɵfac = function MyComponent_Factory(t) { return new (t || MyComponent)(); };
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, MyComponentDefinition, 'Incorrect ChildComponent.ɵcmp');
expectEmit(source, MyComponentFactory, 'Incorrect ChildComponent.ɵfac');
});
it('should not match directives on i18n-prefixed attributes', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule} from '@angular/core';
@Directive({selector: '[i18n]'})
export class I18nDirective {}
@Directive({selector: '[i18n-foo]'})
export class I18nFooDirective {}
@Directive({selector: '[foo]'})
export class FooDirective {}
@Component({selector: 'my-component', template: '<div i18n-foo></div>'})
export class MyComponent {}
@NgModule({declarations: [I18nDirective, I18nFooDirective, FooDirective, MyComponent]})
export class MyModule{}`
}
};
// MyComponent definition should be:
const MyComponentDefinition = `
MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
type: MyComponent,
selectors: [["my-component"]],
decls: 1,
vars: 0,
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵelement(0, "div");
}
},
encapsulation: 2
});
`;
const MyComponentFactory = `
MyComponent.ɵfac = function MyComponent_Factory(t) { return new (t || MyComponent)(); };
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, MyComponentDefinition, 'Incorrect ChildComponent.ɵcmp');
expectEmit(source, MyComponentFactory, 'Incorrect ChildComponent.ɵfac');
});
it('should match directives on property bindings', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, Input, NgModule} from '@angular/core';
@Directive({selector: '[someDirective]'})
export class SomeDirective {
@Input() someDirective;
}
@Component({selector: 'my-component', template: '<div [someDirective]="true"></div>'})
export class MyComponent {}
@NgModule({declarations: [SomeDirective, MyComponent]})
export class MyModule{}
`
}
};
// MyComponent definition should be:
const MyComponentDefinition = `
MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
consts: [[${AttributeMarker.Bindings}, "someDirective"]],
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵelement(0, "div", 0);
}
if (rf & 2) {
$r3$.ɵɵproperty("someDirective", true);
}
},
directives: [SomeDirective],
encapsulation: 2
});
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, MyComponentDefinition, 'Incorrect ChildComponent.ɵcmp');
});
it('should match directives on ng-templates', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
@Directive({
selector: 'ng-template[directiveA]'
})
export class DirectiveA {
constructor(public templateRef: TemplateRef<any>) {}
}
@Component({
selector: 'my-component',
template: \`
<ng-template directiveA>Some content</ng-template>
\`
})
export class MyComponent {}
@NgModule({declarations: [DirectiveA, MyComponent]})
export class MyModule{}
`
}
};
const MyComponentDefinition = `
function MyComponent_ng_template_0_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵtext(0, "Some content");
}
}
MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
consts: [["directiveA", ""]],
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵtemplate(0, MyComponent_ng_template_0_Template, 1, 0, "ng-template", 0);
}
},
directives: [DirectiveA],
});
`;
const result = compile(files, angularFiles);
expectEmit(result.source, MyComponentDefinition, 'Incorrect ChildComponent.ɵcmp');
});
it('should match directives on ng-container', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
@Directive({
selector: 'ng-container[directiveA]'
})
export class DirectiveA {
constructor(public templateRef: TemplateRef<any>) {}
}
@Component({
selector: 'my-component',
template: \`
<ng-container *ngIf="showing" directiveA>Some content</ng-container>
\`
})
export class MyComponent {}
@NgModule({declarations: [DirectiveA, MyComponent]})
export class MyModule{}
`
}
};
const MyComponentDefinition = `
function MyComponent_ng_container_0_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵelementContainerStart(0, 1);
$r3$.ɵɵtext(1, "Some content");
$r3$.ɵɵelementContainerEnd();
}
}
MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
consts: [["directiveA", "", ${AttributeMarker.Template}, "ngIf"], ["directiveA", ""]],
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵtemplate(0, MyComponent_ng_container_0_Template, 2, 0, "ng-container", 0);
}
if (rf & 2) {
$r3$.ɵɵproperty("ngIf", ctx.showing);
}
},
directives: [DirectiveA],
});
`;
const result = compile(files, angularFiles);
expectEmit(result.source, MyComponentDefinition, 'Incorrect ChildComponent.ɵcmp');
});
it('should match directives on ng-template bindings', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, Input, NgModule} from '@angular/core';
@Directive({selector: '[someDirective]'})
export class SomeDirective {
@Input() someDirective;
}
@Component({selector: 'my-component', template: '<ng-template [someDirective]="true"></ng-template>'})
export class MyComponent {}
@NgModule({declarations: [SomeDirective, MyComponent]})
export class MyModule{}
`
}
};
// MyComponent definition should be:
const MyComponentDefinition = `
MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
consts: [[${AttributeMarker.Bindings}, "someDirective"]],
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵtemplate(0, MyComponent_ng_template_0_Template, 0, 0, "ng-template", 0);
}
if (rf & 2) {
$r3$.ɵɵproperty("someDirective", true);
}
},
directives: [SomeDirective],
encapsulation: 2
});
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, MyComponentDefinition, 'Incorrect ChildComponent.ɵcmp');
});
it('should match structural directives', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, Input, NgModule} from '@angular/core';
@Directive({selector: '[someDirective]'})
export class SomeDirective {
@Input() someDirective;
}
@Component({selector: 'my-component', template: '<div *someDirective></div>'})
export class MyComponent {}
@NgModule({declarations: [SomeDirective, MyComponent]})
export class MyModule{}
`
}
};
// MyComponent definition should be:
const MyComponentDefinition = `
MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
consts: [[${AttributeMarker.Template}, "someDirective"]],
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵtemplate(0, MyComponent_div_0_Template, 1, 0, "div", 0);
}
},
directives: [SomeDirective],
encapsulation: 2
});
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, MyComponentDefinition, 'Incorrect ChildComponent.ɵcmp');
});
it('should match directives on element outputs', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, Output, EventEmitter, NgModule} from '@angular/core';
@Directive({selector: '[someDirective]'})
export class SomeDirective {
@Output() someDirective = new EventEmitter();
}
@Component({selector: 'my-component', template: '<div (someDirective)="noop()"></div>'})
export class MyComponent {
noop() {}
}
@NgModule({declarations: [SomeDirective, MyComponent]})
export class MyModule{}
`
}
};
// MyComponent definition should be:
const MyComponentDefinition = `
MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
consts: [[${AttributeMarker.Bindings}, "someDirective"]],
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵelementStart(0, "div", 0);
$r3$.ɵɵlistener("someDirective", function MyComponent_Template_div_someDirective_0_listener() { return ctx.noop(); });
$r3$.ɵɵelementEnd();
}
},
directives: [SomeDirective],
encapsulation: 2
});
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, MyComponentDefinition, 'Incorrect ChildComponent.ɵcmp');
});
});
});

View File

@ -1,88 +0,0 @@
/**
* @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 {MockDirectory, setup} from '@angular/compiler/test/aot/test_util';
import {compile, expectEmit} from './mock_compile';
describe('compiler compliance: listen()', () => {
const angularFiles = setup({
compileAngular: false,
compileFakeCore: true,
compileAnimations: false,
});
it('should declare inputs/outputs', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule, Input, Output} from '@angular/core';
@Component({
selector: 'my-component',
template: \`\`
})
export class MyComponent {
@Input() componentInput;
@Input('renamedComponentInput') originalComponentInput;
@Output() componentOutput;
@Output('renamedComponentOutput') originalComponentOutput;
}
@Directive({
selector: '[my-directive]',
})
export class MyDirective {
@Input() directiveInput;
@Input('renamedDirectiveInput') originalDirectiveInput;
@Output() directiveOutput;
@Output('renamedDirectiveOutput') originalDirectiveOutput;
}
@NgModule({declarations: [MyComponent, MyDirective]})
export class MyModule {}
`
}
};
const componentDef = `
MyComponent.ɵcmp = /*@__PURE__*/ IDENT.ɵɵdefineComponent({
inputs:{
componentInput: "componentInput",
originalComponentInput: ["renamedComponentInput", "originalComponentInput"]
},
outputs: {
componentOutput: "componentOutput",
originalComponentOutput: "renamedComponentOutput"
}
});`;
const directiveDef = `
MyDirective.ɵdir = /*@__PURE__*/ IDENT.ɵɵdefineDirective({
inputs:{
directiveInput: "directiveInput",
originalDirectiveInput: ["renamedDirectiveInput", "originalDirectiveInput"]
},
outputs: {
directiveOutput: "directiveOutput",
originalDirectiveOutput: "renamedDirectiveOutput"
}
});`;
const result = compile(files, angularFiles);
expectEmit(result.source, componentDef, 'Incorrect component definition');
expectEmit(result.source, directiveDef, 'Incorrect directive definition');
});
});

View File

@ -1,583 +0,0 @@
/**
* @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 {AttributeMarker} from '@angular/compiler/src/core';
import {setup} from '@angular/compiler/test/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: listen()', () => {
const angularFiles = setup({
compileAngular: false,
compileFakeCore: true,
compileAnimations: false,
});
it('should create listener instruction on element', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`<div (click)="onClick($event); 1 == 2"></div>\`
})
export class MyComponent {
onClick(event: any) {}
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
// The template should look like this (where IDENT is a wild card for an identifier):
const template = `
consts: [[${AttributeMarker.Bindings}, "click"]],
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵelementStart(0, "div", 0);
$r3$.ɵɵlistener("click", function MyComponent_Template_div_click_0_listener($event) {
ctx.onClick($event);
return 1 == 2;
});
$r3$.ɵɵelementEnd();
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
it('should create listener instruction on other components', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-app',
template: \`<div>My App</div>\`
})
export class MyApp {}
@Component({
selector: 'my-component',
template: \`<my-app (click)="onClick($event);"></my-app>\`
})
export class MyComponent {
onClick(event: any) {}
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const template = `
consts: [[${AttributeMarker.Bindings}, "click"]],
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵelementStart(0, "my-app", 0);
$r3$.ɵɵlistener("click", function MyComponent_Template_my_app_click_0_listener($event) {
return ctx.onClick($event);
});
$r3$.ɵɵelementEnd();
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
it('should create multiple listener instructions that share a view snapshot', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`
<div *ngIf="showing">
<div (click)="onClick(foo)"></div>
<button (click)="onClick2(bar)"></button>
</div>
\`
})
export class MyComponent {
onClick(name: any) {}
onClick2(name: any) {}
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const template = `
function MyComponent_div_0_Template(rf, ctx) {
if (rf & 1) {
const $s$ = $r3$.ɵɵgetCurrentView();
$r3$.ɵɵelementStart(0, "div");
$r3$.ɵɵelementStart(1, "div", 1);
$r3$.ɵɵlistener("click", function MyComponent_div_0_Template_div_click_1_listener() {
$r3$.ɵɵrestoreView($s$);
const $comp$ = $r3$.ɵɵnextContext();
return $comp$.onClick($comp$.foo);
});
$r3$.ɵɵelementEnd();
$r3$.ɵɵelementStart(2, "button", 1);
$r3$.ɵɵlistener("click", function MyComponent_div_0_Template_button_click_2_listener() {
$r3$.ɵɵrestoreView($s$);
const $comp2$ = $r3$.ɵɵnextContext();
return $comp2$.onClick2($comp2$.bar);
});
$r3$.ɵɵelementEnd();
$r3$.ɵɵelementEnd();
}
}
// ...
consts: [[${AttributeMarker.Template}, "ngIf"], [${AttributeMarker.Bindings}, "click"]],
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵtemplate(0, MyComponent_div_0_Template, 3, 0, "div", 0);
}
if (rf & 2) {
$i0$.ɵɵproperty("ngIf", ctx.showing);
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
it('local refs in listeners defined before the local refs', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`
<button (click)="onClick(user.value)">Save</button>
<input #user>
\`
})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const MyComponentDefinition = `
MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
type: MyComponent,
selectors: [["my-component"]],
decls: 4,
vars: 0,
consts: [[${AttributeMarker.Bindings}, "click"], ["user", ""]],
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
const $s$ = $r3$.ɵɵgetCurrentView();
$r3$.ɵɵelementStart(0, "button", 0);
$r3$.ɵɵlistener("click", function MyComponent_Template_button_click_0_listener() {
$r3$.ɵɵrestoreView($s$);
const $user$ = $r3$.ɵɵreference(3);
return ctx.onClick($user$.value);
});
$r3$.ɵɵtext(1, "Save");
$r3$.ɵɵelementEnd();
$r3$.ɵɵelement(2, "input", null, 1);
}
},
encapsulation: 2
});
`;
const MyComponentFactory = `
MyComponent.ɵfac = function MyComponent_Factory(t) { return new (t || MyComponent)(); };
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ɵcmp');
expectEmit(source, MyComponentFactory, 'Incorrect MyComponent.ɵfac');
});
it('should chain multiple listeners on the same element', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`<div (click)="click()" (change)="change()"></div>\`
})
export class MyComponent {
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const template = `
consts: [[${AttributeMarker.Bindings}, "click", "change"]],
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵelementStart(0, "div", 0);
$r3$.ɵɵlistener("click", function MyComponent_Template_div_click_0_listener() {
return ctx.click();
})("change", function MyComponent_Template_div_change_0_listener() {
return ctx.change();
});
$r3$.ɵɵelementEnd();
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
it('should chain multiple listeners across elements', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`
<div (click)="click()" (change)="change()"></div>
<some-comp (update)="update()" (delete)="delete()"></some-comp>
\`
})
export class MyComponent {
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const template = `
consts: [[${AttributeMarker.Bindings}, "click", "change"], [${
AttributeMarker.Bindings}, "update", "delete"]],
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵelementStart(0, "div", 0);
$r3$.ɵɵlistener("click", function MyComponent_Template_div_click_0_listener() { return ctx.click(); })("change", function MyComponent_Template_div_change_0_listener() { return ctx.change(); });
$r3$.ɵɵelementEnd();
$r3$.ɵɵelementStart(1, "some-comp", 1);
$r3$.ɵɵlistener("update", function MyComponent_Template_some_comp_update_1_listener() { return ctx.update(); })("delete", function MyComponent_Template_some_comp_delete_1_listener() { return ctx.delete(); });
$r3$.ɵɵelementEnd();
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
it('should chain multiple listeners on the same template', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`<ng-template (click)="click()" (change)="change()"></ng-template>\`
})
export class MyComponent {
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const template = `
consts: [[${AttributeMarker.Bindings}, "click", "change"]],
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵtemplate(0, MyComponent_ng_template_0_Template, 0, 0, "ng-template", 0);
$r3$.ɵɵlistener("click", function MyComponent_Template_ng_template_click_0_listener() { return ctx.click(); })("change", function MyComponent_Template_ng_template_change_0_listener() { return ctx.change(); });
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
it('should not generate the $event argument if it is not being used in a template', () => {
const files = {
app: {
'spec.ts': `
import {Component} from '@angular/core';
@Component({
template: \`<div (click)="onClick();"></div>\`
})
export class MyComponent {
onClick() {}
}
`
}
};
const template = `
consts: [[${AttributeMarker.Bindings}, "click"]],
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵelementStart(0, "div", 0);
$r3$.ɵɵlistener("click", function MyComponent_Template_div_click_0_listener() {
return ctx.onClick();
});
$r3$.ɵɵelementEnd();
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
it('should not generate the $event argument if it is not being used in a host listener', () => {
const files = {
app: {
'spec.ts': `
import {Component, HostListener} from '@angular/core';
@Component({
template: '',
host: {
'(mousedown)': 'mousedown()'
}
})
export class MyComponent {
mousedown() {}
@HostListener('click')
click() {}
}
`
}
};
const template = `
hostBindings: function MyComponent_HostBindings(rf, ctx) {
if (rf & 1) {
i0.ɵɵlistener("mousedown", function MyComponent_mousedown_HostBindingHandler() {
return ctx.mousedown();
})("click", function MyComponent_click_HostBindingHandler() {
return ctx.click();
});
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect host bindings');
});
it('should generate the $event argument if it is being used in a host listener', () => {
const files = {
app: {
'spec.ts': `
import {Directive, HostListener} from '@angular/core';
@Directive()
export class MyComponent {
@HostListener('click', ['$event.target'])
click(t: EventTarget) {}
}
`
}
};
const template = `
hostBindings: function MyComponent_HostBindings(rf, ctx) {
if (rf & 1) {
i0.ɵɵlistener("click", function MyComponent_click_HostBindingHandler($event) {
return ctx.click($event.target);
});
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect host bindings');
});
it('should assume $event is referring to the event variable in a listener by default', () => {
const files = {
app: {
'spec.ts': `
import {Component} from '@angular/core';
@Component({
template: '<div (click)="c($event)"></div>'
})
class Comp {
c(event: MouseEvent) {}
}
`
}
};
const template = `
i0.ɵɵlistener("click", function Comp_Template_div_click_0_listener($event) { return ctx.c($event); });
`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect event listener');
});
it('should preserve accesses to $event if it is done through `this` in a listener', () => {
const files = {
app: {
'spec.ts': `
import {Component} from '@angular/core';
@Component({
template: '<div (click)="c(this.$event)"></div>'
})
class Comp {
$event = {};
c(value: {}) {}
}
`
}
};
const template = `
i0.ɵɵlistener("click", function Comp_Template_div_click_0_listener() { return ctx.c(ctx.$event); });
`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect event listener');
});
it('should not assume that $event is referring to an event object inside a property', () => {
const files = {
app: {
'spec.ts': `
import {Component} from '@angular/core';
@Component({
template: '<div [event]="$event"></div>'
})
class Comp {
$event = 1;
}
`
}
};
const template = `
i0.ɵɵproperty("event", ctx.$event);
`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect property binding');
});
it('should assume $event is referring to the event variable in a listener by default inside a host binding',
() => {
const files = {
app: {
'spec.ts': `
import {Directive} from '@angular/core';
@Directive({
host: {
'(click)': 'c($event)'
}
})
class Dir {
c(event: MouseEvent) {}
}
`
}
};
const template = `
i0.ɵɵlistener("click", function Dir_click_HostBindingHandler($event) { return ctx.c($event); });
`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect event listener');
});
it('should preserve accesses to $event if it is done through `this` in a listener inside a host binding',
() => {
const files = {
app: {
'spec.ts': `
import {Directive} from '@angular/core';
@Directive({
host: {
'(click)': 'c(this.$event)'
}
})
class Dir {
$event = {};
c(value: {}) {}
}
`
}
};
const template = `
i0.ɵɵlistener("click", function Dir_click_HostBindingHandler() { return ctx.c(ctx.$event); });
`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect event listener');
});
});

View File

@ -1,162 +0,0 @@
/**
* @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 {setup} from '@angular/compiler/test/aot/test_util';
import {compile, expectEmit} from './mock_compile';
describe('compiler compliance: providers', () => {
const angularFiles = setup({
compileAngular: false,
compileFakeCore: true,
compileAnimations: false,
});
it('should emit the ProvidersFeature feature when providers and viewProviders', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
abstract class Greeter { abstract greet(): string; }
class GreeterEN implements Greeter {
greet() { return 'Hi'; }
}
@Component({
selector: 'my-component',
template: '<div></div>',
providers: [GreeterEN, {provide: Greeter, useClass: GreeterEN}],
viewProviders: [GreeterEN]
})
export class MyComponent {
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const result = compile(files, angularFiles);
expectEmit(
result.source,
'features: [i0.ɵɵProvidersFeature([GreeterEN, {provide: Greeter, useClass: GreeterEN}], [GreeterEN])],',
'Incorrect features');
});
it('should emit the ProvidersFeature feature when providers only', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
abstract class Greeter { abstract greet(): string; }
class GreeterEN implements Greeter {
greet() { return 'Hi'; }
}
@Component({
selector: 'my-component',
template: '<div></div>',
providers: [GreeterEN, {provide: Greeter, useClass: GreeterEN}]
})
export class MyComponent {
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const result = compile(files, angularFiles);
expectEmit(
result.source,
'features: [i0.ɵɵProvidersFeature([GreeterEN, {provide: Greeter, useClass: GreeterEN}])],',
'Incorrect features');
});
it('should emit the ProvidersFeature feature when viewProviders only', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
abstract class Greeter { abstract greet(): string; }
class GreeterEN implements Greeter {
greet() { return 'Hi'; }
}
@Component({
selector: 'my-component',
template: '<div></div>',
viewProviders: [GreeterEN]
})
export class MyComponent {
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const result = compile(files, angularFiles);
expectEmit(
result.source, 'features: [i0.ɵɵProvidersFeature([], [GreeterEN])],', 'Incorrect features');
});
it('should not emit the ProvidersFeature feature when no providers', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
abstract class Greeter { abstract greet(): string; }
class GreeterEN implements Greeter {
greet() { return 'Hi'; }
}
@Component({
selector: 'my-component',
template: '<div></div>'
})
export class MyComponent {
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const result = compile(files, angularFiles);
expectEmit(
result.source, `
export class MyComponent {
}
MyComponent.ɵfac = function MyComponent_Factory(t) { return new (t || MyComponent)(); };
MyComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({
type: MyComponent,
selectors: [["my-component"]],
decls: 1,
vars: 0,
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
i0.ɵɵelement(0, "div");
}
},
encapsulation: 2
});`,
'Incorrect features');
});
});

View File

@ -1,247 +0,0 @@
/**
* @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 {MockDirectory, setup} from '@angular/compiler/test/aot/test_util';
import {compile, expectEmit} from './mock_compile';
describe('r3_view_compiler', () => {
const angularFiles = setup({
compileAngular: false,
compileFakeCore: true,
compileAnimations: false,
});
describe('hello world', () => {
it('should be able to generate the hello world component', () => {
const files: MockDirectory = {
app: {
'hello.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'hello-world',
template: 'Hello, world!'
})
export class HelloWorldComponent {
}
@NgModule({
declarations: [HelloWorldComponent]
})
export class HelloWorldModule {}
`
}
};
compile(files, angularFiles);
});
});
it('should be able to generate the example', () => {
const files: MockDirectory = {
app: {
'example.ts': `
import {Component, OnInit, OnDestroy, ElementRef, Input, NgModule} from '@angular/core';
@Component({
selector: 'my-app',
template: '<todo [data]="list"></todo>'
})
export class MyApp implements OnInit {
list: any[] = [];
constructor(public elementRef: ElementRef) {}
ngOnInit(): void {
}
}
@Component({
selector: 'todo',
template: '<ul class="list" [title]="myTitle"><li *ngFor="let item of data">{{data}}</li></ul>'
})
export class TodoComponent implements OnInit, OnDestroy {
@Input()
data: any[] = [];
myTitle: string;
constructor(public elementRef: ElementRef) {}
ngOnInit(): void {}
ngOnDestroy(): void {}
}
@NgModule({
declarations: [TodoComponent, MyApp],
})
export class TodoModule{}
`
}
};
const result = compile(files, angularFiles);
expect(result.source).toContain('@angular/core');
});
describe('interpolations', () => {
// Regression #21927
it('should generate a correct call to textInterpolateV with more than 8 interpolations', () => {
const files: MockDirectory = {
app: {
'example.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-app',
template: ' {{list[0]}} {{list[1]}} {{list[2]}} {{list[3]}} {{list[4]}} {{list[5]}} {{list[6]}} {{list[7]}} {{list[8]}} '
})
export class MyApp {
list: any[] = [];
}
@NgModule({declarations: [MyApp]})
export class MyModule {}`
}
};
const bV_call = `
function MyApp_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵɵtext(0);
}
if (rf & 2) {
$i0$.ɵɵtextInterpolateV([" ", ctx.list[0], " ", ctx.list[1], " ", ctx.list[2], " ", ctx.list[3], " ", ctx.list[4], " ", ctx.list[5], " ", ctx.list[6], " ", ctx.list[7], " ", ctx.list[8], " "]);
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, bV_call, 'Incorrect bV call');
});
});
describe('animations', () => {
it('should not register any @attr attributes as static attributes', () => {
const files: MockDirectory = {
app: {
'example.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-app',
template: '<div @attr [@binding]="exp"></div>'
})
export class MyApp {
}
@NgModule({declarations: [MyApp]})
export class MyModule {}`
}
};
const template = `
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵɵelement(0, "div");
}
if (rf & 2) {
$i0$.ɵɵproperty("@attr", )("@binding", );
}
}`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect initialization attributes');
});
it('should dedup multiple [@event] listeners', () => {
const files: MockDirectory = {
app: {
'example.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-app',
template: '<div (@mySelector.start)="false" (@mySelector.done)="false" [@mySelector]="0"></div>'
})
export class MyApp {
}
@NgModule({declarations: [MyApp]})
export class MyModule {}`
}
};
const template = `
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵɵelementStart(0, "div");
$i0$.ɵɵproperty("@mySelector", );
}
}`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect initialization attributes');
});
});
describe('$any', () => {
it('should strip out $any wrappers', () => {
const files = {
app: {
'spec.ts': `
import {Component} from '@angular/core';
@Component({
template: '<div [tabIndex]="$any(10)"></div>'
})
class Comp {
}
`
}
};
const template = `
i0.ɵɵproperty("tabIndex", 10);
`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
it('should preserve $any if it is accessed through `this`', () => {
const files = {
app: {
'spec.ts': `
import {Component} from '@angular/core';
@Component({
template: '<div [tabIndex]="this.$any(null)"></div>'
})
class Comp {
$any(value: null): any {
return value as any;
}
}
`
}
};
const template = `
i0.ɵɵproperty("tabIndex", ctx.$any(null));
`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
});
});

View File

@ -1,874 +0,0 @@
/**
* @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 {AttributeMarker} from '@angular/compiler/src/core';
import {setup} from '@angular/compiler/test/aot/test_util';
import {compile, expectEmit} from './mock_compile';
describe('compiler compliance: template', () => {
const angularFiles = setup({
compileAngular: false,
compileFakeCore: true,
compileAnimations: false,
});
it('should correctly bind to context in nested template', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`
<ul *ngFor="let outer of items">
<li *ngFor="let middle of outer.items">
<div *ngFor="let inner of items"
(click)="onClick(outer, middle, inner)"
[title]="format(outer, middle, inner, component)"
>
{{format(outer, middle, inner, component)}}
</div>
</li>
</ul>\`
})
export class MyComponent {
component = this;
format(outer: any, middle: any, inner: any) { }
onClick(outer: any, middle: any, inner: any) { }
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
// The template should look like this (where IDENT is a wild card for an identifier):
const template = `
function MyComponent_ul_0_li_1_div_1_Template(rf, ctx) {
if (rf & 1) {
const $s$ = $i0$.ɵɵgetCurrentView();
$i0$.ɵɵelementStart(0, "div", 2);
$i0$.ɵɵlistener("click", function MyComponent_ul_0_li_1_div_1_Template_div_click_0_listener(){
const $sr$ = $i0$.ɵɵrestoreView($s$);
const $inner$ = $sr$.$implicit;
const $middle$ = $i0$.ɵɵnextContext().$implicit;
const $outer$ = $i0$.ɵɵnextContext().$implicit;
const $myComp$ = $i0$.ɵɵnextContext();
return $myComp$.onClick($outer$, $middle$, $inner$);
});
$i0$.ɵɵtext(1);
$i0$.ɵɵelementEnd();
}
if (rf & 2) {
const $inner1$ = ctx.$implicit;
const $middle1$ = $i0$.ɵɵnextContext().$implicit;
const $outer1$ = $i0$.ɵɵnextContext().$implicit;
const $myComp1$ = $i0$.ɵɵnextContext();
$i0$.ɵɵproperty("title", $myComp1$.format($outer1$, $middle1$, $inner1$, $myComp1$.component));
$r3$.ɵɵadvance(1);
$i0$.ɵɵtextInterpolate1(" ", $myComp1$.format($outer1$, $middle1$, $inner1$, $myComp1$.component), " ");
}
}
function MyComponent_ul_0_li_1_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵɵelementStart(0, "li");
$i0$.ɵɵtemplate(1, MyComponent_ul_0_li_1_div_1_Template, 2, 2, "div", 1);
$i0$.ɵɵelementEnd();
}
if (rf & 2) {
const $myComp2$ = $i0$.ɵɵnextContext(2);
$r3$.ɵɵadvance(1);
$i0$.ɵɵproperty("ngForOf", $myComp2$.items);
}
}
function MyComponent_ul_0_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵɵelementStart(0, "ul");
$i0$.ɵɵtemplate(1, MyComponent_ul_0_li_1_Template, 2, 1, "li", 0);
$i0$.ɵɵelementEnd();
}
if (rf & 2) {
const $outer2$ = ctx.$implicit;
$r3$.ɵɵadvance(1);
$i0$.ɵɵproperty("ngForOf", $outer2$.items);
}
}
// ...
consts: [[${AttributeMarker.Template}, "ngFor", "ngForOf"], [${
AttributeMarker.Bindings}, "title", "click", ${
AttributeMarker.Template}, "ngFor", "ngForOf"], [${
AttributeMarker.Bindings}, "title", "click"]],
template:function MyComponent_Template(rf, ctx){
if (rf & 1) {
$i0$.ɵɵtemplate(0, MyComponent_ul_0_Template, 2, 1, "ul", 0);
}
if (rf & 2) {
$i0$.ɵɵproperty("ngForOf", ctx.items);
}
}`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
it('should correctly bind to context in nested template with many bindings', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`
<div *ngFor="let d of _data; let i = index" (click)="_handleClick(d, i)"></div>
\`
})
export class MyComponent {
_data = [1,2,3];
_handleClick(d: any, i: any) {}
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const template = `
function MyComponent_div_0_Template(rf, ctx) {
if (rf & 1) {
const $s$ = $r3$.ɵɵgetCurrentView();
$r3$.ɵɵelementStart(0, "div", 1);
$r3$.ɵɵlistener("click", function MyComponent_div_0_Template_div_click_0_listener() {
const $sr$ = $r3$.ɵɵrestoreView($s$);
const $d$ = $sr$.$implicit;
const $i$ = $sr$.index;
const $comp$ = $r3$.ɵɵnextContext();
return $comp$._handleClick($d$, $i$);
});
$r3$.ɵɵelementEnd();
}
}
// ...
consts: [[${AttributeMarker.Bindings}, "click", ${
AttributeMarker.Template}, "ngFor", "ngForOf"], [${AttributeMarker.Bindings}, "click"]],
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵtemplate(0, MyComponent_div_0_Template, 1, 0, "div", 0);
}
if (rf & 2) {
$r3$.ɵɵproperty("ngForOf", ctx._data);
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
it('should correctly bind to implicit receiver in template', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`
<div *ngIf="true" (click)="greet(this)"></div>
<div *ngIf="true" [id]="this"></div>
\`
})
export class MyComponent {
greet(val: any) {}
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const template = `
function MyComponent_div_0_Template(rf, ctx) {
if (rf & 1) {
const $_r2$ = i0.ɵɵgetCurrentView();
$r3$.ɵɵelementStart(0, "div", 2);
$r3$.ɵɵlistener("click", function MyComponent_div_0_Template_div_click_0_listener() {
i0.ɵɵrestoreView($_r2$);
const $ctx_r1$ = i0.ɵɵnextContext();
return $ctx_r1$.greet($ctx_r1$);
});
$r3$.ɵɵelementEnd();
}
}
// ...
function MyComponent_div_1_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵelement(0, "div", 3);
} if (rf & 2) {
const $ctx_0$ = i0.ɵɵnextContext();
$r3$.ɵɵproperty("id", $ctx_0$);
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
it('should support ngFor context variables', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`
<span *ngFor="let item of items; index as i">
{{ i }} - {{ item }}
</span>\`
})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const template = `
function MyComponent_span_0_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵɵelementStart(0, "span");
$i0$.ɵɵtext(1);
$i0$.ɵɵelementEnd();
}
if (rf & 2) {
const $item$ = ctx.$implicit;
const $i$ = ctx.index;
$r3$.ɵɵadvance(1);
$i0$.ɵɵtextInterpolate2(" ", $i$, " - ", $item$, " ");
}
}
// ...
consts: [[${AttributeMarker.Template}, "ngFor", "ngForOf"]],
template:function MyComponent_Template(rf, ctx){
if (rf & 1) {
$i0$.ɵɵtemplate(0, MyComponent_span_0_Template, 2, 2, "span", 0);
}
if (rf & 2) {
$i0$.ɵɵproperty("ngForOf", ctx.items);
}
}`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
it('should support ngFor context variables in parent views', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`
<div *ngFor="let item of items; index as i">
<span *ngIf="showing">
{{ i }} - {{ item }}
</span>
</div>\`
})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const template = `
function MyComponent_div_0_span_1_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵɵelementStart(0, "span");
$i0$.ɵɵtext(1);
$i0$.ɵɵelementEnd();
}
if (rf & 2) {
const $div$ = $i0$.ɵɵnextContext();
const $i$ = $div$.index;
const $item$ = $div$.$implicit;
$r3$.ɵɵadvance(1);
$i0$.ɵɵtextInterpolate2(" ", $i$, " - ", $item$, " ");
}
}
function MyComponent_div_0_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵɵelementStart(0, "div");
$i0$.ɵɵtemplate(1, MyComponent_div_0_span_1_Template, 2, 2, "span", 1);
$i0$.ɵɵelementEnd();
}
if (rf & 2) {
const $app$ = $i0$.ɵɵnextContext();
$r3$.ɵɵadvance(1);
$i0$.ɵɵproperty("ngIf", $app$.showing);
}
}
// ...
consts: [[${AttributeMarker.Template}, "ngFor", "ngForOf"], [${
AttributeMarker.Template}, "ngIf"]],
template:function MyComponent_Template(rf, ctx){
if (rf & 1) {
$i0$.ɵɵtemplate(0, MyComponent_div_0_Template, 2, 1, "div", 0);
}
if (rf & 2) {
$i0$.ɵɵproperty("ngForOf", ctx.items);
}
}`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
it('should correctly skip contexts as needed', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`
<div *ngFor="let outer of items">
<div *ngFor="let middle of outer.items">
<div *ngFor="let inner of middle.items">
{{ middle.value }} - {{ name }}
</div>
</div>
</div>\`
})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
// The template should look like this (where IDENT is a wild card for an identifier):
const template = `
function MyComponent_div_0_div_1_div_1_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵɵelementStart(0, "div");
$i0$.ɵɵtext(1);
$i0$.ɵɵelementEnd();
}
if (rf & 2) {
const $middle$ = $i0$.ɵɵnextContext().$implicit;
const $myComp$ = $i0$.ɵɵnextContext(2);
$r3$.ɵɵadvance(1);
$i0$.ɵɵtextInterpolate2(" ", $middle$.value, " - ", $myComp$.name, " ");
}
}
function MyComponent_div_0_div_1_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵɵelementStart(0, "div");
$i0$.ɵɵtemplate(1, MyComponent_div_0_div_1_div_1_Template, 2, 2, "div", 0);
$i0$.ɵɵelementEnd();
}
if (rf & 2) {
const $middle$ = ctx.$implicit;
$r3$.ɵɵadvance(1);
$i0$.ɵɵproperty("ngForOf", $middle$.items);
}
}
function MyComponent_div_0_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵɵelementStart(0, "div");
$i0$.ɵɵtemplate(1, MyComponent_div_0_div_1_Template, 2, 1, "div", 0);
$i0$.ɵɵelementEnd();
}
if (rf & 2) {
const $outer$ = ctx.$implicit;
$r3$.ɵɵadvance(1);
$i0$.ɵɵproperty("ngForOf", $outer$.items);
}
}
// ...
consts: [[${AttributeMarker.Template}, "ngFor", "ngForOf"]],
template:function MyComponent_Template(rf, ctx){
if (rf & 1) {
$i0$.ɵɵtemplate(0, MyComponent_div_0_Template, 2, 1, "div", 0);
}
if (rf & 2) {
$i0$.ɵɵproperty("ngForOf", ctx.items);
}
}`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
it('should support <ng-template>', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`
<ng-template [boundAttr]="b" attr="l">
some-content
</ng-template>\`
})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const template = `
function MyComponent_ng_template_0_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵɵtext(0, " some-content ");
}
}
// ...
consts: [["attr", "l", ${AttributeMarker.Bindings}, "boundAttr"]],
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵɵtemplate(0, MyComponent_ng_template_0_Template, 1, 0, "ng-template", 0);
}
if (rf & 2) {
$i0$.ɵɵproperty("boundAttr", ctx.b);
}
}`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
it('should support local refs on <ng-template>', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: '<ng-template #foo>some-content</ng-template>',
})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const template = `
function MyComponent_ng_template_0_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵɵtext(0, "some-content");
}
}
// ...
consts: [["foo", ""]],
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵɵtemplate(0, MyComponent_ng_template_0_Template, 1, 0, "ng-template", null, 0, $i0$.ɵɵtemplateRefExtractor);
}
}`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
it('should support directive outputs on <ng-template>', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: '<ng-template (outDirective)="$event.doSth()"></ng-template>',
})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const template = `
function MyComponent_ng_template_0_Template(rf, ctx) { }
// ...
consts: [[${AttributeMarker.Bindings}, "outDirective"]],
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵɵtemplate(0, MyComponent_ng_template_0_Template, 0, 0, "ng-template", 0);
$i0$.ɵɵlistener("outDirective", function MyComponent_Template_ng_template_outDirective_0_listener($event) { return $event.doSth(); });
}
}`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
it('should allow directive inputs as an interpolated prop on <ng-template>', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, Input} from '@angular/core';
@Directive({selector: '[dir]'})
class WithInput {
@Input() dir: string = '';
}
@Component({
selector: 'my-app',
template: '<ng-template dir="{{ message }}"></ng-template>',
})
export class TestComp {
message = 'Hello';
}
`
}
};
const result = compile(files, angularFiles);
const expectedTemplate = `
consts: [[${AttributeMarker.Bindings}, "dir"]],
template: function TestComp_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵɵtemplate(0, $TestComp_ng_template_0_Template$, 0, 0, "ng-template", 0);
}
if (rf & 2) {
$i0$.ɵɵpropertyInterpolate("dir", ctx.message);
}
},
`;
expectEmit(result.source, expectedTemplate, 'Incorrect template');
});
it('should allow directive inputs as an interpolated prop on <ng-template> (with structural directives)',
() => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, Input} from '@angular/core';
@Directive({selector: '[dir]'})
class WithInput {
@Input() dir: string = '';
}
@Component({
selector: 'my-app',
template: '<ng-template *ngIf="true" dir="{{ message }}"></ng-template>',
})
export class TestComp {
message = 'Hello';
}
`
}
};
const result = compile(files, angularFiles);
// Expect that `ɵɵpropertyInterpolate` is generated in the inner template function.
const expectedInnerTemplate = `
function $TestComp_0_Template$(rf, ctx) {
if (rf & 1) {
$i0$.ɵɵtemplate(0, $TestComp_0_ng_template_0_Template$, 0, 0, "ng-template", 1);
}
if (rf & 2) {
const $ctx_r0$ = i0.ɵɵnextContext();
$i0$.ɵɵpropertyInterpolate("dir", $ctx_r0$.message);
}
}
`;
expectEmit(result.source, expectedInnerTemplate, 'Incorrect template');
// Main template should just contain *ngIf property.
const expectedMainTemplate = `
consts: [[4, "ngIf"], [3, "dir"]],
template: function TestComp_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵɵtemplate(0, $TestComp_0_Template$, 1, 1, undefined, 0);
}
if (rf & 2) {
$i0$.ɵɵproperty("ngIf", true);
}
},
`;
expectEmit(result.source, expectedMainTemplate, 'Incorrect template');
});
it('should create unique template function names even for similar nested template structures',
() => {
const files = {
app: {
'spec1.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'a-component',
template: \`
<div *ngFor="let item of items">
<p *ngIf="item < 10">less than 10</p>
<p *ngIf="item < 10">less than 10</p>
</div>
<div *ngFor="let item of items">
<p *ngIf="item > 10">more than 10</p>
</div>
\`,
})
export class AComponent {
items = [4, 2];
}
@NgModule({declarations: [AComponent]})
export class AModule {}
`,
'spec2.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'b-component',
template: \`
<div *ngFor="let item of items">
<ng-container *ngFor="let subitem of item.subitems">
<p *ngIf="subitem < 10">less than 10</p>
<p *ngIf="subitem < 10">less than 10</p>
</ng-container>
<ng-container *ngFor="let subitem of item.subitems">
<p *ngIf="subitem < 10">less than 10</p>
</ng-container>
</div>
<div *ngFor="let item of items">
<ng-container *ngFor="let subitem of item.subitems">
<p *ngIf="subitem > 10">more than 10</p>
</ng-container>
</div>
\`,
})
export class BComponent {
items = [
{subitems: [1, 3]},
{subitems: [3, 7]},
];
}
@NgModule({declarations: [BComponent]})
export class BModule {}
`,
},
};
const result = compile(files, angularFiles);
const allTemplateFunctionsNames = (result.source.match(/function ([^\s(]+)/g) || [])
.map(x => x.slice(9))
.filter(x => x.includes('Template'))
.sort();
const uniqueTemplateFunctionNames = Array.from(new Set(allTemplateFunctionsNames));
// Expected template function:
// - 5 for AComponent's template.
// - 9 for BComponent's template.
// - 2 for the two components.
expect(allTemplateFunctionsNames.length).toBe(5 + 9 + 2);
expect(allTemplateFunctionsNames).toEqual(uniqueTemplateFunctionNames);
});
it('should create unique template function names for ng-content templates', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'a-component',
template: \`
<ng-content *ngIf="show"></ng-content>
\`,
})
export class AComponent {
show = true;
}
@Component({
selector: 'b-component',
template: \`
<ng-content *ngIf="show"></ng-content>
\`,
})
export class BComponent {
show = true;
}
@NgModule({declarations: [AComponent, BComponent]})
export class AModule {}
`
},
};
const result = compile(files, angularFiles);
const allTemplateFunctionsNames = (result.source.match(/function ([^\s(]+)/g) || [])
.map(x => x.slice(9))
.filter(x => x.includes('Template'))
.sort();
const uniqueTemplateFunctionNames = Array.from(new Set(allTemplateFunctionsNames));
// Expected template function:
// - 1 for AComponent's template.
// - 1 for BComponent's template.
// - 2 for the two components.
expect(allTemplateFunctionsNames.length).toBe(1 + 1 + 2);
expect(allTemplateFunctionsNames).toEqual(uniqueTemplateFunctionNames);
});
it('should create unique listener function names even for similar nested template structures',
() => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`
<div *ngFor="let item of items">
<p (click)="$event">{{ item }}</p>
<p (click)="$event">{{ item }}</p>
</div>
<div *ngFor="let item of items">
<p (click)="$event">{{ item }}</p>
</div>
\`,
})
export class MyComponent {
items = [4, 2];
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`,
},
};
const result = compile(files, angularFiles);
const allListenerFunctionsNames = (result.source.match(/function ([^\s(]+)/g) || [])
.map(x => x.slice(9))
.filter(x => x.includes('listener'))
.sort();
const uniqueListenerFunctionNames = Array.from(new Set(allListenerFunctionsNames));
expect(allListenerFunctionsNames.length).toBe(3);
expect(allListenerFunctionsNames).toEqual(uniqueListenerFunctionNames);
});
it('should support pipes in template bindings', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`
<div *ngIf="val | pipe"></div>\`
})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const template = `
function MyComponent_div_0_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵɵelement(0, "div");
}
}
// ...
consts: [[${AttributeMarker.Template}, "ngIf"]],
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$i0$.ɵɵtemplate(0, MyComponent_div_0_Template, 1, 0, "div", 0);
$i0$.ɵɵpipe(1, "pipe");
} if (rf & 2) {
$i0$.ɵɵproperty("ngIf", $i0$.ɵɵpipeBind1(1, 1, ctx.val));
}
}`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
it('should safely nest ternary operations', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`
{{a?.b ? 1 : 2 }}\`
})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const template = `i0.ɵɵtextInterpolate1(" ", (ctx.a == null ? null : ctx.a.b) ? 1 : 2, "")`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
});