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…
Reference in New Issue