Compare commits

...

27 Commits

Author SHA1 Message Date
Igor Minar 03b35d2e8f docs(changelog): add release notes for 2.4.2 2017-01-06 12:40:35 -08:00
Igor Minar 722543739e chore(release): cut the 2.4.2 release 2017-01-06 12:37:50 -08:00
Chuck Jazdzewski 56b4296a09 fix(language-service): support TypeScript 2.1 (#13655)
@angular/language-service now supports using TypeScript 2.1 as the
the TypeScript host. TypeScript 2.1 is now also partially supported
in `ngc` but is not recommended as Tsickle does not yet support 2.1.
2017-01-06 11:00:12 -08:00
Matias Niemelä f1cde4339b fix(core): animations no longer silently exits if the element is not apart of the DOM (#13763) 2017-01-06 11:00:12 -08:00
Matias Niemelä b245b920a6 fix(core): animations should blend in all previously transitioned styles into next animation if interrupted (#13148) 2017-01-06 11:00:12 -08:00
Victor Berchet f47a71689c refactor: remove unused imports 2017-01-06 11:00:11 -08:00
Victor Berchet 6be55cc214 fix(Common): allow null/undefined values for NgForTrackBy
Reverts a breaking change introduced in 2.4.1 by #13420
fixes #13641
2017-01-06 11:00:11 -08:00
Dzmitry Shylovich 504199cf5a docs(common): add an example how to bind multiple classes based on a single parameter (#13779)
Closes #13778
2017-01-06 11:00:11 -08:00
Dzmitry Shylovich 17c5fa9293 fix(forms): Validators.required properly validate arrays (#13362)
Closes #12274
2017-01-06 11:00:11 -08:00
Dzmitry Shylovich 5f49c3ed23 fix(common): do not override locale provided on bootstrap (#13654)
Closes #13607
2017-01-06 11:00:11 -08:00
Dimitri Benin ebba63057f docs(developer): add linting section and correct command to verify API changes 2017-01-06 11:00:11 -08:00
Tobias Bosch 5058461af7 feat(benchmarks): add detectChanges test for ng2 tree benchmark 2017-01-06 11:00:11 -08:00
William KOZA 21f5f05893 fix(core): Remove reference to "Angular 2" in dev mode warning (#13751) 2017-01-06 11:00:11 -08:00
Jon Walsh f2ee81fa7a Typo (#13698) 2017-01-06 11:00:11 -08:00
Emanuel Hein ae1029da35 docs(Http): fix and extend samples for testing/MockBackend (#13689)
Fix samples for MockBackend and MockBackend.connections that were outdated. Also extend central sample for MockBackend to ease getting started.
2017-01-06 11:00:10 -08:00
Tobias Bosch 230e33f3f1 fix(compiler): don’t throw when using ANALYZE_FOR_ENTRY_COMPONENTS with user classes (#13679)
Fixed #13565
2017-01-05 21:21:33 -08:00
Tsuyoshi Ito ec0ca01224 docs(Core): fix API docs for ContentChild and ViewChildren (#13656)
Move the documentations of the ContentChild and ViewChildren decorators
so that they appear correctly on angular.io.

Closes #13625
2017-01-05 21:21:22 -08:00
Tobias Bosch 1cd73c7a79 fix(compiler): query <template> elements before their children. (#13677)
Fixes #13118
Closes #13167
2017-01-05 21:20:56 -08:00
Dzmitry Shylovich 9f6a647908 fix(router): update route snapshot before emit new values (#13558)
Closes #12912
2017-01-05 21:20:45 -08:00
Victor Berchet 29ffdfdffe fix(Compiler): allow "." in attribute selectors (#13653)
fixes #13645
2017-01-05 21:14:54 -08:00
Dzmitry Shylovich 5754ecc3e1 fix(router): fix lazy loaded module with wildcard route (#13649)
Closes #12955
2017-01-05 21:14:42 -08:00
Victor Berchet dab15c79dd chore(tslint): update tslint to 4.x (#13603) 2017-01-05 21:14:28 -08:00
Georgios Kalpakas 21942a88f0 fix(upgrade): fix/improve support for lifecycle hooks (#13020)
With the exception of `$onChanges()`, all lifecycle hooks in ng1 are called on
the controller, regardless if it is the binding destination or not (i.e.
regardless of the value of `bindToController`).

This change makes `upgrade` mimic that behavior when calling lifecycle hooks.

Additionally, calling the `$onInit()` hook has been moved before calling the
linking functions, which also mimics the ng1 behavior.
2017-01-05 21:14:14 -08:00
Dzmitry Shylovich 018865ee6b fix(router): routerLink support of null/undefined (#13380)
Closes #6971
2017-01-05 21:11:56 -08:00
Dzmitry Shylovich f7234378b6 fix(common): add link to trackBy docs (#13634) 2017-01-05 21:11:34 -08:00
Chase 5f47583c94 fixed minor typo (#13626) 2017-01-05 21:06:13 -08:00
Dzmitry Shylovich 0e7f9f0bff fix(testing): improve misleading error message when don't call compileComponents (#13543)
Closes #11301
2017-01-05 21:05:39 -08:00
114 changed files with 4178 additions and 6038 deletions
+25
View File
@@ -1,3 +1,28 @@
<a name="2.4.2"></a>
## [2.4.2](https://github.com/angular/angular/compare/2.4.1...2.4.2) (2017-01-06)
### Bug Fixes
* **common:** add link to trackBy docs from the error message ([#13634](https://github.com/angular/angular/issues/13634)) ([f723437](https://github.com/angular/angular/commit/f723437))
* **common:** do not override locale provided on bootstrap ([#13654](https://github.com/angular/angular/issues/13654)) ([5f49c3e](https://github.com/angular/angular/commit/5f49c3e)), closes [#13607](https://github.com/angular/angular/issues/13607)
* **common:** allow null/undefined values for `NgForTrackBy` ([6be55cc](https://github.com/angular/angular/commit/6be55cc)), closes [#13641](https://github.com/angular/angular/issues/13641)
* **compiler:** don’t throw when using `ANALYZE_FOR_ENTRY_COMPONENTS` with user classes ([#13679](https://github.com/angular/angular/issues/13679)) ([230e33f](https://github.com/angular/angular/commit/230e33f)), closes [#13565](https://github.com/angular/angular/issues/13565)
* **compiler:** query `<template>` elements before their children. ([#13677](https://github.com/angular/angular/issues/13677)) ([1cd73c7](https://github.com/angular/angular/commit/1cd73c7)), closes [#13118](https://github.com/angular/angular/issues/13118) [#13167](https://github.com/angular/angular/issues/13167)
* **compiler:** allow "." in attribute selectors ([#13653](https://github.com/angular/angular/issues/13653)) ([29ffdfd](https://github.com/angular/angular/commit/29ffdfd)), closes [#13645](https://github.com/angular/angular/issues/13645)
* **core:** animations no longer silently exits if the element is not apart of the DOM ([#13763](https://github.com/angular/angular/issues/13763)) ([f1cde43](https://github.com/angular/angular/commit/f1cde43))
* **core:** animations should blend in all previously transitioned styles into next animation if interrupted ([#13148](https://github.com/angular/angular/issues/13148)) ([b245b92](https://github.com/angular/angular/commit/b245b92))
* **core:** remove reference to "Angular 2" in dev mode warning ([#13751](https://github.com/angular/angular/issues/13751)) ([21f5f05](https://github.com/angular/angular/commit/21f5f05))
* **core/testing:** improve misleading error message when don't call compileComponents ([#13543](https://github.com/angular/angular/issues/13543)) ([0e7f9f0](https://github.com/angular/angular/commit/0e7f9f0)), closes [#11301](https://github.com/angular/angular/issues/11301)
* **forms:** Validators.required properly validate arrays ([#13362](https://github.com/angular/angular/issues/13362)) ([17c5fa9](https://github.com/angular/angular/commit/17c5fa9)), closes [#12274](https://github.com/angular/angular/issues/12274)
* **language-service:** support TypeScript 2.1 ([#13655](https://github.com/angular/angular/issues/13655)) ([56b4296](https://github.com/angular/angular/commit/56b4296))
* **router:** fix lazy loaded module with wildcard route ([#13649](https://github.com/angular/angular/issues/13649)) ([5754ecc](https://github.com/angular/angular/commit/5754ecc)), closes [#12955](https://github.com/angular/angular/issues/12955)
* **router:** routerLink support of null ([#13380](https://github.com/angular/angular/issues/13380)) ([018865e](https://github.com/angular/angular/commit/018865e)), closes [#6971](https://github.com/angular/angular/issues/6971)
* **router:** update route snapshot before emit new values ([#13558](https://github.com/angular/angular/issues/13558)) ([9f6a647](https://github.com/angular/angular/commit/9f6a647)), closes [#12912](https://github.com/angular/angular/issues/12912)
* **upgrade:** fix/improve support for lifecycle hooks ([#13020](https://github.com/angular/angular/issues/13020)) ([21942a8](https://github.com/angular/angular/commit/21942a8))
<a name="2.4.1"></a>
## [2.4.1](https://github.com/angular/angular/compare/2.4.0...2.4.1) (2016-12-21)
+10 -1
View File
@@ -133,7 +133,8 @@ If you happen to modify the public API of Angular, API golden files must be upda
$ gulp public-api:update
```
Note: The command `./test.sh tools` fails when the API doesn't match the golden files.
Note: The command `gulp public-api:enforce` fails when the API doesn't match the golden files. Make sure to rebuild
the project before trying to verify after an API change.
## <a name="clang-format"></a> Formatting your source code
@@ -146,6 +147,14 @@ You can automatically format your code by running:
$ gulp format
```
## Linting/verifying your source code
You can check that your code is properly formatted and adheres to coding style by running:
``` shell
$ gulp lint
```
## Publishing your own personal snapshot build
You may find that your un-merged change needs some validation from external participants.
@@ -25,6 +25,8 @@ import {isPresent, stringify} from '../facade/lang';
* <some-element [ngClass]="{'first': true, 'second': true, 'third': false}">...</some-element>
*
* <some-element [ngClass]="stringExp|arrayExp|objExp">...</some-element>
*
* <some-element [ngClass]="{'class1 class2 class3' : true}">...</some-element>
* ```
*
* @description
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, Directive, DoCheck, EmbeddedViewRef, Input, IterableDiffer, IterableDiffers, OnChanges, SimpleChanges, TemplateRef, TrackByFn, ViewContainerRef} from '@angular/core';
import {ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, Directive, DoCheck, EmbeddedViewRef, Input, IterableDiffer, IterableDiffers, OnChanges, SimpleChanges, TemplateRef, TrackByFn, ViewContainerRef, isDevMode} from '@angular/core';
import {getTypeNameForDebugging} from '../facade/lang';
@@ -91,8 +91,13 @@ export class NgFor implements DoCheck, OnChanges {
@Input() ngForOf: any;
@Input()
set ngForTrackBy(fn: TrackByFn) {
if (typeof fn !== 'function') {
throw new Error(`trackBy must be a function, but received ${JSON.stringify(fn)}`);
if (isDevMode() && fn != null && typeof fn !== 'function') {
// TODO(vicb): use a log service once there is a public one available
if (<any>console && <any>console.warn) {
console.warn(
`trackBy must be a function, but received ${JSON.stringify(fn)}. ` +
`See https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html#!#change-propagation for more information.`);
}
}
this._trackByFn = fn;
}
@@ -294,14 +294,25 @@ export function main() {
}));
describe('track by', () => {
it('should throw if trackBy is not a function', async(() => {
it('should console.warn if trackBy is not a function', async(() => {
// TODO(vicb): expect a warning message when we have a proper log service
const template =
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="item?.id"></template>`;
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="value"></template>`;
fixture = createTestComponent(template);
fixture.componentInstance.value = 0;
fixture.detectChanges();
}));
getComponent().items = [{id: 1}, {id: 2}];
expect(() => fixture.detectChanges())
.toThrowError(/trackBy must be a function, but received null/);
it('should track by identity when trackBy is to `null` or `undefined`', async(() => {
// TODO(vicb): expect no warning message when we have a proper log service
const template =
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="value">{{ item }}</template>`;
fixture = createTestComponent(template);
fixture.componentInstance.items = ['a', 'b', 'c'];
fixture.componentInstance.value = null;
detectChangesAndExpectText('abc');
fixture.componentInstance.value = undefined;
detectChangesAndExpectText('abc');
}));
it('should set the context to the component instance', async(() => {
@@ -343,6 +354,7 @@ export function main() {
getComponent().items = [{'id': 'a', 'color': 'red'}];
detectChangesAndExpectText('red');
}));
it('should move items around and keep them updated ', async(() => {
const template =
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`;
@@ -378,6 +390,7 @@ class Foo {
@Component({selector: 'test-cmp', template: ''})
class TestComponent {
@ContentChild(TemplateRef) contentTpl: TemplateRef<Object>;
value: any;
items: any[] = [1, 2];
trackById(index: number, item: any): string { return item['id']; }
trackByIndex(index: number, item: any): number { return index; }
@@ -394,4 +407,4 @@ const TEMPLATE = '<div><span template="ngFor let item of items">{{item.toString(
function createTestComponent(template: string = TEMPLATE): ComponentFixture<TestComponent> {
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
.createComponent(TestComponent);
}
}
@@ -15,6 +15,8 @@
"declaration": true,
"lib": ["es6", "dom"],
"baseUrl": ".",
"outDir": "../node_modules/third_party"
}
"outDir": "../node_modules/third_party",
// Prevent scanning up the directory tree for types
"typeRoots": ["node_modules/@types"]
}
}
@@ -14,7 +14,9 @@
"rootDir": "",
"declaration": true,
"lib": ["es6", "dom"],
"baseUrl": "."
"baseUrl": ".",
// Prevent scanning up the directory tree for types
"typeRoots": ["node_modules/@types"]
},
"files": [
@@ -11,7 +11,6 @@
* Intended to be used in a build step.
*/
import * as compiler from '@angular/compiler';
import {ViewEncapsulation} from '@angular/core';
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
import {readFileSync} from 'fs';
import * as path from 'path';
@@ -19,7 +18,6 @@ import * as ts from 'typescript';
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
import {Console} from './private_import_core';
const GENERATED_META_FILES = /\.json$/;
@@ -9,10 +9,10 @@
import {__core_private__ as r} from '@angular/core';
export type ReflectorReader = typeof r._ReflectorReader;
export var ReflectorReader: typeof r.ReflectorReader = r.ReflectorReader;
export const ReflectorReader: typeof r.ReflectorReader = r.ReflectorReader;
export type ReflectionCapabilities = typeof r._ReflectionCapabilities;
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export type Console = typeof r._Console;
export var Console: typeof r.Console = r.Console;
export const Console: typeof r.Console = r.Console;
+2 -4
View File
@@ -122,11 +122,9 @@ export class MockCompilerHost implements ts.CompilerHost {
return ts.getDefaultLibFileName(options);
}
writeFile: ts.WriteFileCallback = (fileName, text) => { this.context.writeFile(fileName, text); }
writeFile: ts.WriteFileCallback = (fileName, text) => { this.context.writeFile(fileName, text); };
getCurrentDirectory(): string {
return this.context.currentDirectory;
}
getCurrentDirectory(): string { return this.context.currentDirectory; }
getCanonicalFileName(fileName: string): string { return fileName; }
@@ -9,5 +9,5 @@
import {__core_private__ as r} from '@angular/core';
export type ReflectionCapabilities = typeof r._ReflectionCapabilities;
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export var reflector: typeof r.reflector = r.reflector;
export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export const reflector: typeof r.reflector = r.reflector;
@@ -18,7 +18,7 @@ import {DEFAULT_INTERPOLATION_CONFIG} from './ml_parser/interpolation_config';
import {ClassBuilder, createClassStmt} from './output/class_builder';
import * as o from './output/output_ast';
import {ParseError, ParseErrorLevel, ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
import {Console, LifecycleHooks, isDefaultChangeDetectionStrategy} from './private_import_core';
import {Console, LifecycleHooks} from './private_import_core';
import {ElementSchemaRegistry} from './schema/element_schema_registry';
import {BindingParser} from './template_parser/binding_parser';
import {BoundElementPropertyAst, BoundEventAst} from './template_parser/template_ast';
@@ -120,7 +120,7 @@ function newErrorToken(index: number, message: string): Token {
return new Token(index, TokenType.Error, 0, message);
}
export var EOF: Token = new Token(-1, TokenType.Character, 0, '');
export const EOF: Token = new Token(-1, TokenType.Character, 0, '');
class _Scanner {
length: number;
@@ -68,13 +68,13 @@ export class MapType extends Type {
visitType(visitor: TypeVisitor, context: any): any { return visitor.visitMapType(this, context); }
}
export var DYNAMIC_TYPE = new BuiltinType(BuiltinTypeName.Dynamic);
export var BOOL_TYPE = new BuiltinType(BuiltinTypeName.Bool);
export var INT_TYPE = new BuiltinType(BuiltinTypeName.Int);
export var NUMBER_TYPE = new BuiltinType(BuiltinTypeName.Number);
export var STRING_TYPE = new BuiltinType(BuiltinTypeName.String);
export var FUNCTION_TYPE = new BuiltinType(BuiltinTypeName.Function);
export var NULL_TYPE = new BuiltinType(BuiltinTypeName.Null);
export const DYNAMIC_TYPE = new BuiltinType(BuiltinTypeName.Dynamic);
export const BOOL_TYPE = new BuiltinType(BuiltinTypeName.Bool);
export const INT_TYPE = new BuiltinType(BuiltinTypeName.Int);
export const NUMBER_TYPE = new BuiltinType(BuiltinTypeName.Number);
export const STRING_TYPE = new BuiltinType(BuiltinTypeName.String);
export const FUNCTION_TYPE = new BuiltinType(BuiltinTypeName.Function);
export const NULL_TYPE = new BuiltinType(BuiltinTypeName.Null);
export interface TypeVisitor {
visitBuiltintType(type: BuiltinType, context: any): any;
@@ -451,12 +451,12 @@ export interface ExpressionVisitor {
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any;
}
export var THIS_EXPR = new ReadVarExpr(BuiltinVar.This);
export var SUPER_EXPR = new ReadVarExpr(BuiltinVar.Super);
export var CATCH_ERROR_VAR = new ReadVarExpr(BuiltinVar.CatchError);
export var CATCH_STACK_VAR = new ReadVarExpr(BuiltinVar.CatchStack);
export var NULL_EXPR = new LiteralExpr(null, null);
export var TYPED_NULL_EXPR = new LiteralExpr(null, NULL_TYPE);
export const THIS_EXPR = new ReadVarExpr(BuiltinVar.This);
export const SUPER_EXPR = new ReadVarExpr(BuiltinVar.Super);
export const CATCH_ERROR_VAR = new ReadVarExpr(BuiltinVar.CatchError);
export const CATCH_STACK_VAR = new ReadVarExpr(BuiltinVar.CatchStack);
export const NULL_EXPR = new LiteralExpr(null, null);
export const TYPED_NULL_EXPR = new LiteralExpr(null, NULL_TYPE);
//// Statements
export enum StmtModifier {
+6 -6
View File
@@ -9,12 +9,12 @@
import {getHtmlTagDefinition} from './ml_parser/html_tags';
const _SELECTOR_REGEXP = new RegExp(
'(\\:not\\()|' + //":not("
'([-\\w]+)|' + // "tag"
'(?:\\.([-\\w]+))|' + // ".class"
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
'(\\))|' + // ")"
'(\\s*,\\s*)', // ","
'(\\:not\\()|' + //":not("
'([-\\w]+)|' + // "tag"
'(?:\\.([-\\w]+))|' + // ".class"
'(?:\\[([.-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
'(\\))|' + // ")"
'(\\s*,\\s*)', // ","
'g');
/**
@@ -149,7 +149,7 @@ export class TemplateParser {
return new TemplateParseResult(result, errors);
}
if (isPresent(this.transforms)) {
if (this.transforms) {
this.transforms.forEach(
(transform: TemplateAstVisitor) => { result = templateVisitAll(transform, result); });
}
@@ -218,7 +218,7 @@ class TemplateParseVisitor implements html.Visitor {
visitText(text: html.Text, parent: ElementContext): any {
const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR);
const expr = this._bindingParser.parseInterpolation(text.value, text.sourceSpan);
if (isPresent(expr)) {
if (expr) {
return new BoundTextAst(expr, ngContentIndex, text.sourceSpan);
} else {
return new TextAst(text.value, ngContentIndex, text.sourceSpan);
@@ -248,14 +248,14 @@ class TemplateParseVisitor implements html.Visitor {
return null;
}
const matchableAttrs: string[][] = [];
const matchableAttrs: [string, string][] = [];
const elementOrDirectiveProps: BoundProperty[] = [];
const elementOrDirectiveRefs: ElementOrDirectiveRef[] = [];
const elementVars: VariableAst[] = [];
const events: BoundEventAst[] = [];
const templateElementOrDirectiveProps: BoundProperty[] = [];
const templateMatchableAttrs: string[][] = [];
const templateMatchableAttrs: [string, string][] = [];
const templateElementVars: VariableAst[] = [];
let hasInlineTemplates = false;
@@ -268,14 +268,17 @@ class TemplateParseVisitor implements html.Visitor {
isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, events,
elementOrDirectiveRefs, elementVars);
let templateBindingsSource: string|undefined = undefined;
let prefixToken: string|undefined = undefined;
if (this._normalizeAttributeName(attr.name) == TEMPLATE_ATTR) {
let templateBindingsSource: string|undefined;
let prefixToken: string|undefined;
let normalizedName = this._normalizeAttributeName(attr.name);
if (normalizedName == TEMPLATE_ATTR) {
templateBindingsSource = attr.value;
} else if (attr.name.startsWith(TEMPLATE_ATTR_PREFIX)) {
} else if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
templateBindingsSource = attr.value;
prefixToken = attr.name.substring(TEMPLATE_ATTR_PREFIX.length); // remove the star
prefixToken = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length);
}
const hasTemplateBinding = isPresent(templateBindingsSource);
if (hasTemplateBinding) {
if (hasInlineTemplates) {
@@ -541,10 +544,12 @@ class TemplateParseVisitor implements html.Visitor {
elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[]): DirectiveAst[] {
const matchedReferences = new Set<string>();
let component: CompileDirectiveSummary = null;
const directiveAsts = directives.map((directive) => {
const sourceSpan = new ParseSourceSpan(
elementSourceSpan.start, elementSourceSpan.end,
`Directive ${identifierName(directive.type)}`);
if (directive.isComponent) {
component = directive;
}
@@ -567,6 +572,7 @@ class TemplateParseVisitor implements html.Visitor {
return new DirectiveAst(
directive, directiveProperties, hostProperties, hostEvents, sourceSpan);
});
elementOrDirectiveRefs.forEach((elOrDirRef) => {
if (elOrDirRef.value.length > 0) {
if (!matchedReferences.has(elOrDirRef.name)) {
@@ -581,7 +587,7 @@ class TemplateParseVisitor implements html.Visitor {
}
targetReferences.push(new ReferenceAst(elOrDirRef.name, refToken, elOrDirRef.sourceSpan));
}
}); // fix syntax highlighting issue: `
});
return directiveAsts;
}
@@ -742,7 +748,7 @@ class NonBindableVisitor implements html.Visitor {
return null;
}
const attrNameAndValues = ast.attrs.map(attrAst => [attrAst.name, attrAst.value]);
const attrNameAndValues = ast.attrs.map((attr): [string, string] => [attr.name, attr.value]);
const selector = createElementCssSelector(ast.name, attrNameAndValues);
const ngContentIndex = parent.findNgContentIndex(selector);
const children = html.visitAll(this, ast.children, EMPTY_ELEMENT_CONTEXT);
@@ -811,16 +817,16 @@ class ElementContext {
}
export function createElementCssSelector(
elementName: string, matchableAttrs: string[][]): CssSelector {
elementName: string, attributes: [string, string][]): CssSelector {
const cssSelector = new CssSelector();
const elNameNoNs = splitNsName(elementName)[1];
cssSelector.setElement(elNameNoNs);
for (let i = 0; i < matchableAttrs.length; i++) {
const attrName = matchableAttrs[i][0];
for (let i = 0; i < attributes.length; i++) {
const attrName = attributes[i][0];
const attrNameNoNs = splitNsName(attrName)[1];
const attrValue = matchableAttrs[i][1];
const attrValue = attributes[i][1];
cssSelector.addAttribute(attrNameNoNs, attrValue);
if (attrName.toLowerCase() == CLASS_ATTR) {
@@ -26,7 +26,7 @@ export function createOfflineCompileUrlResolver(): UrlResolver {
/**
* A default provider for {@link PACKAGE_ROOT_URL} that maps to '/'.
*/
export var DEFAULT_PACKAGE_URL_PROVIDER = {
export const DEFAULT_PACKAGE_URL_PROVIDER = {
provide: PACKAGE_ROOT_URL,
useValue: '/'
};
@@ -211,12 +211,7 @@ export class CompileElement extends CompileNode {
const directiveInstance = this.instances.get(tokenReference(identifierToken(directive.type)));
directive.queries.forEach((queryMeta) => { this._addQuery(queryMeta, directiveInstance); });
}
const queriesWithReads: _QueryWithRead[] = [];
Array.from(this._resolvedProviders.values()).forEach((resolvedProvider) => {
const queriesForProvider = this._getQueriesFor(resolvedProvider.token);
queriesWithReads.push(
...queriesForProvider.map(query => new _QueryWithRead(query, resolvedProvider.token)));
});
Object.keys(this.referenceTokens).forEach(varName => {
const token = this.referenceTokens[varName];
let varValue: o.Expression;
@@ -226,27 +221,6 @@ export class CompileElement extends CompileNode {
varValue = this.renderNode;
}
this.view.locals.set(varName, varValue);
const varToken = {value: varName};
queriesWithReads.push(
...this._getQueriesFor(varToken).map(query => new _QueryWithRead(query, varToken)));
});
queriesWithReads.forEach((queryWithRead) => {
let value: o.Expression;
if (isPresent(queryWithRead.read.identifier)) {
// query for an identifier
value = this.instances.get(tokenReference(queryWithRead.read));
} else {
// query for a reference
const token = this.referenceTokens[queryWithRead.read.value];
if (isPresent(token)) {
value = this.instances.get(tokenReference(token));
} else {
value = this.elementRef;
}
}
if (isPresent(value)) {
queryWithRead.query.addValue(value, this.view);
}
});
}
@@ -265,12 +239,14 @@ export class CompileElement extends CompileNode {
this.view.injectorGetMethod.addStmt(createInjectInternalCondition(
this.nodeIndex, providerChildNodeCount, resolvedProvider, providerExpr));
});
}
finish() {
Array.from(this._queries.values())
.forEach(
queries => queries.forEach(
q =>
q.afterChildren(this.view.createMethod, this.view.updateContentQueriesMethod)));
q => q.generateStatements(
this.view.createMethod, this.view.updateContentQueriesMethod)));
}
addContentNode(ngContentIndex: number, nodeExpr: CompileViewRootNode) {
@@ -283,12 +259,11 @@ export class CompileElement extends CompileNode {
null;
}
getProviderTokens(): o.Expression[] {
return Array.from(this._resolvedProviders.values())
.map((resolvedProvider) => createDiTokenExpression(resolvedProvider.token));
getProviderTokens(): CompileTokenMetadata[] {
return Array.from(this._resolvedProviders.values()).map(provider => provider.token);
}
private _getQueriesFor(token: CompileTokenMetadata): CompileQuery[] {
getQueriesFor(token: CompileTokenMetadata): CompileQuery[] {
const result: CompileQuery[] = [];
let currentEl: CompileElement = this;
let distance = 0;
@@ -426,10 +401,3 @@ function createProviderProperty(
}
return o.THIS_EXPR.prop(propName);
}
class _QueryWithRead {
public read: CompileTokenMetadata;
constructor(public query: CompileQuery, match: CompileTokenMetadata) {
this.read = query.meta.read || match;
}
}
@@ -64,7 +64,7 @@ export class CompileQuery {
return !this._values.values.some(value => value instanceof ViewQueryValues);
}
afterChildren(targetStaticMethod: CompileMethod, targetDynamicMethod: CompileMethod) {
generateStatements(targetStaticMethod: CompileMethod, targetDynamicMethod: CompileMethod) {
const values = createQueryValues(this._values);
const updateStmts = [this.queryList.callMethod('reset', [o.literalArr(values)]).toStmt()];
if (isPresent(this.ownerDirectiveExpression)) {
@@ -154,11 +154,11 @@ export class CompileView implements NameResolver {
}
}
afterNodes() {
finish() {
Array.from(this.viewQueries.values())
.forEach(
queries => queries.forEach(
q => q.afterChildren(this.createMethod, this.updateViewQueriesMethod)));
q => q.generateStatements(this.createMethod, this.updateViewQueriesMethod)));
}
}
@@ -133,4 +133,4 @@ type EventSummary = {
name: string,
target: string,
phase: string
}
};
@@ -0,0 +1,55 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {CompileQueryMetadata, CompileTokenMetadata, tokenReference} from '../compile_metadata';
import * as o from '../output/output_ast';
import {CompileElement} from './compile_element';
import {CompileQuery} from './compile_query';
// Note: We can't do this when we create the CompileElements already,
// as we create embedded views before the <template> elements themselves.
export function bindQueryValues(ce: CompileElement) {
const queriesWithReads: _QueryWithRead[] = [];
ce.getProviderTokens().forEach((token) => {
const queriesForProvider = ce.getQueriesFor(token);
queriesWithReads.push(...queriesForProvider.map(query => new _QueryWithRead(query, token)));
});
Object.keys(ce.referenceTokens).forEach(varName => {
const token = ce.referenceTokens[varName];
const varToken = {value: varName};
queriesWithReads.push(
...ce.getQueriesFor(varToken).map(query => new _QueryWithRead(query, varToken)));
});
queriesWithReads.forEach((queryWithRead) => {
let value: o.Expression;
if (queryWithRead.read.identifier) {
// query for an identifier
value = ce.instances.get(tokenReference(queryWithRead.read));
} else {
// query for a reference
const token = ce.referenceTokens[queryWithRead.read.value];
if (token) {
value = ce.instances.get(tokenReference(token));
} else {
value = ce.elementRef;
}
}
if (value) {
queryWithRead.query.addValue(value, ce.view);
}
});
}
class _QueryWithRead {
public read: CompileTokenMetadata;
constructor(public query: CompileQuery, match: CompileTokenMetadata) {
this.read = query.meta.read || match;
}
}
@@ -15,6 +15,7 @@ import {CompileView} from './compile_view';
import {bindOutputs} from './event_binder';
import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindDirectiveWrapperLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder';
import {bindDirectiveHostProps, bindDirectiveInputs, bindRenderInputs, bindRenderText} from './property_binder';
import {bindQueryValues} from './query_binder';
export function bindView(
view: CompileView, parsedTemplate: TemplateAst[], schemaRegistry: ElementSchemaRegistry): void {
@@ -43,6 +44,7 @@ class ViewBinderVisitor implements TemplateAstVisitor {
visitElement(ast: ElementAst, parent: CompileElement): any {
const compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
bindQueryValues(compileElement);
const hasEvents = bindOutputs(ast.outputs, ast.directives, compileElement, true);
bindRenderInputs(ast.inputs, ast.outputs, hasEvents, compileElement);
ast.directives.forEach((directiveAst, dirIndex) => {
@@ -75,6 +77,7 @@ class ViewBinderVisitor implements TemplateAstVisitor {
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: CompileElement): any {
const compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
bindQueryValues(compileElement);
bindOutputs(ast.outputs, ast.directives, compileElement, false);
ast.directives.forEach((directiveAst, dirIndex) => {
const directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference);
@@ -48,13 +48,16 @@ export function buildView(
}
export function finishView(view: CompileView, targetStatements: o.Statement[]) {
view.afterNodes();
createViewTopLevelStmts(view, targetStatements);
view.nodes.forEach((node) => {
if (node instanceof CompileElement && node.hasEmbeddedView) {
finishView(node.embeddedView, targetStatements);
if (node instanceof CompileElement) {
node.finish();
if (node.hasEmbeddedView) {
finishView(node.embeddedView, targetStatements);
}
}
});
view.finish();
createViewTopLevelStmts(view, targetStatements);
}
class ViewBuilderVisitor implements TemplateAstVisitor {
@@ -418,7 +421,9 @@ function createStaticNodeDebugInfo(node: CompileNode): o.Expression {
let componentToken: o.Expression = o.NULL_EXPR;
const varTokenEntries: any[] = [];
if (isPresent(compileElement)) {
providerTokens = compileElement.getProviderTokens();
providerTokens =
compileElement.getProviderTokens().map((token) => createDiTokenExpression(token));
if (isPresent(compileElement.component)) {
componentToken = createDiTokenExpression(identifierToken(compileElement.component.type));
}
@@ -0,0 +1,51 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Component, Directive, Input} from '@angular/core';
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {expect} from '@angular/platform-browser/testing/matchers';
export function main() {
describe('integration tests', () => {
let fixture: ComponentFixture<TestComponent>;
describe('directives', () => {
it('should support dotted selectors', async(() => {
@Directive({selector: '[dot.name]'})
class MyDir {
@Input('dot.name') value: string;
}
TestBed.configureTestingModule({
declarations: [
MyDir,
TestComponent,
],
});
const template = `<div [dot.name]="'foo'"></div>`;
fixture = createTestComponent(template);
fixture.detectChanges();
const myDir = fixture.debugElement.query(By.directive(MyDir)).injector.get(MyDir);
expect(myDir.value).toEqual('foo');
}));
});
});
}
@Component({selector: 'test-cmp', template: ''})
class TestComponent {
}
function createTestComponent(template: string): ComponentFixture<TestComponent> {
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
.createComponent(TestComponent);
}
@@ -44,7 +44,7 @@ const baseErrorIdentifier = {
runtime: BaseError
};
export var codegenExportsVars = [
export const codegenExportsVars = [
'getExpressions',
];
@@ -190,7 +190,7 @@ const _getExpressionsStmts: o.Statement[] = [
]))
];
export var codegenStmts: o.Statement[] = [
export const codegenStmts: o.Statement[] = [
new o.CommentStmt('This is a comment'),
new o.ClassStmt(
+86 -44
View File
@@ -30,14 +30,14 @@ export function main() {
it('should select by element name case sensitive', () => {
matcher.addSelectables(s1 = CssSelector.parse('someTag'), 1);
expect(matcher.match(CssSelector.parse('SOMEOTHERTAG')[0], selectableCollector))
expect(matcher.match(getSelectorFor({tag: 'SOMEOTHERTAG'}), selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('SOMETAG')[0], selectableCollector)).toEqual(false);
expect(matcher.match(getSelectorFor({tag: 'SOMETAG'}), selectableCollector)).toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('someTag')[0], selectableCollector)).toEqual(true);
expect(matcher.match(getSelectorFor({tag: 'someTag'}), selectableCollector)).toEqual(true);
expect(matched).toEqual([s1[0], 1]);
});
@@ -45,21 +45,22 @@ export function main() {
matcher.addSelectables(s1 = CssSelector.parse('.someClass'), 1);
matcher.addSelectables(s2 = CssSelector.parse('.someClass.class2'), 2);
expect(matcher.match(CssSelector.parse('.SOMEOTHERCLASS')[0], selectableCollector))
expect(matcher.match(getSelectorFor({classes: 'SOMEOTHERCLASS'}), selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('.SOMECLASS')[0], selectableCollector)).toEqual(true);
expect(matcher.match(getSelectorFor({classes: 'SOMECLASS'}), selectableCollector))
.toEqual(true);
expect(matched).toEqual([s1[0], 1]);
reset();
expect(matcher.match(CssSelector.parse('.someClass.class2')[0], selectableCollector))
expect(matcher.match(getSelectorFor({classes: 'someClass class2'}), selectableCollector))
.toEqual(true);
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
});
it('should not throw for class name "constructor"', () => {
expect(matcher.match(CssSelector.parse('.constructor')[0], selectableCollector))
expect(matcher.match(getSelectorFor({classes: 'constructor'}), selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
});
@@ -68,36 +69,43 @@ export function main() {
matcher.addSelectables(s1 = CssSelector.parse('[someAttr]'), 1);
matcher.addSelectables(s2 = CssSelector.parse('[someAttr][someAttr2]'), 2);
expect(matcher.match(CssSelector.parse('[SOMEOTHERATTR]')[0], selectableCollector))
expect(matcher.match(getSelectorFor({attrs: [['SOMEOTHERATTR', '']]}), selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('[SOMEATTR]')[0], selectableCollector)).toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('[SOMEATTR=someValue]')[0], selectableCollector))
expect(matcher.match(getSelectorFor({attrs: [['SOMEATTR', '']]}), selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('[someAttr][someAttr2]')[0], selectableCollector))
expect(
matcher.match(getSelectorFor({attrs: [['SOMEATTR', 'someValue']]}), selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(
matcher.match(
getSelectorFor({attrs: [['someAttr', ''], ['someAttr2', '']]}), selectableCollector))
.toEqual(true);
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
reset();
expect(matcher.match(
CssSelector.parse('[someAttr=someValue][someAttr2]')[0], selectableCollector))
getSelectorFor({attrs: [['someAttr', 'someValue'], ['someAttr2', '']]}),
selectableCollector))
.toEqual(true);
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
reset();
expect(matcher.match(
CssSelector.parse('[someAttr2][someAttr=someValue]')[0], selectableCollector))
getSelectorFor({attrs: [['someAttr2', ''], ['someAttr', 'someValue']]}),
selectableCollector))
.toEqual(true);
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
reset();
expect(matcher.match(
CssSelector.parse('[someAttr2=someValue][someAttr]')[0], selectableCollector))
getSelectorFor({attrs: [['someAttr2', 'someValue'], ['someAttr', '']]}),
selectableCollector))
.toEqual(true);
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
});
@@ -105,11 +113,13 @@ export function main() {
it('should support "." in attribute names', () => {
matcher.addSelectables(s1 = CssSelector.parse('[foo.bar]'), 1);
expect(matcher.match(CssSelector.parse('[barfoo]')[0], selectableCollector)).toEqual(false);
expect(matcher.match(getSelectorFor({attrs: [['barfoo', '']]}), selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
reset();
expect(matcher.match(CssSelector.parse('[foo.bar]')[0], selectableCollector)).toEqual(true);
expect(matcher.match(getSelectorFor({attrs: [['foo.bar', '']]}), selectableCollector))
.toEqual(true);
expect(matched).toEqual([s1[0], 1]);
});
@@ -127,15 +137,18 @@ export function main() {
it('should select by attr name case sensitive and value case insensitive', () => {
matcher.addSelectables(s1 = CssSelector.parse('[someAttr=someValue]'), 1);
expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEOTHERATTR]')[0], selectableCollector))
expect(matcher.match(
getSelectorFor({attrs: [['SOMEATTR', 'SOMEOTHERATTR']]}), selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEVALUE]')[0], selectableCollector))
expect(
matcher.match(getSelectorFor({attrs: [['SOMEATTR', 'SOMEVALUE']]}), selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('[someAttr=SOMEVALUE]')[0], selectableCollector))
expect(
matcher.match(getSelectorFor({attrs: [['someAttr', 'SOMEVALUE']]}), selectableCollector))
.toEqual(true);
expect(matched).toEqual([s1[0], 1]);
});
@@ -143,31 +156,38 @@ export function main() {
it('should select by element name, class name and attribute name with value', () => {
matcher.addSelectables(s1 = CssSelector.parse('someTag.someClass[someAttr=someValue]'), 1);
expect(
matcher.match(
getSelectorFor(
{tag: 'someOtherTag', classes: 'someOtherClass', attrs: [['someOtherAttr', '']]}),
selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(
CssSelector.parse('someOtherTag.someOtherClass[someOtherAttr]')[0],
getSelectorFor(
{tag: 'someTag', classes: 'someOtherClass', attrs: [['someOtherAttr', '']]}),
selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(
matcher.match(
CssSelector.parse('someTag.someOtherClass[someOtherAttr]')[0], selectableCollector))
expect(matcher.match(
getSelectorFor(
{tag: 'someTag', classes: 'someClass', attrs: [['someOtherAttr', '']]}),
selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(
CssSelector.parse('someTag.someClass[someOtherAttr]')[0], selectableCollector))
getSelectorFor({tag: 'someTag', classes: 'someClass', attrs: [['someAttr', '']]}),
selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(
matcher.match(CssSelector.parse('someTag.someClass[someAttr]')[0], selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(
matcher.match(
CssSelector.parse('someTag.someClass[someAttr=someValue]')[0], selectableCollector))
expect(matcher.match(
getSelectorFor(
{tag: 'someTag', classes: 'someClass', attrs: [['someAttr', 'someValue']]}),
selectableCollector))
.toEqual(true);
expect(matched).toEqual([s1[0], 1]);
});
@@ -217,7 +237,9 @@ export function main() {
matcher.addSelectables(CssSelector.parse(':not(p)'), 4);
matcher.addSelectables(CssSelector.parse(':not(p[someAttr])'), 5);
expect(matcher.match(CssSelector.parse('p.someClass[someAttr]')[0], selectableCollector))
expect(matcher.match(
getSelectorFor({tag: 'p', classes: 'someClass', attrs: [['someAttr', '']]}),
selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
});
@@ -228,32 +250,38 @@ export function main() {
matcher.addSelectables(s3 = CssSelector.parse(':not(.someClass)'), 3);
matcher.addSelectables(s4 = CssSelector.parse(':not(.someOtherClass[someAttr])'), 4);
expect(matcher.match(
CssSelector.parse('p[someOtherAttr].someOtherClass')[0], selectableCollector))
expect(
matcher.match(
getSelectorFor({tag: 'p', attrs: [['someOtherAttr', '']], classes: 'someOtherClass'}),
selectableCollector))
.toEqual(true);
expect(matched).toEqual([s1[0], 1, s2[0], 2, s3[0], 3, s4[0], 4]);
});
it('should match * with :not selector', () => {
matcher.addSelectables(CssSelector.parse(':not([a])'), 1);
expect(matcher.match(CssSelector.parse('div')[0], () => {})).toEqual(true);
expect(matcher.match(getSelectorFor({tag: 'div'}), () => {})).toEqual(true);
});
it('should match with multiple :not selectors', () => {
matcher.addSelectables(s1 = CssSelector.parse('div:not([a]):not([b])'), 1);
expect(matcher.match(CssSelector.parse('div[a]')[0], selectableCollector)).toBe(false);
expect(matcher.match(CssSelector.parse('div[b]')[0], selectableCollector)).toBe(false);
expect(matcher.match(CssSelector.parse('div[c]')[0], selectableCollector)).toBe(true);
expect(matcher.match(getSelectorFor({tag: 'div', attrs: [['a', '']]}), selectableCollector))
.toBe(false);
expect(matcher.match(getSelectorFor({tag: 'div', attrs: [['b', '']]}), selectableCollector))
.toBe(false);
expect(matcher.match(getSelectorFor({tag: 'div', attrs: [['c', '']]}), selectableCollector))
.toBe(true);
});
it('should select with one match in a list', () => {
matcher.addSelectables(s1 = CssSelector.parse('input[type=text], textbox'), 1);
expect(matcher.match(CssSelector.parse('textbox')[0], selectableCollector)).toEqual(true);
expect(matcher.match(getSelectorFor({tag: 'textbox'}), selectableCollector)).toEqual(true);
expect(matched).toEqual([s1[1], 1]);
reset();
expect(matcher.match(CssSelector.parse('input[type=text]')[0], selectableCollector))
expect(matcher.match(
getSelectorFor({tag: 'input', attrs: [['type', 'text']]}), selectableCollector))
.toEqual(true);
expect(matched).toEqual([s1[0], 1]);
});
@@ -261,7 +289,8 @@ export function main() {
it('should not select twice with two matches in a list', () => {
matcher.addSelectables(s1 = CssSelector.parse('input, .someClass'), 1);
expect(matcher.match(CssSelector.parse('input.someclass')[0], selectableCollector))
expect(
matcher.match(getSelectorFor({tag: 'input', classes: 'someclass'}), selectableCollector))
.toEqual(true);
expect(matched.length).toEqual(2);
expect(matched).toEqual([s1[0], 1]);
@@ -404,3 +433,16 @@ export function main() {
});
});
}
function getSelectorFor(
{tag = '', attrs = [], classes = ''}: {tag?: string, attrs?: any[], classes?: string} = {}):
CssSelector {
const selector = new CssSelector();
selector.setElement(tag);
attrs.forEach(nameValue => { selector.addAttribute(nameValue[0], nameValue[1]); });
classes.trim().split(/\s+/g).forEach(cName => { selector.addClassName(cName); });
return selector;
}
@@ -623,6 +623,23 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
]);
});
it('should parse directive dotted properties', () => {
const dirA =
CompileDirectiveMetadata
.create({
selector: '[dot.name]',
type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}}),
inputs: ['localName: dot.name'],
})
.toSummary();
expect(humanizeTplAst(parse('<div [dot.name]="expr"></div>', [dirA]))).toEqual([
[ElementAst, 'div'],
[DirectiveAst, dirA],
[BoundDirectivePropertyAst, 'localName', 'expr'],
]);
});
it('should locate directives in property bindings', () => {
const dirA =
CompileDirectiveMetadata
@@ -1244,8 +1261,15 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
});
it('should parse variables via let ...', () => {
expect(humanizeTplAst(parse('<div *ngIf="let a=b">', [
]))).toEqual([[EmbeddedTemplateAst], [VariableAst, 'a', 'b'], [ElementAst, 'div']]);
const targetAst = [
[EmbeddedTemplateAst],
[VariableAst, 'a', 'b'],
[ElementAst, 'div'],
];
expect(humanizeTplAst(parse('<div *ngIf="let a=b">', []))).toEqual(targetAst);
expect(humanizeTplAst(parse('<div data-*ngIf="let a=b">', []))).toEqual(targetAst);
});
describe('directives', () => {
@@ -10,14 +10,12 @@
import {__core_private__ as r} from '@angular/core';
export type ViewMetadata = typeof r._ViewMetadata;
export var ViewMetadata: typeof r.ViewMetadata = r.ViewMetadata;
export const ViewMetadata: typeof r.ViewMetadata = r.ViewMetadata;
import {__core_private_testing__ as r2} from '@angular/core/testing';
export type TestingCompiler = typeof r2._TestingCompiler;
export var TestingCompiler: typeof r2.TestingCompiler = r2.TestingCompiler;
export const TestingCompiler: typeof r2.TestingCompiler = r2.TestingCompiler;
export type TestingCompilerFactory = typeof r2._TestingCompilerFactory;
export var TestingCompilerFactory: typeof r2.TestingCompilerFactory = r2.TestingCompilerFactory;
export const TestingCompilerFactory: typeof r2.TestingCompilerFactory = r2.TestingCompilerFactory;
@@ -18,7 +18,7 @@ export function createUrlResolverWithoutPackagePrefix(): UrlResolver {
// This provider is put here just so that we can access it from multiple
// internal test packages.
// TODO: get rid of it or move to a separate @angular/internal_testing package
export var TEST_COMPILER_PROVIDERS: Provider[] = [
export const TEST_COMPILER_PROVIDERS: Provider[] = [
{provide: ElementSchemaRegistry, useValue: new MockSchemaRegistry({}, {}, {}, [], [])},
{provide: ResourceLoader, useClass: MockResourceLoader},
{provide: UrlResolver, useFactory: createUrlResolverWithoutPackagePrefix}
@@ -11,6 +11,7 @@ import {ApplicationInitStatus} from './application_init';
import {ApplicationRef, ApplicationRef_} from './application_ref';
import {APP_ID_RANDOM_PROVIDER} from './application_tokens';
import {IterableDiffers, KeyValueDiffers, defaultIterableDiffers, defaultKeyValueDiffers} from './change_detection/change_detection';
import {Inject, Optional, SkipSelf} from './di/metadata';
import {LOCALE_ID} from './i18n/tokens';
import {Compiler} from './linker/compiler';
import {ViewUtils} from './linker/view_utils';
@@ -24,6 +25,10 @@ export function _keyValueDiffersFactory() {
return defaultKeyValueDiffers;
}
export function _localeFactory(locale?: string): string {
return locale || 'en-US';
}
/**
* This module includes the providers of @angular/core that are needed
* to bootstrap components via `ApplicationRef`.
@@ -41,7 +46,11 @@ export function _keyValueDiffersFactory() {
AnimationQueue,
{provide: IterableDiffers, useFactory: _iterableDiffersFactory},
{provide: KeyValueDiffers, useFactory: _keyValueDiffersFactory},
{provide: LOCALE_ID, useValue: 'en-US'},
{
provide: LOCALE_ID,
useFactory: _localeFactory,
deps: [[new Inject(LOCALE_ID), new Optional(), new SkipSelf()]]
},
]
})
export class ApplicationModule {
+1 -1
View File
@@ -469,7 +469,7 @@ export class ApplicationRef_ extends ApplicationRef {
this._loadComponent(compRef);
if (isDevMode()) {
this._console.log(
`Angular 2 is running in the development mode. Call enableProdMode() to enable the production mode.`);
`Angular is running in the development mode. Call enableProdMode() to enable the production mode.`);
}
return compRef;
}
@@ -43,7 +43,7 @@ import * as api from './render/api';
import * as decorators from './util/decorators';
import {isPromise} from './util/lang';
export var __core_private__: {
export const __core_private__: {
isDefaultChangeDetectionStrategy: typeof constants.isDefaultChangeDetectionStrategy,
ChangeDetectorStatus: typeof constants.ChangeDetectorStatus,
_ChangeDetectorStatus?: constants.ChangeDetectorStatus,
+58 -51
View File
@@ -225,7 +225,33 @@ export const ContentChildren: ContentChildrenDecorator =
*/
export interface ContentChildDecorator {
/**
* @docsNotRequired
* @whatItDoes Configures a content query.
*
* @howToUse
*
* {@example core/di/ts/contentChild/content_child_howto.ts region='HowTo'}
*
* @description
*
* You can use ContentChild to get the first element or the directive matching the selector from
* the content DOM. If the content DOM changes, and a new child matches the selector,
* the property will be updated.
*
* Content queries are set before the `ngAfterContentInit` callback is called.
*
* **Metadata Properties**:
*
* * **selector** - the directive type or the name used for querying.
* * **read** - read a different token from the queried element.
*
* Let's look at an example:
*
* {@example core/di/ts/contentChild/content_child_example.ts region='Component'}
*
* **npm package**: `@angular/core`
*
* @stable
* @Annotation
*/
(selector: Type<any>|Function|string, {read}?: {read?: any}): any;
new (selector: Type<any>|Function|string, {read}?: {read?: any}): ContentChild;
@@ -241,30 +267,7 @@ export interface ContentChildDecorator {
export type ContentChild = Query;
/**
* @whatItDoes Configures a content query.
*
* @howToUse
*
* {@example core/di/ts/contentChild/content_child_howto.ts region='HowTo'}
*
* @description
*
* You can use ContentChild to get the first element or the directive matching the selector from the
* content DOM. If the content DOM changes, and a new child matches the selector,
* the property will be updated.
*
* Content queries are set before the `ngAfterContentInit` callback is called.
*
* **Metadata Properties**:
*
* * **selector** - the directive type or the name used for querying.
* * **read** - read a different token from the queried element.
*
* Let's look at an example:
*
* {@example core/di/ts/contentChild/content_child_example.ts region='Component'}
*
* **npm package**: `@angular/core`
* ContentChild decorator and metadata.
*
* @stable
* @Annotation
@@ -290,8 +293,35 @@ export const ContentChild: ContentChildDecorator = makePropDecorator(
*/
export interface ViewChildrenDecorator {
/**
* @docsNotRequired
*/ (selector: Type<any>|Function|string, {read}?: {read?: any}): any;
* @whatItDoes Configures a view query.
*
* @howToUse
*
* {@example core/di/ts/viewChildren/view_children_howto.ts region='HowTo'}
*
* @description
*
* You can use ViewChildren to get the {@link QueryList} of elements or directives from the
* view DOM. Any time a child element is added, removed, or moved, the query list will be updated,
* and the changes observable of the query list will emit a new value.
*
* View queries are set before the `ngAfterViewInit` callback is called.
*
* **Metadata Properties**:
*
* * **selector** - the directive type or the name used for querying.
* * **read** - read a different token from the queried elements.
*
* Let's look at an example:
*
* {@example core/di/ts/viewChildren/view_children_example.ts region='Component'}
*
* **npm package**: `@angular/core`
*
* @stable
* @Annotation
*/
(selector: Type<any>|Function|string, {read}?: {read?: any}): any;
new (selector: Type<any>|Function|string, {read}?: {read?: any}): ViewChildren;
}
@@ -303,30 +333,7 @@ export interface ViewChildrenDecorator {
export type ViewChildren = Query;
/**
* @whatItDoes Configures a view query.
*
* @howToUse
*
* {@example core/di/ts/viewChildren/view_children_howto.ts region='HowTo'}
*
* @description
*
* You can use ViewChildren to get the {@link QueryList} of elements or directives from the
* view DOM. Any time a child element is added, removed, or moved, the query list will be updated,
* and the changes observable of the query list will emit a new value.
*
* View queries are set before the `ngAfterViewInit` callback is called.
*
* **Metadata Properties**:
*
* * **selector** - the directive type or the name used for querying.
* * **read** - read a different token from the queried elements.
*
* Let's look at an example:
*
* {@example core/di/ts/viewChildren/view_children_example.ts region='Component'}
*
* **npm package**: `@angular/core`
* ViewChildren decorator and metadata.
*
* @stable
* @Annotation
+4 -4
View File
@@ -50,7 +50,7 @@ function noopScope(arg0?: any, arg1?: any): any {
*
* @experimental
*/
export var wtfCreateScope: (signature: string, flags?: any) => WtfScopeFn =
export const wtfCreateScope: (signature: string, flags?: any) => WtfScopeFn =
wtfEnabled ? createScope : (signature: string, flags?: any) => noopScope;
/**
@@ -62,7 +62,7 @@ export var wtfCreateScope: (signature: string, flags?: any) => WtfScopeFn =
* Returns the `returnValue for easy chaining.
* @experimental
*/
export var wtfLeave: <T>(scope: any, returnValue?: T) => T =
export const wtfLeave: <T>(scope: any, returnValue?: T) => T =
wtfEnabled ? leave : (s: any, r?: any) => r;
/**
@@ -78,7 +78,7 @@ export var wtfLeave: <T>(scope: any, returnValue?: T) => T =
* }
* @experimental
*/
export var wtfStartTimeRange: (rangeType: string, action: string) => any =
export const wtfStartTimeRange: (rangeType: string, action: string) => any =
wtfEnabled ? startTimeRange : (rangeType: string, action: string) => null;
/**
@@ -87,4 +87,4 @@ export var wtfStartTimeRange: (rangeType: string, action: string) => any =
* enabled.
* @experimental
*/
export var wtfEndTimeRange: (range: any) => void = wtfEnabled ? endTimeRange : (r: any) => null;
export const wtfEndTimeRange: (range: any) => void = wtfEnabled ? endTimeRange : (r: any) => null;
@@ -7,7 +7,7 @@
*/
import {global, isPresent, stringify} from '../facade/lang';
import {Type} from '../type';
import {Type, isType} from '../type';
import {PlatformReflectionCapabilities} from './platform_reflection_capabilities';
import {GetterFn, MethodFn, SetterFn} from './types';
@@ -105,7 +105,10 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
parameters(type: Type<any>): any[][] {
// Note: only report metadata if we have at least one class decorator
// to stay in sync with the static reflector.
const parentCtor = Object.getPrototypeOf(type.prototype).constructor;
if (!isType(type)) {
return [];
}
const parentCtor = getParentCtor(type);
let parameters = this._ownParameters(type, parentCtor);
if (!parameters && parentCtor !== Object) {
parameters = this.parameters(parentCtor);
@@ -135,7 +138,10 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
}
annotations(typeOrFunc: Type<any>): any[] {
const parentCtor = Object.getPrototypeOf(typeOrFunc.prototype).constructor;
if (!isType(typeOrFunc)) {
return [];
}
const parentCtor = getParentCtor(typeOrFunc);
const ownAnnotations = this._ownAnnotations(typeOrFunc, parentCtor) || [];
const parentAnnotations = parentCtor !== Object ? this.annotations(parentCtor) : [];
return parentAnnotations.concat(ownAnnotations);
@@ -170,7 +176,10 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
}
propMetadata(typeOrFunc: any): {[key: string]: any[]} {
const parentCtor = Object.getPrototypeOf(typeOrFunc.prototype).constructor;
if (!isType(typeOrFunc)) {
return {};
}
const parentCtor = getParentCtor(typeOrFunc);
const propMetadata: {[key: string]: any[]} = {};
if (parentCtor !== Object) {
const parentPropMetadata = this.propMetadata(parentCtor);
@@ -233,3 +242,11 @@ function convertTsickleDecoratorIntoMetadata(decoratorInvocations: any[]): any[]
return new annotationCls(...annotationArgs);
});
}
function getParentCtor(ctor: Function): Type<any> {
const parentProto = Object.getPrototypeOf(ctor.prototype);
const parentCtor = parentProto ? parentProto.constructor : null;
// Note: We always use `Object` as the null value
// to simplify checking later on.
return parentCtor || Object;
}
+3
View File
@@ -18,5 +18,8 @@
*/
export const Type = Function;
export function isType(v: any): v is Type<any> {
return typeof v === 'function';
}
export interface Type<T> extends Function { new (...args: any[]): T; }
@@ -25,6 +25,7 @@ import {AnimationPlayer, NoOpAnimationPlayer} from '../../src/animation/animatio
import {AnimationStyles} from '../../src/animation/animation_styles';
import {AnimationTransitionEvent} from '../../src/animation/animation_transition_event';
import {AUTO_STYLE, animate, group, keyframes, sequence, state, style, transition, trigger} from '../../src/animation/metadata';
import {Input} from '../../src/core';
import {isPresent} from '../../src/facade/lang';
import {TestBed, fakeAsync, flushMicrotasks} from '../../testing';
import {MockAnimationPlayer} from '../../testing/mock_animation_player';
@@ -2243,38 +2244,52 @@ function declareTests({useJit}: {useJit: boolean}) {
});
describe('error handling', () => {
it('should recover if an animation driver or player throws an error during an animation',
if (!getDOM().supportsWebAnimation()) return;
it('should not throw an error when an animation exists within projected content that is not bound to the DOM',
fakeAsync(() => {
TestBed.configureTestingModule({
declarations: [DummyIfCmp],
providers: [{provide: AnimationDriver, useClass: ErroneousAnimationDriver}],
declarations: [DummyIfCmp, DummyLoadingCmp],
providers: [{provide: AnimationDriver, useClass: WebAnimationsDriver}],
imports: [CommonModule]
});
TestBed.overrideComponent(DummyIfCmp, {
set: {
template: `
<div [@myAnimation]="exp" (@myAnimation.start)="callback1($event)" (@myAnimation.done)="callback2($event)"></div>
<dummy-loading-cmp [exp2]="exp">
<div [@myAnimation]="exp ? 'true' : 'false'" (@myAnimation.done)="callback()">world</div>
</dummy-loading-cmp>
`,
animations: [trigger('myAnimation', [transition(
'* => *',
[
animate(1000, style({transform: 'noooooo'})),
style({opacity: 0}),
animate(1000, style({opacity: 1})),
])])]
}
});
TestBed.overrideComponent(
DummyLoadingCmp, {set: {template: `hello <ng-content *ngIf="exp2"></ng-content>`}});
const fixture = TestBed.createComponent(DummyIfCmp);
const cmp = fixture.componentInstance;
let started = false;
let done = false;
cmp.callback1 = (event: AnimationTransitionEvent) => started = true;
cmp.callback2 = (event: AnimationTransitionEvent) => done = true;
const container = fixture.nativeElement;
let animationCalls = 0;
cmp.callback = () => animationCalls++;
cmp.exp = false;
fixture.detectChanges();
flushMicrotasks();
expect(animationCalls).toBe(1);
expect(getDOM().getText(container).trim()).toEqual('hello');
cmp.exp = true;
fixture.detectChanges();
flushMicrotasks();
expect(started).toBe(true);
expect(done).toBe(true);
expect(animationCalls).toBe(2);
expect(getDOM().getText(container).trim()).toMatch(/hello[\s\r\n]+world/m);
}));
});
@@ -2460,6 +2475,9 @@ class DummyIfCmp {
class DummyLoadingCmp {
exp: any = false;
callback = () => {};
@Input('exp2')
exp2: any = false;
}
@Component({
@@ -2571,11 +2589,3 @@ class ExtendedWebAnimationsDriver extends WebAnimationsDriver {
return player;
}
}
class ErroneousAnimationDriver extends MockAnimationDriver {
animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): WebAnimationsPlayer {
throw new Error();
}
}
@@ -32,7 +32,7 @@ export function main() {
{providers: [{provide: APP_INITIALIZER, multi: true, useValue: () => promise}]});
});
it('should updat the status once all async initializers are done',
it('should update the status once all async initializers are done',
async(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
let completerResolver = false;
setTimeout(() => {
@@ -14,4 +14,4 @@ export function main() {
it('should set the default locale to "en-US"',
inject([LOCALE_ID], (defaultLocale: string) => { expect(defaultLocale).toEqual('en-US'); }));
});
}
}
@@ -43,6 +43,8 @@ export function main() {
NeedsContentChildWithRead,
NeedsViewChildrenWithRead,
NeedsViewChildWithRead,
NeedsContentChildTemplateRef,
NeedsContentChildTemplateRefApp,
NeedsViewContainerWithRead,
ManualProjecting
]
@@ -262,6 +264,15 @@ export function main() {
expect(comp.textDirChild.text).toEqual('ca');
});
it('should contain the first descendant content child templateRef', () => {
const template = '<needs-content-child-template-ref-app>' +
'</needs-content-child-template-ref-app>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
view.detectChanges();
expect(view.nativeElement).toHaveText('OUTER');
});
it('should contain the first view child', () => {
const template = '<needs-view-child-read></needs-view-child-read>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
@@ -730,6 +741,23 @@ class NeedsContentChildWithRead {
@ContentChild('nonExisting', {read: TextDirective}) nonExistingVar: TextDirective;
}
@Component({
selector: 'needs-content-child-template-ref',
template: '<div [ngTemplateOutlet]="templateRef"></div>'
})
class NeedsContentChildTemplateRef {
@ContentChild(TemplateRef) templateRef: TemplateRef<any>;
}
@Component({
selector: 'needs-content-child-template-ref-app',
template: '<needs-content-child-template-ref>' +
'<template>OUTER<template>INNER</template></template>' +
'</needs-content-child-template-ref>'
})
class NeedsContentChildTemplateRefApp {
}
@Component({
selector: 'needs-view-children-read',
template: '<div #q text="va"></div><div #w text="vb"></div>',
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Component, Injector, OpaqueToken, Pipe, PipeTransform, Provider} from '@angular/core';
import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, Injector, OpaqueToken, Pipe, PipeTransform, Provider} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/matchers';
@@ -119,7 +119,7 @@ function declareTests({useJit}: {useJit: boolean}) {
expect(injector.get(token)).toEqual(tokenValue);
});
it('should support providers with an anonymous function', () => {
it('should support providers with an anonymous function as token', () => {
const token = () => true;
const tokenValue = 1;
const injector = createInjector([{provide: token, useValue: tokenValue}]);
@@ -147,6 +147,22 @@ function declareTests({useJit}: {useJit: boolean}) {
const injector = createInjector([{provide: 'someToken', useValue: data}]);
expect(injector.get('someToken')).toEqual(data);
});
describe('ANALYZE_FOR_ENTRY_COMPONENTS providers', () => {
it('should support class instances', () => {
class SomeObject {
someMethod() {}
}
expect(
() => createInjector([
{provide: ANALYZE_FOR_ENTRY_COMPONENTS, useValue: new SomeObject(), multi: true}
]))
.not.toThrow();
});
});
});
it('should allow logging a previous elements class binding via interpolation', () => {
@@ -190,6 +190,8 @@ export function main() {
class ChildNoDecorators extends Parent {}
class NoDecorators {}
// Check that metadata for Parent was not changed!
expect(reflector.annotations(Parent)).toEqual([new ClassDecorator({value: 'parent'})]);
@@ -199,6 +201,11 @@ export function main() {
expect(reflector.annotations(ChildNoDecorators)).toEqual([new ClassDecorator(
{value: 'parent'})]);
expect(reflector.annotations(NoDecorators)).toEqual([]);
expect(reflector.annotations(<any>{})).toEqual([]);
expect(reflector.annotations(<any>1)).toEqual([]);
expect(reflector.annotations(null)).toEqual([]);
});
it('should inherit parameters', () => {
@@ -226,6 +233,8 @@ export function main() {
constructor(a: any, b: any, c: any) { super(null, null); }
}
class NoDecorators {}
// Check that metadata for Parent was not changed!
expect(reflector.parameters(Parent)).toEqual([
[A, new ParamDecorator('a')], [B, new ParamDecorator('b')]
@@ -242,6 +251,11 @@ export function main() {
expect(reflector.parameters(ChildWithCtorNoDecorator)).toEqual([
undefined, undefined, undefined
]);
expect(reflector.parameters(NoDecorators)).toEqual([]);
expect(reflector.parameters(<any>{})).toEqual([]);
expect(reflector.parameters(<any>1)).toEqual([]);
expect(reflector.parameters(null)).toEqual([]);
});
it('should inherit property metadata', () => {
@@ -263,6 +277,8 @@ export function main() {
c: C;
}
class NoDecorators {}
// Check that metadata for Parent was not changed!
expect(reflector.propMetadata(Parent)).toEqual({
'a': [new PropDecorator('a')],
@@ -274,6 +290,11 @@ export function main() {
'b': [new PropDecorator('b1'), new PropDecorator('b2')],
'c': [new PropDecorator('c')]
});
expect(reflector.propMetadata(NoDecorators)).toEqual({});
expect(reflector.propMetadata(<any>{})).toEqual({});
expect(reflector.propMetadata(<any>1)).toEqual({});
expect(reflector.propMetadata(null)).toEqual({});
});
it('should inherit lifecycle hooks', () => {
@@ -332,7 +353,7 @@ export function main() {
static ctorParameters = () =>
[{type: A, decorators: [{type: ParamDecorator, args: ['a']}]},
{type: B, decorators: [{type: ParamDecorator, args: ['b']}]},
];
]
}
class Child extends Parent {}
@@ -9,7 +9,7 @@
import * as mock_animation_player from './mock_animation_player';
import * as test_compiler from './test_compiler';
export var __core_private_testing__: {
export const __core_private_testing__: {
TestingCompiler: typeof test_compiler.TestingCompiler,
_TestingCompiler?: test_compiler.TestingCompiler,
TestingCompilerFactory: typeof test_compiler.TestingCompilerFactory,
+1 -1
View File
@@ -262,7 +262,7 @@ export class TestBed implements Injector {
} catch (e) {
if (e.compType) {
throw new Error(
`This test module uses the component ${stringify(e.compType)} which is using a "templateUrl", but they were never compiled. ` +
`This test module uses the component ${stringify(e.compType)} which is using a "templateUrl" or "styleUrls", but they were never compiled. ` +
`Please call "TestBed.compileComponents" before your test.`);
} else {
throw e;
+1 -1
View File
@@ -29,4 +29,4 @@ if (_global.beforeEach) {
// TODO(juliemr): remove this, only used because we need to export something to have compilation
// work.
export var __core_private_testing_placeholder__ = '';
export const __core_private_testing_placeholder__ = '';
@@ -19,12 +19,12 @@ export {inject} from './test_bed';
export * from './logger';
export * from './ng_zone_mock';
export var proxy: ClassDecorator = (t: any) => t;
export const proxy: ClassDecorator = (t: any) => t;
const _global = <any>(typeof window === 'undefined' ? global : window);
export var afterEach: Function = _global.afterEach;
export var expect: (actual: any) => jasmine.Matchers = _global.expect;
export const afterEach: Function = _global.afterEach;
export const expect: (actual: any) => jasmine.Matchers = _global.expect;
const jsmBeforeEach = _global.beforeEach;
const jsmDescribe = _global.describe;
+3 -2
View File
@@ -14,8 +14,9 @@ import {isPresent} from './facade/lang';
import {AbstractControl} from './model';
import {isPromise} from './private_import_core';
function isEmptyInputValue(value: any) {
return value == null || typeof value === 'string' && value.length === 0;
function isEmptyInputValue(value: any): boolean {
// we don't check for string here so it also works with arrays
return value == null || value.length === 0;
}
/**
@@ -48,6 +48,12 @@ export function main() {
it('should accept zero as valid',
() => { expect(Validators.required(new FormControl(0))).toBeNull(); });
it('should error on an empty array',
() => expect(Validators.required(new FormControl([]))).toEqual({'required': true}));
it('should not error on a non-empty array',
() => expect(Validators.required(new FormControl([1, 2]))).toBeNull());
});
describe('requiredTrue', () => {
+94 -38
View File
@@ -114,25 +114,78 @@ export class MockConnection implements Connection {
* ### Example
*
* ```
* import {BaseRequestOptions, Http} from '@angular/http';
* import {MockBackend} from '@angular/http/testing';
* it('should get some data', inject([AsyncTestCompleter], (async) => {
* var connection;
* var injector = Injector.resolveAndCreate([
* MockBackend,
* {provide: Http, useFactory: (backend, options) => {
* return new Http(backend, options);
* }, deps: [MockBackend, BaseRequestOptions]}]);
* var http = injector.get(Http);
* var backend = injector.get(MockBackend);
* //Assign any newly-created connection to local variable
* backend.connections.subscribe(c => connection = c);
* http.request('data.json').subscribe((res) => {
* expect(res.text()).toBe('awesome');
* async.done();
* import {Injectable, ReflectiveInjector} from '@angular/core';
* import {async, fakeAsync, tick} from '@angular/core/testing';
* import {BaseRequestOptions, ConnectionBackend, Http, RequestOptions} from '@angular/http';
* import {Response, ResponseOptions} from '@angular/http';
* import {MockBackend, MockConnection} from '@angular/http/testing';
*
* const HERO_ONE = 'HeroNrOne';
* const HERO_TWO = 'WillBeAlwaysTheSecond';
*
* @Injectable()
* class HeroService {
* constructor(private http: Http) {}
*
* getHeroes(): Promise<String[]> {
* return this.http.get('myservices.de/api/heroes')
* .toPromise()
* .then(response => response.json().data)
* .catch(e => this.handleError(e));
* }
*
* private handleError(error: any): Promise<any> {
* console.error('An error occurred', error);
* return Promise.reject(error.message || error);
* }
* }
*
* describe('MockBackend HeroService Example', () => {
* beforeEach(() => {
* this.injector = ReflectiveInjector.resolveAndCreate([
* {provide: ConnectionBackend, useClass: MockBackend},
* {provide: RequestOptions, useClass: BaseRequestOptions},
* Http,
* HeroService,
* ]);
* this.heroService = this.injector.get(HeroService);
* this.backend = this.injector.get(ConnectionBackend) as MockBackend;
* this.backend.connections.subscribe((connection: any) => this.lastConnection = connection);
* });
* connection.mockRespond(new Response('awesome'));
* }));
*
* it('getHeroes() should query current service url', () => {
* this.heroService.getHeroes();
* expect(this.lastConnection).toBeDefined('no http service connection at all?');
* expect(this.lastConnection.request.url).toMatch(/api\/heroes$/, 'url invalid');
* });
*
* it('getHeroes() should return some heroes', fakeAsync(() => {
* let result: String[];
* this.heroService.getHeroes().then((heroes: String[]) => result = heroes);
* this.lastConnection.mockRespond(new Response(new ResponseOptions({
* body: JSON.stringify({data: [HERO_ONE, HERO_TWO]}),
* })));
* tick();
* expect(result.length).toEqual(2, 'should contain given amount of heroes');
* expect(result[0]).toEqual(HERO_ONE, ' HERO_ONE should be the first hero');
* expect(result[1]).toEqual(HERO_TWO, ' HERO_TWO should be the second hero');
* }));
*
* it('getHeroes() while server is down', fakeAsync(() => {
* let result: String[];
* let catchedError: any;
* this.heroService.getHeroes()
* .then((heroes: String[]) => result = heroes)
* .catch((error: any) => catchedError = error);
* this.lastConnection.mockRespond(new Response(new ResponseOptions({
* status: 404,
* statusText: 'URL not Found',
* })));
* tick();
* expect(result).toBeUndefined();
* expect(catchedError).toBeDefined();
* }));
* });
* ```
*
* This method only exists in the mock implementation, not in real Backends.
@@ -149,27 +202,30 @@ export class MockBackend implements ConnectionBackend {
* ### Example
*
* ```
* import {Http, BaseRequestOptions, Response} from '@angular/http';
* import {MockBackend} from '@angular/http/testing';
* import {Injector, provide} from '@angular/core';
* import {ReflectiveInjector} from '@angular/core';
* import {fakeAsync, tick} from '@angular/core/testing';
* import {BaseRequestOptions, ConnectionBackend, Http, RequestOptions} from '@angular/http';
* import {Response, ResponseOptions} from '@angular/http';
* import {MockBackend, MockConnection} from '@angular/http/testing';
*
* it('should get a response', () => {
* var connection; //this will be set when a new connection is emitted from the backend.
* var text; //this will be set from mock response
* var injector = Injector.resolveAndCreate([
* MockBackend,
* {provide: Http, useFactory: (backend, options) => {
* return new Http(backend, options);
* }, deps: [MockBackend, BaseRequestOptions]}]);
* var backend = injector.get(MockBackend);
* var http = injector.get(Http);
* backend.connections.subscribe(c => connection = c);
* http.request('something.json').subscribe(res => {
* text = res.text();
* });
* connection.mockRespond(new Response({body: 'Something'}));
* expect(text).toBe('Something');
* });
* it('should get a response', fakeAsync(() => {
* let connection:
* MockConnection; // this will be set when a new connection is emitted from the
* // backend.
* let text: string; // this will be set from mock response
* let injector = ReflectiveInjector.resolveAndCreate([
* {provide: ConnectionBackend, useClass: MockBackend},
* {provide: RequestOptions, useClass: BaseRequestOptions},
* Http,
* ]);
* let backend = injector.get(ConnectionBackend);
* let http = injector.get(Http);
* backend.connections.subscribe((c: MockConnection) => connection = c);
* http.request('something.json').toPromise().then((res: any) => text = res.text());
* connection.mockRespond(new Response(new ResponseOptions({body: 'Something'})));
* tick();
* expect(text).toBe('Something');
* }));
* ```
*
* This property only exists in the mock implementation, not in real Backends.
@@ -29,7 +29,17 @@ import {createLanguageService} from './language_service';
import {ReflectorHost} from './reflector_host';
import {BuiltinType, CompletionKind, Declaration, DeclarationError, Declarations, Definition, LanguageService, LanguageServiceHost, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable, TemplateSource, TemplateSources} from './types';
// In TypeScript 2.1 these flags moved
// These helpers work for both 2.0 and 2.1.
const isPrivate = (ts as any).ModifierFlags ?
((node: ts.Node) =>
!!((ts as any).getCombinedModifierFlags(node) & (ts as any).ModifierFlags.Private)) :
((node: ts.Node) => !!(node.flags & (ts as any).NodeFlags.Private));
const isReferenceType = (ts as any).ObjectFlags ?
((type: ts.Type) =>
!!(type.flags & (ts as any).TypeFlags.Object &&
(type as any).objectFlags & (ts as any).ObjectFlags.Reference)) :
((type: ts.Type) => !!(type.flags & (ts as any).TypeFlags.Reference));
/**
* Create a `LanguageServiceHost`
@@ -679,7 +689,7 @@ class TypeScriptSymbolQuery implements SymbolQuery {
const constructorDeclaration = constructor.declarations[0] as ts.ConstructorTypeNode;
for (const parameter of constructorDeclaration.parameters) {
const type = this.checker.getTypeAtLocation(parameter.type);
if (type.symbol.name == 'TemplateRef' && type.flags & ts.TypeFlags.Reference) {
if (type.symbol.name == 'TemplateRef' && isReferenceType(type)) {
const typeReference = type as ts.TypeReference;
if (typeReference.typeArguments.length === 1) {
return typeReference.typeArguments[0].symbol;
@@ -804,7 +814,7 @@ class SymbolWrapper implements Symbol {
get public(): boolean {
// Symbols that are not explicitly made private are public.
return !(getDeclarationFlagsFromSymbol(this.symbol) & ts.NodeFlags.Private);
return !isSymbolPrivate(this.symbol);
}
get callable(): boolean { return typeCallable(this.tsType); }
@@ -1096,10 +1106,8 @@ function getCombinedNodeFlags(node: ts.Node): ts.NodeFlags {
return flags;
}
function getDeclarationFlagsFromSymbol(s: ts.Symbol): ts.NodeFlags {
return s.valueDeclaration ?
getCombinedNodeFlags(s.valueDeclaration) :
s.flags & ts.SymbolFlags.Prototype ? ts.NodeFlags.Public | ts.NodeFlags.Static : 0;
function isSymbolPrivate(s: ts.Symbol): boolean {
return s.valueDeclaration && isPrivate(s.valueDeclaration);
}
function getBuiltinTypeFromTs(kind: BuiltinType, context: TypeContext): ts.Type {
@@ -1257,4 +1265,4 @@ function typeKindOf(type: ts.Type): BuiltinType {
}
}
return BuiltinType.Other;
}
}
@@ -10,17 +10,16 @@ import 'reflect-metadata';
import * as ts from 'typescript';
import {createLanguageService} from '../src/language_service';
import {Completions, Diagnostic, Diagnostics} from '../src/types';
import {Completions} from '../src/types';
import {TypeScriptServiceHost} from '../src/typescript_host';
import {toh} from './test_data';
import {MockTypescriptHost, includeDiagnostic, noDiagnostics} from './test_utils';
import {MockTypescriptHost} from './test_utils';
describe('completions', () => {
let documentRegistry = ts.createDocumentRegistry();
let mockHost = new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts'], toh);
let service = ts.createLanguageService(mockHost, documentRegistry);
let program = service.getProgram();
let ngHost = new TypeScriptServiceHost(mockHost, service);
let ngService = createLanguageService(ngHost);
ngHost.setSite(ngService);
@@ -9,7 +9,7 @@
import * as ts from 'typescript';
import {createLanguageService} from '../src/language_service';
import {Completions, Diagnostic, Diagnostics, Span} from '../src/types';
import {Span} from '../src/types';
import {TypeScriptServiceHost} from '../src/typescript_host';
import {toh} from './test_data';
@@ -20,7 +20,6 @@ describe('definitions', () => {
let documentRegistry = ts.createDocumentRegistry();
let mockHost = new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts'], toh);
let service = ts.createLanguageService(mockHost, documentRegistry);
let program = service.getProgram();
let ngHost = new TypeScriptServiceHost(mockHost, service);
let ngService = createLanguageService(ngHost);
ngHost.setSite(ngService);
@@ -9,7 +9,7 @@
import * as ts from 'typescript';
import {createLanguageService} from '../src/language_service';
import {Completions, Diagnostic, Diagnostics} from '../src/types';
import {Diagnostics} from '../src/types';
import {TypeScriptServiceHost} from '../src/typescript_host';
import {toh} from './test_data';
@@ -19,7 +19,6 @@ describe('diagnostics', () => {
let documentRegistry = ts.createDocumentRegistry();
let mockHost = new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts'], toh);
let service = ts.createLanguageService(mockHost, documentRegistry);
let program = service.getProgram();
let ngHost = new TypeScriptServiceHost(mockHost, service);
let ngService = createLanguageService(ngHost);
ngHost.setSite(ngService);
@@ -11,17 +11,16 @@ import 'reflect-metadata';
import * as ts from 'typescript';
import {createLanguageService} from '../src/language_service';
import {Hover, HoverTextSection} from '../src/types';
import {Hover} from '../src/types';
import {TypeScriptServiceHost} from '../src/typescript_host';
import {toh} from './test_data';
import {MockTypescriptHost, includeDiagnostic, noDiagnostics} from './test_utils';
import {MockTypescriptHost} from './test_utils';
describe('hover', () => {
let documentRegistry = ts.createDocumentRegistry();
let mockHost = new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts'], toh);
let service = ts.createLanguageService(mockHost, documentRegistry);
let program = service.getProgram();
let ngHost = new TypeScriptServiceHost(mockHost, service);
let ngService = createLanguageService(ngHost);
ngHost.setSite(ngService);
@@ -19,7 +19,7 @@ export type MockData = string | MockDirectory;
export type MockDirectory = {
[name: string]: MockData | undefined;
}
};
const angularts = /@angular\/(\w|\/|-)+\.tsx?$/;
const rxjsts = /rxjs\/(\w|\/)+\.tsx?$/;
@@ -9,7 +9,7 @@
import {INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS} from './platform_providers';
import * as resource_loader from './resource_loader/resource_loader_impl';
export var __platform_browser_dynamic_private__: {
export const __platform_browser_dynamic_private__: {
INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS: typeof INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
_ResourceLoaderImpl?: resource_loader.ResourceLoaderImpl,
ResourceLoaderImpl: typeof resource_loader.ResourceLoaderImpl
@@ -8,6 +8,6 @@
import {__core_private__ as r} from '@angular/core';
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export var reflector: typeof r.reflector = r.reflector;
export var Console: typeof r.Console = r.Console;
export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export const reflector: typeof r.reflector = r.reflector;
export const Console: typeof r.Console = r.Console;
@@ -8,6 +8,6 @@
import {__platform_browser_private__ as _} from '@angular/platform-browser';
export var INTERNAL_BROWSER_PLATFORM_PROVIDERS: typeof _.INTERNAL_BROWSER_PLATFORM_PROVIDERS =
export const INTERNAL_BROWSER_PLATFORM_PROVIDERS: typeof _.INTERNAL_BROWSER_PLATFORM_PROVIDERS =
_.INTERNAL_BROWSER_PLATFORM_PROVIDERS;
export var getDOM: typeof _.getDOM = _.getDOM;
export const getDOM: typeof _.getDOM = _.getDOM;
@@ -11,8 +11,6 @@ import {TestComponentRenderer} from '@angular/core/testing';
import {DOCUMENT} from '@angular/platform-browser';
import {getDOM} from './private_import_platform-browser';
/**
* A DOM based implementation of the TestComponentRenderer.
*/
@@ -8,7 +8,7 @@
import * as testing from './dom_test_component_renderer';
export var __platform_browser_dynamic_private__:
export const __platform_browser_dynamic_private__:
{DOMTestComponentRenderer: typeof testing.DOMTestComponentRenderer} = {
DOMTestComponentRenderer: testing.DOMTestComponentRenderer
};
@@ -8,6 +8,6 @@
import {__platform_browser_dynamic_private__ as _} from '@angular/platform-browser-dynamic';
export var INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS:
export const INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS:
typeof _.INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS =
_.INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS;
@@ -8,4 +8,4 @@
import {__platform_browser_private__ as _} from '@angular/platform-browser';
export var getDOM: typeof _.getDOM = _.getDOM;
export const getDOM: typeof _.getDOM = _.getDOM;
@@ -262,12 +262,11 @@ export class DomRenderer implements Renderer {
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string,
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
try {
if (this._rootRenderer.document.body.contains(element)) {
return this._animationDriver.animate(
element, startingStyles, keyframes, duration, delay, easing, previousPlayers);
} catch (e) {
return new NoOpAnimationPlayer();
}
return new NoOpAnimationPlayer();
}
}
@@ -20,10 +20,8 @@ export class WebAnimationsDriver implements AnimationDriver {
previousPlayers: AnimationPlayer[] = []): WebAnimationsPlayer {
let formattedSteps: {[key: string]: string | number}[] = [];
let startingStyleLookup: {[key: string]: string | number} = {};
if (isPresent(startingStyles) && startingStyles.styles.length > 0) {
if (isPresent(startingStyles)) {
startingStyleLookup = _populateStyles(startingStyles, {});
startingStyleLookup['offset'] = 0;
formattedSteps.push(startingStyleLookup);
}
keyframes.forEach((keyframe: AnimationKeyframe) => {
@@ -32,14 +30,18 @@ export class WebAnimationsDriver implements AnimationDriver {
formattedSteps.push(data);
});
// this is a special case when only styles are applied as an
// animation. When this occurs we want to animate from start to
// end with the same values. Removing the offset and having only
// start/end values is suitable enough for the web-animations API
if (formattedSteps.length == 1) {
const start = formattedSteps[0];
start['offset'] = null;
formattedSteps = [start, start];
// Styling passed into element.animate() must always be balanced.
// The special cases below can occur if only style() calls exist
// within an animation or when a style() calls are used prior
// to a group() animation being issued or if the renderer is
// invoked by the user directly.
if (formattedSteps.length == 0) {
formattedSteps = [startingStyleLookup, startingStyleLookup];
} else if (formattedSteps.length == 1) {
const start = startingStyleLookup;
const end = formattedSteps[0];
end['offset'] = null;
formattedSteps = [start, end];
}
const playerOptions: {[key: string]: string | number} = {
@@ -69,12 +69,21 @@ export class WebAnimationsPlayer implements AnimationPlayer {
const previousStyleProps = Object.keys(this.previousStyles);
if (previousStyleProps.length) {
let startingKeyframe = findStartingKeyframe(keyframes);
let startingKeyframe = keyframes[0];
let missingStyleProps: string[] = [];
previousStyleProps.forEach(prop => {
if (isPresent(startingKeyframe[prop])) {
startingKeyframe[prop] = this.previousStyles[prop];
if (!isPresent(startingKeyframe[prop])) {
missingStyleProps.push(prop);
}
startingKeyframe[prop] = this.previousStyles[prop];
});
if (missingStyleProps.length) {
for (let i = 1; i < keyframes.length; i++) {
let kf = keyframes[i];
missingStyleProps.forEach(prop => { kf[prop] = _computeStyle(this.element, prop); });
}
}
}
this._player = this._triggerWebAnimation(this.element, keyframes, this.options);
@@ -180,17 +189,3 @@ function _copyKeyframeStyles(styles: {[style: string]: string | number}):
});
return newStyles;
}
function findStartingKeyframe(keyframes: {[prop: string]: string | number}[]):
{[prop: string]: string | number} {
let startingKeyframe = keyframes[0];
// it's important that we find the LAST keyframe
// to ensure that style overidding is final.
for (let i = 1; i < keyframes.length; i++) {
const kf = keyframes[i];
const offset = kf['offset'];
if (offset !== 0) break;
startingKeyframe = kf;
}
return startingKeyframe;
}
@@ -20,7 +20,7 @@ import * as key_events from './dom/events/key_events';
import * as shared_styles_host from './dom/shared_styles_host';
import {WebAnimationsDriver} from './dom/web_animations_driver';
export var __platform_browser_private__: {
export const __platform_browser_private__: {
_BrowserPlatformLocation?: location.BrowserPlatformLocation,
BrowserPlatformLocation: typeof location.BrowserPlatformLocation,
_DomAdapter?: dom_adapter.DomAdapter,
@@ -9,30 +9,30 @@
import {__core_private__ as r} from '@angular/core';
export type RenderDebugInfo = typeof r._RenderDebugInfo;
export var RenderDebugInfo: typeof r.RenderDebugInfo = r.RenderDebugInfo;
export const RenderDebugInfo: typeof r.RenderDebugInfo = r.RenderDebugInfo;
export type DirectRenderer = typeof r._DirectRenderer;
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export type DebugDomRootRenderer = typeof r._DebugDomRootRenderer;
export var DebugDomRootRenderer: typeof r.DebugDomRootRenderer = r.DebugDomRootRenderer;
export var reflector: typeof r.reflector = r.reflector;
export const DebugDomRootRenderer: typeof r.DebugDomRootRenderer = r.DebugDomRootRenderer;
export const reflector: typeof r.reflector = r.reflector;
export type NoOpAnimationPlayer = typeof r._NoOpAnimationPlayer;
export var NoOpAnimationPlayer: typeof r.NoOpAnimationPlayer = r.NoOpAnimationPlayer;
export const NoOpAnimationPlayer: typeof r.NoOpAnimationPlayer = r.NoOpAnimationPlayer;
export type AnimationPlayer = typeof r._AnimationPlayer;
export var AnimationPlayer: typeof r.AnimationPlayer = r.AnimationPlayer;
export const AnimationPlayer: typeof r.AnimationPlayer = r.AnimationPlayer;
export type AnimationSequencePlayer = typeof r._AnimationSequencePlayer;
export var AnimationSequencePlayer: typeof r.AnimationSequencePlayer = r.AnimationSequencePlayer;
export const AnimationSequencePlayer: typeof r.AnimationSequencePlayer = r.AnimationSequencePlayer;
export type AnimationGroupPlayer = typeof r._AnimationGroupPlayer;
export var AnimationGroupPlayer: typeof r.AnimationGroupPlayer = r.AnimationGroupPlayer;
export const AnimationGroupPlayer: typeof r.AnimationGroupPlayer = r.AnimationGroupPlayer;
export type AnimationKeyframe = typeof r._AnimationKeyframe;
export var AnimationKeyframe: typeof r.AnimationKeyframe = r.AnimationKeyframe;
export const AnimationKeyframe: typeof r.AnimationKeyframe = r.AnimationKeyframe;
export type AnimationStyles = typeof r._AnimationStyles;
export var AnimationStyles: typeof r.AnimationStyles = r.AnimationStyles;
export var prepareFinalAnimationStyles: typeof r.prepareFinalAnimationStyles =
export const AnimationStyles: typeof r.AnimationStyles = r.AnimationStyles;
export const prepareFinalAnimationStyles: typeof r.prepareFinalAnimationStyles =
r.prepareFinalAnimationStyles;
export var balanceAnimationKeyframes: typeof r.balanceAnimationKeyframes =
export const balanceAnimationKeyframes: typeof r.balanceAnimationKeyframes =
r.balanceAnimationKeyframes;
export var clearStyles: typeof r.clearStyles = r.clearStyles;
export var collectAndResolveStyles: typeof r.collectAndResolveStyles = r.collectAndResolveStyles;
export const clearStyles: typeof r.clearStyles = r.clearStyles;
export const collectAndResolveStyles: typeof r.collectAndResolveStyles = r.collectAndResolveStyles;
@@ -6,12 +6,12 @@
* found in the LICENSE file at https://angular.io/license
*/
import {APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, Directive, ErrorHandler, Inject, Input, NgModule, OnDestroy, PLATFORM_INITIALIZER, Pipe, Provider, VERSION, createPlatformFactory} from '@angular/core';
import {APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, Directive, ErrorHandler, Inject, Input, LOCALE_ID, NgModule, OnDestroy, PLATFORM_INITIALIZER, Pipe, Provider, VERSION, createPlatformFactory} from '@angular/core';
import {ApplicationRef, destroyPlatform} from '@angular/core/src/application_ref';
import {Console} from '@angular/core/src/console';
import {ComponentRef} from '@angular/core/src/linker/component_factory';
import {Testability, TestabilityRegistry} from '@angular/core/src/testability/testability';
import {AsyncTestCompleter, Log, afterEach, beforeEach, beforeEachProviders, describe, iit, inject, it} from '@angular/core/testing/testing_internal';
import {AsyncTestCompleter, Log, afterEach, beforeEach, beforeEachProviders, describe, inject, it} from '@angular/core/testing/testing_internal';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
@@ -109,7 +109,8 @@ class DummyConsole implements Console {
class TestModule {}
function bootstrap(cmpType: any, providers: Provider[] = []): Promise<any> {
function bootstrap(
cmpType: any, providers: Provider[] = [], platformProviders: Provider[] = []): Promise<any> {
@NgModule({
imports: [BrowserModule],
declarations: [cmpType],
@@ -119,7 +120,7 @@ function bootstrap(cmpType: any, providers: Provider[] = []): Promise<any> {
})
class TestModule {
}
return platformBrowserDynamic().bootstrapModule(TestModule);
return platformBrowserDynamic(platformProviders).bootstrapModule(TestModule);
}
export function main() {
@@ -281,6 +282,17 @@ export function main() {
});
}));
it('should not override locale provided during bootstrap',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const refPromise =
bootstrap(HelloRootCmp, [testProviders], [{provide: LOCALE_ID, useValue: 'fr-FR'}]);
refPromise.then(ref => {
expect(ref.injector.get(LOCALE_ID)).toEqual('fr-FR');
async.done();
});
}));
it('should avoid cyclic dependencies when root component requires Lifecycle through DI',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const refPromise = bootstrap(HelloRootCmp4, testProviders);
@@ -9,23 +9,9 @@
import {AnimationPlayer} from '@angular/core';
import {el} from '@angular/platform-browser/testing/browser_util';
import {DomAnimatePlayer} from '../../src/dom/dom_animate_player';
import {WebAnimationsDriver} from '../../src/dom/web_animations_driver';
import {WebAnimationsPlayer} from '../../src/dom/web_animations_player';
import {AnimationKeyframe, AnimationStyles, NoOpAnimationPlayer} from '../../src/private_import_core';
import {MockDomAnimatePlayer} from '../../testing/mock_dom_animate_player';
class ExtendedWebAnimationsDriver extends WebAnimationsDriver {
public log: {[key: string]: any}[] = [];
constructor() { super(); }
/** @internal */
_triggerWebAnimation(elm: any, keyframes: any[], options: any): DomAnimatePlayer {
this.log.push({'elm': elm, 'keyframes': keyframes, 'options': options});
return new MockDomAnimatePlayer();
}
}
function _makeStyles(styles: {[key: string]: string | number}): AnimationStyles {
return new AnimationStyles([styles]);
@@ -38,10 +24,10 @@ function _makeKeyframe(
export function main() {
describe('WebAnimationsDriver', () => {
let driver: ExtendedWebAnimationsDriver;
let driver: WebAnimationsDriver;
let elm: HTMLElement;
beforeEach(() => {
driver = new ExtendedWebAnimationsDriver();
driver = new WebAnimationsDriver();
elm = el('<div></div>');
});
@@ -101,6 +87,33 @@ export function main() {
const allZero = player.keyframes.every(kf => kf['offset'] == 0);
expect(allZero).toBe(true);
});
it('should create an animation with only starting-styles as data', () => {
const startingStyles = _makeStyles({color: 'red', fontSize: '20px'});
const player = driver.animate(elm, startingStyles, [], 1000, 1000, null);
expect(player.keyframes).toEqual([
{color: 'red', fontSize: '20px'},
{color: 'red', fontSize: '20px'},
]);
});
it('should create a balanced animation when only one keyframe is passed in', () => {
const startingStyles = _makeStyles({color: 'red', fontSize: '20px'});
const keyframeStyles: any = [_makeKeyframe(1, {color: 'blue'})];
const player = driver.animate(elm, startingStyles, keyframeStyles, 1000, 1000, null);
const kf0 = player.keyframes[0];
const kf1 = player.keyframes[1];
expect(kf0['color']).toEqual('red');
expect(kf0['fontSize']).toEqual('20px');
expect(kf1['color']).toEqual('blue');
expect(kf1['fontSize']).toEqual('20px');
});
});
}
@@ -208,6 +208,32 @@ export function main() {
]);
});
it('should allow previous styles to be merged into the starting keyframe of the animation that were not apart of the animation to begin with',
() => {
if (!getDOM().supportsWebAnimation()) return;
const elm = el('<div></div>');
document.body.appendChild(elm);
elm.style.color = 'rgb(0,0,0)';
const previousStyles = {color: 'red'};
const previousPlayer =
new ExtendedWebAnimationsPlayer(elm, [previousStyles, previousStyles], {}, []);
previousPlayer.play();
previousPlayer.finish();
const player = new ExtendedWebAnimationsPlayer(
elm, [{opacity: '0'}, {opacity: '1'}], {duration: 1000}, [previousPlayer]);
player.init();
const data = player.domPlayer.captures['trigger'][0];
expect(data['keyframes']).toEqual([
{opacity: '0', color: 'red'},
{opacity: '1', color: 'rgb(0, 0, 0)'},
]);
});
it('should properly calculate the previous styles for the player even when its currently playing',
() => {
if (!getDOM().supportsWebAnimation()) return;
@@ -488,7 +488,7 @@ export function main() {
{declarations: [CompWithUrlTemplate]},
() => TestBed.createComponent(CompWithUrlTemplate))))
.toThrowError(
`This test module uses the component ${stringify(CompWithUrlTemplate)} which is using a "templateUrl", but they were never compiled. ` +
`This test module uses the component ${stringify(CompWithUrlTemplate)} which is using a "templateUrl" or "styleUrls", but they were never compiled. ` +
`Please call "TestBed.compileComponents" before your test.`);
restoreJasmineIt();
@@ -11,6 +11,8 @@ import {NgZone} from '@angular/core';
import {global} from './facade/lang';
import {getDOM} from './private_import_platform-browser';
export let browserDetection: BrowserDetection;
export class BrowserDetection {
private _overrideUa: string;
private get _ua(): string {
@@ -133,8 +135,6 @@ export function stringifyElement(el: any /** TODO #9100 */): string {
return result;
}
export var browserDetection: BrowserDetection = new BrowserDetection(null);
export function createNgZone(): NgZone {
return new NgZone({enableLongStackTrace: true});
}
}
@@ -9,25 +9,25 @@ import {__core_private__ as r} from '@angular/core';
export type RenderDebugInfo = typeof r._RenderDebugInfo;
export var RenderDebugInfo: typeof r.RenderDebugInfo = r.RenderDebugInfo;
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export var reflector: typeof r.reflector = r.reflector;
export const RenderDebugInfo: typeof r.RenderDebugInfo = r.RenderDebugInfo;
export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export const reflector: typeof r.reflector = r.reflector;
export type NoOpAnimationPlayer = typeof r._NoOpAnimationPlayer;
export var NoOpAnimationPlayer: typeof r.NoOpAnimationPlayer = r.NoOpAnimationPlayer;
export const NoOpAnimationPlayer: typeof r.NoOpAnimationPlayer = r.NoOpAnimationPlayer;
export type AnimationPlayer = typeof r._AnimationPlayer;
export var AnimationPlayer: typeof r.AnimationPlayer = r.AnimationPlayer;
export const AnimationPlayer: typeof r.AnimationPlayer = r.AnimationPlayer;
export type AnimationSequencePlayer = typeof r._AnimationSequencePlayer;
export var AnimationSequencePlayer: typeof r.AnimationSequencePlayer = r.AnimationSequencePlayer;
export const AnimationSequencePlayer: typeof r.AnimationSequencePlayer = r.AnimationSequencePlayer;
export type AnimationGroupPlayer = typeof r._AnimationGroupPlayer;
export var AnimationGroupPlayer: typeof r.AnimationGroupPlayer = r.AnimationGroupPlayer;
export const AnimationGroupPlayer: typeof r.AnimationGroupPlayer = r.AnimationGroupPlayer;
export type AnimationKeyframe = typeof r._AnimationKeyframe;
export var AnimationKeyframe: typeof r.AnimationKeyframe = r.AnimationKeyframe;
export const AnimationKeyframe: typeof r.AnimationKeyframe = r.AnimationKeyframe;
export type AnimationStyles = typeof r._AnimationStyles;
export var AnimationStyles: typeof r.AnimationStyles = r.AnimationStyles;
export var prepareFinalAnimationStyles: typeof r.prepareFinalAnimationStyles =
export const AnimationStyles: typeof r.AnimationStyles = r.AnimationStyles;
export const prepareFinalAnimationStyles: typeof r.prepareFinalAnimationStyles =
r.prepareFinalAnimationStyles;
export var balanceAnimationKeyframes: typeof r.balanceAnimationKeyframes =
export const balanceAnimationKeyframes: typeof r.balanceAnimationKeyframes =
r.balanceAnimationKeyframes;
export var clearStyles: typeof r.clearStyles = r.clearStyles;
export var collectAndResolveStyles: typeof r.collectAndResolveStyles = r.collectAndResolveStyles;
export const clearStyles: typeof r.clearStyles = r.clearStyles;
export const collectAndResolveStyles: typeof r.collectAndResolveStyles = r.collectAndResolveStyles;
@@ -8,6 +8,6 @@
import {__platform_browser_private__ as _} from '@angular/platform-browser';
export var getDOM: typeof _.getDOM = _.getDOM;
export var BrowserDomAdapter: typeof _.BrowserDomAdapter = _.BrowserDomAdapter;
export var ELEMENT_PROBE_PROVIDERS: typeof _.ELEMENT_PROBE_PROVIDERS = _.ELEMENT_PROBE_PROVIDERS;
export const getDOM: typeof _.getDOM = _.getDOM;
export const BrowserDomAdapter: typeof _.BrowserDomAdapter = _.BrowserDomAdapter;
export const ELEMENT_PROBE_PROVIDERS: typeof _.ELEMENT_PROBE_PROVIDERS = _.ELEMENT_PROBE_PROVIDERS;
@@ -9,7 +9,7 @@
import {INTERNAL_SERVER_PLATFORM_PROVIDERS, SERVER_RENDER_PROVIDERS} from './server';
export var __platform_server_private__: {
export const __platform_server_private__: {
INTERNAL_SERVER_PLATFORM_PROVIDERS: typeof INTERNAL_SERVER_PLATFORM_PROVIDERS,
SERVER_RENDER_PROVIDERS: typeof SERVER_RENDER_PROVIDERS,
} = {
@@ -8,18 +8,16 @@
import {__core_private__ as r} from '@angular/core';
export var reflector: typeof r.reflector = r.reflector;
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export var Console: typeof r.Console = r.Console;
export const reflector: typeof r.reflector = r.reflector;
export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export const Console: typeof r.Console = r.Console;
export type AnimationKeyframe = typeof r._AnimationKeyframe;
export var AnimationKeyframe: typeof r.AnimationKeyframe = r.AnimationKeyframe;
export const AnimationKeyframe: typeof r.AnimationKeyframe = r.AnimationKeyframe;
export type AnimationPlayer = typeof r._AnimationPlayer;
export var AnimationPlayer: typeof r.AnimationPlayer = r.AnimationPlayer;
export const AnimationPlayer: typeof r.AnimationPlayer = r.AnimationPlayer;
export type AnimationStyles = typeof r._AnimationStyles;
export var AnimationStyles: typeof r.AnimationStyles = r.AnimationStyles;
export const AnimationStyles: typeof r.AnimationStyles = r.AnimationStyles;
export type RenderDebugInfo = typeof r._RenderDebugInfo;
export var RenderDebugInfo: typeof r.RenderDebugInfo = r.RenderDebugInfo;
export const RenderDebugInfo: typeof r.RenderDebugInfo = r.RenderDebugInfo;
export type DebugDomRootRenderer = typeof r._DebugDomRootRenderer;
export var DebugDomRootRenderer: typeof r.DebugDomRootRenderer = r.DebugDomRootRenderer;
export type NoOpAnimationPlayer = typeof r._NoOpAnimationPlayer;
export var NoOpAnimationPlayer: typeof r.NoOpAnimationPlayer = r.NoOpAnimationPlayer;
export const DebugDomRootRenderer: typeof r.DebugDomRootRenderer = r.DebugDomRootRenderer;
@@ -9,14 +9,14 @@
import {__platform_browser_private__ as _} from '@angular/platform-browser';
export type DomAdapter = typeof _._DomAdapter;
export var DomAdapter: typeof _.DomAdapter = _.DomAdapter;
export var setRootDomAdapter: typeof _.setRootDomAdapter = _.setRootDomAdapter;
export var getDOM: typeof _.getDOM = _.getDOM;
export var SharedStylesHost: typeof _.SharedStylesHost = _.SharedStylesHost;
export const DomAdapter: typeof _.DomAdapter = _.DomAdapter;
export const setRootDomAdapter: typeof _.setRootDomAdapter = _.setRootDomAdapter;
export const getDOM: typeof _.getDOM = _.getDOM;
export const SharedStylesHost: typeof _.SharedStylesHost = _.SharedStylesHost;
export type SharedStylesHost = typeof _._SharedStylesHost;
export var NAMESPACE_URIS: typeof _.NAMESPACE_URIS = _.NAMESPACE_URIS;
export var shimContentAttribute: typeof _.shimContentAttribute = _.shimContentAttribute;
export var shimHostAttribute: typeof _.shimHostAttribute = _.shimHostAttribute;
export var flattenStyles: typeof _.flattenStyles = _.flattenStyles;
export var splitNamespace: typeof _.splitNamespace = _.splitNamespace;
export var isNamespaced: typeof _.isNamespaced = _.isNamespaced;
export const NAMESPACE_URIS: typeof _.NAMESPACE_URIS = _.NAMESPACE_URIS;
export const shimContentAttribute: typeof _.shimContentAttribute = _.shimContentAttribute;
export const shimHostAttribute: typeof _.shimHostAttribute = _.shimHostAttribute;
export const flattenStyles: typeof _.flattenStyles = _.flattenStyles;
export const splitNamespace: typeof _.splitNamespace = _.splitNamespace;
export const isNamespaced: typeof _.isNamespaced = _.isNamespaced;
@@ -10,7 +10,7 @@ import {APP_ID, Inject, Injectable, NgZone, RenderComponentType, Renderer, RootR
import {AnimationDriver, DOCUMENT} from '@angular/platform-browser';
import {isBlank, isPresent, stringify} from './facade/lang';
import {AnimationKeyframe, AnimationPlayer, AnimationStyles, NoOpAnimationPlayer, RenderDebugInfo} from './private_import_core';
import {AnimationKeyframe, AnimationPlayer, AnimationStyles, RenderDebugInfo} from './private_import_core';
import {NAMESPACE_URIS, SharedStylesHost, flattenStyles, getDOM, isNamespaced, shimContentAttribute, shimHostAttribute, splitNamespace} from './private_import_platform-browser';
const TEMPLATE_COMMENT_TEXT = 'template bindings={}';
@@ -208,12 +208,8 @@ export class ServerRenderer implements Renderer {
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string,
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
try {
return this._animationDriver.animate(
element, startingStyles, keyframes, duration, delay, easing, previousPlayers);
} catch (e) {
return new NoOpAnimationPlayer();
}
return this._animationDriver.animate(
element, startingStyles, keyframes, duration, delay, easing, previousPlayers);
}
}
@@ -8,6 +8,6 @@
import {__platform_server_private__ as _} from '@angular/platform-server';
export var INTERNAL_SERVER_PLATFORM_PROVIDERS: typeof _.INTERNAL_SERVER_PLATFORM_PROVIDERS =
export const INTERNAL_SERVER_PLATFORM_PROVIDERS: typeof _.INTERNAL_SERVER_PLATFORM_PROVIDERS =
_.INTERNAL_SERVER_PLATFORM_PROVIDERS;
export var SERVER_RENDER_PROVIDERS: typeof _.SERVER_RENDER_PROVIDERS = _.SERVER_RENDER_PROVIDERS;
export const SERVER_RENDER_PROVIDERS: typeof _.SERVER_RENDER_PROVIDERS = _.SERVER_RENDER_PROVIDERS;
@@ -8,6 +8,6 @@
import {__core_private__ as r} from '@angular/core';
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export var reflector: typeof r.reflector = r.reflector;
export var Console: typeof r.Console = r.Console;
export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export const reflector: typeof r.reflector = r.reflector;
export const Console: typeof r.Console = r.Console;
@@ -8,4 +8,4 @@
import {__platform_browser_dynamic_private__ as _} from '@angular/platform-browser-dynamic';
export var ResourceLoaderImpl: typeof _.ResourceLoaderImpl = _.ResourceLoaderImpl;
export const ResourceLoaderImpl: typeof _.ResourceLoaderImpl = _.ResourceLoaderImpl;
@@ -8,6 +8,6 @@
import {__platform_browser_private__ as _} from '@angular/platform-browser';
export var INTERNAL_BROWSER_PLATFORM_PROVIDERS: typeof _.INTERNAL_BROWSER_PLATFORM_PROVIDERS =
export const INTERNAL_BROWSER_PLATFORM_PROVIDERS: typeof _.INTERNAL_BROWSER_PLATFORM_PROVIDERS =
_.INTERNAL_BROWSER_PLATFORM_PROVIDERS;
export var getDOM: typeof _.getDOM = _.getDOM;
export const getDOM: typeof _.getDOM = _.getDOM;
@@ -9,30 +9,30 @@
import {__core_private__ as r} from '@angular/core';
export type RenderDebugInfo = typeof r._RenderDebugInfo;
export var RenderDebugInfo: typeof r.RenderDebugInfo = r.RenderDebugInfo;
export const RenderDebugInfo: typeof r.RenderDebugInfo = r.RenderDebugInfo;
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export type DebugDomRootRenderer = typeof r._DebugDomRootRenderer;
export var DebugDomRootRenderer: typeof r.DebugDomRootRenderer = r.DebugDomRootRenderer;
export var reflector: typeof r.reflector = r.reflector;
export const DebugDomRootRenderer: typeof r.DebugDomRootRenderer = r.DebugDomRootRenderer;
export const reflector: typeof r.reflector = r.reflector;
export type NoOpAnimationPlayer = typeof r._NoOpAnimationPlayer;
export var NoOpAnimationPlayer: typeof r.NoOpAnimationPlayer = r.NoOpAnimationPlayer;
export const NoOpAnimationPlayer: typeof r.NoOpAnimationPlayer = r.NoOpAnimationPlayer;
export type AnimationPlayer = typeof r._AnimationPlayer;
export var AnimationPlayer: typeof r.AnimationPlayer = r.AnimationPlayer;
export const AnimationPlayer: typeof r.AnimationPlayer = r.AnimationPlayer;
export type AnimationSequencePlayer = typeof r._AnimationSequencePlayer;
export var AnimationSequencePlayer: typeof r.AnimationSequencePlayer = r.AnimationSequencePlayer;
export const AnimationSequencePlayer: typeof r.AnimationSequencePlayer = r.AnimationSequencePlayer;
export type AnimationGroupPlayer = typeof r._AnimationGroupPlayer;
export var AnimationGroupPlayer: typeof r.AnimationGroupPlayer = r.AnimationGroupPlayer;
export const AnimationGroupPlayer: typeof r.AnimationGroupPlayer = r.AnimationGroupPlayer;
export type AnimationKeyframe = typeof r._AnimationKeyframe;
export var AnimationKeyframe: typeof r.AnimationKeyframe = r.AnimationKeyframe;
export const AnimationKeyframe: typeof r.AnimationKeyframe = r.AnimationKeyframe;
export type AnimationStyles = typeof r._AnimationStyles;
export var AnimationStyles: typeof r.AnimationStyles = r.AnimationStyles;
export var prepareFinalAnimationStyles: typeof r.prepareFinalAnimationStyles =
export const AnimationStyles: typeof r.AnimationStyles = r.AnimationStyles;
export const prepareFinalAnimationStyles: typeof r.prepareFinalAnimationStyles =
r.prepareFinalAnimationStyles;
export var balanceAnimationKeyframes: typeof r.balanceAnimationKeyframes =
export const balanceAnimationKeyframes: typeof r.balanceAnimationKeyframes =
r.balanceAnimationKeyframes;
export var clearStyles: typeof r.clearStyles = r.clearStyles;
export var collectAndResolveStyles: typeof r.collectAndResolveStyles = r.collectAndResolveStyles;
export var APP_ID_RANDOM_PROVIDER: typeof r.APP_ID_RANDOM_PROVIDER = r.APP_ID_RANDOM_PROVIDER;
export const clearStyles: typeof r.clearStyles = r.clearStyles;
export const collectAndResolveStyles: typeof r.collectAndResolveStyles = r.collectAndResolveStyles;
export const APP_ID_RANDOM_PROVIDER: typeof r.APP_ID_RANDOM_PROVIDER = r.APP_ID_RANDOM_PROVIDER;
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AUTO_STYLE, AnimationTransitionEvent, Component, Injector, animate, state, style, transition, trigger} from '@angular/core';
import {AUTO_STYLE, AnimationTransitionEvent, Component, Injector, ViewChild, animate, state, style, transition, trigger} from '@angular/core';
import {DebugDomRootRenderer} from '@angular/core/src/debug/debug_renderer';
import {RootRenderer} from '@angular/core/src/render/api';
import {TestBed, fakeAsync, flushMicrotasks} from '@angular/core/testing';
@@ -84,7 +84,7 @@ export function main() {
workerRenderStore = new RenderStore();
TestBed.configureTestingModule({
declarations: [AnimationCmp, MultiAnimationCmp],
declarations: [AnimationCmp, MultiAnimationCmp, ContainerAnimationCmp],
providers: [
Serializer, {provide: RenderStore, useValue: workerRenderStore}, {
provide: RootRenderer,
@@ -231,10 +231,9 @@ export function main() {
return (event: AnimationTransitionEvent) => { log[phaseName] = event; };
}
const f1 = TestBed.createComponent(AnimationCmp);
const f2 = TestBed.createComponent(AnimationCmp);
const cmp1 = f1.componentInstance;
const cmp2 = f2.componentInstance;
const fixture = TestBed.createComponent(ContainerAnimationCmp);
const cmp1 = fixture.componentInstance.compOne;
const cmp2 = fixture.componentInstance.compTwo;
const cmp1Log: {[phaseName: string]: AnimationTransitionEvent} = {};
const cmp2Log: {[phaseName: string]: AnimationTransitionEvent} = {};
@@ -246,8 +245,7 @@ export function main() {
cmp1.state = 'off';
cmp2.state = 'on';
f1.detectChanges();
f2.detectChanges();
fixture.detectChanges();
flushMicrotasks();
uiDriver.log.shift()['player'].finish();
@@ -316,6 +314,18 @@ export function main() {
});
}
@Component({
selector: 'container-comp',
template: `
<my-comp #one></my-comp>
<my-comp #two></my-comp>
`
})
class ContainerAnimationCmp {
@ViewChild('one') public compOne: AnimationCmp;
@ViewChild('two') public compTwo: AnimationCmp;
}
@Component({
selector: 'my-comp',
@@ -235,7 +235,7 @@ class ApplyRedirects {
if (route.loadChildren) {
return map.call(this.configLoader.load(injector, route.loadChildren), (r: any) => {
(<any>route)._loadedConfig = r;
return of (new UrlSegmentGroup(segments, {}));
return new UrlSegmentGroup(segments, {});
});
} else {
return of (new UrlSegmentGroup(segments, {}));
@@ -503,4 +503,4 @@ function emptyPathRedirect(
function getOutlet(route: Route): string {
return route.outlet ? route.outlet : PRIMARY_OUTLET;
}
}
@@ -34,7 +34,7 @@ export function createUrlTree(
}
function isMatrixParams(command: any): boolean {
return typeof command === 'object' && !command.outlets && !command.segmentPath;
return typeof command === 'object' && command != null && !command.outlets && !command.segmentPath;
}
function tree(
@@ -70,7 +70,7 @@ class Navigation {
throw new Error('Root segment cannot have matrix parameters');
}
const cmdWithOutlet = commands.find(c => typeof c === 'object' && c.outlets);
const cmdWithOutlet = commands.find(c => typeof c === 'object' && c != null && c.outlets);
if (cmdWithOutlet && cmdWithOutlet !== last(commands)) {
throw new Error('{outlets:{}} has to be the last command');
}
@@ -91,7 +91,7 @@ function computeNavigation(commands: any[]): Navigation {
let isAbsolute = false;
const res: any[] = commands.reduce((res, cmd, cmdIdx) => {
if (typeof cmd === 'object') {
if (typeof cmd === 'object' && cmd != null) {
if (cmd.outlets) {
const outlets: {[k: string]: any} = {};
forEach(cmd.outlets, (commands: any, name: string) => {
@@ -169,7 +169,9 @@ function createPositionApplyingDoubleDots(
}
function getPath(command: any): any {
if (typeof command === 'object' && command.outlets) return command.outlets[PRIMARY_OUTLET];
if (typeof command === 'object' && command != null && command.outlets) {
return command.outlets[PRIMARY_OUTLET];
}
return `${command}`;
}
@@ -306,4 +308,4 @@ function stringify(params: {[key: string]: any}): {[key: string]: string} {
function compare(path: string, params: {[key: string]: any}, segment: UrlSegment): boolean {
return path == segment.path && shallowEqual(params, segment.parameters);
}
}
@@ -92,15 +92,15 @@ export class RouterLink {
constructor(private router: Router, private route: ActivatedRoute) {}
@Input()
set routerLink(data: any[]|string) {
if (Array.isArray(data)) {
this.commands = data;
set routerLink(commands: any[]|string) {
if (commands != null) {
this.commands = Array.isArray(commands) ? commands : [commands];
} else {
this.commands = [data];
this.commands = [];
}
}
@HostListener('click', [])
@HostListener('click')
onClick(): boolean {
const extras = {
skipLocationChange: attrBoolValue(this.skipLocationChange),
@@ -157,11 +157,11 @@ export class RouterLinkWithHref implements OnChanges, OnDestroy {
}
@Input()
set routerLink(data: any[]|string) {
if (Array.isArray(data)) {
this.commands = data;
set routerLink(commands: any[]|string) {
if (commands != null) {
this.commands = Array.isArray(commands) ? commands : [commands];
} else {
this.commands = [data];
this.commands = [];
}
}
+32 -33
View File
@@ -159,39 +159,37 @@ export type Inherited = {
params: Params,
data: Data,
resolve: Data,
}
};
/** @internal */
export function
inheritedParamsDataResolve(route: ActivatedRouteSnapshot):
Inherited {
const pathToRoot = route.pathFromRoot;
export function inheritedParamsDataResolve(route: ActivatedRouteSnapshot): Inherited {
const pathToRoot = route.pathFromRoot;
let inhertingStartingFrom = pathToRoot.length - 1;
let inhertingStartingFrom = pathToRoot.length - 1;
while (inhertingStartingFrom >= 1) {
const current = pathToRoot[inhertingStartingFrom];
const parent = pathToRoot[inhertingStartingFrom - 1];
// current route is an empty path => inherits its parent's params and data
if (current.routeConfig && current.routeConfig.path === '') {
inhertingStartingFrom--;
while (inhertingStartingFrom >= 1) {
const current = pathToRoot[inhertingStartingFrom];
const parent = pathToRoot[inhertingStartingFrom - 1];
// current route is an empty path => inherits its parent's params and data
if (current.routeConfig && current.routeConfig.path === '') {
inhertingStartingFrom--;
// parent is componentless => current route should inherit its params and data
} else if (!parent.component) {
inhertingStartingFrom--;
// parent is componentless => current route should inherit its params and data
} else if (!parent.component) {
inhertingStartingFrom--;
} else {
break;
}
}
return pathToRoot.slice(inhertingStartingFrom).reduce((res, curr) => {
const params = merge(res.params, curr.params);
const data = merge(res.data, curr.data);
const resolve = merge(res.resolve, curr._resolvedData);
return {params, data, resolve};
}, <any>{params: {}, data: {}, resolve: {}});
} else {
break;
}
}
return pathToRoot.slice(inhertingStartingFrom).reduce((res, curr) => {
const params = merge(res.params, curr.params);
const data = merge(res.data, curr.data);
const resolve = merge(res.resolve, curr._resolvedData);
return {params, data, resolve};
}, <any>{params: {}, data: {}, resolve: {}});
}
/**
* @whatItDoes Contains the information about a route associated with a component loaded in an
@@ -330,22 +328,23 @@ function serializeNode(node: TreeNode<ActivatedRouteSnapshot>): string {
*/
export function advanceActivatedRoute(route: ActivatedRoute): void {
if (route.snapshot) {
if (!shallowEqual(route.snapshot.queryParams, route._futureSnapshot.queryParams)) {
const currentSnapshot = route.snapshot;
route.snapshot = route._futureSnapshot;
if (!shallowEqual(currentSnapshot.queryParams, route._futureSnapshot.queryParams)) {
(<any>route.queryParams).next(route._futureSnapshot.queryParams);
}
if (route.snapshot.fragment !== route._futureSnapshot.fragment) {
if (currentSnapshot.fragment !== route._futureSnapshot.fragment) {
(<any>route.fragment).next(route._futureSnapshot.fragment);
}
if (!shallowEqual(route.snapshot.params, route._futureSnapshot.params)) {
if (!shallowEqual(currentSnapshot.params, route._futureSnapshot.params)) {
(<any>route.params).next(route._futureSnapshot.params);
}
if (!shallowEqualArrays(route.snapshot.url, route._futureSnapshot.url)) {
if (!shallowEqualArrays(currentSnapshot.url, route._futureSnapshot.url)) {
(<any>route.url).next(route._futureSnapshot.url);
}
if (!equalParamsAndUrlSegments(route.snapshot, route._futureSnapshot)) {
if (!equalParamsAndUrlSegments(currentSnapshot, route._futureSnapshot)) {
(<any>route.data).next(route._futureSnapshot.data);
}
route.snapshot = route._futureSnapshot;
} else {
route.snapshot = route._futureSnapshot;
@@ -358,4 +357,4 @@ export function advanceActivatedRoute(route: ActivatedRoute): void {
export function equalParamsAndUrlSegments(
a: ActivatedRouteSnapshot, b: ActivatedRouteSnapshot): boolean {
return shallowEqual(a.params, b.params) && equalSegments(a.url, b.url);
}
}
@@ -551,13 +551,17 @@ describe('Integration', () => {
const user = fixture.debugElement.children[1].children[1].componentInstance;
expect(team.recordedParams).toEqual([{id: '22'}]);
expect(team.snapshotParams).toEqual([{id: '22'}]);
expect(user.recordedParams).toEqual([{name: 'victor'}]);
expect(user.snapshotParams).toEqual([{name: 'victor'}]);
router.navigateByUrl('/team/22/user/fedor');
advance(fixture);
expect(team.recordedParams).toEqual([{id: '22'}]);
expect(team.snapshotParams).toEqual([{id: '22'}]);
expect(user.recordedParams).toEqual([{name: 'victor'}, {name: 'fedor'}]);
expect(user.snapshotParams).toEqual([{name: 'victor'}, {name: 'fedor'}]);
})));
it('should work when navigating to /', fakeAsync(inject([Router], (router: Router) => {
@@ -1022,6 +1026,26 @@ describe('Integration', () => {
expect(native.getAttribute('href')).toEqual('/home');
}));
it('should not throw when commands is null', fakeAsync(() => {
@Component({
selector: 'someCmp',
template:
`<router-outlet></router-outlet><a [routerLink]="null">Link</a><button [routerLink]="null">Button</button>`
})
class CmpWithLink {
}
TestBed.configureTestingModule({declarations: [CmpWithLink]});
const router: Router = TestBed.get(Router);
let fixture: ComponentFixture<CmpWithLink> = createRoot(router, CmpWithLink);
router.resetConfig([{path: 'home', component: SimpleCmp}]);
const anchor = fixture.nativeElement.querySelector('a');
const button = fixture.nativeElement.querySelector('button');
expect(() => anchor.click()).not.toThrow();
expect(() => button.click()).not.toThrow();
}));
it('should update hrefs when query params or fragment change', fakeAsync(() => {
@Component({
@@ -2294,6 +2318,32 @@ describe('Integration', () => {
expect(location.path()).toEqual('/lazy/loaded');
})));
it('should work with wildcard route',
fakeAsync(inject(
[Router, Location, NgModuleFactoryLoader],
(router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
@Component({selector: 'lazy', template: 'lazy-loaded'})
class LazyLoadedComponent {
}
@NgModule({
declarations: [LazyLoadedComponent],
imports: [RouterModule.forChild([{path: '', component: LazyLoadedComponent}])],
})
class LoadedModule {
}
loader.stubbedModules = {lazy: LoadedModule};
const fixture = createRoot(router, RootCmp);
router.resetConfig([{path: '**', loadChildren: 'lazy'}]);
router.navigateByUrl('/lazy');
advance(fixture);
expect(location.path()).toEqual('/lazy');
})));
describe('preloading', () => {
beforeEach(() => {
TestBed.configureTestingModule(
@@ -2667,11 +2717,15 @@ class BlankCmp {
class TeamCmp {
id: Observable<string>;
recordedParams: Params[] = [];
snapshotParams: Params[] = [];
routerLink = ['.'];
constructor(public route: ActivatedRoute) {
this.id = map.call(route.params, (p: any) => p['id']);
route.params.forEach(_ => this.recordedParams.push(_));
route.params.forEach(p => {
this.recordedParams.push(p);
this.snapshotParams.push(route.snapshot.params);
});
}
}
@@ -2687,10 +2741,14 @@ class TwoOutletsCmp {
class UserCmp {
name: Observable<string>;
recordedParams: Params[] = [];
snapshotParams: Params[] = [];
constructor(route: ActivatedRoute) {
this.name = map.call(route.params, (p: any) => p['name']);
route.params.forEach(_ => this.recordedParams.push(_));
route.params.forEach(p => {
this.recordedParams.push(p);
this.snapshotParams.push(route.snapshot.params);
});
}
}
@@ -2813,7 +2871,7 @@ function createRoot(router: Router, type: any): ComponentFixture<any> {
RootCmp,
RelativeLinkInIfCmp,
RootCmpWithTwoOutlets,
EmptyQueryParamsCmp
EmptyQueryParamsCmp,
],
@@ -2839,7 +2897,7 @@ function createRoot(router: Router, type: any): ComponentFixture<any> {
RootCmp,
RelativeLinkInIfCmp,
RootCmpWithTwoOutlets,
EmptyQueryParamsCmp
EmptyQueryParamsCmp,
],
@@ -2866,7 +2924,7 @@ function createRoot(router: Router, type: any): ComponentFixture<any> {
RootCmp,
RelativeLinkInIfCmp,
RootCmpWithTwoOutlets,
EmptyQueryParamsCmp
EmptyQueryParamsCmp,
]
})
class TestModule {
@@ -8,6 +8,6 @@
import {__router_private__ as _} from '@angular/router';
export var ROUTER_PROVIDERS: typeof _.ROUTER_PROVIDERS = _.ROUTER_PROVIDERS;
export var ROUTES: typeof _.ROUTES = _.ROUTES;
export var flatten: typeof _.flatten = _.flatten;
export const ROUTER_PROVIDERS: typeof _.ROUTER_PROVIDERS = _.ROUTER_PROVIDERS;
export const ROUTES: typeof _.ROUTES = _.ROUTES;
export const flatten: typeof _.flatten = _.flatten;
+7 -7
View File
@@ -57,7 +57,7 @@ export interface IRootScopeService {
[key: string]: any;
}
export interface IScope extends IRootScopeService {}
;
export interface IAngularBootstrapConfig { strictDi?: boolean; }
export interface IDirective {
compile?: IDirectiveCompileFn;
@@ -190,9 +190,9 @@ try {
// ignore in CJS mode.
}
export var bootstrap = angular.bootstrap;
export var module = angular.module;
export var element = angular.element;
export var version = angular.version;
export var resumeBootstrap = angular.resumeBootstrap;
export var getTestability = angular.getTestability;
export const bootstrap = angular.bootstrap;
export const module = angular.module;
export const element = angular.element;
export const version = angular.version;
export const resumeBootstrap = angular.resumeBootstrap;
export const getTestability = angular.getTestability;
@@ -211,12 +211,12 @@ export class UpgradeAdapter {
* - `compile`: not supported because the host element is owned by Angular 2+, which does
* not allow modifying DOM structure during compilation.
* - `controller`: supported. (NOTE: injection of `$attrs` and `$transclude` is not supported.)
* - `controllerAs': supported.
* - `bindToController': supported.
* - `link': supported. (NOTE: only pre-link function is supported.)
* - `name': supported.
* - `priority': ignored.
* - `replace': not supported.
* - `controllerAs`: supported.
* - `bindToController`: supported.
* - `link`: supported. (NOTE: only pre-link function is supported.)
* - `name`: supported.
* - `priority`: ignored.
* - `replace`: not supported.
* - `require`: supported.
* - `restrict`: must be set to 'E'.
* - `scope`: supported.
@@ -12,6 +12,21 @@ import * as angular from './angular_js';
import {NG1_COMPILE, NG1_CONTROLLER, NG1_HTTP_BACKEND, NG1_SCOPE, NG1_TEMPLATE_CACHE} from './constants';
import {controllerKey} from './util';
interface IBindingDestination {
[key: string]: any;
$onChanges?: (changes: SimpleChanges) => void;
}
interface IControllerInstance extends IBindingDestination {
$doCheck?: () => void;
$onDestroy?: () => void;
$onInit?: () => void;
$postLink?: () => void;
}
type LifecycleHook = '$doCheck' | '$onChanges' | '$onDestroy' | '$onInit' | '$postLink';
const CAMEL_CASE = /([A-Z])/g;
const INITIAL_VALUE = {
__UNINITIALIZED__: true
@@ -134,11 +149,11 @@ export class UpgradeNg1ComponentAdapterBuilder {
httpBackend: angular.IHttpBackendService): Promise<angular.ILinkFn> {
if (this.directive.template !== undefined) {
this.linkFn = compileHtml(
typeof this.directive.template === 'function' ? this.directive.template() :
this.directive.template);
isFunction(this.directive.template) ? this.directive.template() :
this.directive.template);
} else if (this.directive.templateUrl) {
const url = typeof this.directive.templateUrl === 'function' ? this.directive.templateUrl() :
this.directive.templateUrl;
const url = isFunction(this.directive.templateUrl) ? this.directive.templateUrl() :
this.directive.templateUrl;
const html = templateCache.get(url);
if (html !== undefined) {
this.linkFn = compileHtml(html);
@@ -193,7 +208,8 @@ export class UpgradeNg1ComponentAdapterBuilder {
}
class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck {
destinationObj: any = null;
private controllerInstance: IControllerInstance = null;
destinationObj: IBindingDestination = null;
checkLastValues: any[] = [];
componentScope: angular.IScope;
element: Element;
@@ -209,7 +225,8 @@ class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck {
this.$element = angular.element(this.element);
const controllerType = directive.controller;
if (directive.bindToController && controllerType) {
this.destinationObj = this.buildController(controllerType);
this.controllerInstance = this.buildController(controllerType);
this.destinationObj = this.controllerInstance;
} else {
this.destinationObj = this.componentScope;
}
@@ -231,8 +248,13 @@ class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck {
ngOnInit() {
if (!this.directive.bindToController && this.directive.controller) {
this.buildController(this.directive.controller);
this.controllerInstance = this.buildController(this.directive.controller);
}
if (this.controllerInstance && isFunction(this.controllerInstance.$onInit)) {
this.controllerInstance.$onInit();
}
let link = this.directive.link;
if (typeof link == 'object') link = (<angular.IDirectivePrePost>link).pre;
if (link) {
@@ -257,8 +279,9 @@ class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck {
parentBoundTranscludeFn: (scope: any /** TODO #9100 */,
cloneAttach: any /** TODO #9100 */) => { cloneAttach(childNodes); }
});
if (this.destinationObj.$onInit) {
this.destinationObj.$onInit();
if (this.controllerInstance && isFunction(this.controllerInstance.$postLink)) {
this.controllerInstance.$postLink();
}
}
@@ -269,7 +292,8 @@ class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck {
this.setComponentProperty(name, change.currentValue);
ng1Changes[this.propertyMap[name]] = change;
});
if (this.destinationObj.$onChanges) {
if (isFunction(this.destinationObj.$onChanges)) {
this.destinationObj.$onChanges(ng1Changes);
}
}
@@ -290,14 +314,15 @@ class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck {
}
}
}
if (this.destinationObj.$doCheck && this.directive.controller) {
this.destinationObj.$doCheck();
if (this.controllerInstance && isFunction(this.controllerInstance.$doCheck)) {
this.controllerInstance.$doCheck();
}
}
ngOnDestroy() {
if (this.destinationObj.$onDestroy && this.directive.controller) {
this.destinationObj.$onDestroy();
if (this.controllerInstance && isFunction(this.controllerInstance.$onDestroy)) {
this.controllerInstance.$onDestroy();
}
}
@@ -353,3 +378,7 @@ class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck {
`Directive '${this.directive.name}' require syntax unrecognized: ${this.directive.require}`);
}
}
function isFunction(value: any): value is Function {
return typeof value === 'function';
}
+2 -2
View File
@@ -21,9 +21,9 @@ export function controllerKey(name: string): string {
return '$' + name + 'Controller';
}
export function getAttributesAsArray(node: Node): string[][] {
export function getAttributesAsArray(node: Node): [string, string][] {
const attributes = node.attributes;
let asArray: string[][];
let asArray: [string, string][];
if (attributes) {
let attrLen = attributes.length;
asArray = new Array(attrLen);
+553 -160
View File
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ChangeDetectorRef, Class, Component, EventEmitter, NO_ERRORS_SCHEMA, NgModule, SimpleChanges, Testability, destroyPlatform, forwardRef} from '@angular/core';
import {ChangeDetectorRef, Class, Component, EventEmitter, NO_ERRORS_SCHEMA, NgModule, SimpleChange, SimpleChanges, Testability, destroyPlatform, forwardRef} from '@angular/core';
import {async, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
@@ -900,167 +900,560 @@ export function main() {
});
}));
it('should call $onInit of components', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $onInitSpy = jasmine.createSpy('$onInit');
describe('with lifecycle hooks', () => {
it('should call `$onInit()` on controller', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $onInitSpyA = jasmine.createSpy('$onInitA');
const $onInitSpyB = jasmine.createSpy('$onInitB');
@Component({selector: 'ng2', template: '<ng1></ng1>'})
class Ng2Component {
}
angular.module('ng1', [])
.component('ng1', {
bindings: {},
template: '',
controller: function() { this.$onInit = $onInitSpy; }
})
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
@NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component],
imports: [BrowserModule],
})
class Ng2Module {
}
const element = html(`<div><ng2></ng2></div>`);
adapter.bootstrap(element, ['ng1']).ready((ref) => {
expect($onInitSpy).toHaveBeenCalled();
ref.dispose();
});
}));
it('should call $doCheck of components', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $doCheckSpy = jasmine.createSpy('$doCheck');
let changeDetector: ChangeDetectorRef;
@Component({selector: 'ng2', template: '<ng1></ng1>'})
class Ng2Component {
constructor(cd: ChangeDetectorRef) { changeDetector = cd; }
}
angular.module('ng1', [])
.component('ng1', {
bindings: {},
template: '{{$ctrl.value}}',
controller: function() { this.$doCheck = $doCheckSpy; }
})
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
@NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component],
imports: [BrowserModule],
})
class Ng2Module {
}
const element = html(`<div><ng2></ng2></div>`);
adapter.bootstrap(element, ['ng1']).ready((ref) => {
expect($doCheckSpy).toHaveBeenCalled();
$doCheckSpy.calls.reset();
changeDetector.detectChanges();
expect($doCheckSpy).toHaveBeenCalled();
ref.dispose();
});
}));
it('should call $onChanges of components', fakeAsync(() => {
const EXPECTED_VALUE = '$onChanges called';
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $onChangesSpy = jasmine.createSpy('$onChanges');
let ng2Instance: any;
@Component({selector: 'ng2', template: '<ng1 [val]="val"></ng1>'})
class Ng2Component {
constructor() { ng2Instance = this; }
}
angular.module('ng1Module', [])
.component('ng1', {
bindings: {val: '<'},
template: '',
controller: function() { this.$onChanges = $onChangesSpy; }
})
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
@NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component],
imports: [BrowserModule],
})
class Ng2Module {
}
const element = html(`<div><ng2></ng2></div>`);
adapter.bootstrap(element, ['ng1Module']).ready((ref) => {
ng2Instance.val = EXPECTED_VALUE;
tick();
ref.ng1RootScope.$digest();
expect($onChangesSpy).toHaveBeenCalled();
const changes = $onChangesSpy.calls.mostRecent().args[0] as SimpleChanges;
expect(changes['val'].currentValue).toEqual(EXPECTED_VALUE);
ref.dispose();
});
}));
it('should call $onDestroy of components', fakeAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $onDestroySpy = jasmine.createSpy('$onDestroy');
@Component({selector: 'ng2', template: '<ng1></ng1>'})
class Ng2Component {
}
angular.module('ng1', [])
.component('ng1', {
bindings: {},
template: '<div>ng1</div>',
controller: function() { this.$onDestroy = $onDestroySpy; }
})
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
@NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component],
imports: [BrowserModule],
})
class Ng2Module {
}
const element = html(`<div ng-if="!destroy"><ng2></ng2></div>`);
adapter.bootstrap(element, ['ng1']).ready((ref) => {
const $rootScope = ref.ng1RootScope as any;
$rootScope.destroy = false;
tick();
$rootScope.$digest();
expect($onDestroySpy).not.toHaveBeenCalled();
$rootScope.destroy = true;
tick();
$rootScope.$digest();
expect($onDestroySpy).toHaveBeenCalled();
ref.dispose();
if (!(global as any)['requestAnimationFrame']) {
// Needed for browser which don't support RAF and use a 16.6 setTimeout instead in
// ng1's AnimateRunner.
// This setTimeout remains at the end of the test and needs to be discarded.
tick(20);
@Component({selector: 'ng2', template: '<ng1-a></ng1-a> | <ng1-b></ng1-b>'})
class Ng2Component {
}
});
}));
angular.module('ng1', [])
.directive('ng1A', () => ({
template: '',
scope: {},
bindToController: true,
controllerAs: '$ctrl',
controller: class {$onInit() { $onInitSpyA(); }}
}))
.directive('ng1B', () => ({
template: '',
scope: {},
bindToController: false,
controllerAs: '$ctrl',
controller: function() { this.$onInit = $onInitSpyB; }
}))
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
@NgModule({
declarations: [
adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'),
Ng2Component
],
imports: [BrowserModule],
})
class Ng2Module {
}
const element = html(`<div><ng2></ng2></div>`);
adapter.bootstrap(element, ['ng1']).ready((ref) => {
expect($onInitSpyA).toHaveBeenCalled();
expect($onInitSpyB).toHaveBeenCalled();
ref.dispose();
});
}));
it('should not call `$onInit()` on scope', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $onInitSpy = jasmine.createSpy('$onInit');
@Component({selector: 'ng2', template: '<ng1-a></ng1-a> | <ng1-b></ng1-b>'})
class Ng2Component {
}
angular.module('ng1', [])
.directive('ng1A', () => ({
template: '',
scope: {},
bindToController: true,
controllerAs: '$ctrl',
controller: function($scope: angular.IScope) {
Object.getPrototypeOf($scope).$onInit = $onInitSpy;
}
}))
.directive('ng1B', () => ({
template: '',
scope: {},
bindToController: false,
controllerAs: '$ctrl',
controller: function($scope: angular.IScope) {
$scope['$onInit'] = $onInitSpy;
}
}))
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
@NgModule({
declarations: [
adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'),
Ng2Component
],
imports: [BrowserModule],
})
class Ng2Module {
}
const element = html(`<div><ng2></ng2></div>`);
adapter.bootstrap(element, ['ng1']).ready((ref) => {
expect($onInitSpy).not.toHaveBeenCalled();
ref.dispose();
});
}));
it('should call `$doCheck()` on controller', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $doCheckSpyA = jasmine.createSpy('$doCheckA');
const $doCheckSpyB = jasmine.createSpy('$doCheckB');
let changeDetector: ChangeDetectorRef;
@Component({selector: 'ng2', template: '<ng1-a></ng1-a> | <ng1-b></ng1-b>'})
class Ng2Component {
constructor(cd: ChangeDetectorRef) { changeDetector = cd; }
}
angular.module('ng1', [])
.directive('ng1A', () => ({
template: '',
scope: {},
bindToController: true,
controllerAs: '$ctrl',
controller: class {$doCheck() { $doCheckSpyA(); }}
}))
.directive('ng1B', () => ({
template: '',
scope: {},
bindToController: false,
controllerAs: '$ctrl',
controller: function() { this.$doCheck = $doCheckSpyB; }
}))
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
@NgModule({
declarations: [
adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'),
Ng2Component
],
imports: [BrowserModule],
})
class Ng2Module {
}
const element = html(`<div><ng2></ng2></div>`);
adapter.bootstrap(element, ['ng1']).ready((ref) => {
expect($doCheckSpyA).toHaveBeenCalled();
expect($doCheckSpyB).toHaveBeenCalled();
$doCheckSpyA.calls.reset();
$doCheckSpyB.calls.reset();
changeDetector.detectChanges();
expect($doCheckSpyA).toHaveBeenCalled();
expect($doCheckSpyB).toHaveBeenCalled();
ref.dispose();
});
}));
it('should not call `$doCheck()` on scope', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $doCheckSpyA = jasmine.createSpy('$doCheckA');
const $doCheckSpyB = jasmine.createSpy('$doCheckB');
let changeDetector: ChangeDetectorRef;
@Component({selector: 'ng2', template: '<ng1-a></ng1-a> | <ng1-b></ng1-b>'})
class Ng2Component {
constructor(cd: ChangeDetectorRef) { changeDetector = cd; }
}
angular.module('ng1', [])
.directive('ng1A', () => ({
template: '',
scope: {},
bindToController: true,
controllerAs: '$ctrl',
controller: function($scope: angular.IScope) {
Object.getPrototypeOf($scope).$doCheck = $doCheckSpyA;
}
}))
.directive('ng1B', () => ({
template: '',
scope: {},
bindToController: false,
controllerAs: '$ctrl',
controller: function($scope: angular.IScope) {
$scope['$doCheck'] = $doCheckSpyB;
}
}))
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
@NgModule({
declarations: [
adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'),
Ng2Component
],
imports: [BrowserModule],
})
class Ng2Module {
}
const element = html(`<div><ng2></ng2></div>`);
adapter.bootstrap(element, ['ng1']).ready((ref) => {
$doCheckSpyA.calls.reset();
$doCheckSpyB.calls.reset();
changeDetector.detectChanges();
expect($doCheckSpyA).not.toHaveBeenCalled();
expect($doCheckSpyB).not.toHaveBeenCalled();
ref.dispose();
});
}));
it('should call `$postLink()` on controller', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $postLinkSpyA = jasmine.createSpy('$postLinkA');
const $postLinkSpyB = jasmine.createSpy('$postLinkB');
@Component({selector: 'ng2', template: '<ng1-a></ng1-a> | <ng1-b></ng1-b>'})
class Ng2Component {
}
angular.module('ng1', [])
.directive('ng1A', () => ({
template: '',
scope: {},
bindToController: true,
controllerAs: '$ctrl',
controller: class {$postLink() { $postLinkSpyA(); }}
}))
.directive('ng1B', () => ({
template: '',
scope: {},
bindToController: false,
controllerAs: '$ctrl',
controller: function() { this.$postLink = $postLinkSpyB; }
}))
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
@NgModule({
declarations: [
adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'),
Ng2Component
],
imports: [BrowserModule],
})
class Ng2Module {
}
const element = html(`<div><ng2></ng2></div>`);
adapter.bootstrap(element, ['ng1']).ready((ref) => {
expect($postLinkSpyA).toHaveBeenCalled();
expect($postLinkSpyB).toHaveBeenCalled();
ref.dispose();
});
}));
it('should not call `$postLink()` on scope', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $postLinkSpy = jasmine.createSpy('$postLink');
@Component({selector: 'ng2', template: '<ng1-a></ng1-a> | <ng1-b></ng1-b>'})
class Ng2Component {
}
angular.module('ng1', [])
.directive('ng1A', () => ({
template: '',
scope: {},
bindToController: true,
controllerAs: '$ctrl',
controller: function($scope: angular.IScope) {
Object.getPrototypeOf($scope).$postLink = $postLinkSpy;
}
}))
.directive('ng1B', () => ({
template: '',
scope: {},
bindToController: false,
controllerAs: '$ctrl',
controller: function($scope: angular.IScope) {
$scope['$postLink'] = $postLinkSpy;
}
}))
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
@NgModule({
declarations: [
adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'),
Ng2Component
],
imports: [BrowserModule],
})
class Ng2Module {
}
const element = html(`<div><ng2></ng2></div>`);
adapter.bootstrap(element, ['ng1']).ready((ref) => {
expect($postLinkSpy).not.toHaveBeenCalled();
ref.dispose();
});
}));
it('should call `$onChanges()` on binding destination', fakeAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $onChangesControllerSpyA = jasmine.createSpy('$onChangesControllerA');
const $onChangesControllerSpyB = jasmine.createSpy('$onChangesControllerB');
const $onChangesScopeSpy = jasmine.createSpy('$onChangesScope');
let ng2Instance: any;
@Component({
selector: 'ng2',
template: '<ng1-a [valA]="val"></ng1-a> | <ng1-b [valB]="val"></ng1-b>'
})
class Ng2Component {
constructor() { ng2Instance = this; }
}
angular.module('ng1', [])
.directive('ng1A', () => ({
template: '',
scope: {valA: '<'},
bindToController: true,
controllerAs: '$ctrl',
controller: function($scope: angular.IScope) {
this.$onChanges = $onChangesControllerSpyA;
}
}))
.directive(
'ng1B',
() => ({
template: '',
scope: {valB: '<'},
bindToController: false,
controllerAs: '$ctrl',
controller: class {
$onChanges(changes: SimpleChanges) { $onChangesControllerSpyB(changes); }
}
}))
.directive('ng2', adapter.downgradeNg2Component(Ng2Component))
.run(($rootScope: angular.IRootScopeService) => {
Object.getPrototypeOf($rootScope).$onChanges = $onChangesScopeSpy;
});
@NgModule({
declarations: [
adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'),
Ng2Component
],
imports: [BrowserModule],
})
class Ng2Module {
}
const element = html(`<div><ng2></ng2></div>`);
adapter.bootstrap(element, ['ng1']).ready((ref) => {
// Initial `$onChanges()` call
tick();
expect($onChangesControllerSpyA.calls.count()).toBe(1);
expect($onChangesControllerSpyA.calls.argsFor(0)[0]).toEqual({
valA: jasmine.any(SimpleChange)
});
expect($onChangesControllerSpyB).not.toHaveBeenCalled();
expect($onChangesScopeSpy.calls.count()).toBe(1);
expect($onChangesScopeSpy.calls.argsFor(0)[0]).toEqual({
valB: jasmine.any(SimpleChange)
});
$onChangesControllerSpyA.calls.reset();
$onChangesControllerSpyB.calls.reset();
$onChangesScopeSpy.calls.reset();
// `$onChanges()` call after a change
ng2Instance.val = 'new value';
tick();
ref.ng1RootScope.$digest();
expect($onChangesControllerSpyA.calls.count()).toBe(1);
expect($onChangesControllerSpyA.calls.argsFor(0)[0]).toEqual({
valA: jasmine.objectContaining({currentValue: 'new value'})
});
expect($onChangesControllerSpyB).not.toHaveBeenCalled();
expect($onChangesScopeSpy.calls.count()).toBe(1);
expect($onChangesScopeSpy.calls.argsFor(0)[0]).toEqual({
valB: jasmine.objectContaining({currentValue: 'new value'})
});
ref.dispose();
});
}));
it('should call `$onDestroy()` on controller', fakeAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $onDestroySpyA = jasmine.createSpy('$onDestroyA');
const $onDestroySpyB = jasmine.createSpy('$onDestroyB');
let ng2ComponentInstance: Ng2Component;
@Component({
selector: 'ng2',
template: `
<div *ngIf="!ng2Destroy">
<ng1-a></ng1-a> | <ng1-b></ng1-b>
</div>
`
})
class Ng2Component {
ng2Destroy: boolean = false;
constructor() { ng2ComponentInstance = this; }
}
// On browsers that don't support `requestAnimationFrame` (IE 9, Android <= 4.3),
// `$animate` will use `setTimeout(..., 16.6)` instead. This timeout will still be on
// the queue at the end of the test, causing it to fail.
// Mocking animations (via `ngAnimateMock`) avoids the issue.
angular.module('ng1', ['ngAnimateMock'])
.directive('ng1A', () => ({
template: '',
scope: {},
bindToController: true,
controllerAs: '$ctrl',
controller: class {$onDestroy() { $onDestroySpyA(); }}
}))
.directive('ng1B', () => ({
template: '',
scope: {},
bindToController: false,
controllerAs: '$ctrl',
controller: function() { this.$onDestroy = $onDestroySpyB; }
}))
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
@NgModule({
declarations: [
adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'),
Ng2Component
],
imports: [BrowserModule],
})
class Ng2Module {
}
const element = html(`<div ng-if="!ng1Destroy"><ng2></ng2></div>`);
adapter.bootstrap(element, ['ng1']).ready((ref) => {
const $rootScope = ref.ng1RootScope as any;
$rootScope.ng1Destroy = false;
tick();
$rootScope.$digest();
expect($onDestroySpyA).not.toHaveBeenCalled();
expect($onDestroySpyB).not.toHaveBeenCalled();
$rootScope.ng1Destroy = true;
tick();
$rootScope.$digest();
expect($onDestroySpyA).toHaveBeenCalled();
expect($onDestroySpyB).toHaveBeenCalled();
$onDestroySpyA.calls.reset();
$onDestroySpyB.calls.reset();
$rootScope.ng1Destroy = false;
tick();
$rootScope.$digest();
expect($onDestroySpyA).not.toHaveBeenCalled();
expect($onDestroySpyB).not.toHaveBeenCalled();
ng2ComponentInstance.ng2Destroy = true;
tick();
$rootScope.$digest();
expect($onDestroySpyA).toHaveBeenCalled();
expect($onDestroySpyB).toHaveBeenCalled();
ref.dispose();
});
}));
it('should not call `$onDestroy()` on scope', fakeAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $onDestroySpy = jasmine.createSpy('$onDestroy');
let ng2ComponentInstance: Ng2Component;
@Component({
selector: 'ng2',
template: `
<div *ngIf="!ng2Destroy">
<ng1-a></ng1-a> | <ng1-b></ng1-b>
</div>
`
})
class Ng2Component {
ng2Destroy: boolean = false;
constructor() { ng2ComponentInstance = this; }
}
// On browsers that don't support `requestAnimationFrame` (IE 9, Android <= 4.3),
// `$animate` will use `setTimeout(..., 16.6)` instead. This timeout will still be on
// the queue at the end of the test, causing it to fail.
// Mocking animations (via `ngAnimateMock`) avoids the issue.
angular.module('ng1', ['ngAnimateMock'])
.directive('ng1A', () => ({
template: '',
scope: {},
bindToController: true,
controllerAs: '$ctrl',
controller: function($scope: angular.IScope) {
Object.getPrototypeOf($scope).$onDestroy = $onDestroySpy;
}
}))
.directive('ng1B', () => ({
template: '',
scope: {},
bindToController: false,
controllerAs: '$ctrl',
controller: function($scope: angular.IScope) {
$scope['$onDestroy'] = $onDestroySpy;
}
}))
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
@NgModule({
declarations: [
adapter.upgradeNg1Component('ng1A'), adapter.upgradeNg1Component('ng1B'),
Ng2Component
],
imports: [BrowserModule],
})
class Ng2Module {
}
const element = html(`<div ng-if="!ng1Destroy"><ng2></ng2></div>`);
adapter.bootstrap(element, ['ng1']).ready((ref) => {
const $rootScope = ref.ng1RootScope as any;
$rootScope.ng1Destroy = false;
tick();
$rootScope.$digest();
$rootScope.ng1Destroy = true;
tick();
$rootScope.$digest();
$rootScope.ng1Destroy = false;
tick();
$rootScope.$digest();
ng2ComponentInstance.ng2Destroy = true;
tick();
$rootScope.$digest();
expect($onDestroySpy).not.toHaveBeenCalled();
ref.dispose();
});
}));
});
it('should bind input properties (<) of components', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
+15 -2
View File
@@ -132,13 +132,25 @@ describe('tree benchmark perf', () => {
}).then(done, done.fail);
});
});
it('should run ng2 changedetection', (done) => {
runTreeBenchmark({
id: `deepTree.ng2.changedetection`,
url: 'all/benchmarks/src/tree/ng2/index.html',
work: () => $('#detectChanges').click(),
setup: () => $('#createDom').click(),
}).then(done, done.fail);
});
});
function runTreeBenchmark(config: {
id: string,
url: string, ignoreBrowserSynchronization?: boolean,
work: () => any,
prepare: () => any, extraParams?: {name: string, value: any}[]
prepare?: () => any,
extraParams?: {name: string, value: any}[],
setup?: () => any
}) {
let params = [{name: 'depth', value: 11}];
if (config.extraParams) {
@@ -150,7 +162,8 @@ describe('tree benchmark perf', () => {
ignoreBrowserSynchronization: config.ignoreBrowserSynchronization,
params: params,
work: config.work,
prepare: config.prepare
prepare: config.prepare,
setup: config.setup
});
}
});
+7
View File
@@ -19,6 +19,13 @@ describe('tree benchmark spec', () => {
});
});
it('should work for ng2 detect changes', () => {
let params = [{name: 'depth', value: 4}];
openBrowser({url: 'all/benchmarks/src/tree/ng2/index.html'});
$('#detectChanges').click();
expect($('#numberOfChecks').getText()).toContain('10');
});
it('should work for ng2 ftl', () => {
testTreeBenchmark({
url: 'all/benchmarks/src/tree/ng2_ftl/index.html',
@@ -8,31 +8,31 @@
import {Math} from '@angular/facade/src/math';
export var ITEMS = 1000;
export var ITEM_HEIGHT = 40;
export var VISIBLE_ITEMS = 17;
export const ITEMS = 1000;
export const ITEM_HEIGHT = 40;
export const VISIBLE_ITEMS = 17;
export var HEIGHT = ITEMS * ITEM_HEIGHT;
export var VIEW_PORT_HEIGHT = ITEM_HEIGHT * VISIBLE_ITEMS;
export const HEIGHT = ITEMS * ITEM_HEIGHT;
export const VIEW_PORT_HEIGHT = ITEM_HEIGHT * VISIBLE_ITEMS;
export var COMPANY_NAME_WIDTH = 100;
export var OPPORTUNITY_NAME_WIDTH = 100;
export var OFFERING_NAME_WIDTH = 100;
export var ACCOUNT_CELL_WIDTH = 50;
export var BASE_POINTS_WIDTH = 50;
export var KICKER_POINTS_WIDTH = 50;
export var STAGE_BUTTONS_WIDTH = 220;
export var BUNDLES_WIDTH = 120;
export var DUE_DATE_WIDTH = 100;
export var END_DATE_WIDTH = 100;
export var AAT_STATUS_WIDTH = 100;
export var ROW_WIDTH = COMPANY_NAME_WIDTH + OPPORTUNITY_NAME_WIDTH + OFFERING_NAME_WIDTH +
export const COMPANY_NAME_WIDTH = 100;
export const OPPORTUNITY_NAME_WIDTH = 100;
export const OFFERING_NAME_WIDTH = 100;
export const ACCOUNT_CELL_WIDTH = 50;
export const BASE_POINTS_WIDTH = 50;
export const KICKER_POINTS_WIDTH = 50;
export const STAGE_BUTTONS_WIDTH = 220;
export const BUNDLES_WIDTH = 120;
export const DUE_DATE_WIDTH = 100;
export const END_DATE_WIDTH = 100;
export const AAT_STATUS_WIDTH = 100;
export const ROW_WIDTH = COMPANY_NAME_WIDTH + OPPORTUNITY_NAME_WIDTH + OFFERING_NAME_WIDTH +
ACCOUNT_CELL_WIDTH + BASE_POINTS_WIDTH + KICKER_POINTS_WIDTH + STAGE_BUTTONS_WIDTH +
BUNDLES_WIDTH + DUE_DATE_WIDTH + END_DATE_WIDTH + AAT_STATUS_WIDTH;
export var STATUS_LIST = ['Planned', 'Pitched', 'Won', 'Lost'];
export const STATUS_LIST = ['Planned', 'Pitched', 'Won', 'Lost'];
export var AAT_STATUS_LIST = ['Active', 'Passive', 'Abandoned'];
export const AAT_STATUS_LIST = ['Active', 'Passive', 'Abandoned'];
// Imitate Streamy entities.
@@ -14,10 +14,15 @@
<p>
<button id="destroyDom">destroyDom</button>
<button id="createDom">createDom</button>
<button id="detectChanges">detectChanges</button>
<button id="updateDomProfile">profile updateDom</button>
<button id="createDomProfile">profile createDom</button>
<button id="detectChangesProfile">profile detectChanges</button>
</p>
<div>
Change detection runs:<span id="numberOfChecks"></span>
</div>
<div>
<tree id="root">Loading...</tree>
</div>

Some files were not shown because too many files have changed in this diff Show More