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:
parent
e16d234709
commit
4810f5c819
@ -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",
|
|
||||||
],
|
|
||||||
)
|
|
@ -1,5 +0,0 @@
|
|||||||
Tests in this directory should be run with:
|
|
||||||
|
|
||||||
```
|
|
||||||
yarn bazel test --config=ivy packages/compiler-cli/test/compliance:compliance
|
|
||||||
```
|
|
@ -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",
|
|
||||||
],
|
|
||||||
)
|
|
@ -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;
|
|
||||||
}
|
|
@ -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\)\//);
|
|
||||||
});
|
|
||||||
});
|
|
@ -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",
|
|
||||||
],
|
|
||||||
)
|
|
@ -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);
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
@ -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');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
File diff suppressed because it is too large
Load Diff
@ -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');
|
|
||||||
});
|
|
||||||
});
|
|
@ -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');
|
|
||||||
});
|
|
||||||
});
|
|
@ -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');
|
|
||||||
});
|
|
||||||
});
|
|
@ -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');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
File diff suppressed because it is too large
Load Diff
@ -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');
|
|
||||||
});
|
|
||||||
});
|
|
Loading…
x
Reference in New Issue
Block a user