From 4810f5c819c4df032641179bc4d94a6695d94b69 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Sat, 10 Apr 2021 17:35:56 +0100 Subject: [PATCH] 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 --- .../test/compliance_old/BUILD.bazel | 36 - .../test/compliance_old/README.md | 5 - .../compliance_old/mock_compile/BUILD.bazel | 19 - .../test/compliance_old/mock_compile/index.ts | 249 -- .../test/compliance_old/mock_compiler_spec.ts | 245 -- .../test/compliance_old/prelink/BUILD.bazel | 41 - .../test/compliance_old/prelink/bootstrap.ts | 79 - .../r3_compiler_compliance_spec.ts | 3356 ---------------- .../r3_view_compiler_binding_spec.ts | 1503 ------- .../r3_view_compiler_di_spec.ts | 398 -- .../r3_view_compiler_directives_spec.ts | 410 -- .../r3_view_compiler_i18n_spec.ts | 3446 ----------------- .../r3_view_compiler_input_outputs_spec.ts | 88 - .../r3_view_compiler_listener_spec.ts | 583 --- .../r3_view_compiler_providers_spec.ts | 162 - .../compliance_old/r3_view_compiler_spec.ts | 247 -- .../r3_view_compiler_styling_spec.ts | 2099 ---------- .../r3_view_compiler_template_spec.ts | 874 ----- 18 files changed, 13840 deletions(-) delete mode 100644 packages/compiler-cli/test/compliance_old/BUILD.bazel delete mode 100644 packages/compiler-cli/test/compliance_old/README.md delete mode 100644 packages/compiler-cli/test/compliance_old/mock_compile/BUILD.bazel delete mode 100644 packages/compiler-cli/test/compliance_old/mock_compile/index.ts delete mode 100644 packages/compiler-cli/test/compliance_old/mock_compiler_spec.ts delete mode 100644 packages/compiler-cli/test/compliance_old/prelink/BUILD.bazel delete mode 100644 packages/compiler-cli/test/compliance_old/prelink/bootstrap.ts delete mode 100644 packages/compiler-cli/test/compliance_old/r3_compiler_compliance_spec.ts delete mode 100644 packages/compiler-cli/test/compliance_old/r3_view_compiler_binding_spec.ts delete mode 100644 packages/compiler-cli/test/compliance_old/r3_view_compiler_di_spec.ts delete mode 100644 packages/compiler-cli/test/compliance_old/r3_view_compiler_directives_spec.ts delete mode 100644 packages/compiler-cli/test/compliance_old/r3_view_compiler_i18n_spec.ts delete mode 100644 packages/compiler-cli/test/compliance_old/r3_view_compiler_input_outputs_spec.ts delete mode 100644 packages/compiler-cli/test/compliance_old/r3_view_compiler_listener_spec.ts delete mode 100644 packages/compiler-cli/test/compliance_old/r3_view_compiler_providers_spec.ts delete mode 100644 packages/compiler-cli/test/compliance_old/r3_view_compiler_spec.ts delete mode 100644 packages/compiler-cli/test/compliance_old/r3_view_compiler_styling_spec.ts delete mode 100644 packages/compiler-cli/test/compliance_old/r3_view_compiler_template_spec.ts diff --git a/packages/compiler-cli/test/compliance_old/BUILD.bazel b/packages/compiler-cli/test/compliance_old/BUILD.bazel deleted file mode 100644 index 3635c4b97f..0000000000 --- a/packages/compiler-cli/test/compliance_old/BUILD.bazel +++ /dev/null @@ -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", - ], -) diff --git a/packages/compiler-cli/test/compliance_old/README.md b/packages/compiler-cli/test/compliance_old/README.md deleted file mode 100644 index c383188ac1..0000000000 --- a/packages/compiler-cli/test/compliance_old/README.md +++ /dev/null @@ -1,5 +0,0 @@ -Tests in this directory should be run with: - -``` -yarn bazel test --config=ivy packages/compiler-cli/test/compliance:compliance -``` \ No newline at end of file diff --git a/packages/compiler-cli/test/compliance_old/mock_compile/BUILD.bazel b/packages/compiler-cli/test/compliance_old/mock_compile/BUILD.bazel deleted file mode 100644 index 9cd4bf7687..0000000000 --- a/packages/compiler-cli/test/compliance_old/mock_compile/BUILD.bazel +++ /dev/null @@ -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", - ], -) diff --git a/packages/compiler-cli/test/compliance_old/mock_compile/index.ts b/packages/compiler-cli/test/compliance_old/mock_compile/index.ts deleted file mode 100644 index 95c50391ea..0000000000 --- a/packages/compiler-cli/test/compliance_old/mock_compile/index.ts +++ /dev/null @@ -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 ? '' : 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} { - const results: string[] = []; - let first = true; - let group = 0; - - const groups = new Map(); - 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; -} diff --git a/packages/compiler-cli/test/compliance_old/mock_compiler_spec.ts b/packages/compiler-cli/test/compliance_old/mock_compiler_spec.ts deleted file mode 100644 index ba8cf564b5..0000000000 --- a/packages/compiler-cli/test/compliance_old/mock_compiler_spec.ts +++ /dev/null @@ -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'); - - // $$ 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\)\//); - }); -}); diff --git a/packages/compiler-cli/test/compliance_old/prelink/BUILD.bazel b/packages/compiler-cli/test/compliance_old/prelink/BUILD.bazel deleted file mode 100644 index 3953c78dc9..0000000000 --- a/packages/compiler-cli/test/compliance_old/prelink/BUILD.bazel +++ /dev/null @@ -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", - ], -) diff --git a/packages/compiler-cli/test/compliance_old/prelink/bootstrap.ts b/packages/compiler-cli/test/compliance_old/prelink/bootstrap.ts deleted file mode 100644 index 3830156273..0000000000 --- a/packages/compiler-cli/test/compliance_old/prelink/bootstrap.ts +++ /dev/null @@ -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); diff --git a/packages/compiler-cli/test/compliance_old/r3_compiler_compliance_spec.ts b/packages/compiler-cli/test/compliance_old/r3_compiler_compliance_spec.ts deleted file mode 100644 index 8b99bf37ad..0000000000 --- a/packages/compiler-cli/test/compliance_old/r3_compiler_compliance_spec.ts +++ /dev/null @@ -1,3356 +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 * as ts from 'typescript'; -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', () => { - const angularFiles = setup({ - compileAngular: false, - compileAnimations: false, - compileFakeCore: true, - }); - - describe('elements', () => { - it('should handle SVG', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \`

test

\` - }) - export class MyComponent {} - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - // The factory should look like this: - const factory = - 'MyComponent.ɵfac = function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; - - // The template should look like this (where IDENT is a wild card for an identifier): - const template = ` - … - consts: [["title", "Hello", ${ - AttributeMarker.Classes}, "my-app"], ["cx", "20", "cy", "30", "r", "50"]], - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div", 0); - $r3$.ɵɵnamespaceSVG(); - $r3$.ɵɵelementStart(1, "svg"); - $r3$.ɵɵelement(2, "circle", 1); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵnamespaceHTML(); - $r3$.ɵɵelementStart(3, "p"); - $r3$.ɵɵtext(4, "test"); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementEnd(); - } - } - `; - - - const result = compile(files, angularFiles); - - expectEmit(result.source, factory, 'Incorrect factory'); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should handle MathML', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \`

test

\` - }) - export class MyComponent {} - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - // The factory should look like this: - const factory = - 'MyComponent.ɵfac = function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; - - // The template should look like this (where IDENT is a wild card for an identifier): - const template = ` - … - consts: [["title", "Hello", ${AttributeMarker.Classes}, "my-app"]], - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div", 0); - $r3$.ɵɵnamespaceMathML(); - $r3$.ɵɵelementStart(1, "math"); - $r3$.ɵɵelement(2, "infinity"); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵnamespaceHTML(); - $r3$.ɵɵelementStart(3, "p"); - $r3$.ɵɵtext(4, "test"); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementEnd(); - } - } - `; - - - const result = compile(files, angularFiles); - expectEmit(result.source, factory, 'Incorrect factory'); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should translate DOM structure', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \`
Hello World!
\` - }) - export class MyComponent {} - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - // The factory should look like this: - const factory = - 'MyComponent.ɵfac = function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; - - // The template should look like this (where IDENT is a wild card for an identifier): - const template = ` - … - consts: [["title", "Hello", ${AttributeMarker.Classes}, "my-app"]], - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div", 0); - $r3$.ɵɵtext(1, "Hello "); - $r3$.ɵɵelementStart(2, "b"); - $r3$.ɵɵtext(3, "World"); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵtext(4, "!"); - $r3$.ɵɵelementEnd(); - } - } - `; - - - const result = compile(files, angularFiles); - - expectEmit(result.source, factory, 'Incorrect factory'); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should support namespaced attributes', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \`
Hello World!
\` - }) - export class MyComponent {} - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - // The factory should look like this: - const factory = - 'MyComponent.ɵfac = function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; - - // The template should look like this (where IDENT is a wild card for an identifier): - const template = ` - … - consts: [[${AttributeMarker.NamespaceURI}, "xmlns", "foo", "http://someuri/foo", ${ - AttributeMarker.NamespaceURI}, "foo", "bar", "baz", "title", "Hello", ${ - AttributeMarker.NamespaceURI}, "foo", "qux", "quacks", ${ - AttributeMarker.Classes}, "my-app"]], - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div", 0); - $r3$.ɵɵtext(1, "Hello "); - $r3$.ɵɵelementStart(2, "b"); - $r3$.ɵɵtext(3, "World"); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵtext(4, "!"); - $r3$.ɵɵelementEnd(); - } - } - `; - - - const result = compile(files, angularFiles); - - expectEmit(result.source, factory, 'Incorrect factory'); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should support ', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \`in a container\` - }) - 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 = ` - … - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - i0.ɵɵelementContainerStart(0); - i0.ɵɵelementStart(1, "span"); - i0.ɵɵtext(2, "in a "); - i0.ɵɵelementEnd(); - i0.ɵɵtext(3, "container"); - i0.ɵɵelementContainerEnd(); - } - } - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should generate self-closing elementContainer instruction for empty ', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \`\` - }) - 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 = ` - … - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - i0.ɵɵelementContainer(0); - } - } - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should bind to element properties', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \`
\` - }) - export class MyComponent { - id = 'one'; - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const factory = - 'MyComponent.ɵfac = function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; - const template = ` - … - consts: [[${AttributeMarker.Bindings}, "id"]], - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div", 0); - } - if (rf & 2) { - $r3$.ɵɵproperty("id", ctx.id); - } - } - `; - - - const result = compile(files, angularFiles); - - expectEmit(result.source, factory, 'Incorrect factory'); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should reserve slots for pure functions', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \`
\` - }) - export class MyComponent { - id = 'one'; - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - /////////////// - // TODO(FW-1273): The code generated below is adding extra parens, and we need to stop - // generating those. - // - // For example: - // `$r3$.ɵɵproperty("ternary", (ctx.cond ? $r3$.ɵɵpureFunction1(8, $c0$, ctx.a): $c1$));` - /////////////// - - const factory = - 'MyComponent.ɵfac = function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; - const template = ` - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div", 0); - $r3$.ɵɵpipe(1, "pipe"); - } - if (rf & 2) { - $r3$.ɵɵproperty("ternary", ctx.cond ? $r3$.ɵɵpureFunction1(8, $c0$, ctx.a): $r3$.ɵɵpureFunction0(10, $c1$))("pipe", $r3$.ɵɵpipeBind3(1, 4, ctx.value, 1, 2))("and", ctx.cond && $r3$.ɵɵpureFunction1(11, $c0$, ctx.b))("or", ctx.cond || $r3$.ɵɵpureFunction1(13, $c0$, ctx.c)); - } - } - `; - - - const result = compile(files, angularFiles); - - expectEmit(result.source, factory, 'Incorrect factory'); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should reserve slots for pure functions in host binding function', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule, Input} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: '...', - host: { - '[@expansionHeight]': \`{ - value: getExpandedState(), - params: { - collapsedHeight: collapsedHeight, - expandedHeight: expandedHeight - } - }\`, - '[@expansionWidth]': \`{ - value: getExpandedState(), - params: { - collapsedWidth: collapsedWidth, - expandedWidth: expandedWidth - } - }\` - } - }) - export class MyComponent { - @Input() expandedHeight: string; - @Input() collapsedHeight: string; - - @Input() expandedWidth: string; - @Input() collapsedWidth: string; - - getExpandedState() { - return 'expanded'; - } - } - - @NgModule({ - declarations: [MyComponent] - }) - export class MyModule {} - ` - } - }; - - const hostBindingsDef = ` - const $_c0$ = function (a0, a1) { return { collapsedHeight: a0, expandedHeight: a1 }; }; - const $_c1$ = function (a0, a1) { return { value: a0, params: a1 }; }; - const $_c2$ = function (a0, a1) { return { collapsedWidth: a0, expandedWidth: a1 }; }; - … - hostVars: 14, - hostBindings: function MyComponent_HostBindings(rf, ctx) { - if (rf & 2) { - $r3$.ɵɵsyntheticHostProperty("@expansionHeight", - $r3$.ɵɵpureFunction2(5, $_c1$, ctx.getExpandedState(), - $r3$.ɵɵpureFunction2(2, $_c0$, ctx.collapsedHeight, ctx.expandedHeight) - ) - )("@expansionWidth", - $r3$.ɵɵpureFunction2(11, $_c1$, ctx.getExpandedState(), - $r3$.ɵɵpureFunction2(8, $_c2$, ctx.collapsedWidth, ctx.expandedWidth) - ) - ); - } - }, - … - `; - const result = compile(files, angularFiles); - expectEmit(result.source, hostBindingsDef, 'Incorrect "hostBindings" function'); - }); - - it('should bind to class and style names', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \`
\` - }) - export class MyComponent { - error = true; - color = 'red'; - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const factory = - 'MyComponent.ɵfac = function MyComponent_Factory(t) { return new (t || MyComponent)(); }'; - const template = ` - MyComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({type:MyComponent,selectors:[["my-component"]], - decls: 1, - vars: 4, - template: function MyComponent_Template(rf,ctx){ - if (rf & 1) { - $r3$.ɵɵelement(0, "div"); - } - if (rf & 2) { - $r3$.ɵɵstyleProp("background-color", ctx.color); - $r3$.ɵɵclassProp("error", ctx.error); - } - }, - encapsulation: 2 - }); - `; - - - const result = compile(files, angularFiles); - - expectEmit(result.source, factory, 'Incorrect factory'); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should de-duplicate attribute arrays', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \` -
- - \` - }) - export class MyComponent { - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - … - consts: [["title", "hi"]], - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div", 0); - $r3$.ɵɵelement(1, "span", 0); - } - … - } - `; - - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - }); - - describe('components & directives', () => { - it('should instantiate directives', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, Directive, NgModule} from '@angular/core'; - - @Component({selector: 'child', template: 'child-view'}) - export class ChildComponent {} - - @Directive({selector: '[some-directive]'}) - export class SomeDirective {} - - @Component({selector: 'my-component', template: '!'}) - export class MyComponent {} - - @NgModule({declarations: [ChildComponent, SomeDirective, MyComponent]}) - export class MyModule{} - ` - } - }; - - // ChildComponent definition should be: - const ChildComponentDefinition = ` - ChildComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: ChildComponent, - selectors: [["child"]], - decls: 1, - vars: 0, - template: function ChildComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵtext(0, "child-view"); - } - }, - encapsulation: 2 - });`; - - const ChildComponentFactory = - `ChildComponent.ɵfac = function ChildComponent_Factory(t) { return new (t || ChildComponent)(); };`; - - // SomeDirective definition should be: - const SomeDirectiveDefinition = ` - SomeDirective.ɵdir = /*@__PURE__*/ $r3$.ɵɵdefineDirective({ - type: SomeDirective, - selectors: [["", "some-directive", ""]] - }); - `; - - const SomeDirectiveFactory = - `SomeDirective.ɵfac = function SomeDirective_Factory(t) {return new (t || SomeDirective)(); };`; - - // MyComponent definition should be: - const MyComponentDefinition = ` - … - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyComponent, - selectors: [["my-component"]], - decls: 2, - vars: 0, - consts: [["some-directive", ""]], - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "child", 0); - $r3$.ɵɵtext(1, "!"); - } - }, - directives: [ChildComponent, SomeDirective], - 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, ChildComponentDefinition, 'Incorrect ChildComponent.ɵcmp'); - expectEmit(source, ChildComponentFactory, 'Incorrect ChildComponent.ɵfac'); - expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeDirective.ɵdir'); - expectEmit(source, SomeDirectiveFactory, 'Incorrect SomeDirective.ɵfac'); - expectEmit(source, MyComponentDefinition, 'Incorrect MyComponentDefinition.ɵcmp'); - expectEmit(source, MyComponentFactory, 'Incorrect MyComponentDefinition.ɵfac'); - }); - - it('should support complex selectors', () => { - const files = { - app: { - 'spec.ts': ` - import {Directive, NgModule} from '@angular/core'; - - @Directive({selector: 'div.foo[some-directive]:not([title]):not(.baz)'}) - export class SomeDirective {} - - @Directive({selector: ':not(span[title]):not(.baz)'}) - export class OtherDirective {} - - @NgModule({declarations: [SomeDirective, OtherDirective]}) - export class MyModule{} - ` - } - }; - - // SomeDirective definition should be: - const SomeDirectiveDefinition = ` - SomeDirective.ɵdir = /*@__PURE__*/ $r3$.ɵɵdefineDirective({ - type: SomeDirective, - selectors: [["div", "some-directive", "", 8, "foo", 3, "title", "", 9, "baz"]] - }); - `; - - const SomeDirectiveFactory = - `SomeDirective.ɵfac = function SomeDirective_Factory(t) {return new (t || SomeDirective)(); };`; - - // OtherDirective definition should be: - const OtherDirectiveDefinition = ` - OtherDirective.ɵdir = /*@__PURE__*/ $r3$.ɵɵdefineDirective({ - type: OtherDirective, - selectors: [["", 5, "span", "title", "", 9, "baz"]] - }); - `; - - const OtherDirectiveFactory = - `OtherDirective.ɵfac = function OtherDirective_Factory(t) {return new (t || OtherDirective)(); };`; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeDirective.ɵdir'); - expectEmit(source, SomeDirectiveFactory, 'Incorrect SomeDirective.ɵfac'); - expectEmit(source, OtherDirectiveDefinition, 'Incorrect OtherDirective.ɵdir'); - expectEmit(source, OtherDirectiveFactory, 'Incorrect OtherDirective.ɵfac'); - }); - - it('should convert #my-app selector to ["", "id", "my-app"]', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({selector: '#my-app', template: ''}) - export class SomeComponent {} - - @NgModule({declarations: [SomeComponent]}) - export class MyModule{} - ` - } - }; - - // SomeDirective definition should be: - const SomeDirectiveDefinition = ` - SomeComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: SomeComponent, - selectors: [["", "id", "my-app"]], - … - }); - `; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeComponent.ɵcomp'); - }); - it('should support components without selector', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, Directive, NgModule} from '@angular/core'; - - @Component({template: ''}) - export class EmptyOutletComponent {} - - @NgModule({declarations: [EmptyOutletComponent]}) - export class MyModule{} - ` - } - }; - - // EmptyOutletComponent definition should be: - const EmptyOutletComponentDefinition = ` - … - EmptyOutletComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: EmptyOutletComponent, - selectors: [["ng-component"]], - decls: 1, - vars: 0, - template: function EmptyOutletComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "router-outlet"); - } - }, - encapsulation: 2 - }); - `; - - const EmptyOutletComponentFactory = - `EmptyOutletComponent.ɵfac = function EmptyOutletComponent_Factory(t) { return new (t || EmptyOutletComponent)(); };`; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, EmptyOutletComponentDefinition, 'Incorrect EmptyOutletComponent.ɵcmp'); - expectEmit(source, EmptyOutletComponentFactory, 'Incorrect EmptyOutletComponent.ɵfac'); - }); - - it('should not treat ElementRef, ViewContainerRef, or ChangeDetectorRef specially when injecting', - () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule, ElementRef, ChangeDetectorRef, ViewContainerRef} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: '' - }) - export class MyComponent { - constructor(public el: ElementRef, public vcr: ViewContainerRef, public cdr: ChangeDetectorRef) {} - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const MyComponentDefinition = ` - … - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyComponent, - selectors: [["my-component"]], - decls: 0, - vars: 0, - template: function MyComponent_Template(rf, ctx) {}, - encapsulation: 2 - });`; - - const MyComponentFactory = `MyComponent.ɵfac = function MyComponent_Factory(t) { - return new (t || MyComponent)( - $r3$.ɵɵdirectiveInject($i$.ElementRef), $r3$.ɵɵdirectiveInject($i$.ViewContainerRef), - $r3$.ɵɵdirectiveInject($i$.ChangeDetectorRef)); - };`; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ɵcmp'); - expectEmit(source, MyComponentFactory, 'Incorrect MyComponent.ɵfac'); - }); - - it('should support structural directives', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, Directive, NgModule, TemplateRef} from '@angular/core'; - - @Directive({selector: '[if]'}) - export class IfDirective { - constructor(template: TemplateRef) { } - } - - @Component({ - selector: 'my-component', - template: '
  • {{salutation}} {{foo}}
