refactor(ivy): run the compiler compliance tests against ngtsc (#24862)
This commit moves the compiler compliance tests into compiler-cli, and uses ngtsc to run them instead of the custom compilation pipeline used before. Testing against ngtsc allows for validation of the real compiler output. This commit also fixes a few small issues that prevented the tests from passing. PR Close #24862
This commit is contained in:

committed by
Victor Berchet

parent
b7bbc82e3e
commit
9fd70c9715
@ -20,10 +20,6 @@ ts_library(
|
||||
jasmine_node_test(
|
||||
name = "test",
|
||||
bootstrap = ["angular/tools/testing/init_node_spec.js"],
|
||||
data = [
|
||||
"//packages/common:npm_package",
|
||||
"//packages/core:npm_package",
|
||||
],
|
||||
deps = [
|
||||
":test_lib",
|
||||
"//tools/testing:node",
|
||||
|
@ -1,329 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AotCompilerOptions, AotSummaryResolver, CompileMetadataResolver, CompilerConfig, DEFAULT_INTERPOLATION_CONFIG, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, Lexer, NgModuleResolver, ParseError, Parser, PipeResolver, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser, TypeScriptEmitter, analyzeNgModules, createAotUrlResolver} from '@angular/compiler';
|
||||
import {ViewEncapsulation} from '@angular/core';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {NgAnalyzedModules} from '../../src/aot/compiler';
|
||||
import {ConstantPool} from '../../src/constant_pool';
|
||||
import * as html from '../../src/ml_parser/ast';
|
||||
import {removeWhitespaces} from '../../src/ml_parser/html_whitespaces';
|
||||
import * as o from '../../src/output/output_ast';
|
||||
import {compilePipeFromRender2} from '../../src/render3/r3_pipe_compiler';
|
||||
import {htmlAstToRender3Ast} from '../../src/render3/r3_template_transform';
|
||||
import {compileComponentFromRender2, compileDirectiveFromRender2} from '../../src/render3/view/compiler';
|
||||
import {BindingParser} from '../../src/template_parser/binding_parser';
|
||||
import {OutputContext, escapeRegExp} from '../../src/util';
|
||||
import {MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, arrayToMockDir, expectNoDiagnostics, settings, toMockFileArray} from '../aot/test_util';
|
||||
|
||||
|
||||
const IDENTIFIER = /[A-Za-z_$ɵ][A-Za-z0-9_$]*/;
|
||||
const OPERATOR =
|
||||
/!|%|\*|\/|\^|&&?|\|\|?|\(|\)|\{|\}|\[|\]|:|;|<=?|>=?|={1,3}|!==?|=>|\+\+?|--?|@|,|\.|\.\.\./;
|
||||
const STRING = /'[^']*'|"[^"]*"|`[\s\S]*?`/;
|
||||
const NUMBER = /\d+/;
|
||||
|
||||
const ELLIPSIS = '…';
|
||||
const TOKEN = new RegExp(
|
||||
`\\s*((${IDENTIFIER.source})|(${OPERATOR.source})|(${STRING.source})|${NUMBER.source}|${ELLIPSIS})`,
|
||||
'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 = 0;
|
||||
|
||||
let match: RegExpMatchArray|null;
|
||||
const pieces: Piece[] = [];
|
||||
|
||||
while ((match = TOKEN.exec(text)) !== null) {
|
||||
const token = match[1];
|
||||
if (token === 'IDENT') {
|
||||
pieces.push(IDENTIFIER);
|
||||
} else if (token === ELLIPSIS) {
|
||||
pieces.push(SKIP);
|
||||
} else {
|
||||
pieces.push(token);
|
||||
}
|
||||
}
|
||||
|
||||
if (pieces.length === 0 || TOKEN.lastIndex !== 0) {
|
||||
const from = TOKEN.lastIndex;
|
||||
const to = from + ERROR_CONTEXT_WIDTH;
|
||||
throw Error(`Invalid test, no token found for '${text.substr(from, to)}...'`);
|
||||
}
|
||||
|
||||
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) {
|
||||
fail(
|
||||
`${description}: Expected to find ${expectedPiece} '${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,
|
||||
};
|
||||
}
|
||||
|
||||
function doCompile(
|
||||
data: MockDirectory, angularFiles: MockData, options: AotCompilerOptions = {},
|
||||
errorCollector: (error: any, fileName?: string) => void = error => { throw error; },
|
||||
compileAction: (
|
||||
outputCtx: OutputContext, analyzedModules: NgAnalyzedModules,
|
||||
resolver: CompileMetadataResolver, htmlParser: HtmlParser, templateParser: TemplateParser,
|
||||
hostBindingParser: BindingParser, reflector: StaticReflector) => void) {
|
||||
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 compilerHost = new MockAotCompilerHost(mockCompilerHost);
|
||||
|
||||
const program = ts.createProgram(scripts, {...settings}, mockCompilerHost);
|
||||
expectNoDiagnostics(program);
|
||||
|
||||
// TODO(chuckj): Replace with a variant of createAotCompiler() when the r3_view_compiler is
|
||||
// integrated
|
||||
|
||||
const urlResolver = createAotUrlResolver(compilerHost);
|
||||
const symbolCache = new StaticSymbolCache();
|
||||
const summaryResolver = new AotSummaryResolver(compilerHost, symbolCache);
|
||||
const symbolResolver = new StaticSymbolResolver(compilerHost, symbolCache, summaryResolver);
|
||||
const staticReflector =
|
||||
new StaticReflector(summaryResolver, symbolResolver, [], [], errorCollector);
|
||||
const htmlParser = new HtmlParser();
|
||||
const config = new CompilerConfig({
|
||||
defaultEncapsulation: ViewEncapsulation.Emulated,
|
||||
useJit: false,
|
||||
missingTranslation: options.missingTranslation,
|
||||
preserveWhitespaces: options.preserveWhitespaces,
|
||||
strictInjectionParameters: options.strictInjectionParameters,
|
||||
});
|
||||
const normalizer = new DirectiveNormalizer(
|
||||
{get: (url: string) => compilerHost.loadResource(url)}, urlResolver, htmlParser, config);
|
||||
const expressionParser = new Parser(new Lexer());
|
||||
const elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||
const templateParser = new TemplateParser(
|
||||
config, staticReflector, expressionParser, elementSchemaRegistry, htmlParser, console, []);
|
||||
const resolver = new CompileMetadataResolver(
|
||||
config, htmlParser, new NgModuleResolver(staticReflector),
|
||||
new DirectiveResolver(staticReflector), new PipeResolver(staticReflector), summaryResolver,
|
||||
elementSchemaRegistry, normalizer, console, symbolCache, staticReflector, errorCollector);
|
||||
|
||||
// Create the TypeScript program
|
||||
const sourceFiles = program.getSourceFiles().map(sf => sf.fileName);
|
||||
|
||||
// Analyze the modules
|
||||
// TODO(chuckj): Eventually this should not be necessary as the ts.SourceFile should be sufficient
|
||||
// to generate a template definition.
|
||||
const analyzedModules = analyzeNgModules(sourceFiles, compilerHost, symbolResolver, resolver);
|
||||
|
||||
const pipesOrDirectives = Array.from(analyzedModules.ngModuleByPipeOrDirective.keys());
|
||||
|
||||
const fakeOutputContext: OutputContext = {
|
||||
genFilePath: 'fakeFactory.ts',
|
||||
statements: [],
|
||||
importExpr(symbol: StaticSymbol, typeParams: o.Type[]) {
|
||||
if (!(symbol instanceof StaticSymbol)) {
|
||||
if (!symbol) {
|
||||
throw new Error('Invalid: undefined passed to as a symbol');
|
||||
}
|
||||
throw new Error(`Invalid: ${(symbol as any).constructor.name} is not a symbol`);
|
||||
}
|
||||
return (symbol.members || [])
|
||||
.reduce(
|
||||
(expr, member) => expr.prop(member),
|
||||
<o.Expression>o.importExpr(new o.ExternalReference(symbol.filePath, symbol.name)));
|
||||
},
|
||||
constantPool: new ConstantPool()
|
||||
};
|
||||
|
||||
const errors: ParseError[] = [];
|
||||
|
||||
const hostBindingParser = new BindingParser(
|
||||
expressionParser, DEFAULT_INTERPOLATION_CONFIG, elementSchemaRegistry, null, errors);
|
||||
|
||||
// Load all directives and pipes
|
||||
for (const pipeOrDirective of pipesOrDirectives) {
|
||||
const module = analyzedModules.ngModuleByPipeOrDirective.get(pipeOrDirective) !;
|
||||
resolver.loadNgModuleDirectiveAndPipeMetadata(module.type.reference, true);
|
||||
}
|
||||
|
||||
compileAction(
|
||||
fakeOutputContext, analyzedModules, resolver, htmlParser, templateParser, hostBindingParser,
|
||||
staticReflector);
|
||||
|
||||
fakeOutputContext.statements.unshift(...fakeOutputContext.constantPool.statements);
|
||||
|
||||
const emitter = new TypeScriptEmitter();
|
||||
|
||||
const result = emitter.emitStatementsAndContext(
|
||||
fakeOutputContext.genFilePath, fakeOutputContext.statements, '', false,
|
||||
/* referenceFilter */ undefined,
|
||||
/* importFilter */ e => e.moduleName != null && e.moduleName.startsWith('/app'));
|
||||
|
||||
if (errors.length) {
|
||||
throw new Error('Unexpected errors:' + errors.map(e => e.toString()).join(', '));
|
||||
}
|
||||
|
||||
return {source: result.sourceText, outputContext: fakeOutputContext};
|
||||
}
|
||||
|
||||
export function compile(
|
||||
data: MockDirectory, angularFiles: MockData, options: AotCompilerOptions = {},
|
||||
errorCollector: (error: any, fileName?: string) => void = error => { throw error;}) {
|
||||
return doCompile(
|
||||
data, angularFiles, options, errorCollector,
|
||||
(outputCtx: OutputContext, analyzedModules: NgAnalyzedModules,
|
||||
resolver: CompileMetadataResolver, htmlParser: HtmlParser, templateParser: TemplateParser,
|
||||
hostBindingParser: BindingParser, reflector: StaticReflector) => {
|
||||
const pipesOrDirectives = Array.from(analyzedModules.ngModuleByPipeOrDirective.keys());
|
||||
for (const pipeOrDirective of pipesOrDirectives) {
|
||||
const module = analyzedModules.ngModuleByPipeOrDirective.get(pipeOrDirective);
|
||||
if (!module || !module.type.reference.filePath.startsWith('/app')) {
|
||||
continue;
|
||||
}
|
||||
if (resolver.isDirective(pipeOrDirective)) {
|
||||
const directive = resolver.getDirectiveMetadata(pipeOrDirective);
|
||||
if (directive.isComponent) {
|
||||
const fakeUrl = 'ng://fake-template-url.html';
|
||||
let htmlAst = htmlParser.parse(directive.template !.template !, fakeUrl);
|
||||
|
||||
// Map of StaticType by directive selectors
|
||||
const directiveTypeBySel = new Map<string, any>();
|
||||
|
||||
const directives = module.transitiveModule.directives.map(
|
||||
dir => resolver.getDirectiveSummary(dir.reference));
|
||||
|
||||
directives.forEach(directive => {
|
||||
if (directive.selector) {
|
||||
directiveTypeBySel.set(directive.selector, directive.type.reference);
|
||||
}
|
||||
});
|
||||
|
||||
// Map of StaticType by pipe names
|
||||
const pipeTypeByName = new Map<string, any>();
|
||||
|
||||
const pipes = module.transitiveModule.pipes.map(
|
||||
pipe => resolver.getPipeSummary(pipe.reference));
|
||||
|
||||
pipes.forEach(pipe => { pipeTypeByName.set(pipe.name, pipe.type.reference); });
|
||||
|
||||
const preserveWhitespaces = directive.template !.preserveWhitespaces;
|
||||
if (!preserveWhitespaces) {
|
||||
htmlAst = removeWhitespaces(htmlAst);
|
||||
}
|
||||
|
||||
const render3Ast = htmlAstToRender3Ast(htmlAst.rootNodes, hostBindingParser);
|
||||
|
||||
compileComponentFromRender2(
|
||||
outputCtx, directive, render3Ast, reflector, hostBindingParser,
|
||||
directiveTypeBySel, pipeTypeByName);
|
||||
} else {
|
||||
compileDirectiveFromRender2(outputCtx, directive, reflector, hostBindingParser);
|
||||
}
|
||||
} else if (resolver.isPipe(pipeOrDirective)) {
|
||||
const metadata = resolver.getPipeMetadata(pipeOrDirective);
|
||||
if (metadata) {
|
||||
compilePipeFromRender2(outputCtx, metadata, reflector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
@ -1,220 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
|
||||
import {MockDirectory, setup} from '../aot/test_util';
|
||||
import {compile, expectEmit} from './mock_compile';
|
||||
|
||||
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: true,
|
||||
compileAnimations: false,
|
||||
compileCommon: true,
|
||||
});
|
||||
|
||||
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');
|
||||
|
||||
// The output context is also returned if the actual output ast is needed.
|
||||
expect(result.outputContext.statements.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
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 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$.ɵT(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\)\//);
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load Diff
@ -1,121 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {MockDirectory, setup} from '../aot/test_util';
|
||||
import {compile, expectEmit} from './mock_compile';
|
||||
|
||||
describe('compiler compliance: bindings', () => {
|
||||
const angularFiles = setup({
|
||||
compileAngular: true,
|
||||
compileAnimations: false,
|
||||
compileCommon: false,
|
||||
});
|
||||
|
||||
describe('text bindings', () => {
|
||||
it('should generate interpolation instruction', () => {
|
||||
const files: MockDirectory = {
|
||||
app: {
|
||||
'example.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`
|
||||
<div>Hello {{ name }}</div>\`
|
||||
})
|
||||
export class MyComponent {
|
||||
name = 'World';
|
||||
}
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
template:function MyComponent_Template(rf: IDENT, $ctx$: IDENT){
|
||||
if (rf & 1) {
|
||||
$i0$.ɵE(0, 'div');
|
||||
$i0$.ɵT(1);
|
||||
$i0$.ɵe();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$i0$.ɵt(1, $i0$.ɵi1('Hello ', $ctx$.name, ''));
|
||||
}
|
||||
}`;
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect interpolated text binding');
|
||||
});
|
||||
});
|
||||
|
||||
describe('property bindings', () => {
|
||||
it('should generate bind instruction', () => {
|
||||
const files: MockDirectory = {
|
||||
app: {
|
||||
'example.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template: '<a [title]="title"></a>'
|
||||
})
|
||||
export class MyComponent {
|
||||
title = 'Hello World';
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
template:function MyComponent_Template(rf: IDENT, $ctx$: IDENT){
|
||||
if (rf & 1) {
|
||||
$i0$.ɵEe(0, 'a');
|
||||
}
|
||||
if (rf & 2) {
|
||||
$i0$.ɵp(0, 'title', $i0$.ɵb($ctx$.title));
|
||||
}
|
||||
}`;
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect property binding');
|
||||
});
|
||||
|
||||
it('should generate interpolation instruction for {{...}} bindings', () => {
|
||||
const files: MockDirectory = {
|
||||
app: {
|
||||
'example.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`
|
||||
<a title="Hello {{name}}"></a>\`
|
||||
})
|
||||
export class MyComponent {
|
||||
name = 'World';
|
||||
}
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
template:function MyComponent_Template(rf: IDENT, $ctx$: IDENT){
|
||||
if (rf & 1) {
|
||||
$i0$.ɵEe(0, 'a');
|
||||
}
|
||||
if (rf & 2) {
|
||||
$i0$.ɵp(0, 'title', $i0$.ɵi1('Hello ', $ctx$.name, ''));
|
||||
}
|
||||
}`;
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect interpolated property binding');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@ -1,70 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {MockDirectory, setup} from '../aot/test_util';
|
||||
import {compile, expectEmit} from './mock_compile';
|
||||
|
||||
describe('compiler compliance: dependency injection', () => {
|
||||
const angularFiles = setup({
|
||||
compileAngular: true,
|
||||
compileAnimations: false,
|
||||
compileCommon: true,
|
||||
});
|
||||
|
||||
it('should create factory methods', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule, Injectable, Attribute, Host, SkipSelf, Self, Optional} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
|
||||
@Injectable()
|
||||
export class MyService {}
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`\`
|
||||
})
|
||||
export class MyComponent {
|
||||
constructor(
|
||||
@Attribute('name') name:string,
|
||||
s1: MyService,
|
||||
@Host() s2: MyService,
|
||||
@Self() s4: MyService,
|
||||
@SkipSelf() s3: MyService,
|
||||
@Optional() s5: MyService,
|
||||
@Self() @Optional() s6: MyService,
|
||||
) {}
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComponent], imports: [CommonModule], providers: [MyService]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const factory = `
|
||||
factory: function MyComponent_Factory() {
|
||||
return new MyComponent(
|
||||
$r3$.ɵinjectAttribute('name'),
|
||||
$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');
|
||||
});
|
||||
|
||||
});
|
@ -1,225 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {setup} from '../aot/test_util';
|
||||
import {compile, expectEmit} from './mock_compile';
|
||||
|
||||
const TRANSLATION_NAME_REGEXP = /^MSG_[A-Z0-9]+/;
|
||||
|
||||
describe('i18n support in the view compiler', () => {
|
||||
const angularFiles = setup({
|
||||
compileAngular: true,
|
||||
compileAnimations: false,
|
||||
compileCommon: true,
|
||||
});
|
||||
|
||||
describe('single text nodes', () => {
|
||||
it('should translate single text nodes with the i18n attribute', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`
|
||||
<div i18n>Hello world</div>
|
||||
<div>&</div>
|
||||
<div i18n>farewell</div>
|
||||
<div i18n>farewell</div>
|
||||
\`
|
||||
})
|
||||
export class MyComponent {}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
const $msg_1$ = goog.getMsg('Hello world');
|
||||
const $msg_2$ = goog.getMsg('farewell');
|
||||
…
|
||||
template: function MyComponent_Template(rf: IDENT, ctx: IDENT) {
|
||||
if (rf & 1) {
|
||||
…
|
||||
$r3$.ɵT(1, $msg_1$);
|
||||
…
|
||||
$r3$.ɵT(3,'&');
|
||||
…
|
||||
$r3$.ɵT(5, $msg_2$);
|
||||
…
|
||||
$r3$.ɵT(7, $msg_2$);
|
||||
…
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template', {
|
||||
'$msg_1$': TRANSLATION_NAME_REGEXP,
|
||||
'$msg_2$': TRANSLATION_NAME_REGEXP,
|
||||
});
|
||||
});
|
||||
|
||||
it('should add the meaning and description as JsDoc comments', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`
|
||||
<div i18n="meaning|desc@@id" i18n-title="desc" title="introduction">Hello world</div>
|
||||
\`
|
||||
})
|
||||
export class MyComponent {}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
/**
|
||||
* @desc desc
|
||||
*/
|
||||
const $msg_1$ = goog.getMsg('introduction');
|
||||
const $c1$ = ['title', $msg_1$];
|
||||
…
|
||||
/**
|
||||
* @desc desc
|
||||
* @meaning meaning
|
||||
*/
|
||||
const $msg_2$ = goog.getMsg('Hello world');
|
||||
…
|
||||
template: function MyComponent_Template(rf: IDENT, ctx: IDENT) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵE(0, 'div', $c1$);
|
||||
$r3$.ɵT(1, $msg_2$);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template', {
|
||||
'$msg_1$': TRANSLATION_NAME_REGEXP,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('static attributes', () => {
|
||||
it('should translate static attributes', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`
|
||||
<div i18n id="static" i18n-title="m|d" title="introduction"></div>
|
||||
\`
|
||||
})
|
||||
export class MyComponent {}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
/**
|
||||
* @desc d
|
||||
* @meaning m
|
||||
*/
|
||||
const $msg_1$ = goog.getMsg('introduction');
|
||||
const $c1$ = ['id', 'static', 'title', $msg_1$];
|
||||
…
|
||||
template: function MyComponent_Template(rf: IDENT, ctx: IDENT) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵEe(0, 'div', $c1$);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template', {
|
||||
'$msg_1$': TRANSLATION_NAME_REGEXP,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// TODO(vicb): this feature is not supported yet
|
||||
xdescribe('nested nodes', () => {
|
||||
it('should generate the placeholders maps', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`
|
||||
<div i18n>Hello <b>{{name}}<i>!</i><i>!</i></b></div>
|
||||
<div>Other</div>
|
||||
<div i18n>2nd</div>
|
||||
<div i18n><i>3rd</i></div>
|
||||
\`
|
||||
})
|
||||
export class MyComponent {}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
const $r1$ = {'b':[2], 'i':[4, 6]};
|
||||
const $r2$ = {'i':[13]};
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
});
|
||||
|
||||
describe('errors', () => {
|
||||
it('should throw on nested i18n sections', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`
|
||||
<div i18n><div i18n></div></div>
|
||||
\`
|
||||
})
|
||||
export class MyComponent {}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
expect(() => compile(files, angularFiles))
|
||||
.toThrowError(
|
||||
'Could not mark an element as translatable inside of a translatable section');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
@ -1,89 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {MockDirectory, setup} from '../aot/test_util';
|
||||
import {compile, expectEmit} from './mock_compile';
|
||||
|
||||
describe('compiler compliance: listen()', () => {
|
||||
const angularFiles = setup({
|
||||
compileAngular: true,
|
||||
compileAnimations: false,
|
||||
compileCommon: true,
|
||||
});
|
||||
|
||||
it('should create 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 = `
|
||||
static ngComponentDef = IDENT.ɵdefineComponent({
|
||||
…
|
||||
inputs:{
|
||||
componentInput: 'componentInput',
|
||||
originalComponentInput: 'renamedComponentInput'
|
||||
},
|
||||
outputs: {
|
||||
componentOutput: 'componentOutput',
|
||||
originalComponentOutput: 'renamedComponentOutput'
|
||||
}
|
||||
…
|
||||
});`;
|
||||
|
||||
const directiveDef = `
|
||||
static ngDirectiveDef = IDENT.ɵdefineDirective({
|
||||
…
|
||||
inputs:{
|
||||
directiveInput: 'directiveInput',
|
||||
originalDirectiveInput: 'renamedDirectiveInput'
|
||||
},
|
||||
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,61 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {MockDirectory, setup} from '../aot/test_util';
|
||||
import {compile, expectEmit} from './mock_compile';
|
||||
|
||||
/* These tests are codified version of the tests in compiler_canonical_spec.ts. Every
|
||||
* test in compiler_canonical_spec.ts should have a corresponding test here.
|
||||
*/
|
||||
describe('compiler compliance: listen()', () => {
|
||||
const angularFiles = setup({
|
||||
compileAngular: true,
|
||||
compileAnimations: false,
|
||||
compileCommon: true,
|
||||
});
|
||||
|
||||
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 = `
|
||||
template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵE(0, 'div');
|
||||
$r3$.ɵL('click', function MyComponent_Template_div_click_listener($event: $any$) {
|
||||
ctx.onClick($event);
|
||||
return (1 == 2);
|
||||
});
|
||||
$r3$.ɵe();
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
});
|
@ -1,124 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {MockDirectory, setup} from '../aot/test_util';
|
||||
import {compile, expectEmit} from './mock_compile';
|
||||
|
||||
describe('r3_view_compiler', () => {
|
||||
const angularFiles = setup({
|
||||
compileAngular: true,
|
||||
compileAnimations: false,
|
||||
compileCommon: true,
|
||||
});
|
||||
|
||||
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';
|
||||
import {CommonModule} from '@angular/common';
|
||||
|
||||
@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],
|
||||
imports: [CommonModule]
|
||||
})
|
||||
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 bV 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 = `$r3$.ɵiV([' ',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');
|
||||
});
|
||||
});
|
||||
});
|
@ -1,264 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {InitialStylingFlags} from '../../src/core';
|
||||
import {MockDirectory, setup} from '../aot/test_util';
|
||||
|
||||
import {compile, expectEmit} from './mock_compile';
|
||||
|
||||
describe('compiler compliance: styling', () => {
|
||||
const angularFiles = setup({
|
||||
compileAngular: true,
|
||||
compileAnimations: false,
|
||||
compileCommon: true,
|
||||
});
|
||||
|
||||
describe('[style] and [style.prop]', () => {
|
||||
it('should create style instructions on the element', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`<div [style]="myStyleExp"></div>\`
|
||||
})
|
||||
export class MyComponent {
|
||||
myStyleExp = [{color:'red'}, {color:'blue', duration:1000}]
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
template: function MyComponent_Template(rf: $RenderFlags$, $ctx$: $MyComponent$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵE(0, 'div');
|
||||
$r3$.ɵs();
|
||||
$r3$.ɵe();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵsm(0, $ctx$.myStyleExp);
|
||||
$r3$.ɵsa(0);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should place initial, multi, singular and application followed by attribute style instructions in the template code in that order',
|
||||
() => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`<div style="opacity:1"
|
||||
[attr.style]="'border-width: 10px'"
|
||||
[style.width]="myWidth"
|
||||
[style]="myStyleExp"
|
||||
[style.height]="myHeight"></div>\`
|
||||
})
|
||||
export class MyComponent {
|
||||
myStyleExp = [{color:'red'}, {color:'blue', duration:1000}]
|
||||
myWidth = '100px';
|
||||
myHeight = '100px';
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
const _c0 = ['opacity','width','height',${InitialStylingFlags.VALUES_MODE},'opacity','1'];
|
||||
class MyComponent {
|
||||
static ngComponentDef = i0.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
selectors:[['my-component']],
|
||||
factory:function MyComponent_Factory(){
|
||||
return new MyComponent();
|
||||
},
|
||||
template: function MyComponent_Template(rf: $RenderFlags$, $ctx$: $MyComponent$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵE(0, 'div');
|
||||
$r3$.ɵs(_c0);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵsm(0, $ctx$.myStyleExp);
|
||||
$r3$.ɵsp(0, 1, $ctx$.myWidth);
|
||||
$r3$.ɵsp(0, 2, $ctx$.myHeight);
|
||||
$r3$.ɵsa(0);
|
||||
$r3$.ɵa(0, 'style', $r3$.ɵb('border-width: 10px'));
|
||||
}
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
});
|
||||
|
||||
describe('[class]', () => {
|
||||
it('should create class styling instructions on the element', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`<div [class]="myClassExp"></div>\`
|
||||
})
|
||||
export class MyComponent {
|
||||
myClassExp = {'foo':true}
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
template: function MyComponent_Template(rf: $RenderFlags$, $ctx$: $MyComponent$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵE(0, 'div');
|
||||
$r3$.ɵs();
|
||||
$r3$.ɵe();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵsm(0,(null as any),$ctx$.myClassExp);
|
||||
$r3$.ɵsa(0);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should place initial, multi, singular and application followed by attribute class instructions in the template code in that order',
|
||||
() => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`<div class="grape"
|
||||
[attr.class]="'banana'"
|
||||
[class.apple]="yesToApple"
|
||||
[class]="myClassExp"
|
||||
[class.orange]="yesToOrange"></div>\`
|
||||
})
|
||||
export class MyComponent {
|
||||
myClassExp = {a:true, b:true};
|
||||
yesToApple = true;
|
||||
yesToOrange = true;
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
const _c0 = ['grape','apple','orange',${InitialStylingFlags.VALUES_MODE},'grape',true];
|
||||
class MyComponent {
|
||||
static ngComponentDef = i0.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
selectors:[['my-component']],
|
||||
factory:function MyComponent_Factory(){
|
||||
return new MyComponent();
|
||||
},
|
||||
template: function MyComponent_Template(rf: $RenderFlags$, $ctx$: $MyComponent$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵE(0, 'div');
|
||||
$r3$.ɵs((null as any), _c0);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵsm(0, (null as any), $ctx$.myClassExp);
|
||||
$r3$.ɵcp(0, 1, $ctx$.yesToApple);
|
||||
$r3$.ɵcp(0, 2, $ctx$.yesToOrange);
|
||||
$r3$.ɵsa(0);
|
||||
$r3$.ɵa(0, 'class', $r3$.ɵb('banana'));
|
||||
}
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should not generate the styling apply instruction if there are only static style/class attributes',
|
||||
() => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`<div class="foo"
|
||||
style="width:100px"
|
||||
[attr.class]="'round'"
|
||||
[attr.style]="'height:100px'"></div>\`
|
||||
})
|
||||
export class MyComponent {}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
const _c0 = ['width',${InitialStylingFlags.VALUES_MODE},'width','100px'];
|
||||
const _c1 = ['foo',${InitialStylingFlags.VALUES_MODE},'foo',true];
|
||||
class MyComponent {
|
||||
static ngComponentDef = i0.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
selectors:[['my-component']],
|
||||
factory:function MyComponent_Factory(){
|
||||
return new MyComponent();
|
||||
},
|
||||
template: function MyComponent_Template(rf: $RenderFlags$, $ctx$: $MyComponent$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵE(0, 'div');
|
||||
$r3$.ɵs(_c0, _c1);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵa(0, 'class', $r3$.ɵb('round'));
|
||||
$r3$.ɵa(0, 'style', $r3$.ɵb('height:100px'));
|
||||
}
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
});
|
||||
});
|
@ -1,112 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {MockDirectory, setup} from '../aot/test_util';
|
||||
import {compile, expectEmit} from './mock_compile';
|
||||
|
||||
describe('compiler compliance: template', () => {
|
||||
const angularFiles = setup({
|
||||
compileAngular: true,
|
||||
compileAnimations: false,
|
||||
compileCommon: true,
|
||||
});
|
||||
|
||||
it('should correctly bind to context in nested template', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
|
||||
@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], imports: [CommonModule]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
// The template should look like this (where IDENT is a wild card for an identifier):
|
||||
const template = `
|
||||
const $c0$ = ['ngFor','','ngForOf',''];
|
||||
// ...
|
||||
template:function MyComponent_Template(rf: IDENT, $ctx$: IDENT){
|
||||
if (rf & 1) {
|
||||
$i0$.ɵC(0, MyComponent_ul_Template_0, null, _c0);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$i0$.ɵp(0, 'ngForOf', $i0$.ɵb($ctx$.items));
|
||||
}
|
||||
|
||||
function MyComponent_ul_Template_0(rf: IDENT, $ctx0$: IDENT) {
|
||||
if (rf & 1) {
|
||||
$i0$.ɵE(0, 'ul');
|
||||
$i0$.ɵC(1, MyComponent_ul_li_Template_1, null, _c0);
|
||||
$i0$.ɵe();
|
||||
}
|
||||
if (rf & 2) {
|
||||
const $outer$ = $ctx0$.$implicit;
|
||||
$i0$.ɵp(1, 'ngForOf', $i0$.ɵb($outer$.items));
|
||||
}
|
||||
function MyComponent_ul_li_Template_1(rf: IDENT, $ctx1$: IDENT) {
|
||||
if (rf & 1) {
|
||||
$i0$.ɵE(0, 'li');
|
||||
$i0$.ɵC(1, MyComponent_ul_li_div_Template_1, null, _c0);
|
||||
$i0$.ɵe();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$i0$.ɵp(1, 'ngForOf', $i0$.ɵb($ctx$.items));
|
||||
}
|
||||
function MyComponent_ul_li_div_Template_1(rf: IDENT, $ctx2$: IDENT) {
|
||||
if (rf & 1) {
|
||||
$i0$.ɵE(0, 'div');
|
||||
$i0$.ɵL('click', function MyComponent_ul_li_div_Template_1_div_click_listener($event:any){
|
||||
const $outer$ = $ctx0$.$implicit;
|
||||
const $middle$ = $ctx1$.$implicit;
|
||||
const $inner$ = $ctx2$.$implicit;
|
||||
return ctx.onClick($outer$, $middle$, $inner$);
|
||||
});
|
||||
$i0$.ɵT(1);
|
||||
$i0$.ɵe();
|
||||
}
|
||||
if (rf & 2) {
|
||||
const $outer$ = $ctx0$.$implicit;
|
||||
const $middle$ = $ctx1$.$implicit;
|
||||
const $inner$ = $ctx2$.$implicit;
|
||||
$i0$.ɵp(0, 'title', $i0$.ɵb(ctx.format($outer$, $middle$, $inner$, $ctx$.component)));
|
||||
$i0$.ɵt(1, $i0$.ɵi1(' ', ctx.format($outer$, $middle$, $inner$, $ctx$.component), ' '));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
});
|
Reference in New Issue
Block a user