' - }) - export class MyComponent { - salutation = 'Hello'; - } - - @NgModule({declarations: [IfDirective, MyComponent]}) - export class MyModule {} - ` - } - }; - - const IfDirectiveDefinition = ` - IfDirective.ɵdir = /*@__PURE__*/ $r3$.ɵɵdefineDirective({ - type: IfDirective, - selectors: [["", "if", ""]] - });`; - const IfDirectiveFactory = - `IfDirective.ɵfac = function IfDirective_Factory(t) { return new (t || IfDirective)($r3$.ɵɵdirectiveInject($i$.TemplateRef)); };`; - - const MyComponentDefinition = ` - function MyComponent_li_2_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "li"); - $r3$.ɵɵtext(1); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - const $myComp$ = $r3$.ɵɵnextContext(); - const $foo$ = $r3$.ɵɵreference(1); - $r3$.ɵɵadvance(1); - $r3$.ɵɵtextInterpolate2("", $myComp$.salutation, " ", $foo$, ""); - } - } - … - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyComponent, - selectors: [["my-component"]], - decls: 3, - vars: 0, - consts: [["foo", ""], [${AttributeMarker.Template}, "if"]], - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "ul", null, 0); - $r3$.ɵɵtemplate(2, MyComponent_li_2_Template, 2, 2, "li", 1); - $r3$.ɵɵelementEnd(); - } - }, - directives: [IfDirective], - 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, IfDirectiveDefinition, 'Incorrect IfDirective.ɵdir'); - expectEmit(source, IfDirectiveFactory, 'Incorrect IfDirective.ɵfac'); - expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ɵcmp'); - expectEmit(source, MyComponentFactory, 'Incorrect MyComponent.ɵfac'); - }); - - describe('value composition', () => { - it('should support array literals', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, Input, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-comp', - template: \` -

{{ names[0] }}

-

{{ names[1] }}

- \` - }) - export class MyComp { - @Input() names: string[]; - } - - @Component({ - selector: 'my-app', - template: \` - - \` - }) - export class MyApp { - customName = 'Bess'; - } - - @NgModule({declarations: [MyComp, MyApp]}) - export class MyModule { } - ` - } - }; - - const MyAppDeclaration = ` - const $e0_ff$ = function ($v$) { return ["Nancy", $v$]; }; - … - MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyApp, - selectors: [["my-app"]], - decls: 1, - vars: 3, - consts: [[${AttributeMarker.Bindings}, "names"]], - template: function MyApp_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "my-comp", 0); - } - if (rf & 2) { - $r3$.ɵɵproperty("names", $r3$.ɵɵpureFunction1(1, $e0_ff$, ctx.customName)); - } - }, - directives: [MyComp], - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, MyAppDeclaration, 'Invalid array emit'); - }); - - it('should support 9+ bindings in array literals', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, Input, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-comp', - template: \` - {{ names[0] }} - {{ names[1] }} - {{ names[3] }} - {{ names[4] }} - {{ names[5] }} - {{ names[6] }} - {{ names[7] }} - {{ names[8] }} - {{ names[9] }} - {{ names[10] }} - {{ names[11] }} - \` - }) - export class MyComp { - @Input() names: string[]; - } - - @Component({ - selector: 'my-app', - template: \` - - - \` - }) - export class MyApp { - n0 = 'a'; - n1 = 'b'; - n2 = 'c'; - n3 = 'd'; - n4 = 'e'; - n5 = 'f'; - n6 = 'g'; - n7 = 'h'; - n8 = 'i'; - } - - @NgModule({declarations: [MyComp, MyApp]}) - export class MyModule {} - ` - } - }; - - const MyAppDefinition = ` - const $e0_ff$ = function ($v0$, $v1$, $v2$, $v3$, $v4$, $v5$, $v6$, $v7$, $v8$) { - return ["start-", $v0$, $v1$, $v2$, $v3$, $v4$, "-middle-", $v5$, $v6$, $v7$, $v8$, "-end"]; - } - … - MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyApp, - selectors: [["my-app"]], - decls: 1, - vars: 11, - consts: [[${AttributeMarker.Bindings}, "names"]], - template: function MyApp_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "my-comp", 0); - } - if (rf & 2) { - $r3$.ɵɵproperty("names", - $r3$.ɵɵpureFunctionV(1, $e0_ff$, [ctx.n0, ctx.n1, ctx.n2, ctx.n3, ctx.n4, ctx.n5, ctx.n6, ctx.n7, ctx.n8])); - } - }, - directives: [MyComp], - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, MyAppDefinition, 'Invalid array binding'); - }); - - it('should support object literals', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, Input, NgModule} from '@angular/core'; - - @Component({ - selector: 'object-comp', - template: \` -

{{ config['duration'] }}

-

{{ config.animation }}

- \` - }) - export class ObjectComp { - @Input() config: {[key: string]: any}; - } - - @Component({ - selector: 'my-app', - template: \` - - \` - }) - export class MyApp { - name = 'slide'; - } - - @NgModule({declarations: [ObjectComp, MyApp]}) - export class MyModule {} - ` - } - }; - - const MyAppDefinition = ` - const $e0_ff$ = function ($v$) { return {"duration": 500, animation: $v$}; }; - … - MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyApp, - selectors: [["my-app"]], - decls: 1, - vars: 3, - consts: [[${AttributeMarker.Bindings}, "config"]], - template: function MyApp_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "object-comp", 0); - } - if (rf & 2) { - $r3$.ɵɵproperty("config", $r3$.ɵɵpureFunction1(1, $e0_ff$, ctx.name)); - } - }, - directives: [ObjectComp], - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, MyAppDefinition, 'Invalid object literal binding'); - }); - - it('should support expressions nested deeply in object/array literals', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, Input, NgModule} from '@angular/core'; - - @Component({ - selector: 'nested-comp', - template: \` -

{{ config.animation }}

-

{{config.actions[0].opacity }}

-

{{config.actions[1].duration }}

- \` - }) - export class NestedComp { - @Input() config: {[key: string]: any}; - } - - @Component({ - selector: 'my-app', - template: \` - - - \` - }) - export class MyApp { - name = 'slide'; - duration = 100; - } - - @NgModule({declarations: [NestedComp, MyApp]}) - export class MyModule {} - ` - } - }; - - const MyAppDefinition = ` - const $c0$ = function () { return {opacity: 0, duration: 0}; }; - const $e0_ff$ = function ($v$) { return {opacity: 1, duration: $v$}; }; - const $e0_ff_1$ = function ($v1$, $v2$) { return [$v1$, $v2$]; }; - const $e0_ff_2$ = function ($v1$, $v2$) { return {animation: $v1$, actions: $v2$}; }; - … - MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyApp, - selectors: [["my-app"]], - decls: 1, - vars: 10, - consts: [[${AttributeMarker.Bindings}, "config"]], - template: function MyApp_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "nested-comp", 0); - } - if (rf & 2) { - $r3$.ɵɵproperty("config", - $r3$.ɵɵpureFunction2(7, $e0_ff_2$, ctx.name, $r3$.ɵɵpureFunction2(4, $e0_ff_1$, $r3$.ɵɵpureFunction0(1, $c0$), $r3$.ɵɵpureFunction1(2, $e0_ff$, ctx.duration)))); - } - }, - directives: [NestedComp], - encapsulation: 2 - }); - `; - - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, MyAppDefinition, 'Invalid array/object literal binding'); - }); - }); - - describe('content projection', () => { - it('should support content projection in root template', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, Directive, NgModule, TemplateRef} from '@angular/core'; - - @Component({selector: 'simple', template: '
'}) - export class SimpleComponent {} - - @Component({ - selector: 'complex', - template: \` -
-
\` - }) - export class ComplexComponent { } - - @NgModule({declarations: [SimpleComponent, ComplexComponent]}) - export class MyModule {} - - @Component({ - selector: 'my-app', - template: 'content ' - }) - export class MyApp {} - ` - } - }; - - const SimpleComponentDefinition = ` - SimpleComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: SimpleComponent, - selectors: [["simple"]], - ngContentSelectors: $c0$, - decls: 2, - vars: 0, - template: function SimpleComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵprojectionDef(); - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵprojection(1); - $r3$.ɵɵelementEnd(); - } - }, - encapsulation: 2 - });`; - - const ComplexComponentDefinition = ` - const $c1$ = [[["span", "title", "tofirst"]], [["span", "title", "tosecond"]]]; - … - ComplexComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: ComplexComponent, - selectors: [["complex"]], - ngContentSelectors: $c2$, - decls: 4, - vars: 0, - consts: [["id","first"], ["id","second"]], - template: function ComplexComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵprojectionDef($c1$); - $r3$.ɵɵelementStart(0, "div", 0); - $r3$.ɵɵprojection(1); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementStart(2, "div", 1); - $r3$.ɵɵprojection(3, 1); - $r3$.ɵɵelementEnd(); - } - }, - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit( - result.source, SimpleComponentDefinition, 'Incorrect SimpleComponent definition'); - expectEmit( - result.source, ComplexComponentDefinition, 'Incorrect ComplexComponent definition'); - }); - - it('should support multi-slot content projection with multiple wildcard slots', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - template: \` - - - - \`, - }) - class Cmp {} - - @NgModule({ declarations: [Cmp] }) - class Module {} - `, - } - }; - - const output = ` - const $c0$ = ["*", [["", "spacer", ""]], "*"]; - const $c1$ = ["*", "[spacer]", "*"]; - … - Cmp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: Cmp, - selectors: [["ng-component"]], - ngContentSelectors: $c1$, - decls: 3, - vars: 0, - template: function Cmp_Template(rf, ctx) { - if (rf & 1) { - i0.ɵɵprojectionDef($c0$); - i0.ɵɵprojection(0); - i0.ɵɵprojection(1, 1); - i0.ɵɵprojection(2, 2); - } - }, - encapsulation: 2 - }); - `; - - const {source} = compile(files, angularFiles); - expectEmit(source, output, 'Invalid content projection instructions generated'); - }); - - it('should support content projection in nested templates', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - template: \` -
- -
-
- No ng-content, no instructions generated. -
- - '*' selector: - - \`, - }) - class Cmp {} - - @NgModule({ declarations: [Cmp] }) - class Module {} - ` - } - }; - const output = ` - function Cmp_div_0_Template(rf, ctx) { if (rf & 1) { - $r3$.ɵɵelementStart(0, "div", 2); - $r3$.ɵɵprojection(1); - $r3$.ɵɵelementEnd(); - } } - function Cmp_div_1_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div", 3); - $r3$.ɵɵtext(1, " No ng-content, no instructions generated. "); - $r3$.ɵɵelementEnd(); - } - } - function Cmp_ng_template_2_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵtext(0, " '*' selector: "); - $r3$.ɵɵprojection(1, 1); - } - } - const $_c4$ = [[["span", "title", "tofirst"]], "*"]; - … - consts: [["id", "second", ${AttributeMarker.Template}, "ngIf"], ["id", "third", ${ - AttributeMarker.Template}, "ngIf"], ["id", "second"], ["id", "third"]], - template: function Cmp_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵprojectionDef($_c4$); - $r3$.ɵɵtemplate(0, Cmp_div_0_Template, 2, 0, "div", 0); - $r3$.ɵɵtemplate(1, Cmp_div_1_Template, 2, 0, "div", 1); - $r3$.ɵɵtemplate(2, Cmp_ng_template_2_Template, 2, 0, "ng-template"); - } - if (rf & 2) { - $r3$.ɵɵproperty("ngIf", ctx.visible); - $r3$.ɵɵadvance(1); - $r3$.ɵɵproperty("ngIf", ctx.visible); - } - } - `; - - const {source} = compile(files, angularFiles); - expectEmit(source, output, 'Invalid content projection instructions generated'); - }); - - it('should support content projection in both the root and nested templates', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - template: \` - - - - - - - - - '*' selector in a template: - - - \`, - }) - class Cmp {} - - @NgModule({ declarations: [Cmp] }) - class Module {} - ` - } - }; - - const output = ` - function Cmp_ng_template_1_ng_template_1_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵprojection(0, 3); - } - } - function Cmp_ng_template_1_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵprojection(0, 2); - $r3$.ɵɵtemplate(1, Cmp_ng_template_1_ng_template_1_Template, 1, 0, "ng-template"); - } - } - function Cmp_ng_template_2_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵtext(0, " '*' selector in a template: "); - $r3$.ɵɵprojection(1, 4); - } - } - const $_c0$ = [[["", "id", "tomainbefore"]], [["", "id", "tomainafter"]], [["", "id", "totemplate"]], [["", "id", "tonestedtemplate"]], "*"]; - const $_c1$ = ["[id=toMainBefore]", "[id=toMainAfter]", "[id=toTemplate]", "[id=toNestedTemplate]", "*"]; - … - template: function Cmp_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵprojectionDef($_c0$); - $r3$.ɵɵprojection(0); - $r3$.ɵɵtemplate(1, Cmp_ng_template_1_Template, 2, 0, "ng-template"); - $r3$.ɵɵtemplate(2, Cmp_ng_template_2_Template, 2, 0, "ng-template"); - $r3$.ɵɵprojection(3, 1); - } - } - `; - - const {source} = compile(files, angularFiles); - expectEmit(source, output, 'Invalid content projection instructions generated'); - }); - - it('should parse the selector that is passed into ngProjectAs', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'simple', - template: '
' - }) - export class SimpleComponent {} - - @NgModule({declarations: [SimpleComponent]}) - export class MyModule {} - - @Component({ - selector: 'my-app', - template: '

' - }) - export class MyApp {} - ` - } - }; - - // Note that the c0 and c1 constants aren't being used in this particular test, - // but they are used in some of the logic that is folded under the ellipsis. - const SimpleComponentDefinition = ` - const $_c0$ = [[["", "title", ""]]]; - const $_c1$ = ["[title]"]; - … - MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyApp, - selectors: [["my-app"]], - decls: 2, - vars: 0, - consts: [["ngProjectAs", "[title]", 5, ["", "title", ""]]], - template: function MyApp_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "simple"); - $r3$.ɵɵelement(1, "h1", 0); - $r3$.ɵɵelementEnd(); - } - }, - encapsulation: 2 - })`; - - const result = compile(files, angularFiles); - - expectEmit( - result.source, SimpleComponentDefinition, 'Incorrect SimpleComponent definition'); - }); - - it('should take the first selector if multiple values are passed into ngProjectAs', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'simple', - template: '
' - }) - export class SimpleComponent {} - - @NgModule({declarations: [SimpleComponent]}) - export class MyModule {} - - @Component({ - selector: 'my-app', - template: '

' - }) - export class MyApp {} - ` - } - }; - - // Note that the c0 and c1 constants aren't being used in this particular test, - // but they are used in some of the logic that is folded under the ellipsis. - const SimpleComponentDefinition = ` - const $_c0$ = [[["", "title", ""]]]; - const $_c1$ = ["[title]"]; - … - MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyApp, - selectors: [["my-app"]], - decls: 2, - vars: 0, - consts: [["ngProjectAs", "[title],[header]", 5, ["", "title", ""]]], - template: function MyApp_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "simple"); - $r3$.ɵɵelement(1, "h1", 0); - $r3$.ɵɵelementEnd(); - } - }, - encapsulation: 2 - })`; - - const result = compile(files, angularFiles); - - expectEmit( - result.source, SimpleComponentDefinition, 'Incorrect SimpleComponent definition'); - }); - - it('should include parsed ngProjectAs selectors into template attrs', () => { - const files = { - app: { - 'spec.ts': ` - import {Component} from '@angular/core'; - - @Component({ - selector: 'my-app', - template: '
' - }) - export class MyApp { - show = true; - } - ` - } - }; - - const SimpleComponentDefinition = ` - MyApp.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ - type: MyApp, - selectors: [ - ["my-app"] - ], - decls: 1, - vars: 1, - consts: [ - ["ngProjectAs", ".someclass", ${AttributeMarker.ProjectAs}, ["", 8, "someclass"], ${ - AttributeMarker.Template}, "ngIf"], - ["ngProjectAs", ".someclass", ${AttributeMarker.ProjectAs}, ["", 8, "someclass"]] - ], - template: function MyApp_Template(rf, ctx) { - if (rf & 1) { - i0.ɵɵtemplate(0, MyApp_div_0_Template, 1, 0, "div", 0); - } - if (rf & 2) { - i0.ɵɵproperty("ngIf", ctx.show); - } - }, - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, SimpleComponentDefinition, 'Incorrect MyApp definition'); - }); - - it('should capture the node name of ng-content with a structural directive', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, Directive, NgModule, TemplateRef} from '@angular/core'; - - @Component({selector: 'simple', template: ''}) - export class SimpleComponent {} - ` - } - }; - - const SimpleComponentDefinition = ` - SimpleComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: SimpleComponent, - selectors: [["simple"]], - ngContentSelectors: $c0$, - decls: 1, - vars: 1, - consts: [[4, "ngIf"]], - template: function SimpleComponent_Template(rf, ctx) { - if (rf & 1) { - i0.ɵɵprojectionDef(); - i0.ɵɵtemplate(0, SimpleComponent_ng_content_0_Template, 1, 0, "ng-content", 0); - } - if (rf & 2) { - i0.ɵɵproperty("ngIf", ctx.showContent); - } - }, - encapsulation: 2 - });`; - - const result = compile(files, angularFiles); - expectEmit( - result.source, SimpleComponentDefinition, 'Incorrect SimpleComponent definition'); - }); - }); - - describe('queries', () => { - const directive = { - 'some.directive.ts': ` - import {Directive} from '@angular/core'; - - @Directive({ - selector: '[someDir]', - }) - export class SomeDirective { } - ` - }; - - it('should support view queries with directives', () => { - const files = { - app: { - ...directive, - 'view_query.component.ts': ` - import {Component, NgModule, ViewChild, ViewChildren} from '@angular/core'; - import {SomeDirective} from './some.directive'; - - @Component({ - selector: 'view-query-component', - template: \` -
- \` - }) - export class ViewQueryComponent { - @ViewChild(SomeDirective) someDir: SomeDirective; - @ViewChildren(SomeDirective) someDirs: QueryList; - } - - @NgModule({declarations: [SomeDirective, ViewQueryComponent]}) - export class MyModule {} - ` - } - }; - - const ViewQueryComponentDefinition = ` - … - ViewQueryComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: ViewQueryComponent, - selectors: [["view-query-component"]], - viewQuery: function ViewQueryComponent_Query(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵviewQuery(SomeDirective, 5); - $r3$.ɵɵviewQuery(SomeDirective, 5); - } - if (rf & 2) { - let $tmp$; - $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDir = $tmp$.first); - $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDirs = $tmp$); - } - }, - decls: 1, - vars: 0, - consts: [["someDir",""]], - template: function ViewQueryComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div", 0); - } - }, - directives: function () { return [SomeDirective]; }, - encapsulation: 2 - });`; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, ViewQueryComponentDefinition, 'Invalid ViewQuery declaration'); - }); - - it('should support view queries with local refs', () => { - const files = { - app: { - 'view_query.component.ts': ` - import {Component, NgModule, ViewChild, ViewChildren, QueryList} from '@angular/core'; - - @Component({ - selector: 'view-query-component', - template: \` -
-
- \` - }) - export class ViewQueryComponent { - @ViewChild('myRef') myRef: any; - @ViewChildren('myRef1, myRef2, myRef3') myRefs: QueryList; - } - - @NgModule({declarations: [ViewQueryComponent]}) - export class MyModule {} - ` - } - }; - - const ViewQueryComponentDefinition = ` - const $e0_attrs$ = ["myRef"]; - const $e1_attrs$ = ["myRef1", "myRef2", "myRef3"]; - … - ViewQueryComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - … - viewQuery: function ViewQueryComponent_Query(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵviewQuery($e0_attrs$, 5); - $r3$.ɵɵviewQuery($e1_attrs$, 5); - } - if (rf & 2) { - let $tmp$; - $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.myRef = $tmp$.first); - $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.myRefs = $tmp$); - } - }, - … - });`; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, ViewQueryComponentDefinition, 'Invalid ViewQuery declaration'); - }); - - it('should support static view queries', () => { - const files = { - app: { - ...directive, - 'view_query.component.ts': ` - import {Component, NgModule, ViewChild} from '@angular/core'; - import {SomeDirective} from './some.directive'; - - @Component({ - selector: 'view-query-component', - template: \` -
- \` - }) - export class ViewQueryComponent { - @ViewChild(SomeDirective, {static: true}) someDir !: SomeDirective; - @ViewChild('foo') foo !: ElementRef; - } - - @NgModule({declarations: [SomeDirective, ViewQueryComponent]}) - export class MyModule {} - ` - } - }; - - const ViewQueryComponentDefinition = ` - const $refs$ = ["foo"]; - … - ViewQueryComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: ViewQueryComponent, - selectors: [["view-query-component"]], - viewQuery: function ViewQueryComponent_Query(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵviewQuery(SomeDirective, 7); - $r3$.ɵɵviewQuery($refs$, 5); - } - if (rf & 2) { - let $tmp$; - $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDir = $tmp$.first); - $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.foo = $tmp$.first); - } - }, - decls: 1, - vars: 0, - consts: [["someDir",""]], - template: function ViewQueryComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div", 0); - } - }, - directives: function () { return [SomeDirective]; }, - encapsulation: 2 - });`; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, ViewQueryComponentDefinition, 'Invalid ViewQuery declaration'); - }); - - it('should support view queries with read tokens specified', () => { - const files = { - app: { - ...directive, - 'view_query.component.ts': ` - import {Component, NgModule, ViewChild, ViewChildren, QueryList, ElementRef, TemplateRef} from '@angular/core'; - import {SomeDirective} from './some.directive'; - - @Component({ - selector: 'view-query-component', - template: \` -
-
-
- \` - }) - export class ViewQueryComponent { - @ViewChild('myRef', {read: TemplateRef}) myRef: TemplateRef; - @ViewChildren('myRef1, myRef2, myRef3', {read: ElementRef}) myRefs: QueryList; - @ViewChild(SomeDirective, {read: ElementRef}) someDir: ElementRef; - @ViewChildren(SomeDirective, {read: TemplateRef}) someDirs: QueryList; - } - - @NgModule({declarations: [ViewQueryComponent]}) - export class MyModule {} - ` - } - }; - - const ViewQueryComponentDefinition = ` - const $e0_attrs$ = ["myRef"]; - const $e1_attrs$ = ["myRef1", "myRef2", "myRef3"]; - … - ViewQueryComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - … - viewQuery: function ViewQueryComponent_Query(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵviewQuery($e0_attrs$, 5, TemplateRef); - $r3$.ɵɵviewQuery(SomeDirective, 5, ElementRef); - $r3$.ɵɵviewQuery($e1_attrs$, 5, ElementRef); - $r3$.ɵɵviewQuery(SomeDirective, 5, TemplateRef); - } - if (rf & 2) { - let $tmp$; - $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.myRef = $tmp$.first); - $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDir = $tmp$.first); - $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.myRefs = $tmp$); - $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDirs = $tmp$); - } - }, - … - });`; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, ViewQueryComponentDefinition, 'Invalid ViewQuery declaration'); - }); - - it('should support content queries with directives', () => { - const files = { - app: { - ...directive, - 'content_query.ts': ` - import {Component, ContentChild, ContentChildren, NgModule, QueryList} from '@angular/core'; - import {SomeDirective} from './some.directive'; - - @Component({ - selector: 'content-query-component', - template: \` -
- \` - }) - export class ContentQueryComponent { - @ContentChild(SomeDirective) someDir: SomeDirective; - @ContentChildren(SomeDirective) someDirList !: QueryList; - } - - @Component({ - selector: 'my-app', - template: \` - -
-
- \` - }) - export class MyApp { } - - @NgModule({declarations: [SomeDirective, ContentQueryComponent, MyApp]}) - export class MyModule { } - ` - } - }; - - const ContentQueryComponentDefinition = ` - ContentQueryComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: ContentQueryComponent, - selectors: [["content-query-component"]], - contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) { - if (rf & 1) { - $r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 5); - $r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 4); - } - if (rf & 2) { - let $tmp$; - $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDir = $tmp$.first); - $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDirList = $tmp$); - } - }, - ngContentSelectors: _c0, - decls: 2, - vars: 0, - template: function ContentQueryComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵprojectionDef(); - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵprojection(1); - $r3$.ɵɵelementEnd(); - } - }, - encapsulation: 2 - });`; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, ContentQueryComponentDefinition, 'Invalid ContentQuery declaration'); - }); - - it('should support content queries with local refs', () => { - const files = { - app: { - 'content_query.component.ts': ` - import {Component, ContentChild, ContentChildren, NgModule, QueryList} from '@angular/core'; - - @Component({ - selector: 'content-query-component', - template: \` -
-
- \` - }) - export class ContentQueryComponent { - @ContentChild('myRef') myRef: any; - @ContentChildren('myRef1, myRef2, myRef3') myRefs: QueryList; - } - @NgModule({declarations: [ContentQueryComponent]}) - export class MyModule {} - ` - } - }; - - const ContentQueryComponentDefinition = ` - const $e0_attrs$ = ["myRef"]; - const $e1_attrs$ = ["myRef1", "myRef2", "myRef3"]; - … - ContentQueryComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - … - contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) { - if (rf & 1) { - $r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, 5); - $r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, 4); - } - if (rf & 2) { - let $tmp$; - $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.myRef = $tmp$.first); - $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.myRefs = $tmp$); - } - }, - … - });`; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, ContentQueryComponentDefinition, 'Invalid ContentQuery declaration'); - }); - - it('should support static content queries', () => { - const files = { - app: { - ...directive, - 'content_query.ts': ` - import {Component, ContentChild, NgModule} from '@angular/core'; - import {SomeDirective} from './some.directive'; - - @Component({ - selector: 'content-query-component', - template: \` -
- \` - }) - export class ContentQueryComponent { - @ContentChild(SomeDirective, {static: true}) someDir !: SomeDirective; - @ContentChild('foo') foo !: ElementRef; - } - - @Component({ - selector: 'my-app', - template: \` - -
-
- \` - }) - export class MyApp { } - - @NgModule({declarations: [SomeDirective, ContentQueryComponent, MyApp]}) - export class MyModule { } - ` - } - }; - - const ContentQueryComponentDefinition = ` - ContentQueryComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: ContentQueryComponent, - selectors: [["content-query-component"]], - contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) { - if (rf & 1) { - $r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 7); - $r3$.ɵɵcontentQuery(dirIndex, $ref0$, 5); - } - if (rf & 2) { - let $tmp$; - $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDir = $tmp$.first); - $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.foo = $tmp$.first); - } - }, - ngContentSelectors: $_c1$, - decls: 2, - vars: 0, - template: function ContentQueryComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵprojectionDef(); - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵprojection(1); - $r3$.ɵɵelementEnd(); - } - }, - encapsulation: 2 - });`; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, ContentQueryComponentDefinition, 'Invalid ContentQuery declaration'); - }); - - it('should support content queries with read tokens specified', () => { - const files = { - app: { - ...directive, - 'content_query.component.ts': ` - import {Component, ContentChild, ContentChildren, NgModule, QueryList, ElementRef, TemplateRef} from '@angular/core'; - import {SomeDirective} from './some.directive'; - - @Component({ - selector: 'content-query-component', - template: \` -
-
-
- \` - }) - export class ContentQueryComponent { - @ContentChild('myRef', {read: TemplateRef}) myRef: TemplateRef; - @ContentChildren('myRef1, myRef2, myRef3', {read: ElementRef}) myRefs: QueryList; - @ContentChild(SomeDirective, {read: ElementRef}) someDir: ElementRef; - @ContentChildren(SomeDirective, {read: TemplateRef}) someDirs: QueryList; - } - @NgModule({declarations: [ContentQueryComponent]}) - export class MyModule {} - ` - } - }; - - const ContentQueryComponentDefinition = ` - const $e0_attrs$ = ["myRef"]; - const $e1_attrs$ = ["myRef1", "myRef2", "myRef3"]; - … - ContentQueryComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - … - contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) { - if (rf & 1) { - $r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, 5, TemplateRef); - $r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 5, ElementRef); - $r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, 4, ElementRef); - $r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 4, TemplateRef); - } - if (rf & 2) { - let $tmp$; - $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.myRef = $tmp$.first); - $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDir = $tmp$.first); - $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.myRefs = $tmp$); - $r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDirs = $tmp$); - } - }, - … - });`; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, ContentQueryComponentDefinition, 'Invalid ContentQuery declaration'); - }); - }); - - describe('pipes', () => { - it('should render pipes', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule, Pipe, PipeTransform, OnDestroy} from '@angular/core'; - - @Pipe({ - name: 'myPipe', - pure: false - }) - export class MyPipe implements PipeTransform, - OnDestroy { - transform(value: any, ...args: any[]) { return value; } - ngOnDestroy(): void { } - } - - @Pipe({ - name: 'myPurePipe', - pure: true, - }) - export class MyPurePipe implements PipeTransform { - transform(value: any, ...args: any[]) { return value; } - } - - @Component({ - selector: 'my-app', - template: '{{name | myPipe:size | myPurePipe:size }}

{{ name | myPipe:1:2:3:4:5 }} {{ name ? 1 : 2 | myPipe }}

' - }) - export class MyApp { - name = 'World'; - size = 0; - } - - @NgModule({declarations:[MyPipe, MyPurePipe, MyApp]}) - export class MyModule {} - ` - } - }; - - const MyPipeDefinition = ` - MyPipe.ɵpipe = /*@__PURE__*/ $r3$.ɵɵdefinePipe({ - name: "myPipe", - type: MyPipe, - pure: false - }); - `; - - const MyPipeFactoryDef = ` - MyPipe.ɵfac = function MyPipe_Factory(t) { return new (t || MyPipe)(); }; - `; - - const MyPurePipeDefinition = ` - MyPurePipe.ɵpipe = /*@__PURE__*/ $r3$.ɵɵdefinePipe({ - name: "myPurePipe", - type: MyPurePipe, - pure: true - });`; - - const MyPurePipeFactoryDef = ` - MyPurePipe.ɵfac = function MyPurePipe_Factory(t) { return new (t || MyPurePipe)(); }; - `; - - const MyAppDefinition = ` - const $c0$ = function ($a0$) { - return [$a0$, 1, 2, 3, 4, 5]; - }; - // ... - MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyApp, - selectors: [["my-app"]], - decls: 7, - vars: 20, - template: function MyApp_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵtext(0); - $r3$.ɵɵpipe(1, "myPurePipe"); - $r3$.ɵɵpipe(2, "myPipe"); - $r3$.ɵɵelementStart(3, "p"); - $r3$.ɵɵtext(4); - $r3$.ɵɵpipe(5, "myPipe"); - $r3$.ɵɵpipe(6, "myPipe"); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵtextInterpolate($r3$.ɵɵpipeBind2(1, 3, $r3$.ɵɵpipeBind2(2, 6, ctx.name, ctx.size), ctx.size)); - $r3$.ɵɵadvance(4); - $r3$.ɵɵtextInterpolate2("", $r3$.ɵɵpipeBindV(5, 9, $r3$.ɵɵpureFunction1(18, $c0$, ctx.name)), " ", ctx.name ? 1 : $r3$.ɵɵpipeBind1(6, 16, 2), ""); - } - }, - pipes: [MyPurePipe, MyPipe], - encapsulation: 2 - });`; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, MyPipeDefinition, 'Invalid pipe definition'); - expectEmit(source, MyPipeFactoryDef, 'Invalid pipe factory function'); - expectEmit(source, MyPurePipeDefinition, 'Invalid pure pipe definition'); - expectEmit(source, MyPurePipeFactoryDef, 'Invalid pure pipe factory function'); - expectEmit(source, MyAppDefinition, 'Invalid MyApp definition'); - }); - - it('should use appropriate function for a given no of pipe arguments', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule, Pipe, PipeTransform, OnDestroy} from '@angular/core'; - - @Pipe({ - name: 'myPipe', - pure: false - }) - export class MyPipe implements PipeTransform, - OnDestroy { - transform(value: any, ...args: any[]) { return value; } - ngOnDestroy(): void { } - } - - @Component({ - selector: 'my-app', - template: '0:{{name | myPipe}}1:{{name | myPipe:1}}2:{{name | myPipe:1:2}}3:{{name | myPipe:1:2:3}}4:{{name | myPipe:1:2:3:4}}' - }) - export class MyApp { - } - - @NgModule({declarations:[MyPipe, MyApp]}) - export class MyModule {} - ` - } - }; - - const MyAppDefinition = ` - // ... - MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyApp, - selectors: [["my-app"]], - decls: 6, - vars: 27, - template: function MyApp_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵtext(0); - $r3$.ɵɵpipe(1, "myPipe"); - $r3$.ɵɵpipe(2, "myPipe"); - $r3$.ɵɵpipe(3, "myPipe"); - $r3$.ɵɵpipe(4, "myPipe"); - $r3$.ɵɵpipe(5, "myPipe"); - } - if (rf & 2) { - $r3$.ɵɵtextInterpolate5( - "0:", i0.ɵɵpipeBind1(1, 5, ctx.name), - "1:", i0.ɵɵpipeBind2(2, 7, ctx.name, 1), - "2:", i0.ɵɵpipeBind3(3, 10, ctx.name, 1, 2), - "3:", i0.ɵɵpipeBind4(4, 14, ctx.name, 1, 2, 3), - "4:", i0.ɵɵpipeBindV(5, 19, $r3$.ɵɵpureFunction1(25, $c0$, ctx.name)), - "" - ); - } - }, - pipes: [MyPipe], - encapsulation: 2 - });`; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, MyAppDefinition, 'Invalid MyApp definition'); - }); - - it('should generate the proper instruction when injecting ChangeDetectorRef into a pipe', - () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule, Pipe, PipeTransform, ChangeDetectorRef, Optional} from '@angular/core'; - - @Pipe({name: 'myPipe'}) - export class MyPipe implements PipeTransform { - constructor(changeDetectorRef: ChangeDetectorRef) {} - - transform(value: any, ...args: any[]) { return value; } - } - - @Pipe({name: 'myOtherPipe'}) - export class MyOtherPipe implements PipeTransform { - constructor(@Optional() changeDetectorRef: ChangeDetectorRef) {} - - transform(value: any, ...args: any[]) { return value; } - } - - @Component({ - selector: 'my-app', - template: '{{name | myPipe }}

{{ name | myOtherPipe }}

' - }) - export class MyApp { - name = 'World'; - } - - @NgModule({declarations:[MyPipe, MyOtherPipe, MyApp]}) - export class MyModule {} - ` - } - }; - - const MyPipeDefinition = ` - MyPipe.ɵpipe = /*@__PURE__*/ $r3$.ɵɵdefinePipe({ - name: "myPipe", - type: MyPipe, - pure: true - }); - `; - - const MyPipeFactory = ` - MyPipe.ɵfac = function MyPipe_Factory(t) { return new (t || MyPipe)($i0$.ɵɵdirectiveInject($i0$.ChangeDetectorRef, 16)); }; - `; - - const MyOtherPipeDefinition = ` - MyOtherPipe.ɵpipe = /*@__PURE__*/ $r3$.ɵɵdefinePipe({ - name: "myOtherPipe", - type: MyOtherPipe, - pure: true - });`; - - const MyOtherPipeFactory = ` - MyOtherPipe.ɵfac = function MyOtherPipe_Factory(t) { return new (t || MyOtherPipe)($i0$.ɵɵdirectiveInject($i0$.ChangeDetectorRef, 24)); }; - `; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, MyPipeDefinition, 'Invalid pipe definition'); - expectEmit(source, MyPipeFactory, 'Invalid pipe factory function'); - expectEmit(source, MyOtherPipeDefinition, 'Invalid alternate pipe definition'); - expectEmit(source, MyOtherPipeFactory, 'Invalid alternate pipe factory function'); - }); - }); - - it('local reference', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({selector: 'my-component', template: 'Hello {{user.value}}!'}) - export class MyComponent {} - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const MyComponentDefinition = ` - … - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyComponent, - selectors: [["my-component"]], - decls: 3, - vars: 1, - consts: [["user", ""]], - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "input", null, 0); - $r3$.ɵɵtext(2); - } - if (rf & 2) { - const $user$ = $r3$.ɵɵreference(1); - $r3$.ɵɵadvance(2); - $r3$.ɵɵtextInterpolate1("Hello ", $user$.value, "!"); - } - }, - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ɵcmp'); - }); - - it('local references in nested views', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, Directive, NgModule, TemplateRef} from '@angular/core'; - - @Directive({selector: '[if]'}) - export class IfDirective { - constructor(template: TemplateRef) { } - } - - @Component({ - selector: 'my-component', - template: \` -
- {{foo}} -
- {{foo}}-{{bar}} - {{foo}}-{{bar}}-{{baz}} - -
-
- \` - }) - export class MyComponent {} - - @NgModule({declarations: [IfDirective, MyComponent]}) - export class MyModule {} - ` - } - }; - - const MyComponentDefinition = ` - function MyComponent_div_3_span_2_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "span"); - $r3$.ɵɵtext(1); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵnextContext(); - const $bar$ = $r3$.ɵɵreference(4); - $r3$.ɵɵnextContext(); - const $foo$ = $r3$.ɵɵreference(1); - const $baz$ = $r3$.ɵɵreference(5); - $r3$.ɵɵadvance(1); - $r3$.ɵɵtextInterpolate3("", $foo$, "-", $bar$, "-", $baz$, ""); - } - } - function MyComponent_div_3_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵtext(1); - $r3$.ɵɵtemplate(2, MyComponent_div_3_span_2_Template, 2, 3, "span", 1); - $r3$.ɵɵelement(3, "span", null, 3); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - const $bar$ = $r3$.ɵɵreference(4); - $r3$.ɵɵnextContext(); - const $foo$ = $r3$.ɵɵreference(1); - $r3$.ɵɵadvance(1); - $r3$.ɵɵtextInterpolate2(" ", $foo$, "-", $bar$, " "); - } - } - … - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyComponent, - selectors: [["my-component"]], - decls: 6, - vars: 1, - consts: [["foo", ""], [${AttributeMarker.Template}, "if"], ["baz", ""], ["bar", ""]], - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div", null, 0); - $r3$.ɵɵtext(2); - $r3$.ɵɵtemplate(3, MyComponent_div_3_Template, 5, 2, "div", 1); - $r3$.ɵɵelement(4, "div", null, 2); - } - if (rf & 2) { - const $foo$ = $r3$.ɵɵreference(1); - $r3$.ɵɵadvance(2); - $r3$.ɵɵtextInterpolate1(" ", $foo$, " "); - } - }, - directives:[IfDirective], - encapsulation: 2 - });`; - - const result = compile(files, angularFiles); - const source = result.source; - expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ɵcmp'); - }); - - it('should support local refs mixed with context assignments', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \` -
-
- - {{ foo }} - {{ item }} - -
\` - }) - export class MyComponent {} - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - function MyComponent_div_0_span_3_Template(rf, ctx) { - if (rf & 1) { - $i0$.ɵɵelementStart(0, "span"); - $i0$.ɵɵtext(1); - $i0$.ɵɵelementEnd(); - } - if (rf & 2) { - const $item$ = $i0$.ɵɵnextContext().$implicit; - const $foo$ = $i0$.ɵɵreference(2); - $r3$.ɵɵadvance(1); - $i0$.ɵɵtextInterpolate2(" ", $foo$, " - ", $item$, " "); - } - } - - function MyComponent_div_0_Template(rf, ctx) { - if (rf & 1) { - $i0$.ɵɵelementStart(0, "div"); - $i0$.ɵɵelement(1, "div", null, 1); - $i0$.ɵɵtemplate(3, MyComponent_div_0_span_3_Template, 2, 2, "span", 2); - $i0$.ɵɵelementEnd(); - } - if (rf & 2) { - const $app$ = $i0$.ɵɵnextContext(); - $r3$.ɵɵadvance(3); - $i0$.ɵɵproperty("ngIf", $app$.showing); - } - } - - // ... - consts: [[${AttributeMarker.Template}, "ngFor", "ngForOf"], ["foo", ""], [${ - AttributeMarker.Template}, "ngIf"]], - template:function MyComponent_Template(rf, ctx){ - if (rf & 1) { - $i0$.ɵɵtemplate(0, MyComponent_div_0_Template, 4, 1, "div", 0); - } - if (rf & 2) { - $i0$.ɵɵproperty("ngForOf", ctx.items); - } - }`; - - const result = compile(files, angularFiles); - - expectEmit(result.source, template, 'Incorrect template'); - }); - - describe('lifecycle hooks', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, Input, NgModule} from '@angular/core'; - - let events: string[] = []; - - @Component({selector: 'lifecycle-comp', template: ''}) - export class LifecycleComp { - @Input('name') nameMin: string; - - ngOnChanges() { events.push('changes' + this.nameMin); } - - ngOnInit() { events.push('init' + this.nameMin); } - ngDoCheck() { events.push('check' + this.nameMin); } - - ngAfterContentInit() { events.push('content init' + this.nameMin); } - ngAfterContentChecked() { events.push('content check' + this.nameMin); } - - ngAfterViewInit() { events.push('view init' + this.nameMin); } - ngAfterViewChecked() { events.push('view check' + this.nameMin); } - - ngOnDestroy() { events.push(this.nameMin); } - } - - @Component({ - selector: 'simple-layout', - template: \` - - - \` - }) - export class SimpleLayout { - name1 = '1'; - name2 = '2'; - } - - @NgModule({declarations: [LifecycleComp, SimpleLayout]}) - export class LifecycleModule {} - ` - } - }; - - it('should gen hooks with a few simple components', () => { - const LifecycleCompDefinition = ` - LifecycleComp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: LifecycleComp, - selectors: [["lifecycle-comp"]], - inputs: {nameMin: ["name", "nameMin"]}, - features: [$r3$.ɵɵNgOnChangesFeature], - decls: 0, - vars: 0, - template: function LifecycleComp_Template(rf, ctx) {}, - encapsulation: 2 - });`; - - const SimpleLayoutDefinition = ` - SimpleLayout.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: SimpleLayout, - selectors: [["simple-layout"]], - decls: 2, - vars: 2, - consts: [[3, "name"]], - template: function SimpleLayout_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "lifecycle-comp", 0); - $r3$.ɵɵelement(1, "lifecycle-comp", 0); - } - if (rf & 2) { - $r3$.ɵɵproperty("name", ctx.name1); - $r3$.ɵɵadvance(1); - $r3$.ɵɵproperty("name", ctx.name2); - } - }, - directives: [LifecycleComp], - encapsulation: 2 - });`; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, LifecycleCompDefinition, 'Invalid LifecycleComp definition'); - expectEmit(source, SimpleLayoutDefinition, 'Invalid SimpleLayout definition'); - }); - }); - - describe('template variables', () => { - const shared = { - shared: { - 'for_of.ts': ` - import {Directive, Input, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core'; - - export interface ForOfContext { - $implicit: any; - index: number; - even: boolean; - odd: boolean; - } - - @Directive({selector: '[forOf]'}) - export class ForOfDirective { - private previous: any[]; - - constructor(private view: ViewContainerRef, private template: TemplateRef) {} - - @Input() forOf: any[]; - - ngOnChanges(simpleChanges: SimpleChanges) { - if ('forOf' in simpleChanges) { - this.update(); - } - } - - ngDoCheck(): void { - const previous = this.previous; - const current = this.forOf; - if (!previous || previous.length != current.length || - previous.some((value: any, index: number) => current[index] !== previous[index])) { - this.update(); - } - } - - private update() { - // TODO(chuckj): Not implemented yet - // this.view.clear(); - if (this.forOf) { - const current = this.forOf; - for (let i = 0; i < current.length; i++) { - const context = {$implicit: current[i], index: i, even: i % 2 == 0, odd: i % 2 == 1}; - // TODO(chuckj): Not implemented yet - // this.view.createEmbeddedView(this.template, context); - } - this.previous = [...this.forOf]; - } - } - } - ` - } - }; - - it('should support embedded views in the SVG namespace', () => { - const files = { - app: { - ...shared, - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - import {ForOfDirective} from './shared/for_of'; - - @Component({ - selector: 'my-component', - template: \`\` - }) - export class MyComponent { - items = [{ data: 42 }, { data: 42 }]; - } - - @NgModule({ - declarations: [MyComponent, ForOfDirective] - }) - export class MyModule {} - ` - } - }; - - // TODO(benlesh): Enforce this when the directives are specified - const ForDirectiveDefinition = ` - ForOfDirective.ɵdir = /*@__PURE__*/ $r3$.ɵɵdefineDirective({ - type: ForOfDirective, - selectors: [["", "forOf", ""]], - features: [$r3$.ɵɵNgOnChangesFeature], - inputs: {forOf: "forOf"} - }); - `; - - const ForDirectiveFactory = `ForOfDirective.ɵfac = function ForOfDirective_Factory(t) { - return new (t || ForOfDirective)($r3$.ɵɵdirectiveInject(ViewContainerRef), $r3$.ɵɵdirectiveInject(TemplateRef)); - };`; - - const MyComponentDefinition = ` - function MyComponent__svg_g_1_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵnamespaceSVG(); - $r3$.ɵɵelementStart(0,"g"); - $r3$.ɵɵelement(1,"circle"); - $r3$.ɵɵelementEnd(); - } - } - … - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyComponent, - selectors: [["my-component"]], - decls: 2, - vars: 1, - consts: [[${AttributeMarker.Template}, "for", "forOf"]], - template: function MyComponent_Template(rf, ctx){ - if (rf & 1) { - $r3$.ɵɵnamespaceSVG(); - $r3$.ɵɵelementStart(0,"svg"); - $r3$.ɵɵtemplate(1, MyComponent__svg_g_1_Template, 2, 0, "g", 0); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(1); - $r3$.ɵɵproperty("forOf", ctx.items); - } - }, - directives: function() { return [ForOfDirective]; }, - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - const source = result.source; - - // TODO(benlesh): Enforce this when the directives are specified - // expectEmit(source, ForDirectiveDefinition, 'Invalid directive definition'); - // expectEmit(source, ForDirectiveFactory, 'Invalid directive factory'); - expectEmit(source, MyComponentDefinition, 'Invalid component definition'); - }); - - it('should support a let variable and reference', () => { - const files = { - app: { - ...shared, - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - import {ForOfDirective} from './shared/for_of'; - - @Component({ - selector: 'my-component', - template: \`
  • {{item.name}}
\` - }) - export class MyComponent { - items = [{name: 'one'}, {name: 'two'}]; - } - - @NgModule({ - declarations: [MyComponent, ForOfDirective] - }) - export class MyModule {} - ` - } - }; - - // TODO(chuckj): Enforce this when the directives are specified - const ForDirectiveDefinition = ` - ForOfDirective.ɵdir = /*@__PURE__*/ $r3$.ɵɵdefineDirective({ - type: ForOfDirective, - selectors: [["", "forOf", ""]], - features: [$r3$.ɵɵNgOnChangesFeature], - inputs: {forOf: "forOf"} - }); - `; - - const ForDirectiveFactory = ` - ForOfDirective.ɵfac = function ForOfDirective_Factory(t) { - return new (t || ForOfDirective)($r3$.ɵɵdirectiveInject(ViewContainerRef), $r3$.ɵɵdirectiveInject(TemplateRef)); - }; - `; - - const MyComponentDefinition = ` - function MyComponent_li_1_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "li"); - $r3$.ɵɵtext(1); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - const $item$ = ctx.$implicit; - $r3$.ɵɵadvance(1); - $r3$.ɵɵtextInterpolate($item$.name); - } - } - … - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyComponent, - selectors: [["my-component"]], - decls: 2, - vars: 1, - consts: [[${AttributeMarker.Template}, "for", "forOf"]], - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "ul"); - $r3$.ɵɵtemplate(1, MyComponent_li_1_Template, 2, 1, "li", 0); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(1); - $r3$.ɵɵproperty("forOf", ctx.items); - } - }, - directives: function() { return [ForOfDirective]; }, - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - const source = result.source; - - // TODO(chuckj): Enforce this when the directives are specified - // expectEmit(source, ForDirectiveDefinition, 'Invalid directive definition'); - // expectEmit(source, ForDirectiveFactory, 'Invalid directive factory'); - expectEmit(source, MyComponentDefinition, 'Invalid component definition'); - }); - - it('should support accessing parent template variables', () => { - const files = { - app: { - ...shared, - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - import {ForOfDirective} from './shared/for_of'; - - @Component({ - selector: 'my-component', - template: \` -
    -
  • -
    {{item.name}}
    -
      -
    • - {{item.name}}: {{info.description}} -
    • -
    -
  • -
\` - }) - export class MyComponent { - items = [ - {name: 'one', infos: [{description: '11'}, {description: '12'}]}, - {name: 'two', infos: [{description: '21'}, {description: '22'}]} - ]; - } - - @NgModule({ - declarations: [MyComponent, ForOfDirective] - }) - export class MyModule {} - ` - } - }; - - const MyComponentDefinition = ` - function MyComponent_li_1_li_4_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "li"); - $r3$.ɵɵtext(1); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - const $info$ = ctx.$implicit; - const $item$ = $r3$.ɵɵnextContext().$implicit; - $r3$.ɵɵadvance(1); - $r3$.ɵɵtextInterpolate2(" ", $item$.name, ": ", $info$.description, " "); - } - } - - function MyComponent_li_1_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "li"); - $r3$.ɵɵelementStart(1, "div"); - $r3$.ɵɵtext(2); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementStart(3, "ul"); - $r3$.ɵɵtemplate(4, MyComponent_li_1_li_4_Template, 2, 2, "li", 0); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - const $item$ = ctx.$implicit; - $r3$.ɵɵadvance(2); - $r3$.ɵɵtextInterpolate(IDENT.name); - $r3$.ɵɵadvance(2); - $r3$.ɵɵproperty("forOf", IDENT.infos); - } - } - - … - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyComponent, - selectors: [["my-component"]], - decls: 2, - vars: 1, - consts: [[${AttributeMarker.Template}, "for", "forOf"]], - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "ul"); - $r3$.ɵɵtemplate(1, MyComponent_li_1_Template, 5, 2, "li", 0); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(1); - $r3$.ɵɵproperty("forOf", ctx.items); - } - }, - directives: function () { return [ForOfDirective]; }, - encapsulation: 2 - });`; - - const result = compile(files, angularFiles); - const source = result.source; - expectEmit(source, MyComponentDefinition, 'Invalid component definition'); - }); - }); - - it('should instantiate directives in a closure when they are forward referenced', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule, Directive} from '@angular/core'; - - @Component({ - selector: 'host-binding-comp', - template: \` - - \` - }) - export class HostBindingComp { - } - - @Directive({ - selector: 'my-forward-directive' - }) - class MyForwardDirective {} - - @NgModule({declarations: [HostBindingComp, MyForwardDirective]}) - export class MyModule {} - ` - } - }; - - const MyAppDefinition = ` - … - directives: function () { return [MyForwardDirective]; } - … - `; - - const result = compile(files, angularFiles); - const source = result.source; - expectEmit(source, MyAppDefinition, 'Invalid component definition'); - }); - - it('should instantiate pipes in a closure when they are forward referenced', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule, Pipe} from '@angular/core'; - - @Component({ - selector: 'host-binding-comp', - template: \` -
...
- \` - }) - export class HostBindingComp { - } - - @Pipe({ - name: 'my_forward_pipe' - }) - class MyForwardPipe {} - - @NgModule({declarations: [HostBindingComp, MyForwardPipe]}) - export class MyModule {} - ` - } - }; - - const MyAppDefinition = ` - … - pipes: function () { return [MyForwardPipe]; } - … - `; - - const result = compile(files, angularFiles); - const source = result.source; - expectEmit(source, MyAppDefinition, 'Invalid component definition'); - }); - - it('should split multiple `exportAs` values into an array', () => { - const files = { - app: { - 'spec.ts': ` - import {Directive, NgModule} from '@angular/core'; - - @Directive({selector: '[some-directive]', exportAs: 'someDir, otherDir'}) - export class SomeDirective {} - - @NgModule({declarations: [SomeDirective]}) - export class MyModule{} - ` - } - }; - - // SomeDirective definition should be: - const SomeDirectiveDefinition = ` - SomeDirective.ɵdir = /*@__PURE__*/ $r3$.ɵɵdefineDirective({ - type: SomeDirective, - selectors: [["", "some-directive", ""]], - exportAs: ["someDir", "otherDir"] - }); - `; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeDirective.ɵdir'); - }); - - it('should not throw for empty property bindings on ng-template', () => { - const files = { - app: { - 'example.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-app', - template: '' - }) - export class MyComponent { - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {}` - } - }; - - expect(() => compile(files, angularFiles)).not.toThrow(); - }); - - it('should not generate a selectors array if the directive does not have a selector', () => { - const files = { - app: { - 'spec.ts': ` - import {Directive} from '@angular/core'; - - @Directive() - export class AbstractDirective { - } - ` - } - }; - const expectedOutput = ` - // ... - AbstractDirective.ɵdir = /*@__PURE__*/ $r3$.ɵɵdefineDirective({ - type: AbstractDirective - }); - // ... - `; - const result = compile(files, angularFiles); - expectEmit(result.source, expectedOutput, 'Invalid directive definition'); - }); - - it('should generate a pure function for constant object literals', () => { - const files = { - app: { - 'spec.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: '' - }) - export class MyApp { - } - ` - } - }; - - const MyAppDeclaration = ` - const $c0$ = function () { return {}; }; - const $c1$ = function () { return { a: 1, b: 2 }; }; - … - MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyApp, - selectors: [["ng-component"]], - decls: 1, - vars: 4, - consts: [[${AttributeMarker.Bindings}, "prop", "otherProp"]], - template: function MyApp_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "some-comp", 0); - } - if (rf & 2) { - $r3$.ɵɵproperty("prop", $r3$.ɵɵpureFunction0(2, $c0$))("otherProp", $r3$.ɵɵpureFunction0(3, $c1$)); - } - }, - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, MyAppDeclaration, 'Invalid component definition'); - }); - - it('should generate a pure function for constant array literals', () => { - const files = { - app: { - 'spec.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: '' - }) - export class MyApp { - } - ` - } - }; - - const MyAppDeclaration = ` - const $c0$ = function () { return []; }; - const $c1$ = function () { return [0, 1, 2]; }; - … - MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyApp, - selectors: [["ng-component"]], - decls: 1, - vars: 4, - consts: [[${AttributeMarker.Bindings}, "prop", "otherProp"]], - template: function MyApp_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "some-comp", 0); - } - if (rf & 2) { - $r3$.ɵɵproperty("prop", $r3$.ɵɵpureFunction0(2, $c0$))("otherProp", $r3$.ɵɵpureFunction0(3, $c1$)); - } - }, - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, MyAppDeclaration, 'Invalid component definition'); - }); - - it('should not share pure functions between null and object literals', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - template: \` -
-
- \` - }) - export class MyApp {} - - @NgModule({declarations: [MyApp]}) - export class MyModule {} - ` - } - }; - - const MyAppDeclaration = ` - const $c0$ = function () { return { foo: null }; }; - const $c1$ = function () { return {}; }; - const $c2$ = function (a0) { return { foo: a0 }; }; - … - MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyApp, - selectors: [["ng-component"]], - decls: 2, - vars: 6, - consts: [[${AttributeMarker.Bindings}, "dir"]], - template: function MyApp_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div", 0); - $r3$.ɵɵelement(1, "div", 0); - } - if (rf & 2) { - $r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction0(2, $c0$)); - $r3$.ɵɵadvance(1); - $r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction1(4, $c2$, $r3$.ɵɵpureFunction0(3, $c1$))); - } - }, - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, MyAppDeclaration, 'Invalid component definition'); - }); - - it('should not share pure functions between null and array literals', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - template: \` -
-
- \` - }) - export class MyApp {} - - @NgModule({declarations: [MyApp]}) - export class MyModule {} - ` - } - }; - - const MyAppDeclaration = ` - const $c0$ = function () { return { foo: null }; }; - const $c1$ = function () { return []; }; - const $c2$ = function (a0) { return { foo: a0 }; }; - … - MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyApp, - selectors: [["ng-component"]], - decls: 2, - vars: 6, - consts: [[${AttributeMarker.Bindings}, "dir"]], - template: function MyApp_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div", 0); - $r3$.ɵɵelement(1, "div", 0); - } - if (rf & 2) { - $r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction0(2, $c0$)); - $r3$.ɵɵadvance(1); - $r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction1(4, $c2$, $r3$.ɵɵpureFunction0(3, $c1$))); - } - }, - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, MyAppDeclaration, 'Invalid component definition'); - }); - - it('should not share pure functions between null and function calls', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - template: \` -
-
- \` - }) - export class MyApp { - getFoo() { - return 'foo!'; - } - } - - @NgModule({declarations: [MyApp]}) - export class MyModule {} - ` - } - }; - - const MyAppDeclaration = ` - const $c0$ = function () { return { foo: null }; }; - const $c1$ = function (a0) { return { foo: a0 }; }; - … - MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyApp, - selectors: [["ng-component"]], - decls: 2, - vars: 5, - consts: [[${AttributeMarker.Bindings}, "dir"]], - template: function MyApp_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div", 0); - $r3$.ɵɵelement(1, "div", 0); - } - if (rf & 2) { - $r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction0(2, $c0$)); - $r3$.ɵɵadvance(1); - $r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction1(3, $c1$, ctx.getFoo())); - } - }, - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, MyAppDeclaration, 'Invalid component definition'); - }); - - it('should emit a valid setClassMetadata call in ES5 if a class with a custom decorator is referencing itself inside its own metadata', - () => { - const files = { - app: { - 'spec.ts': ` - import {Component, InjectionToken} from "@angular/core"; - - const token = new InjectionToken('token'); - - export function Custom() { - return function(target: any) {}; - } - - @Custom() - @Component({ - template: '', - providers: [{ provide: token, useExisting: Comp }], - }) - export class Comp {} - ` - } - }; - - // The setClassMetadata call should look like this. - const setClassMetadata = ` - (function() { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(Comp, [{ - type: Component, - args: [{ - template: '', - providers: [{provide: token, useExisting: Comp}], - }] - }], null, null); })(); - `; - - const result = compile(files, angularFiles, {target: ts.ScriptTarget.ES5}); - expectEmit(result.source, setClassMetadata, 'Incorrect setClassMetadata call'); - }); - }); -}); diff --git a/packages/compiler-cli/test/compliance_old/r3_view_compiler_binding_spec.ts b/packages/compiler-cli/test/compliance_old/r3_view_compiler_binding_spec.ts deleted file mode 100644 index 206f4ff1b4..0000000000 --- a/packages/compiler-cli/test/compliance_old/r3_view_compiler_binding_spec.ts +++ /dev/null @@ -1,1503 +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 {MockDirectory, setup} from '@angular/compiler/test/aot/test_util'; -import {compile, expectEmit} from './mock_compile'; - -describe('compiler compliance: bindings', () => { - const angularFiles = setup({ - compileAngular: false, - compileFakeCore: true, - compileAnimations: 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: \` -
Hello {{ name }}
\` - }) - export class MyComponent { - name = 'World'; - } - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - template:function MyComponent_Template(rf, $ctx$){ - if (rf & 1) { - $i0$.ɵɵelementStart(0, "div"); - $i0$.ɵɵtext(1); - $i0$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(1); - $i0$.ɵɵtextInterpolate1("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: '' - }) - export class MyComponent { - title = 'Hello World'; - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {}` - } - }; - - const template = ` - … - consts: [[${AttributeMarker.Bindings}, "title"]], - template:function MyComponent_Template(rf, $ctx$){ - if (rf & 1) { - $i0$.ɵɵelement(0, "a", 0); - } - if (rf & 2) { - $i0$.ɵɵproperty("title", $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: \` - \` - }) - export class MyComponent { - name = 'World'; - } - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - … - consts: [[${AttributeMarker.Bindings}, "title"]], - template:function MyComponent_Template(rf, $ctx$){ - if (rf & 1) { - $i0$.ɵɵelement(0, "a", 0); - } - if (rf & 2) { - $i0$.ɵɵpropertyInterpolate1("title", "Hello ", $ctx$.name, ""); - } - }`; - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect interpolated property binding'); - }); - - it('should ignore empty bindings', () => { - const files: MockDirectory = { - app: { - 'example.ts': ` - import {Component} from '@angular/core'; - @Component({ - selector: 'test', - template: '
' - }) - class FooCmp {} - ` - } - }; - const result = compile(files, angularFiles); - expect(result.source).not.toContain('i0.ɵɵproperty'); - }); - - it('should not remap property names whose names do not correspond to their attribute names', - () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \` - \` - }) - export class MyComponent { - forValue = 'some-input'; - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - consts: [[${AttributeMarker.Bindings}, "for"]] - - // ... - - function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $i0$.ɵɵelement(0, "label", 0); - } - if (rf & 2) { - $i0$.ɵɵproperty("for", ctx.forValue); - } - }`; - - const result = compile(files, angularFiles); - - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should emit temporary evaluation within the binding expression for in-order execution', - () => { - // https://github.com/angular/angular/issues/37194 - // Verifies that temporary expressions used for expressions with potential side-effects in - // the LHS of a safe navigation access are emitted within the binding expression itself, to - // ensure that these temporaries are evaluated during the evaluation of the binding. This - // is important for when the LHS contains a pipe, as pipe evaluation depends on the current - // binding index. - const files = { - app: { - 'example.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: '' - }) - export class MyComponent { - myTitle = 'hello'; - auth?: () => { identity(): any; }; - }` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - template: function MyComponent_Template(rf, ctx) { - … - if (rf & 2) { - let $tmp0$ = null; - $r3$.ɵɵproperty("title", ctx.myTitle)("id", ($tmp0$ = $r3$.ɵɵpipeBind1(1, 3, ($tmp0$ = ctx.auth()) == null ? null : $tmp0$.identity())) == null ? null : $tmp0$.id)("tabindex", 1); - } - } - `; - - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain multiple property bindings into a single instruction', () => { - const files = { - app: { - 'example.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: '' - }) - export class MyComponent { - myTitle = 'hello'; - buttonId = 'special-button'; - }` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - template: function MyComponent_Template(rf, ctx) { - … - if (rf & 2) { - $r3$.ɵɵproperty("title", ctx.myTitle)("id", ctx.buttonId)("tabindex", 1); - } - } - `; - - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain property bindings in the presence of other bindings', () => { - const files = { - app: { - 'example.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: '' - }) - export class MyComponent {}` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - template: function MyComponent_Template(rf, ctx) { - … - if (rf & 2) { - $r3$.ɵɵpropertyInterpolate("aria-label", 1 + 3); - $r3$.ɵɵproperty("title", 1)("tabindex", 3); - $r3$.ɵɵattribute("id", 2); - } - } - `; - - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should not add interpolated properties to the property instruction chain', () => { - const files = { - app: { - 'example.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: '' - }) - export class MyComponent {}` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - template: function MyComponent_Template(rf, ctx) { - … - if (rf & 2) { - $r3$.ɵɵpropertyInterpolate("tabindex", 0 + 3); - $r3$.ɵɵpropertyInterpolate2("aria-label", "hello-", 1 + 3, "-", 2 + 3, ""); - $r3$.ɵɵproperty("title", 1)("id", 2); - } - } - `; - - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain synthetic property bindings together with regular property bindings', () => { - const files = { - app: { - 'example.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: \` - - \` - }) - export class MyComponent { - expansionState = 'expanded'; - }` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - template: function MyComponent_Template(rf, ctx) { - … - if (rf & 2) { - $r3$.ɵɵproperty("title", ctx.myTitle)("@expand", ctx.expansionState)("tabindex", 1)("@fade", "out"); - } - } - `; - - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain multiple property bindings on an ng-template', () => { - const files = { - app: { - 'example.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: '' - }) - export class MyComponent { - myTitle = 'hello'; - buttonId = 'custom-id'; - }` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - template: function MyComponent_Template(rf, ctx) { - … - if (rf & 2) { - $r3$.ɵɵproperty("title", ctx.myTitle)("id", ctx.buttonId)("tabindex", 1); - } - } - `; - - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain multiple property bindings when there are multiple elements', () => { - const files = { - app: { - 'example.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: \` - - - - \` - }) - export class MyComponent { - myTitle = 'hello'; - buttonId = 'special-button'; - }` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - template: function MyComponent_Template(rf, ctx) { - … - if (rf & 2) { - $r3$.ɵɵproperty("title", ctx.myTitle)("id", ctx.buttonId)("tabindex", 1); - $r3$.ɵɵadvance(1); - $r3$.ɵɵproperty("id", 1)("title", "hello")("someProp", 1 + 2); - $r3$.ɵɵadvance(1); - $r3$.ɵɵproperty("prop", "one")("otherProp", 2); - } - } - `; - - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain multiple property bindings when there are child elements', () => { - const files = { - app: { - 'example.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: \` - \` - }) - export class MyComponent { - myTitle = 'hello'; - buttonId = 'special-button'; - }` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - template: function MyComponent_Template(rf, ctx) { - … - if (rf & 2) { - $r3$.ɵɵproperty("title", ctx.myTitle)("id", ctx.buttonId)("tabindex", 1); - $r3$.ɵɵadvance(1); - $r3$.ɵɵproperty("id", 1)("title", "hello")("someProp", 1 + 2); - } - } - `; - - expectEmit(result.source, template, 'Incorrect template'); - }); - }); - - describe('attribute bindings', () => { - it('should chain multiple attribute bindings into a single instruction', () => { - const files = { - app: { - 'example.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: \` - - \` - }) - export class MyComponent { - myTitle = 'hello'; - buttonId = 'special-button'; - }` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - template: function MyComponent_Template(rf, ctx) { - … - if (rf & 2) { - $r3$.ɵɵattribute("title", ctx.myTitle)("id", ctx.buttonId)("tabindex", 1); - } - } - `; - - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain multiple single-interpolation attribute bindings into one instruction', () => { - const files = { - app: { - 'example.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: \` - - \` - }) - export class MyComponent { - myTitle = 'hello'; - buttonId = 'special-button'; - }` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - template: function MyComponent_Template(rf, ctx) { - … - if (rf & 2) { - $r3$.ɵɵattribute("title", ctx.myTitle)("id", ctx.buttonId)("tabindex", 1); - } - } - `; - - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain attribute bindings in the presence of other bindings', () => { - const files = { - app: { - 'example.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: \` - - \` - }) - export class MyComponent {}` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - template: function MyComponent_Template(rf, ctx) { - … - if (rf & 2) { - $r3$.ɵɵattributeInterpolate1("aria-label", "prefix-", 1 + 3, ""); - $r3$.ɵɵproperty("id", 2); - $r3$.ɵɵattribute("title", 1)("tabindex", 3); - } - } - `; - - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should not add interpolated attributes to the attribute instruction chain', () => { - const files = { - app: { - 'example.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: \` - \` - }) - export class MyComponent {}` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - template: function MyComponent_Template(rf, ctx) { - … - if (rf & 2) { - $r3$.ɵɵattributeInterpolate1("tabindex", "prefix-", 0 + 3, ""); - $r3$.ɵɵattributeInterpolate2("aria-label", "hello-", 1 + 3, "-", 2 + 3, ""); - $r3$.ɵɵattribute("title", 1)("id", 2); - } - } - `; - - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain multiple attribute bindings when there are multiple elements', () => { - const files = { - app: { - 'example.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: \` - - - - \` - }) - export class MyComponent { - myTitle = 'hello'; - buttonId = 'special-button'; - }` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - template: function MyComponent_Template(rf, ctx) { - … - if (rf & 2) { - $r3$.ɵɵattribute("title", ctx.myTitle)("id", ctx.buttonId)("tabindex", 1); - $r3$.ɵɵadvance(1); - $r3$.ɵɵattribute("id", 1)("title", "hello")("some-attr", 1 + 2); - $r3$.ɵɵadvance(1); - $r3$.ɵɵattribute("some-attr", "one")("some-other-attr", 2); - } - } - `; - - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain multiple attribute bindings when there are child elements', () => { - const files = { - app: { - 'example.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: \` - \` - }) - export class MyComponent { - myTitle = 'hello'; - buttonId = 'special-button'; - }` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - template: function MyComponent_Template(rf, ctx) { - … - if (rf & 2) { - $r3$.ɵɵattribute("title", ctx.myTitle)("id", ctx.buttonId)("tabindex", 1); - $r3$.ɵɵadvance(1); - $r3$.ɵɵattribute("id", 1)("title", "hello")("some-attr", 1 + 2); - } - } - `; - - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should exclude attribute bindings from the attributes array', () => { - const files: MockDirectory = { - app: { - 'example.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-app', - template: \`\` - }) - export class MyComponent { - doThings() {} - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {}` - } - }; - - const template = ` - consts: [["target", "_blank", "aria-label", "link", ${ - AttributeMarker.Bindings}, "title", "id", "customEvent"]], - … - `; - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect attribute array'); - }); - }); - - describe('host bindings', () => { - it('should support host bindings', () => { - const files = { - app: { - 'spec.ts': ` - import {Directive, HostBinding, NgModule} from '@angular/core'; - - @Directive({selector: '[hostBindingDir]'}) - export class HostBindingDir { - @HostBinding('id') dirId = 'some id'; - } - - @NgModule({declarations: [HostBindingDir]}) - export class MyModule {} - ` - } - }; - - const HostBindingDirDeclaration = ` - HostBindingDir.ɵdir = /*@__PURE__*/ $r3$.ɵɵdefineDirective({ - type: HostBindingDir, - selectors: [["", "hostBindingDir", ""]], - hostVars: 1, - hostBindings: function HostBindingDir_HostBindings(rf, ctx) { - if (rf & 2) { - $r3$.ɵɵhostProperty("id", ctx.dirId); - } - } - }); - `; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, HostBindingDirDeclaration, 'Invalid host binding code'); - }); - - it('should support host bindings with temporary expressions', () => { - const files = { - app: { - 'spec.ts': ` - import {Directive, NgModule} from '@angular/core'; - - @Directive({ - selector: '[hostBindingDir]', - host: {'[id]': 'getData()?.id'} - }) - export class HostBindingDir { - getData?: () => { id: number }; - } - - @NgModule({declarations: [HostBindingDir]}) - export class MyModule {} - ` - } - }; - - const HostBindingDirDeclaration = ` - HostBindingDir.ɵdir = /*@__PURE__*/ $r3$.ɵɵdefineDirective({ - type: HostBindingDir, - selectors: [["", "hostBindingDir", ""]], - hostVars: 1, - hostBindings: function HostBindingDir_HostBindings(rf, ctx) { - if (rf & 2) { - let $tmp0$ = null; - $r3$.ɵɵhostProperty("id", ($tmp0$ = ctx.getData()) == null ? null : $tmp0$.id); - } - } - }); - `; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, HostBindingDirDeclaration, 'Invalid host binding code'); - }); - - it('should support host bindings with pure functions', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'host-binding-comp', - host: { - '[id]': '["red", id]' - }, - template: '' - }) - export class HostBindingComp { - id = 'some id'; - } - - @NgModule({declarations: [HostBindingComp]}) - export class MyModule {} - ` - } - }; - - const HostBindingCompDeclaration = ` - const $ff$ = function ($v$) { return ["red", $v$]; }; - … - HostBindingComp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: HostBindingComp, - selectors: [["host-binding-comp"]], - hostVars: 3, - hostBindings: function HostBindingComp_HostBindings(rf, ctx) { - if (rf & 2) { - $r3$.ɵɵhostProperty("id", $r3$.ɵɵpureFunction1(1, $ff$, ctx.id)); - } - }, - decls: 0, - vars: 0, - template: function HostBindingComp_Template(rf, ctx) {}, - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, HostBindingCompDeclaration, 'Invalid host binding code'); - }); - - it('should support host attribute bindings', () => { - const files = { - app: { - 'spec.ts': ` - import {Directive, NgModule} from '@angular/core'; - - @Directive({ - selector: '[hostAttributeDir]', - host: { - '[attr.required]': 'required' - } - }) - export class HostAttributeDir { - required = true; - } - - @NgModule({declarations: [HostAttributeDir]}) - export class MyModule {} - ` - } - }; - - const HostAttributeDirDeclaration = ` - HostAttributeDir.ɵdir = /*@__PURE__*/ $r3$.ɵɵdefineDirective({ - type: HostAttributeDir, - selectors: [["", "hostAttributeDir", ""]], - hostVars: 1, - hostBindings: function HostAttributeDir_HostBindings(rf, ctx) { - if (rf & 2) { - $r3$.ɵɵattribute("required", ctx.required); - } - } - }); - `; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, HostAttributeDirDeclaration, 'Invalid host attribute code'); - }); - - it('should support host attributes', () => { - const files = { - app: { - 'spec.ts': ` - import {Directive, NgModule} from '@angular/core'; - - @Directive({ - selector: '[hostAttributeDir]', - host: { - 'aria-label': 'label' - } - }) - export class HostAttributeDir { - } - - @NgModule({declarations: [HostAttributeDir]}) - export class MyModule {} - ` - } - }; - - const HostAttributeDirDeclaration = ` - HostAttributeDir.ɵdir = /*@__PURE__*/ $r3$.ɵɵdefineDirective({ - type: HostAttributeDir, - selectors: [["", "hostAttributeDir", ""]], - hostAttrs: ["aria-label", "label"] - }); - `; - - const result = compile(files, angularFiles); - const source = result.source; - - expectEmit(source, HostAttributeDirDeclaration, 'Invalid host attribute code'); - }); - - it('should support host attributes together with host classes and styles', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, Directive, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-host-attribute-component', - template: "...", - host: { - 'title': 'hello there from component', - 'style': 'opacity:1' - } - }) - export class HostAttributeComp { - } - - @Directive({ - selector: '[hostAttributeDir]', - host: { - 'style': 'width: 200px; height: 500px', - '[style.opacity]': "true", - 'class': 'one two', - '[class.three]': "true", - 'title': 'hello there from directive', - } - }) - export class HostAttributeDir { - } - - @NgModule({declarations: [HostAttributeComp, HostAttributeDir]}) - export class MyModule {} - ` - } - }; - - const CompAndDirDeclaration = ` - HostAttributeComp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: HostAttributeComp, - selectors: [["my-host-attribute-component"]], - hostAttrs: ["title", "hello there from component", ${ - AttributeMarker.Styles}, "opacity", "1"], - … - HostAttributeDir.ɵdir = /*@__PURE__*/ $r3$.ɵɵdefineDirective({ - type: HostAttributeDir, - selectors: [["", "hostAttributeDir", ""]], - hostAttrs: ["title", "hello there from directive", ${ - AttributeMarker.Classes}, "one", "two", ${ - AttributeMarker.Styles}, "width", "200px", "height", "500px"], - hostVars: 4, - hostBindings: function HostAttributeDir_HostBindings(rf, ctx) { - … - } - `; - - const result = compile(files, angularFiles); - const source = result.source; - expectEmit(source, CompAndDirDeclaration, 'Invalid host attribute code'); - }); - - it('should chain multiple host property bindings into a single instruction', () => { - const files = { - app: { - 'example.ts': ` - import {Directive} from '@angular/core'; - - @Directive({ - selector: '[my-dir]', - host: { - '[title]': 'myTitle', - '[tabindex]': '1', - '[id]': 'myId' - } - }) - export class MyDirective { - myTitle = 'hello'; - myId = 'special-directive'; - }` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - hostBindings: function MyDirective_HostBindings(rf, ctx) { - … - if (rf & 2) { - $r3$.ɵɵhostProperty("title", ctx.myTitle)("tabindex", 1)("id", ctx.myId); - } - } - `; - - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain both host properties in the decorator and on the class', () => { - const files = { - app: { - 'example.ts': ` - import {Directive, HostBinding} from '@angular/core'; - - @Directive({ - selector: '[my-dir]', - host: { - '[tabindex]': '1' - } - }) - export class MyDirective { - @HostBinding('title') - myTitle = 'hello'; - - @HostBinding('id') - myId = 'special-directive'; - }` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - hostBindings: function MyDirective_HostBindings(rf, ctx) { - … - if (rf & 2) { - $r3$.ɵɵhostProperty("tabindex", 1)("title", ctx.myTitle)("id", ctx.myId); - } - } - `; - - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain multiple host property bindings in the presence of other bindings', () => { - const files = { - app: { - 'example.ts': ` - import {Directive} from '@angular/core'; - - @Directive({ - selector: '[my-dir]', - host: { - '[title]': '"my title"', - '[attr.tabindex]': '1', - '[id]': '"my-id"' - } - }) - export class MyDirective {}` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - hostBindings: function MyDirective_HostBindings(rf, ctx) { - … - if (rf & 2) { - $r3$.ɵɵhostProperty("title", "my title")("id", "my-id"); - $r3$.ɵɵattribute("tabindex", 1); - } - } - `; - - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain multiple synthetic properties into a single instruction call', () => { - const files = { - app: { - 'example.ts': ` - import {Directive} from '@angular/core'; - - @Directive({ - selector: '[my-dir]', - host: { - '[@expand]': 'expandedState', - '[@fadeOut]': 'true', - '[@shrink]': 'isSmall' - } - }) - export class MyDirective { - expandedState = 'collapsed'; - isSmall = true; - }` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - hostBindings: function MyDirective_HostBindings(rf, ctx) { - … - if (rf & 2) { - $r3$.ɵɵsyntheticHostProperty("@expand", ctx.expandedState)("@fadeOut", true)("@shrink", ctx.isSmall); - } - } - `; - - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain multiple host attribute bindings into a single instruction', () => { - const files = { - app: { - 'example.ts': ` - import {Directive} from '@angular/core'; - - @Directive({ - selector: '[my-dir]', - host: { - '[attr.title]': 'myTitle', - '[attr.tabindex]': '1', - '[attr.id]': 'myId' - } - }) - export class MyDirective { - myTitle = 'hello'; - myId = 'special-directive'; - }` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - hostBindings: function MyDirective_HostBindings(rf, ctx) { - … - if (rf & 2) { - $r3$.ɵɵattribute("title", ctx.myTitle)("tabindex", 1)("id", ctx.myId); - } - } - `; - - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain both host attributes in the decorator and on the class', () => { - const files = { - app: { - 'example.ts': ` - import {Directive, HostBinding} from '@angular/core'; - - @Directive({ - selector: '[my-dir]', - host: { - '[attr.tabindex]': '1' - } - }) - export class MyDirective { - @HostBinding('attr.title') - myTitle = 'hello'; - - @HostBinding('attr.id') - myId = 'special-directive'; - }` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - hostBindings: function MyDirective_HostBindings(rf, ctx) { - … - if (rf & 2) { - $r3$.ɵɵattribute("tabindex", 1)("title", ctx.myTitle)("id", ctx.myId); - } - } - `; - - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain multiple host attribute bindings in the presence of other bindings', () => { - const files = { - app: { - 'example.ts': ` - import {Directive} from '@angular/core'; - - @Directive({ - selector: '[my-dir]', - host: { - '[attr.title]': '"my title"', - '[tabindex]': '1', - '[attr.id]': '"my-id"' - } - }) - export class MyDirective {}` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - hostBindings: function MyDirective_HostBindings(rf, ctx) { - … - if (rf & 2) { - $r3$.ɵɵhostProperty("tabindex", 1); - $r3$.ɵɵattribute("title", "my title")("id", "my-id"); - } - } - `; - - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain multiple host listeners into a single instruction', () => { - const files = { - app: { - 'example.ts': ` - import {Directive, HostListener} from '@angular/core'; - - @Directive({ - selector: '[my-dir]', - host: { - '(mousedown)': 'mousedown()', - '(mouseup)': 'mouseup()', - } - }) - export class MyDirective { - mousedown() {} - mouseup() {} - - @HostListener('click') - click() {} - }` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - hostBindings: function MyDirective_HostBindings(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵlistener("mousedown", function MyDirective_mousedown_HostBindingHandler() { return ctx.mousedown(); })("mouseup", function MyDirective_mouseup_HostBindingHandler() { return ctx.mouseup(); })("click", function MyDirective_click_HostBindingHandler() { return ctx.click(); }); - } - } - `; - - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain multiple synthetic host listeners into a single instruction', () => { - const files = { - app: { - 'example.ts': ` - import {Component, HostListener} from '@angular/core'; - - @Component({ - selector: 'my-comp', - template: '', - host: { - '(@animation.done)': 'done()', - } - }) - export class MyComponent { - @HostListener('@animation.start') - start() {} - }` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - hostBindings: function MyComponent_HostBindings(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵsyntheticHostListener("@animation.done", function MyComponent_animation_animation_done_HostBindingHandler() { return ctx.done(); })("@animation.start", function MyComponent_animation_animation_start_HostBindingHandler() { return ctx.start(); }); - } - } - `; - - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain multiple regular and synthetic host listeners into two instructions', () => { - const files = { - app: { - 'example.ts': ` - import {Component, HostListener} from '@angular/core'; - - @Component({ - selector: 'my-comp', - template: '', - host: { - '(mousedown)': 'mousedown()', - '(@animation.done)': 'done()', - '(mouseup)': 'mouseup()', - } - }) - export class MyComponent { - @HostListener('@animation.start') - start() {} - - @HostListener('click') - click() {} - }` - } - }; - - const result = compile(files, angularFiles); - const template = ` - … - hostBindings: function MyComponent_HostBindings(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵsyntheticHostListener("@animation.done", function MyComponent_animation_animation_done_HostBindingHandler() { return ctx.done(); })("@animation.start", function MyComponent_animation_animation_start_HostBindingHandler() { return ctx.start(); }); - $r3$.ɵɵlistener("mousedown", function MyComponent_mousedown_HostBindingHandler() { return ctx.mousedown(); })("mouseup", function MyComponent_mouseup_HostBindingHandler() { return ctx.mouseup(); })("click", function MyComponent_click_HostBindingHandler() { return ctx.click(); }); - } - } - `; - expectEmit(result.source, template, 'Incorrect template'); - }); - }); - - describe('non bindable behavior', () => { - const getAppFiles = (template: string = ''): MockDirectory => ({ - app: { - 'example.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-app', - template: \`${template}\` - }) - export class MyComponent { - name = 'John Doe'; - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {}` - } - }); - - it('should generate the proper update instructions for interpolated properties', () => { - const files: MockDirectory = getAppFiles(` -
-
-
-
-
-
-
-
-
-
- `); - - const template = ` - … - if (rf & 2) { - i0.ɵɵpropertyInterpolateV("title", ["a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i", ctx.nine, "j"]); - i0.ɵɵadvance(1); - i0.ɵɵpropertyInterpolate8("title", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i"); - i0.ɵɵadvance(1); - i0.ɵɵpropertyInterpolate7("title", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h"); - i0.ɵɵadvance(1); - i0.ɵɵpropertyInterpolate6("title", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g"); - i0.ɵɵadvance(1); - i0.ɵɵpropertyInterpolate5("title", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f"); - i0.ɵɵadvance(1); - i0.ɵɵpropertyInterpolate4("title", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e"); - i0.ɵɵadvance(1); - i0.ɵɵpropertyInterpolate3("title", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d"); - i0.ɵɵadvance(1); - i0.ɵɵpropertyInterpolate2("title", "a", ctx.one, "b", ctx.two, "c"); - i0.ɵɵadvance(1); - i0.ɵɵpropertyInterpolate1("title", "a", ctx.one, "b"); - i0.ɵɵadvance(1); - i0.ɵɵpropertyInterpolate("title", ctx.one); - } - … - `; - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect handling of interpolated properties'); - }); - - - it('should generate the proper update instructions for interpolated attributes', () => { - const files: MockDirectory = getAppFiles(` -
-
-
-
-
-
-
-
-
-
- `); - - const template = ` - … - if (rf & 2) { - i0.ɵɵattributeInterpolateV("title", ["a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i", ctx.nine, "j"]); - i0.ɵɵadvance(1); - i0.ɵɵattributeInterpolate8("title", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i"); - i0.ɵɵadvance(1); - i0.ɵɵattributeInterpolate7("title", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h"); - i0.ɵɵadvance(1); - i0.ɵɵattributeInterpolate6("title", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g"); - i0.ɵɵadvance(1); - i0.ɵɵattributeInterpolate5("title", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f"); - i0.ɵɵadvance(1); - i0.ɵɵattributeInterpolate4("title", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e"); - i0.ɵɵadvance(1); - i0.ɵɵattributeInterpolate3("title", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d"); - i0.ɵɵadvance(1); - i0.ɵɵattributeInterpolate2("title", "a", ctx.one, "b", ctx.two, "c"); - i0.ɵɵadvance(1); - i0.ɵɵattributeInterpolate1("title", "a", ctx.one, "b"); - i0.ɵɵadvance(1); - i0.ɵɵattribute("title", ctx.one); - } - … - `; - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect handling of interpolated properties'); - }); - - it('should keep local ref for host element', () => { - const files: MockDirectory = getAppFiles(` - - Hello {{ name }}! - - {{ myRef.id }} - `); - - const template = ` - … - consts: [["id", "my-id"], ["myRef", ""]], - template:function MyComponent_Template(rf, $ctx$){ - if (rf & 1) { - $i0$.ɵɵelementStart(0, "b", 0, 1); - $i0$.ɵɵdisableBindings(); - $i0$.ɵɵelementStart(2, "i"); - $i0$.ɵɵtext(3, "Hello {{ name }}!"); - $i0$.ɵɵelementEnd(); - $i0$.ɵɵenableBindings(); - $i0$.ɵɵelementEnd(); - $i0$.ɵɵtext(4); - } - if (rf & 2) { - const $_r0$ = $i0$.ɵɵreference(1); - $r3$.ɵɵadvance(4); - $i0$.ɵɵtextInterpolate1(" ", $_r0$.id, " "); - } - } - `; - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect handling of local refs for host element'); - }); - - it('should not have local refs for nested elements', () => { - const files: MockDirectory = getAppFiles(` -
- {{ myInput.value }} -
- `); - - const template = ` - … - consts: [["value", "one", "#myInput", ""]], - template:function MyComponent_Template(rf, $ctx$){ - if (rf & 1) { - $i0$.ɵɵelementStart(0, "div"); - $i0$.ɵɵdisableBindings(); - $i0$.ɵɵelement(1, "input", 0); - $i0$.ɵɵtext(2, " {{ myInput.value }} "); - $i0$.ɵɵenableBindings(); - $i0$.ɵɵelementEnd(); - } - `; - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect handling of local refs for nested elements'); - }); - - it('should not process property bindings and listeners', () => { - const files: MockDirectory = getAppFiles(` -
-
-
- `); - - const template = ` - … - consts: [["[id]", "my-id", "(click)", "onclick"]], - template:function MyComponent_Template(rf, $ctx$){ - if (rf & 1) { - $i0$.ɵɵelementStart(0, "div"); - $i0$.ɵɵdisableBindings(); - $i0$.ɵɵelement(1, "div", 0); - $i0$.ɵɵenableBindings(); - $i0$.ɵɵelementEnd(); - } - `; - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect handling of property bindings and listeners'); - }); - - it('should not generate extra instructions for elements with no children', () => { - const files: MockDirectory = getAppFiles(` -
- `); - - const template = ` - template:function MyComponent_Template(rf, $ctx$){ - if (rf & 1) { - $i0$.ɵɵelement(0, "div"); - } - } - `; - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect handling of elements with no children'); - }); - }); -}); diff --git a/packages/compiler-cli/test/compliance_old/r3_view_compiler_di_spec.ts b/packages/compiler-cli/test/compliance_old/r3_view_compiler_di_spec.ts deleted file mode 100644 index 91820d3953..0000000000 --- a/packages/compiler-cli/test/compliance_old/r3_view_compiler_di_spec.ts +++ /dev/null @@ -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); - }); -}); diff --git a/packages/compiler-cli/test/compliance_old/r3_view_compiler_directives_spec.ts b/packages/compiler-cli/test/compliance_old/r3_view_compiler_directives_spec.ts deleted file mode 100644 index e5019a9aed..0000000000 --- a/packages/compiler-cli/test/compliance_old/r3_view_compiler_directives_spec.ts +++ /dev/null @@ -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: '
'}) - 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: '
'}) - 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: '
'}) - 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) {} - } - - @Component({ - selector: 'my-component', - template: \` - Some content - \` - }) - 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) {} - } - - @Component({ - selector: 'my-component', - template: \` - Some content - \` - }) - 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: ''}) - 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: '
'}) - 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: '
'}) - 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'); - }); - }); -}); diff --git a/packages/compiler-cli/test/compliance_old/r3_view_compiler_i18n_spec.ts b/packages/compiler-cli/test/compliance_old/r3_view_compiler_i18n_spec.ts deleted file mode 100644 index f0cf9ac3a5..0000000000 --- a/packages/compiler-cli/test/compliance_old/r3_view_compiler_i18n_spec.ts +++ /dev/null @@ -1,3446 +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 * as ts from 'typescript'; - -import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../../compiler/src/compiler'; -import {decimalDigest} from '../../../compiler/src/i18n/digest'; -import {extractMessages} from '../../../compiler/src/i18n/extractor_merger'; -import {HtmlParser} from '../../../compiler/src/ml_parser/html_parser'; - -import {compile, expectEmit} from './mock_compile'; - -const angularFiles = setup({ - compileAngular: false, - compileFakeCore: true, - compileAnimations: false, -}); - -const htmlParser = new HtmlParser(); - -// TODO: update translation extraction RegExp to support `$localize` tags. -const EXTRACT_GENERATED_TRANSLATIONS_REGEXP = - /const\s*(.*?)\s*=\s*goog\.getMsg\("(.*?)",?\s*(.*?)\)/g; - -const diff = (a: Set, b: Set): Set => - new Set([...Array.from(a)].filter(x => !b.has(x))); - -const extract = (from: string, regex: any, transformFn: (match: any[], state: Set) => any) => { - const result = new Set(); - let item; - while ((item = regex.exec(from)) !== null) { - result.add(transformFn(item, result)); - } - return result; -}; - -// verify that we extracted all the necessary translations -// and their ids match the ones extracted via 'ng extract-i18n' -const verifyTranslationIds = - (source: string, output: string, exceptions = {}, - interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG) => { - const parseResult = - htmlParser.parse(source, 'path:://to/template', {tokenizeExpansionForms: true}); - const extractedIdToMsg = new Map(); - const extractedIds = new Set(); - const generatedIds = new Set(); - const msgs = extractMessages(parseResult.rootNodes, interpolationConfig, [], {}); - msgs.messages.forEach(msg => { - const id = msg.id || decimalDigest(msg); - extractedIds.add(id); - extractedIdToMsg.set(id, msg); - }); - const regexp = /const\s*MSG_EXTERNAL_(.+?)\s*=\s*goog\.getMsg/g; - const ids = extract(output, regexp, v => v[1]); - ids.forEach(id => { - generatedIds.add(id.split('$$')[0]); - }); - const delta = diff(extractedIds, generatedIds); - if (delta.size) { - // check if we have ids in exception list - const outstanding = diff(delta, new Set(Object.keys(exceptions))); - if (outstanding.size) { - throw new Error(` - Extracted and generated IDs don't match, delta: - ${JSON.stringify(Array.from(delta))} - `); - } - } - return true; - }; - -// verify that placeholders in translation string match -// placeholders object defined as goog.getMsg function argument -const verifyPlaceholdersIntegrity = (output: string) => { - const extractTranslations = (from: string) => { - return extract(from, EXTRACT_GENERATED_TRANSLATIONS_REGEXP, v => [v[2], v[3]]); - }; - const extractPlaceholdersFromBody = (body: string) => { - const regex = /{\$(.*?)}/g; - return extract(body, regex, v => v[1]); - }; - const extractPlaceholdersFromArgs = (args: string) => { - const regex = /\s+"(.+?)":\s*".*?"/g; - return extract(args, regex, v => v[1]); - }; - const translations = extractTranslations(output); - translations.forEach((translation) => { - const bodyPhs = extractPlaceholdersFromBody(translation[0]); - const argsPhs = extractPlaceholdersFromArgs(translation[1]); - if (bodyPhs.size !== argsPhs.size || diff(bodyPhs, argsPhs).size) { - return false; - } - }); - return true; -}; - -const verifyUniqueConsts = (output: string) => { - extract( - output, EXTRACT_GENERATED_TRANSLATIONS_REGEXP, - (current: string[], state: Set): string => { - const key = current[1]; - if (state.has(key)) { - throw new Error(`Duplicate const ${key} found in generated output!`); - } - return key; - }); - return true; -}; - -/** - * Escape the template string for being placed inside a backtick string literal. - * - * * "\" would erroneously indicate a control character - * * "`" and "${" strings would erroneously indicate the end of a message part - */ -const escapeTemplate = (template: string) => - template.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '$\\{'); - -const getAppFilesWithTemplate = (template: string, args: any = {}) => ({ - app: { - 'spec.template.html': template, - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - ${args.preserveWhitespaces ? 'preserveWhitespaces: true,' : ''} - ${args.interpolation ? 'interpolation: ' + JSON.stringify(args.interpolation) + ', ' : ''} - ${ - args.templateUrl ? `templateUrl: 'spec.template.html'` : - `template: \`${escapeTemplate(template)}\``}) - export class MyComponent {} - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } -}); - -const maybePrint = (output: string, verbose: boolean) => { - if (!verbose) return; - // tslint:disable-next-line - console.log(` -========== Generated output: ========== -${output} -======================================= - `); -}; - -const verify = (input: string, output: string, extra: { - inputArgs?: any, - compilerOptions?: any, - skipPathBasedCheck?: boolean, - skipIdBasedCheck?: boolean, - verbose?: boolean, - exceptions?: {} -} = {}): void => { - const files = getAppFilesWithTemplate(input, extra.inputArgs); - const opts = (i18nUseExternalIds: boolean) => - ({i18nUseExternalIds, ...(extra.compilerOptions || {})}); - - // invoke with file-based prefix translation names - if (!extra.skipPathBasedCheck) { - const result = compile(files, angularFiles, opts(false)); - maybePrint(result.source, !!extra.verbose); - expect(verifyPlaceholdersIntegrity(result.source)).toBe(true); - expect(verifyUniqueConsts(result.source)).toBe(true); - expectEmit(result.source, output, 'Incorrect template'); - } - - // invoke with translation names based on external ids - if (!extra.skipIdBasedCheck) { - const result = compile(files, angularFiles, opts(true)); - maybePrint(result.source, !!extra.verbose); - const interpolationConfig = extra.inputArgs && extra.inputArgs.interpolation ? - InterpolationConfig.fromArray(extra.inputArgs.interpolation) : - undefined; - expect(verifyTranslationIds(input, result.source, extra.exceptions, interpolationConfig)) - .toBe(true); - expect(verifyPlaceholdersIntegrity(result.source)).toBe(true); - expect(verifyUniqueConsts(result.source)).toBe(true); - expectEmit(result.source, output, 'Incorrect template'); - } -}; - -// Describes message metadata object. -interface Meta { - desc?: string; - meaning?: string; - id?: string; -} - -// Describes placeholder type used in tests. Note: the type is an array (not an object), since it's -// important to preserve the order of placeholders (so that we can compare it with generated -// output). -type Placeholder = [string, string]; - -// Unique message id index that is needed to avoid different i18n vars with the same name to appear -// in the i18n block while generating an output string (used to verify compiler-generated code). -let msgIndex = 0; - -// Wraps a string into quotes is needed. -// Note: if a string starts with `$` is a special case in tests when ICU reference -// is used as a placeholder value, this we should not wrap it in quotes. -const quotedValue = (value: string) => value.startsWith('$') ? value : `"${value}"`; - -// Generates a string that represents expected Closure metadata output. -const i18nMsgClosureMeta = (meta?: Meta): string => { - if (!meta || !(meta.desc || meta.meaning)) return ''; - return ` - /** - ${meta.desc ? '* @desc ' + meta.desc : ''} - ${meta.meaning ? '* @meaning ' + meta.meaning : ''} - */ - `; -}; - -// Converts a set of placeholders to a string (as it's expected from compiler). -const i18nPlaceholdersToString = (placeholders: Placeholder[]): string => { - if (placeholders.length === 0) return ''; - const result = placeholders.map(([key, value]) => `"${key}": ${quotedValue(value)}`); - return `, { ${result.join(',')} }`; -}; - -// Generates a string that represents expected $localize metadata output. -const i18nMsgLocalizeMeta = (meta?: Meta): string => { - if (!meta) return ''; - let localizeMeta = ''; - if (meta.meaning) localizeMeta += `${meta.meaning}|`; - if (meta.desc) localizeMeta += meta.desc; - if (meta.id) localizeMeta += `@@${meta.id}`; - return `:${localizeMeta}:`; -}; - -// Transforms a message in a Closure format to a $localize version. -const i18nMsgInsertLocalizePlaceholders = - (message: string, placeholders: Placeholder[]): string => { - if (placeholders.length > 0) { - message = message.replace(/{\$(.*?)}/g, function(_, name) { - const value = placeholders.find(([k, _]) => k === name)![1]; - // e.g. startDivTag -> START_DIV_TAG - const key = name.replace(/[A-Z]/g, (ch: string) => '_' + ch).toUpperCase(); - return '$' + String.raw`{${quotedValue(value)}}:${key}:`; - }); - } - return message; - }; - -// Generates a string that represents expected i18n block content for simple message. -const i18nMsg = (message: string, placeholders: Placeholder[] = [], meta?: Meta) => { - const varName = `$I18N_${msgIndex++}$`; - const closurePlaceholders = i18nPlaceholdersToString(placeholders); - const locMessageWithPlaceholders = i18nMsgInsertLocalizePlaceholders(message, placeholders); - return String.raw` - let ${varName}; - if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { - ${i18nMsgClosureMeta(meta)} - const $MSG_EXTERNAL_${msgIndex}$ = goog.getMsg("${message}"${closurePlaceholders}); - ${varName} = $MSG_EXTERNAL_${msgIndex}$; - } - else { - ${varName} = $localize \`${i18nMsgLocalizeMeta(meta)}${locMessageWithPlaceholders}\`; - }`; -}; - -// Generates a string that represents expected i18n block content for a message that requires -// post-processing (thus includes `ɵɵi18nPostprocess` in generated code). -const i18nMsgWithPostprocess = - (message: string, placeholders: Placeholder[] = [], meta?: Meta, - postprocessPlaceholders?: Placeholder[]) => { - const varName = `$I18N_${msgIndex}$`; - const ppPaceholders = - postprocessPlaceholders ? i18nPlaceholdersToString(postprocessPlaceholders) : ''; - return String.raw` - ${i18nMsg(message, placeholders, meta)} - ${varName} = $r3$.ɵɵi18nPostprocess($${varName}$${ppPaceholders}); - `; - }; - -// Generates a string that represents expected i18n block content for an ICU. -const i18nIcuMsg = (message: string, placeholders: Placeholder[] = []) => { - return i18nMsgWithPostprocess(message, [], undefined, placeholders); -}; - -describe('i18n support in the template compiler', () => { - describe('element attributes', () => { - it('should add the meaning and description as JsDoc comments and metadata blocks', () => { - const input = ` -
Content A
-
Content B
-
Content C
-
Content D
-
Content E
-
Content F
-
Content G
-
Content H
- `; - - const i18n_0 = i18nMsg('Content A', [], {id: 'idA', meaning: 'meaningA', desc: 'descA'}); - const i18n_1 = i18nMsg('Title B', [], {id: 'idB', meaning: 'meaningB', desc: 'descB'}); - const i18n_2 = i18nMsg('Title C', [], {meaning: 'meaningC'}); - const i18n_3 = i18nMsg('Title D', [], {meaning: 'meaningD', desc: 'descD'}); - const i18n_4 = i18nMsg('Title E', [], {id: 'idE', desc: 'meaningE'}); - const i18n_5 = i18nMsg('Title F', [], {id: 'idF'}); - - // Keeping this block as a raw string, since it checks escaping of special chars. - const i18n_6 = String.raw` - let $i18n_23$; - if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { - /** - * @desc [BACKUP_$` + - String.raw`{MESSAGE}_ID:idH]` + - '`' + String.raw`desc - */ - const $MSG_EXTERNAL_idG$$APP_SPEC_TS_24$ = goog.getMsg("Title G"); - $i18n_23$ = $MSG_EXTERNAL_idG$$APP_SPEC_TS_24$; - } - else { - $i18n_23$ = $localize \`:[BACKUP_$\{MESSAGE}_ID\:idH]\\\`desc@@idG:Title G\`; - } - `; - - // Keeping this block as a raw string, since it checks escaping of special chars. - const i18n_7 = String.raw` - let $i18n_7$; - if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { - /** - * @desc Some text \' [BACKUP_MESSAGE_ID: xxx] - */ - const $MSG_EXTERNAL_idG$$APP_SPEC_TS_21$ = goog.getMsg("Content H"); - $i18n_7$ = $MSG_EXTERNAL_idG$$APP_SPEC_TS_21$; - } - else { - $i18n_7$ = $localize \`:Some text \\' [BACKUP_MESSAGE_ID\: xxx]:Content H\`; - } - `; - - const output = String.raw` - consts: function () { - ${i18n_0} - ${i18n_1} - ${i18n_2} - ${i18n_3} - ${i18n_4} - ${i18n_5} - ${i18n_6} - ${i18n_7} - return [ - $i18n_0$, - ["title", $i18n_1$], - ["title", $i18n_2$], - ["title", $i18n_3$], - ["title", $i18n_4$], - ["title", $i18n_5$], - ["title", $i18n_6$], - $i18n_7$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18n(1, 0); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementStart(2, "div", 1); - $r3$.ɵɵtext(3, "Content B"); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementStart(4, "div", 2); - $r3$.ɵɵtext(5, "Content C"); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementStart(6, "div", 3); - $r3$.ɵɵtext(7, "Content D"); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementStart(8, "div", 4); - $r3$.ɵɵtext(9, "Content E"); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementStart(10, "div", 5); - $r3$.ɵɵtext(11, "Content F"); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementStart(12, "div", 6); - $r3$.ɵɵtext(13, "Content G"); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementStart(14, "div"); - $r3$.ɵɵi18n(15, 7); - $r3$.ɵɵelementEnd(); - } - } - `; - - verify(input, output); - }); - - it('should support i18n attributes on explicit elements', () => { - const input = ` - - `; - - const i18n_0 = i18nMsg('Hello'); - const output = String.raw` - consts: function () { - ${i18n_0} - return [ - ["title", $i18n_0$] - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵtemplate(0, MyComponent_ng_template_0_Template, 0, 0, "ng-template", 0); - } - } - `; - verify(input, output); - }); - - it('should support i18n attributes on explicit with structural directives', - () => { - const input = ` - Test - `; - - const i18n_0 = i18nMsg('Hello'); - - const output = String.raw` - function MyComponent_0_ng_template_0_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵtext(0, "Test"); - } - } - function MyComponent_0_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵtemplate(0, MyComponent_0_ng_template_0_Template, 1, 0, "ng-template", 1); - } - } - … - consts: function() { - ${i18n_0} - return [ - [${AttributeMarker.Template}, "ngIf"], - ["title", $i18n_0$] - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵtemplate(0, MyComponent_0_Template, 1, 0, undefined, 0); - } - if (rf & 2) { - $r3$.ɵɵproperty("ngIf", ctx.visible); - } - } - `; - verify(input, output); - }); - - it('should support i18n attributes with interpolations on explicit elements', - () => { - const input = ` - - `; - - const i18n_0 = - i18nMsg('Hello {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]]); - const output = String.raw` - consts: function() { - ${i18n_0} - return [ - [${AttributeMarker.Bindings}, "title"], - ["title", $i18n_0$] - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵtemplate(0, MyComponent_ng_template_0_Template, 0, 0, "ng-template", 0); - $r3$.ɵɵi18nAttributes(1, 1); - } - if (rf & 2) { - $r3$.ɵɵi18nExp(ctx.name); - $r3$.ɵɵi18nApply(1); - } - } - `; - verify(input, output); - }); - - it('should support i18n attributes with interpolations on explicit elements with structural directives', - () => { - const input = ` - - `; - - const i18n_0 = - i18nMsg('Hello {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]]); - const output = String.raw` - function MyComponent_0_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵtemplate(0, MyComponent_0_ng_template_0_Template, 0, 0, "ng-template", 1); - $r3$.ɵɵi18nAttributes(1, 2); - } - if (rf & 2) { - const $ctx_r2$ = $r3$.ɵɵnextContext(); - $r3$.ɵɵi18nExp($ctx_r2$.name); - $r3$.ɵɵi18nApply(1); - } - } - … - consts: function() { - ${i18n_0} - return [ - [${AttributeMarker.Template}, "ngIf"], - [${AttributeMarker.Bindings}, "title"], - ["title", $i18n_0$] - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵtemplate(0, MyComponent_0_Template, 2, 1, undefined, 0); - } - if (rf & 2) { - $r3$.ɵɵproperty("ngIf", true); - } - }, - `; - verify(input, output); - }); - - it('should not create translations for empty attributes', () => { - const input = ` -
- `; - - const output = ` - … - consts: [["id", "static", "title", ""]], - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div", 0); - } - } - `; - - verify(input, output); - }); - - it('should not create translations for bound attributes', () => { - const input = ` -
-
- `; - - const output = ` - consts: [[3, "title"]], - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div", 0); - } - if (rf & 2) { - $r3$.ɵɵproperty("title", ctx.title); - $r3$.ɵɵattribute("label", ctx.label); - } - } - `; - - verify(input, output); - }); - - it('should translate static attributes', () => { - const input = ` -
- `; - - const i18n_0 = i18nMsg('introduction', [], {meaning: 'm', desc: 'd'}); - - const output = String.raw` - consts: function() { - ${i18n_0} - return [ - ["id", "static", "title", $i18n_0$] - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div", 0); - } - } - `; - - verify(input, output); - }); - - it('should support interpolation', () => { - const input = ` -
-
- `; - - const i18n_0 = i18nMsg('static text'); - const i18n_1 = i18nMsg( - 'intro {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], - {meaning: 'm', desc: 'd'}); - const i18n_2 = i18nMsg( - '{$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], - {meaning: 'm1', desc: 'd1'}); - const i18n_3 = i18nMsg( - '{$interpolation} and {$interpolation_1} and again {$interpolation_2}', - [ - ['interpolation', String.raw`\uFFFD0\uFFFD`], - ['interpolation_1', String.raw`\uFFFD1\uFFFD`], - ['interpolation_2', String.raw`\uFFFD2\uFFFD`] - ], - {meaning: 'm2', desc: 'd2'}); - const i18n_4 = i18nMsg('{$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]]); - - const output = String.raw` - decls: 5, - vars: 8, - consts: function() { - ${i18n_0} - ${i18n_1} - ${i18n_2} - ${i18n_3} - ${i18n_4} - return [ - ["id", "dynamic-1", "aria-roledescription", $i18n_0$, ${AttributeMarker.I18n}, - "title", "aria-label"], - ["title", $i18n_1$, "aria-label", $i18n_2$], - ["id", "dynamic-2", ${AttributeMarker.I18n}, "title", "aria-roledescription"], - ["title", $i18n_3$, "aria-roledescription", $i18n_4$] - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div", 0); - $r3$.ɵɵpipe(1, "uppercase"); - $r3$.ɵɵi18nAttributes(2, 1); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementStart(3, "div", 2); - $r3$.ɵɵi18nAttributes(4, 3); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(1, 6, ctx.valueA))(ctx.valueB); - $r3$.ɵɵi18nApply(2); - $r3$.ɵɵadvance(3); - $r3$.ɵɵi18nExp(ctx.valueA)(ctx.valueB)(ctx.valueA + ctx.valueB)(ctx.valueC); - $r3$.ɵɵi18nApply(4); - } - } - `; - - verify(input, output); - }); - - it('should support interpolation with custom interpolation config', () => { - const input = ` -
- `; - - const i18n_0 = i18nMsg( - 'intro {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], - {meaning: 'm', desc: 'd'}); - - const output = String.raw` - consts: function() { - ${i18n_0} - return [ - [${AttributeMarker.I18n}, "title"], - ["title", $i18n_0$] - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div", 0); - $r3$.ɵɵpipe(1, "uppercase"); - $r3$.ɵɵi18nAttributes(2, 1); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(1, 1, ctx.valueA)); - $r3$.ɵɵi18nApply(2); - } - } - `; - verify(input, output, {inputArgs: {interpolation: ['{%', '%}']}}); - }); - - it('should correctly bind to context in nested template', () => { - const input = ` -
-
-
- `; - - const i18n_0 = i18nMsg( - 'different scope {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], - {meaning: 'm', desc: 'd'}); - - const output = String.raw` - function MyComponent_div_0_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵelementStart(1, "div", 1); - $r3$.ɵɵpipe(2, "uppercase"); - $r3$.ɵɵi18nAttributes(3, 2); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - const $outer_r1$ = ctx.$implicit; - $r3$.ɵɵadvance(1); - $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(2, 1, $outer_r1$)); - $r3$.ɵɵi18nApply(3); - } - } - … - decls: 1, - vars: 1, - consts: function() { - ${i18n_0} - return [ - [${AttributeMarker.Template}, "ngFor", "ngForOf"], - [${AttributeMarker.I18n}, "title"], - ["title", $i18n_0$] - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵtemplate(0, MyComponent_div_0_Template, 4, 3, "div", 0); - } - if (rf & 2) { - $r3$.ɵɵproperty("ngForOf", ctx.items); - } - } - `; - - verify(input, output); - }); - - it('should support complex expressions in interpolation', () => { - const input = ` -
- `; - - const i18n_0 = - i18nMsg('{$interpolation} title', [['interpolation', String.raw`\uFFFD0\uFFFD`]]); - - const output = String.raw` - decls: 2, - vars: 1, - consts: function() { - ${i18n_0} - return [ - [${AttributeMarker.I18n}, "title"], - ["title", $i18n_0$] - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div", 0); - $r3$.ɵɵi18nAttributes(1, 1); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - let $tmp_0_0$ = null; - $r3$.ɵɵi18nExp(($tmp_0_0$ = ctx.valueA.getRawValue()) == null ? null : $tmp_0_0$.getTitle()); - $r3$.ɵɵi18nApply(1); - } - } - `; - - verify(input, output); - }); - - it('should work correctly when placed on i18n root node', () => { - const input = ` -
Some content
- `; - - const i18n_0 = i18nMsg('Element title', [], {meaning: 'm', desc: 'd'}); - const i18n_1 = i18nMsg('Some content'); - - const output = String.raw` - consts: function() { - ${i18n_0} - ${i18n_1} - return [ - ["title", $i18n_0$], - $i18n_1$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div", 0); - $r3$.ɵɵi18n(1, 1); - $r3$.ɵɵelementEnd(); - } - } - `; - verify(input, output); - }); - - it('should sanitize ids and generate proper variable names', () => { - const input = ` -
- Some content -
- `; - - // Keeping raw content (avoiding `i18nMsg`) to illustrate message id sanitization. - const output = String.raw` - let $I18N_0$; - if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { - const $MSG_EXTERNAL_ID_WITH_INVALID_CHARS$$APP_SPEC_TS_1$ = goog.getMsg("Element title"); - $I18N_0$ = $MSG_EXTERNAL_ID_WITH_INVALID_CHARS$$APP_SPEC_TS_1$; - } - else { - $I18N_0$ = $localize \`:@@ID.WITH.INVALID.CHARS:Element title\`; - } - … - let $I18N_2$; - if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { - const $MSG_EXTERNAL_ID_WITH_INVALID_CHARS_2$$APP_SPEC_TS_4$ = goog.getMsg(" Some content "); - $I18N_2$ = $MSG_EXTERNAL_ID_WITH_INVALID_CHARS_2$$APP_SPEC_TS_4$; - } - else { - $I18N_2$ = $localize \`:@@ID.WITH.INVALID.CHARS.2: Some content \`; - } - `; - - const exceptions = { - 'ID.WITH.INVALID.CHARS': 'Verify const name generation only', - 'ID.WITH.INVALID.CHARS.2': 'Verify const name generation only' - }; - verify(input, output, {exceptions, skipPathBasedCheck: true}); - }); - }); - - describe('nested nodes', () => { - it('should not produce instructions for empty content', () => { - const input = ` -
-
-
- -
- `; - - const output = String.raw` - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div"); - $r3$.ɵɵelement(1, "div"); - $r3$.ɵɵelement(2, "div"); - } - } - `; - - const exceptions = { - '6524085439495453930': 'No translation is produced for empty content (whitespaces)', - '814405839137385666': 'No translation is produced for empty content (line breaks)' - }; - verify(input, output, {exceptions}); - }); - - it('should ignore HTML comments within translated text', () => { - const input = `
Some text
`; - const output = i18nMsg('Some text'); - verify(input, output); - }); - - it('should properly escape quotes in content', () => { - const input = ` -
Some text 'with single quotes', "with double quotes", \`with backticks\` and without quotes.
- `; - - // Keeping raw content (avoiding `i18nMsg`) to illustrate quotes escaping. - const output = String.raw` - let $I18N_0$; - if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { - const $MSG_EXTERNAL_4924931801512133405$$APP_SPEC_TS_0$ = goog.getMsg("Some text 'with single quotes', \"with double quotes\", ` + - '`with backticks`' + String.raw` and without quotes."); - $I18N_0$ = $MSG_EXTERNAL_4924931801512133405$$APP_SPEC_TS_0$; - } - else { - $I18N_0$ = $localize \`Some text 'with single quotes', "with double quotes", \\\`with backticks\\\` and without quotes.\`; - } - `; - - verify(input, output); - }); - - it('should handle interpolations wrapped in backticks', () => { - const input = '
`{{ count }}`
'; - // Keeping raw content (avoiding `i18nMsg`) to illustrate backticks escaping. - const output = String.raw` - let $I18N_0$; - if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { - const $MSG_APP_SPEC_TS_1$ = goog.getMsg("` + - '`{$interpolation}`' + String.raw`", { "interpolation": "\uFFFD0\uFFFD" }); - $I18N_0$ = $MSG_APP_SPEC_TS_1$; - } - else { - $I18N_0$ = $localize \`\\\`$` + - String.raw`{"\uFFFD0\uFFFD"}:INTERPOLATION:\\\`\`; - }`; - verify(input, output); - }); - - it('should handle i18n attributes with plain-text content', () => { - const input = ` -
My i18n block #1
-
My non-i18n block #1
-
My i18n block #2
-
My non-i18n block #2
-
My i18n block #3
- `; - - const i18n_0 = i18nMsg('My i18n block #1'); - const i18n_1 = i18nMsg('My i18n block #2'); - const i18n_2 = i18nMsg('My i18n block #3'); - - const output = String.raw` - consts: function() { - ${i18n_0} - ${i18n_1} - ${i18n_2} - return [ - $i18n_0$, - $i18n_1$, - $i18n_2$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18n(1, 0); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementStart(2, "div"); - $r3$.ɵɵtext(3, "My non-i18n block #1"); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementStart(4, "div"); - $r3$.ɵɵi18n(5, 1); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementStart(6, "div"); - $r3$.ɵɵtext(7, "My non-i18n block #2"); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementStart(8, "div"); - $r3$.ɵɵi18n(9, 2); - $r3$.ɵɵelementEnd(); - } - } - `; - - verify(input, output); - }); - - it('should support named interpolations', () => { - const input = ` -
- Named interpolation: {{ valueA // i18n(ph="PH_A") }} - Named interpolation with spaces: {{ valueB // i18n(ph="PH B") }} -
- `; - - // Keeping raw content (avoiding `i18nMsg`) to illustrate how named interpolations are - // generated. - const i18n_0 = String.raw` - let $I18N_0$; - if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { - const $MSG_EXTERNAL_7597881511811528589$$APP_SPEC_TS_0$ = goog.getMsg(" Named interpolation: {$phA} Named interpolation with spaces: {$phB} ", { - "phA": "\uFFFD0\uFFFD", - "phB": "\uFFFD1\uFFFD" - }); - $I18N_0$ = $MSG_EXTERNAL_7597881511811528589$$APP_SPEC_TS_0$; - } - else { - $I18N_0$ = $localize \` Named interpolation: $` + - String.raw`{"\uFFFD0\uFFFD"}:PH_A: Named interpolation with spaces: $` + - String.raw`{"\uFFFD1\uFFFD"}:PH_B: \`; - } - `; - - const output = String.raw` - decls: 2, - vars: 2, - consts: function() { - ${i18n_0} - return [ - $i18n_0$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18n(1, 0); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(1); - $r3$.ɵɵi18nExp(ctx.valueA)(ctx.valueB); - $r3$.ɵɵi18nApply(1); - } - } - `; - - verify(input, output); - }); - - it('should support interpolation with custom interpolation config', () => { - const input = ` -
{% valueA %}
- `; - - const i18n_0 = i18nMsg('{$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]]); - - const output = String.raw` - consts: function() { - ${i18n_0} - return [ - $i18n_0$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18n(1, 0); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(1); - $r3$.ɵɵi18nExp(ctx.valueA); - $r3$.ɵɵi18nApply(1); - } - } - `; - verify(input, output, {inputArgs: {interpolation: ['{%', '%}']}}); - }); - - it('should support interpolations with complex expressions', () => { - const input = ` -
- {{ valueA | async }} - {{ valueA?.a?.b }} - {{ valueA.getRawValue()?.getTitle() }} -
- `; - - const i18n_0 = i18nMsg(' {$interpolation} {$interpolation_1} {$interpolation_2} ', [ - ['interpolation', String.raw`\uFFFD0\uFFFD`], - ['interpolation_1', String.raw`\uFFFD1\uFFFD`], - ['interpolation_2', String.raw`\uFFFD2\uFFFD`] - ]); - - const output = String.raw` - consts: function() { - ${i18n_0} - return [ - $i18n_0$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18n(1, 0); - $r3$.ɵɵpipe(2, "async"); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - let $tmp_2_0$ = null; - $r3$.ɵɵadvance(2); - $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(2, 3, ctx.valueA)) - (ctx.valueA == null ? null : ctx.valueA.a == null ? null : ctx.valueA.a.b) - (($tmp_2_0$ = ctx.valueA.getRawValue()) == null ? null : $tmp_2_0$.getTitle()); - $r3$.ɵɵi18nApply(1); - } - } - `; - verify(input, output); - }); - - it('should handle i18n attributes with bindings in content', () => { - const input = ` -
My i18n block #{{ one }}
-
My i18n block #{{ two | uppercase }}
-
My i18n block #{{ three + four + five }}
- `; - - const i18n_0 = i18nMsg( - 'My i18n block #{$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]]); - const i18n_1 = i18nMsg( - 'My i18n block #{$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]]); - const i18n_2 = i18nMsg( - 'My i18n block #{$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]]); - - const output = String.raw` - decls: 7, - vars: 5, - consts: function() { - ${i18n_0} - ${i18n_1} - ${i18n_2} - return [ - $i18n_0$, - $i18n_1$, - $i18n_2$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18n(1, 0); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementStart(2, "div"); - $r3$.ɵɵi18n(3, 1); - $r3$.ɵɵpipe(4, "uppercase"); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementStart(5, "div"); - $r3$.ɵɵi18n(6, 2); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(1); - $r3$.ɵɵi18nExp(ctx.one); - $r3$.ɵɵi18nApply(1); - $r3$.ɵɵadvance(3); - $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(4, 3, ctx.two)); - $r3$.ɵɵi18nApply(3); - $r3$.ɵɵadvance(2); - $r3$.ɵɵi18nExp(ctx.three + ctx.four + ctx.five); - $r3$.ɵɵi18nApply(6); - } - } - `; - - verify(input, output); - }); - - it('should handle i18n attributes with bindings and nested elements in content', () => { - const input = ` -
- My i18n block #{{ one }} - Plain text in nested element -
-
- My i18n block #{{ two | uppercase }} -
-
- - More bindings in more nested element: {{ nestedInBlockTwo }} - -
-
-
- `; - - const i18n_0 = i18nMsg( - ' My i18n block #{$interpolation} {$startTagSpan}Plain text in nested element{$closeTagSpan}', - [ - ['interpolation', String.raw`\uFFFD0\uFFFD`], - ['startTagSpan', String.raw`\uFFFD#2\uFFFD`], - ['closeTagSpan', String.raw`\uFFFD/#2\uFFFD`] - ]); - const i18n_1 = i18nMsgWithPostprocess( - ' My i18n block #{$interpolation} {$startTagDiv}{$startTagDiv}{$startTagSpan} More bindings in more nested element: {$interpolation_1} {$closeTagSpan}{$closeTagDiv}{$closeTagDiv}', - [ - ['interpolation', String.raw`\uFFFD0\uFFFD`], - ['startTagDiv', String.raw`[\uFFFD#6\uFFFD|\uFFFD#7\uFFFD]`], - ['startTagSpan', String.raw`\uFFFD#8\uFFFD`], - ['interpolation_1', String.raw`\uFFFD1\uFFFD`], - ['closeTagSpan', String.raw`\uFFFD/#8\uFFFD`], - ['closeTagDiv', String.raw`[\uFFFD/#7\uFFFD|\uFFFD/#6\uFFFD]`] - ]); - - const output = String.raw` - decls: 9, - vars: 5, - consts: function() { - ${i18n_0} - ${i18n_1} - return [ - $i18n_0$, - $i18n_1$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18nStart(1, 0); - $r3$.ɵɵelement(2, "span"); - $r3$.ɵɵi18nEnd(); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementStart(3, "div"); - $r3$.ɵɵi18nStart(4, 1); - $r3$.ɵɵpipe(5, "uppercase"); - $r3$.ɵɵelementStart(6, "div"); - $r3$.ɵɵelementStart(7, "div"); - $r3$.ɵɵelement(8, "span"); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵi18nEnd(); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(2); - $r3$.ɵɵi18nExp(ctx.one); - $r3$.ɵɵi18nApply(1); - $r3$.ɵɵadvance(6); - $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(5, 3, ctx.two))(ctx.nestedInBlockTwo); - $r3$.ɵɵi18nApply(4); - } - } - `; - - verify(input, output); - }); - - it('should handle i18n attributes with bindings in content and element attributes', () => { - const input = ` -
- My i18n block #1 with value: {{ valueA }} - - Plain text in nested element (block #1) - -
-
- My i18n block #2 with value {{ valueD | uppercase }} - - Plain text in nested element (block #2) - -
- `; - - const i18n_0 = i18nMsg('Span title {$interpolation} and {$interpolation_1}', [ - ['interpolation', String.raw`\uFFFD0\uFFFD`], ['interpolation_1', String.raw`\uFFFD1\uFFFD`] - ]); - const i18n_1 = i18nMsg( - ' My i18n block #1 with value: {$interpolation} {$startTagSpan} Plain text in nested element (block #1) {$closeTagSpan}', - [ - ['interpolation', String.raw`\uFFFD0\uFFFD`], - ['startTagSpan', String.raw`\uFFFD#2\uFFFD`], - ['closeTagSpan', String.raw`\uFFFD/#2\uFFFD`] - ]); - const i18n_2 = - i18nMsg('Span title {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]]); - const i18n_3 = i18nMsg( - ' My i18n block #2 with value {$interpolation} {$startTagSpan} Plain text in nested element (block #2) {$closeTagSpan}', - [ - ['interpolation', String.raw`\uFFFD0\uFFFD`], - ['startTagSpan', String.raw`\uFFFD#7\uFFFD`], - ['closeTagSpan', String.raw`\uFFFD/#7\uFFFD`] - ]); - - const output = String.raw` - decls: 9, - vars: 7, - consts: function() { - ${i18n_0} - ${i18n_1} - ${i18n_2} - ${i18n_3} - return [ - $i18n_0$, - [${AttributeMarker.I18n}, "title"], - ["title", $i18n_1$], - $i18n_2$, - ["title", $i18n_3$] - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18nStart(1, 0); - $r3$.ɵɵelementStart(2, "span", 1); - $r3$.ɵɵi18nAttributes(3, 2); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵi18nEnd(); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementStart(4, "div"); - $r3$.ɵɵi18nStart(5, 3); - $r3$.ɵɵpipe(6, "uppercase"); - $r3$.ɵɵelementStart(7, "span", 1); - $r3$.ɵɵi18nAttributes(8, 4); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵi18nEnd(); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(2); - $r3$.ɵɵi18nExp(ctx.valueB)(ctx.valueC); - $r3$.ɵɵi18nApply(3); - $r3$.ɵɵadvance(1); - $r3$.ɵɵi18nExp(ctx.valueA); - $r3$.ɵɵi18nApply(1); - $r3$.ɵɵadvance(4); - $r3$.ɵɵi18nExp(ctx.valueE); - $r3$.ɵɵi18nApply(8); - $r3$.ɵɵadvance(1); - $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(6, 5, ctx.valueD)); - $r3$.ɵɵi18nApply(5); - } - } - `; - - verify(input, output); - }); - - it('should handle i18n attributes in nested templates', () => { - const input = ` -
- Some content -
-
- Some other content {{ valueA }} -
- More nested levels with bindings {{ valueB | uppercase }} -
-
-
-
- `; - - const i18n_0 = i18nMsg( - ' Some other content {$interpolation} {$startTagDiv} More nested levels with bindings {$interpolation_1} {$closeTagDiv}', - [ - ['interpolation', String.raw`\uFFFD0\uFFFD`], - ['startTagDiv', String.raw`\uFFFD#3\uFFFD`], - ['interpolation_1', String.raw`\uFFFD1\uFFFD`], - ['closeTagDiv', String.raw`\uFFFD/#3\uFFFD`] - ]); - - const output = String.raw` - function MyComponent_div_2_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵelementStart(1, "div"); - $r3$.ɵɵi18nStart(2, 1); - $r3$.ɵɵelement(3, "div"); - $r3$.ɵɵpipe(4, "uppercase"); - $r3$.ɵɵi18nEnd(); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - const $ctx_r0$ = $r3$.ɵɵnextContext(); - $r3$.ɵɵadvance(4); - $r3$.ɵɵi18nExp($ctx_r0$.valueA)($r3$.ɵɵpipeBind1(4, 2, $ctx_r0$.valueB)); - $r3$.ɵɵi18nApply(2); - } - } - … - decls: 3, - vars: 1, - consts: function() { - ${i18n_0} - return [ - [${AttributeMarker.Template}, "ngIf"], - $i18n_0$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵtext(1, " Some content "); - $r3$.ɵɵtemplate(2, MyComponent_div_2_Template, 5, 4, "div", 0); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(2); - $r3$.ɵɵproperty("ngIf", ctx.visible); - } - } - `; - - verify(input, output); - }); - - it('should ignore i18n attributes on self-closing tags', () => { - const input = ` - - - - `; - - const i18n_0 = - i18nMsg('App logo #{$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]]); - - const output = String.raw` - function MyComponent_img_1_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "img", 0); - } - } - … - function MyComponent_img_2_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "img", 3); - $r3$.ɵɵi18nAttributes(1, 4); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - const $ctx_r1$ = $r3$.ɵɵnextContext(); - $r3$.ɵɵi18nExp($ctx_r1$.id); - $r3$.ɵɵi18nApply(1); - } - } - … - decls: 3, - vars: 2, - consts: function() { - ${i18n_0} - return [ - ["src", "logo.png"], - ["src", "logo.png", ${AttributeMarker.Template}, "ngIf"], - ["src", "logo.png", ${AttributeMarker.Bindings}, "title", - ${AttributeMarker.Template}, "ngIf"], - ["src", "logo.png", ${AttributeMarker.I18n}, "title"], - ["title", $i18n_0$] - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "img", 0); - $r3$.ɵɵtemplate(1, MyComponent_img_1_Template, 1, 0, "img", 1); - $r3$.ɵɵtemplate(2, MyComponent_img_2_Template, 2, 1, "img", 2); - } - if (rf & 2) { - $r3$.ɵɵadvance(1); - $r3$.ɵɵproperty("ngIf", ctx.visible); - $r3$.ɵɵadvance(1); - $r3$.ɵɵproperty("ngIf", ctx.visible); - } - } - `; - - verify(input, output); - }); - - it('should handle i18n context in nested templates', () => { - const input = ` -
- Some content -
- Some other content {{ valueA }} -
- More nested levels with bindings {{ valueB | uppercase }} -
- Content inside sub-template {{ valueC }} -
- Bottom level element {{ valueD }} -
-
-
-
-
- Some other content {{ valueE + valueF }} -
- More nested levels with bindings {{ valueG | uppercase }} -
-
-
- `; - - const i18n_0 = i18nMsgWithPostprocess( - ' Some content {$startTagDiv_2} Some other content {$interpolation} {$startTagDiv} More nested levels with bindings {$interpolation_1} {$startTagDiv_1} Content inside sub-template {$interpolation_2} {$startTagDiv} Bottom level element {$interpolation_3} {$closeTagDiv}{$closeTagDiv}{$closeTagDiv}{$closeTagDiv}{$startTagDiv_3} Some other content {$interpolation_4} {$startTagDiv} More nested levels with bindings {$interpolation_5} {$closeTagDiv}{$closeTagDiv}', - [ - ['startTagDiv_2', String.raw`\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD`], - [ - 'closeTagDiv', - String - .raw`[\uFFFD/#2:2\uFFFD|\uFFFD/#1:2\uFFFD\uFFFD/*4:2\uFFFD|\uFFFD/#2:1\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD|\uFFFD/#2:3\uFFFD|\uFFFD/#1:3\uFFFD\uFFFD/*3:3\uFFFD]` - ], - ['startTagDiv_3', String.raw`\uFFFD*3:3\uFFFD\uFFFD#1:3\uFFFD`], - ['interpolation', String.raw`\uFFFD0:1\uFFFD`], - ['startTagDiv', String.raw`[\uFFFD#2:1\uFFFD|\uFFFD#2:2\uFFFD|\uFFFD#2:3\uFFFD]`], - ['interpolation_1', String.raw`\uFFFD1:1\uFFFD`], - ['startTagDiv_1', String.raw`\uFFFD*4:2\uFFFD\uFFFD#1:2\uFFFD`], - ['interpolation_2', String.raw`\uFFFD0:2\uFFFD`], - ['interpolation_3', String.raw`\uFFFD1:2\uFFFD`], - ['interpolation_4', String.raw`\uFFFD0:3\uFFFD`], - ['interpolation_5', String.raw`\uFFFD1:3\uFFFD`] - ]); - - const output = String.raw` - function MyComponent_div_2_div_4_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵi18nStart(0, 0, 2); - $r3$.ɵɵelementStart(1, "div"); - $r3$.ɵɵelement(2, "div"); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵi18nEnd(); - } - if (rf & 2) { - const $ctx_r2$ = $r3$.ɵɵnextContext(2); - $r3$.ɵɵadvance(2); - $r3$.ɵɵi18nExp($ctx_r2$.valueC)($ctx_r2$.valueD); - $r3$.ɵɵi18nApply(0); - } - } - function MyComponent_div_2_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵi18nStart(0, 0, 1); - $r3$.ɵɵelementStart(1, "div"); - $r3$.ɵɵelementStart(2, "div"); - $r3$.ɵɵpipe(3, "uppercase"); - $r3$.ɵɵtemplate(4, MyComponent_div_2_div_4_Template, 3, 2, "div", 1); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵi18nEnd(); - } - if (rf & 2) { - const $ctx_r0$ = $r3$.ɵɵnextContext(); - $r3$.ɵɵadvance(4); - $r3$.ɵɵproperty("ngIf", $ctx_r0$.exists); - $r3$.ɵɵi18nExp($ctx_r0$.valueA)($r3$.ɵɵpipeBind1(3, 3, $ctx_r0$.valueB)); - $r3$.ɵɵi18nApply(0); - } - } - … - function MyComponent_div_3_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵi18nStart(0, 0, 3); - $r3$.ɵɵelementStart(1, "div"); - $r3$.ɵɵelement(2, "div"); - $r3$.ɵɵpipe(3, "uppercase"); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵi18nEnd(); - } - if (rf & 2) { - const $ctx_r1$ = $r3$.ɵɵnextContext(); - $r3$.ɵɵadvance(3); - $r3$.ɵɵi18nExp($ctx_r1$.valueE + $ctx_r1$.valueF)($r3$.ɵɵpipeBind1(3, 2, $ctx_r1$.valueG)); - $r3$.ɵɵi18nApply(0); - } - } - … - decls: 4, - vars: 2, - consts: function() { - ${i18n_0} - return [ - $i18n_0$, - [${AttributeMarker.Template}, "ngIf"] - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18nStart(1, 0); - $r3$.ɵɵtemplate(2, MyComponent_div_2_Template, 5, 5, "div", 1); - $r3$.ɵɵtemplate(3, MyComponent_div_3_Template, 4, 4, "div", 1); - $r3$.ɵɵi18nEnd(); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(2); - $r3$.ɵɵproperty("ngIf", ctx.visible); - $r3$.ɵɵadvance(1); - $r3$.ɵɵproperty("ngIf", !ctx.visible); - } - } - `; - - verify(input, output); - }); - - it('should handle i18n attribute with directives', () => { - const input = ` -
Some other content {{ valueA }}
- `; - - const i18n_0 = i18nMsg('Some other content {$startTagSpan}{$interpolation}{$closeTagSpan}', [ - ['startTagSpan', String.raw`\uFFFD#2\uFFFD`], ['interpolation', String.raw`\uFFFD0\uFFFD`], - ['closeTagSpan', String.raw`\uFFFD/#2\uFFFD`] - ]); - - const output = String.raw` - function MyComponent_div_0_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18nStart(1, 1); - $r3$.ɵɵelement(2, "span"); - $r3$.ɵɵi18nEnd(); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - const $ctx_r0$ = $r3$.ɵɵnextContext(); - $r3$.ɵɵadvance(2); - $r3$.ɵɵi18nExp($ctx_r0$.valueA); - $r3$.ɵɵi18nApply(1); - } - } - … - decls: 1, - vars: 1, - consts: function() { - ${i18n_0} - return [ - [${AttributeMarker.Template}, "ngIf"], - $i18n_0$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵtemplate(0, MyComponent_div_0_Template, 3, 1, "div", 0); - } - if (rf & 2) { - $r3$.ɵɵproperty("ngIf", ctx.visible); - } - } - `; - - verify(input, output); - }); - - it('should generate event listeners instructions before i18n ones', () => { - const input = ` -
Hello
- `; - - const i18n_0 = i18nMsg('Hello'); - - const output = String.raw` - consts: function() { - ${i18n_0} - return [ - [${AttributeMarker.Bindings}, "click"], - $i18n_0$ - ]; - }, - 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$.ɵɵi18n(1, 1); - $r3$.ɵɵelementEnd(); - } - } - `; - - verify(input, output); - }); - }); - - describe('self-closing i18n instructions', () => { - it('should be generated with text-only content', () => { - const input = ` -
My i18n block #1
- `; - - const i18n_0 = i18nMsg('My i18n block #1'); - - const output = String.raw` - consts: function() { - ${i18n_0} - return [ - $i18n_0$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18n(1, 0); - $r3$.ɵɵelementEnd(); - } - } - `; - - verify(input, output); - }); - - it('should be generated for ICU-only i18n blocks', () => { - const input = ` -
{age, select, 10 {ten} 20 {twenty} other {other}}
- `; - - const i18n_0 = i18nIcuMsg( - '{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}', - [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]]); - - const output = String.raw` - decls: 2, - vars: 1, - consts: function() { - ${i18n_0} - return [ - $i18n_0$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18n(1, 0); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(1); - $r3$.ɵɵi18nExp(ctx.age); - $r3$.ɵɵi18nApply(1); - } - } - `; - - verify(input, output); - }); - - it('should be generated within and blocks', () => { - const input = ` - My i18n block #1 - My i18n block #2 - `; - - const i18n_0 = i18nMsg('My i18n block #2'); - const i18n_1 = i18nMsg('My i18n block #1'); - - const output = String.raw` - function MyComponent_ng_template_0_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵi18n(0, 1); - } - } - … - consts: function() { - ${i18n_0} - ${i18n_1} - return [ - $i18n_0$, - $i18n_1$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵtemplate(0, MyComponent_ng_template_0_Template, 1, 0, "ng-template"); - $r3$.ɵɵelementContainerStart(1); - $r3$.ɵɵi18n(2, 0); - $r3$.ɵɵelementContainerEnd(); - } - } - `; - - verify(input, output); - }); - - it('should not be generated in case we have styling instructions', () => { - const input = ` - Text #1 - Text #2 - `; - - const i18n_0 = i18nMsg('Text #1'); - const i18n_1 = i18nMsg('Text #2'); - - const output = String.raw` - decls: 4, - vars: 0, - consts: function() { - ${i18n_0} - ${i18n_1} - return [ - [${AttributeMarker.Classes}, "myClass"], - $i18n_0$, - [${AttributeMarker.Styles}, "padding", "10px"], - $i18n_1$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "span", 0); - $r3$.ɵɵi18n(1, 1); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementStart(2, "span", 2); - $r3$.ɵɵi18n(3, 3); - $r3$.ɵɵelementEnd(); - } - } - `; - - verify(input, output); - }); - }); - - describe('ng-container and ng-template', () => { - it('should handle single translation message using ', () => { - const input = ` - Some content: {{ valueA | uppercase }} - `; - - const i18n_0 = - i18nMsg('Some content: {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]]); - - const output = String.raw` - decls: 3, - vars: 3, - consts: function() { - ${i18n_0} - return [ - $i18n_0$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementContainerStart(0); - $r3$.ɵɵi18n(1, 0); - $r3$.ɵɵpipe(2, "uppercase"); - $r3$.ɵɵelementContainerEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(2); - $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(2, 1, ctx.valueA)); - $r3$.ɵɵi18nApply(1); - } - } - `; - - verify(input, output); - }); - - it('should handle single translation message using ', () => { - const input = ` - Some content: {{ valueA | uppercase }} - `; - - const i18n_0 = - i18nMsg('Some content: {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]]); - - const output = String.raw` - function MyComponent_ng_template_0_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵi18n(0, 0); - $r3$.ɵɵpipe(1, "uppercase"); - } if (rf & 2) { - const $ctx_r0$ = $r3$.ɵɵnextContext(); - $r3$.ɵɵadvance(1); - $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(1, 1, $ctx_r0$.valueA)); - $r3$.ɵɵi18nApply(0); - } - } - … - decls: 1, - vars: 0, - consts: function() { - ${i18n_0} - return [ - $i18n_0$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵtemplate(0, MyComponent_ng_template_0_Template, 2, 3, "ng-template"); - } - } - `; - - verify(input, output); - }); - - it('should be able to act as child elements inside i18n block', () => { - const input = ` -
- Template content: {{ valueA | uppercase }} - Container content: {{ valueB | uppercase }} -
- `; - - const i18n_0 = i18nMsg( - '{$startTagNgTemplate}Template content: {$interpolation}{$closeTagNgTemplate}{$startTagNgContainer}Container content: {$interpolation_1}{$closeTagNgContainer}', - [ - ['startTagNgTemplate', String.raw`\uFFFD*2:1\uFFFD`], - ['closeTagNgTemplate', String.raw`\uFFFD/*2:1\uFFFD`], - ['startTagNgContainer', String.raw`\uFFFD#3\uFFFD`], - ['interpolation_1', String.raw`\uFFFD0\uFFFD`], - ['closeTagNgContainer', String.raw`\uFFFD/#3\uFFFD`], - ['interpolation', String.raw`\uFFFD0:1\uFFFD`] - ]); - - const output = String.raw` - function MyComponent_ng_template_2_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵi18n(0, 0, 1); - $r3$.ɵɵpipe(1, "uppercase"); - } - if (rf & 2) { - const $ctx_r0$ = $r3$.ɵɵnextContext(); - $r3$.ɵɵadvance(1); - $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(1, 1, $ctx_r0$.valueA)); - $r3$.ɵɵi18nApply(0); - } - } - … - decls: 5, - vars: 3, - consts: function() { - ${i18n_0} - return [ - $i18n_0$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18nStart(1, 0); - $r3$.ɵɵtemplate(2, MyComponent_ng_template_2_Template, 2, 3, "ng-template"); - $r3$.ɵɵelementContainer(3); - $r3$.ɵɵpipe(4, "uppercase"); - $r3$.ɵɵi18nEnd(); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(4); - $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(4, 1, ctx.valueB)); - $r3$.ɵɵi18nApply(1); - } - } - `; - - verify(input, output); - }); - - it('should handle ICUs outside of translatable sections', () => { - const input = ` - {gender, select, male {male} female {female} other {other}} - {age, select, 10 {ten} 20 {twenty} other {other}} - `; - - const i18n_0 = i18nIcuMsg( - '{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}', - [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]]); - const i18n_1 = i18nIcuMsg( - '{VAR_SELECT, select, male {male} female {female} other {other}}', - [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]]); - - const output = String.raw` - function MyComponent_ng_template_0_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵi18n(0, 1); - } - if (rf & 2) { - const $ctx_r0$ = $r3$.ɵɵnextContext(); - $r3$.ɵɵi18nExp($ctx_r0$.gender); - $r3$.ɵɵi18nApply(0); - } - } - … - decls: 3, - vars: 1, - consts: function() { - ${i18n_0} - ${i18n_1} - return [ - $i18n_0$, - $i18n_1$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵtemplate(0, MyComponent_ng_template_0_Template, 1, 1, "ng-template"); - $r3$.ɵɵelementContainerStart(1); - $r3$.ɵɵi18n(2, 0); - $r3$.ɵɵelementContainerEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(2); - $r3$.ɵɵi18nExp(ctx.age); - $r3$.ɵɵi18nApply(2); - } - } - `; - - verify(input, output); - }); - - it('should correctly propagate i18n context through nested templates', () => { - const input = ` -
- - Template A: {{ valueA | uppercase }} - - Template B: {{ valueB }} - - Template C: {{ valueC }} - - - -
- `; - - const i18n_0 = i18nMsgWithPostprocess( - '{$startTagNgTemplate} Template A: {$interpolation} {$startTagNgTemplate} Template B: {$interpolation_1} {$startTagNgTemplate} Template C: {$interpolation_2} {$closeTagNgTemplate}{$closeTagNgTemplate}{$closeTagNgTemplate}', - [ - [ - 'startTagNgTemplate', String.raw`[\uFFFD*2:1\uFFFD|\uFFFD*2:2\uFFFD|\uFFFD*1:3\uFFFD]` - ], - [ - 'closeTagNgTemplate', - String.raw`[\uFFFD/*1:3\uFFFD|\uFFFD/*2:2\uFFFD|\uFFFD/*2:1\uFFFD]` - ], - ['interpolation', String.raw`\uFFFD0:1\uFFFD`], - ['interpolation_1', String.raw`\uFFFD0:2\uFFFD`], - ['interpolation_2', String.raw`\uFFFD0:3\uFFFD`] - ]); - - const output = String.raw` - function MyComponent_ng_template_2_ng_template_2_ng_template_1_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵi18n(0, 0, 3); - } - if (rf & 2) { - const $ctx_r2$ = $r3$.ɵɵnextContext(3); - $r3$.ɵɵi18nExp($ctx_r2$.valueC); - $r3$.ɵɵi18nApply(0); - } - } - function MyComponent_ng_template_2_ng_template_2_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵi18nStart(0, 0, 2); - $r3$.ɵɵtemplate(1, MyComponent_ng_template_2_ng_template_2_ng_template_1_Template, 1, 1, "ng-template"); - $r3$.ɵɵi18nEnd(); - } - if (rf & 2) { - const $ctx_r1$ = $r3$.ɵɵnextContext(2); - $r3$.ɵɵadvance(1); - $r3$.ɵɵi18nExp($ctx_r1$.valueB); - $r3$.ɵɵi18nApply(0); - } - } - … - function MyComponent_ng_template_2_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵi18nStart(0, 0, 1); - $r3$.ɵɵpipe(1, "uppercase"); - $r3$.ɵɵtemplate(2, MyComponent_ng_template_2_ng_template_2_Template, 2, 1, "ng-template"); - $r3$.ɵɵi18nEnd(); - } - if (rf & 2) { - const $ctx_r0$ = $r3$.ɵɵnextContext(); - $r3$.ɵɵadvance(2); - $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(1, 1, $ctx_r0$.valueA)); - $r3$.ɵɵi18nApply(0); - } - } - … - decls: 3, - vars: 0, - consts: function() { - ${i18n_0} - return [ - $i18n_0$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18nStart(1, 0); - $r3$.ɵɵtemplate(2, MyComponent_ng_template_2_Template, 3, 3, "ng-template"); - $r3$.ɵɵi18nEnd(); - $r3$.ɵɵelementEnd(); - } - } - `; - - verify(input, output); - }); - - it('should work with ICUs', () => { - const input = ` - {gender, select, male {male} female {female} other {other}} - {age, select, 10 {ten} 20 {twenty} other {other}} - `; - - const i18n_0 = i18nIcuMsg( - '{VAR_SELECT, select, male {male} female {female} other {other}}', - [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]]); - const i18n_1 = i18nIcuMsg( - '{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}', - [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]]); - - const output = String.raw` - function MyComponent_ng_template_2_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵi18n(0, 1); - } - if (rf & 2) { - const $ctx_r0$ = $r3$.ɵɵnextContext(); - $r3$.ɵɵi18nExp($ctx_r0$.age); - $r3$.ɵɵi18nApply(0); - } - } - … - decls: 3, - vars: 1, - consts: function() { - ${i18n_0} - ${i18n_1} - return [ - $i18n_0$, - $i18n_1$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementContainerStart(0); - $r3$.ɵɵi18n(1, 0); - $r3$.ɵɵelementContainerEnd(); - $r3$.ɵɵtemplate(2, MyComponent_ng_template_2_Template, 1, 1, "ng-template"); - } - if (rf & 2) { - $r3$.ɵɵadvance(1); - $r3$.ɵɵi18nExp(ctx.gender); - $r3$.ɵɵi18nApply(1); - } - } - `; - - verify(input, output); - }); - - it('should handle self-closing tags as content', () => { - const input = ` - - is my logo #1 - - - is my logo #2 - - `; - - const i18n_0 = i18nMsg( - '{$tagImg} is my logo #1 ', [['tagImg', String.raw`\uFFFD#2\uFFFD\uFFFD/#2\uFFFD`]]); - const i18n_1 = i18nMsg( - '{$tagImg} is my logo #2 ', [['tagImg', String.raw`\uFFFD#1\uFFFD\uFFFD/#1\uFFFD`]]); - - const output = String.raw` - function MyComponent_ng_template_3_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵi18nStart(0, 2); - $r3$.ɵɵelement(1, "img", 1); - $r3$.ɵɵi18nEnd(); - } - } - … - consts: function() { - ${i18n_0} - ${i18n_1} - return [ - $i18n_0$, - ["src", "logo.png", "title", "Logo"], - $i18n_1$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementContainerStart(0); - $r3$.ɵɵi18nStart(1, 0); - $r3$.ɵɵelement(2, "img", 1); - $r3$.ɵɵi18nEnd(); - $r3$.ɵɵelementContainerEnd(); - $r3$.ɵɵtemplate(3, MyComponent_ng_template_3_Template, 2, 0, "ng-template"); - } - } - `; - - verify(input, output); - }); - - it('should not emit duplicate i18n consts for nested s', () => { - const input = ` - - Root content - - Nested content - - - `; - - const output = - i18nMsg(' Root content {$startTagNgContainer} Nested content {$closeTagNgContainer}', [ - ['startTagNgContainer', String.raw`\uFFFD*1:1\uFFFD\uFFFD#1:1\uFFFD`], - ['closeTagNgContainer', String.raw`\uFFFD/#1:1\uFFFD\uFFFD/*1:1\uFFFD`] - ]); - - verify(input, output); - }); - - it('should not emit duplicate i18n consts for elements with the same content', () => { - const input = ` -
Test
-
Test
- `; - - // TODO(FW-635): currently we generate unique consts for each i18n block even though it - // might contain the same content. This should be optimized by translation statements caching, - // that can be implemented in the future. - const output = String.raw` - ${i18nMsg('Test')} - ${i18nMsg('Test')} - `; - - verify(input, output); - }); - - it('should generate a self-closing container instruction for ng-container inside i18n', () => { - const input = ` -
- Hello there -
- `; - - const i18n_0 = i18nMsg(' Hello {$startTagNgContainer}there{$closeTagNgContainer}', [ - ['startTagNgContainer', String.raw`\uFFFD#2\uFFFD`], - ['closeTagNgContainer', String.raw`\uFFFD/#2\uFFFD`] - ]); - - const output = String.raw` - decls: 3, - vars: 0, - consts: function() { - ${i18n_0} - return [ - $i18n_0$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18nStart(1, 0); - $r3$.ɵɵelementContainer(2); - $r3$.ɵɵi18nEnd(); - $r3$.ɵɵelementEnd(); - } - } - `; - - verify(input, output); - }); - - it('should not generate a self-closing container instruction for ng-container with non-text content inside i18n', - () => { - const input = ` -
- Hello there ! -
- `; - - const i18n_0 = i18nMsg( - ' Hello {$startTagNgContainer}there {$startTagStrong}!{$closeTagStrong}{$closeTagNgContainer}', - [ - ['startTagNgContainer', String.raw`\uFFFD#2\uFFFD`], - ['startTagStrong', String.raw`\uFFFD#3\uFFFD`], - ['closeTagStrong', String.raw`\uFFFD/#3\uFFFD`], - ['closeTagNgContainer', String.raw`\uFFFD/#2\uFFFD`] - ]); - - const output = String.raw` - decls: 4, - vars: 0, - consts: function() { - ${i18n_0} - return [ - $i18n_0$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18nStart(1, 0); - $r3$.ɵɵelementContainerStart(2); - $r3$.ɵɵelement(3, "strong"); - $r3$.ɵɵelementContainerEnd(); - $r3$.ɵɵi18nEnd(); - $r3$.ɵɵelementEnd(); - } - } - `; - - verify(input, output); - }); - - // Note: applying structural directives to is typically user error, - // but it is technically allowed, so we need to support it. - it('should handle structural directives', () => { - const input = ` - Content A - Content B - `; - - const i18n_0 = i18nMsg('Content A'); - const i18n_1 = i18nMsg('Content B'); - - const output = String.raw` - function MyComponent_0_ng_template_0_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵi18n(0, 1); - } - } - function MyComponent_0_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵtemplate(0, MyComponent_0_ng_template_0_Template, 1, 0, "ng-template"); - } - } - … - function MyComponent_ng_container_1_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementContainerStart(0); - $r3$.ɵɵi18n(1, 2); - $r3$.ɵɵelementContainerEnd(); - } - } - … - decls: 2, - vars: 2, - consts: function() { - ${i18n_0} - ${i18n_1} - return [ - [${AttributeMarker.Template}, "ngIf"], - $i18n_0$, - $i18n_1$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵtemplate(0, MyComponent_0_Template, 1, 0, undefined, 0); - $r3$.ɵɵtemplate(1, MyComponent_ng_container_1_Template, 2, 0, "ng-container", 0); - } - if (rf & 2) { - $r3$.ɵɵproperty("ngIf", ctx.someFlag); - $r3$.ɵɵadvance(1); - $r3$.ɵɵproperty("ngIf", ctx.someFlag); - } - } - `; - verify(input, output); - }); - }); - - describe('whitespace preserving mode', () => { - it('should keep inner content of i18n block as is', () => { - const input = ` -
- Some text - Text inside span -
- `; - - // Keeping raw content (avoiding `i18nMsg`) to illustrate message layout - // in case of whitespace preserving mode. - const i18n_0 = String.raw` - let $I18N_0$; - if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { - const $MSG_EXTERNAL_963542717423364282$$APP_SPEC_TS_0$ = goog.getMsg("\n Some text\n {$startTagSpan}Text inside span{$closeTagSpan}\n ", { - "startTagSpan": "\uFFFD#3\uFFFD", - "closeTagSpan": "\uFFFD/#3\uFFFD" - }); - $I18N_0$ = $MSG_EXTERNAL_963542717423364282$$APP_SPEC_TS_0$; - } - else { - $I18N_0$ = $localize \` - Some text - $` + - String.raw`{"\uFFFD#3\uFFFD"}:START_TAG_SPAN:Text inside span$` + - String.raw`{"\uFFFD/#3\uFFFD"}:CLOSE_TAG_SPAN: - \`; - } - `; - - const output = String.raw` - consts: function() { - ${i18n_0} - return [ - $i18n_0$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵtext(0, "\n "); - $r3$.ɵɵelementStart(1, "div"); - $r3$.ɵɵi18nStart(2, 0); - $r3$.ɵɵelement(3, "span"); - $r3$.ɵɵi18nEnd(); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵtext(4, "\n "); - } - } - `; - - verify(input, output, {inputArgs: {preserveWhitespaces: true}}); - }); - }); - - describe('icu logic', () => { - it('should handle single icus', () => { - const input = ` -
{gender, select, male {male} female {female} other {other}}
- `; - - const i18n_0 = i18nIcuMsg( - '{VAR_SELECT, select, male {male} female {female} other {other}}', - [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]]); - - const output = String.raw` - decls: 2, - vars: 1, - consts: function() { - ${i18n_0} - return [ - $i18n_0$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18n(1, 0); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(1); - $r3$.ɵɵi18nExp(ctx.gender); - $r3$.ɵɵi18nApply(1); - } - } - `; - - verify(input, output); - }); - - it('should properly escape quotes in content', () => { - const input = ` -
{gender, select, single {'single quotes'} double {"double quotes"} other {other}}
- `; - - const output = String.raw` - let $I18N_0$; - if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { - const $MSG_EXTERNAL_4166854826696768832$$APP_SPEC_TS_0$ = goog.getMsg("{VAR_SELECT, select, single {'single quotes'} double {\"double quotes\"} other {other}}"); - $I18N_0$ = $MSG_EXTERNAL_4166854826696768832$$APP_SPEC_TS_0$; - } - else { - $I18N_0$ = $localize \`{VAR_SELECT, select, single {'single quotes'} double {"double quotes"} other {other}}\`; - } - $I18N_0$ = $r3$.ɵɵi18nPostprocess($I18N_0$, { - "VAR_SELECT": "\uFFFD0\uFFFD" - }); - `; - - verify(input, output); - }); - - it('should support ICU-only templates', () => { - const input = ` - {age, select, 10 {ten} 20 {twenty} other {other}} - `; - - const i18n_0 = i18nIcuMsg( - '{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}', - [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]]); - - const output = String.raw` - decls: 1, - vars: 1, - consts: function() { - ${i18n_0} - return [ - $i18n_0$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵi18n(0, 0); - } - if (rf & 2) { - $r3$.ɵɵi18nExp(ctx.age); - $r3$.ɵɵi18nApply(0); - } - } - `; - - verify(input, output); - }); - - it('should generate i18n instructions for icus generated outside of i18n blocks', () => { - const input = ` -
{gender, select, male {male} female {female} other {other}}
-
- {age, select, 10 {ten} 20 {twenty} other {other}} -
-
- You have {count, select, 0 {no emails} 1 {one email} other {{{count}} emails}}. -
- `; - - const i18n_0 = i18nIcuMsg( - '{VAR_SELECT, select, male {male} female {female} other {other}}', - [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]]); - const i18n_1 = i18nIcuMsg( - '{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}', - [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]]); - const i18n_2 = i18nIcuMsg( - '{VAR_SELECT, select, 0 {no emails} 1 {one email} other {{INTERPOLATION} emails}}', [ - ['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['INTERPOLATION', String.raw`\uFFFD1\uFFFD`] - ]); - - const output = String.raw` - function MyComponent_div_2_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div", 3); - $r3$.ɵɵtext(1, " "); - $r3$.ɵɵi18n(2, 4); - $r3$.ɵɵtext(3, " "); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - const $ctx_r0$ = $r3$.ɵɵnextContext(); - $r3$.ɵɵadvance(2); - $r3$.ɵɵi18nExp($ctx_r0$.age); - $r3$.ɵɵi18nApply(2); - } - } - … - function MyComponent_div_3_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div", 5); - $r3$.ɵɵtext(1, " You have "); - $r3$.ɵɵi18n(2, 6); - $r3$.ɵɵtext(3, ". "); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - const $ctx_r1$ = $r3$.ɵɵnextContext(); - $r3$.ɵɵadvance(2); - $r3$.ɵɵi18nExp($ctx_r1$.count)($ctx_r1$.count); - $r3$.ɵɵi18nApply(2); - } - } - … - decls: 4, - vars: 3, - consts: function() { - ${i18n_0} - ${i18n_1} - ${i18n_2} - return [ - $i18n_0$, - ["title", "icu only", ${AttributeMarker.Template}, "ngIf"], - ["title", "icu and text", ${AttributeMarker.Template}, "ngIf"], - ["title", "icu only"], - $i18n_1$, - ["title", "icu and text"], - $i18n_2$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18n(1, 0); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵtemplate(2, MyComponent_div_2_Template, 4, 1, "div", 1); - $r3$.ɵɵtemplate(3, MyComponent_div_3_Template, 4, 2, "div", 2); - } - if (rf & 2) { - $r3$.ɵɵadvance(1); - $r3$.ɵɵi18nExp(ctx.gender); - $r3$.ɵɵi18nApply(1); - $r3$.ɵɵadvance(1); - $r3$.ɵɵproperty("ngIf", ctx.visible); - $r3$.ɵɵadvance(1); - $r3$.ɵɵproperty("ngIf", ctx.available); - } - } - `; - - verify(input, output); - }); - - it('should support interpolation with custom interpolation config', () => { - const input = ` -
{age, select, 10 {ten} 20 {twenty} other {{% other %}}}
- `; - - const i18n_0 = - i18nIcuMsg('{VAR_SELECT, select, 10 {ten} 20 {twenty} other {{INTERPOLATION}}}', [ - ['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['INTERPOLATION', String.raw`\uFFFD1\uFFFD`] - ]); - - const output = String.raw` - consts: function() { - ${i18n_0} - return [ - $i18n_0$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18n(1, 0); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(1); - $r3$.ɵɵi18nExp(ctx.age)(ctx.other); - $r3$.ɵɵi18nApply(1); - } - } - `; - - verify(input, output, {inputArgs: {interpolation: ['{%', '%}']}}); - }); - - it('should handle icus with html', () => { - const input = ` -
- {gender, select, male {male - male} female {female female} other {
other
}} - Other content -
Another content
-
- `; - - const i18n_0 = i18nIcuMsg( - '{VAR_SELECT, select, male {male - {START_BOLD_TEXT}male{CLOSE_BOLD_TEXT}} female {female {START_BOLD_TEXT}female{CLOSE_BOLD_TEXT}} other {{START_TAG_DIV}{START_ITALIC_TEXT}other{CLOSE_ITALIC_TEXT}{CLOSE_TAG_DIV}}}', - [ - ['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], - ['START_BOLD_TEXT', ''], - ['CLOSE_BOLD_TEXT', ''], - ['START_ITALIC_TEXT', ''], - ['CLOSE_ITALIC_TEXT', ''], - ['START_TAG_DIV', '
'], - ['CLOSE_TAG_DIV', '
'], - ]); - - const i18n_1 = i18nMsg( - ' {$icu} {$startBoldText}Other content{$closeBoldText}{$startTagDiv}{$startItalicText}Another content{$closeItalicText}{$closeTagDiv}', - [ - ['startBoldText', String.raw`\uFFFD#2\uFFFD`], - ['closeBoldText', String.raw`\uFFFD/#2\uFFFD`], - ['startTagDiv', String.raw`\uFFFD#3\uFFFD`], - ['startItalicText', String.raw`\uFFFD#4\uFFFD`], - ['closeItalicText', String.raw`\uFFFD/#4\uFFFD`], - ['closeTagDiv', String.raw`\uFFFD/#3\uFFFD`], - ['icu', '$I18N_0$'], - ]); - - const output = String.raw` - decls: 5, - vars: 1, - consts: function() { - ${i18n_0} - ${i18n_1} - return [ - $i18n_1$, - [${AttributeMarker.Classes}, "other"] - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18nStart(1, 0); - $r3$.ɵɵelement(2, "b"); - $r3$.ɵɵelementStart(3, "div", 1); - $r3$.ɵɵelement(4, "i"); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵi18nEnd(); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(4); - $r3$.ɵɵi18nExp(ctx.gender); - $r3$.ɵɵi18nApply(1); - } - } - `; - - verify(input, output); - }); - - it('should handle icus with expressions', () => { - const input = ` -
{gender, select, male {male of age: {{ ageA + ageB + ageC }}} female {female} other {other}}
- `; - - const i18n_0 = i18nIcuMsg( - '{VAR_SELECT, select, male {male of age: {INTERPOLATION}} female {female} other {other}}', - [ - ['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], - ['INTERPOLATION', String.raw`\uFFFD1\uFFFD`], - ]); - - const output = String.raw` - decls: 2, - vars: 2, - consts: function() { - ${i18n_0} - return [ - $i18n_0$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18n(1, 0); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(1); - $r3$.ɵɵi18nExp(ctx.gender)(ctx.ageA + ctx.ageB + ctx.ageC); - $r3$.ɵɵi18nApply(1); - } - } - `; - - verify(input, output); - }); - - it('should handle multiple icus in one block', () => { - const input = ` -
- {gender, select, male {male} female {female} other {other}} - {age, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}} -
- `; - - const i18n_0 = i18nIcuMsg( - '{VAR_SELECT, select, male {male} female {female} other {other}}', - [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]]); - const i18n_1 = i18nIcuMsg( - '{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}', - [['VAR_SELECT', String.raw`\uFFFD1\uFFFD`]]); - const i18n_2 = i18nMsg(' {$icu} {$icu_1} ', [ - ['icu', '$i18n_0$'], - ['icu_1', '$i18n_1$'], - ]); - - const output = String.raw` - decls: 2, - vars: 2, - consts: function() { - ${i18n_0} - ${i18n_1} - ${i18n_2} - return [ - $i18n_2$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18n(1, 0); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(1); - $r3$.ɵɵi18nExp(ctx.gender)(ctx.age); - $r3$.ɵɵi18nApply(1); - } - } - `; - - verify(input, output); - }); - - it('should handle multiple icus that share same placeholder', () => { - const input = ` -
- {gender, select, male {male} female {female} other {other}} -
- {gender, select, male {male} female {female} other {other}} -
-
- {gender, select, male {male} female {female} other {other}} -
-
- `; - - // Keeping raw content here to illustrate the difference in placeholders generated for - // goog.getMsg and $localize calls (see last i18n block). - const i18n_0 = String.raw` - let $I18N_1$; - if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { - const $MSG_APP_SPEC_TS_1$ = goog.getMsg("{VAR_SELECT, select, male {male} female {female} other {other}}"); - $I18N_1$ = $MSG_APP_SPEC_TS_1$; - } - else { - $I18N_1$ = $localize \`{VAR_SELECT, select, male {male} female {female} other {other}}\`; - } - $I18N_1$ = $r3$.ɵɵi18nPostprocess($I18N_1$, { - "VAR_SELECT": "\uFFFD0\uFFFD" - }); - let $I18N_2$; - if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { - const $MSG_APP_SPEC_TS_2$ = goog.getMsg("{VAR_SELECT, select, male {male} female {female} other {other}}"); - $I18N_2$ = $MSG_APP_SPEC_TS_2$; - } - else { - $I18N_2$ = $localize \`{VAR_SELECT, select, male {male} female {female} other {other}}\`; - } - $I18N_2$ = $r3$.ɵɵi18nPostprocess($I18N_2$, { - "VAR_SELECT": "\uFFFD1\uFFFD" - }); - let $I18N_4$; - if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { - const $MSG_APP_SPEC_TS__4$ = goog.getMsg("{VAR_SELECT, select, male {male} female {female} other {other}}"); - $I18N_4$ = $MSG_APP_SPEC_TS__4$; - } - else { - $I18N_4$ = $localize \`{VAR_SELECT, select, male {male} female {female} other {other}}\`; - } - $I18N_4$ = $r3$.ɵɵi18nPostprocess($I18N_4$, { - "VAR_SELECT": "\uFFFD0:1\uFFFD" - }); - let $I18N_0$; - if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { - const $MSG_APP_SPEC_TS_0$ = goog.getMsg(" {$icu} {$startTagDiv} {$icu} {$closeTagDiv}{$startTagDiv_1} {$icu} {$closeTagDiv}", { - "startTagDiv": "\uFFFD#2\uFFFD", - "closeTagDiv": "[\uFFFD/#2\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*3:1\uFFFD]", - "startTagDiv_1": "\uFFFD*3:1\uFFFD\uFFFD#1:1\uFFFD", - "icu": "\uFFFDI18N_EXP_ICU\uFFFD" - }); - $I18N_0$ = $MSG_APP_SPEC_TS_0$; - } - else { - $I18N_0$ = $localize \` $` + - String.raw`{"\uFFFDI18N_EXP_ICU\uFFFD"}:ICU: $` + - String.raw`{"\uFFFD#2\uFFFD"}:START_TAG_DIV: $` + - String.raw`{"\uFFFDI18N_EXP_ICU\uFFFD"}:ICU: $` + String.raw - `{"[\uFFFD/#2\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*3:1\uFFFD]"}:CLOSE_TAG_DIV:$` + - String.raw`{"\uFFFD*3:1\uFFFD\uFFFD#1:1\uFFFD"}:START_TAG_DIV_1: $` + - String.raw`{"\uFFFDI18N_EXP_ICU\uFFFD"}:ICU: $` + String.raw - `{"[\uFFFD/#2\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*3:1\uFFFD]"}:CLOSE_TAG_DIV:\`; - } - $I18N_0$ = $r3$.ɵɵi18nPostprocess($I18N_0$, { - "ICU": [$I18N_1$, $I18N_2$, $I18N_4$] - }); - `; - - const output = String.raw` - function MyComponent_div_3_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵi18nStart(0, 0, 1); - $r3$.ɵɵelement(1, "div"); - $r3$.ɵɵi18nEnd(); - } - if (rf & 2) { - const $ctx_r0$ = $r3$.ɵɵnextContext(); - $r3$.ɵɵadvance(1); - $r3$.ɵɵi18nExp($ctx_r0$.gender); - $r3$.ɵɵi18nApply(0); - } - } - … - decls: 4, - vars: 3, - consts: function() { - ${i18n_0} - return [ - $i18n_0$, - [${AttributeMarker.Template}, "ngIf"] - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18nStart(1, 0); - $r3$.ɵɵelement(2, "div"); - $r3$.ɵɵtemplate(3, MyComponent_div_3_Template, 2, 1, "div", 1); - $r3$.ɵɵi18nEnd(); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(3); - $r3$.ɵɵproperty("ngIf", ctx.visible); - $r3$.ɵɵi18nExp(ctx.gender)(ctx.gender); - $r3$.ɵɵi18nApply(1); - } - } - `; - - // TODO(FW-635): this use-case is currently supported with - // file-based prefix for translation const names. Translation statements - // caching is required to support this use-case with id-based consts. - verify(input, output, {skipIdBasedCheck: true}); - }); - - it('should handle nested icus', () => { - const input = ` -
- {gender, select, - male {male of age: {age, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}} - female {female} - other {other} - } -
- `; - - const i18n_0 = i18nIcuMsg( - '{VAR_SELECT_1, select, male {male of age: {VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}} female {female} other {other}}', - [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['VAR_SELECT_1', String.raw`\uFFFD1\uFFFD`]]); - const i18n_1 = i18nMsg(' {$icu} ', [['icu', '$i18n_0$']]); - - const output = String.raw` - decls: 2, - vars: 2, - consts: function() { - ${i18n_0} - ${i18n_1} - return [ - $i18n_1$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18n(1, 0); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(1); - $r3$.ɵɵi18nExp(ctx.age)(ctx.gender); - $r3$.ɵɵi18nApply(1); - } - } - `; - - const exceptions = { - '3052001905251380936': - 'Wrapper message generated by "ng extract-i18n" around ICU: " {$ICU} "' - }; - verify(input, output, {exceptions}); - }); - - it('nested with interpolations in "other" blocks', () => { - const input = ` -
{count, plural, - =0 {zero} - =2 {{{count}} {name, select, - cat {cats} - dog {dogs} - other {animals}} !} - other {other - {{count}}} - }
- `; - - const i18n_0 = i18nIcuMsg( - '{VAR_PLURAL, plural, =0 {zero} =2 {{INTERPOLATION} {VAR_SELECT, select, cat {cats} dog {dogs} other {animals}} !} other {other - {INTERPOLATION}}}', - [ - ['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], - ['VAR_PLURAL', String.raw`\uFFFD1\uFFFD`], - ['INTERPOLATION', String.raw`\uFFFD2\uFFFD`], - ]); - - const output = String.raw` - decls: 2, - vars: 3, - consts: function() { - ${i18n_0} - return [ - $i18n_0$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18n(1, 0); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(1); - $r3$.ɵɵi18nExp(ctx.name)(ctx.count)(ctx.count); - $r3$.ɵɵi18nApply(1); - } - } - `; - - verify(input, output); - }); - - it('should handle icus in different contexts', () => { - const input = ` -
- {gender, select, male {male} female {female} other {other}} - - {age, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}} - -
- `; - - const i18n_0 = i18nIcuMsg( - '{VAR_SELECT, select, male {male} female {female} other {other}}', - [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]]); - const i18n_1 = i18nIcuMsg( - '{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}', - [['VAR_SELECT', String.raw`\uFFFD0:1\uFFFD`]]); - const i18n_2 = i18nMsg(' {$icu} {$startTagSpan} {$icu_1} {$closeTagSpan}', [ - ['startTagSpan', String.raw`\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD`], - ['closeTagSpan', String.raw`\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD`], - ['icu', '$i18n_0$'], - ['icu_1', '$i18n_1$'], - ]); - - const output = String.raw` - function MyComponent_span_2_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵi18nStart(0, 0, 1); - $r3$.ɵɵelement(1, "span"); - $r3$.ɵɵi18nEnd(); - } - if (rf & 2) { - const $ctx_r0$ = $r3$.ɵɵnextContext(); - $r3$.ɵɵadvance(1); - $r3$.ɵɵi18nExp($ctx_r0$.age); - $r3$.ɵɵi18nApply(0); - } - } - … - decls: 3, - vars: 2, - consts: function() { - ${i18n_0} - ${i18n_1} - ${i18n_2} - return [ - $i18n_2$, - [${AttributeMarker.Template}, "ngIf"] - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18nStart(1, 0); - $r3$.ɵɵtemplate(2, MyComponent_span_2_Template, 2, 1, "span", 1); - $r3$.ɵɵi18nEnd(); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(2); - $r3$.ɵɵproperty("ngIf", ctx.ageVisible); - $r3$.ɵɵi18nExp(ctx.gender); - $r3$.ɵɵi18nApply(1); - } - } - `; - - verify(input, output); - }); - - it('should handle icus with interpolations', () => { - const input = ` -
- {gender, select, male {male {{ weight }}} female {female {{ height }}} other {other}} - - {age, select, 10 {ten} 20 {twenty} 30 {thirty} other {other: {{ otherAge }}}} - -
- `; - - const i18n_0 = i18nIcuMsg( - '{VAR_SELECT, select, male {male {INTERPOLATION}} female {female {INTERPOLATION_1}} other {other}}', - [ - ['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], - ['INTERPOLATION', String.raw`\uFFFD1\uFFFD`], - ['INTERPOLATION_1', String.raw`\uFFFD2\uFFFD`], - ]); - const i18n_1 = i18nIcuMsg( - '{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other: {INTERPOLATION}}}', [ - ['VAR_SELECT', String.raw`\uFFFD0:1\uFFFD`], - ['INTERPOLATION', String.raw`\uFFFD1:1\uFFFD`], - ]); - const i18n_2 = i18nMsg(' {$icu} {$startTagSpan} {$icu_1} {$closeTagSpan}', [ - ['startTagSpan', String.raw`\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD`], - ['closeTagSpan', String.raw`\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD`], - ['icu', '$i18n_0$'], - ['icu_1', '$i18n_1$'], - ]); - - const output = String.raw` - function MyComponent_span_2_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵi18nStart(0, 0, 1); - $r3$.ɵɵelement(1, "span"); - $r3$.ɵɵi18nEnd(); - } - if (rf & 2) { - const $ctx_r0$ = $r3$.ɵɵnextContext(); - $r3$.ɵɵadvance(1); - $r3$.ɵɵi18nExp($ctx_r0$.age)($ctx_r0$.otherAge); - $r3$.ɵɵi18nApply(0); - } - } - … - decls: 3, - vars: 4, - consts: function() { - ${i18n_0} - ${i18n_1} - ${i18n_2} - return [ - $i18n_2$, - [${AttributeMarker.Template}, "ngIf"] - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18nStart(1, 0); - $r3$.ɵɵtemplate(2, MyComponent_span_2_Template, 2, 2, "span", 1); - $r3$.ɵɵi18nEnd(); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(2); - $r3$.ɵɵproperty("ngIf", ctx.ageVisible); - $r3$.ɵɵi18nExp(ctx.gender)(ctx.weight)(ctx.height); - $r3$.ɵɵi18nApply(1); - } - } - `; - - verify(input, output); - }); - - it('should handle icus with named interpolations', () => { - const input = ` -
{ - gender, - select, - male {male {{ weight // i18n(ph="PH_A") }}} - female {female {{ height // i18n(ph="PH_B") }}} - other {other {{ age // i18n(ph="PH WITH SPACES") }}} - }
- `; - - const i18n_0 = i18nIcuMsg( - '{VAR_SELECT, select, male {male {PH_A}} female {female {PH_B}} other {other {PH_WITH_SPACES}}}', - [ - ['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], - ['PH_A', String.raw`\uFFFD1\uFFFD`], - ['PH_B', String.raw`\uFFFD2\uFFFD`], - ['PH_WITH_SPACES', String.raw`\uFFFD3\uFFFD`], - ]); - - const output = String.raw` - decls: 2, - vars: 4, - consts: function() { - ${i18n_0} - return [ - $i18n_0$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵi18n(1, 0); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵadvance(1); - $r3$.ɵɵi18nExp(ctx.gender)(ctx.weight)(ctx.height)(ctx.age); - $r3$.ɵɵi18nApply(1); - } - } - `; - - verify(input, output); - }); - - it('should attach metadata in case an ICU represents the whole message', () => { - const input = ` -
{count, select, 1 {one} other {more than one}}
- `; - - const output = i18nMsgWithPostprocess( - '{VAR_SELECT, select, 1 {one} other {more than one}}', [], - {meaning: 'meaningA', desc: 'descA', id: 'idA'}, - [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]]); - - verify(input, output); - }); - - it('should produce proper messages when `select` or `plural` keywords have spaces after them', - () => { - const input = ` -
- {count, select , 1 {one} other {more than one}} - {count, plural , =1 {one} other {more than one}} -
- `; - - const i18n_0 = i18nIcuMsg( - '{VAR_SELECT , select , 1 {one} other {more than one}}', - [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]]); - const i18n_1 = i18nIcuMsg( - '{VAR_PLURAL , plural , =1 {one} other {more than one}}', - [['VAR_PLURAL', String.raw`\uFFFD1\uFFFD`]]); - - const output = String.raw` - ${i18n_0} - ${i18n_1} - `; - - verify(input, output); - }); - }); - - describe('$localize legacy message ids', () => { - it('should add legacy message ids if `enableI18nLegacyMessageIdFormat` is true', () => { - const input = `
Some Message
`; - - const output = String.raw` - let $I18N_0$; - if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { … } - else { - $I18N_0$ = $localize \`:␟ec93160d6d6a8822214060dd7938bf821c22b226␟6795333002533525253:Some Message\`; - } - … - `; - - verify(input, output, {compilerOptions: {enableI18nLegacyMessageIdFormat: true}}); - }); - - it('should add legacy message ids if `enableI18nLegacyMessageIdFormat` is undefined', () => { - const input = `
Some Message
`; - - const output = String.raw` - let $I18N_0$; - if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { … } - else { - $I18N_0$ = $localize \`:␟ec93160d6d6a8822214060dd7938bf821c22b226␟6795333002533525253:Some Message\`; - } - … - `; - - verify(input, output, {compilerOptions: {enableI18nLegacyMessageIdFormat: undefined}}); - }); - }); - - describe('line ending normalization', () => { - [true, false].forEach( - templateUrl => describe(templateUrl ? '[templateUrl]' : '[inline template]', () => { - [true, false, undefined].forEach( - i18nNormalizeLineEndingsInICUs => describe( - `{i18nNormalizeLineEndingsInICUs: ${i18nNormalizeLineEndingsInICUs}}`, () => { - it('should normalize line endings in templates', () => { - const input = - `
\r\nSome Message\r\n{\r\n value,\r\n select,\r\n =0 {\r\n zero\r\n }\r\n}
`; - - const output = String.raw` - $I18N_0$ = $localize \`abc -def\`; - … - $I18N_4$ = $localize \`{VAR_SELECT, select, =0 {zero - }}\` - … - $I18N_3$ = $localize \` -Some Message -$` + String.raw`{$I18N_4$}:ICU:\`; - `; - - verify(input, output, { - inputArgs: {templateUrl}, - compilerOptions: {i18nNormalizeLineEndingsInICUs} - }); - }); - - it('should compute the correct message id for messages', () => { - const input = - `
\r\nSome Message\r\n{\r\n value,\r\n select,\r\n =0 {\r\n zero\r\n }\r\n}
`; - - // The ids generated by the compiler are different if the template is external - // and we are not explicitly normalizing the line endings. - const ICU_EXPRESSION_ID = - templateUrl && i18nNormalizeLineEndingsInICUs !== true ? - `␟70a685282be2d956e4db234fa3d985970672faa0` : - `␟b5fe162f4e47ab5b3e534491d30b715e0dff0f52`; - const ICU_ID = templateUrl && i18nNormalizeLineEndingsInICUs !== true ? - `␟6a55b51b9bcf8f84b1b868c585ae09949668a72b` : - `␟e31c7bc4db2f2e56dc40f005958055a02fd43a2e`; - - const output = - String.raw` - $I18N_0$ = $localize \`:␟4f9ce2c66b187afd9898b25f6336d1eb2be8b5dc␟7326958852138509669:abc -def\`; - … - $I18N_4$ = $localize \`:${ - ICU_EXPRESSION_ID}␟4863953183043480207:{VAR_SELECT, select, =0 {zero - }}\` - … - $I18N_3$ = $localize \`:${ICU_ID}␟2773178924738647105: -Some Message -$` + String.raw`{$I18N_4$}:ICU:\`; - `; - - verify(input, output, { - inputArgs: {templateUrl}, - compilerOptions: - {i18nNormalizeLineEndingsInICUs, enableI18nLegacyMessageIdFormat: true} - }); - }); - })); - })); - }); - - describe('es5 support', () => { - it('should generate ES5 compliant localized messages if the target is ES5', () => { - const input = ` -
Content A
- `; - - const output = String.raw` - var $I18N_0$; - … - $I18N_0$ = $localize(…__makeTemplateObject([":meaning:A|descA@@idA:Content A"], [":meaning\\:A|descA@@idA:Content A"])…); - `; - - verify( - input, output, {skipIdBasedCheck: true, compilerOptions: {target: ts.ScriptTarget.ES5}}); - }); - }); - - describe('namespaces', () => { - it('should handle namespaces inside i18n blocks', () => { - const input = ` - - - - Count: 5 - - - - `; - - const i18n_0 = String.raw` - let $I18N_0$; - if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { - const $MSG_EXTERNAL_7128002169381370313$$APP_SPEC_TS_1$ = goog.getMsg("{$startTagXhtmlDiv} Count: {$startTagXhtmlSpan}5{$closeTagXhtmlSpan}{$closeTagXhtmlDiv}", { - "startTagXhtmlDiv": "\uFFFD#3\uFFFD", - "startTagXhtmlSpan": "\uFFFD#4\uFFFD", - "closeTagXhtmlSpan": "\uFFFD/#4\uFFFD", - "closeTagXhtmlDiv": "\uFFFD/#3\uFFFD" - }); - $I18N_0$ = $MSG_EXTERNAL_7128002169381370313$$APP_SPEC_TS_1$; - } - else { - $I18N_0$ = $localize \`$` + - String.raw`{"\uFFFD#3\uFFFD"}:START_TAG__XHTML_DIV: Count: $` + - String.raw`{"\uFFFD#4\uFFFD"}:START_TAG__XHTML_SPAN:5$` + - String.raw`{"\uFFFD/#4\uFFFD"}:CLOSE_TAG__XHTML_SPAN:$` + - String.raw`{"\uFFFD/#3\uFFFD"}:CLOSE_TAG__XHTML_DIV:\`; - } - `; - - const output = String.raw` - … - consts: function() { - ${i18n_0} - return [ - ["xmlns", "http://www.w3.org/2000/svg"], - $i18n_0$, - ["xmlns", "http://www.w3.org/1999/xhtml"] - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵnamespaceSVG(); - $r3$.ɵɵelementStart(0, "svg", 0); - $r3$.ɵɵelementStart(1, "foreignObject"); - $r3$.ɵɵi18nStart(2, 1); - $r3$.ɵɵnamespaceHTML(); - $r3$.ɵɵelementStart(3, "div", 2); - $r3$.ɵɵelement(4, "span"); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵi18nEnd(); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementEnd(); - } - } - `; - - verify(input, output); - }); - - it('should handle namespaces on i18n block containers', () => { - const input = ` - - - - Count: 5 - - - - `; - - const i18n_0 = String.raw` - let $I18N_0$; - if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { - const $MSG_EXTERNAL_7428861019045796010$$APP_SPEC_TS_1$ = goog.getMsg(" Count: {$startTagXhtmlSpan}5{$closeTagXhtmlSpan}", { - "startTagXhtmlSpan": "\uFFFD#4\uFFFD", - "closeTagXhtmlSpan": "\uFFFD/#4\uFFFD" - }); - $I18N_0$ = $MSG_EXTERNAL_7428861019045796010$$APP_SPEC_TS_1$; - } - else { - $I18N_0$ = $localize \` Count: $` + - String.raw`{"\uFFFD#4\uFFFD"}:START_TAG__XHTML_SPAN:5$` + - String.raw`{"\uFFFD/#4\uFFFD"}:CLOSE_TAG__XHTML_SPAN:\`; - } - `; - - const output = String.raw` - consts: function() { - ${i18n_0} - return [ - ["xmlns", "http://www.w3.org/2000/svg"], - ["xmlns", "http://www.w3.org/1999/xhtml"], - $i18n_0$ - ]; - }, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵnamespaceSVG(); - $r3$.ɵɵelementStart(0, "svg", 0); - $r3$.ɵɵelementStart(1, "foreignObject"); - $r3$.ɵɵnamespaceHTML(); - $r3$.ɵɵelementStart(2, "div", 1); - $r3$.ɵɵi18nStart(3, 2); - $r3$.ɵɵelement(4, "span"); - $r3$.ɵɵi18nEnd(); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementEnd(); - $r3$.ɵɵelementEnd(); - } - } - `; - - verify(input, output); - }); - }); -}); diff --git a/packages/compiler-cli/test/compliance_old/r3_view_compiler_input_outputs_spec.ts b/packages/compiler-cli/test/compliance_old/r3_view_compiler_input_outputs_spec.ts deleted file mode 100644 index 8c62938582..0000000000 --- a/packages/compiler-cli/test/compliance_old/r3_view_compiler_input_outputs_spec.ts +++ /dev/null @@ -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'); - }); -}); diff --git a/packages/compiler-cli/test/compliance_old/r3_view_compiler_listener_spec.ts b/packages/compiler-cli/test/compliance_old/r3_view_compiler_listener_spec.ts deleted file mode 100644 index ae160dbdb7..0000000000 --- a/packages/compiler-cli/test/compliance_old/r3_view_compiler_listener_spec.ts +++ /dev/null @@ -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: \`
\` - }) - 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: \`
My App
\` - }) - export class MyApp {} - - @Component({ - selector: 'my-component', - template: \`\` - }) - 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: \` -
-
- -
- - \` - }) - 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: \` - - - \` - }) - 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: \`
\` - }) - 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: \` -
- - \` - }) - 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: \`\` - }) - 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: \`
\` - }) - 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: '
' - }) - 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: '
' - }) - 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: '
' - }) - 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'); - }); -}); diff --git a/packages/compiler-cli/test/compliance_old/r3_view_compiler_providers_spec.ts b/packages/compiler-cli/test/compliance_old/r3_view_compiler_providers_spec.ts deleted file mode 100644 index f14e0ffe97..0000000000 --- a/packages/compiler-cli/test/compliance_old/r3_view_compiler_providers_spec.ts +++ /dev/null @@ -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: '
', - 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: '
', - 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: '
', - 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: '
' - }) - 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'); - }); -}); diff --git a/packages/compiler-cli/test/compliance_old/r3_view_compiler_spec.ts b/packages/compiler-cli/test/compliance_old/r3_view_compiler_spec.ts deleted file mode 100644 index bdfbd1762d..0000000000 --- a/packages/compiler-cli/test/compliance_old/r3_view_compiler_spec.ts +++ /dev/null @@ -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: '' - }) - export class MyApp implements OnInit { - - list: any[] = []; - - constructor(public elementRef: ElementRef) {} - - ngOnInit(): void { - } - } - - @Component({ - selector: 'todo', - template: '
  • {{data}}
' - }) - 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: '
' - }) - 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: '
' - }) - 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: '
' - }) - 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: '
' - }) - 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'); - }); - }); -}); diff --git a/packages/compiler-cli/test/compliance_old/r3_view_compiler_styling_spec.ts b/packages/compiler-cli/test/compliance_old/r3_view_compiler_styling_spec.ts deleted file mode 100644 index a28ea3f2b4..0000000000 --- a/packages/compiler-cli/test/compliance_old/r3_view_compiler_styling_spec.ts +++ /dev/null @@ -1,2099 +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: styling', () => { - const angularFiles = setup({ - compileAngular: false, - compileFakeCore: true, - compileAnimations: false, - }); - - describe('@Component.styles', () => { - it('should pass in the component metadata styles into the component definition and shim them using style encapsulation', - () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: "my-component", - styles: ["div.foo { color: red; }", ":host p:nth-child(even) { --webkit-transition: 1s linear all; }"], - template: "..." - }) - export class MyComponent { - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = - 'styles: ["div.foo[_ngcontent-%COMP%] { color: red; }", "[_nghost-%COMP%] p[_ngcontent-%COMP%]:nth-child(even) { --webkit-transition: 1s linear all; }"]'; - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should pass in styles, but skip shimming the styles if the view encapsulation signals not to', - () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule, ViewEncapsulation} from '@angular/core'; - - @Component({ - selector: "my-component", - encapsulation: ViewEncapsulation.None, - styles: ["div.tall { height: 123px; }", ":host.small p { height:5px; }"], - template: "..." - }) - export class MyComponent { - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = 'div.tall { height: 123px; }", ":host.small p { height:5px; }'; - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should pass in the component metadata styles into the component definition but skip shimming when style encapsulation is set to shadow dom', - () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule, ViewEncapsulation} from '@angular/core'; - - @Component({ - encapsulation: ViewEncapsulation.ShadowDom, - selector: "my-component", - styles: ["div.cool { color: blue; }", ":host.nice p { color: gold; }"], - template: "..." - }) - export class MyComponent { - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - … - styles: ["div.cool { color: blue; }", ":host.nice p { color: gold; }"], - encapsulation: 3 - }) - `; - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - }); - - describe('@Component.animations', () => { - it('should pass in the component metadata animations into the component definition', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: "my-component", - animations: [{name: 'foo123'}, {name: 'trigger123'}], - template: "" - }) - export class MyComponent { - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyComponent, - selectors:[["my-component"]], - decls: 0, - vars: 0, - template: function MyComponent_Template(rf, $ctx$) { - }, - encapsulation: 2, - data: { - animation: [{name: 'foo123'}, {name: 'trigger123'}] - } - }); - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should include animations even if the provided array is empty', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: "my-component", - animations: [], - template: "" - }) - export class MyComponent { - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyComponent, - selectors:[["my-component"]], - decls: 0, - vars: 0, - template: function MyComponent_Template(rf, $ctx$) { - }, - encapsulation: 2, - data: { - animation: [] - } - }); - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should generate any animation triggers into the component template', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: "my-component", - template: \` -
-
-
\`, - }) - export class MyComponent { - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - … - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - … - decls: 3, - vars: 3, - template: function MyComponent_Template(rf, $ctx$) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div"); - $r3$.ɵɵelement(1, "div"); - $r3$.ɵɵelement(2, "div"); - } - if (rf & 2) { - $r3$.ɵɵproperty("@foo", ctx.exp); - $r3$.ɵɵadvance(1); - $r3$.ɵɵproperty("@bar", undefined); - $r3$.ɵɵadvance(1); - $r3$.ɵɵproperty("@baz", undefined); - } - }, - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should generate animation listeners', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-cmp', - template: \` -
- \`, - animations: [trigger( - 'myAnimation', - [transition( - '* => state', - [style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])], - }) - class MyComponent { - exp: any; - startEvent: any; - doneEvent: any; - onStart(event: any) { this.startEvent = event; } - onDone(event: any) { this.doneEvent = event; } - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - … - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - … - decls: 1, - vars: 1, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵlistener("@myAnimation.start", function MyComponent_Template_div_animation_myAnimation_start_0_listener($event) { return ctx.onStart($event); })("@myAnimation.done", function MyComponent_Template_div_animation_myAnimation_done_0_listener($event) { return ctx.onDone($event); }); - $r3$.ɵɵelementEnd(); - } if (rf & 2) { - $r3$.ɵɵproperty("@myAnimation", ctx.exp); - } - }, - encapsulation: 2, - … - }); - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should generate animation host binding and listener code for directives', () => { - const files = { - app: { - 'spec.ts': ` - import {Directive, Component, NgModule} from '@angular/core'; - - @Directive({ - selector: '[my-anim-dir]', - animations: [ - {name: 'myAnim'} - ], - host: { - '[@myAnim]': 'myAnimState', - '(@myAnim.start)': 'onStart()', - '(@myAnim.done)': 'onDone()' - } - }) - class MyAnimDir { - onStart() {} - onDone() {} - myAnimState = '123'; - } - - @Component({ - selector: 'my-cmp', - template: \` -
- \` - }) - class MyComponent { - } - - @NgModule({declarations: [MyComponent, MyAnimDir]}) - export class MyModule {} - ` - } - }; - - const template = ` - MyAnimDir.ɵdir = /*@__PURE__*/ $r3$.ɵɵdefineDirective({ - … - hostVars: 1, - hostBindings: function MyAnimDir_HostBindings(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵsyntheticHostListener("@myAnim.start", function MyAnimDir_animation_myAnim_start_HostBindingHandler() { return ctx.onStart(); })("@myAnim.done", function MyAnimDir_animation_myAnim_done_HostBindingHandler() { return ctx.onDone(); }); - } - if (rf & 2) { - $r3$.ɵɵsyntheticHostProperty("@myAnim", ctx.myAnimState); - } - } - … - }); - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - }); - - 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: \`
\` - }) - export class MyComponent { - myStyleExp = [{color:'red'}, {color:'blue', duration:1000}] - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - template: function MyComponent_Template(rf, $ctx$) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div"); - } - if (rf & 2) { - $r3$.ɵɵstyleMap($ctx$.myStyleExp); - } - } - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should correctly count the total slots required when style/class bindings include interpolation', - () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component-with-interpolation', - template: \` -
- \` - }) - export class MyComponentWithInterpolation { - fooId = '123'; - } - - @Component({ - selector: 'my-component-with-muchos-interpolation', - template: \` -
- \` - }) - export class MyComponentWithMuchosInterpolation { - fooId = '123'; - fooUsername = 'superfoo'; - } - - @Component({ - selector: 'my-component-without-interpolation', - template: \` -
- \` - }) - export class MyComponentWithoutInterpolation { - exp = 'bar'; - } - - @NgModule({declarations: [MyComponentWithInterpolation, MyComponentWithMuchosInterpolation, MyComponentWithoutInterpolation]}) - export class MyModule {} - ` - } - }; - - const template = ` - … - decls: 1, - vars: 3, - template: function MyComponentWithInterpolation_Template(rf, $ctx$) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div"); - } - if (rf & 2) { - $r3$.ɵɵclassMapInterpolate1("foo foo-", $ctx$.fooId, ""); - } - } - … - decls: 1, - vars: 4, - template: function MyComponentWithMuchosInterpolation_Template(rf, $ctx$) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div"); - } - if (rf & 2) { - $r3$.ɵɵclassMapInterpolate2("foo foo-", $ctx$.fooId, "-", $ctx$.fooUsername, ""); - } - } - … - decls: 1, - vars: 2, - template: function MyComponentWithoutInterpolation_Template(rf, $ctx$) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div"); - } - if (rf & 2) { - $r3$.ɵɵclassMap($ctx$.exp); - } - } - `; - - 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: \`
\` - }) - export class MyComponent { - myStyleExp = [{color:'red'}, {color:'blue', duration:1000}] - myWidth = '100px'; - myHeight = '100px'; - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - … - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyComponent, - selectors:[["my-component"]], - decls: 1, - vars: 7, - consts: [[${AttributeMarker.Styles}, "opacity", "1"]], - template: function MyComponent_Template(rf, $ctx$) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div", 0); - } - if (rf & 2) { - $r3$.ɵɵstyleMap($ctx$.myStyleExp); - $r3$.ɵɵstyleProp("width", $ctx$.myWidth)("height", $ctx$.myHeight); - $r3$.ɵɵattribute("style", "border-width: 10px", $r3$.ɵɵsanitizeStyle); - } - }, - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should assign a sanitizer instance to the element style allocation instruction if any url-based properties are detected', - () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \`
\` - }) - export class MyComponent { - myImage = 'url(foo.jpg)'; - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyComponent, - selectors: [["my-component"]], - decls: 1, - vars: 2, - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div"); - } - if (rf & 2) { - $r3$.ɵɵstyleProp("background-image", ctx.myImage); - } - }, - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should support [style.foo.suffix] style bindings with a suffix', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \`
\` - }) - export class MyComponent { - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - template: function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div"); - } - if (rf & 2) { - $r3$.ɵɵstyleProp("font-size", 12, "px"); - } - } - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should not create instructions for empty style bindings', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \`
\` - }) - export class MyComponent { - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const result = compile(files, angularFiles); - expect(result.source).not.toContain('styling'); - }); - }); - - 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: \`
\` - }) - export class MyComponent { - myClassExp = {'foo':true} - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - template: function MyComponent_Template(rf, $ctx$) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div"); - } - if (rf & 2) { - $r3$.ɵɵclassMap($ctx$.myClassExp); - } - } - `; - - 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: \`
\` - }) - export class MyComponent { - myClassExp = {a:true, b:true}; - yesToApple = true; - yesToOrange = true; - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - … - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyComponent, - selectors:[["my-component"]], - decls: 1, - vars: 7, - consts: [[${AttributeMarker.Classes}, "grape"]], - template: function MyComponent_Template(rf, $ctx$) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div", 0); - } - if (rf & 2) { - $r3$.ɵɵclassMap($ctx$.myClassExp); - $r3$.ɵɵclassProp("apple", $ctx$.yesToApple)("orange", $ctx$.yesToOrange); - $r3$.ɵɵattribute("class", "banana"); - } - }, - encapsulation: 2 - }); - `; - - 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: \`
\` - }) - export class MyComponent {} - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - … - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - type: MyComponent, - selectors:[["my-component"]], - decls: 1, - vars: 2, - consts: [[${AttributeMarker.Classes}, "foo", ${ - AttributeMarker.Styles}, "width", "100px"]], - template: function MyComponent_Template(rf, $ctx$) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div", 0); - } - if (rf & 2) { - $r3$.ɵɵattribute("class", "round")("style", "height:100px", $r3$.ɵɵsanitizeStyle); - } - }, - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should not create instructions for empty class bindings', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \`
\` - }) - export class MyComponent { - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const result = compile(files, angularFiles); - expect(result.source).not.toContain('styling'); - }); - }); - - describe('[style] mixed with [class]', () => { - it('should split [style] and [class] bindings into a separate instructions', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \`
\` - }) - export class MyComponent { - myStyleExp = [{color:'red'}, {color:'blue', duration:1000}] - myClassExp = 'foo bar apple'; - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - template: function MyComponent_Template(rf, $ctx$) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div"); - } - if (rf & 2) { - $r3$.ɵɵstyleMap($ctx$.myStyleExp); - $r3$.ɵɵclassMap($ctx$.myClassExp); - } - } - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should stamp out pipe definitions in the creation block if used by styling bindings', - () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \`
\` - }) - export class MyComponent { - myStyleExp = [{color:'red'}, {color:'blue', duration:1000}] - myClassExp = 'foo bar apple'; - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - template: function MyComponent_Template(rf, $ctx$) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵpipe(1, "stylePipe"); - $r3$.ɵɵpipe(2, "classPipe"); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵstyleMap($r3$.ɵɵpipeBind1(1, 4, $ctx$.myStyleExp)); - $r3$.ɵɵclassMap($r3$.ɵɵpipeBind1(2, 6, $ctx$.myClassExp)); - } - } - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should properly offset multiple style pipe references for styling bindings', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \` -
- {{ item }}
\` - }) - export class MyComponent { - myStyleExp = {}; - fooExp = 'foo'; - barExp = 'bar'; - bazExp = 'baz'; - items = [1,2,3]; - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - template: function MyComponent_Template(rf, $ctx$) { - if (rf & 1) { - $r3$.ɵɵelementStart(0, "div"); - $r3$.ɵɵpipe(1, "pipe"); - $r3$.ɵɵpipe(2, "pipe"); - $r3$.ɵɵpipe(3, "pipe"); - $r3$.ɵɵpipe(4, "pipe"); - $r3$.ɵɵtext(5); - $r3$.ɵɵelementEnd(); - } - if (rf & 2) { - $r3$.ɵɵstyleMap($r3$.ɵɵpipeBind2(1, 11, $ctx$.myStyleExp, 1000)); - $r3$.ɵɵclassMap($r3$.ɵɵpureFunction0(23, _c0)); - $r3$.ɵɵstyleProp("bar", $r3$.ɵɵpipeBind2(2, 14, $ctx$.barExp, 3000))("baz", $r3$.ɵɵpipeBind2(3, 17, $ctx$.bazExp, 4000)); - $r3$.ɵɵclassProp("foo", $r3$.ɵɵpipeBind2(4, 20, $ctx$.fooExp, 2000)); - $r3$.ɵɵadvance(5); - $r3$.ɵɵtextInterpolate1(" ", $ctx$.item, ""); - } - } - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should always generate select() statements before any styling instructions', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \` -
-
-
-
- \` - }) - export class MyComponent { - w1 = '100px'; - h1 = '100px'; - a1 = true; - r1 = true; - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - … - template: function MyComponent_Template(rf, $ctx$) { - … - if (rf & 2) { - $r3$.ɵɵstyleProp("width", $ctx$.w1); - $r3$.ɵɵadvance(1); - $r3$.ɵɵstyleProp("height", $ctx$.h1); - $r3$.ɵɵadvance(1); - $r3$.ɵɵclassProp("active", $ctx$.a1); - $r3$.ɵɵadvance(1); - $r3$.ɵɵclassProp("removed", $ctx$.r1); - } - } - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - }); - - describe('@Component host styles/classes', () => { - it('should generate style/class instructions for a host component creation definition', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule, HostBinding} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: '', - host: { - 'style': 'width:200px; height:500px', - 'class': 'foo baz' - } - }) - export class MyComponent { - @HostBinding('style') - myStyle = {width:'100px'}; - - @HostBinding('class') - myClass = {bar:false}; - - @HostBinding('style.color') - myColorProp = 'red'; - - @HostBinding('class.foo') - myFooClass = 'red'; - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - hostAttrs: [${AttributeMarker.Classes}, "foo", "baz", ${ - AttributeMarker.Styles}, "width", "200px", "height", "500px"], - hostVars: 8, - hostBindings: function MyComponent_HostBindings(rf, ctx) { - if (rf & 2) { - $r3$.ɵɵstyleMap(ctx.myStyle); - $r3$.ɵɵclassMap(ctx.myClass); - $r3$.ɵɵstyleProp("color", ctx.myColorProp); - $r3$.ɵɵclassProp("foo", ctx.myFooClass); - } - }, - decls: 0, - vars: 0, - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should generate style/class instructions for multiple host binding definitions', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule, HostBinding} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: '', - host: { - '[style.height.pt]': 'myHeightProp', - '[class.bar]': 'myBarClass' - } - }) - export class MyComponent { - myHeightProp = 20; - myBarClass = true; - - @HostBinding('style') - myStyle = {}; - - @HostBinding('style.width') - myWidthProp = '500px'; - - @HostBinding('class.foo') - myFooClass = true; - - @HostBinding('class') - myClasses = {a:true, b:true}; - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - hostVars: 12, - hostBindings: function MyComponent_HostBindings(rf, ctx) { - if (rf & 2) { - $r3$.ɵɵstyleMap(ctx.myStyle); - $r3$.ɵɵclassMap(ctx.myClasses); - $r3$.ɵɵstyleProp("height", ctx.myHeightProp, "pt")("width", ctx.myWidthProp); - $r3$.ɵɵclassProp("bar", ctx.myBarClass)("foo", ctx.myFooClass); - } - }, - decls: 0, - vars: 0, - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should generate override instructions for only single-level styling bindings when !important is present', - () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule, HostBinding} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \` -
- \`, - host: { - '[style!important]': 'myStyleExp', - '[class!important]': 'myClassExp' - } - }) - export class MyComponent { - @HostBinding('class.foo!important') - myFooClassExp = true; - - @HostBinding('style.width!important') - myWidthExp = '100px'; - - myBarClassExp = true; - myHeightExp = '200px'; - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - function MyComponent_Template(rf, ctx) { - if (rf & 1) { - $r3$.ɵɵelement(0, "div"); - } - if (rf & 2) { - $r3$.ɵɵstyleMap(ctx.myStyleExp); - $r3$.ɵɵclassMap(ctx.myClassExp); - $r3$.ɵɵstyleProp("height", ctx.myHeightExp); - $r3$.ɵɵclassProp("bar", ctx.myBarClassExp); - } - }, - `; - - const hostBindings = ` - hostVars: 8, - hostBindings: function MyComponent_HostBindings(rf, ctx) { - if (rf & 2) { - $r3$.ɵɵstyleMap(ctx.myStyleExp); - $r3$.ɵɵclassMap(ctx.myClassExp); - $r3$.ɵɵstyleProp("width", ctx.myWidthExp); - $r3$.ɵɵclassProp("foo", ctx.myFooClassExp); - } - }, - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, hostBindings, 'Incorrect template'); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should support class interpolation', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule, HostBinding} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \` -
-
-
-
-
-
-
-
-
- \`, - }) - export class MyComponent { - p1 = 100; - p2 = 100; - p3 = 100; - p4 = 100; - p5 = 100; - p6 = 100; - p6 = 100; - p7 = 100; - p8 = 100; - p9 = 100; - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - function MyComponent_Template(rf, ctx) { - if (rf & 1) { - … - } - if (rf & 2) { - $r3$.ɵɵclassMapInterpolate1("A", ctx.p1, "B"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵclassMapInterpolate2("A", ctx.p1, "B", ctx.p2, "C"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵclassMapInterpolate3("A", ctx.p1, "B", ctx.p2, "C", ctx.p3, "D"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵclassMapInterpolate4("A", ctx.p1, "B", ctx.p2, "C", ctx.p3, "D", ctx.p4, "E"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵclassMapInterpolate5("A", ctx.p1, "B", ctx.p2, "C", ctx.p3, "D", ctx.p4, "E", ctx.p5, "F"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵclassMapInterpolate6("A", ctx.p1, "B", ctx.p2, "C", ctx.p3, "D", ctx.p4, "E", ctx.p5, "F", ctx.p6, "G"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵclassMapInterpolate7("A", ctx.p1, "B", ctx.p2, "C", ctx.p3, "D", ctx.p4, "E", ctx.p5, "F", ctx.p6, "G", ctx.p7, "H"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵclassMapInterpolate8("A", ctx.p1, "B", ctx.p2, "C", ctx.p3, "D", ctx.p4, "E", ctx.p5, "F", ctx.p6, "G", ctx.p7, "H", ctx.p8, "I"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵclassMapInterpolateV(["A", ctx.p1, "B", ctx.p2, "C", ctx.p3, "D", ctx.p4, "E", ctx.p5, "F", ctx.p6, "G", ctx.p7, "H", ctx.p8, "I", ctx.p9, "J"]); - } - }, - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should support style interpolation', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule, HostBinding} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \` -
-
-
-
-
-
-
-
-
- \`, - }) - export class MyComponent { - p1 = 100; - p2 = 100; - p3 = 100; - p4 = 100; - p5 = 100; - p6 = 100; - p6 = 100; - p7 = 100; - p8 = 100; - p9 = 100; - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - function MyComponent_Template(rf, ctx) { - if (rf & 1) { - … - } - if (rf & 2) { - $r3$.ɵɵstyleMapInterpolate1("p1:", ctx.p1, ";"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵstyleMapInterpolate2("p1:", ctx.p1, ";p2:", ctx.p2, ";"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵstyleMapInterpolate3("p1:", ctx.p1, ";p2:", ctx.p2, ";p3:", ctx.p3, ";"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵstyleMapInterpolate4("p1:", ctx.p1, ";p2:", ctx.p2, ";p3:", ctx.p3, ";p4:", ctx.p4, ";"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵstyleMapInterpolate5("p1:", ctx.p1, ";p2:", ctx.p2, ";p3:", ctx.p3, ";p4:", ctx.p4, ";p5:", ctx.p5, ";"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵstyleMapInterpolate6("p1:", ctx.p1, ";p2:", ctx.p2, ";p3:", ctx.p3, ";p4:", ctx.p4, ";p5:", ctx.p5, ";p6:", ctx.p6, ";"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵstyleMapInterpolate7("p1:", ctx.p1, ";p2:", ctx.p2, ";p3:", ctx.p3, ";p4:", ctx.p4, ";p5:", ctx.p5, ";p6:", ctx.p6, ";p7:", ctx.p7, ";"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵstyleMapInterpolate8("p1:", ctx.p1, ";p2:", ctx.p2, ";p3:", ctx.p3, ";p4:", ctx.p4, ";p5:", ctx.p5, ";p6:", ctx.p6, ";p7:", ctx.p7, ";p8:", ctx.p8, ";"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵstyleMapInterpolateV(["p1:", ctx.p1, ";p2:", ctx.p2, ";p3:", ctx.p3, ";p4:", ctx.p4, ";p5:", ctx.p5, ";p6:", ctx.p6, ";p7:", ctx.p7, ";p8:", ctx.p8, ";p9:", ctx.p9, ";"]); - } - }, - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should generate styling instructions for multiple directives that contain host binding definitions', - () => { - const files = { - app: { - 'spec.ts': ` - import {Directive, Component, NgModule, HostBinding} from '@angular/core'; - - @Directive({selector: '[myClassDir]'}) - export class ClassDirective { - @HostBinding('class') - myClassMap = {red: true}; - } - - @Directive({selector: '[myWidthDir]'}) - export class WidthDirective { - @HostBinding('style.width') - myWidth = 200; - - @HostBinding('class.foo') - myFooClass = true; - } - - @Directive({selector: '[myHeightDir]'}) - export class HeightDirective { - @HostBinding('style.height') - myHeight = 200; - - @HostBinding('class.bar') - myBarClass = true; - } - - @Component({ - selector: 'my-component', - template: '
', - }) - export class MyComponent { - } - - @NgModule({declarations: [MyComponent, WidthDirective, HeightDirective, ClassDirective]}) - export class MyModule {} - ` - } - }; - - // NOTE: IF YOU ARE CHANGING THIS COMPILER SPEC, YOU MAY NEED TO CHANGE THE DIRECTIVE - // DEF THAT'S HARD-CODED IN `ng_class.ts`. - const template = ` - … - hostVars: 2, - hostBindings: function ClassDirective_HostBindings(rf, ctx) { - if (rf & 2) { - $r3$.ɵɵclassMap(ctx.myClassMap); - } - } - … - hostVars: 4, - hostBindings: function WidthDirective_HostBindings(rf, ctx) { - if (rf & 2) { - $r3$.ɵɵstyleProp("width", ctx.myWidth); - $r3$.ɵɵclassProp("foo", ctx.myFooClass); - } - } - … - hostVars: 4, - hostBindings: function HeightDirective_HostBindings(rf, ctx) { - if (rf & 2) { - $r3$.ɵɵstyleProp("height", ctx.myHeight); - $r3$.ɵɵclassProp("bar", ctx.myBarClass); - } - } - … - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - }); - - describe('interpolations', () => { - it('should generate the proper update instructions for interpolated classes', () => { - const files = { - app: { - 'spec.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: \` -
-
-
-
-
-
-
-
-
-
- \` - }) - export class MyComponent { - } - ` - } - }; - - const template = ` - … - if (rf & 2) { - $r3$.ɵɵclassMapInterpolateV(["a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i", ctx.nine, "j"]); - $r3$.ɵɵadvance(1); - $r3$.ɵɵclassMapInterpolate8("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵclassMapInterpolate7("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵclassMapInterpolate6("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵclassMapInterpolate5("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵclassMapInterpolate4("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵclassMapInterpolate3("a", ctx.one, "b", ctx.two, "c", ctx.three, "d"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵclassMapInterpolate2("a", ctx.one, "b", ctx.two, "c"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵclassMapInterpolate1("a", ctx.one, "b"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵclassMap(ctx.one); - } - … - `; - const result = compile(files, angularFiles); - - expectEmit(result.source, template, 'Incorrect handling of interpolated classes'); - }); - - it('should throw for interpolations inside individual class bindings', () => { - const files = { - app: { - 'spec.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: '
' - }) - export class MyComponent { - } - ` - } - }; - - expect(() => compile(files, angularFiles)).toThrowError(/Unexpected interpolation/); - }); - - it('should generate the proper update instructions for interpolated style properties', () => { - const files = { - app: { - 'spec.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: \` -
-
-
-
-
-
-
-
-
-
- \` - }) - export class MyComponent { - } - ` - } - }; - - const template = ` - … - if (rf & 2) { - $r3$.ɵɵstylePropInterpolateV("color", ["a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i", ctx.nine, "j"]); - $r3$.ɵɵadvance(1); - $r3$.ɵɵstylePropInterpolate8("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵstylePropInterpolate7("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵstylePropInterpolate6("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵstylePropInterpolate5("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵstylePropInterpolate4("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵstylePropInterpolate3("color", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵstylePropInterpolate2("color", "a", ctx.one, "b", ctx.two, "c"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵstylePropInterpolate1("color", "a", ctx.one, "b"); - $r3$.ɵɵadvance(1); - $r3$.ɵɵstyleProp("color", ctx.one); - } - … - `; - const result = compile(files, angularFiles); - - expectEmit(result.source, template, 'Incorrect handling of interpolated style properties'); - }); - - it('should generate update instructions for interpolated style properties with a suffix', - () => { - const files = { - app: { - 'spec.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: \` -
- \` - }) - export class MyComponent { - } - ` - } - }; - - const template = ` - … - if (rf & 2) { - $r3$.ɵɵstylePropInterpolate2("width", "a", ctx.one, "b", ctx.two, "c", "px"); - } - … - `; - const result = compile(files, angularFiles); - - expectEmit(result.source, template, 'Incorrect handling of interpolated style properties'); - }); - - it('should generate update instructions for interpolated style properties with a sanitizer', - () => { - const files = { - app: { - 'spec.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: \` -
- \` - }) - export class MyComponent { - myUrl1 = '...'; - myUrl2 = '...'; - myBoxX = '0px'; - myBoxY = '0px'; - myBoxWidth = '100px'; - myRepeat = 'no-repeat'; - } - ` - } - }; - - const template = ` - … - if (rf & 2) { - $r3$.ɵɵstylePropInterpolate1("background", "url(", ctx.myUrl1, ")"); - $r3$.ɵɵstylePropInterpolate2("border-image", "url(", ctx.myUrl2, ") ", ctx.myRepeat, " auto"); - $r3$.ɵɵstylePropInterpolate3("box-shadow", "", ctx.myBoxX, " ", ctx.myBoxY, " ", ctx.myBoxWidth, " black"); - } - … - `; - const result = compile(files, angularFiles); - - expectEmit(result.source, template, 'Incorrect handling of interpolated style properties'); - }); - - it('should generate update instructions for interpolated style properties with !important', - () => { - const files = { - app: { - 'spec.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: \` -
- \` - }) - export class MyComponent { - } - ` - } - }; - - const template = ` - … - if (rf & 2) { - $r3$.ɵɵstylePropInterpolate2("width", "a", ctx.one, "b", ctx.two, "c"); - } - … - `; - const result = compile(files, angularFiles); - - expectEmit(result.source, template, 'Incorrect handling of interpolated style properties'); - }); - }); - - describe('instruction chaining', () => { - it('should chain classProp instruction calls', () => { - const files = { - app: { - 'spec.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: \`
\` - }) - export class MyComponent { - yesToApple = true; - yesToOrange = true; - tesToTomato = false; - } - ` - } - }; - - const template = ` - … - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - … - template: function MyComponent_Template(rf, $ctx$) { - … - if (rf & 2) { - $r3$.ɵɵclassProp("apple", $ctx$.yesToApple)("orange", $ctx$.yesToOrange)("tomato", $ctx$.yesToTomato); - } - }, - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain styleProp instruction calls', () => { - const files = { - app: { - 'spec.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: \`
\` - }) - export class MyComponent { - color = 'red'; - border = '1px solid purple'; - transition = 'all 1337ms ease'; - } - ` - } - }; - - const template = ` - … - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - … - template: function MyComponent_Template(rf, $ctx$) { - … - if (rf & 2) { - $r3$.ɵɵstyleProp("color", $ctx$.color)("border", $ctx$.border)("transition", $ctx$.transition); - } - }, - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain mixed styleProp and classProp calls', () => { - const files = { - app: { - 'spec.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: \`
\` - }) - export class MyComponent { - color = 'red'; - border = '1px solid purple'; - transition = 'all 1337ms ease'; - yesToApple = true; - yesToOrange = true; - tesToTomato = false; - } - ` - } - }; - - const template = ` - … - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - … - template: function MyComponent_Template(rf, $ctx$) { - … - if (rf & 2) { - $r3$.ɵɵstyleProp("color", $ctx$.color)("border", $ctx$.border)("transition", $ctx$.transition); - $r3$.ɵɵclassProp("apple", $ctx$.yesToApple)("orange", $ctx$.yesToOrange)("tomato", $ctx$.yesToTomato); - } - }, - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain style interpolations of the same kind', () => { - const files = { - app: { - 'spec.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: \`
\` - }) - export class MyComponent { - } - ` - } - }; - - const template = ` - … - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - … - template: function MyComponent_Template(rf, $ctx$) { - … - if (rf & 2) { - $r3$.ɵɵstylePropInterpolate1("color", "a", ctx.one, "b")("border", "a", ctx.one, "b")("transition", "a", ctx.one, "b"); - } - }, - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain style interpolations of multiple kinds', () => { - const files = { - app: { - 'spec.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: \`
\` - }) - export class MyComponent { - } - ` - } - }; - - const template = ` - … - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - … - template: function MyComponent_Template(rf, $ctx$) { - … - if (rf & 2) { - $r3$.ɵɵstylePropInterpolate1("color", "a", ctx.one, "b")("border", "a", ctx.one, "b"); - $r3$.ɵɵstylePropInterpolate2("transition", "a", ctx.one, "b", ctx.two, "c")("width", "a", ctx.one, "b", ctx.two, "c"); - $r3$.ɵɵstylePropInterpolate3("height", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d")("top", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d"); - } - }, - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should break into multiple chains if there are other styling instructions in between', - () => { - const files = { - app: { - 'spec.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: \`
\` - }) - export class MyComponent { - transition = 'all 1337ms ease'; - width = '42px'; - yesToApple = true; - yesToOrange = true; - } - ` - } - }; - - const template = ` - … - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - … - template: function MyComponent_Template(rf, $ctx$) { - … - if (rf & 2) { - $r3$.ɵɵstylePropInterpolate1("color", "a", ctx.one, "b")("border", "a", ctx.one, "b"); - $r3$.ɵɵstyleProp("transition", ctx.transition)("width", ctx.width); - $r3$.ɵɵstylePropInterpolate1("height", "a", ctx.one, "b")("top", "a", ctx.one, "b"); - $r3$.ɵɵclassProp("apple", ctx.yesToApple)("orange", ctx.yesToOrange); - } - }, - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should break into multiple chains if there are other styling interpolation instructions in between', - () => { - const files = { - app: { - 'spec.ts': ` - import {Component} from '@angular/core'; - - @Component({ - template: \`
\` - }) - export class MyComponent { - transition = 'all 1337ms ease'; - width = '42px'; - } - ` - } - }; - - const template = ` - … - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - … - template: function MyComponent_Template(rf, $ctx$) { - … - if (rf & 2) { - $r3$.ɵɵstylePropInterpolate1("color", "a", ctx.one, "b")("border", "a", ctx.one, "b"); - $r3$.ɵɵstylePropInterpolate2("transition", "a", ctx.one, "b", ctx.two, "c"); - $r3$.ɵɵstylePropInterpolate3("width", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d"); - $r3$.ɵɵstylePropInterpolate1("height", "a", ctx.one, "b")("top", "a", ctx.one, "b"); - } - }, - encapsulation: 2 - }); - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should chain styling instructions inside host bindings', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, HostBinding} from '@angular/core'; - - @Component({ - template: '', - host: { - '[class.apple]': 'yesToApple', - '[style.color]': 'color', - '[class.tomato]': 'yesToTomato', - '[style.transition]': 'transition' - } - }) - export class MyComponent { - color = 'red'; - transition = 'all 1337ms ease'; - yesToApple = true; - tesToTomato = false; - - @HostBinding('style.border') - border = '1px solid purple'; - - @HostBinding('class.orange') - yesToOrange = true; - } - ` - } - }; - - const template = ` - … - MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ - … - hostBindings: function MyComponent_HostBindings(rf, $ctx$) { - … - if (rf & 2) { - $r3$.ɵɵstyleProp("color", $ctx$.color)("transition", $ctx$.transition)("border", $ctx$.border); - $r3$.ɵɵclassProp("apple", $ctx$.yesToApple)("tomato", $ctx$.yesToTomato)("orange", $ctx$.yesToOrange); - } - }, - … - }); - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - }); - - it('should count only non-style and non-class host bindings on Components', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule, HostBinding} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: '', - host: { - 'style': 'width:200px; height:500px', - 'class': 'foo baz', - 'title': 'foo title' - } - }) - export class MyComponent { - @HostBinding('style') - myStyle = {width:'100px'}; - - @HostBinding('class') - myClass = {bar:false}; - - @HostBinding('id') - id = 'some id'; - - @HostBinding('title') - title = 'some title'; - - @Input('name') - name = ''; - } - - @NgModule({declarations: [MyComponent]}) - export class MyModule {} - ` - } - }; - - const template = ` - hostAttrs: ["title", "foo title", ${AttributeMarker.Classes}, "foo", "baz", ${ - AttributeMarker.Styles}, "width", "200px", "height", "500px"], - hostVars: 6, - hostBindings: function MyComponent_HostBindings(rf, ctx) { - if (rf & 2) { - $r3$.ɵɵhostProperty("id", ctx.id)("title", ctx.title); - $r3$.ɵɵstyleMap(ctx.myStyle); - $r3$.ɵɵclassMap(ctx.myClass); - } - } - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - it('should count only non-style and non-class host bindings on Directives', () => { - const files = { - app: { - 'spec.ts': ` - import {Directive, Component, NgModule, HostBinding} from '@angular/core'; - - @Directive({selector: '[myWidthDir]'}) - export class WidthDirective { - @HostBinding('style.width') - myWidth = 200; - - @HostBinding('class.foo') - myFooClass = true; - - @HostBinding('id') - id = 'some id'; - - @HostBinding('title') - title = 'some title'; - } - ` - } - }; - - const template = ` - hostVars: 6, - hostBindings: function WidthDirective_HostBindings(rf, ctx) { - if (rf & 2) { - $r3$.ɵɵhostProperty("id", ctx.id)("title", ctx.title); - $r3$.ɵɵstyleProp("width", ctx.myWidth); - $r3$.ɵɵclassProp("foo", ctx.myFooClass); - } - } - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - - describe('new styling refactor', () => { - it('should generate the correct amount of host bindings when styling is present', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, Directive, NgModule} from '@angular/core'; - - @Directive({ - selector: '[my-dir]', - host: { - '[title]': 'title', - '[class.foo]': 'foo', - '[@anim]': \`{ - value: _animValue, - params: { - param1: _animParam1, - param2: _animParam2 - } - }\` - } - }) - export class MyDir { - title = ''; - foo = true; - _animValue = null; - _animParam1 = null; - _animParam2 = null; - } - - @Component({ - selector: 'my-app', - template: \` -
- \` - }) - export class MyAppComp {} - - @NgModule({declarations: [MyAppComp, MyDir]}) - export class MyModule {} - ` - } - }; - - const template = ` - hostVars: 10, - hostBindings: function MyDir_HostBindings(rf, ctx) { - if (rf & 2) { - $r3$.ɵɵhostProperty("title", ctx.title); - $r3$.ɵɵsyntheticHostProperty("@anim", - $r3$.ɵɵpureFunction2(7, _c1, ctx._animValue, - $r3$.ɵɵpureFunction2(4, _c0, ctx._animParam1, ctx._animParam2))); - $r3$.ɵɵclassProp("foo", ctx.foo); - } - } - `; - - const result = compile(files, angularFiles); - expectEmit(result.source, template, 'Incorrect template'); - }); - }); -}); diff --git a/packages/compiler-cli/test/compliance_old/r3_view_compiler_template_spec.ts b/packages/compiler-cli/test/compliance_old/r3_view_compiler_template_spec.ts deleted file mode 100644 index 26fd0703e4..0000000000 --- a/packages/compiler-cli/test/compliance_old/r3_view_compiler_template_spec.ts +++ /dev/null @@ -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: \` -
    -
  • -
    - {{format(outer, middle, inner, component)}} -
    -
  • -
\` - }) - 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: \` -
- \` - }) - 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: \` -
-
- \` - }) - 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: \` - - {{ i }} - {{ item }} - \` - }) - 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: \` -
- - {{ i }} - {{ item }} - -
\` - }) - 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: \` -
-
-
- {{ middle.value }} - {{ name }} -
-
-
\` - }) - 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 ', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: \` - - some-content - \` - }) - 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 ', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - template: 'some-content', - }) - 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 ', () => { - const files = { - app: { - 'spec.ts': ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - selector: 'my-component', - 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 ', () => { - 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: '', - }) - 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 (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: '', - }) - 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: \` -
-

less than 10

-

less than 10

-
-
-

more than 10

-
- \`, - }) - 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: \` -
- -

less than 10

-

less than 10

-
- -

less than 10

-
-
-
- -

more than 10

-
-
- \`, - }) - 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: \` - - \`, - }) - export class AComponent { - show = true; - } - - @Component({ - selector: 'b-component', - template: \` - - \`, - }) - 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: \` -
-

{{ item }}

-

{{ item }}

-
-
-

{{ item }}

-
- \`, - }) - 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: \` -
\` - }) - 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'); - }); -});