Compare commits

..

70 Commits

Author SHA1 Message Date
91f7aa3b15 docs(CHANGELOG): add changelog for 2.0.2 2016-10-05 16:25:04 -07:00
58b8091097 docs(readme): remove incorrect download count badge
npm doesn't currently support download counts for scoped packages, so there is no replacement right now.
2016-10-05 15:33:16 -07:00
0fde7ecd0f refactor: add license header to JS files & format files (#12081) 2016-10-05 15:33:16 -07:00
d25cd244af refactor(benchmarks): refactor to support AOT bootstrap in G3 (#12075) 2016-10-05 15:33:16 -07:00
e00de0c606 fix(forms): properly validate empty strings with patterns (#11450) 2016-10-05 15:33:16 -07:00
205103bdaa docs(traige): update triaging doc 2016-10-05 15:33:16 -07:00
0528dcb9dc refactor: simplify arrow functions (#12057) 2016-10-05 15:33:16 -07:00
6f7ed32154 fix(ShadowCss): fix attribute selectors in :host and :host-context (#12056)
Fix a regression introduced in #11917 while fixing #6249
2016-10-05 15:33:16 -07:00
1bd8ba80a7 refactor(facade): Remove most of StringMapWrapper facade. (#12022)
This change mostly automated by
12012b07a2
with some manual fixes.
2016-10-05 15:33:16 -07:00
adb17fed98 fix(Header): preserve case of the first init, set() or append() (#12023)
fixes #11624
2016-10-05 15:33:16 -07:00
3067ce6eb4 docs(NgModule): Fixed docs for NgModule.entryComponents (#12006)
* docs(NgModule): Corrected the wording of the documentation of `entryComponents`, fixed some minor grammar issues

* docs(NgModule): Remove redundant ComponentFactory mentions

* docs(NgModule): Restore ComponentFactory/ComponentResolver links
2016-10-05 15:33:16 -07:00
e102cd4757 docs(DEVELOPER.md): fix typos on "Tests" section (#12029) 2016-10-05 15:33:16 -07:00
db00ba7ae7 refactor(facade): remove DateWrapper (#12027) 2016-10-05 15:33:16 -07:00
5a8f116687 text(offline compiler): fix expected output 2016-10-05 15:33:16 -07:00
02a862f8af test(AstSerializer): fix serializing void tags 2016-10-05 15:33:16 -07:00
7578d8573b fix(xlif): fix <x> ctype names
fixes #12000
see http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html#ctype
2016-10-05 15:33:16 -07:00
de56e31cf0 style(I18N): Carriage returns in serialized files 2016-10-05 15:33:16 -07:00
eb85a7709a docs(gh): try to improve the issue template (#11891) 2016-10-05 15:33:16 -07:00
c99ef4938f fix(ShadowCss): support @page and @document CSS rules (#11878)
fixes #11860
2016-10-05 15:33:16 -07:00
7395400066 fix(ShadowCss): support quoted attribute values
fixes #6085
2016-10-05 15:33:16 -07:00
d985cc0253 refactor(ShadowCss): add missing types 2016-10-05 15:33:16 -07:00
aca117ac56 fix(ShadowCss): fix :host(tag) and :host-context(tag)
fixes #11972
2016-10-05 15:33:16 -07:00
2c3825f2c8 fix(BrowserAdapter): correctly removes styles on IE
fixes #7916
2016-10-05 15:33:16 -07:00
ea6defcf11 refactor(BrowserAdapter): cleanup 2016-10-05 15:33:16 -07:00
975aca95bb refactor(CssSelector): misc cleanup 2016-10-05 15:33:16 -07:00
5cb78566c2 docs(CssSelector): [name*=value] is not supported
fixes #6042
2016-10-05 15:33:16 -07:00
9bacb32ec5 feat(Parse5): update to the latest version 2.2.1
fixes #6237
2016-10-05 15:33:16 -07:00
6970991546 docs: update docs for ng2_ftl benchmark 2016-10-05 15:33:16 -07:00
bd012efcc4 fix(ShadowCss): support [attr="value with space"]
fixes #6249
2016-10-05 15:33:16 -07:00
2dd399658d refactor(ShadowCss): cleanup 2016-10-05 15:33:16 -07:00
d0dea574cb test(DirectiveResolver): test that a prop can have both @Input and @HostBinding 2016-10-05 15:33:16 -07:00
f7864edd4b refactor(DirectiveResolver): cleanup 2016-10-05 15:33:16 -07:00
9cc0a4ed10 fix(UrlSearchParams): change a behavior when a param value is null or undefined (#11990) 2016-10-05 15:33:16 -07:00
3ee8c75eff refactor(routerLinkActive): optimised routerLinkActive active check code (#11968)
Modify routerLinkActive to optimise performance by removing unnecessary iteration. By replacing Array.reduce with Array.some, the loop will break when it finds an active link. Useful if used on the parent of a large group of routerLinks. Furthermore, if a RouterLink is active it will not check the RouterLinkWithHrefs.
2016-10-05 15:33:16 -07:00
b39d3a173e refactor(facade): Inline isBlank called with object-type argument (#11992) 2016-10-05 15:33:16 -07:00
a4af1561b7 benchmarks: add ng2_ftl and ng2_switch_ftl benchmarks (#11963)
These benchmarks take the output of AoT
and manually tweaks it to explore possible
future changes to the compiler to produce
this output directly.
2016-10-05 15:33:16 -07:00
0851238e78 fix(upgrade): bind optional properties when upgrading from ng1 (#11411)
Previously, optional properties of a directive/component would be wrongly mapped and thus ignored.

Closes #10181
2016-10-05 15:33:16 -07:00
d2d98dad61 docs(forms): Added FormControl initialization information (#11948) 2016-10-05 15:33:16 -07:00
826c98e50c fix(ngc): allow ReflectorHost passed as argument to CodeGenerator#create (#11951) 2016-10-05 15:33:16 -07:00
830e6352dd chore(lint): remove unused imports (#11923)
This was done automatically by tslint, which can now fix issues it finds.
The fixer is still pending in PR https://github.com/palantir/tslint/pull/1568
Also I have a local bugfix for https://github.com/palantir/tslint/issues/1569
which causes too many imports to be deleted.
2016-10-05 15:33:16 -07:00
5911c3bd43 fix(compiler): move detection of unsafe properties for binding to ElementSchemaRegistry (#11378) 2016-10-05 15:33:16 -07:00
51e1994af0 fix(compiler): Do not embed templateUrl in view factories in non-debug mode. (#11818)
Fixes #11117.
2016-10-05 15:33:16 -07:00
faf5d90bf6 refactor: remove dead code 2016-10-05 15:33:16 -07:00
7e5413da89 refactor(facade): remove useless facades 2016-10-05 15:33:16 -07:00
e82a78e641 docs(upgrade): rename undeclared Ng2 to Ng2Component (#11950) 2016-10-05 15:33:16 -07:00
c13e55c8c3 (docs): removing addProvider from UpgradeAdapter (#11934)
The `addProvider` function in the `UpgradeAdapter` was deprecated in this [commit](d21331e902 (diff-77163e956a7842149f583846c1c01651)) and has been removed in final. Given this, the documentation for downgrading ng2 providers for use in ng1 is invalid.
2016-10-05 15:33:16 -07:00
a153504212 docs(router_config): add missing quote (#11925) 2016-10-05 15:33:16 -07:00
b8a75818ee docs: remove outdated docs (#11875) 2016-10-05 15:33:16 -07:00
f633826e99 chore(CHANGELOG): fix wrong issue link (#11871) 2016-10-05 15:33:16 -07:00
0a8887240a docs(ExceptionHandler): fix API docs (#11772)
fixes #11769
2016-10-05 15:33:16 -07:00
6d606dd02b ci(travis): increase node's heap size to prevent OOM on travis (#11869) 2016-10-05 15:33:16 -07:00
5c4215ccb4 Fix(http): invalidStateError if response body without content (#11786)
Fix(http): invalidStateError if response body without content
If the responseType has been specified and other than 'text', responseText throw an InvalidStateError exception

See XHR doc => https://xhr.spec.whatwg.org/#the-responsetext-attribute

Unit Test to prevent invalidStateError
2016-10-05 15:33:16 -07:00
85489a166e refactor(animations): ensure animation input/outputs are managed within the template parser (#11782)
Closes #11782
Closes #11601
Related #11707
2016-10-05 15:33:16 -07:00
cf750e17ed fix(router): do not reset the router state when updating the component (#11867) 2016-09-24 07:14:06 +09:00
712d1a7c37 chore(release): v2.0.1 2016-09-23 11:28:36 -07:00
16601f9359 docs(changelog): add changelog for 2.0.1 2016-09-23 10:50:42 -07:00
b81e2e7a31 fix(upgrade): allow attribute selectors for components in ng2 which are not part of upgrade (#11808)
fixes #11280
2016-09-23 10:48:47 -07:00
98fac36706 docs(Component): API docs for .encapsulation and .interpolation 2016-09-23 10:22:24 -07:00
3e780c032e refactor: misc cleanup 2016-09-23 10:22:05 -07:00
e09882180e refactor(common): cleanup (#11668) 2016-09-23 10:21:58 -07:00
0e18c57a17 docs(core): mark TestBed as stable api and add preliminary docs (#11767)
TestBed was accidentaly ommited from the 'stable' api list during the API sweep before final. We do consider it to be stable.
2016-09-23 10:21:50 -07:00
51e2b9c073 docs(contributing): remove preview references (#11795) 2016-09-23 10:21:44 -07:00
f218e240d3 ci(BrowserStack): add Safari 10 (#11796) 2016-09-23 10:21:31 -07:00
af6b219f8e refactor(TemplateParser): clearer error message for on* binding (#11802)
fixes #11756
2016-09-23 10:21:15 -07:00
20addf5f9f chore(ISSUE_TEMPLATE): update Angular version field (#11821) 2016-09-23 10:21:02 -07:00
2860418a3c fix(forms): disable all radios with disable() 2016-09-23 10:19:58 -07:00
39e251eea7 fix(forms): support unbound disabled in ngModel (#11736) 2016-09-23 10:19:46 -07:00
d7d716d5db chore(zone.js): update to 0.6.25 (#11725) 2016-09-23 10:19:30 -07:00
a95d65241c fix(compiler): Safe property access expressions work in event bindings (#11724) 2016-09-23 10:19:24 -07:00
fdb22bd185 refactor: misc cleanup (#11654) 2016-09-23 10:18:38 -07:00
1000 changed files with 22379 additions and 52350 deletions

View File

@ -1,557 +1,3 @@
<a name="4.0.0-beta.2"></a>
# [4.0.0-beta.2](https://github.com/angular/angular/compare/4.0.0-beta.1...4.0.0-beta.2) (2017-01-06)
### Features
* **compiler:** generate less code for bindings to DOM elements ([db49d42](https://github.com/angular/angular/commit/db49d42))
* **compiler:** generate proper reexports in `.ngfactory.ts` files to not need transitive deps for compiling `.ngfactory.ts` files. ([#13524](https://github.com/angular/angular/issues/13524)) ([9c69703](https://github.com/angular/angular/commit/9c69703)), closes [#12787](https://github.com/angular/angular/issues/12787)
* **router:** add an extra argument to CanDeactivate interface ([#13560](https://github.com/angular/angular/issues/13560)) ([69fa3bb](https://github.com/angular/angular/commit/69fa3bb)), closes [#9853](https://github.com/angular/angular/issues/9853)
### Bug Fixes
* **common:** improve error message by adding a link to trackBy docs ([#13634](https://github.com/angular/angular/issues/13634)) ([6976903](https://github.com/angular/angular/commit/6976903))
* **common:** do not override locale provided on bootstrap ([#13654](https://github.com/angular/angular/issues/13654)) ([2dd6280](https://github.com/angular/angular/commit/2dd6280)), closes [#13607](https://github.com/angular/angular/issues/13607)
* **common:** allow null/undefined values for `NgForTrackBy` ([f88cd2f](https://github.com/angular/angular/commit/f88cd2f)), closes [#13641](https://github.com/angular/angular/issues/13641)
* **compiler:** dont throw when using `ANALYZE_FOR_ENTRY_COMPONENTS` with user classes ([#13679](https://github.com/angular/angular/issues/13679)) ([7690d02](https://github.com/angular/angular/commit/7690d02)), closes [#13565](https://github.com/angular/angular/issues/13565)
* **compiler:** improve error message for undefined providers ([#13546](https://github.com/angular/angular/issues/13546)) ([6b02b80](https://github.com/angular/angular/commit/6b02b80)), closes [#10835](https://github.com/angular/angular/issues/10835)
* **compiler:** improve the error when template is not a string ([2c0c86e](https://github.com/angular/angular/commit/2c0c86e)), closes [#8708](https://github.com/angular/angular/issues/8708) [#13377](https://github.com/angular/angular/issues/13377)
* **compiler:** query `<template>` elements before their children. ([#13677](https://github.com/angular/angular/issues/13677)) ([7c21064](https://github.com/angular/angular/commit/7c21064)), closes [#13118](https://github.com/angular/angular/issues/13118) [#13167](https://github.com/angular/angular/issues/13167)
* **compiler:** throw an error for invalid provider ([#13544](https://github.com/angular/angular/issues/13544)) ([445ed43](https://github.com/angular/angular/commit/445ed43)), closes [#8870](https://github.com/angular/angular/issues/8870)
* **compiler:** allow "." in attribute selectors ([#13653](https://github.com/angular/angular/issues/13653)) ([881eb89](https://github.com/angular/angular/commit/881eb89)), 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)) ([21030e9](https://github.com/angular/angular/commit/21030e9))
* **core:** animations should blend in all previously transitioned styles into next animation if interrupted ([#13148](https://github.com/angular/angular/issues/13148)) ([889b48d](https://github.com/angular/angular/commit/889b48d))
* **core:** remove reference to "Angular 2" in dev mode warning ([#13751](https://github.com/angular/angular/issues/13751)) ([c5c53f3](https://github.com/angular/angular/commit/c5c53f3))
* **forms:** Validators.required properly validate arrays ([#13362](https://github.com/angular/angular/issues/13362)) ([9898d8f](https://github.com/angular/angular/commit/9898d8f)), closes [#12274](https://github.com/angular/angular/issues/12274)
* **i18n:** parse ICU messages while normalizing templates ([e74d8aa](https://github.com/angular/angular/commit/e74d8aa))
* **language-service:** support TypeScript 2.1 ([#13655](https://github.com/angular/angular/issues/13655)) ([8063b0d](https://github.com/angular/angular/commit/8063b0d))
* **router:** fix lazy loaded module with wildcard route ([#13649](https://github.com/angular/angular/issues/13649)) ([0eca960](https://github.com/angular/angular/commit/0eca960)), closes [#12955](https://github.com/angular/angular/issues/12955)
* **router:** routerLink support of null ([#13380](https://github.com/angular/angular/issues/13380)) ([174334d](https://github.com/angular/angular/commit/174334d)), 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)) ([07e0fce](https://github.com/angular/angular/commit/07e0fce)), closes [#12912](https://github.com/angular/angular/issues/12912)
* **testing:** improve misleading error message when don't call compileComponents ([#13543](https://github.com/angular/angular/issues/13543)) ([67380d4](https://github.com/angular/angular/commit/67380d4)), closes [#11301](https://github.com/angular/angular/issues/11301)
* **upgrade:** fix/improve support for lifecycle hooks ([#13020](https://github.com/angular/angular/issues/13020)) ([e5c4e58](https://github.com/angular/angular/commit/e5c4e58))
### BREAKING CHANGES
* **core**: `SimpleChange` now takes an additional argument that defines whether this is the first
change or not. This is a low profile API and we don't expect anyone to be affected by this change.
If you are impacted by this change please file an issue. ([465516b](https://github.com/angular/angular/commit/465516b))
<a name="4.0.0-beta.1"></a>
# [4.0.0-beta.1](https://github.com/angular/angular/compare/2.4.0-marker...4.0.0-beta.1) (2016-12-22)
### Features
* **upgrade:** support the `$doCheck()` lifecycle hook in `UpgradeComponent` ([#13015](https://github.com/angular/angular/issues/13015)) ([9da4c25](https://github.com/angular/angular/commit/9da4c25))
Note: 4.0.0-beta.1 release also contains all the changes present in the 2.4.0 and the 2.4.1 releases.
<a name="2.4.1"></a>
## [2.4.1](https://github.com/angular/angular/compare/2.4.0...2.4.1) (2016-12-21)
### Bug Fixes
* **animations:** always quote string map key values in AOT code ([#13602](https://github.com/angular/angular/issues/13602)) ([6a5e46c](https://github.com/angular/angular/commit/6a5e46c))
* **animations:** always recover from a failed animation step ([#13604](https://github.com/angular/angular/issues/13604)) ([d788c67](https://github.com/angular/angular/commit/d788c67))
* **compiler:** ignore `@import` in comments ([#13368](https://github.com/angular/angular/issues/13368)) ([6316e5d](https://github.com/angular/angular/commit/6316e5d)), closes [#12196](https://github.com/angular/angular/issues/12196)
* **core:** improve error message when component factory cannot be found ([#13541](https://github.com/angular/angular/issues/13541)) ([b9e979e](https://github.com/angular/angular/commit/b9e979e)), closes [#12678](https://github.com/angular/angular/issues/12678)
* **router:** should reset location if a navigation by location is successful ([#13545](https://github.com/angular/angular/issues/13545)) ([a38f14b](https://github.com/angular/angular/commit/a38f14b)), closes [#13491](https://github.com/angular/angular/issues/13491)
<a name="2.4.0"></a>
# [2.4.0 stability-interjection](https://github.com/angular/angular/compare/2.3.1...2.4.0) (2016-12-20)
### Bug Fixes
* **animations:** allow players to be destroyed before initialized ([#13346](https://github.com/angular/angular/issues/13346)) ([b36f4bc](https://github.com/angular/angular/commit/b36f4bc)), closes [#13293](https://github.com/angular/angular/issues/13293)
* **build:** use bash string comparison operator ([#13502](https://github.com/angular/angular/issues/13502)) ([50afbe0](https://github.com/angular/angular/commit/50afbe0))
* **compiler:** do not lex `}}` when interpolation is disabled ([#13531](https://github.com/angular/angular/issues/13531)) ([9b87bb6](https://github.com/angular/angular/commit/9b87bb6)), closes [#13525](https://github.com/angular/angular/issues/13525)
* **compiler-cli:** produce metadata for .d.ts files without metadata ([#13526](https://github.com/angular/angular/issues/13526)) ([debb0c9](https://github.com/angular/angular/commit/debb0c9)), closes [#13307](https://github.com/angular/angular/issues/13307) [#13473](https://github.com/angular/angular/issues/13473) [#13521](https://github.com/angular/angular/issues/13521)
* **i18n:** add a default example to xmb placeholders ([#13507](https://github.com/angular/angular/issues/13507)) ([3f17841](https://github.com/angular/angular/commit/3f17841))
* **upgrade:** fix `registerForNg1Tests` ([#13522](https://github.com/angular/angular/issues/13522)) ([c26c24c](https://github.com/angular/angular/commit/c26c24c))
### Features
* update to `rxjs@5.0.1` and unpin the rxjs peerDeps via `^5.0.1` ([#13572](https://github.com/angular/angular/issues/13572)) ([8d5da1e](https://github.com/angular/angular/commit/8d5da1e)), closes [#13561](https://github.com/angular/angular/issues/13561) [#13478](https://github.com/angular/angular/issues/13478)
<a name="4.0.0-beta.0"></a>
# [4.0.0-beta.0](https://github.com/angular/angular/compare/2.3.0...4.0.0-beta.0) (2016-12-15)
### Features
* **common:** add a `titlecase` pipe ([#13324](https://github.com/angular/angular/issues/13324)) ([61d7c1e](https://github.com/angular/angular/commit/61d7c1e)), closes [#11436](https://github.com/angular/angular/issues/11436)
* **common:** export NgLocaleLocalization ([#13367](https://github.com/angular/angular/issues/13367)) ([56dce0e](https://github.com/angular/angular/commit/56dce0e)), closes [#11921](https://github.com/angular/angular/issues/11921)
* **compiler:** add id property to i18nMessage ([6dd5201](https://github.com/angular/angular/commit/6dd5201))
* **compiler:** digest methods return i18nMessage id if sets ([562f7a2](https://github.com/angular/angular/commit/562f7a2))
* **forms:** add novalidate by default ([#13092](https://github.com/angular/angular/issues/13092)) ([4c35be3](https://github.com/angular/angular/commit/4c35be3))
* **http:** simplify URLSearchParams creation ([#13338](https://github.com/angular/angular/issues/13338)) ([90c2235](https://github.com/angular/angular/commit/90c2235)), closes [#8858](https://github.com/angular/angular/issues/8858)
* **language-service:** warn when a method isn't called in an event ([#13437](https://github.com/angular/angular/issues/13437)) ([9ec0a4e](https://github.com/angular/angular/commit/9ec0a4e))
* **platform browser:** introduce Meta service ([#12322](https://github.com/angular/angular/issues/12322)) ([72361fb](https://github.com/angular/angular/commit/72361fb))
* **router:** routerLink add tabindex attribute ([#13094](https://github.com/angular/angular/issues/13094)) ([a006c14](https://github.com/angular/angular/commit/a006c14)), closes [#10895](https://github.com/angular/angular/issues/10895)
* **testing:** add overrideTemplate method ([#13372](https://github.com/angular/angular/issues/13372)) ([169ed82](https://github.com/angular/angular/commit/169ed82)), closes [#10685](https://github.com/angular/angular/issues/10685)
* **common** ngIf now supports else; saves condition to local var ([b4db73d](https://github.com/angular/angular/commit/b4db73d)), closes [#13061](https://github.com/angular/angular/issues/13061) [#13297](https://github.com/angular/angular/issues/13297)
Note: 4.0.0-beta.0 release also contains all the changes present in the 2.3.1 release.
<a name="2.3.1"></a>
## [2.3.1](https://github.com/angular/angular/compare/2.3.0...2.3.1) (2016-12-15)
### Bug Fixes
* **animations:** always cleanup players after they have finished internally ([#13334](https://github.com/angular/angular/issues/13334)) ([a26e054](https://github.com/angular/angular/commit/a26e054)), closes [#13333](https://github.com/angular/angular/issues/13333)
* **animations:** throw errors and normalize offset beyond the range of [0,1] ([6557bc3](https://github.com/angular/angular/commit/6557bc3)), closes [#13348](https://github.com/angular/angular/issues/13348) [#13440](https://github.com/angular/angular/issues/13440)
* **compiler:** emit quoted object literal keys if the source is quoted ([ecfad46](https://github.com/angular/angular/commit/ecfad46)), closes [#13249](https://github.com/angular/angular/issues/13249) [#13356](https://github.com/angular/angular/issues/13356)
* **compiler:** fix merge error in compiler_host ([69b52eb](https://github.com/angular/angular/commit/69b52eb))
* **compiler:** fix PR 13322 ([#13331](https://github.com/angular/angular/issues/13331)) ([79728b4](https://github.com/angular/angular/commit/79728b4))
* **compiler:** fix simplify a reference without a name ([1c279b3](https://github.com/angular/angular/commit/1c279b3)), closes [#13470](https://github.com/angular/angular/issues/13470)
* **compiler:** fix transpiled ES5 code ([#13322](https://github.com/angular/angular/issues/13322)) ([6c1d790](https://github.com/angular/angular/commit/6c1d790)), closes [#13301](https://github.com/angular/angular/issues/13301)
* **compiler:** generated CSS files suffixed with ngstyle. ([#13353](https://github.com/angular/angular/issues/13353)) ([c8a9b70](https://github.com/angular/angular/commit/c8a9b70)), closes [#13141](https://github.com/angular/angular/issues/13141)
* **compiler:** make sure provider values with `name` property dont break. ([efa2d80](https://github.com/angular/angular/commit/efa2d80)), closes [#13394](https://github.com/angular/angular/issues/13394) [#13445](https://github.com/angular/angular/issues/13445)
* **compiler:** narrow the span reported for invalid pipes ([307d305](https://github.com/angular/angular/commit/307d305)), closes [#13326](https://github.com/angular/angular/issues/13326) [#13411](https://github.com/angular/angular/issues/13411)
* **compiler:** propagate exports when upgrading metadata to v2 ([f6ef7d6](https://github.com/angular/angular/commit/f6ef7d6))
* **compiler:** resolver should merge host bindings and listeners ([#13474](https://github.com/angular/angular/issues/13474)) ([6aeaca3](https://github.com/angular/angular/commit/6aeaca3)), closes [#13327](https://github.com/angular/angular/issues/13327)
* **compiler:** support dotted property binding ([8db184d](https://github.com/angular/angular/commit/8db184d)), closes [angular/flex-layout#34](https://github.com/angular/flex-layout/issues/34)
* **compiler:** update to metadata version 3 ([#13464](https://github.com/angular/angular/issues/13464)) ([b9b557c](https://github.com/angular/angular/commit/b9b557c))
* **core:** detectChanges() doesn't work on detached instance ([4d6ac9d](https://github.com/angular/angular/commit/4d6ac9d)), closes [#13426](https://github.com/angular/angular/issues/13426) [#13472](https://github.com/angular/angular/issues/13472)
* **core:** properly destroy embedded Views attatched to ApplicationRef ([#13459](https://github.com/angular/angular/issues/13459)) ([d40bbf4](https://github.com/angular/angular/commit/d40bbf4)), closes [#13062](https://github.com/angular/angular/issues/13062)
* **core:** remove logError from logGroup ([#12925](https://github.com/angular/angular/issues/12925)) ([5fab871](https://github.com/angular/angular/commit/5fab871))
* **forms:** ensure `select[multiple]` retains selections ([b3dcff0](https://github.com/angular/angular/commit/b3dcff0)), closes [#12527](https://github.com/angular/angular/issues/12527) [#12654](https://github.com/angular/angular/issues/12654)
* **forms:** fix Validators.min/maxLength with FormArray ([#13095](https://github.com/angular/angular/issues/13095)) ([7383e4a](https://github.com/angular/angular/commit/7383e4a)), closes [#13089](https://github.com/angular/angular/issues/13089)
* **forms:** introduce checkbox required validator ([124267c](https://github.com/angular/angular/commit/124267c)), closes [#11459](https://github.com/angular/angular/issues/11459) [#13364](https://github.com/angular/angular/issues/13364)
* **http:** check response body text against undefined ([#13017](https://github.com/angular/angular/issues/13017)) ([f106a18](https://github.com/angular/angular/commit/f106a18))
* **http:** create a copy of headers when merge options ([#13365](https://github.com/angular/angular/issues/13365)) ([65c9b5b](https://github.com/angular/angular/commit/65c9b5b)), closes [#11980](https://github.com/angular/angular/issues/11980)
* **language-service:** correctly type `undefined` ([0a7364f](https://github.com/angular/angular/commit/0a7364f)), closes [#13412](https://github.com/angular/angular/issues/13412) [#13414](https://github.com/angular/angular/issues/13414)
* **compiler**: better error when directive not listed in NgModule.declarations ([b0cd514](https://github.com/angular/angular/commit/b0cd514))
* **language-service:** treat string unions as strings ([#13406](https://github.com/angular/angular/issues/13406)) ([14dd2b3](https://github.com/angular/angular/commit/14dd2b3)), closes [#13403](https://github.com/angular/angular/issues/13403)
* **router:** add support for query params with multiple values ([e4d5a5f](https://github.com/angular/angular/commit/e4d5a5f)), closes [#11373](https://github.com/angular/angular/issues/11373)
* **router:** Use T type in Resolve interface ([#13242](https://github.com/angular/angular/issues/13242)) ([5ee8155](https://github.com/angular/angular/commit/5ee8155))
* **selector:** SelectorMatcher match elements with :not selector ([#12977](https://github.com/angular/angular/issues/12977)) ([392c9ac](https://github.com/angular/angular/commit/392c9ac))
* **tsc-wrapped:** generate metadata for exports without module specifier ([cd03c77](https://github.com/angular/angular/commit/cd03c77)), closes [#13327](https://github.com/angular/angular/issues/13327)
* **upgrade:** fix downgrade content projection and injector inheritance ([86c5098](https://github.com/angular/angular/commit/86c5098)), closes [#6629](https://github.com/angular/angular/issues/6629) [#7727](https://github.com/angular/angular/issues/7727) [#8729](https://github.com/angular/angular/issues/8729) [#9643](https://github.com/angular/angular/issues/9643) [#9649](https://github.com/angular/angular/issues/9649) [#12675](https://github.com/angular/angular/issues/12675)
* **upgrade:** enable Angular 1 unit testing of upgrade module ([2fc0560](https://github.com/angular/angular/commit/2fc0560)), closes [#5462](https://github.com/angular/angular/issues/5462) [#12675](https://github.com/angular/angular/issues/12675)
### Performance Improvements
* **animations:** always run the animation queue outside of zones ([e2622ad](https://github.com/angular/angular/commit/e2622ad)), closes [#13440](https://github.com/angular/angular/issues/13440)
### Note ###
Due to regression in the 2.3.0 release that was fixed by [#13464](https://github.com/angular/angular/pull/13464),
components that have been compiled using 2.3.0 and published to npm will need to be recompiled and republished.
The >=2.3.1 compiler will issue is the following error if it encounters components compiled with 2.3.0:
`Unsupported metadata version 2 for module ${module}. This module should be compiled with a newer version of ngc`.
We are adding more tests to our test suite to catch these kinds of problems before we cut a release.
<a name="2.3.0"></a>
# [2.3.0](https://github.com/angular/angular/compare/2.3.0-rc.0...2.3.0) (2016-12-07)
### Bug Fixes
* **common:** make sure the plural category exists ([#13169](https://github.com/angular/angular/issues/13169)) ([82c81cd](https://github.com/angular/angular/commit/82c81cd)), closes [#12379](https://github.com/angular/angular/issues/12379)
* **compiler:** include the summaries of reexported modules / directives / pipes ([#13196](https://github.com/angular/angular/issues/13196)) ([75d1617](https://github.com/angular/angular/commit/75d1617))
* **compiler:** serialize any `StaticSymbol` correctly, not matter in which context ([5614c4f](https://github.com/angular/angular/commit/5614c4f))
* **compiler:** short-circut expressions with an index ([#13263](https://github.com/angular/angular/issues/13263)) ([f31c947](https://github.com/angular/angular/commit/f31c947)), closes [#13254](https://github.com/angular/angular/issues/13254)
* **core:** display framework version on bootstrapped component ([#13252](https://github.com/angular/angular/issues/13252)) ([16efb13](https://github.com/angular/angular/commit/16efb13))
* **facade:** cache original format string ([#12764](https://github.com/angular/angular/issues/12764)) ([a132287](https://github.com/angular/angular/commit/a132287))
* **http:** set the default Accept header ([#12989](https://github.com/angular/angular/issues/12989)) ([986abbe](https://github.com/angular/angular/commit/986abbe)), closes [#6354](https://github.com/angular/angular/issues/6354)
* **language-service:** avoid throwing for invalid class declarations ([#13257](https://github.com/angular/angular/issues/13257)) ([93556a5](https://github.com/angular/angular/commit/93556a5)), closes [#13253](https://github.com/angular/angular/issues/13253)
* **language-service:** do not throw for invalid metadata ([#13261](https://github.com/angular/angular/issues/13261)) ([4a09c81](https://github.com/angular/angular/commit/4a09c81)), closes [#13255](https://github.com/angular/angular/issues/13255)
* **language-service:** remove incompletely used parameter from `createLanguageServiceFromTypescript()` ([#13278](https://github.com/angular/angular/issues/13278)) ([25c2141](https://github.com/angular/angular/commit/25c2141)), closes [#13277](https://github.com/angular/angular/issues/13277)
* **language-service:** update to use `CompilerHost` from compiler-cli ([#13189](https://github.com/angular/angular/issues/13189)) ([3ff6554](https://github.com/angular/angular/commit/3ff6554))
* **router:** allow specifying a matcher without specifying a path ([bbb7a39](https://github.com/angular/angular/commit/bbb7a39)), closes [#12972](https://github.com/angular/angular/issues/12972)
* **router:** fix replaceUrl on RouterLink directives ([349ad75](https://github.com/angular/angular/commit/349ad75))
* **router:** fix skipLocationChanges on RouterLink directives ([f562cbf](https://github.com/angular/angular/commit/f562cbf)), closes [#13156](https://github.com/angular/angular/issues/13156)
* **router:** make setUpLocationChangeListener idempotent ([25e5b2f](https://github.com/angular/angular/commit/25e5b2f))
* **router:** runs guards every time when unsuccessfully navigating to the same url over and over again ([#13209](https://github.com/angular/angular/issues/13209)) ([d46b8de](https://github.com/angular/angular/commit/d46b8de))
* **router:** throw a better error message when angular 1 is not bootstraped ([c767df0](https://github.com/angular/angular/commit/c767df0))
* **router:** validate nested routes ([#13224](https://github.com/angular/angular/issues/13224)) ([2893c2c](https://github.com/angular/angular/commit/2893c2c)), closes [#12827](https://github.com/angular/angular/issues/12827)
* **tsc-wrapped:** have UserError display the actual error ([393c100](https://github.com/angular/angular/commit/393c100))
### Features
* **compiler:** read and write `.ngsummary.json` files ([614a35d](https://github.com/angular/angular/commit/614a35d)), closes [#12787](https://github.com/angular/angular/issues/12787)
<a name="2.3.0-rc.0"></a>
# [2.3.0-rc.0](https://github.com/angular/angular/compare/2.3.0-beta.0...2.3.0-rc.0) (2016-11-30)
### Bug Fixes
* **animations:** blend in all previously transitioned styles into next animation if interrupted ([#13014](https://github.com/angular/angular/issues/13014)) ([ef96763](https://github.com/angular/angular/commit/ef96763)), closes [#13013](https://github.com/angular/angular/issues/13013)
* **benchmarks:** use sanitized style values ([#12943](https://github.com/angular/angular/issues/12943)) ([fc5ac1e](https://github.com/angular/angular/commit/fc5ac1e))
* **build:** update versions of umd bundles ([#13038](https://github.com/angular/angular/issues/13038)) ([86ffa88](https://github.com/angular/angular/commit/86ffa88)), closes [#13037](https://github.com/angular/angular/issues/13037)
* **changelog:** replace beta.1 with beta.0 ([#12961](https://github.com/angular/angular/issues/12961)) ([07a986d](https://github.com/angular/angular/commit/07a986d))
* **ci:** pin version of npm on CircleCI ([#12954](https://github.com/angular/angular/issues/12954)) ([a3884db](https://github.com/angular/angular/commit/a3884db))
* **closure:** quote date pattern aliases ([#13012](https://github.com/angular/angular/issues/13012)) ([7dcca30](https://github.com/angular/angular/commit/7dcca30))
* **common:** update DatePipe to allow closure compilation ([b2b7219](https://github.com/angular/angular/commit/b2b7219))
* **compiler:** correctly evaluate references to static functions ([#13133](https://github.com/angular/angular/issues/13133)) ([627282d](https://github.com/angular/angular/commit/627282d))
* **compiler:** fix performance regression caused by 5b0f9e2 ([43c0e9a](https://github.com/angular/angular/commit/43c0e9a)), closes [#13146](https://github.com/angular/angular/issues/13146)
* **compiler:** fix versions of `@angular/tsc-wrapped` ([bccf0e6](https://github.com/angular/angular/commit/bccf0e6))
* **compiler-cli:** fix paths in source maps to be relative ([2a3ca7b](https://github.com/angular/angular/commit/2a3ca7b)), closes [#13040](https://github.com/angular/angular/issues/13040)
* **compiler-cli:** pin the version of `tsc-wrapped` ([966bcba](https://github.com/angular/angular/commit/966bcba))
* **language-service:** harden against partial normalization of directives ([2975d89](https://github.com/angular/angular/commit/2975d89))
* **core:** shrinkwrap was out of date with packages. ([e45b7ff](https://github.com/angular/angular/commit/e45b7ff))
* **language-service:** make link check pass ([7194fc2](https://github.com/angular/angular/commit/7194fc2))
* **router:** guards restore an incorrect url when used with skipLocationChange ([ad20d7d](https://github.com/angular/angular/commit/ad20d7d)), closes [#12825](https://github.com/angular/angular/issues/12825)
* **router:** support redirects to named outlets ([602522b](https://github.com/angular/angular/commit/602522b)), closes [#12740](https://github.com/angular/angular/issues/12740) [#9921](https://github.com/angular/angular/issues/9921)
* **tsc-wrapped:** set correct version number ([897555c](https://github.com/angular/angular/commit/897555c))
* **tsc-wrapped:** still emit version 1 metadata to allow use of new components in old setups ([bc69c74](https://github.com/angular/angular/commit/bc69c74))
* **upgrade:** call ng1 lifecycle hooks ([#12875](https://github.com/angular/angular/issues/12875)) ([1ef4696](https://github.com/angular/angular/commit/1ef4696))
* **version:** take all of version string after patch version ([f275f36](https://github.com/angular/angular/commit/f275f36))
### Features
* **core:** update RxJS peer dependency to 5.0.0-rc.4 Please see [this gist](https://gist.github.com/robwormald/19dea0c70a6e01aadced6731aed4f9f7) if you depend on the `cache` operator ([2d6a003](https://github.com/angular/angular/commit/2d6a003)), closes [#13125](https://github.com/angular/angular/issues/13125)
* **core:** upgrade zone.js to v0.7.1 ([c4bbafc](https://github.com/angular/angular/commit/c4bbafc))
* **build:** record angular version in the dom ([#13164](https://github.com/angular/angular/issues/13164)) ([e628b66](https://github.com/angular/angular/commit/e628b66))
* **core:** expose destroy() method on ViewRef ([808275a](https://github.com/angular/angular/commit/808275a))
* **core:** properly support inheritance ([f5c8e09](https://github.com/angular/angular/commit/f5c8e09)), closes [#11606](https://github.com/angular/angular/issues/11606) [#12892](https://github.com/angular/angular/issues/12892)
* **language-service:** add services to support editors ([#12987](https://github.com/angular/angular/issues/12987)) ([519a324](https://github.com/angular/angular/commit/519a324))
* **router:** add support for custom route reuse strategies ([42cf06f](https://github.com/angular/angular/commit/42cf06f))
* **tools:** allow disabling annotation lowering ([c1a62e2](https://github.com/angular/angular/commit/c1a62e2))
<a name="2.2.4"></a>
## [2.2.4](https://github.com/angular/angular/compare/2.2.3...2.2.4) (2016-11-30)
### Bug Fixes
* **common:** update DatePipe to allow closure compilation ([eba53fd](https://github.com/angular/angular/commit/eba53fd))
* **compiler:** fix performance regression caused by 5b0f9e2 ([ee2d6e5](https://github.com/angular/angular/commit/ee2d6e5)), closes [#13146](https://github.com/angular/angular/issues/13146)
* **compiler-cli:** fix paths in source maps to be relative ([eb173bc](https://github.com/angular/angular/commit/eb173bc)), closes [#13040](https://github.com/angular/angular/issues/13040)
<a name="2.2.3"></a>
## [2.2.3](https://github.com/angular/angular/compare/2.2.2...2.2.3) (2016-11-23)
### Bug Fixes
* **compiler:** Revert: fix versions of `@angular/tsc-wrapped` ([015ca47](https://github.com/angular/angular/commit/015ca47))
* **animations:** Revert: blend in all previously transitioned styles into next animation if interrupted ([c12e56e](https://github.com/angular/angular/commit/c12e56e))
<a name="2.2.2"></a>
## [2.2.2](https://github.com/angular/angular/compare/2.2.1...2.2.2) (2016-11-22)
### Bug Fixes
* **animations:** blend in all previously transitioned styles into next animation if interrupted ([#13014](https://github.com/angular/angular/issues/13014)) ([ea4fc9b](https://github.com/angular/angular/commit/ea4fc9b)), closes [#13013](https://github.com/angular/angular/issues/13013)
* **benchmarks:** use sanitized style values ([#12943](https://github.com/angular/angular/issues/12943)) ([33a7902](https://github.com/angular/angular/commit/33a7902))
* **closure:** quote date pattern aliases ([#13012](https://github.com/angular/angular/issues/13012)) ([0956ace](https://github.com/angular/angular/commit/0956ace))
* **compiler:** fix versions of `@angular/tsc-wrapped` ([2fe6fb1](https://github.com/angular/angular/commit/2fe6fb1))
* **router:** add a banner file for the router ([#12919](https://github.com/angular/angular/issues/12919)) ([8df328b](https://github.com/angular/angular/commit/8df328b))
* **router:** add a banner file for the router ([#12919](https://github.com/angular/angular/issues/12919)) ([511cd4d](https://github.com/angular/angular/commit/511cd4d))
* **router:** removes a peer dependency from router to upgrade ([115f18f](https://github.com/angular/angular/commit/115f18f))
* **router:** removes a peer dependency from router to upgrade ([87d5d49](https://github.com/angular/angular/commit/87d5d49))
* **router:** support redirects to named outlets ([09226d9](https://github.com/angular/angular/commit/09226d9)), closes [#12740](https://github.com/angular/angular/issues/12740) [#9921](https://github.com/angular/angular/issues/9921)
* **upgrade:** call ng1 lifecycle hooks ([#12875](https://github.com/angular/angular/issues/12875)) ([462316b](https://github.com/angular/angular/commit/462316b))
<a name="2.3.0-beta.0"></a>
# [2.3.0-beta.0](https://github.com/angular/angular/compare/2.2.0...2.3.0-beta.0) (2016-11-17)
### Bug Fixes
* **compiler:** assert xliff messages have translations ([7908679](https://github.com/angular/angular/commit/7908679)), closes [#12815](https://github.com/angular/angular/issues/12815) [#12604](https://github.com/angular/angular/issues/12604)
* **compiler:** updates hash algo for xmb/xtb files ([2f14415](https://github.com/angular/angular/commit/2f14415))
* **core:** fix placeholders handling in i18n. ([76e4911](https://github.com/angular/angular/commit/76e4911)), closes [#12512](https://github.com/angular/angular/issues/12512)
* **core:** misc i18n fixes ([ed5e98d](https://github.com/angular/angular/commit/ed5e98d))
* **core:** xmb serializer uses decimal messaged IDs ([08c038e](https://github.com/angular/angular/commit/08c038e)), closes [#12511](https://github.com/angular/angular/issues/12511)
* **platform-browser:** enable AOT ([efbbefd](https://github.com/angular/angular/commit/efbbefd)), closes [#12783](https://github.com/angular/angular/issues/12783)
### Features
* **core:** add `attachView` / `detachView` to ApplicationRef ([9f7d32a](https://github.com/angular/angular/commit/9f7d32a)), closes [#9293](https://github.com/angular/angular/issues/9293)
* **core:** expose `ViewRef` as `ChangeDetectorRef` ([1b5384e](https://github.com/angular/angular/commit/1b5384e)), closes [#12722](https://github.com/angular/angular/issues/12722)
* **core:** implements a decimal fingerprint for i18n ([582550a](https://github.com/angular/angular/commit/582550a))
* **router:** register router with ngprobe ([c2fae72](https://github.com/angular/angular/commit/c2fae72))
* **router_link:** add skipLocationChange and replaceUrl inputs ([#12850](https://github.com/angular/angular/issues/12850)) ([46d1502](https://github.com/angular/angular/commit/46d1502))
Note: The 2.3.0-beta.0 release also contains all the changes present in the 2.2.1 release.
<a name="2.2.1"></a>
## [2.2.1](https://github.com/angular/angular/compare/2.2.0...2.2.1) (2016-11-17)
### Bug Fixes
* **animations:** only pass in same typed players as previous players into web-animations ([#12907](https://github.com/angular/angular/issues/12907)) ([583d283](https://github.com/angular/angular/commit/583d283))
* **animations:** retain styling when transition destinations are changed ([#12208](https://github.com/angular/angular/issues/12208)) ([5c46c49](https://github.com/angular/angular/commit/5c46c49)), closes [#9661](https://github.com/angular/angular/issues/9661)
* **core:** support `ngTemplateOutlet` in production mode ([#12921](https://github.com/angular/angular/issues/12921)) ([4628798](https://github.com/angular/angular/commit/4628798)), closes [#12911](https://github.com/angular/angular/issues/12911)
* **http:** correctly handle response body for 204 status code ([21a4de9](https://github.com/angular/angular/commit/21a4de9)), closes [#12830](https://github.com/angular/angular/issues/12830) [#12393](https://github.com/angular/angular/issues/12393)
* **http:** return request url if it cannot be retrieved from response ([845ea23](https://github.com/angular/angular/commit/845ea23)), closes [#12837](https://github.com/angular/angular/issues/12837)
* **upgrade:** make AoT ngUpgrade work with the testability API and resumeBootstrap() ([#12910](https://github.com/angular/angular/issues/12910)) ([dc1662a](https://github.com/angular/angular/commit/dc1662a))
* **platform-browser:** fix disableDebugTools() ([#12918](https://github.com/angular/angular/issues/12918)) ([7b67bad](https://github.com/angular/angular/commit/7b67bad))
* **router:** add a banner file for the router ([#12919](https://github.com/angular/angular/issues/12919)) ([364642d](https://github.com/angular/angular/commit/364642d))
* **router:** removes a peer dependency from router to upgrade ([1dcf1f4](https://github.com/angular/angular/commit/1dcf1f4))
* **forms** allow for null values in HTML select options bound with ngValue ([e0ce545](https://github.com/angular/angular/commit/e0ce545)), closes [#10349](https://github.com/angular/angular/issues/10349)
* **router:** should not create a route state if navigation is canceled ([#12868](https://github.com/angular/angular/issues/12868)) ([dabaf85](https://github.com/angular/angular/commit/dabaf85)), closes [#12776](https://github.com/angular/angular/issues/12776)
* **common:** select should allow for null values in HTML select options bound with ngValue ([e02c180](https://github.com/angular/angular/commit/e02c180)), closes [#12829](https://github.com/angular/angular/issues/12829)
* **compiler-cli:** support ctorParams in function closure ([#12876](https://github.com/angular/angular/issues/12876)) ([6cdc3b5](https://github.com/angular/angular/commit/6cdc3b5))
<a name="2.2.0"></a>
# [2.2.0 upgrade-firebooster](https://github.com/angular/angular/compare/2.2.0-rc.0...2.2.0) (2016-11-14)
### Features (summary of all features from 2.2.0-beta.0 - 2.2.0-rc.0 releases)
* **common:** support narrow forms for month and weekdays in DatePipe ([#12297](https://github.com/angular/angular/issues/12297)) ([f77ab6a](https://github.com/angular/angular/commit/f77ab6a)), closes [#12294](https://github.com/angular/angular/issues/12294)
* **core:** map 'for' attribute to 'htmlFor' property ([#10546](https://github.com/angular/angular/issues/10546)) ([634b3bb](https://github.com/angular/angular/commit/634b3bb)), closes [#7516](https://github.com/angular/angular/issues/7516)
* **core:** add the find method to QueryList ([7c16ef9](https://github.com/angular/angular/commit/7c16ef9))
* **forms:** add hasError and getError to AbstractControlDirective ([#11985](https://github.com/angular/angular/issues/11985)) ([592f40a](https://github.com/angular/angular/commit/592f40a)), closes [#7255](https://github.com/angular/angular/issues/7255)
* **forms:** add ng-pending CSS class during async validation ([#11243](https://github.com/angular/angular/issues/11243)) ([97bc971](https://github.com/angular/angular/commit/97bc971)), closes [#10336](https://github.com/angular/angular/issues/10336)
* **forms:** add emitEvent to AbstractControl methods ([#11949](https://github.com/angular/angular/issues/11949)) ([b9fc090](https://github.com/angular/angular/commit/b9fc090))
* **forms:** make 'parent' a public property of 'AbstractControl' ([#11855](https://github.com/angular/angular/issues/11855)) ([445e592](https://github.com/angular/angular/commit/445e592))
* **forms:** Validator.pattern accepts a RegExp ([#12323](https://github.com/angular/angular/issues/12323)) ([bf60418](https://github.com/angular/angular/commit/bf60418))
* **router:** add a provider making angular1/angular2 integration easier ([#12769](https://github.com/angular/angular/issues/12769)) ([6e35d13](https://github.com/angular/angular/commit/6e35d13))
* **router:** add support for custom url matchers ([7340735](https://github.com/angular/angular/commit/7340735)), closes [#12442](https://github.com/angular/angular/issues/12442) [#12772](https://github.com/angular/angular/issues/12772)
* **router:** export routerLinkActive w/ isActive property ([c9f58cf](https://github.com/angular/angular/commit/c9f58cf))
* **router:** add support for ng1/ng2 migration ([#12160](https://github.com/angular/angular/issues/12160)) ([8b9ab44](https://github.com/angular/angular/commit/8b9ab44))
* **upgrade:** add support for AoT compiled upgrade applications ([d6791ff](https://github.com/angular/angular/commit/d6791ff)), closes [#12239](https://github.com/angular/angular/issues/12239)
* **upgrade:** add support for `require` in UpgradeComponent ([fe1d0e2](https://github.com/angular/angular/commit/fe1d0e2))
* **upgrade:** add/improve support for lifecycle hooks in UpgradeComponent ([469010e](https://github.com/angular/angular/commit/469010e))
### Performance Improvements
* **compiler:** introduce direct rendering ([9c23884](https://github.com/angular/angular/commit/9c23884))
* **core:** dont use `DomAdapter` nor zone for regular events ([648ce59](https://github.com/angular/angular/commit/648ce59))
* **core:** use `array.push` / `array.pop` instead of `splice` if possible ([0fc11a4](https://github.com/angular/angular/commit/0fc11a4))
* **platform-browser:** cache plugin resolution in the EventManager ([73593d4](https://github.com/angular/angular/commit/73593d4)), closes [#12824](https://github.com/angular/angular/issues/12824)
* **platform-browser:** dont use `DomAdapter` any more ([d708a88](https://github.com/angular/angular/commit/d708a88))
### Bug Fixes
* **animations:** allow animations to be destroyed manually ([#12719](https://github.com/angular/angular/issues/12719)) ([fe35bc3](https://github.com/angular/angular/commit/fe35bc3)), closes [#12456](https://github.com/angular/angular/issues/12456)
* **animations:** always normalize style properties and values during compilation ([#12755](https://github.com/angular/angular/issues/12755)) ([a0e9fde](https://github.com/angular/angular/commit/a0e9fde)), closes [#11582](https://github.com/angular/angular/issues/11582) [#12481](https://github.com/angular/angular/issues/12481)
* **animations:** always trigger animations after the change detection check ([#12713](https://github.com/angular/angular/issues/12713)) ([383f23b](https://github.com/angular/angular/commit/383f23b))
* **animations:** ensure animations work with web-workers ([#12656](https://github.com/angular/angular/issues/12656)) ([19e869e](https://github.com/angular/angular/commit/19e869e))
* **animations:** ensure web-animations are caught within the Angular zone ([f80a157](https://github.com/angular/angular/commit/f80a157)), closes [#11881](https://github.com/angular/angular/issues/11881) [#11712](https://github.com/angular/angular/issues/11712) [#12355](https://github.com/angular/angular/issues/12355) [#11881](https://github.com/angular/angular/issues/11881) [#12546](https://github.com/angular/angular/issues/12546) [#12707](https://github.com/angular/angular/issues/12707) [#12774](https://github.com/angular/angular/issues/12774)
* **common:** `NgSwitch` - dont create the default case if another case matches ([#12726](https://github.com/angular/angular/issues/12726)) ([d8f23f4](https://github.com/angular/angular/commit/d8f23f4)), closes [#11297](https://github.com/angular/angular/issues/11297) [#9420](https://github.com/angular/angular/issues/9420)
* **common:** I18nSelectPipe selects other case on default ([4708b24](https://github.com/angular/angular/commit/4708b24))
* **common:** no TZ Offset added by DatePipe for dates without time ([#12380](https://github.com/angular/angular/issues/12380)) ([2aba8b0](https://github.com/angular/angular/commit/2aba8b0))
* **common:** NgClass should throw a descriptive error when CSS class is not a string ([#12662](https://github.com/angular/angular/issues/12662)) ([f3793b5](https://github.com/angular/angular/commit/f3793b5)), closes [#12586](https://github.com/angular/angular/issues/12586)
* **common:** DatePipe should handle empty string ([#12374](https://github.com/angular/angular/issues/12374)) ([3dc6177](https://github.com/angular/angular/commit/3dc6177))
* **compiler:** don't convert undefined to null literals ([#11503](https://github.com/angular/angular/issues/11503)) ([f0cdb42](https://github.com/angular/angular/commit/f0cdb42)), closes [#11493](https://github.com/angular/angular/issues/11493)
* **compiler:** generate safe access strictNullChecks compatible code ([#12800](https://github.com/angular/angular/issues/12800)) ([a965d11](https://github.com/angular/angular/commit/a965d11)), closes [#12795](https://github.com/angular/angular/issues/12795)
* **compiler:** support more than 9 interpolations ([#12710](https://github.com/angular/angular/issues/12710)) ([22c021c](https://github.com/angular/angular/commit/22c021c)), closes [#10253](https://github.com/angular/angular/issues/10253)
* **compiler:** use the other case by default in ICU messages ([55dc0e4](https://github.com/angular/angular/commit/55dc0e4))
* **compiler-cli:** suppress closure compiler suspiciousCode check in codegen ([#12666](https://github.com/angular/angular/issues/12666)) ([7103754](https://github.com/angular/angular/commit/7103754))
* **compiler-cli:** suppress two more closure compiler checks in codegen ([#12698](https://github.com/angular/angular/issues/12698)) ([77cbf7f](https://github.com/angular/angular/commit/77cbf7f))
* **core:** allow to query content of templates that are stamped out at a different place ([f2bbef3](https://github.com/angular/angular/commit/f2bbef3)), closes [#12283](https://github.com/angular/angular/issues/12283) [#12094](https://github.com/angular/angular/issues/12094)
* **core:** apply host attributes to root elements ([#12761](https://github.com/angular/angular/issues/12761)) ([ad3bf6c](https://github.com/angular/angular/commit/ad3bf6c)), closes [#12744](https://github.com/angular/angular/issues/12744)
* **core:** ensure that component views that have no bindings recurse into nested components / view containers. ([051d748](https://github.com/angular/angular/commit/051d748))
* **core:** fix pseudo-selector shimming ([#12754](https://github.com/angular/angular/issues/12754)) ([acbf1d8](https://github.com/angular/angular/commit/acbf1d8)), closes [#12730](https://github.com/angular/angular/issues/12730) [#12354](https://github.com/angular/angular/issues/12354)
* **forms:** check if registerOnValidatorChange exists on validator before trying to invoke it ([#12801](https://github.com/angular/angular/issues/12801)) ([ef88147](https://github.com/angular/angular/commit/ef88147)), closes [#12593](https://github.com/angular/angular/issues/12593)
* **forms:** getRawValue returns any instead of Object ([#12599](https://github.com/angular/angular/issues/12599)) ([09092ac](https://github.com/angular/angular/commit/09092ac))
* **http:** preserve header case when copying headers ([#12697](https://github.com/angular/angular/issues/12697)) ([121e508](https://github.com/angular/angular/commit/121e508))
* **router:** advance a route only after its children have been deactivated ([#12676](https://github.com/angular/angular/issues/12676)) ([9ddf9b3](https://github.com/angular/angular/commit/9ddf9b3)), closes [#11715](https://github.com/angular/angular/issues/11715)
* **router:** avoid router initialization for non root components ([2a4bf9a](https://github.com/angular/angular/commit/2a4bf9a)), closes [#12338](https://github.com/angular/angular/issues/12338) [#12814](https://github.com/angular/angular/issues/12814)
* **router:** check if windows.console exists before using it ([#12348](https://github.com/angular/angular/issues/12348)) ([7886561](https://github.com/angular/angular/commit/7886561))
* **router:** correctly export concatMap operator in es5 ([#12430](https://github.com/angular/angular/issues/12430)) ([e25baa0](https://github.com/angular/angular/commit/e25baa0))
* **router:** do not require the creation of empty-path routes when no url left ([2c11093](https://github.com/angular/angular/commit/2c11093)), closes [#12133](https://github.com/angular/angular/issues/12133)
* **router:** ignore null or undefined query parameters ([#12333](https://github.com/angular/angular/issues/12333)) ([3052fb2](https://github.com/angular/angular/commit/3052fb2))
* **router:** incorrect injector is used when instantiating components loaded lazily ([#12817](https://github.com/angular/angular/issues/12817)) ([52be848](https://github.com/angular/angular/commit/52be848))
* **router:** resolve guard observables on the first emit ([#10412](https://github.com/angular/angular/issues/10412)) ([2e78b76](https://github.com/angular/angular/commit/2e78b76))
* **router:** Route.isActive also compares query params ([#12321](https://github.com/angular/angular/issues/12321)) ([785b7b6](https://github.com/angular/angular/commit/785b7b6))
* **router:** router should not swallow "unhandled" errors ([e5a753e](https://github.com/angular/angular/commit/e5a753e)), closes [#12802](https://github.com/angular/angular/issues/12802)
* **router:** throw an error when encounter undefined route ([#12389](https://github.com/angular/angular/issues/12389)) ([77dc1ab](https://github.com/angular/angular/commit/77dc1ab))
* **platform-browser:** enableDebugTools should create AngularTools by merging into context.ng ([#12003](https://github.com/angular/angular/issues/12003)) ([b2cf379](https://github.com/angular/angular/commit/b2cf379)), closes [#12002](https://github.com/angular/angular/issues/12002)
* **platform-browser:** provide the ability to register global hammer.js events ([768cddb](https://github.com/angular/angular/commit/768cddb)), closes [#12797](https://github.com/angular/angular/issues/12797)
* **tsc-wrapped:** harden collector against invalid asts ([#12793](https://github.com/angular/angular/issues/12793)) ([69f87ca](https://github.com/angular/angular/commit/69f87ca))
<a name="2.2.0-rc.0"></a>
# [2.2.0-rc.0](https://github.com/angular/angular/compare/2.2.0-beta.1...2.2.0-rc.0) (2016-11-02)
### Bug Fixes
* **compiler:** dedupe NgModule declarations, … ([a178bc6](https://github.com/angular/angular/commit/a178bc6))
* **compiler:** dont double bind functions ([e391cac](https://github.com/angular/angular/commit/e391cac))
* **compiler:** Dont throw on empty property bindings ([642c1db](https://github.com/angular/angular/commit/642c1db)), closes [#12583](https://github.com/angular/angular/issues/12583)
* **compiler:** support multiple components in a view container ([6fda972](https://github.com/angular/angular/commit/6fda972))
* **core:** improve error when multiple components match the same element ([e9fd864](https://github.com/angular/angular/commit/e9fd864)), closes [#7067](https://github.com/angular/angular/issues/7067)
* **router:** call data observers when the path changes ([1de04b2](https://github.com/angular/angular/commit/1de04b2))
* **router:** CanDeactivate receives a wrong component ([830a780](https://github.com/angular/angular/commit/830a780)), closes [#12592](https://github.com/angular/angular/issues/12592)
* **router:** rerun resolvers when url changes ([fe47e6b](https://github.com/angular/angular/commit/fe47e6b)), closes [#12603](https://github.com/angular/angular/issues/12603)
* **router:** reset URL to the stable state when a navigation gets canceled ([d509ee0](https://github.com/angular/angular/commit/d509ee0)), closes [#10321](https://github.com/angular/angular/issues/10321)
* **router:** routerLink should not prevent default on non-link elements ([8e221b8](https://github.com/angular/angular/commit/8e221b8))
* **router:** run navigations serially ([091c390](https://github.com/angular/angular/commit/091c390)), closes [#11754](https://github.com/angular/angular/issues/11754)
* **upgrade:** silent bootstrap failures ([fa93fd6](https://github.com/angular/angular/commit/fa93fd6)), closes [#12062](https://github.com/angular/angular/issues/12062)
### Features
* **core:** add the find method to QueryList ([7c16ef9](https://github.com/angular/angular/commit/7c16ef9))
<a name="2.1.2"></a>
# [2.1.2](https://github.com/angular/angular/compare/2.1.1...2.1.2) (2016-10-27)
### Bug Fixes
* **compiler:** don't access view local variables nor pipes in host expressions ([#12396](https://github.com/angular/angular/issues/12396)) ([867494a](https://github.com/angular/angular/commit/867494a)), closes [#12004](https://github.com/angular/angular/issues/12004) [#12071](https://github.com/angular/angular/issues/12071)
* **compiler:** walk third party modules ([#12453](https://github.com/angular/angular/issues/12453)) ([a838aba](https://github.com/angular/angular/commit/a838aba)), closes [#11889](https://github.com/angular/angular/issues/11889) [#12428](https://github.com/angular/angular/issues/12428)
* **compiler:** remove double exports of template_ast ([7742ec0](https://github.com/angular/angular/commit/7742ec0))
* **compiler:** use Maps instead of objects in selector implementation ([d321b0e](https://github.com/angular/angular/commit/d321b0e))
* **compiler-cli:** fix types ([ef15364](https://github.com/angular/angular/commit/ef15364))
* **compiler-cli:** assert that all pipes and directives are declared by a module ([7221632](https://github.com/angular/angular/commit/7221632))
* **http:** overwrite already set xsrf header ([b4265e0](https://github.com/angular/angular/commit/b4265e0))
* **router:** add a test to make sure canDeactivate guards are called for aux routes ([fc60fa7](https://github.com/angular/angular/commit/fc60fa7)), closes [#11345](https://github.com/angular/angular/issues/11345)
* **router:** canDeactivate guards are not triggered for componentless routes ([b741853](https://github.com/angular/angular/commit/b741853)), closes [#12375](https://github.com/angular/angular/issues/12375)
* **router:** change router not to deactivate aux routes when navigating from a componentless routes ([52a853e](https://github.com/angular/angular/commit/52a853e))
* **router:** disallow component routes with named outlets ([8f2fa0f](https://github.com/angular/angular/commit/8f2fa0f)), closes [#11208](https://github.com/angular/angular/issues/11208) [#11082](https://github.com/angular/angular/issues/11082)
* **router:** preserve resolve data ([6ccbfd4](https://github.com/angular/angular/commit/6ccbfd4)), closes [#12306](https://github.com/angular/angular/issues/12306)
<a name="2.2.0-beta.1"></a>
# [2.2.0-beta.1](https://github.com/angular/angular/compare/2.2.0-beta.0...2.2.0-beta.1) (2016-10-27)
### Code Refactoring
* **upgrade:** re-export the new static upgrade APIs on new entry ([a26dd28](https://github.com/angular/angular/commit/a26dd28))
### Features
* **router:** export routerLinkActive w/ isActive property ([c9f58cf](https://github.com/angular/angular/commit/c9f58cf))
### BREAKING CHANGES (only for beta version users)
* upgrade: Four newly added APIs in 2.2.0-beta:
downgradeComponent, downgradeInjectable, UpgradeComponent, and UpgradeModule are no longer exported by @angular/upgrade.
Import these from @angular/upgrade/static instead.
Note: The 2.2.0-beta.1 release also contains all the changes present in the 2.1.2 release.
# [2.1.1](https://github.com/angular/angular/compare/2.1.0...2.1.1) (2016-10-20)
### Bug Fixes
* **compiler:** generate aot code for animation trigger output events ([#12291](https://github.com/angular/angular/issues/12291)) ([6e5f8b5](https://github.com/angular/angular/commit/6e5f8b5)), closes [#11707](https://github.com/angular/angular/issues/11707)
* **compiler:** don't redeclare a var in the same scope ([#12386](https://github.com/angular/angular/issues/12386)) ([cca4a5c](https://github.com/angular/angular/commit/cca4a5c))
* **core:** fix decorator default values ([bd1dcb5](https://github.com/angular/angular/commit/bd1dcb5))
* **core:** fix property decorators ([3993279](https://github.com/angular/angular/commit/3993279)), closes [#12224](https://github.com/angular/angular/issues/12224)
* **http:** make normalizeMethodName optimizer-compatible. ([#12370](https://github.com/angular/angular/issues/12370)) ([8409b65](https://github.com/angular/angular/commit/8409b65))
* **router:** correctly export filter operator in es5 ([#12286](https://github.com/angular/angular/issues/12286)) ([27d7677](https://github.com/angular/angular/commit/27d7677))
* **router:** do not update primary route if only secondary outlet is given ([#11797](https://github.com/angular/angular/issues/11797)) ([da5fc69](https://github.com/angular/angular/commit/da5fc69))
* **router:** fix lazy loading triggered by redirects from wildcard routes ([5ae6915](https://github.com/angular/angular/commit/5ae6915)), closes [#12183](https://github.com/angular/angular/issues/12183)
* **router:** module loader should start compiling modules when stubbedModules are set ([#11742](https://github.com/angular/angular/issues/11742)) ([b44b6ef](https://github.com/angular/angular/commit/b44b6ef))
### Performance Improvements
* **common:** optimize NgSwitch default case ([fdf4309](https://github.com/angular/angular/commit/fdf4309))
<a name="2.2.0-beta.0"></a>
# [2.2.0-beta.0](https://github.com/angular/angular/compare/2.1.0...2.2.0-beta.0) (2016-10-20)
### Features
* **common:** support narrow forms for month and weekdays in DatePipe ([#12297](https://github.com/angular/angular/issues/12297)) ([f77ab6a](https://github.com/angular/angular/commit/f77ab6a)), closes [#12294](https://github.com/angular/angular/issues/12294)
* **forms:** add hasError and getError to AbstractControlDirective ([#11985](https://github.com/angular/angular/issues/11985)) ([592f40a](https://github.com/angular/angular/commit/592f40a)), closes [#7255](https://github.com/angular/angular/issues/7255)
* **forms:** add ng-pending CSS class during async validation ([#11243](https://github.com/angular/angular/issues/11243)) ([97bc971](https://github.com/angular/angular/commit/97bc971)), closes [#10336](https://github.com/angular/angular/issues/10336)
* **forms:** Added emitEvent to AbstractControl methods ([#11949](https://github.com/angular/angular/issues/11949)) ([b9fc090](https://github.com/angular/angular/commit/b9fc090))
* **forms:** make 'parent' a public property of 'AbstractControl' ([#11855](https://github.com/angular/angular/issues/11855)) ([445e592](https://github.com/angular/angular/commit/445e592))
* **forms:** Validator.pattern accepts a RegExp ([#12323](https://github.com/angular/angular/issues/12323)) ([bf60418](https://github.com/angular/angular/commit/bf60418))
* **upgrade:** add support for AoT compiled upgrade applications ([d6791ff](https://github.com/angular/angular/commit/d6791ff)), closes [#12239](https://github.com/angular/angular/issues/12239)
* **router:** add support for ng1/ng2 migration ([#12160](https://github.com/angular/angular/issues/12160)) ([8b9ab44](https://github.com/angular/angular/commit/8b9ab44))
Note: The 2.2.0-beta.0 release also contains all the changes present in the 2.1.1 release.
<a name="2.1.0"></a>
# [2.1.0 incremental-metamorphosis](https://github.com/angular/angular/compare/2.1.0-rc.0...2.1.0) (2016-10-12)
### Bug Fixes
* **compiler:** allow whitespace as `<ng-content>` content ([#12225](https://github.com/angular/angular/issues/12225)) ([df1718d](https://github.com/angular/angular/commit/df1718d))
* **compiler:** interpolation expressions report the correct offset ([#12125](https://github.com/angular/angular/issues/12125)) ([d641c36](https://github.com/angular/angular/commit/d641c36))
* **compiler:** properly shim `:host:before` and `:host(:before)` ([#12171](https://github.com/angular/angular/issues/12171)) ([aa92512](https://github.com/angular/angular/commit/aa92512)), closes [#12165](https://github.com/angular/angular/issues/12165)
* **compiler:** validate `@HostBinding` name ([#12139](https://github.com/angular/angular/issues/12139)) ([13ecc14](https://github.com/angular/angular/commit/13ecc14))
* **compiler-cli:** don't clone static symbols when simplifying annotation metadata ([#12158](https://github.com/angular/angular/issues/12158)) ([8c477b2](https://github.com/angular/angular/commit/8c477b2))
* **compiler-cli:** remove peerDependency on [@angular](https://github.com/angular)/platform-server ([#12122](https://github.com/angular/angular/issues/12122)) ([71b7654](https://github.com/angular/angular/commit/71b7654))
* **compiler-cli:** remove unused parse5 dependency from package.json ([eaaec69](https://github.com/angular/angular/commit/eaaec69))
* **forms:** allow optional fields with pattern and minlength validators ([#12147](https://github.com/angular/angular/issues/12147)) ([d22eeb7](https://github.com/angular/angular/commit/d22eeb7))
* **forms:** properly validate blank strings with minlength ([#12091](https://github.com/angular/angular/issues/12091)) ([f50c1da](https://github.com/angular/angular/commit/f50c1da))
* **http:** fix Headers initialization from Headers and Object ([#12106](https://github.com/angular/angular/issues/12106)) ([f4566f8](https://github.com/angular/angular/commit/f4566f8))
* **http:** Headers.append should append to the list ([a67c067](https://github.com/angular/angular/commit/a67c067))
* **platform-browser-dynamic:** mark platformBrowserDynamic as stable API ([#12154](https://github.com/angular/angular/issues/12154)) ([bcef5ef](https://github.com/angular/angular/commit/bcef5ef))
* **router:** improve error message ([#12102](https://github.com/angular/angular/issues/12102)) ([e06303a](https://github.com/angular/angular/commit/e06303a))
* **router:** parent resolve should complete before merging resolved data ([1681e4f](https://github.com/angular/angular/commit/1681e4f)), closes [#12032](https://github.com/angular/angular/issues/12032)
* **router:** wildcards routes should support lazy loading ([40b92dd](https://github.com/angular/angular/commit/40b92dd)), closes [#12024](https://github.com/angular/angular/issues/12024)
* **upgrade:** allow compilerOptions in bootstrap ([#10575](https://github.com/angular/angular/issues/10575)) ([5effc33](https://github.com/angular/angular/commit/5effc33))
<a name="2.1.0-rc.0"></a>
# [2.1.0-rc.0](https://github.com/angular/angular/compare/2.1.0-beta.0...2.1.0-rc.0) (2016-10-05)
### Features
* **animations:** provide aliases for `:enter` and `:leave` transitions ([#11991](https://github.com/angular/angular/issues/11991)) ([e884f48](https://github.com/angular/angular/commit/e884f48))
Note: 2.1.0-rc.0 release also contains all the changes present in the 2.0.2 release.
<a name="2.1.0-beta.0"></a>
# [2.1.0-beta.0](https://github.com/angular/angular/compare/2.0.0...2.1.0-beta.0) (2016-09-23)
### Features
* **router:** add router preloader to optimistically preload routes ([5a84982](https://github.com/angular/angular/commit/5a84982))
### Bug Fixes
* **router:** update the router not to reset router state when updating root component ([#11799](https://github.com/angular/angular/issues/11799)) ([31dce72](https://github.com/angular/angular/commit/31dce72))
Note: 2.1.0-beta.0 release also contains all the changes present in the 2.0.1 release.
<a name="2.0.2"></a> <a name="2.0.2"></a>
## [2.0.2](https://github.com/angular/angular/compare/2.0.1...2.0.2) (2016-10-05) ## [2.0.2](https://github.com/angular/angular/compare/2.0.1...2.0.2) (2016-10-05)
@ -561,18 +7,18 @@ Note: 2.1.0-beta.0 release also contains all the changes present in the 2.0.1 re
* **common:** correctly removes styles on IE ([#11953](https://github.com/angular/angular/pull/11953)), closes [#7916](https://github.com/angular/angular/issues/7916) * **common:** correctly removes styles on IE ([#11953](https://github.com/angular/angular/pull/11953)), closes [#7916](https://github.com/angular/angular/issues/7916)
* **compiler:** do not embed templateUrl in view factories in non-debug mode. ([#11818](https://github.com/angular/angular/issues/11818)) ([51e1994](https://github.com/angular/angular/commit/51e1994)), closes [#11117](https://github.com/angular/angular/issues/11117) * **compiler:** do not embed templateUrl in view factories in non-debug mode. ([#11818](https://github.com/angular/angular/issues/11818)) ([51e1994](https://github.com/angular/angular/commit/51e1994)), closes [#11117](https://github.com/angular/angular/issues/11117)
* **compiler:** move detection of unsafe properties for binding to ElementSchemaRegistry ([#11378](https://github.com/angular/angular/issues/11378)) ([5911c3b](https://github.com/angular/angular/commit/5911c3b)) * **compiler:** move detection of unsafe properties for binding to ElementSchemaRegistry ([#11378](https://github.com/angular/angular/issues/11378)) ([5911c3b](https://github.com/angular/angular/commit/5911c3b))
* **compiler:** fix `:host(tag)` and `:host-context(tag)` ([a6bb84e0](https://github.com/angular/angular/commit/a6bb84e02b7579f8d957ef6ba5b10d83482ed756)), closes [#11972](https://github.com/angular/angular/issues/11972)
* **compiler:** fix attribute selectors in :host and :host-context ([#12056](https://github.com/angular/angular/issues/12056)) ([6f7ed32](https://github.com/angular/angular/commit/6f7ed32)), closes [#11917](https://github.com/angular/angular/issues/11917)
* **compiler:** support `@page` and `@document` CSS rules ([#11878](https://github.com/angular/angular/issues/11878)) ([c99ef49](https://github.com/angular/angular/commit/c99ef49)), closes [#11860](https://github.com/angular/angular/issues/11860)
* **compiler:** support `[attr="value with space"]` ([bd012ef](https://github.com/angular/angular/commit/bd012ef)), closes [#6249](https://github.com/angular/angular/issues/6249)
* **compiler:** support quoted attribute values ([7395400](https://github.com/angular/angular/commit/7395400)), closes [#6085](https://github.com/angular/angular/issues/6085)
* **compiler:** fix `<x>` ctype names ([7578d85](https://github.com/angular/angular/commit/7578d85)), closes [#12000](https://github.com/angular/angular/issues/12000)
* **compiler-cli:** allow ReflectorHost passed as argument to CodeGenerator#create ([#11951](https://github.com/angular/angular/issues/11951)) ([826c98e](https://github.com/angular/angular/commit/826c98e))
* **forms:** properly validate empty strings with patterns ([#11450](https://github.com/angular/angular/issues/11450)) ([e00de0c](https://github.com/angular/angular/commit/e00de0c)) * **forms:** properly validate empty strings with patterns ([#11450](https://github.com/angular/angular/issues/11450)) ([e00de0c](https://github.com/angular/angular/commit/e00de0c))
* **http:** preserve case of the first init, `set()` or `append()` ([#12023](https://github.com/angular/angular/issues/12023)) ([adb17fe](https://github.com/angular/angular/commit/adb17fe)), closes [#11624](https://github.com/angular/angular/issues/11624) * **http:** preserve case of the first init, `set()` or `append()` ([#12023](https://github.com/angular/angular/issues/12023)) ([adb17fe](https://github.com/angular/angular/commit/adb17fe)), closes [#11624](https://github.com/angular/angular/issues/11624)
* **http:** remove url params if provided value is null or undefined ([#11990](https://github.com/angular/angular/issues/11990)) ([9cc0a4e](https://github.com/angular/angular/commit/9cc0a4e)) * **compiler-cli:** allow ReflectorHost passed as argument to CodeGenerator#create ([#11951](https://github.com/angular/angular/issues/11951)) ([826c98e](https://github.com/angular/angular/commit/826c98e))
* **router:** do not reset the router state when updating the component ([#11867](https://github.com/angular/angular/issues/11867)) ([cf750e1](https://github.com/angular/angular/commit/cf750e1)) * **router:** do not reset the router state when updating the component ([#11867](https://github.com/angular/angular/issues/11867)) ([cf750e1](https://github.com/angular/angular/commit/cf750e1))
* **compiler:** fix `:host(tag)` and `:host-context(tag)` ([a6bb84e0](https://github.com/angular/angular/commit/a6bb84e02b7579f8d957ef6ba5b10d83482ed756)), closes [#11972](https://github.com/angular/angular/issues/11972)
* **compiler:** fix attribute selectors in :host and :host-context ([#12056](https://github.com/angular/angular/issues/12056)) ([6f7ed32](https://github.com/angular/angular/commit/6f7ed32)), closes [#11917](https://github.com/angular/angular/issues/11917)
* **compiler:** support `[@page](https://github.com/page)` and `[@document](https://github.com/document)` CSS rules ([#11878](https://github.com/angular/angular/issues/11878)) ([c99ef49](https://github.com/angular/angular/commit/c99ef49)), closes [#11860](https://github.com/angular/angular/issues/11860)
* **compiler:** support `[attr="value with space"]` ([bd012ef](https://github.com/angular/angular/commit/bd012ef)), closes [#6249](https://github.com/angular/angular/issues/6249)
* **compiler:** support quoted attribute values ([7395400](https://github.com/angular/angular/commit/7395400)), closes [#6085](https://github.com/angular/angular/issues/6085)
* **upgrade:** bind optional properties when upgrading from ng1 ([#11411](https://github.com/angular/angular/issues/11411)) ([0851238](https://github.com/angular/angular/commit/0851238)), closes [#10181](https://github.com/angular/angular/issues/10181) * **upgrade:** bind optional properties when upgrading from ng1 ([#11411](https://github.com/angular/angular/issues/11411)) ([0851238](https://github.com/angular/angular/commit/0851238)), closes [#10181](https://github.com/angular/angular/issues/10181)
* **http:** change a behavior when a param value is null or undefined ([#11990](https://github.com/angular/angular/issues/11990)) ([9cc0a4e](https://github.com/angular/angular/commit/9cc0a4e))
* **compiler:** fix `<x>` ctype names ([7578d85](https://github.com/angular/angular/commit/7578d85)), closes [#12000](https://github.com/angular/angular/issues/12000)
<a name="2.0.1"></a> <a name="2.0.1"></a>
@ -595,7 +41,7 @@ Note: 2.1.0-beta.0 release also contains all the changes present in the 2.0.1 re
<a name="2.0.0"></a> <a name="2.0.0"></a>
# [2.0.0 proprioception-reinforcement](https://github.com/angular/angular/compare/2.0.0-rc.7...2.0.0) (2016-09-14) # [2.0.0](https://github.com/angular/angular/compare/2.0.0-rc.7...2.0.0) (2016-09-14)
### Bug Fixes ### Bug Fixes
@ -1116,7 +562,7 @@ prefix using `animate-` must now be preixed using `bind-animate-`.
* core: * core:
- `ApplicationRef.dispose` is deprecated. Destroy the module that was - `ApplicationRef.dispose` is deprecated. Destroy the module that was
created during bootstrap instead by calling `NgModuleRef.destroy`. created during bootstrap instead by calling `NgModuleRef.destroy`.
- `ApplicationRef.registerDisposeListener` is deprecated. - `AplicationRef.registerDisposeListener` is deprecated.
Use the `ngOnDestroy` lifecycle hook for providers or Use the `ngOnDestroy` lifecycle hook for providers or
`NgModuleRef.onDestroy` instead. `NgModuleRef.onDestroy` instead.
- `disposePlatform` is deprecated. Use `destroyPlatform` instead. - `disposePlatform` is deprecated. Use `destroyPlatform` instead.

View File

@ -1,6 +1,6 @@
# Contributing to Angular # Contributing to Angular 2
We would love for you to contribute to Angular and help make it even better than it is We would love for you to contribute to Angular 2 and help make it even better than it is
today! As a contributor, here are the guidelines we would like you to follow: today! As a contributor, here are the guidelines we would like you to follow:
- [Code of Conduct](#coc) - [Code of Conduct](#coc)
@ -17,26 +17,17 @@ Help us keep Angular open and inclusive. Please read and follow our [Code of Con
## <a name="question"></a> Got a Question or Problem? ## <a name="question"></a> Got a Question or Problem?
Please, do not open issues for the general support questions as we want to keep GitHub issues for bug reports and feature requests. You've got much better chances of getting your question answered on [StackOverflow](https://stackoverflow.com/questions/tagged/angular) where the questions should be tagged with tag `angular`. If you have questions about how to *use* Angular, please direct them to the [Google Group][angular-group]
discussion list or [StackOverflow][stackoverflow]. Please note that the Angular team's capacity to answer usage questions is limited. We are also available on [Gitter][gitter].
StackOverflow is a much better place to ask questions since: ## <a name="issue"></a> Found an Issue?
- there are thousands of people willing to help on StackOverflow
- questions and answers stay available for public viewing so your question / answer might help someone else
- StackOverflow's voting system assures that the best answers are prominently visible.
To save your and our time we will be systematically closing all the issues that are requests for general support and redirecting people to StackOverflow.
If you would like to chat about the question in real-time, you can reach out via [our gitter channel][gitter].
## <a name="issue"></a> Found a Bug?
If you find a bug in the source code, you can help us by If you find a bug in the source code, you can help us by
[submitting an issue](#submit-issue) to our [GitHub Repository][github]. Even better, you can [submitting an issue](#submit-issue) to our [GitHub Repository][github]. Even better, you can
[submit a Pull Request](#submit-pr) with a fix. [submit a Pull Request](#submit-pr) with a fix.
## <a name="feature"></a> Missing a Feature? ## <a name="feature"></a> Want a Feature?
You can *request* a new feature by [submitting an issue](#submit-issue) to our GitHub You can *request* a new feature by [submitting an issue](#submit-issue) to our [GitHub
Repository. If you would like to *implement* a new feature, please submit an issue with Repository][github]. If you would like to *implement* a new feature, please submit an issue with
a proposal for your work first, to be sure that we can use it. a proposal for your work first, to be sure that we can use it.
Please consider what kind of change it is: Please consider what kind of change it is:
@ -48,22 +39,24 @@ and help you to craft the change so that it is successfully accepted into the pr
## <a name="submit"></a> Submission Guidelines ## <a name="submit"></a> Submission Guidelines
### <a name="submit-issue"></a> Submitting an Issue ### <a name="submit-issue"></a> Submitting an Issue
Before you submit an issue, search the archive, maybe your question was already answered.
Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists and the discussion might inform you of workarounds readily available. If your issue appears to be a bug, and hasn't been reported, open a new issue.
Help us to maximize the effort we can spend fixing issues and adding new
features, by not reporting duplicate issues. Providing the following information will increase the
chances of your issue being dealt with quickly:
We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs we will systematically ask you to provide a minimal reproduction scenario using http://plnkr.co. Having a live, reproducible scenario gives us wealth of important information without going back & forth to you with additional questions like: * **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps
* **Angular Version** - what version of Angular is affected (e.g. 2.0.0-alpha.53)
* **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you
* **Browsers and Operating System** - is this a problem with all browsers?
* **Reproduce the Error** - provide a live example (using [Plunker][plunker],
[JSFiddle][jsfiddle] or [Runnable][runnable]) or a unambiguous set of steps
* **Related Issues** - has a similar issue been reported before?
* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be
causing the problem (line of code or commit)
- version of Angular used You can file new issues by providing the above information [here](https://github.com/angular/angular/issues/new).
- 3rd-party libraries and their versions
- and most importantly - a use-case that fails
A minimal reproduce scenario using http://plnkr.co/ allows us to quickly confirm a bug (or point out coding problem) as well as confirm that we are fixing the right problem. If plunker is not a suitable way to demonstrate the problem (for example for issues related to our npm packaging), please create a standalone git repository demonstrating the problem.
We will be insisting on a minimal reproduce scenario in order to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal plunk. We understand that sometimes it might be hard to extract essentials bits of code from a larger code-base but we really need to isolate the problem before we can fix it.
Unfortunately we are not able to investigate / fix bugs without a minimal reproduction, so if we don't hear back from you we are going to close an issue that don't have enough info to be reproduced.
You can file new issues by filling out our [new issue form](https://github.com/angular/angular/issues/new).
### <a name="submit-pr"></a> Submitting a Pull Request (PR) ### <a name="submit-pr"></a> Submitting a Pull Request (PR)
@ -101,7 +94,7 @@ Before you submit your Pull Request (PR) consider the following guidelines:
* In GitHub, send a pull request to `angular:master`. * In GitHub, send a pull request to `angular:master`.
* If we suggest changes then: * If we suggest changes then:
* Make the required updates. * Make the required updates.
* Re-run the Angular test suites to ensure tests are still passing. * Re-run the Angular 2 test suites to ensure tests are still passing.
* Rebase your branch and force push to your GitHub repository (this will update your Pull Request): * Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
```shell ```shell
@ -244,7 +237,7 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise
[github]: https://github.com/angular/angular [github]: https://github.com/angular/angular
[gitter]: https://gitter.im/angular/angular [gitter]: https://gitter.im/angular/angular
[individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html [individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html
[js-style-guide]: https://google.github.io/styleguide/jsguide.html [js-style-guide]: https://google.github.io/styleguide/javascriptguide.xml
[jsfiddle]: http://jsfiddle.net [jsfiddle]: http://jsfiddle.net
[plunker]: http://plnkr.co/edit [plunker]: http://plnkr.co/edit
[runnable]: http://runnable.com [runnable]: http://runnable.com

View File

@ -1,6 +1,6 @@
# Building and Testing Angular # Building and Testing Angular 2 for JS
This document describes how to set up your development environment to build and test Angular. This document describes how to set up your development environment to build and test Angular 2 JS version.
It also explains the basic mechanics of using `git`, `node`, and `npm`. It also explains the basic mechanics of using `git`, `node`, and `npm`.
* [Prerequisite Software](#prerequisite-software) * [Prerequisite Software](#prerequisite-software)
@ -74,15 +74,6 @@ use in these instructions.
*Option 2*: defining a bash alias like `alias nbin='PATH=$(npm bin):$PATH'` as detailed in this *Option 2*: defining a bash alias like `alias nbin='PATH=$(npm bin):$PATH'` as detailed in this
[Stackoverflow answer](http://stackoverflow.com/questions/9679932/how-to-use-package-installed-locally-in-node-modules/15157360#15157360) and used like this: e.g., `nbin gulp build`. [Stackoverflow answer](http://stackoverflow.com/questions/9679932/how-to-use-package-installed-locally-in-node-modules/15157360#15157360) and used like this: e.g., `nbin gulp build`.
## Installing Bower Modules
Now run `bower` to install additional dependencies:
```shell
# Install other Angular project dependencies (bower.json)
bower install
```
## Windows only ## Windows only
In order to create the right symlinks, run **as administrator**: In order to create the right symlinks, run **as administrator**:
@ -133,10 +124,9 @@ If you happen to modify the public API of Angular, API golden files must be upda
$ gulp public-api:update $ gulp public-api:update
``` ```
Note: The command `gulp public-api:enforce` fails when the API doesn't match the golden files. Make sure to rebuild Note: The command `./test.sh tools` fails when the API doesn't match the golden files.
the project before trying to verify after an API change.
## <a name="clang-format"></a> Formatting your source code ## Formatting your source code
Angular uses [clang-format](http://clang.llvm.org/docs/ClangFormat.html) to format the source code. If the source code Angular uses [clang-format](http://clang.llvm.org/docs/ClangFormat.html) to format the source code. If the source code
is not properly formatted, the CI will fail and the PR can not be merged. is not properly formatted, the CI will fail and the PR can not be merged.
@ -147,32 +137,4 @@ You can automatically format your code by running:
$ gulp format $ 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.
Rather than requiring them to pull your Pull Request and build Angular locally, you can
publish the `*-builds` snapshots just like our Travis build does.
First time, you need to create the github repositories:
``` shell
$ export TOKEN=[get one from https://github.com/settings/tokens]
$ CREATE_REPOS=1 ./scripts/publish/publish-build-artifacts.sh [github username]
```
For subsequent snapshots, just run
``` shell
$ ./scripts/publish/publish-build-artifacts.sh [github username]
```
The script will publish the build snapshot to a branch with the same name as your current branch,
and create it if it doesn't exist.

View File

@ -1,62 +0,0 @@
# Saved Responses for Angular's Issue Tracker
The following are canned responses that the Angular team should use to close issues on our issue tracker that fall into the listed resolution categories.
Since GitHub currently doesn't allow us to have a repository-wide or organization-wide list of [saved replies](https://help.github.com/articles/working-with-saved-replies/), these replies need to be maintained by individual team members. Since the responses can be modified in the future, all responses are versioned to simplify the process of keeping the responses up to date.
## Angular: Already Fixed (v1)
```
Thanks for reporting this issue. Luckily it has already been fixed in one of the recent releases. Please update to the most recent version to resolve the problem.
If after upgrade the problem still exists in your application please open a new issue and provide a plunker reproducing the problem and describing the difference between the expected and current behavior. You can use this plunker template: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5?p=catalogue
```
## Angular: Don't Understand (v1)
```
I'm sorry but we don't understand the problem you are reporting.
If the problem still exists please open a new issue and provide a plunker reproducing the problem and describing the difference between the expected and current behavior. You can use this plunker template: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5?p=catalogue
```
## Angular: Duplicate (v1)
```
Thanks for reporting this issue. However this issue is a duplicate of an existing issue #<ISSUE_NUMBER>. Please subscribe to that issue for future updates.
```
## Angular: Insufficient Information Provided (v1)
```
Thanks for reporting this issue. However, you didn't provide sufficient information for us to understand and reproduce the problem. Please check out [our submission guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-submitting-an-issue) to understand why we can't act on issues that are lacking important information.
If the problem still persists, please file a new issue and ensure you provide all of the required information when filling out the issue template.
```
## Angular: Issue Outside of Angular (v1)
```
I'm sorry but this issue is not caused by Angular. Please contact the author(s) of project <PROJECT NAME> or file issue on their issue tracker.
```
## Angular: Non-reproducible (v1)
```
I'm sorry but we can't reproduce the problem following the instructions you provided.
If the problem still exists please open a new issue following [our submission guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-submitting-an-issue).
```
## Angular: Obsolete (v1)
```
Thanks for reporting this issue. This issue is now obsolete due to changes in the recent releases. Please update to the most recent Angular version.
If the problem still persists, please file a new issue and ensure you provide the version of Angular affected and include the steps to reproduce the problem when filling out the issue template.
```
## Angular: Support Request (v1)
```
Hello, we reviewed this issue and determined that it doesn't fall into the bug report or feature request category. This issue tracker is not suitable for support requests, please repost your issue on [StackOverflow](http://stackoverflow.com/) using tag `angular`.
If you are wondering why we don't resolve support issues via the issue tracker, please [check out this explanation](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-got-a-question-or-problem).
```

View File

@ -1,7 +1,7 @@
# Triage Process and Github Labels for Angular 2 # Triage Process and Github Labels for Angular 2
This document describes how the Angular team uses labels and milestones This document describes how the Angular team uses labels and milestones
to triage issues on github. The basic idea of the process is that to triage issues on github. The basic idea of the new process is that
caretaker only assigns a component and type (bug, feature) label. The caretaker only assigns a component and type (bug, feature) label. The
owner of the component than is in full control of how the issues should owner of the component than is in full control of how the issues should
be triaged further. be triaged further.
@ -24,9 +24,8 @@ with it.
* `comp: forms`: `@kara` * `comp: forms`: `@kara`
* `comp: http`: `@jeffbcross` * `comp: http`: `@jeffbcross`
* `comp: i18n`: `@vicb` * `comp: i18n`: `@vicb`
* `comp: language service`: `@chuckjaz`
* `comp: metadata-extractor`: `@chuckjaz` * `comp: metadata-extractor`: `@chuckjaz`
* `comp: router`: `@vicb` * `comp: router`: `@vsavkin`
* `comp: testing`: `@juliemr` * `comp: testing`: `@juliemr`
* `comp: upgrade`: `@mhevery` * `comp: upgrade`: `@mhevery`
* `comp: web-worker`: `@vicb` * `comp: web-worker`: `@vicb`
@ -36,7 +35,7 @@ There are few components which are cross-cutting. They don't have
a clear location in the source tree. We will treat them as a component a clear location in the source tree. We will treat them as a component
even thought no specific source tree is associated with them. even thought no specific source tree is associated with them.
* `comp: docs`: `@naomiblack` * `comp: documentation`: `@naomiblack`
* `comp: packaging`: `@IgorMinar` * `comp: packaging`: `@IgorMinar`
* `comp: performance`: `@tbosch` * `comp: performance`: `@tbosch`
* `comp: security`: `@IgorMinar` * `comp: security`: `@IgorMinar`
@ -54,11 +53,11 @@ What kind of problem is this?
## Caretaker Triage Process ## Caretaker Triage Process
It is the caretaker's responsibility to assign `comp: *` to each new It is the caretaker's responsibility to assign `comp: *` and `type: *`
issue as they come in. The reason why we limit the responsibility of the to each new issue as they come in. The reason why we limit the
caretaker to this one label is that it is likely that without domain responsibility of the caretaker to these two labels is that it is
knowledge the caretaker could mislabel issues or lack knowledge of unlikely that without domain knowledge the caretaker could add any
duplicate issues. additional labels of value.
## Component's owner Triage Process ## Component's owner Triage Process
@ -69,37 +68,11 @@ process for their component.
It will be up to the component owner to determine the order in which the It will be up to the component owner to determine the order in which the
issues within the component will be resolved. issues within the component will be resolved.
Several owners have adopted the issue categorization based on
[user pain](http://www.lostgarden.com/2008/05/improving-bug-triage-with-user-pain.html)
used by Angular 1. In this system every issue is assigned frequency and
severity based on which the total user pain score is calculated.
Following is the definition of various frequency and severity levels:
1. `freq(score): *` How often does this issue come up? How many developers does this affect?
* low (1) - obscure issue affecting a handful of developers
* moderate (2) - impacts auxiliary usage patterns, only small number of applications are affected
* high (3) - impacts primary usage patterns, affecting most Angular apps
* critical (4) - impacts all Angular apps
1. `severity(score): *` - How bad is the issue?
* inconvenience (1) - causes ugly/boilerplate code in apps
* confusing (2) - unexpected or inconsistent behavior; hard-to-debug
* broken expected use (3) - it's hard or impossible for a developer using Angular to accomplish something that Angular should be able to do
* memory leak (4)
* regression (5) - functionality that used to work no longer works in a new release due to an unintentional change
* security issue (6)
These criteria are then used to calculate a "user pain" score as follows:
`pain = severity × frequency`
### Assigning Issues to Milestones ### Assigning Issues to Milestones
Any issue that is being worked on must have: Any issue that is being worked on must have:
* An `Assignee`: The person doing the work. * An `assignee`: The person doing the work.
* A `Milestone`: When we expect to complete this work. * A `Milestone`: When we expect to complete this work.
We aim to only have at most three milestones open at a time: We aim to only have at most three milestones open at a time:

View File

@ -36,7 +36,7 @@ var CIconfiguration = {
'iOS7': {unitTest: {target: 'BS', required: true}, e2e: {target: null, required: true}}, 'iOS7': {unitTest: {target: 'BS', required: true}, e2e: {target: null, required: true}},
'iOS8': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}, 'iOS8': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'iOS9': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}, 'iOS9': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'iOS10': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}}, 'iOS10': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'WindowsPhone': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}} 'WindowsPhone': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}
}; };
@ -44,10 +44,10 @@ var customLaunchers = {
'DartiumWithWebPlatform': 'DartiumWithWebPlatform':
{base: 'Dartium', flags: ['--enable-experimental-web-platform-features']}, {base: 'Dartium', flags: ['--enable-experimental-web-platform-features']},
'ChromeNoSandbox': {base: 'Chrome', flags: ['--no-sandbox']}, 'ChromeNoSandbox': {base: 'Chrome', flags: ['--no-sandbox']},
'SL_CHROME': {base: 'SauceLabs', browserName: 'chrome', version: '54'}, 'SL_CHROME': {base: 'SauceLabs', browserName: 'chrome', version: '52'},
'SL_CHROMEBETA': {base: 'SauceLabs', browserName: 'chrome', version: 'beta'}, 'SL_CHROMEBETA': {base: 'SauceLabs', browserName: 'chrome', version: 'beta'},
'SL_CHROMEDEV': {base: 'SauceLabs', browserName: 'chrome', version: 'dev'}, 'SL_CHROMEDEV': {base: 'SauceLabs', browserName: 'chrome', version: 'dev'},
'SL_FIREFOX': {base: 'SauceLabs', browserName: 'firefox', version: '50'}, 'SL_FIREFOX': {base: 'SauceLabs', browserName: 'firefox', version: '46'},
'SL_FIREFOXBETA': {base: 'SauceLabs', browserName: 'firefox', version: 'beta'}, 'SL_FIREFOXBETA': {base: 'SauceLabs', browserName: 'firefox', version: 'beta'},
'SL_FIREFOXDEV': {base: 'SauceLabs', browserName: 'firefox', version: 'dev'}, 'SL_FIREFOXDEV': {base: 'SauceLabs', browserName: 'firefox', version: 'dev'},
'SL_SAFARI7': {base: 'SauceLabs', browserName: 'safari', platform: 'OS X 10.9', version: '7.0'}, 'SL_SAFARI7': {base: 'SauceLabs', browserName: 'safari', platform: 'OS X 10.9', version: '7.0'},
@ -96,7 +96,7 @@ var customLaunchers = {
'BS_IE10': { 'BS_IE10': {
base: 'BrowserStack', base: 'BrowserStack',
browser: 'ie', browser: 'ie',
browser_version: '10.1', browser_version: '10.0',
os: 'Windows', os: 'Windows',
os_version: '8' os_version: '8'
}, },

View File

@ -14,17 +14,12 @@ PACKAGES=(core
platform-webworker platform-webworker
platform-webworker-dynamic platform-webworker-dynamic
http http
upgrade
router router
upgrade
compiler-cli compiler-cli
language-service
benchpress) benchpress)
BUILD_ALL=true BUILD_ALL=true
BUNDLE=true BUNDLE=true
VERSION_PREFIX=$(node -p "require('./package.json').version")
VERSION_SUFFIX="-$(git log --oneline -1 | awk '{print $1}')"
ROUTER_VERSION_PREFIX=$(node -p "require('./package.json').version.replace(/^2/, '3')")
REMOVE_BENCHPRESS=false
for ARG in "$@"; do for ARG in "$@"; do
case "$ARG" in case "$ARG" in
@ -36,10 +31,6 @@ for ARG in "$@"; do
--bundle=*) --bundle=*)
BUNDLE=( "${ARG#--bundle=}" ) BUNDLE=( "${ARG#--bundle=}" )
;; ;;
--publish)
VERSION_SUFFIX=""
REMOVE_BENCHPRESS=true
;;
*) *)
echo "Unknown option $ARG." echo "Unknown option $ARG."
exit 1 exit 1
@ -47,10 +38,6 @@ for ARG in "$@"; do
esac esac
done done
VERSION="${VERSION_PREFIX}${VERSION_SUFFIX}"
ROUTER_VERSION="${ROUTER_VERSION_PREFIX}${VERSION_SUFFIX}"
echo "====== BUILDING: Version ${VERSION} (Router ${ROUTER_VERSION})"
export NODE_PATH=${NODE_PATH}:$(pwd)/dist/all:$(pwd)/dist/tools export NODE_PATH=${NODE_PATH}:$(pwd)/dist/all:$(pwd)/dist/tools
TSC="node --max-old-space-size=3000 dist/tools/@angular/tsc-wrapped/src/main" TSC="node --max-old-space-size=3000 dist/tools/@angular/tsc-wrapped/src/main"
UGLIFYJS=`pwd`/node_modules/.bin/uglifyjs UGLIFYJS=`pwd`/node_modules/.bin/uglifyjs
@ -76,11 +63,10 @@ if [[ ${BUILD_ALL} == true ]]; then
ln -s ../../../../node_modules/zone.js/dist/zone.js . ln -s ../../../../node_modules/zone.js/dist/zone.js .
ln -s ../../../../node_modules/zone.js/dist/long-stack-trace-zone.js . ln -s ../../../../node_modules/zone.js/dist/long-stack-trace-zone.js .
ln -s ../../../../node_modules/systemjs/dist/system.src.js . ln -s ../../../../node_modules/systemjs/dist/system.src.js .
ln -s ../../../../node_modules/base64-js . ln -s ../../../../node_modules/base64-js/lib/b64.js .
ln -s ../../../../node_modules/reflect-metadata/Reflect.js . ln -s ../../../../node_modules/reflect-metadata/Reflect.js .
ln -s ../../../../node_modules/rxjs . ln -s ../../../../node_modules/rxjs .
ln -s ../../../../node_modules/angular/angular.js . ln -s ../../../../node_modules/angular/angular.js .
ln -s ../../../../node_modules/hammerjs/hammer.js .
cd - cd -
echo "====== Copying files needed for benchmarks =====" echo "====== Copying files needed for benchmarks ====="
@ -92,6 +78,7 @@ if [[ ${BUILD_ALL} == true ]]; then
ln -s ../../../../node_modules/zone.js/dist/zone.js . ln -s ../../../../node_modules/zone.js/dist/zone.js .
ln -s ../../../../node_modules/zone.js/dist/long-stack-trace-zone.js . ln -s ../../../../node_modules/zone.js/dist/long-stack-trace-zone.js .
ln -s ../../../../node_modules/systemjs/dist/system.src.js . ln -s ../../../../node_modules/systemjs/dist/system.src.js .
ln -s ../../../../node_modules/base64-js/lib/b64.js .
ln -s ../../../../node_modules/reflect-metadata/Reflect.js . ln -s ../../../../node_modules/reflect-metadata/Reflect.js .
ln -s ../../../../node_modules/rxjs . ln -s ../../../../node_modules/rxjs .
ln -s ../../../../node_modules/angular/angular.js . ln -s ../../../../node_modules/angular/angular.js .
@ -114,29 +101,14 @@ do
DESTDIR=${PWD}/dist/packages-dist/${PACKAGE} DESTDIR=${PWD}/dist/packages-dist/${PACKAGE}
UMD_ES5_PATH=${DESTDIR}/bundles/${PACKAGE}.umd.js UMD_ES5_PATH=${DESTDIR}/bundles/${PACKAGE}.umd.js
UMD_TESTING_ES5_PATH=${DESTDIR}/bundles/${PACKAGE}-testing.umd.js UMD_TESTING_ES5_PATH=${DESTDIR}/bundles/${PACKAGE}-testing.umd.js
UMD_STATIC_ES5_PATH=${DESTDIR}/bundles/${PACKAGE}-static.umd.js
UMD_UPGRADE_ES5_PATH=${DESTDIR}/bundles/${PACKAGE}-upgrade.umd.js
UMD_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}.umd.min.js UMD_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}.umd.min.js
UMD_STATIC_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}-static.umd.min.js
UMD_UPGRADE_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}-upgrade.umd.min.js
if [[ ${PACKAGE} != router ]]; then
LICENSE_BANNER=${PWD}/modules/@angular/license-banner.txt LICENSE_BANNER=${PWD}/modules/@angular/license-banner.txt
fi
if [[ ${PACKAGE} == router ]]; then
LICENSE_BANNER=${PWD}/modules/@angular/router-license-banner.txt
fi
rm -rf ${DESTDIR} rm -rf ${DESTDIR}
echo "====== COMPILING: ${TSC} -p ${SRCDIR}/tsconfig-build.json =====" echo "====== COMPILING: ${TSC} -p ${SRCDIR}/tsconfig-build.json ====="
$TSC -p ${SRCDIR}/tsconfig-build.json $TSC -p ${SRCDIR}/tsconfig-build.json
if [[ -e ${SRCDIR}/tsconfig-upgrade.json ]]; then
echo "====== COMPILING: ${TSC} -p ${SRCDIR}/tsconfig-upgrade.json ====="
$TSC -p ${SRCDIR}/tsconfig-upgrade.json
fi
cp ${SRCDIR}/package.json ${DESTDIR}/ cp ${SRCDIR}/package.json ${DESTDIR}/
cp ${PWD}/modules/@angular/README.md ${DESTDIR}/ cp ${PWD}/modules/@angular/README.md ${DESTDIR}/
@ -145,11 +117,6 @@ do
$TSC -p ${SRCDIR}/tsconfig-testing.json $TSC -p ${SRCDIR}/tsconfig-testing.json
fi fi
if [[ -e ${SRCDIR}/tsconfig-2015.json ]]; then
echo "====== COMPILING ESM: ${TSC} -p ${SRCDIR}/tsconfig-2015.json"
${TSC} -p ${SRCDIR}/tsconfig-2015.json
fi
echo "====== TSC 1.8 d.ts compat for ${DESTDIR} =====" echo "====== TSC 1.8 d.ts compat for ${DESTDIR} ====="
# safely strips 'readonly' specifier from d.ts files to make them compatible with tsc 1.8 # safely strips 'readonly' specifier from d.ts files to make them compatible with tsc 1.8
if [ "$(uname)" == "Darwin" ]; then if [ "$(uname)" == "Darwin" ]; then
@ -181,6 +148,7 @@ do
mv ${UMD_ES5_PATH}.tmp ${UMD_ES5_PATH} mv ${UMD_ES5_PATH}.tmp ${UMD_ES5_PATH}
$UGLIFYJS -c --screw-ie8 --comments -o ${UMD_ES5_MIN_PATH} ${UMD_ES5_PATH} $UGLIFYJS -c --screw-ie8 --comments -o ${UMD_ES5_MIN_PATH} ${UMD_ES5_PATH}
if [[ -e rollup-testing.config.js ]]; then if [[ -e rollup-testing.config.js ]]; then
echo "====== Rollup ${PACKAGE} testing" echo "====== Rollup ${PACKAGE} testing"
../../../node_modules/.bin/rollup -c rollup-testing.config.js ../../../node_modules/.bin/rollup -c rollup-testing.config.js
@ -189,49 +157,9 @@ do
cat ${UMD_TESTING_ES5_PATH} >> ${UMD_TESTING_ES5_PATH}.tmp cat ${UMD_TESTING_ES5_PATH} >> ${UMD_TESTING_ES5_PATH}.tmp
mv ${UMD_TESTING_ES5_PATH}.tmp ${UMD_TESTING_ES5_PATH} mv ${UMD_TESTING_ES5_PATH}.tmp ${UMD_TESTING_ES5_PATH}
fi fi
if [[ -e rollup-static.config.js ]]; then
echo "====== Rollup ${PACKAGE} static"
../../../node_modules/.bin/rollup -c rollup-static.config.js
# create dir because it doesn't exist yet, we should move the src code here and remove this line
mkdir ${DESTDIR}/static
echo "{\"main\": \"../bundles/${PACKAGE}-static.umd.js\"}" > ${DESTDIR}/static/package.json
cat ${LICENSE_BANNER} > ${UMD_STATIC_ES5_PATH}.tmp
cat ${UMD_STATIC_ES5_PATH} >> ${UMD_STATIC_ES5_PATH}.tmp
mv ${UMD_STATIC_ES5_PATH}.tmp ${UMD_STATIC_ES5_PATH}
$UGLIFYJS -c --screw-ie8 --comments -o ${UMD_STATIC_ES5_MIN_PATH} ${UMD_STATIC_ES5_PATH}
fi
if [[ -e rollup-upgrade.config.js ]]; then
echo "====== Rollup ${PACKAGE} upgrade"
../../../node_modules/.bin/rollup -c rollup-upgrade.config.js
# create dir because it doesn't exist yet, we should move the src code here and remove this line
mkdir ${DESTDIR}/upgrade
echo "{\"main\": \"../bundles/${PACKAGE}-upgrade.umd.js\"}" > ${DESTDIR}/upgrade/package.json
cat ${LICENSE_BANNER} > ${UMD_UPGRADE_ES5_PATH}.tmp
cat ${UMD_UPGRADE_ES5_PATH} >> ${UMD_UPGRADE_ES5_PATH}.tmp
mv ${UMD_UPGRADE_ES5_PATH}.tmp ${UMD_UPGRADE_ES5_PATH}
$UGLIFYJS -c --screw-ie8 --comments -o ${UMD_UPGRADE_ES5_MIN_PATH} ${UMD_UPGRADE_ES5_PATH}
fi
) 2>&1 | grep -v "as external dependency" ) 2>&1 | grep -v "as external dependency"
fi
( fi
echo "====== VERSION: Updating version references"
cd ${DESTDIR}
echo "====== EXECUTE: perl -p -i -e \"s/0\.0\.0\-PLACEHOLDER/${VERSION}/g\" $""(grep -ril 0\.0\.0\-PLACEHOLDER .)"
perl -p -i -e "s/0\.0\.0\-PLACEHOLDER/${VERSION}/g" $(grep -ril 0\.0\.0\-PLACEHOLDER .) < /dev/null 2> /dev/null
echo "====== EXECUTE: perl -p -i -e \"s/0\.0\.0\-ROUTERPLACEHOLDER/${ROUTER_VERSION}/g\" $""(grep -ril 0\.0\.0\-ROUTERPLACEHOLDER .)"
perl -p -i -e "s/0\.0\.0\-ROUTERPLACEHOLDER/${ROUTER_VERSION}/g" $(grep -ril 0\.0\.0\-ROUTERPLACEHOLDER .) < /dev/null 2> /dev/null
)
done done
echo ""
echo "====== Building examples: ./modules/@angular/examples/build.sh ====="
./modules/@angular/examples/build.sh ./modules/@angular/examples/build.sh
if [[ ${REMOVE_BENCHPRESS} == true ]]; then
echo ""
echo "==== Removing benchpress from publication"
rm -r dist/packages-dist/benchpress
fi

View File

@ -4,7 +4,7 @@ machine:
dependencies: dependencies:
pre: pre:
- npm install -g npm@3.6.0 - npm install -g npm
test: test:
override: override:

View File

@ -1,40 +0,0 @@
# Supported Public API Surface of Angular
Our SemVer, timed-release cycle and deprecation policy currently applies to these npm packages:
- `@angular/core`
- `@angular/common`
- `@angular/platform-browser`
- `@angular/platform-browser-dynamic`
- `@angular/platform-server`
- `@angular/platform-webworker`
- `@angular/platform-webworker-dynamic`
- `@angular/upgrade`
- `@angular/router`
- `@angular/forms`
- `@angular/http`
One intentional omission from this list is `@angular/compiler`, which is currently considered a low level api and is subject to internal changes. These changes will not affect any applications or libraries using the higher-level apis (the command line interface or JIT compilation via `@angular/platform-browser-dynamic`). Only very specific use-cases require direct access to the compiler API (mostly tooling integration for IDEs, linters, etc). If you are working on this kind of integration, please reach out to us first.
Additionally only the command line usage (not direct use of APIs) of `@angular/compiler-cli` is covered.
Other projects developed by the Angular team like angular-cli, Angular Material, benchpress, will be covered by these or similar guarantees in the future as they mature.
Within the supported packages, we provide guarantees for:
- symbols exported via the main entry point (e.g. `@angular/core`) and testing entry point (e.g. `@angular/core/testing`). This applies to both runtime/JavaScript values and TypeScript types.
- symbols exported via global namespace `ng` (e.g. `ng.core`)
- bundles located in the `bundles/` directory of our npm packages (e.g. `@angular/core/bundles/core.umd.js`)
We explicitly don't consider the following to be our public API surface:
- any file/import paths within our package except for the `/`, `/testing` and `/bundles/*`
- constructors of injectable classes (services and directives) - please use DI to obtain instances of these classes
- any class members or symbols marked as `private` or prefixed with underscore
- extending any of our classes unless the support for this is specifically documented in the API docs
- the contents and API surface of the code generated by Angular's compiler (with one notable exception: the existence and name of `NgModuleFactory` instances exported from generated code is guaranteed)
Our peer dependencies (e.g. typescript, zone.js, or rxjs) are not considered part of our API surface, but they are included in our SemVer policies. We might update the required version of any of these dependencies in minor releases if the update doesn't cause breaking changes for Angular applications. Peer dependency updates that result in non-trivial breaking changes must be deferred to major Angular releases.

View File

@ -58,7 +58,6 @@ const entrypoints = [
//'dist/packages-dist/compiler/index.d.ts', //'dist/packages-dist/compiler/index.d.ts',
//'dist/packages-dist/compiler/testing.d.ts', //'dist/packages-dist/compiler/testing.d.ts',
'dist/packages-dist/upgrade/index.d.ts', 'dist/packages-dist/upgrade/index.d.ts',
'dist/packages-dist/upgrade/static.d.ts',
'dist/packages-dist/platform-browser/index.d.ts', 'dist/packages-dist/platform-browser/index.d.ts',
'dist/packages-dist/platform-browser/testing/index.d.ts', 'dist/packages-dist/platform-browser/testing/index.d.ts',
'dist/packages-dist/platform-browser-dynamic/index.d.ts', 'dist/packages-dist/platform-browser-dynamic/index.d.ts',
@ -125,31 +124,38 @@ gulp.task('public-api:update', ['build.sh'], (done) => {
.on('close', done); .on('close', done);
}); });
// Checks tests for presence of ddescribe, fdescribe, fit, iit and fails the build if one of the
// focused tests is found.
// Currently xdescribe and xit are _not_ reported as errors since there are a couple of excluded
// tests in our code base.
gulp.task('check-tests', function() {
const ddescribeIit = require('gulp-ddescribe-iit');
return gulp
.src([
'modules/**/*.spec.ts',
'modules/**/*_spec.ts',
])
.pipe(ddescribeIit({allowDisabledTests: true}));
});
// Check the coding standards and programming errors // Check the coding standards and programming errors
gulp.task('lint', ['format:enforce', 'tools:build'], () => { gulp.task('lint', ['check-tests', 'format:enforce', 'tools:build'], () => {
const tslint = require('gulp-tslint'); const tslint = require('gulp-tslint');
// Built-in rules are at // Built-in rules are at
// https://palantir.github.io/tslint/rules/ // https://github.com/palantir/tslint#supported-rules
const tslintConfig = require('./tslint.json'); const tslintConfig = require('./tslint.json');
return gulp return gulp
.src([ .src([
// todo(vicb): add .js files when supported // todo(vicb): add .js files when supported
// see https://github.com/palantir/tslint/pull/1515 // see https://github.com/palantir/tslint/pull/1515
'./modules/**/*.ts', 'modules/@angular/**/*.ts',
'./tools/**/*.ts', 'modules/benchpress/**/*.ts',
'./*.ts', './*.ts',
// Ignore TypeScript mocks because it's not managed by us
'!./tools/@angular/tsc-wrapped/test/typescript.mocks.ts',
// Ignore generated files due to lack of copyright header
// todo(alfaproject): make generated files lintable
'!**/*.d.ts',
'!**/*.ngfactory.ts',
]) ])
.pipe(tslint({ .pipe(tslint({
tslint: require('tslint').default, tslint: require('tslint').default,
configuration: tslintConfig, configuration: tslintConfig,
rulesDirectory: 'dist/tools/tslint',
formatter: 'prose', formatter: 'prose',
})) }))
.pipe(tslint.report({emitError: true})); .pipe(tslint.report({emitError: true}));

View File

@ -23,26 +23,18 @@ module.exports = function(config) {
'node_modules/core-js/client/core.js', 'node_modules/core-js/client/core.js',
// include Angular v1 for upgrade module testing // include Angular v1 for upgrade module testing
'node_modules/angular/angular.js', 'node_modules/angular/angular.min.js',
'node_modules/angular-mocks/angular-mocks.js',
'node_modules/zone.js/dist/zone.js', 'node_modules/zone.js/dist/zone.js', 'node_modules/zone.js/dist/long-stack-trace-zone.js',
'node_modules/zone.js/dist/long-stack-trace-zone.js', 'node_modules/zone.js/dist/proxy.js', 'node_modules/zone.js/dist/sync-test.js',
'node_modules/zone.js/dist/proxy.js', 'node_modules/zone.js/dist/jasmine-patch.js', 'node_modules/zone.js/dist/async-test.js',
'node_modules/zone.js/dist/sync-test.js',
'node_modules/zone.js/dist/jasmine-patch.js',
'node_modules/zone.js/dist/async-test.js',
'node_modules/zone.js/dist/fake-async-test.js', 'node_modules/zone.js/dist/fake-async-test.js',
// Including systemjs because it defines `__eval`, which produces correct stack traces. // Including systemjs because it defines `__eval`, which produces correct stack traces.
'shims_for_IE.js', 'shims_for_IE.js', 'node_modules/systemjs/dist/system.src.js',
'node_modules/systemjs/dist/system.src.js',
{pattern: 'node_modules/rxjs/**', included: false, watched: false, served: true}, {pattern: 'node_modules/rxjs/**', included: false, watched: false, served: true},
'node_modules/reflect-metadata/Reflect.js', 'node_modules/reflect-metadata/Reflect.js', 'tools/build/file2modulename.js', 'test-main.js',
'tools/build/file2modulename.js', {pattern: 'dist/all/empty.*', included: false, watched: false}, {
'test-main.js',
{pattern: 'dist/all/empty.*', included: false, watched: false},
{
pattern: 'modules/@angular/platform-browser/test/static_assets/**', pattern: 'modules/@angular/platform-browser/test/static_assets/**',
included: false, included: false,
watched: false watched: false
@ -56,15 +48,11 @@ module.exports = function(config) {
exclude: [ exclude: [
'dist/all/@angular/**/e2e_test/**', 'dist/all/@angular/**/e2e_test/**',
'dist/all/@angular/**/*node_only_spec.js',
'dist/all/@angular/benchpress/**',
'dist/all/@angular/compiler-cli/**',
'dist/all/@angular/compiler/test/aot/**',
'dist/all/@angular/examples/**/e2e_test/*',
'dist/all/@angular/language-service/**',
'dist/all/@angular/router/**', 'dist/all/@angular/router/**',
'dist/all/@angular/platform-browser/testing/e2e_util.js', 'dist/all/@angular/compiler-cli/**',
'dist/all/@angular/benchpress/**',
'dist/all/angular1_router.js', 'dist/all/angular1_router.js',
'dist/all/@angular/platform-browser/testing/e2e_util.js',
'dist/examples/**/e2e_test/**', 'dist/examples/**/e2e_test/**',
], ],
@ -102,17 +90,17 @@ module.exports = function(config) {
project: 'Angular2', project: 'Angular2',
startTunnel: false, startTunnel: false,
retryLimit: 3, retryLimit: 3,
timeout: 1800, timeout: 600,
pollingTimeout: 10000, pollingTimeout: 10000,
}, },
browsers: ['Chrome'], browsers: ['Chrome'],
port: 9876, port: 9876,
captureTimeout: 180000, captureTimeout: 60000,
browserDisconnectTimeout: 180000, browserDisconnectTimeout: 60000,
browserDisconnectTolerance: 3, browserDisconnectTolerance: 3,
browserNoActivityTimeout: 300000, browserNoActivityTimeout: 60000,
}); });
if (process.env.TRAVIS) { if (process.env.TRAVIS) {

View File

@ -5,7 +5,7 @@ See [here for an example project](https://github.com/angular/benchpress-tree).
The sources for this package are in the main [Angular2](https://github.com/angular/angular) repo. Please file issues and pull requests against that repo. The sources for this package are in the main [Angular2](https://github.com/angular/angular) repo. Please file issues and pull requests against that repo.
License: MIT License: Apache MIT 2.0
# Why? # Why?

View File

@ -7,7 +7,7 @@
"dependencies": { "dependencies": {
"@angular/core": "^2.0.0-rc.7", "@angular/core": "^2.0.0-rc.7",
"reflect-metadata": "^0.1.2", "reflect-metadata": "^0.1.2",
"rxjs": "^5.0.1", "rxjs": "5.0.0-beta.12",
"jpm": "1.1.4", "jpm": "1.1.4",
"firefox-profile": "0.4.0", "firefox-profile": "0.4.0",
"selenium-webdriver": "^2.53.3" "selenium-webdriver": "^2.53.3"

View File

@ -10,7 +10,7 @@ declare var exportFunction: any;
declare var unsafeWindow: any; declare var unsafeWindow: any;
exportFunction(function() { exportFunction(function() {
const curTime = unsafeWindow.performance.now(); var curTime = unsafeWindow.performance.now();
(<any>self).port.emit('startProfiler', curTime); (<any>self).port.emit('startProfiler', curTime);
}, unsafeWindow, {defineAs: 'startProfiler'}); }, unsafeWindow, {defineAs: 'startProfiler'});
@ -28,11 +28,11 @@ exportFunction(function() {
}, unsafeWindow, {defineAs: 'forceGC'}); }, unsafeWindow, {defineAs: 'forceGC'});
exportFunction(function(name: string) { exportFunction(function(name: string) {
const curTime = unsafeWindow.performance.now(); var curTime = unsafeWindow.performance.now();
(<any>self).port.emit('markStart', name, curTime); (<any>self).port.emit('markStart', name, curTime);
}, unsafeWindow, {defineAs: 'markStart'}); }, unsafeWindow, {defineAs: 'markStart'});
exportFunction(function(name: string) { exportFunction(function(name: string) {
const curTime = unsafeWindow.performance.now(); var curTime = unsafeWindow.performance.now();
(<any>self).port.emit('markEnd', name, curTime); (<any>self).port.emit('markEnd', name, curTime);
}, unsafeWindow, {defineAs: 'markEnd'}); }, unsafeWindow, {defineAs: 'markEnd'});

View File

@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
const {Cc, Ci, Cu} = require('chrome'); var {Cc, Ci, Cu} = require('chrome');
const os = Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService); var os = Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService);
const ParserUtil = require('./parser_util'); var ParserUtil = require('./parser_util');
class Profiler { class Profiler {
private _profiler: any; private _profiler: any;
@ -26,8 +26,8 @@ class Profiler {
stop() { this._profiler.StopProfiler(); } stop() { this._profiler.StopProfiler(); }
getProfilePerfEvents() { getProfilePerfEvents() {
const profileData = this._profiler.getProfileData(); var profileData = this._profiler.getProfileData();
let perfEvents = ParserUtil.convertPerfProfileToEvents(profileData); var perfEvents = ParserUtil.convertPerfProfileToEvents(profileData);
perfEvents = this._mergeMarkerEvents(perfEvents); perfEvents = this._mergeMarkerEvents(perfEvents);
perfEvents.sort(function(event1: any, event2: any) { perfEvents.sort(function(event1: any, event2: any) {
return event1.ts - event2.ts; return event1.ts - event2.ts;
@ -55,9 +55,9 @@ function forceGC() {
os.notifyObservers(null, 'child-gc-request', null); os.notifyObservers(null, 'child-gc-request', null);
}; };
const mod = require('sdk/page-mod'); var mod = require('sdk/page-mod');
const data = require('sdk/self').data; var data = require('sdk/self').data;
const profiler = new Profiler(); var profiler = new Profiler();
mod.PageMod({ mod.PageMod({
include: ['*'], include: ['*'],
contentScriptFile: data.url('installed_script.js'), contentScriptFile: data.url('installed_script.js'),

View File

@ -12,11 +12,11 @@
* within the perf profile. * within the perf profile.
*/ */
export function convertPerfProfileToEvents(perfProfile: any): any[] { export function convertPerfProfileToEvents(perfProfile: any): any[] {
const inProgressEvents = new Map(); // map from event name to start time var inProgressEvents = new Map(); // map from event name to start time
const finishedEvents: {[key: string]: any}[] = []; // Event[] finished events var finishedEvents: {[key: string]: any}[] = []; // Event[] finished events
const addFinishedEvent = function(eventName: string, startTime: number, endTime: number) { var addFinishedEvent = function(eventName: string, startTime: number, endTime: number) {
const categorizedEventName = categorizeEvent(eventName); var categorizedEventName = categorizeEvent(eventName);
let args: {[key: string]: any} = undefined; var args: {[key: string]: any} = undefined;
if (categorizedEventName == 'gc') { if (categorizedEventName == 'gc') {
// TODO: We cannot measure heap size at the moment // TODO: We cannot measure heap size at the moment
args = {usedHeapSize: 0}; args = {usedHeapSize: 0};
@ -31,17 +31,17 @@ export function convertPerfProfileToEvents(perfProfile: any): any[] {
} }
}; };
const samples = perfProfile.threads[0].samples; var samples = perfProfile.threads[0].samples;
// In perf profile, firefox samples all the frames in set time intervals. Here // In perf profile, firefox samples all the frames in set time intervals. Here
// we go through all the samples and construct the start and end time for each // we go through all the samples and construct the start and end time for each
// event. // event.
for (let i = 0; i < samples.length; ++i) { for (var i = 0; i < samples.length; ++i) {
const sample = samples[i]; var sample = samples[i];
const sampleTime = sample.time; var sampleTime = sample.time;
// Add all the frames into a set so it's easier/faster to find the set // Add all the frames into a set so it's easier/faster to find the set
// differences // differences
const sampleFrames = new Set(); var sampleFrames = new Set();
sample.frames.forEach(function(frame: {[key: string]: any}) { sample.frames.forEach(function(frame: {[key: string]: any}) {
sampleFrames.add(frame['location']); sampleFrames.add(frame['location']);
}); });
@ -49,7 +49,7 @@ export function convertPerfProfileToEvents(perfProfile: any): any[] {
// If an event is in the inProgressEvents map, but not in the current sample, // If an event is in the inProgressEvents map, but not in the current sample,
// then it must have just finished. We add this event to the finishedEvents // then it must have just finished. We add this event to the finishedEvents
// array and remove it from the inProgressEvents map. // array and remove it from the inProgressEvents map.
const previousSampleTime = (i == 0 ? /* not used */ -1 : samples[i - 1].time); var previousSampleTime = (i == 0 ? /* not used */ -1 : samples[i - 1].time);
inProgressEvents.forEach(function(startTime, eventName) { inProgressEvents.forEach(function(startTime, eventName) {
if (!(sampleFrames.has(eventName))) { if (!(sampleFrames.has(eventName))) {
addFinishedEvent(eventName, startTime, previousSampleTime); addFinishedEvent(eventName, startTime, previousSampleTime);
@ -69,7 +69,7 @@ export function convertPerfProfileToEvents(perfProfile: any): any[] {
// If anything is still in progress, we need to included it as a finished event // If anything is still in progress, we need to included it as a finished event
// since recording ended. // since recording ended.
const lastSampleTime = samples[samples.length - 1].time; var lastSampleTime = samples[samples.length - 1].time;
inProgressEvents.forEach(function(startTime, eventName) { inProgressEvents.forEach(function(startTime, eventName) {
addFinishedEvent(eventName, startTime, lastSampleTime); addFinishedEvent(eventName, startTime, lastSampleTime);
}); });

View File

@ -6,15 +6,15 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
const q = require('q'); var q = require('q');
const FirefoxProfile = require('firefox-profile'); var FirefoxProfile = require('firefox-profile');
const jpm = require('jpm/lib/xpi'); var jpm = require('jpm/lib/xpi');
const pathUtil = require('path'); var pathUtil = require('path');
const PERF_ADDON_PACKAGE_JSON_DIR = '..'; var PERF_ADDON_PACKAGE_JSON_DIR = '..';
exports.getAbsolutePath = function(path: string) { exports.getAbsolutePath = function(path: string) {
const normalizedPath = pathUtil.normalize(path); var normalizedPath = pathUtil.normalize(path);
if (pathUtil.resolve(normalizedPath) == normalizedPath) { if (pathUtil.resolve(normalizedPath) == normalizedPath) {
// Already absolute path // Already absolute path
return normalizedPath; return normalizedPath;
@ -24,12 +24,12 @@ exports.getAbsolutePath = function(path: string) {
}; };
exports.getFirefoxProfile = function(extensionPath: string) { exports.getFirefoxProfile = function(extensionPath: string) {
const deferred = q.defer(); var deferred = q.defer();
const firefoxProfile = new FirefoxProfile(); var firefoxProfile = new FirefoxProfile();
firefoxProfile.addExtensions([extensionPath], () => { firefoxProfile.addExtensions([extensionPath], () => {
firefoxProfile.encoded((encodedProfile: any) => { firefoxProfile.encoded((encodedProfile: any) => {
const multiCapabilities = [{browserName: 'firefox', firefox_profile: encodedProfile}]; var multiCapabilities = [{browserName: 'firefox', firefox_profile: encodedProfile}];
deferred.resolve(multiCapabilities); deferred.resolve(multiCapabilities);
}); });
}); });
@ -38,10 +38,10 @@ exports.getFirefoxProfile = function(extensionPath: string) {
}; };
exports.getFirefoxProfileWithExtension = function() { exports.getFirefoxProfileWithExtension = function() {
const absPackageJsonDir = pathUtil.join(__dirname, PERF_ADDON_PACKAGE_JSON_DIR); var absPackageJsonDir = pathUtil.join(__dirname, PERF_ADDON_PACKAGE_JSON_DIR);
const packageJson = require(pathUtil.join(absPackageJsonDir, 'package.json')); var packageJson = require(pathUtil.join(absPackageJsonDir, 'package.json'));
const savedCwd = process.cwd(); var savedCwd = process.cwd();
process.chdir(absPackageJsonDir); process.chdir(absPackageJsonDir);
return jpm(packageJson).then((xpiPath: string) => { return jpm(packageJson).then((xpiPath: string) => {

View File

@ -55,9 +55,9 @@ export class MultiMetric extends Metric {
} }
function mergeStringMaps(maps: {[key: string]: string}[]): {[key: string]: string} { function mergeStringMaps(maps: {[key: string]: string}[]): {[key: string]: string} {
const result: {[key: string]: string} = {}; var result: {[key: string]: string} = {};
maps.forEach(map => { Object.keys(map).forEach(prop => { result[prop] = map[prop]; }); }); maps.forEach(map => { Object.keys(map).forEach(prop => { result[prop] = map[prop]; }); });
return result; return result;
} }
const _CHILDREN = new OpaqueToken('MultiMetric.children'); var _CHILDREN = new OpaqueToken('MultiMetric.children');

View File

@ -56,7 +56,7 @@ export class PerflogMetric extends Metric {
} }
describe(): {[key: string]: string} { describe(): {[key: string]: string} {
const res: {[key: string]: any} = { var res: {[key: string]: any} = {
'scriptTime': 'script execution time in ms, including gc and render', 'scriptTime': 'script execution time in ms, including gc and render',
'pureScriptTime': 'script execution time in ms, without gc nor render' 'pureScriptTime': 'script execution time in ms, without gc nor render'
}; };
@ -80,7 +80,7 @@ export class PerflogMetric extends Metric {
} }
if (this._captureFrames) { if (this._captureFrames) {
if (!this._perfLogFeatures.frameCapture) { if (!this._perfLogFeatures.frameCapture) {
const warningMsg = 'WARNING: Metric requested, but not supported by driver'; var warningMsg = 'WARNING: Metric requested, but not supported by driver';
// using dot syntax for metric name to keep them grouped together in console reporter // using dot syntax for metric name to keep them grouped together in console reporter
res['frameTime.mean'] = warningMsg; res['frameTime.mean'] = warningMsg;
res['frameTime.worst'] = warningMsg; res['frameTime.worst'] = warningMsg;
@ -93,14 +93,14 @@ export class PerflogMetric extends Metric {
res['frameTime.smooth'] = 'percentage of frames that hit 60fps'; res['frameTime.smooth'] = 'percentage of frames that hit 60fps';
} }
} }
for (const name in this._microMetrics) { for (let name in this._microMetrics) {
res[name] = this._microMetrics[name]; res[name] = this._microMetrics[name];
} }
return res; return res;
} }
beginMeasure(): Promise<any> { beginMeasure(): Promise<any> {
let resultPromise = Promise.resolve(null); var resultPromise = Promise.resolve(null);
if (this._forceGc) { if (this._forceGc) {
resultPromise = resultPromise.then((_) => this._driverExtension.gc()); resultPromise = resultPromise.then((_) => this._driverExtension.gc());
} }
@ -119,7 +119,7 @@ export class PerflogMetric extends Metric {
private _endPlainMeasureAndMeasureForceGc(restartMeasure: boolean) { private _endPlainMeasureAndMeasureForceGc(restartMeasure: boolean) {
return this._endMeasure(true).then((measureValues) => { return this._endMeasure(true).then((measureValues) => {
// disable frame capture for measurements during forced gc // disable frame capture for measurements during forced gc
const originalFrameCaptureValue = this._captureFrames; var originalFrameCaptureValue = this._captureFrames;
this._captureFrames = false; this._captureFrames = false;
return this._driverExtension.gc() return this._driverExtension.gc()
.then((_) => this._endMeasure(restartMeasure)) .then((_) => this._endMeasure(restartMeasure))
@ -137,8 +137,8 @@ export class PerflogMetric extends Metric {
} }
private _endMeasure(restart: boolean): Promise<{[key: string]: number}> { private _endMeasure(restart: boolean): Promise<{[key: string]: number}> {
const markName = this._markName(this._measureCount - 1); var markName = this._markName(this._measureCount - 1);
const nextMarkName = restart ? this._markName(this._measureCount++) : null; var nextMarkName = restart ? this._markName(this._measureCount++) : null;
return this._driverExtension.timeEnd(markName, nextMarkName) return this._driverExtension.timeEnd(markName, nextMarkName)
.then((_) => this._readUntilEndMark(markName)); .then((_) => this._readUntilEndMark(markName));
} }
@ -150,26 +150,26 @@ export class PerflogMetric extends Metric {
} }
return this._driverExtension.readPerfLog().then((events) => { return this._driverExtension.readPerfLog().then((events) => {
this._addEvents(events); this._addEvents(events);
const result = this._aggregateEvents(this._remainingEvents, markName); var result = this._aggregateEvents(this._remainingEvents, markName);
if (result) { if (result) {
this._remainingEvents = events; this._remainingEvents = events;
return result; return result;
} }
let resolve: (result: any) => void; var resolve: (result: any) => void;
const promise = new Promise(res => { resolve = res; }); var promise = new Promise(res => { resolve = res; });
this._setTimeout(() => resolve(this._readUntilEndMark(markName, loopCount + 1)), 100); this._setTimeout(() => resolve(this._readUntilEndMark(markName, loopCount + 1)), 100);
return promise; return promise;
}); });
} }
private _addEvents(events: PerfLogEvent[]) { private _addEvents(events: PerfLogEvent[]) {
let needSort = false; var needSort = false;
events.forEach(event => { events.forEach(event => {
if (event['ph'] === 'X') { if (event['ph'] === 'X') {
needSort = true; needSort = true;
const startEvent: PerfLogEvent = {}; var startEvent: PerfLogEvent = {};
const endEvent: PerfLogEvent = {}; var endEvent: PerfLogEvent = {};
for (const prop in event) { for (let prop in event) {
startEvent[prop] = event[prop]; startEvent[prop] = event[prop];
endEvent[prop] = event[prop]; endEvent[prop] = event[prop];
} }
@ -185,14 +185,14 @@ export class PerflogMetric extends Metric {
if (needSort) { if (needSort) {
// Need to sort because of the ph==='X' events // Need to sort because of the ph==='X' events
this._remainingEvents.sort((a, b) => { this._remainingEvents.sort((a, b) => {
const diff = a['ts'] - b['ts']; var diff = a['ts'] - b['ts'];
return diff > 0 ? 1 : diff < 0 ? -1 : 0; return diff > 0 ? 1 : diff < 0 ? -1 : 0;
}); });
} }
} }
private _aggregateEvents(events: PerfLogEvent[], markName: string): {[key: string]: number} { private _aggregateEvents(events: PerfLogEvent[], markName: string): {[key: string]: number} {
const result: {[key: string]: number} = {'scriptTime': 0, 'pureScriptTime': 0}; var result: {[key: string]: number} = {'scriptTime': 0, 'pureScriptTime': 0};
if (this._perfLogFeatures.gc) { if (this._perfLogFeatures.gc) {
result['gcTime'] = 0; result['gcTime'] = 0;
result['majorGcTime'] = 0; result['majorGcTime'] = 0;
@ -207,7 +207,7 @@ export class PerflogMetric extends Metric {
result['frameTime.worst'] = 0; result['frameTime.worst'] = 0;
result['frameTime.smooth'] = 0; result['frameTime.smooth'] = 0;
} }
for (const name in this._microMetrics) { for (let name in this._microMetrics) {
result[name] = 0; result[name] = 0;
} }
if (this._receivedData) { if (this._receivedData) {
@ -217,11 +217,11 @@ export class PerflogMetric extends Metric {
result['requestCount'] = 0; result['requestCount'] = 0;
} }
let markStartEvent: PerfLogEvent = null; var markStartEvent: PerfLogEvent = null;
let markEndEvent: PerfLogEvent = null; var markEndEvent: PerfLogEvent = null;
events.forEach((event) => { events.forEach((event) => {
const ph = event['ph']; var ph = event['ph'];
const name = event['name']; var name = event['name'];
if (ph === 'B' && name === markName) { if (ph === 'B' && name === markName) {
markStartEvent = event; markStartEvent = event;
} else if (ph === 'I' && name === 'navigationStart') { } else if (ph === 'I' && name === 'navigationStart') {
@ -237,23 +237,23 @@ export class PerflogMetric extends Metric {
return null; return null;
} }
let gcTimeInScript = 0; var gcTimeInScript = 0;
let renderTimeInScript = 0; var renderTimeInScript = 0;
const frameTimestamps: number[] = []; var frameTimestamps: number[] = [];
const frameTimes: number[] = []; var frameTimes: number[] = [];
let frameCaptureStartEvent: PerfLogEvent = null; var frameCaptureStartEvent: PerfLogEvent = null;
let frameCaptureEndEvent: PerfLogEvent = null; var frameCaptureEndEvent: PerfLogEvent = null;
const intervalStarts: {[key: string]: PerfLogEvent} = {}; var intervalStarts: {[key: string]: PerfLogEvent} = {};
const intervalStartCount: {[key: string]: number} = {}; var intervalStartCount: {[key: string]: number} = {};
let inMeasureRange = false; var inMeasureRange = false;
events.forEach((event) => { events.forEach((event) => {
const ph = event['ph']; var ph = event['ph'];
let name = event['name']; var name = event['name'];
let microIterations = 1; var microIterations = 1;
const microIterationsMatch = name.match(_MICRO_ITERATIONS_REGEX); var microIterationsMatch = name.match(_MICRO_ITERATIONS_REGEX);
if (microIterationsMatch) { if (microIterationsMatch) {
name = microIterationsMatch[1]; name = microIterationsMatch[1];
microIterations = parseInt(microIterationsMatch[2], 10); microIterations = parseInt(microIterationsMatch[2], 10);
@ -307,15 +307,15 @@ export class PerflogMetric extends Metric {
} else if ((ph === 'E') && intervalStarts[name]) { } else if ((ph === 'E') && intervalStarts[name]) {
intervalStartCount[name]--; intervalStartCount[name]--;
if (intervalStartCount[name] === 0) { if (intervalStartCount[name] === 0) {
const startEvent = intervalStarts[name]; var startEvent = intervalStarts[name];
const duration = (event['ts'] - startEvent['ts']); var duration = (event['ts'] - startEvent['ts']);
intervalStarts[name] = null; intervalStarts[name] = null;
if (name === 'gc') { if (name === 'gc') {
result['gcTime'] += duration; result['gcTime'] += duration;
const amount = var amount =
(startEvent['args']['usedHeapSize'] - event['args']['usedHeapSize']) / 1000; (startEvent['args']['usedHeapSize'] - event['args']['usedHeapSize']) / 1000;
result['gcAmount'] += amount; result['gcAmount'] += amount;
const majorGc = event['args']['majorGc']; var majorGc = event['args']['majorGc'];
if (majorGc && majorGc) { if (majorGc && majorGc) {
result['majorGcTime'] += duration; result['majorGcTime'] += duration;
} }
@ -351,7 +351,7 @@ export class PerflogMetric extends Metric {
private _addFrameMetrics(result: {[key: string]: number}, frameTimes: any[]) { private _addFrameMetrics(result: {[key: string]: number}, frameTimes: any[]) {
result['frameTime.mean'] = frameTimes.reduce((a, b) => a + b, 0) / frameTimes.length; result['frameTime.mean'] = frameTimes.reduce((a, b) => a + b, 0) / frameTimes.length;
const firstFrame = frameTimes[0]; var firstFrame = frameTimes[0];
result['frameTime.worst'] = frameTimes.reduce((a, b) => a > b ? a : b, firstFrame); result['frameTime.worst'] = frameTimes.reduce((a, b) => a > b ? a : b, firstFrame);
result['frameTime.best'] = frameTimes.reduce((a, b) => a < b ? a : b, firstFrame); result['frameTime.best'] = frameTimes.reduce((a, b) => a < b ? a : b, firstFrame);
result['frameTime.smooth'] = result['frameTime.smooth'] =
@ -361,11 +361,11 @@ export class PerflogMetric extends Metric {
private _markName(index: number) { return `${_MARK_NAME_PREFIX}${index}`; } private _markName(index: number) { return `${_MARK_NAME_PREFIX}${index}`; }
} }
const _MICRO_ITERATIONS_REGEX = /(.+)\*(\d+)$/; var _MICRO_ITERATIONS_REGEX = /(.+)\*(\d+)$/;
const _MAX_RETRY_COUNT = 20; var _MAX_RETRY_COUNT = 20;
const _MARK_NAME_PREFIX = 'benchpress'; var _MARK_NAME_PREFIX = 'benchpress';
const _MARK_NAME_FRAME_CAPUTRE = 'frameCapture'; var _MARK_NAME_FRAME_CAPUTRE = 'frameCapture';
// using 17ms as a somewhat looser threshold, instead of 16.6666ms // using 17ms as a somewhat looser threshold, instead of 16.6666ms
const _FRAME_TIME_SMOOTH_THRESHOLD = 17; var _FRAME_TIME_SMOOTH_THRESHOLD = 17;

View File

@ -9,6 +9,7 @@
import {Inject, Injectable} from '@angular/core'; import {Inject, Injectable} from '@angular/core';
import {Options} from '../common_options'; import {Options} from '../common_options';
import {isNumber} from '../facade/lang';
import {Metric} from '../metric'; import {Metric} from '../metric';
import {WebDriverAdapter} from '../web_driver_adapter'; import {WebDriverAdapter} from '../web_driver_adapter';
@ -33,20 +34,20 @@ export class UserMetric extends Metric {
endMeasure(restart: boolean): Promise<{[key: string]: any}> { endMeasure(restart: boolean): Promise<{[key: string]: any}> {
let resolve: (result: any) => void; let resolve: (result: any) => void;
let reject: (error: any) => void; let reject: (error: any) => void;
const promise = new Promise((res, rej) => { let promise = new Promise((res, rej) => {
resolve = res; resolve = res;
reject = rej; reject = rej;
}); });
const adapter = this._wdAdapter; let adapter = this._wdAdapter;
const names = Object.keys(this._userMetrics); let names = Object.keys(this._userMetrics);
function getAndClearValues() { function getAndClearValues() {
Promise.all(names.map(name => adapter.executeScript(`return window.${name}`))) Promise.all(names.map(name => adapter.executeScript(`return window.${name}`)))
.then((values: any[]) => { .then((values: any[]) => {
if (values.every(v => typeof v === 'number')) { if (values.every(isNumber)) {
Promise.all(names.map(name => adapter.executeScript(`delete window.${name}`))) Promise.all(names.map(name => adapter.executeScript(`delete window.${name}`)))
.then((_: any[]) => { .then((_: any[]) => {
const map: {[k: string]: any} = {}; let map: {[k: string]: any} = {};
for (let i = 0, n = names.length; i < n; i++) { for (let i = 0, n = names.length; i < n; i++) {
map[names[i]] = values[i]; map[names[i]] = values[i];
} }

View File

@ -28,8 +28,8 @@ export class ConsoleReporter extends Reporter {
]; ];
private static _lpad(value: string, columnWidth: number, fill = ' ') { private static _lpad(value: string, columnWidth: number, fill = ' ') {
let result = ''; var result = '';
for (let i = 0; i < columnWidth - value.length; i++) { for (var i = 0; i < columnWidth - value.length; i++) {
result += fill; result += fill;
} }
return result + value; return result + value;
@ -49,7 +49,7 @@ export class ConsoleReporter extends Reporter {
private _printDescription(sampleDescription: SampleDescription) { private _printDescription(sampleDescription: SampleDescription) {
this._print(`BENCHMARK ${sampleDescription.id}`); this._print(`BENCHMARK ${sampleDescription.id}`);
this._print('Description:'); this._print('Description:');
const props = sortedProps(sampleDescription.description); var props = sortedProps(sampleDescription.description);
props.forEach((prop) => { this._print(`- ${prop}: ${sampleDescription.description[prop]}`); }); props.forEach((prop) => { this._print(`- ${prop}: ${sampleDescription.description[prop]}`); });
this._print('Metrics:'); this._print('Metrics:');
this._metricNames.forEach((metricName) => { this._metricNames.forEach((metricName) => {
@ -61,8 +61,8 @@ export class ConsoleReporter extends Reporter {
} }
reportMeasureValues(measureValues: MeasureValues): Promise<any> { reportMeasureValues(measureValues: MeasureValues): Promise<any> {
const formattedValues = this._metricNames.map(metricName => { var formattedValues = this._metricNames.map(metricName => {
const value = measureValues.values[metricName]; var value = measureValues.values[metricName];
return formatNum(value); return formatNum(value);
}); });
this._printStringRow(formattedValues); this._printStringRow(formattedValues);

View File

@ -9,6 +9,7 @@
import {Inject, Injectable, OpaqueToken} from '@angular/core'; import {Inject, Injectable, OpaqueToken} from '@angular/core';
import {Options} from '../common_options'; import {Options} from '../common_options';
import {Json} from '../facade/lang';
import {MeasureValues} from '../measure_values'; import {MeasureValues} from '../measure_values';
import {Reporter} from '../reporter'; import {Reporter} from '../reporter';
import {SampleDescription} from '../sample_description'; import {SampleDescription} from '../sample_description';
@ -38,15 +39,13 @@ export class JsonFileReporter extends Reporter {
sortedProps(this._description.metrics).forEach((metricName) => { sortedProps(this._description.metrics).forEach((metricName) => {
stats[metricName] = formatStats(validSample, metricName); stats[metricName] = formatStats(validSample, metricName);
}); });
const content = JSON.stringify( var content = Json.stringify({
{
'description': this._description, 'description': this._description,
'stats': stats, 'stats': stats,
'completeSample': completeSample, 'completeSample': completeSample,
'validSample': validSample, 'validSample': validSample,
}, });
null, 2); var filePath = `${this._path}/${this._description.id}_${this._now().getTime()}.json`;
const filePath = `${this._path}/${this._description.id}_${this._now().getTime()}.json`;
return this._writeFile(filePath, content); return this._writeFile(filePath, content);
} }
} }

View File

@ -39,4 +39,4 @@ export class MultiReporter extends Reporter {
} }
} }
const _CHILDREN = new OpaqueToken('MultiReporter.children'); var _CHILDREN = new OpaqueToken('MultiReporter.children');

View File

@ -6,23 +6,28 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {NumberWrapper} from '../facade/lang';
import {MeasureValues} from '../measure_values'; import {MeasureValues} from '../measure_values';
import {Statistic} from '../statistic'; import {Statistic} from '../statistic';
export function formatNum(n: number) { export function formatNum(n: number) {
return n.toFixed(2); return NumberWrapper.toFixed(n, 2);
} }
export function sortedProps(obj: {[key: string]: any}) { export function sortedProps(obj: {[key: string]: any}) {
return Object.keys(obj).sort(); var props: string[] = [];
props.push(...Object.keys(obj));
props.sort();
return props;
} }
export function formatStats(validSamples: MeasureValues[], metricName: string): string { export function formatStats(validSamples: MeasureValues[], metricName: string): string {
const samples = validSamples.map(measureValues => measureValues.values[metricName]); var samples = validSamples.map(measureValues => measureValues.values[metricName]);
const mean = Statistic.calculateMean(samples); var mean = Statistic.calculateMean(samples);
const cv = Statistic.calculateCoefficientOfVariation(samples, mean); var cv = Statistic.calculateCoefficientOfVariation(samples, mean);
const formattedMean = formatNum(mean); var formattedMean = formatNum(mean);
// Note: Don't use the unicode character for +- as it might cause // Note: Don't use the unicode character for +- as it might cause
// hickups for consoles... // hickups for consoles...
return isNaN(cv) ? formattedMean : `${formattedMean}+-${Math.floor(cv)}%`; return NumberWrapper.isNaN(cv) ? formattedMean : `${formattedMean}+-${Math.floor(cv)}%`;
} }

View File

@ -45,7 +45,7 @@ export class Runner {
providers?: Provider[], providers?: Provider[],
userMetrics?: {[key: string]: string} userMetrics?: {[key: string]: string}
}): Promise<SampleState> { }): Promise<SampleState> {
const sampleProviders: Provider[] = [ var sampleProviders: Provider[] = [
_DEFAULT_PROVIDERS, this._defaultProviders, {provide: Options.SAMPLE_ID, useValue: id}, _DEFAULT_PROVIDERS, this._defaultProviders, {provide: Options.SAMPLE_ID, useValue: id},
{provide: Options.EXECUTE, useValue: execute} {provide: Options.EXECUTE, useValue: execute}
]; ];
@ -62,33 +62,33 @@ export class Runner {
sampleProviders.push(providers); sampleProviders.push(providers);
} }
const inj = ReflectiveInjector.resolveAndCreate(sampleProviders); var inj = ReflectiveInjector.resolveAndCreate(sampleProviders);
const adapter: WebDriverAdapter = inj.get(WebDriverAdapter); var adapter: WebDriverAdapter = inj.get(WebDriverAdapter);
return Promise return Promise
.all([adapter.capabilities(), adapter.executeScript('return window.navigator.userAgent;')]) .all([adapter.capabilities(), adapter.executeScript('return window.navigator.userAgent;')])
.then((args) => { .then((args) => {
const capabilities = args[0]; var capabilities = args[0];
const userAgent = args[1]; var userAgent = args[1];
// This might still create instances twice. We are creating a new injector with all the // This might still create instances twice. We are creating a new injector with all the
// providers. // providers.
// Only WebDriverAdapter is reused. // Only WebDriverAdapter is reused.
// TODO vsavkin consider changing it when toAsyncFactory is added back or when child // TODO vsavkin consider changing it when toAsyncFactory is added back or when child
// injectors are handled better. // injectors are handled better.
const injector = ReflectiveInjector.resolveAndCreate([ var injector = ReflectiveInjector.resolveAndCreate([
sampleProviders, {provide: Options.CAPABILITIES, useValue: capabilities}, sampleProviders, {provide: Options.CAPABILITIES, useValue: capabilities},
{provide: Options.USER_AGENT, useValue: userAgent}, {provide: Options.USER_AGENT, useValue: userAgent},
{provide: WebDriverAdapter, useValue: adapter} {provide: WebDriverAdapter, useValue: adapter}
]); ]);
const sampler = injector.get(Sampler); var sampler = injector.get(Sampler);
return sampler.sample(); return sampler.sample();
}); });
} }
} }
const _DEFAULT_PROVIDERS = [ var _DEFAULT_PROVIDERS = [
Options.DEFAULT_PROVIDERS, Options.DEFAULT_PROVIDERS,
Sampler.PROVIDERS, Sampler.PROVIDERS,
ConsoleReporter.PROVIDERS, ConsoleReporter.PROVIDERS,

View File

@ -49,7 +49,7 @@ export class Sampler {
} }
private _iterate(lastState: SampleState): Promise<SampleState> { private _iterate(lastState: SampleState): Promise<SampleState> {
let resultPromise: Promise<SampleState>; var resultPromise: Promise<SampleState>;
if (this._prepare !== Options.NO_PREPARE) { if (this._prepare !== Options.NO_PREPARE) {
resultPromise = this._driver.waitFor(this._prepare); resultPromise = this._driver.waitFor(this._prepare);
} else { } else {
@ -64,10 +64,10 @@ export class Sampler {
} }
private _report(state: SampleState, metricValues: {[key: string]: any}): Promise<SampleState> { private _report(state: SampleState, metricValues: {[key: string]: any}): Promise<SampleState> {
const measureValues = new MeasureValues(state.completeSample.length, this._now(), metricValues); var measureValues = new MeasureValues(state.completeSample.length, this._now(), metricValues);
const completeSample = state.completeSample.concat([measureValues]); var completeSample = state.completeSample.concat([measureValues]);
const validSample = this._validator.validate(completeSample); var validSample = this._validator.validate(completeSample);
let resultPromise = this._reporter.reportMeasureValues(measureValues); var resultPromise = this._reporter.reportMeasureValues(measureValues);
if (isPresent(validSample)) { if (isPresent(validSample)) {
resultPromise = resultPromise =
resultPromise.then((_) => this._reporter.reportSample(completeSample, validSample)); resultPromise.then((_) => this._reporter.reportSample(completeSample, validSample));

View File

@ -6,20 +6,22 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Math} from './facade/math';
export class Statistic { export class Statistic {
static calculateCoefficientOfVariation(sample: number[], mean: number) { static calculateCoefficientOfVariation(sample: number[], mean: number) {
return Statistic.calculateStandardDeviation(sample, mean) / mean * 100; return Statistic.calculateStandardDeviation(sample, mean) / mean * 100;
} }
static calculateMean(samples: number[]) { static calculateMean(samples: number[]) {
let total = 0; var total = 0;
// TODO: use reduce // TODO: use reduce
samples.forEach(x => total += x); samples.forEach(x => total += x);
return total / samples.length; return total / samples.length;
} }
static calculateStandardDeviation(samples: number[], mean: number) { static calculateStandardDeviation(samples: number[], mean: number) {
let deviation = 0; var deviation = 0;
// TODO: use reduce // TODO: use reduce
samples.forEach(x => deviation += Math.pow(x - mean, 2)); samples.forEach(x => deviation += Math.pow(x - mean, 2));
deviation = deviation / (samples.length); deviation = deviation / (samples.length);
@ -30,9 +32,9 @@ export class Statistic {
static calculateRegressionSlope( static calculateRegressionSlope(
xValues: number[], xMean: number, yValues: number[], yMean: number) { xValues: number[], xMean: number, yValues: number[], yMean: number) {
// See http://en.wikipedia.org/wiki/Simple_linear_regression // See http://en.wikipedia.org/wiki/Simple_linear_regression
let dividendSum = 0; var dividendSum = 0;
let divisorSum = 0; var divisorSum = 0;
for (let i = 0; i < xValues.length; i++) { for (var i = 0; i < xValues.length; i++) {
dividendSum += (xValues[i] - xMean) * (yValues[i] - yMean); dividendSum += (xValues[i] - xMean) * (yValues[i] - yMean);
divisorSum += Math.pow(xValues[i] - xMean, 2); divisorSum += Math.pow(xValues[i] - xMean, 2);
} }

View File

@ -8,10 +8,13 @@
import {Inject, Injectable, OpaqueToken} from '@angular/core'; import {Inject, Injectable, OpaqueToken} from '@angular/core';
import {ListWrapper} from '../facade/collection';
import {MeasureValues} from '../measure_values'; import {MeasureValues} from '../measure_values';
import {Statistic} from '../statistic'; import {Statistic} from '../statistic';
import {Validator} from '../validator'; import {Validator} from '../validator';
/** /**
* A validator that checks the regression slope of a specific metric. * A validator that checks the regression slope of a specific metric.
* Waits for the regression slope to be >=0. * Waits for the regression slope to be >=0.
@ -37,17 +40,17 @@ export class RegressionSlopeValidator extends Validator {
validate(completeSample: MeasureValues[]): MeasureValues[] { validate(completeSample: MeasureValues[]): MeasureValues[] {
if (completeSample.length >= this._sampleSize) { if (completeSample.length >= this._sampleSize) {
const latestSample = var latestSample = ListWrapper.slice(
completeSample.slice(completeSample.length - this._sampleSize, completeSample.length); completeSample, completeSample.length - this._sampleSize, completeSample.length);
const xValues: number[] = []; var xValues: number[] = [];
const yValues: number[] = []; var yValues: number[] = [];
for (let i = 0; i < latestSample.length; i++) { for (var i = 0; i < latestSample.length; i++) {
// For now, we only use the array index as x value. // For now, we only use the array index as x value.
// TODO(tbosch): think about whether we should use time here instead // TODO(tbosch): think about whether we should use time here instead
xValues.push(i); xValues.push(i);
yValues.push(latestSample[i].values[this._metric]); yValues.push(latestSample[i].values[this._metric]);
} }
const regressionSlope = Statistic.calculateRegressionSlope( var regressionSlope = Statistic.calculateRegressionSlope(
xValues, Statistic.calculateMean(xValues), yValues, Statistic.calculateMean(yValues)); xValues, Statistic.calculateMean(xValues), yValues, Statistic.calculateMean(yValues));
return regressionSlope >= 0 ? latestSample : null; return regressionSlope >= 0 ? latestSample : null;
} else { } else {

View File

@ -8,9 +8,12 @@
import {Inject, Injectable, OpaqueToken} from '@angular/core'; import {Inject, Injectable, OpaqueToken} from '@angular/core';
import {ListWrapper} from '../facade/collection';
import {MeasureValues} from '../measure_values'; import {MeasureValues} from '../measure_values';
import {Validator} from '../validator'; import {Validator} from '../validator';
/** /**
* A validator that waits for the sample to have a certain size. * A validator that waits for the sample to have a certain size.
*/ */
@ -25,7 +28,8 @@ export class SizeValidator extends Validator {
validate(completeSample: MeasureValues[]): MeasureValues[] { validate(completeSample: MeasureValues[]): MeasureValues[] {
if (completeSample.length >= this._sampleSize) { if (completeSample.length >= this._sampleSize) {
return completeSample.slice(completeSample.length - this._sampleSize, completeSample.length); return ListWrapper.slice(
completeSample, completeSample.length - this._sampleSize, completeSample.length);
} else { } else {
return null; return null;
} }

View File

@ -34,7 +34,7 @@ export type PerfLogEvent = {
*/ */
export abstract class WebDriverExtension { export abstract class WebDriverExtension {
static provideFirstSupported(childTokens: any[]): any[] { static provideFirstSupported(childTokens: any[]): any[] {
const res = [ var res = [
{ {
provide: _CHILDREN, provide: _CHILDREN,
useFactory: (injector: Injector) => childTokens.map(token => injector.get(token)), useFactory: (injector: Injector) => childTokens.map(token => injector.get(token)),
@ -43,7 +43,7 @@ export abstract class WebDriverExtension {
{ {
provide: WebDriverExtension, provide: WebDriverExtension,
useFactory: (children: WebDriverExtension[], capabilities: {[key: string]: any}) => { useFactory: (children: WebDriverExtension[], capabilities: {[key: string]: any}) => {
let delegate: WebDriverExtension; var delegate: WebDriverExtension;
children.forEach(extension => { children.forEach(extension => {
if (extension.supports(capabilities)) { if (extension.supports(capabilities)) {
delegate = extension; delegate = extension;
@ -101,4 +101,4 @@ export class PerfLogFeatures {
} }
} }
const _CHILDREN = new OpaqueToken('WebDriverExtension.children'); var _CHILDREN = new OpaqueToken('WebDriverExtension.children');

View File

@ -34,7 +34,7 @@ export class ChromeDriverExtension extends WebDriverExtension {
if (!userAgent) { if (!userAgent) {
return -1; return -1;
} }
let v = userAgent.split(/Chrom(e|ium)\//g)[2]; var v = userAgent.split(/Chrom(e|ium)\//g)[2];
if (!v) { if (!v) {
return -1; return -1;
} }
@ -52,7 +52,7 @@ export class ChromeDriverExtension extends WebDriverExtension {
} }
timeEnd(name: string, restartName: string = null): Promise<any> { timeEnd(name: string, restartName: string = null): Promise<any> {
let script = `console.timeEnd('${name}');`; var script = `console.timeEnd('${name}');`;
if (restartName) { if (restartName) {
script += `console.time('${restartName}');`; script += `console.time('${restartName}');`;
} }
@ -67,9 +67,9 @@ export class ChromeDriverExtension extends WebDriverExtension {
return this._driver.executeScript('1+1') return this._driver.executeScript('1+1')
.then((_) => this._driver.logs('performance')) .then((_) => this._driver.logs('performance'))
.then((entries) => { .then((entries) => {
const events: PerfLogEvent[] = []; var events: PerfLogEvent[] = [];
entries.forEach(entry => { entries.forEach(entry => {
const message = JSON.parse(entry['message'])['message']; var message = JSON.parse(entry['message'])['message'];
if (message['method'] === 'Tracing.dataCollected') { if (message['method'] === 'Tracing.dataCollected') {
events.push(message['params']); events.push(message['params']);
} }
@ -95,8 +95,8 @@ export class ChromeDriverExtension extends WebDriverExtension {
} }
private _convertEvent(event: {[key: string]: any}, categories: string[]) { private _convertEvent(event: {[key: string]: any}, categories: string[]) {
const name = event['name']; var name = event['name'];
const args = event['args']; var args = event['args'];
if (this._isEvent(categories, name, ['blink.console'])) { if (this._isEvent(categories, name, ['blink.console'])) {
return normalizeEvent(event, {'name': name}); return normalizeEvent(event, {'name': name});
} else if (this._isEvent( } else if (this._isEvent(
@ -109,7 +109,7 @@ export class ChromeDriverExtension extends WebDriverExtension {
// new surfaces framework (not broadly enabled yet) // new surfaces framework (not broadly enabled yet)
// 3rd choice: BenchmarkInstrumentation::ImplThreadRenderingStats - fallback event that is // 3rd choice: BenchmarkInstrumentation::ImplThreadRenderingStats - fallback event that is
// always available if something is rendered // always available if something is rendered
const frameCount = event['args']['data']['frame_count']; var frameCount = event['args']['data']['frame_count'];
if (frameCount > 1) { if (frameCount > 1) {
throw new Error('multi-frame render stats not supported'); throw new Error('multi-frame render stats not supported');
} }
@ -122,14 +122,14 @@ export class ChromeDriverExtension extends WebDriverExtension {
categories, name, ['disabled-by-default-devtools.timeline'], 'CompositeLayers')) { categories, name, ['disabled-by-default-devtools.timeline'], 'CompositeLayers')) {
return normalizeEvent(event, {'name': 'render'}); return normalizeEvent(event, {'name': 'render'});
} else if (this._isEvent(categories, name, ['devtools.timeline', 'v8'], 'MajorGC')) { } else if (this._isEvent(categories, name, ['devtools.timeline', 'v8'], 'MajorGC')) {
const normArgs = { var normArgs = {
'majorGc': true, 'majorGc': true,
'usedHeapSize': args['usedHeapSizeAfter'] !== undefined ? args['usedHeapSizeAfter'] : 'usedHeapSize': args['usedHeapSizeAfter'] !== undefined ? args['usedHeapSizeAfter'] :
args['usedHeapSizeBefore'] args['usedHeapSizeBefore']
}; };
return normalizeEvent(event, {'name': 'gc', 'args': normArgs}); return normalizeEvent(event, {'name': 'gc', 'args': normArgs});
} else if (this._isEvent(categories, name, ['devtools.timeline', 'v8'], 'MinorGC')) { } else if (this._isEvent(categories, name, ['devtools.timeline', 'v8'], 'MinorGC')) {
const normArgs = { var normArgs = {
'majorGc': false, 'majorGc': false,
'usedHeapSize': args['usedHeapSizeAfter'] !== undefined ? args['usedHeapSizeAfter'] : 'usedHeapSize': args['usedHeapSizeAfter'] !== undefined ? args['usedHeapSizeAfter'] :
args['usedHeapSizeBefore'] args['usedHeapSizeBefore']
@ -151,11 +151,11 @@ export class ChromeDriverExtension extends WebDriverExtension {
this._isEvent(categories, name, ['devtools.timeline'], 'Paint')) { this._isEvent(categories, name, ['devtools.timeline'], 'Paint')) {
return normalizeEvent(event, {'name': 'render'}); return normalizeEvent(event, {'name': 'render'});
} else if (this._isEvent(categories, name, ['devtools.timeline'], 'ResourceReceivedData')) { } else if (this._isEvent(categories, name, ['devtools.timeline'], 'ResourceReceivedData')) {
const normArgs = {'encodedDataLength': args['data']['encodedDataLength']}; let normArgs = {'encodedDataLength': args['data']['encodedDataLength']};
return normalizeEvent(event, {'name': 'receivedData', 'args': normArgs}); return normalizeEvent(event, {'name': 'receivedData', 'args': normArgs});
} else if (this._isEvent(categories, name, ['devtools.timeline'], 'ResourceSendRequest')) { } else if (this._isEvent(categories, name, ['devtools.timeline'], 'ResourceSendRequest')) {
const data = args['data']; let data = args['data'];
const normArgs = {'url': data['url'], 'method': data['requestMethod']}; let normArgs = {'url': data['url'], 'method': data['requestMethod']};
return normalizeEvent(event, {'name': 'sendRequest', 'args': normArgs}); return normalizeEvent(event, {'name': 'sendRequest', 'args': normArgs});
} else if (this._isEvent(categories, name, ['blink.user_timing'], 'navigationStart')) { } else if (this._isEvent(categories, name, ['blink.user_timing'], 'navigationStart')) {
return normalizeEvent(event, {'name': 'navigationStart'}); return normalizeEvent(event, {'name': 'navigationStart'});
@ -168,7 +168,7 @@ export class ChromeDriverExtension extends WebDriverExtension {
private _isEvent( private _isEvent(
eventCategories: string[], eventName: string, expectedCategories: string[], eventCategories: string[], eventName: string, expectedCategories: string[],
expectedName: string = null): boolean { expectedName: string = null): boolean {
const hasCategories = expectedCategories.reduce( var hasCategories = expectedCategories.reduce(
(value, cat) => value && eventCategories.indexOf(cat) !== -1, true); (value, cat) => value && eventCategories.indexOf(cat) !== -1, true);
return !expectedName ? hasCategories : hasCategories && eventName === expectedName; return !expectedName ? hasCategories : hasCategories && eventName === expectedName;
} }
@ -183,7 +183,7 @@ export class ChromeDriverExtension extends WebDriverExtension {
} }
function normalizeEvent(chromeEvent: {[key: string]: any}, data: PerfLogEvent): PerfLogEvent { function normalizeEvent(chromeEvent: {[key: string]: any}, data: PerfLogEvent): PerfLogEvent {
let ph = chromeEvent['ph'].toUpperCase(); var ph = chromeEvent['ph'].toUpperCase();
if (ph === 'S') { if (ph === 'S') {
ph = 'B'; ph = 'B';
} else if (ph === 'F') { } else if (ph === 'F') {
@ -192,16 +192,16 @@ function normalizeEvent(chromeEvent: {[key: string]: any}, data: PerfLogEvent):
// mark events from navigation timing // mark events from navigation timing
ph = 'I'; ph = 'I';
} }
const result: {[key: string]: any} = var result: {[key: string]: any} =
{'pid': chromeEvent['pid'], 'ph': ph, 'cat': 'timeline', 'ts': chromeEvent['ts'] / 1000}; {'pid': chromeEvent['pid'], 'ph': ph, 'cat': 'timeline', 'ts': chromeEvent['ts'] / 1000};
if (ph === 'X') { if (ph === 'X') {
let dur = chromeEvent['dur']; var dur = chromeEvent['dur'];
if (dur === undefined) { if (dur === undefined) {
dur = chromeEvent['tdur']; dur = chromeEvent['tdur'];
} }
result['dur'] = !dur ? 0.0 : dur / 1000; result['dur'] = !dur ? 0.0 : dur / 1000;
} }
for (const prop in data) { for (let prop in data) {
result[prop] = data[prop]; result[prop] = data[prop];
} }
return result; return result;

View File

@ -8,7 +8,7 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {isPresent} from '../facade/lang'; import {StringWrapper, isPresent} from '../facade/lang';
import {WebDriverAdapter} from '../web_driver_adapter'; import {WebDriverAdapter} from '../web_driver_adapter';
import {PerfLogEvent, PerfLogFeatures, WebDriverExtension} from '../web_driver_extension'; import {PerfLogEvent, PerfLogFeatures, WebDriverExtension} from '../web_driver_extension';
@ -34,7 +34,7 @@ export class FirefoxDriverExtension extends WebDriverExtension {
} }
timeEnd(name: string, restartName: string = null): Promise<any> { timeEnd(name: string, restartName: string = null): Promise<any> {
let script = 'window.markEnd("' + name + '");'; var script = 'window.markEnd("' + name + '");';
if (isPresent(restartName)) { if (isPresent(restartName)) {
script += 'window.markStart("' + restartName + '");'; script += 'window.markStart("' + restartName + '");';
} }
@ -48,6 +48,6 @@ export class FirefoxDriverExtension extends WebDriverExtension {
perfLogFeatures(): PerfLogFeatures { return new PerfLogFeatures({render: true, gc: true}); } perfLogFeatures(): PerfLogFeatures { return new PerfLogFeatures({render: true, gc: true}); }
supports(capabilities: {[key: string]: any}): boolean { supports(capabilities: {[key: string]: any}): boolean {
return capabilities['browserName'].toLowerCase() === 'firefox'; return StringWrapper.equals(capabilities['browserName'].toLowerCase(), 'firefox');
} }
} }

View File

@ -8,7 +8,7 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {isBlank, isPresent} from '../facade/lang'; import {StringWrapper, isBlank, isPresent} from '../facade/lang';
import {WebDriverAdapter} from '../web_driver_adapter'; import {WebDriverAdapter} from '../web_driver_adapter';
import {PerfLogEvent, PerfLogFeatures, WebDriverExtension} from '../web_driver_extension'; import {PerfLogEvent, PerfLogFeatures, WebDriverExtension} from '../web_driver_extension';
@ -25,7 +25,7 @@ export class IOsDriverExtension extends WebDriverExtension {
} }
timeEnd(name: string, restartName: string = null): Promise<any> { timeEnd(name: string, restartName: string = null): Promise<any> {
let script = `console.timeEnd('${name}');`; var script = `console.timeEnd('${name}');`;
if (isPresent(restartName)) { if (isPresent(restartName)) {
script += `console.time('${restartName}');`; script += `console.time('${restartName}');`;
} }
@ -39,10 +39,10 @@ export class IOsDriverExtension extends WebDriverExtension {
return this._driver.executeScript('1+1') return this._driver.executeScript('1+1')
.then((_) => this._driver.logs('performance')) .then((_) => this._driver.logs('performance'))
.then((entries) => { .then((entries) => {
const records: any[] = []; var records: any[] = [];
entries.forEach(entry => { entries.forEach(entry => {
const message = JSON.parse(entry['message'])['message']; var message = JSON.parse(entry['message'])['message'];
if (message['method'] === 'Timeline.eventRecorded') { if (StringWrapper.equals(message['method'], 'Timeline.eventRecorded')) {
records.push(message['params']['record']); records.push(message['params']['record']);
} }
}); });
@ -56,22 +56,25 @@ export class IOsDriverExtension extends WebDriverExtension {
events = []; events = [];
} }
records.forEach((record) => { records.forEach((record) => {
let endEvent: PerfLogEvent = null; var endEvent: PerfLogEvent = null;
const type = record['type']; var type = record['type'];
const data = record['data']; var data = record['data'];
const startTime = record['startTime']; var startTime = record['startTime'];
const endTime = record['endTime']; var endTime = record['endTime'];
if (type === 'FunctionCall' && (data == null || data['scriptName'] !== 'InjectedScript')) { if (StringWrapper.equals(type, 'FunctionCall') &&
(isBlank(data) || !StringWrapper.equals(data['scriptName'], 'InjectedScript'))) {
events.push(createStartEvent('script', startTime)); events.push(createStartEvent('script', startTime));
endEvent = createEndEvent('script', endTime); endEvent = createEndEvent('script', endTime);
} else if (type === 'Time') { } else if (StringWrapper.equals(type, 'Time')) {
events.push(createMarkStartEvent(data['message'], startTime)); events.push(createMarkStartEvent(data['message'], startTime));
} else if (type === 'TimeEnd') { } else if (StringWrapper.equals(type, 'TimeEnd')) {
events.push(createMarkEndEvent(data['message'], startTime)); events.push(createMarkEndEvent(data['message'], startTime));
} else if ( } else if (
type === 'RecalculateStyles' || type === 'Layout' || type === 'UpdateLayerTree' || StringWrapper.equals(type, 'RecalculateStyles') || StringWrapper.equals(type, 'Layout') ||
type === 'Paint' || type === 'Rasterize' || type === 'CompositeLayers') { StringWrapper.equals(type, 'UpdateLayerTree') || StringWrapper.equals(type, 'Paint') ||
StringWrapper.equals(type, 'Rasterize') ||
StringWrapper.equals(type, 'CompositeLayers')) {
events.push(createStartEvent('render', startTime)); events.push(createStartEvent('render', startTime));
endEvent = createEndEvent('render', endTime); endEvent = createEndEvent('render', endTime);
} }
@ -89,13 +92,13 @@ export class IOsDriverExtension extends WebDriverExtension {
perfLogFeatures(): PerfLogFeatures { return new PerfLogFeatures({render: true}); } perfLogFeatures(): PerfLogFeatures { return new PerfLogFeatures({render: true}); }
supports(capabilities: {[key: string]: any}): boolean { supports(capabilities: {[key: string]: any}): boolean {
return capabilities['browserName'].toLowerCase() === 'safari'; return StringWrapper.equals(capabilities['browserName'].toLowerCase(), 'safari');
} }
} }
function createEvent( function createEvent(
ph: 'X' | 'B' | 'E' | 'B' | 'E', name: string, time: number, args: any = null) { ph: 'X' | 'B' | 'E' | 'B' | 'E', name: string, time: number, args: any = null) {
const result: PerfLogEvent = { var result: PerfLogEvent = {
'cat': 'timeline', 'cat': 'timeline',
'name': name, 'name': name,
'ts': time, 'ts': time,

View File

@ -8,7 +8,7 @@
require('core-js'); require('core-js');
require('reflect-metadata'); require('reflect-metadata');
const testHelper = require('../../src/firefox_extension/lib/test_helper.js'); var testHelper = require('../../src/firefox_extension/lib/test_helper.js');
exports.config = { exports.config = {
specs: ['spec.js', 'sample_benchmark.js'], specs: ['spec.js', 'sample_benchmark.js'],

View File

@ -10,10 +10,10 @@ import {convertPerfProfileToEvents} from '../../src/firefox_extension/lib/parser
function assertEventsEqual(actualEvents: any[], expectedEvents: any[]) { function assertEventsEqual(actualEvents: any[], expectedEvents: any[]) {
expect(actualEvents.length == expectedEvents.length); expect(actualEvents.length == expectedEvents.length);
for (let i = 0; i < actualEvents.length; ++i) { for (var i = 0; i < actualEvents.length; ++i) {
const actualEvent = actualEvents[i]; var actualEvent = actualEvents[i];
const expectedEvent = expectedEvents[i]; var expectedEvent = expectedEvents[i];
for (const key in actualEvent) { for (var key in actualEvent) {
expect(actualEvent[key]).toEqual(expectedEvent[key]); expect(actualEvent[key]).toEqual(expectedEvent[key]);
} }
} }
@ -22,17 +22,17 @@ function assertEventsEqual(actualEvents: any[], expectedEvents: any[]) {
export function main() { export function main() {
describe('convertPerfProfileToEvents', function() { describe('convertPerfProfileToEvents', function() {
it('should convert single instantaneous event', function() { it('should convert single instantaneous event', function() {
const profileData = { var profileData = {
threads: [ threads: [
{samples: [{time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]}]} {samples: [{time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]}]}
] ]
}; };
const perfEvents = convertPerfProfileToEvents(profileData); var perfEvents = convertPerfProfileToEvents(profileData);
assertEventsEqual(perfEvents, [{ph: 'X', ts: 1, name: 'script'}]); assertEventsEqual(perfEvents, [{ph: 'X', ts: 1, name: 'script'}]);
}); });
it('should convert single non-instantaneous event', function() { it('should convert single non-instantaneous event', function() {
const profileData = { var profileData = {
threads: [{ threads: [{
samples: [ samples: [
{time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]}, {time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]},
@ -41,13 +41,13 @@ export function main() {
] ]
}] }]
}; };
const perfEvents = convertPerfProfileToEvents(profileData); var perfEvents = convertPerfProfileToEvents(profileData);
assertEventsEqual( assertEventsEqual(
perfEvents, [{ph: 'B', ts: 1, name: 'script'}, {ph: 'E', ts: 100, name: 'script'}]); perfEvents, [{ph: 'B', ts: 1, name: 'script'}, {ph: 'E', ts: 100, name: 'script'}]);
}); });
it('should convert multiple instantaneous events', function() { it('should convert multiple instantaneous events', function() {
const profileData = { var profileData = {
threads: [{ threads: [{
samples: [ samples: [
{time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]}, {time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]},
@ -55,13 +55,13 @@ export function main() {
] ]
}] }]
}; };
const perfEvents = convertPerfProfileToEvents(profileData); var perfEvents = convertPerfProfileToEvents(profileData);
assertEventsEqual( assertEventsEqual(
perfEvents, [{ph: 'X', ts: 1, name: 'script'}, {ph: 'X', ts: 2, name: 'render'}]); perfEvents, [{ph: 'X', ts: 1, name: 'script'}, {ph: 'X', ts: 2, name: 'render'}]);
}); });
it('should convert multiple mixed events', function() { it('should convert multiple mixed events', function() {
const profileData = { var profileData = {
threads: [{ threads: [{
samples: [ samples: [
{time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]}, {time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]},
@ -71,7 +71,7 @@ export function main() {
] ]
}] }]
}; };
const perfEvents = convertPerfProfileToEvents(profileData); var perfEvents = convertPerfProfileToEvents(profileData);
assertEventsEqual(perfEvents, [ assertEventsEqual(perfEvents, [
{ph: 'X', ts: 1, name: 'script'}, {ph: 'X', ts: 2, name: 'render'}, {ph: 'X', ts: 1, name: 'script'}, {ph: 'X', ts: 2, name: 'render'},
{ph: 'B', ts: 5, name: 'script'}, {ph: 'E', ts: 10, name: 'script'} {ph: 'B', ts: 5, name: 'script'}, {ph: 'E', ts: 10, name: 'script'}
@ -79,13 +79,13 @@ export function main() {
}); });
it('should add args to gc events', function() { it('should add args to gc events', function() {
const profileData = {threads: [{samples: [{time: 1, frames: [{location: 'forceGC'}]}]}]}; var profileData = {threads: [{samples: [{time: 1, frames: [{location: 'forceGC'}]}]}]};
const perfEvents = convertPerfProfileToEvents(profileData); var perfEvents = convertPerfProfileToEvents(profileData);
assertEventsEqual(perfEvents, [{ph: 'X', ts: 1, name: 'gc', args: {usedHeapSize: 0}}]); assertEventsEqual(perfEvents, [{ph: 'X', ts: 1, name: 'gc', args: {usedHeapSize: 0}}]);
}); });
it('should skip unknown events', function() { it('should skip unknown events', function() {
const profileData = { var profileData = {
threads: [{ threads: [{
samples: [ samples: [
{time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]}, {time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]},
@ -93,7 +93,7 @@ export function main() {
] ]
}] }]
}; };
const perfEvents = convertPerfProfileToEvents(profileData); var perfEvents = convertPerfProfileToEvents(profileData);
assertEventsEqual(perfEvents, [{ph: 'X', ts: 1, name: 'script'}]); assertEventsEqual(perfEvents, [{ph: 'X', ts: 1, name: 'script'}]);
}); });
}); });

View File

@ -6,10 +6,8 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {$, browser} from 'protractor'; var benchpress = require('../../index.js');
var runner = new benchpress.Runner([
const benchpress = require('../../index.js');
const runner = new benchpress.Runner([
// use protractor as Webdriver client // use protractor as Webdriver client
benchpress.SeleniumWebDriverAdapter.PROTRACTOR_PROVIDERS, benchpress.SeleniumWebDriverAdapter.PROTRACTOR_PROVIDERS,
// use RegressionSlopeValidator to validate samples // use RegressionSlopeValidator to validate samples

View File

@ -6,12 +6,9 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
/* tslint:disable:no-console */ var assertEventsContainsName = function(events: any[], eventName: string) {
import {browser} from 'protractor'; var found = false;
for (var i = 0; i < events.length; ++i) {
const assertEventsContainsName = function(events: any[], eventName: string) {
let found = false;
for (let i = 0; i < events.length; ++i) {
if (events[i].name == eventName) { if (events[i].name == eventName) {
found = true; found = true;
break; break;
@ -21,7 +18,7 @@ const assertEventsContainsName = function(events: any[], eventName: string) {
}; };
describe('firefox extension', function() { describe('firefox extension', function() {
const TEST_URL = 'http://localhost:8001/playground/src/hello_world/index.html'; var TEST_URL = 'http://localhost:8001/playground/src/hello_world/index.html';
it('should measure performance', function() { it('should measure performance', function() {
browser.sleep(3000); // wait for extension to load browser.sleep(3000); // wait for extension to load

View File

@ -11,7 +11,7 @@ import {Metric, MultiMetric, ReflectiveInjector} from '../../index';
export function main() { export function main() {
function createMetric(ids: any[]) { function createMetric(ids: any[]) {
const m = ReflectiveInjector var m = ReflectiveInjector
.resolveAndCreate([ .resolveAndCreate([
ids.map(id => ({provide: id, useValue: new MockMetric(id)})), ids.map(id => ({provide: id, useValue: new MockMetric(id)})),
MultiMetric.provideWith(ids) MultiMetric.provideWith(ids)
@ -56,13 +56,13 @@ class MockMetric extends Metric {
beginMeasure(): Promise<string> { return Promise.resolve(`${this._id}_beginMeasure`); } beginMeasure(): Promise<string> { return Promise.resolve(`${this._id}_beginMeasure`); }
endMeasure(restart: boolean): Promise<{[key: string]: any}> { endMeasure(restart: boolean): Promise<{[key: string]: any}> {
const result: {[key: string]: any} = {}; var result: {[key: string]: any} = {};
result[this._id] = {'restart': restart}; result[this._id] = {'restart': restart};
return Promise.resolve(result); return Promise.resolve(result);
} }
describe(): {[key: string]: string} { describe(): {[key: string]: string} {
const result: {[key: string]: string} = {}; var result: {[key: string]: string} = {};
result[this._id] = 'describe'; result[this._id] = 'describe';
return result; return result;
} }

View File

@ -14,8 +14,8 @@ import {isPresent} from '../../src/facade/lang';
import {TraceEventFactory} from '../trace_event_factory'; import {TraceEventFactory} from '../trace_event_factory';
export function main() { export function main() {
let commandLog: any[]; var commandLog: any[];
const eventFactory = new TraceEventFactory('timeline', 'pid0'); var eventFactory = new TraceEventFactory('timeline', 'pid0');
function createMetric( function createMetric(
perfLogs: PerfLogEvent[], perfLogFeatures: PerfLogFeatures, perfLogs: PerfLogEvent[], perfLogFeatures: PerfLogFeatures,
@ -34,7 +34,7 @@ export function main() {
if (!microMetrics) { if (!microMetrics) {
microMetrics = {}; microMetrics = {};
} }
const providers: Provider[] = [ var providers: Provider[] = [
Options.DEFAULT_PROVIDERS, PerflogMetric.PROVIDERS, Options.DEFAULT_PROVIDERS, PerflogMetric.PROVIDERS,
{provide: Options.MICRO_METRICS, useValue: microMetrics}, { {provide: Options.MICRO_METRICS, useValue: microMetrics}, {
provide: PerflogMetric.SET_TIMEOUT, provide: PerflogMetric.SET_TIMEOUT,
@ -66,7 +66,7 @@ export function main() {
describe('perflog metric', () => { describe('perflog metric', () => {
function sortedKeys(stringMap: {[key: string]: any}) { function sortedKeys(stringMap: {[key: string]: any}) {
const res: string[] = []; var res: string[] = [];
res.push(...Object.keys(stringMap)); res.push(...Object.keys(stringMap));
res.sort(); res.sort();
return res; return res;
@ -102,13 +102,13 @@ export function main() {
}); });
it('should describe itself based on micro metrics', () => { it('should describe itself based on micro metrics', () => {
const description = var description =
createMetric([[]], null, {microMetrics: {'myMicroMetric': 'someDesc'}}).describe(); createMetric([[]], null, {microMetrics: {'myMicroMetric': 'someDesc'}}).describe();
expect(description['myMicroMetric']).toEqual('someDesc'); expect(description['myMicroMetric']).toEqual('someDesc');
}); });
it('should describe itself if frame capture is requested and available', () => { it('should describe itself if frame capture is requested and available', () => {
const description = createMetric([[]], new PerfLogFeatures({frameCapture: true}), { var description = createMetric([[]], new PerfLogFeatures({frameCapture: true}), {
captureFrames: true captureFrames: true
}).describe(); }).describe();
expect(description['frameTime.mean']).not.toContain('WARNING'); expect(description['frameTime.mean']).not.toContain('WARNING');
@ -118,7 +118,7 @@ export function main() {
}); });
it('should describe itself if frame capture is requested and not available', () => { it('should describe itself if frame capture is requested and not available', () => {
const description = createMetric([[]], new PerfLogFeatures({frameCapture: false}), { var description = createMetric([[]], new PerfLogFeatures({frameCapture: false}), {
captureFrames: true captureFrames: true
}).describe(); }).describe();
expect(description['frameTime.mean']).toContain('WARNING'); expect(description['frameTime.mean']).toContain('WARNING');
@ -131,7 +131,7 @@ export function main() {
it('should not force gc and mark the timeline', it('should not force gc and mark the timeline',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const metric = createMetric([[]], null); var metric = createMetric([[]], null);
metric.beginMeasure().then((_) => { metric.beginMeasure().then((_) => {
expect(commandLog).toEqual([['timeBegin', 'benchpress0']]); expect(commandLog).toEqual([['timeBegin', 'benchpress0']]);
@ -141,7 +141,7 @@ export function main() {
it('should force gc and mark the timeline', it('should force gc and mark the timeline',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const metric = createMetric([[]], null, {forceGc: true}); var metric = createMetric([[]], null, {forceGc: true});
metric.beginMeasure().then((_) => { metric.beginMeasure().then((_) => {
expect(commandLog).toEqual([['gc'], ['timeBegin', 'benchpress0']]); expect(commandLog).toEqual([['gc'], ['timeBegin', 'benchpress0']]);
@ -155,11 +155,11 @@ export function main() {
it('should mark and aggregate events in between the marks', it('should mark and aggregate events in between the marks',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const events = [[ var events = [[
eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 4), eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 4),
eventFactory.end('script', 6), eventFactory.markEnd('benchpress0', 10) eventFactory.end('script', 6), eventFactory.markEnd('benchpress0', 10)
]]; ]];
const metric = createMetric(events, null); var metric = createMetric(events, null);
metric.beginMeasure().then((_) => metric.endMeasure(false)).then((data) => { metric.beginMeasure().then((_) => metric.endMeasure(false)).then((data) => {
expect(commandLog).toEqual([ expect(commandLog).toEqual([
['timeBegin', 'benchpress0'], ['timeEnd', 'benchpress0', null], 'readPerfLog' ['timeBegin', 'benchpress0'], ['timeEnd', 'benchpress0', null], 'readPerfLog'
@ -172,13 +172,13 @@ export function main() {
it('should mark and aggregate events since navigationStart', it('should mark and aggregate events since navigationStart',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const events = [[ var events = [[
eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 4), eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 4),
eventFactory.end('script', 6), eventFactory.instant('navigationStart', 7), eventFactory.end('script', 6), eventFactory.instant('navigationStart', 7),
eventFactory.start('script', 8), eventFactory.end('script', 9), eventFactory.start('script', 8), eventFactory.end('script', 9),
eventFactory.markEnd('benchpress0', 10) eventFactory.markEnd('benchpress0', 10)
]]; ]];
const metric = createMetric(events, null); var metric = createMetric(events, null);
metric.beginMeasure().then((_) => metric.endMeasure(false)).then((data) => { metric.beginMeasure().then((_) => metric.endMeasure(false)).then((data) => {
expect(data['scriptTime']).toBe(1); expect(data['scriptTime']).toBe(1);
@ -187,7 +187,7 @@ export function main() {
})); }));
it('should restart timing', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { it('should restart timing', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const events = [ var events = [
[ [
eventFactory.markStart('benchpress0', 0), eventFactory.markStart('benchpress0', 0),
eventFactory.markEnd('benchpress0', 1), eventFactory.markEnd('benchpress0', 1),
@ -195,7 +195,7 @@ export function main() {
], ],
[eventFactory.markEnd('benchpress1', 3)] [eventFactory.markEnd('benchpress1', 3)]
]; ];
const metric = createMetric(events, null); var metric = createMetric(events, null);
metric.beginMeasure() metric.beginMeasure()
.then((_) => metric.endMeasure(true)) .then((_) => metric.endMeasure(true))
.then((_) => metric.endMeasure(true)) .then((_) => metric.endMeasure(true))
@ -211,7 +211,7 @@ export function main() {
it('should loop and aggregate until the end mark is present', it('should loop and aggregate until the end mark is present',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const events = [ var events = [
[eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 1)], [eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 1)],
[eventFactory.end('script', 2)], [eventFactory.end('script', 2)],
[ [
@ -219,7 +219,7 @@ export function main() {
eventFactory.markEnd('benchpress0', 10) eventFactory.markEnd('benchpress0', 10)
] ]
]; ];
const metric = createMetric(events, null); var metric = createMetric(events, null);
metric.beginMeasure().then((_) => metric.endMeasure(false)).then((data) => { metric.beginMeasure().then((_) => metric.endMeasure(false)).then((data) => {
expect(commandLog).toEqual([ expect(commandLog).toEqual([
['timeBegin', 'benchpress0'], ['timeEnd', 'benchpress0', null], 'readPerfLog', ['timeBegin', 'benchpress0'], ['timeEnd', 'benchpress0', null], 'readPerfLog',
@ -233,7 +233,7 @@ export function main() {
it('should store events after the end mark for the next call', it('should store events after the end mark for the next call',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const events = [ var events = [
[ [
eventFactory.markStart('benchpress0', 0), eventFactory.markEnd('benchpress0', 1), eventFactory.markStart('benchpress0', 0), eventFactory.markEnd('benchpress0', 1),
eventFactory.markStart('benchpress1', 1), eventFactory.start('script', 1), eventFactory.markStart('benchpress1', 1), eventFactory.start('script', 1),
@ -244,7 +244,7 @@ export function main() {
eventFactory.markEnd('benchpress1', 6) eventFactory.markEnd('benchpress1', 6)
] ]
]; ];
const metric = createMetric(events, null); var metric = createMetric(events, null);
metric.beginMeasure() metric.beginMeasure()
.then((_) => metric.endMeasure(true)) .then((_) => metric.endMeasure(true))
.then((data) => { .then((data) => {
@ -263,7 +263,7 @@ export function main() {
})); }));
describe('with forced gc', () => { describe('with forced gc', () => {
let events: PerfLogEvent[][]; var events: PerfLogEvent[][];
beforeEach(() => { beforeEach(() => {
events = [[ events = [[
eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 4), eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 4),
@ -276,7 +276,7 @@ export function main() {
}); });
it('should measure forced gc', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { it('should measure forced gc', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const metric = createMetric(events, null, {forceGc: true}); var metric = createMetric(events, null, {forceGc: true});
metric.beginMeasure().then((_) => metric.endMeasure(false)).then((data) => { metric.beginMeasure().then((_) => metric.endMeasure(false)).then((data) => {
expect(commandLog).toEqual([ expect(commandLog).toEqual([
['gc'], ['timeBegin', 'benchpress0'], ['timeEnd', 'benchpress0', 'benchpress1'], ['gc'], ['timeBegin', 'benchpress0'], ['timeEnd', 'benchpress0', 'benchpress1'],
@ -291,7 +291,7 @@ export function main() {
it('should restart after the forced gc if needed', it('should restart after the forced gc if needed',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const metric = createMetric(events, null, {forceGc: true}); var metric = createMetric(events, null, {forceGc: true});
metric.beginMeasure().then((_) => metric.endMeasure(true)).then((data) => { metric.beginMeasure().then((_) => metric.endMeasure(true)).then((data) => {
expect(commandLog[5]).toEqual(['timeEnd', 'benchpress1', 'benchpress2']); expect(commandLog[5]).toEqual(['timeEnd', 'benchpress1', 'benchpress2']);
@ -313,7 +313,7 @@ export function main() {
} = {}) { } = {}) {
events.unshift(eventFactory.markStart('benchpress0', 0)); events.unshift(eventFactory.markStart('benchpress0', 0));
events.push(eventFactory.markEnd('benchpress0', 10)); events.push(eventFactory.markEnd('benchpress0', 10));
const metric = createMetric([events], null, { var metric = createMetric([events], null, {
microMetrics: microMetrics, microMetrics: microMetrics,
captureFrames: captureFrames, captureFrames: captureFrames,
receivedData: receivedData, receivedData: receivedData,
@ -502,8 +502,8 @@ export function main() {
it('should ignore events from different processed as the start mark', it('should ignore events from different processed as the start mark',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const otherProcessEventFactory = new TraceEventFactory('timeline', 'pid1'); var otherProcessEventFactory = new TraceEventFactory('timeline', 'pid1');
const metric = createMetric( var metric = createMetric(
[[ [[
eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 0, null), eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 0, null),
eventFactory.end('script', 5, null), eventFactory.end('script', 5, null),
@ -685,7 +685,7 @@ class MockDriverExtension extends WebDriverExtension {
readPerfLog(): Promise<any> { readPerfLog(): Promise<any> {
this._commandLog.push('readPerfLog'); this._commandLog.push('readPerfLog');
if (this._perfLogs.length > 0) { if (this._perfLogs.length > 0) {
const next = this._perfLogs[0]; var next = this._perfLogs[0];
this._perfLogs.shift(); this._perfLogs.shift();
return Promise.resolve(next); return Promise.resolve(next);
} else { } else {

View File

@ -12,7 +12,7 @@ import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/te
import {Options, PerfLogEvent, PerfLogFeatures, UserMetric, WebDriverAdapter} from '../../index'; import {Options, PerfLogEvent, PerfLogFeatures, UserMetric, WebDriverAdapter} from '../../index';
export function main() { export function main() {
let wdAdapter: MockDriverAdapter; var wdAdapter: MockDriverAdapter;
function createMetric( function createMetric(
perfLogs: PerfLogEvent[], perfLogFeatures: PerfLogFeatures, perfLogs: PerfLogEvent[], perfLogFeatures: PerfLogFeatures,
@ -25,7 +25,7 @@ export function main() {
userMetrics = {}; userMetrics = {};
} }
wdAdapter = new MockDriverAdapter(); wdAdapter = new MockDriverAdapter();
const providers: Provider[] = [ var providers: Provider[] = [
Options.DEFAULT_PROVIDERS, UserMetric.PROVIDERS, Options.DEFAULT_PROVIDERS, UserMetric.PROVIDERS,
{provide: Options.USER_METRICS, useValue: userMetrics}, {provide: Options.USER_METRICS, useValue: userMetrics},
{provide: WebDriverAdapter, useValue: wdAdapter} {provide: WebDriverAdapter, useValue: wdAdapter}
@ -45,7 +45,7 @@ export function main() {
describe('endMeasure', () => { describe('endMeasure', () => {
it('should stop measuring when all properties have numeric values', it('should stop measuring when all properties have numeric values',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const metric = createMetric( let metric = createMetric(
[[]], new PerfLogFeatures(), [[]], new PerfLogFeatures(),
{userMetrics: {'loadTime': 'time to load', 'content': 'time to see content'}}); {userMetrics: {'loadTime': 'time to load', 'content': 'time to see content'}});
metric.beginMeasure() metric.beginMeasure()
@ -71,7 +71,7 @@ class MockDriverAdapter extends WebDriverAdapter {
executeScript(script: string): any { executeScript(script: string): any {
// Just handles `return window.propName` ignores `delete window.propName`. // Just handles `return window.propName` ignores `delete window.propName`.
if (script.indexOf('return window.') == 0) { if (script.indexOf('return window.') == 0) {
const metricName = script.substring('return window.'.length); let metricName = script.substring('return window.'.length);
return Promise.resolve(this.data[metricName]); return Promise.resolve(this.data[metricName]);
} else if (script.indexOf('delete window.') == 0) { } else if (script.indexOf('delete window.') == 0) {
return Promise.resolve(null); return Promise.resolve(null);

View File

@ -14,8 +14,8 @@ import {isBlank, isPresent} from '../../src/facade/lang';
export function main() { export function main() {
describe('console reporter', () => { describe('console reporter', () => {
let reporter: ConsoleReporter; var reporter: ConsoleReporter;
let log: string[]; var log: string[];
function createReporter( function createReporter(
{columnWidth = null, sampleId = null, descriptions = null, metrics = null}: { {columnWidth = null, sampleId = null, descriptions = null, metrics = null}: {
@ -28,10 +28,10 @@ export function main() {
if (!descriptions) { if (!descriptions) {
descriptions = []; descriptions = [];
} }
if (sampleId == null) { if (isBlank(sampleId)) {
sampleId = 'null'; sampleId = 'null';
} }
const providers: Provider[] = [ var providers: Provider[] = [
ConsoleReporter.PROVIDERS, { ConsoleReporter.PROVIDERS, {
provide: SampleDescription, provide: SampleDescription,
useValue: new SampleDescription(sampleId, descriptions, metrics) useValue: new SampleDescription(sampleId, descriptions, metrics)

View File

@ -9,11 +9,11 @@
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal';
import {JsonFileReporter, MeasureValues, Options, ReflectiveInjector, SampleDescription} from '../../index'; import {JsonFileReporter, MeasureValues, Options, ReflectiveInjector, SampleDescription} from '../../index';
import {isPresent} from '../../src/facade/lang'; import {Json, isPresent} from '../../src/facade/lang';
export function main() { export function main() {
describe('file reporter', () => { describe('file reporter', () => {
let loggedFile: any; var loggedFile: any;
function createReporter({sampleId, descriptions, metrics, path}: { function createReporter({sampleId, descriptions, metrics, path}: {
sampleId: string, sampleId: string,
@ -21,7 +21,7 @@ export function main() {
metrics: {[key: string]: string}, metrics: {[key: string]: string},
path: string path: string
}) { }) {
const providers = [ var providers = [
JsonFileReporter.PROVIDERS, { JsonFileReporter.PROVIDERS, {
provide: SampleDescription, provide: SampleDescription,
useValue: new SampleDescription(sampleId, descriptions, metrics) useValue: new SampleDescription(sampleId, descriptions, metrics)
@ -49,9 +49,9 @@ export function main() {
.reportSample( .reportSample(
[mv(0, 0, {'a': 3, 'b': 6})], [mv(0, 0, {'a': 3, 'b': 6})],
[mv(0, 0, {'a': 3, 'b': 6}), mv(1, 1, {'a': 5, 'b': 9})]); [mv(0, 0, {'a': 3, 'b': 6}), mv(1, 1, {'a': 5, 'b': 9})]);
const regExp = /somePath\/someId_\d+\.json/; var regExp = /somePath\/someId_\d+\.json/;
expect(isPresent(loggedFile['filename'].match(regExp))).toBe(true); expect(isPresent(loggedFile['filename'].match(regExp))).toBe(true);
const parsedContent = JSON.parse(loggedFile['content']); var parsedContent = Json.parse(loggedFile['content']);
expect(parsedContent).toEqual({ expect(parsedContent).toEqual({
'description': { 'description': {
'id': 'someId', 'id': 'someId',

View File

@ -12,7 +12,7 @@ import {MeasureValues, MultiReporter, ReflectiveInjector, Reporter} from '../../
export function main() { export function main() {
function createReporters(ids: any[]) { function createReporters(ids: any[]) {
const r = ReflectiveInjector var r = ReflectiveInjector
.resolveAndCreate([ .resolveAndCreate([
ids.map(id => ({provide: id, useValue: new MockReporter(id)})), ids.map(id => ({provide: id, useValue: new MockReporter(id)})),
MultiReporter.provideWith(ids) MultiReporter.provideWith(ids)
@ -25,7 +25,7 @@ export function main() {
it('should reportMeasureValues to all', it('should reportMeasureValues to all',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const mv = new MeasureValues(0, new Date(), {}); var mv = new MeasureValues(0, new Date(), {});
createReporters(['m1', 'm2']).then((r) => r.reportMeasureValues(mv)).then((values) => { createReporters(['m1', 'm2']).then((r) => r.reportMeasureValues(mv)).then((values) => {
expect(values).toEqual([{'id': 'm1', 'values': mv}, {'id': 'm2', 'values': mv}]); expect(values).toEqual([{'id': 'm1', 'values': mv}, {'id': 'm2', 'values': mv}]);
@ -34,9 +34,9 @@ export function main() {
})); }));
it('should reportSample to call', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { it('should reportSample to call', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const completeSample = var completeSample =
[new MeasureValues(0, new Date(), {}), new MeasureValues(1, new Date(), {})]; [new MeasureValues(0, new Date(), {}), new MeasureValues(1, new Date(), {})];
const validSample = [completeSample[1]]; var validSample = [completeSample[1]];
createReporters(['m1', 'm2']) createReporters(['m1', 'm2'])
.then((r) => r.reportSample(completeSample, validSample)) .then((r) => r.reportSample(completeSample, validSample))

View File

@ -12,8 +12,8 @@ import {Injector, Metric, Options, ReflectiveInjector, Runner, SampleDescription
export function main() { export function main() {
describe('runner', () => { describe('runner', () => {
let injector: ReflectiveInjector; var injector: ReflectiveInjector;
let runner: Runner; var runner: Runner;
function createRunner(defaultProviders: any[] = null): Runner { function createRunner(defaultProviders: any[] = null): Runner {
if (!defaultProviders) { if (!defaultProviders) {
@ -76,7 +76,7 @@ export function main() {
it('should provide Options.EXECUTE', it('should provide Options.EXECUTE',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const execute = () => {}; var execute = () => {};
createRunner().sample({id: 'someId', execute: execute}).then((_) => { createRunner().sample({id: 'someId', execute: execute}).then((_) => {
expect(injector.get(Options.EXECUTE)).toEqual(execute); expect(injector.get(Options.EXECUTE)).toEqual(execute);
async.done(); async.done();
@ -85,7 +85,7 @@ export function main() {
it('should provide Options.PREPARE', it('should provide Options.PREPARE',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const prepare = () => {}; var prepare = () => {};
createRunner().sample({id: 'someId', prepare: prepare}).then((_) => { createRunner().sample({id: 'someId', prepare: prepare}).then((_) => {
expect(injector.get(Options.PREPARE)).toEqual(prepare); expect(injector.get(Options.PREPARE)).toEqual(prepare);
async.done(); async.done();

View File

@ -12,10 +12,10 @@ import {MeasureValues, Metric, Options, ReflectiveInjector, Reporter, Sampler, V
import {isBlank, isPresent} from '../src/facade/lang'; import {isBlank, isPresent} from '../src/facade/lang';
export function main() { export function main() {
const EMPTY_EXECUTE = () => {}; var EMPTY_EXECUTE = () => {};
describe('sampler', () => { describe('sampler', () => {
let sampler: Sampler; var sampler: Sampler;
function createSampler({driver, metric, reporter, validator, prepare, execute}: { function createSampler({driver, metric, reporter, validator, prepare, execute}: {
driver?: any, driver?: any,
@ -25,7 +25,7 @@ export function main() {
prepare?: any, prepare?: any,
execute?: any execute?: any
} = {}) { } = {}) {
let time = 1000; var time = 1000;
if (!metric) { if (!metric) {
metric = new MockMetric([]); metric = new MockMetric([]);
} }
@ -35,7 +35,7 @@ export function main() {
if (isBlank(driver)) { if (isBlank(driver)) {
driver = new MockDriverAdapter([]); driver = new MockDriverAdapter([]);
} }
const providers = [ var providers = [
Options.DEFAULT_PROVIDERS, Sampler.PROVIDERS, {provide: Metric, useValue: metric}, Options.DEFAULT_PROVIDERS, Sampler.PROVIDERS, {provide: Metric, useValue: metric},
{provide: Reporter, useValue: reporter}, {provide: WebDriverAdapter, useValue: driver}, {provide: Reporter, useValue: reporter}, {provide: WebDriverAdapter, useValue: driver},
{provide: Options.EXECUTE, useValue: execute}, {provide: Validator, useValue: validator}, {provide: Options.EXECUTE, useValue: execute}, {provide: Validator, useValue: validator},
@ -50,10 +50,10 @@ export function main() {
it('should call the prepare and execute callbacks using WebDriverAdapter.waitFor', it('should call the prepare and execute callbacks using WebDriverAdapter.waitFor',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const log: any[] = []; var log: any[] = [];
let count = 0; var count = 0;
const driver = new MockDriverAdapter([], (callback: Function) => { var driver = new MockDriverAdapter([], (callback: Function) => {
const result = callback(); var result = callback();
log.push(result); log.push(result);
return Promise.resolve(result); return Promise.resolve(result);
}); });
@ -73,8 +73,8 @@ export function main() {
it('should call prepare, beginMeasure, execute, endMeasure for every iteration', it('should call prepare, beginMeasure, execute, endMeasure for every iteration',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
let workCount = 0; var workCount = 0;
const log: any[] = []; var log: any[] = [];
createSampler({ createSampler({
metric: createCountingMetric(log), metric: createCountingMetric(log),
validator: createCountingValidator(2), validator: createCountingValidator(2),
@ -98,8 +98,8 @@ export function main() {
it('should call execute, endMeasure for every iteration if there is no prepare callback', it('should call execute, endMeasure for every iteration if there is no prepare callback',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const log: any[] = []; var log: any[] = [];
let workCount = 0; var workCount = 0;
createSampler({ createSampler({
metric: createCountingMetric(log), metric: createCountingMetric(log),
validator: createCountingValidator(2), validator: createCountingValidator(2),
@ -120,14 +120,14 @@ export function main() {
it('should only collect metrics for execute and ignore metrics from prepare', it('should only collect metrics for execute and ignore metrics from prepare',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
let scriptTime = 0; var scriptTime = 0;
let iterationCount = 1; var iterationCount = 1;
createSampler({ createSampler({
validator: createCountingValidator(2), validator: createCountingValidator(2),
metric: new MockMetric( metric: new MockMetric(
[], [],
() => { () => {
const result = Promise.resolve({'script': scriptTime}); var result = Promise.resolve({'script': scriptTime});
scriptTime = 0; scriptTime = 0;
return result; return result;
}), }),
@ -147,8 +147,8 @@ export function main() {
it('should call the validator for every execution and store the valid sample', it('should call the validator for every execution and store the valid sample',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const log: any[] = []; var log: any[] = [];
const validSample = [mv(null, null, {})]; var validSample = [mv(null, null, {})];
createSampler({ createSampler({
metric: createCountingMetric(), metric: createCountingMetric(),
@ -174,8 +174,8 @@ export function main() {
it('should report the metric values', it('should report the metric values',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const log: any[] = []; var log: any[] = [];
const validSample = [mv(null, null, {})]; var validSample = [mv(null, null, {})];
createSampler({ createSampler({
validator: createCountingValidator(2, validSample), validator: createCountingValidator(2, validSample),
metric: createCountingMetric(), metric: createCountingMetric(),
@ -212,7 +212,7 @@ function createCountingValidator(
return new MockValidator(log, (completeSample: MeasureValues[]) => { return new MockValidator(log, (completeSample: MeasureValues[]) => {
count--; count--;
if (count === 0) { if (count === 0) {
return validSample || completeSample; return isPresent(validSample) ? validSample : completeSample;
} else { } else {
return null; return null;
} }
@ -220,7 +220,7 @@ function createCountingValidator(
} }
function createCountingMetric(log: any[] = []) { function createCountingMetric(log: any[] = []) {
let scriptTime = 0; var scriptTime = 0;
return new MockMetric(log, () => ({'script': scriptTime++})); return new MockMetric(log, () => ({'script': scriptTime++}));
} }
@ -239,8 +239,7 @@ class MockDriverAdapter extends WebDriverAdapter {
class MockValidator extends Validator { class MockValidator extends Validator {
constructor(private _log: any[] = [], private _validate: Function = null) { super(); } constructor(private _log: any[] = [], private _validate: Function = null) { super(); }
validate(completeSample: MeasureValues[]): MeasureValues[] { validate(completeSample: MeasureValues[]): MeasureValues[] {
const stableSample = var stableSample = isPresent(this._validate) ? this._validate(completeSample) : completeSample;
isPresent(this._validate) ? this._validate(completeSample) : completeSample;
this._log.push(['validate', completeSample, stableSample]); this._log.push(['validate', completeSample, stableSample]);
return stableSample; return stableSample;
} }
@ -253,7 +252,7 @@ class MockMetric extends Metric {
return Promise.resolve(null); return Promise.resolve(null);
} }
endMeasure(restart: boolean) { endMeasure(restart: boolean) {
const measureValues = isPresent(this._endMeasure) ? this._endMeasure() : {}; var measureValues = isPresent(this._endMeasure) ? this._endMeasure() : {};
this._log.push(['endMeasure', restart, measureValues]); this._log.push(['endMeasure', restart, measureValues]);
return Promise.resolve(measureValues); return Promise.resolve(measureValues);
} }

View File

@ -13,7 +13,7 @@ export class TraceEventFactory {
constructor(private _cat: string, private _pid: string) {} constructor(private _cat: string, private _pid: string) {}
create(ph: any, name: string, time: number, args: any = null) { create(ph: any, name: string, time: number, args: any = null) {
const res: var res:
PerfLogEvent = {'name': name, 'cat': this._cat, 'ph': ph, 'ts': time, 'pid': this._pid}; PerfLogEvent = {'name': name, 'cat': this._cat, 'ph': ph, 'ts': time, 'pid': this._pid};
if (isPresent(args)) { if (isPresent(args)) {
res['args'] = args; res['args'] = args;
@ -34,7 +34,7 @@ export class TraceEventFactory {
} }
complete(name: string, time: number, duration: number, args: any = null) { complete(name: string, time: number, duration: number, args: any = null) {
const res = this.create('X', name, time, args); var res = this.create('X', name, time, args);
res['dur'] = duration; res['dur'] = duration;
return res; return res;
} }

View File

@ -9,10 +9,11 @@
import {describe, expect, it} from '@angular/core/testing/testing_internal'; import {describe, expect, it} from '@angular/core/testing/testing_internal';
import {MeasureValues, ReflectiveInjector, RegressionSlopeValidator} from '../../index'; import {MeasureValues, ReflectiveInjector, RegressionSlopeValidator} from '../../index';
import {ListWrapper} from '../../src/facade/collection';
export function main() { export function main() {
describe('regression slope validator', () => { describe('regression slope validator', () => {
let validator: RegressionSlopeValidator; var validator: RegressionSlopeValidator;
function createValidator({size, metric}: {size: number, metric: string}) { function createValidator({size, metric}: {size: number, metric: string}) {
validator = ReflectiveInjector validator = ReflectiveInjector
@ -42,16 +43,18 @@ export function main() {
it('should return the last sampleSize runs when the regression slope is ==0', () => { it('should return the last sampleSize runs when the regression slope is ==0', () => {
createValidator({size: 2, metric: 'script'}); createValidator({size: 2, metric: 'script'});
const sample = [mv(0, 0, {'script': 1}), mv(1, 1, {'script': 1}), mv(2, 2, {'script': 1})]; var sample = [mv(0, 0, {'script': 1}), mv(1, 1, {'script': 1}), mv(2, 2, {'script': 1})];
expect(validator.validate(sample.slice(0, 2))).toEqual(sample.slice(0, 2)); expect(validator.validate(ListWrapper.slice(sample, 0, 2)))
expect(validator.validate(sample)).toEqual(sample.slice(1, 3)); .toEqual(ListWrapper.slice(sample, 0, 2));
expect(validator.validate(sample)).toEqual(ListWrapper.slice(sample, 1, 3));
}); });
it('should return the last sampleSize runs when the regression slope is >0', () => { it('should return the last sampleSize runs when the regression slope is >0', () => {
createValidator({size: 2, metric: 'script'}); createValidator({size: 2, metric: 'script'});
const sample = [mv(0, 0, {'script': 1}), mv(1, 1, {'script': 2}), mv(2, 2, {'script': 3})]; var sample = [mv(0, 0, {'script': 1}), mv(1, 1, {'script': 2}), mv(2, 2, {'script': 3})];
expect(validator.validate(sample.slice(0, 2))).toEqual(sample.slice(0, 2)); expect(validator.validate(ListWrapper.slice(sample, 0, 2)))
expect(validator.validate(sample)).toEqual(sample.slice(1, 3)); .toEqual(ListWrapper.slice(sample, 0, 2));
expect(validator.validate(sample)).toEqual(ListWrapper.slice(sample, 1, 3));
}); });
}); });

View File

@ -9,10 +9,11 @@
import {describe, expect, it} from '@angular/core/testing/testing_internal'; import {describe, expect, it} from '@angular/core/testing/testing_internal';
import {MeasureValues, ReflectiveInjector, SizeValidator} from '../../index'; import {MeasureValues, ReflectiveInjector, SizeValidator} from '../../index';
import {ListWrapper} from '../../src/facade/collection';
export function main() { export function main() {
describe('size validator', () => { describe('size validator', () => {
let validator: SizeValidator; var validator: SizeValidator;
function createValidator(size: number) { function createValidator(size: number) {
validator = validator =
@ -35,9 +36,10 @@ export function main() {
it('should return the last sampleSize runs when it has at least the given size', () => { it('should return the last sampleSize runs when it has at least the given size', () => {
createValidator(2); createValidator(2);
const sample = [mv(0, 0, {'a': 1}), mv(1, 1, {'b': 2}), mv(2, 2, {'c': 3})]; var sample = [mv(0, 0, {'a': 1}), mv(1, 1, {'b': 2}), mv(2, 2, {'c': 3})];
expect(validator.validate(sample.slice(0, 2))).toEqual(sample.slice(0, 2)); expect(validator.validate(ListWrapper.slice(sample, 0, 2)))
expect(validator.validate(sample)).toEqual(sample.slice(1, 3)); .toEqual(ListWrapper.slice(sample, 0, 2));
expect(validator.validate(sample)).toEqual(ListWrapper.slice(sample, 1, 3));
}); });
}); });

View File

@ -9,7 +9,7 @@
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal';
import {Options, ReflectiveInjector, WebDriverExtension} from '../index'; import {Options, ReflectiveInjector, WebDriverExtension} from '../index';
import {isPresent} from '../src/facade/lang'; import {StringWrapper, isPresent} from '../src/facade/lang';
export function main() { export function main() {
function createExtension(ids: any[], caps: any) { function createExtension(ids: any[], caps: any) {
@ -52,6 +52,6 @@ class MockExtension extends WebDriverExtension {
constructor(public id: string) { super(); } constructor(public id: string) { super(); }
supports(capabilities: {[key: string]: any}): boolean { supports(capabilities: {[key: string]: any}): boolean {
return capabilities['browser'] === this.id; return StringWrapper.equals(capabilities['browser'], this.id);
} }
} }

View File

@ -9,28 +9,28 @@
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal';
import {ChromeDriverExtension, Options, ReflectiveInjector, WebDriverAdapter, WebDriverExtension} from '../../index'; import {ChromeDriverExtension, Options, ReflectiveInjector, WebDriverAdapter, WebDriverExtension} from '../../index';
import {isBlank} from '../../src/facade/lang'; import {Json, isBlank} from '../../src/facade/lang';
import {TraceEventFactory} from '../trace_event_factory'; import {TraceEventFactory} from '../trace_event_factory';
export function main() { export function main() {
describe('chrome driver extension', () => { describe('chrome driver extension', () => {
const CHROME45_USER_AGENT = var CHROME45_USER_AGENT =
'"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2499.0 Safari/537.36"'; '"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2499.0 Safari/537.36"';
let log: any[]; var log: any[];
let extension: ChromeDriverExtension; var extension: ChromeDriverExtension;
const blinkEvents = new TraceEventFactory('blink.console', 'pid0'); var blinkEvents = new TraceEventFactory('blink.console', 'pid0');
const v8Events = new TraceEventFactory('v8', 'pid0'); var v8Events = new TraceEventFactory('v8', 'pid0');
const v8EventsOtherProcess = new TraceEventFactory('v8', 'pid1'); var v8EventsOtherProcess = new TraceEventFactory('v8', 'pid1');
const chromeTimelineEvents = var chromeTimelineEvents =
new TraceEventFactory('disabled-by-default-devtools.timeline', 'pid0'); new TraceEventFactory('disabled-by-default-devtools.timeline', 'pid0');
const chrome45TimelineEvents = new TraceEventFactory('devtools.timeline', 'pid0'); var chrome45TimelineEvents = new TraceEventFactory('devtools.timeline', 'pid0');
const chromeTimelineV8Events = new TraceEventFactory('devtools.timeline,v8', 'pid0'); var chromeTimelineV8Events = new TraceEventFactory('devtools.timeline,v8', 'pid0');
const chromeBlinkTimelineEvents = new TraceEventFactory('blink,devtools.timeline', 'pid0'); var chromeBlinkTimelineEvents = new TraceEventFactory('blink,devtools.timeline', 'pid0');
const chromeBlinkUserTimingEvents = new TraceEventFactory('blink.user_timing', 'pid0'); var chromeBlinkUserTimingEvents = new TraceEventFactory('blink.user_timing', 'pid0');
const benchmarkEvents = new TraceEventFactory('benchmark', 'pid0'); var benchmarkEvents = new TraceEventFactory('benchmark', 'pid0');
const normEvents = new TraceEventFactory('timeline', 'pid0'); var normEvents = new TraceEventFactory('timeline', 'pid0');
function createExtension( function createExtension(
perfRecords: any[] = null, userAgent: string = null, perfRecords: any[] = null, userAgent: string = null,
@ -101,7 +101,7 @@ export function main() {
it('should normalize "tdur" to "dur"', it('should normalize "tdur" to "dur"',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const event: any = chromeTimelineV8Events.create('X', 'FunctionCall', 1100, null); var event: any = chromeTimelineV8Events.create('X', 'FunctionCall', 1100, null);
event['tdur'] = 5500; event['tdur'] = 5500;
createExtension([event]).readPerfLog().then((events) => { createExtension([event]).readPerfLog().then((events) => {
expect(events).toEqual([ expect(events).toEqual([
@ -398,8 +398,8 @@ class MockDriverAdapter extends WebDriverAdapter {
if (type === 'performance') { if (type === 'performance') {
return Promise.resolve(this._events.map( return Promise.resolve(this._events.map(
(event) => ({ (event) => ({
'message': JSON.stringify( 'message':
{'message': {'method': this._messageMethod, 'params': event}}, null, 2) Json.stringify({'message': {'method': this._messageMethod, 'params': event}})
}))); })));
} else { } else {
return null; return null;

View File

@ -9,14 +9,15 @@
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal';
import {IOsDriverExtension, ReflectiveInjector, WebDriverAdapter, WebDriverExtension} from '../../index'; import {IOsDriverExtension, ReflectiveInjector, WebDriverAdapter, WebDriverExtension} from '../../index';
import {Json} from '../../src/facade/lang';
import {TraceEventFactory} from '../trace_event_factory'; import {TraceEventFactory} from '../trace_event_factory';
export function main() { export function main() {
describe('ios driver extension', () => { describe('ios driver extension', () => {
let log: any[]; var log: any[];
let extension: IOsDriverExtension; var extension: IOsDriverExtension;
const normEvents = new TraceEventFactory('timeline', 'pid0'); var normEvents = new TraceEventFactory('timeline', 'pid0');
function createExtension(perfRecords: any[] = null): WebDriverExtension { function createExtension(perfRecords: any[] = null): WebDriverExtension {
if (!perfRecords) { if (!perfRecords) {
@ -183,9 +184,8 @@ class MockDriverAdapter extends WebDriverAdapter {
if (type === 'performance') { if (type === 'performance') {
return Promise.resolve(this._perfRecords.map(function(record) { return Promise.resolve(this._perfRecords.map(function(record) {
return { return {
'message': JSON.stringify( 'message': Json.stringify(
{'message': {'method': 'Timeline.eventRecorded', 'params': {'record': record}}}, null, {'message': {'method': 'Timeline.eventRecorded', 'params': {'record': record}}})
2)
}; };
})); }));
} else { } else {

View File

@ -11,6 +11,9 @@
* @description * @description
* Entry point for all public APIs of the common package. * Entry point for all public APIs of the common package.
*/ */
export * from './src/common'; export * from './src/location';
export {NgLocalization} from './src/localization';
export {CommonModule} from './src/common_module';
// This file only reexports content of the `src` folder. Keep it that way. export {NgClass, NgFor, NgIf, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet} from './src/directives/index';
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe} from './src/pipes/index';

View File

@ -1,20 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* @module
* @description
* Entry point for all public APIs of the common package.
*/
export * from './location/index';
export {NgLocaleLocalization, NgLocalization} from './localization';
export {CommonModule} from './common_module';
export {NgClass, NgFor, NgIf, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet} from './directives/index';
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe, TitleCasePipe} from './pipes/index';
export {VERSION} from './version';
export {Version} from '@angular/core';

View File

@ -9,7 +9,9 @@
import {CollectionChangeRecord, Directive, DoCheck, ElementRef, Input, IterableDiffer, IterableDiffers, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core'; import {CollectionChangeRecord, Directive, DoCheck, ElementRef, Input, IterableDiffer, IterableDiffers, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
import {isListLikeIterable} from '../facade/collection'; import {isListLikeIterable} from '../facade/collection';
import {isPresent, stringify} from '../facade/lang'; import {isPresent} from '../facade/lang';
/** /**
* @ngModule CommonModule * @ngModule CommonModule
@ -25,17 +27,15 @@ import {isPresent, stringify} from '../facade/lang';
* <some-element [ngClass]="{'first': true, 'second': true, 'third': false}">...</some-element> * <some-element [ngClass]="{'first': true, 'second': true, 'third': false}">...</some-element>
* *
* <some-element [ngClass]="stringExp|arrayExp|objExp">...</some-element> * <some-element [ngClass]="stringExp|arrayExp|objExp">...</some-element>
*
* <some-element [ngClass]="{'class1 class2 class3' : true}">...</some-element>
* ``` * ```
* *
* @description * @description
* *
* The CSS classes are updated as follows, depending on the type of the expression evaluation: * The CSS classes are updated as follow depending on the type of the expression evaluation:
* - `string` - the CSS classes listed in the string (space delimited) are added, * - `string` - the CSS classes listed in a string (space delimited) are added,
* - `Array` - the CSS classes declared as Array elements are added, * - `Array` - the CSS classes (Array elements) are added,
* - `Object` - keys are CSS classes that get added when the expression given in the value * - `Object` - keys are CSS class names that get added when the expression given in the value
* evaluates to a truthy value, otherwise they are removed. * evaluates to a truthy value, otherwise class are removed.
* *
* @stable * @stable
*/ */
@ -50,6 +50,7 @@ export class NgClass implements DoCheck {
private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers, private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers,
private _ngEl: ElementRef, private _renderer: Renderer) {} private _ngEl: ElementRef, private _renderer: Renderer) {}
@Input('class') @Input('class')
set klass(v: string) { set klass(v: string) {
this._applyInitialClasses(true); this._applyInitialClasses(true);
@ -110,14 +111,8 @@ export class NgClass implements DoCheck {
} }
private _applyIterableChanges(changes: any): void { private _applyIterableChanges(changes: any): void {
changes.forEachAddedItem((record: CollectionChangeRecord) => { changes.forEachAddedItem(
if (typeof record.item === 'string') { (record: CollectionChangeRecord) => this._toggleClass(record.item, true));
this._toggleClass(record.item, true);
} else {
throw new Error(
`NgClass can only toggle CSS classes expressed as strings, got ${stringify(record.item)}`);
}
});
changes.forEachRemovedItem( changes.forEachRemovedItem(
(record: CollectionChangeRecord) => this._toggleClass(record.item, false)); (record: CollectionChangeRecord) => this._toggleClass(record.item, false));

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * 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, isDevMode} from '@angular/core'; import {ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, Directive, DoCheck, EmbeddedViewRef, Input, IterableDiffer, IterableDiffers, OnChanges, SimpleChanges, TemplateRef, TrackByFn, ViewContainerRef} from '@angular/core';
import {getTypeNameForDebugging} from '../facade/lang'; import {getTypeNameForDebugging} from '../facade/lang';
@ -89,23 +89,9 @@ export class NgForRow {
@Directive({selector: '[ngFor][ngForOf]'}) @Directive({selector: '[ngFor][ngForOf]'})
export class NgFor implements DoCheck, OnChanges { export class NgFor implements DoCheck, OnChanges {
@Input() ngForOf: any; @Input() ngForOf: any;
@Input() @Input() ngForTrackBy: TrackByFn;
set ngForTrackBy(fn: TrackByFn) {
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;
}
get ngForTrackBy(): TrackByFn { return this._trackByFn; }
private _differ: IterableDiffer = null; private _differ: IterableDiffer = null;
private _trackByFn: TrackByFn;
constructor( constructor(
private _viewContainer: ViewContainerRef, private _template: TemplateRef<NgForRow>, private _viewContainer: ViewContainerRef, private _template: TemplateRef<NgForRow>,
@ -133,7 +119,7 @@ export class NgFor implements DoCheck, OnChanges {
} }
} }
ngDoCheck(): void { ngDoCheck() {
if (this._differ) { if (this._differ) {
const changes = this._differ.diff(this.ngForOf); const changes = this._differ.diff(this.ngForOf);
if (changes) this._applyChanges(changes); if (changes) this._applyChanges(changes);
@ -164,13 +150,13 @@ export class NgFor implements DoCheck, OnChanges {
} }
for (let i = 0, ilen = this._viewContainer.length; i < ilen; i++) { for (let i = 0, ilen = this._viewContainer.length; i < ilen; i++) {
const viewRef = <EmbeddedViewRef<NgForRow>>this._viewContainer.get(i); let viewRef = <EmbeddedViewRef<NgForRow>>this._viewContainer.get(i);
viewRef.context.index = i; viewRef.context.index = i;
viewRef.context.count = ilen; viewRef.context.count = ilen;
} }
changes.forEachIdentityChange((record: any) => { changes.forEachIdentityChange((record: any) => {
const viewRef = <EmbeddedViewRef<NgForRow>>this._viewContainer.get(record.currentIndex); let viewRef = <EmbeddedViewRef<NgForRow>>this._viewContainer.get(record.currentIndex);
viewRef.context.$implicit = record.item; viewRef.context.$implicit = record.item;
}); });
} }

View File

@ -6,152 +6,46 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef} from '@angular/core'; import {Directive, Input, TemplateRef, ViewContainerRef} from '@angular/core';
/** /**
* Conditionally includes a template based on the value of an `expression`. * Removes or recreates a portion of the DOM tree based on an {expression}.
* *
* `ngIf` evaluates the `expression` and then renders the `then` or `else` template in its place * If the expression assigned to `ngIf` evaluates to a false value then the element
* when expression is truthy or falsy respectively. Typically the: * is removed from the DOM, otherwise a clone of the element is reinserted into the DOM.
* - `then` template is the inline template of `ngIf` unless bound to a different value.
* - `else` template is blank unless it is bound.
* *
* # Most common usage * ### Example ([live demo](http://plnkr.co/edit/fe0kgemFBtmQOY31b4tw?p=preview)):
*
* The most common usage of the `ngIf` directive is to conditionally show the inline template as
* seen in this example:
* {@example common/ngIf/ts/module.ts region='NgIfSimple'}
*
* # Showing an alternative template using `else`
*
* If it is necessary to display a template when the `expression` is falsy use the `else` template
* binding as shown. Note that the `else` binding points to a `<template>` labeled `#elseBlock`.
* The template can be defined anywhere in the component view but is typically placed right after
* `ngIf` for readability.
*
* {@example common/ngIf/ts/module.ts region='NgIfElse'}
*
* # Using non-inlined `then` template
*
* Usually the `then` template is the inlined template of the `ngIf`, but it can be changed using
* a binding (just like `else`). Because `then` and `else` are bindings, the template references can
* change at runtime as shown in this example.
*
* {@example common/ngIf/ts/module.ts region='NgIfThenElse'}
*
* # Storing conditional result in a variable
*
* A common pattern is that we need to show a set of properties from the same object. If the
* object is undefined, then we have to use the safe-traversal-operator `?.` to guard against
* dereferencing a `null` value. This is especially the case when waiting on async data such as
* when using the `async` pipe as shown in folowing example:
* *
* ``` * ```
* Hello {{ (userStream|async)?.last }}, {{ (userStream|async)?.first }}! * <div *ngIf="errorCount > 0" class="error">
* <!-- Error message displayed when the errorCount property on the current context is greater
* than 0. -->
* {{errorCount}} errors detected
* </div>
* ``` * ```
* *
* There are several inefficiencies in the above example:
* - We create multiple subscriptions on `userStream`. One for each `async` pipe, or two in the
* example above.
* - We cannot display an alternative screen while waiting for the data to arrive asynchronously.
* - We have to use the safe-traversal-operator `?.` to access properties, which is cumbersome.
* - We have to place the `async` pipe in parenthesis.
*
* A better way to do this is to use `ngIf` and store the result of the condition in a local
* variable as shown in the the example below:
*
* {@example common/ngIf/ts/module.ts region='NgIfLet'}
*
* Notice that:
* - We use only one `async` pipe and hence only one subscription gets created.
* - `ngIf` stores the result of the `userStream|async` in the local variable `user`.
* - The local `user` can then be bound repeatedly in a more efficient way.
* - No need to use the safe-traversal-operator `?.` to access properties as `ngIf` will only
* display the data if `userStream` returns a value.
* - We can display an alternative template while waiting for the data.
*
* ### Syntax * ### Syntax
* *
* Simple form:
* - `<div *ngIf="condition">...</div>` * - `<div *ngIf="condition">...</div>`
* - `<div template="ngIf condition">...</div>` * - `<div template="ngIf condition">...</div>`
* - `<template [ngIf]="condition"><div>...</div></template>` * - `<template [ngIf]="condition"><div>...</div></template>`
* *
* Form with an else block:
* ```
* <div *ngIf="condition; else elseBlock">...</div>
* <template #elseBlock>...</template>
* ```
*
* Form with a `then` and `else` block:
* ```
* <div *ngIf="condition; then thenBlock else elseBlock"></div>
* <template #thenBlock>...</template>
* <template #elseBlock>...</template>
* ```
*
* Form with storing the value locally:
* ```
* <div *ngIf="condition; else elseBlock; let value">{{value}}</div>
* <template #elseBlock>...</template>
* ```
*
* @stable * @stable
*/ */
@Directive({selector: '[ngIf]'}) @Directive({selector: '[ngIf]'})
export class NgIf { export class NgIf {
private _context: NgIfContext = new NgIfContext(); private _hasView: boolean = false;
private _thenTemplateRef: TemplateRef<NgIfContext> = null;
private _elseTemplateRef: TemplateRef<NgIfContext> = null;
private _thenViewRef: EmbeddedViewRef<NgIfContext> = null;
private _elseViewRef: EmbeddedViewRef<NgIfContext> = null;
constructor(private _viewContainer: ViewContainerRef, templateRef: TemplateRef<NgIfContext>) { constructor(private _viewContainer: ViewContainerRef, private _template: TemplateRef<Object>) {}
this._thenTemplateRef = templateRef;
}
@Input() @Input()
set ngIf(condition: any) { set ngIf(condition: any) {
this._context.$implicit = condition; if (condition && !this._hasView) {
this._updateView(); this._hasView = true;
} this._viewContainer.createEmbeddedView(this._template);
} else if (!condition && this._hasView) {
@Input() this._hasView = false;
set ngIfThen(templateRef: TemplateRef<NgIfContext>) {
this._thenTemplateRef = templateRef;
this._thenViewRef = null; // clear previous view if any.
this._updateView();
}
@Input()
set ngIfElse(templateRef: TemplateRef<NgIfContext>) {
this._elseTemplateRef = templateRef;
this._elseViewRef = null; // clear previous view if any.
this._updateView();
}
private _updateView() {
if (this._context.$implicit) {
if (!this._thenViewRef) {
this._viewContainer.clear(); this._viewContainer.clear();
this._elseViewRef = null;
if (this._thenTemplateRef) {
this._thenViewRef =
this._viewContainer.createEmbeddedView(this._thenTemplateRef, this._context);
}
}
} else {
if (!this._elseViewRef) {
this._viewContainer.clear();
this._thenViewRef = null;
if (this._elseTemplateRef) {
this._elseViewRef =
this._viewContainer.createEmbeddedView(this._elseTemplateRef, this._context);
}
}
} }
} }
} }
export class NgIfContext { public $implicit: any = null; }

View File

@ -25,7 +25,7 @@ import {Directive, DoCheck, ElementRef, Input, KeyValueChangeRecord, KeyValueDif
* @description * @description
* *
* The styles are updated according to the value of the expression evaluation: * The styles are updated according to the value of the expression evaluation:
* - keys are style names with an optional `.<unit>` suffix (ie 'top.px', 'font-style.em'), * - keys are style names with an option `.<unit>` suffix (ie 'top.px', 'font-style.em'),
* - values are the values assigned to those properties (expressed in the given unit). * - values are the values assigned to those properties (expressed in the given unit).
* *
* @stable * @stable

View File

@ -6,31 +6,19 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, DoCheck, Host, Input, TemplateRef, ViewContainerRef} from '@angular/core'; import {Directive, Host, Input, TemplateRef, ViewContainerRef} from '@angular/core';
import {ListWrapper} from '../facade/collection';
const _CASE_DEFAULT = new Object();
export class SwitchView { export class SwitchView {
private _created = false;
constructor( constructor(
private _viewContainerRef: ViewContainerRef, private _templateRef: TemplateRef<Object>) {} private _viewContainerRef: ViewContainerRef, private _templateRef: TemplateRef<Object>) {}
create(): void { create(): void { this._viewContainerRef.createEmbeddedView(this._templateRef); }
this._created = true;
this._viewContainerRef.createEmbeddedView(this._templateRef);
}
destroy(): void { destroy(): void { this._viewContainerRef.clear(); }
this._created = false;
this._viewContainerRef.clear();
}
enforceState(created: boolean) {
if (created && !this._created) {
this.create();
} else if (!created && this._created) {
this.destroy();
}
}
} }
/** /**
@ -50,7 +38,7 @@ export class SwitchView {
* <inner-element></inner-element> * <inner-element></inner-element>
* <inner-other-element></inner-other-element> * <inner-other-element></inner-other-element>
* </ng-container> * </ng-container>
* <some-element *ngSwitchDefault>...</some-element> * <some-element *ngSwitchDefault>...</p>
* </container-element> * </container-element>
* ``` * ```
* @description * @description
@ -65,7 +53,8 @@ export class SwitchView {
* root elements. * root elements.
* *
* Elements within `NgSwitch` but outside of a `NgSwitchCase` or `NgSwitchDefault` directives will * Elements within `NgSwitch` but outside of a `NgSwitchCase` or `NgSwitchDefault` directives will
* be preserved at the location. * be
* preserved at the location.
* *
* The `ngSwitchCase` directive informs the parent `NgSwitch` of which view to display when the * The `ngSwitchCase` directive informs the parent `NgSwitch` of which view to display when the
* expression is evaluated. * expression is evaluated.
@ -76,52 +65,87 @@ export class SwitchView {
*/ */
@Directive({selector: '[ngSwitch]'}) @Directive({selector: '[ngSwitch]'})
export class NgSwitch { export class NgSwitch {
private _defaultViews: SwitchView[]; private _switchValue: any;
private _defaultUsed = false; private _useDefault: boolean = false;
private _caseCount = 0; private _valueViews = new Map<any, SwitchView[]>();
private _lastCaseCheckIndex = 0; private _activeViews: SwitchView[] = [];
private _lastCasesMatched = false;
private _ngSwitch: any;
@Input() @Input()
set ngSwitch(newValue: any) { set ngSwitch(value: any) {
this._ngSwitch = newValue; // Empty the currently active ViewContainers
if (this._caseCount === 0) { this._emptyAllActiveViews();
this._updateDefaultCases(true);
// Add the ViewContainers matching the value (with a fallback to default)
this._useDefault = false;
let views = this._valueViews.get(value);
if (!views) {
this._useDefault = true;
views = this._valueViews.get(_CASE_DEFAULT) || null;
}
this._activateViews(views);
this._switchValue = value;
}
/** @internal */
_onCaseValueChanged(oldCase: any, newCase: any, view: SwitchView): void {
this._deregisterView(oldCase, view);
this._registerView(newCase, view);
if (oldCase === this._switchValue) {
view.destroy();
ListWrapper.remove(this._activeViews, view);
} else if (newCase === this._switchValue) {
if (this._useDefault) {
this._useDefault = false;
this._emptyAllActiveViews();
}
view.create();
this._activeViews.push(view);
}
// Switch to default when there is no more active ViewContainers
if (this._activeViews.length === 0 && !this._useDefault) {
this._useDefault = true;
this._activateViews(this._valueViews.get(_CASE_DEFAULT));
}
}
private _emptyAllActiveViews(): void {
const activeContainers = this._activeViews;
for (var i = 0; i < activeContainers.length; i++) {
activeContainers[i].destroy();
}
this._activeViews = [];
}
private _activateViews(views: SwitchView[]): void {
if (views) {
for (var i = 0; i < views.length; i++) {
views[i].create();
}
this._activeViews = views;
} }
} }
/** @internal */ /** @internal */
_addCase(): number { return this._caseCount++; } _registerView(value: any, view: SwitchView): void {
let views = this._valueViews.get(value);
/** @internal */ if (!views) {
_addDefault(view: SwitchView) { views = [];
if (!this._defaultViews) { this._valueViews.set(value, views);
this._defaultViews = [];
} }
this._defaultViews.push(view); views.push(view);
} }
/** @internal */ private _deregisterView(value: any, view: SwitchView): void {
_matchCase(value: any): boolean { // `_CASE_DEFAULT` is used a marker for non-registered cases
const matched = value == this._ngSwitch; if (value === _CASE_DEFAULT) return;
this._lastCasesMatched = this._lastCasesMatched || matched; const views = this._valueViews.get(value);
this._lastCaseCheckIndex++; if (views.length == 1) {
if (this._lastCaseCheckIndex === this._caseCount) { this._valueViews.delete(value);
this._updateDefaultCases(!this._lastCasesMatched); } else {
this._lastCaseCheckIndex = 0; ListWrapper.remove(views, view);
this._lastCasesMatched = false;
}
return matched;
}
private _updateDefaultCases(useDefault: boolean) {
if (this._defaultViews && useDefault !== this._defaultUsed) {
this._defaultUsed = useDefault;
for (let i = 0; i < this._defaultViews.length; i++) {
const defaultView = this._defaultViews[i];
defaultView.enforceState(useDefault);
}
} }
} }
} }
@ -151,20 +175,24 @@ export class NgSwitch {
* @stable * @stable
*/ */
@Directive({selector: '[ngSwitchCase]'}) @Directive({selector: '[ngSwitchCase]'})
export class NgSwitchCase implements DoCheck { export class NgSwitchCase {
// `_CASE_DEFAULT` is used as a marker for a not yet initialized value
private _value: any = _CASE_DEFAULT;
private _view: SwitchView; private _view: SwitchView;
private _switch: NgSwitch;
@Input()
ngSwitchCase: any;
constructor( constructor(
viewContainer: ViewContainerRef, templateRef: TemplateRef<Object>, viewContainer: ViewContainerRef, templateRef: TemplateRef<Object>,
@Host() private ngSwitch: NgSwitch) { @Host() ngSwitch: NgSwitch) {
ngSwitch._addCase(); this._switch = ngSwitch;
this._view = new SwitchView(viewContainer, templateRef); this._view = new SwitchView(viewContainer, templateRef);
} }
ngDoCheck() { this._view.enforceState(this.ngSwitch._matchCase(this.ngSwitchCase)); } @Input()
set ngSwitchCase(value: any) {
this._switch._onCaseValueChanged(this._value, value, this._view);
this._value = value;
}
} }
/** /**
@ -194,7 +222,7 @@ export class NgSwitchCase implements DoCheck {
export class NgSwitchDefault { export class NgSwitchDefault {
constructor( constructor(
viewContainer: ViewContainerRef, templateRef: TemplateRef<Object>, viewContainer: ViewContainerRef, templateRef: TemplateRef<Object>,
@Host() ngSwitch: NgSwitch) { @Host() sswitch: NgSwitch) {
ngSwitch._addDefault(new SwitchView(viewContainer, templateRef)); sswitch._registerView(_CASE_DEFAULT, new SwitchView(viewContainer, templateRef));
} }
} }

View File

@ -23,23 +23,9 @@ export abstract class NgLocalization { abstract getPluralCategory(value: any): s
*/ */
export function getPluralCategory( export function getPluralCategory(
value: number, cases: string[], ngLocalization: NgLocalization): string { value: number, cases: string[], ngLocalization: NgLocalization): string {
let key = `=${value}`; const nbCase = `=${value}`;
if (cases.indexOf(key) > -1) { return cases.indexOf(nbCase) > -1 ? nbCase : ngLocalization.getPluralCategory(value);
return key;
}
key = ngLocalization.getPluralCategory(value);
if (cases.indexOf(key) > -1) {
return key;
}
if (cases.indexOf('other') > -1) {
return 'other';
}
throw new Error(`No plural message found for value "${value}"`);
} }
/** /**
@ -49,10 +35,10 @@ export function getPluralCategory(
*/ */
@Injectable() @Injectable()
export class NgLocaleLocalization extends NgLocalization { export class NgLocaleLocalization extends NgLocalization {
constructor(@Inject(LOCALE_ID) protected locale: string) { super(); } constructor(@Inject(LOCALE_ID) private _locale: string) { super(); }
getPluralCategory(value: any): string { getPluralCategory(value: any): string {
const plural = getPluralCase(this.locale, value); const plural = getPluralCase(this._locale, value);
switch (plural) { switch (plural) {
case Plural.Zero: case Plural.Zero:

View File

@ -0,0 +1,13 @@
/**
* @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
*/
export * from './location/platform_location';
export * from './location/location_strategy';
export * from './location/hash_location_strategy';
export * from './location/path_location_strategy';
export * from './location/location';

View File

@ -17,8 +17,6 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
/** /**
* @whatItDoes Use URL hash for storing application location data.
* @description
* `HashLocationStrategy` is a {@link LocationStrategy} used to configure the * `HashLocationStrategy` is a {@link LocationStrategy} used to configure the
* {@link Location} service to represent its state in the * {@link Location} service to represent its state in the
* [hash fragment](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) * [hash fragment](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax)
@ -29,7 +27,18 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
* *
* ### Example * ### Example
* *
* {@example common/location/ts/hash_location_component.ts region='LocationComponent'} * ```
* import {Component, NgModule} from '@angular/core';
* import {
* LocationStrategy,
* HashLocationStrategy
* } from '@angular/common';
*
* @NgModule({
* providers: [{provide: LocationStrategy, useClass: HashLocationStrategy}]
* })
* class AppModule {}
* ```
* *
* @stable * @stable
*/ */
@ -55,19 +64,19 @@ export class HashLocationStrategy extends LocationStrategy {
path(includeHash: boolean = false): string { path(includeHash: boolean = false): string {
// the hash value is always prefixed with a `#` // the hash value is always prefixed with a `#`
// and if it is empty then it will stay empty // and if it is empty then it will stay empty
let path = this._platformLocation.hash; var path = this._platformLocation.hash;
if (!isPresent(path)) path = '#'; if (!isPresent(path)) path = '#';
return path.length > 0 ? path.substring(1) : path; return path.length > 0 ? path.substring(1) : path;
} }
prepareExternalUrl(internal: string): string { prepareExternalUrl(internal: string): string {
const url = Location.joinWithSlash(this._baseHref, internal); var url = Location.joinWithSlash(this._baseHref, internal);
return url.length > 0 ? ('#' + url) : url; return url.length > 0 ? ('#' + url) : url;
} }
pushState(state: any, title: string, path: string, queryParams: string) { pushState(state: any, title: string, path: string, queryParams: string) {
let url = this.prepareExternalUrl(path + Location.normalizeQueryParams(queryParams)); var url = this.prepareExternalUrl(path + Location.normalizeQueryParams(queryParams));
if (url.length == 0) { if (url.length == 0) {
url = this._platformLocation.pathname; url = this._platformLocation.pathname;
} }
@ -75,7 +84,7 @@ export class HashLocationStrategy extends LocationStrategy {
} }
replaceState(state: any, title: string, path: string, queryParams: string) { replaceState(state: any, title: string, path: string, queryParams: string) {
let url = this.prepareExternalUrl(path + Location.normalizeQueryParams(queryParams)); var url = this.prepareExternalUrl(path + Location.normalizeQueryParams(queryParams));
if (url.length == 0) { if (url.length == 0) {
url = this._platformLocation.pathname; url = this._platformLocation.pathname;
} }

View File

@ -1,13 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export * from './platform_location';
export * from './location_strategy';
export * from './hash_location_strategy';
export * from './path_location_strategy';
export * from './location';

View File

@ -12,8 +12,7 @@ import {LocationStrategy} from './location_strategy';
/** /**
* @whatItDoes `Location` is a service that applications can use to interact with a browser's URL. * `Location` is a service that applications can use to interact with a browser's URL.
* @description
* Depending on which {@link LocationStrategy} is used, `Location` will either persist * Depending on which {@link LocationStrategy} is used, `Location` will either persist
* to the URL's path or the URL's hash segment. * to the URL's path or the URL's hash segment.
* *
@ -29,7 +28,19 @@ import {LocationStrategy} from './location_strategy';
* - `/my/app/user/123/` **is not** normalized * - `/my/app/user/123/` **is not** normalized
* *
* ### Example * ### Example
* {@example common/location/ts/path_location_component.ts region='LocationComponent'} *
* ```
* import {Component} from '@angular/core';
* import {Location} from '@angular/common';
*
* @Component({selector: 'app-component'})
* class AppCmp {
* constructor(location: Location) {
* location.go('/foo');
* }
* }
* ```
*
* @stable * @stable
*/ */
@Injectable() @Injectable()
@ -85,7 +96,7 @@ export class Location {
* used, or the `APP_BASE_HREF` if the `PathLocationStrategy` is in use. * used, or the `APP_BASE_HREF` if the `PathLocationStrategy` is in use.
*/ */
prepareExternalUrl(url: string): string { prepareExternalUrl(url: string): string {
if (url && url[0] !== '/') { if (url.length > 0 && !url.startsWith('/')) {
url = '/' + url; url = '/' + url;
} }
return this._platformStrategy.prepareExternalUrl(url); return this._platformStrategy.prepareExternalUrl(url);
@ -132,7 +143,7 @@ export class Location {
* is. * is.
*/ */
public static normalizeQueryParams(params: string): string { public static normalizeQueryParams(params: string): string {
return params && params[0] !== '?' ? '?' + params : params; return (params.length > 0 && params.substring(0, 1) != '?') ? ('?' + params) : params;
} }
/** /**
@ -145,7 +156,7 @@ export class Location {
if (end.length == 0) { if (end.length == 0) {
return start; return start;
} }
let slashes = 0; var slashes = 0;
if (start.endsWith('/')) { if (start.endsWith('/')) {
slashes++; slashes++;
} }
@ -164,13 +175,25 @@ export class Location {
/** /**
* If url has a trailing slash, remove it, otherwise return url as is. * If url has a trailing slash, remove it, otherwise return url as is.
*/ */
public static stripTrailingSlash(url: string): string { return url.replace(/\/$/, ''); } public static stripTrailingSlash(url: string): string {
if (/\/$/g.test(url)) {
url = url.substring(0, url.length - 1);
}
return url;
}
} }
function _stripBaseHref(baseHref: string, url: string): string { function _stripBaseHref(baseHref: string, url: string): string {
return baseHref && url.startsWith(baseHref) ? url.substring(baseHref.length) : url; if (baseHref.length > 0 && url.startsWith(baseHref)) {
return url.substring(baseHref.length);
}
return url;
} }
function _stripIndexHtml(url: string): string { function _stripIndexHtml(url: string): string {
return url.replace(/\/index.html$/, ''); if (/\/index.html$/g.test(url)) {
// '/index.html'.length == 11
return url.substring(0, url.length - 11);
}
return url;
} }

View File

@ -12,7 +12,7 @@ import {LocationChangeListener} from './platform_location';
/** /**
* `LocationStrategy` is responsible for representing and reading route state * `LocationStrategy` is responsible for representing and reading route state
* from the browser's URL. Angular provides two strategies: * from the browser's URL. Angular provides two strategies:
* {@link HashLocationStrategy} and {@link PathLocationStrategy}. * {@link HashLocationStrategy} and {@link PathLocationStrategy} (default).
* *
* This is used under the hood of the {@link Location} service. * This is used under the hood of the {@link Location} service.
* *

View File

@ -17,13 +17,14 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
/** /**
* @whatItDoes Use URL for storing application location data.
* @description
* `PathLocationStrategy` is a {@link LocationStrategy} used to configure the * `PathLocationStrategy` is a {@link LocationStrategy} used to configure the
* {@link Location} service to represent its state in the * {@link Location} service to represent its state in the
* [path](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) of the * [path](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) of the
* browser's URL. * browser's URL.
* *
* `PathLocationStrategy` is the default binding for {@link LocationStrategy}
* provided in {@link ROUTER_PROVIDERS}.
*
* If you're using `PathLocationStrategy`, you must provide a {@link APP_BASE_HREF} * If you're using `PathLocationStrategy`, you must provide a {@link APP_BASE_HREF}
* or add a base element to the document. This URL prefix that will be preserved * or add a base element to the document. This URL prefix that will be preserved
* when generating and recognizing URLs. * when generating and recognizing URLs.
@ -36,10 +37,6 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
* `location.go('/foo')`, the browser's URL will become * `location.go('/foo')`, the browser's URL will become
* `example.com/my/app/foo`. * `example.com/my/app/foo`.
* *
* ### Example
*
* {@example common/location/ts/path_location_component.ts region='LocationComponent'}
*
* @stable * @stable
*/ */
@Injectable() @Injectable()
@ -82,12 +79,12 @@ export class PathLocationStrategy extends LocationStrategy {
} }
pushState(state: any, title: string, url: string, queryParams: string) { pushState(state: any, title: string, url: string, queryParams: string) {
const externalUrl = this.prepareExternalUrl(url + Location.normalizeQueryParams(queryParams)); var externalUrl = this.prepareExternalUrl(url + Location.normalizeQueryParams(queryParams));
this._platformLocation.pushState(state, title, externalUrl); this._platformLocation.pushState(state, title, externalUrl);
} }
replaceState(state: any, title: string, url: string, queryParams: string) { replaceState(state: any, title: string, url: string, queryParams: string) {
const externalUrl = this.prepareExternalUrl(url + Location.normalizeQueryParams(queryParams)); var externalUrl = this.prepareExternalUrl(url + Location.normalizeQueryParams(queryParams));
this._platformLocation.replaceState(state, title, externalUrl); this._platformLocation.replaceState(state, title, externalUrl);
} }

View File

@ -6,6 +6,8 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
export class GeneratedFile { /**
constructor(public srcFileUrl: string, public genFileUrl: string, public source: string) {} * @module
} * @description
* This module provides a set of common Pipes.
*/

View File

@ -59,7 +59,7 @@ const _observableStrategy = new ObservableStrategy();
* {@example common/pipes/ts/async_pipe.ts region='AsyncPipePromise'} * {@example common/pipes/ts/async_pipe.ts region='AsyncPipePromise'}
* *
* It's also possible to use `async` with Observables. The example below binds the `time` Observable * It's also possible to use `async` with Observables. The example below binds the `time` Observable
* to the view. The Observable continuously updates the view with the current time. * to the view. The Observable continuesly updates the view with the current time.
* *
* {@example common/pipes/ts/async_pipe.ts region='AsyncPipeObservable'} * {@example common/pipes/ts/async_pipe.ts region='AsyncPipeObservable'}
* *

View File

@ -1,72 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Pipe, PipeTransform} from '@angular/core';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
/**
* Transforms text to lowercase.
*
* {@example common/pipes/ts/lowerupper_pipe.ts region='LowerUpperPipe' }
*
* @stable
*/
@Pipe({name: 'lowercase'})
export class LowerCasePipe implements PipeTransform {
transform(value: string): string {
if (!value) return value;
if (typeof value !== 'string') {
throw new InvalidPipeArgumentError(LowerCasePipe, value);
}
return value.toLowerCase();
}
}
/**
* Helper method to transform a single word to titlecase.
*
* @stable
*/
function titleCaseWord(word: string) {
if (!word) return word;
return word[0].toUpperCase() + word.substr(1).toLowerCase();
}
/**
* Transforms text to titlecase.
*
* @stable
*/
@Pipe({name: 'titlecase'})
export class TitleCasePipe implements PipeTransform {
transform(value: string): string {
if (!value) return value;
if (typeof value !== 'string') {
throw new InvalidPipeArgumentError(TitleCasePipe, value);
}
return value.split(/\b/g).map(word => titleCaseWord(word)).join('');
}
}
/**
* Transforms text to uppercase.
*
* @stable
*/
@Pipe({name: 'uppercase'})
export class UpperCasePipe implements PipeTransform {
transform(value: string): string {
if (!value) return value;
if (typeof value !== 'string') {
throw new InvalidPipeArgumentError(UpperCasePipe, value);
}
return value.toUpperCase();
}
}

View File

@ -7,14 +7,11 @@
*/ */
import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core'; import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
import {DateFormatter} from '../facade/intl';
import {NumberWrapper, isDate} from '../facade/lang'; import {NumberWrapper, isBlank, isDate} from '../facade/lang';
import {DateFormatter} from './intl';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error'; import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
/** /**
* @ngModule CommonModule * @ngModule CommonModule
* @whatItDoes Formats a date according to locale rules. * @whatItDoes Formats a date according to locale rules.
@ -36,30 +33,27 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
* - `'shortTime'`: equivalent to `'jm'` (e.g. `12:05 PM` for `en-US`) * - `'shortTime'`: equivalent to `'jm'` (e.g. `12:05 PM` for `en-US`)
* *
* *
* | Component | Symbol | Narrow | Short Form | Long Form | Numeric | 2-digit | * | Component | Symbol | Short Form | Long Form | Numeric | 2-digit |
* |-----------|:------:|--------|--------------|-------------------|-----------|-----------| * |-----------|:------:|--------------|-------------------|-----------|-----------|
* | era | G | G (A) | GGG (AD) | GGGG (Anno Domini)| - | - | * | era | G | G (AD) | GGGG (Anno Domini)| - | - |
* | year | y | - | - | - | y (2015) | yy (15) | * | year | y | - | - | y (2015) | yy (15) |
* | month | M | L (S) | MMM (Sep) | MMMM (September) | M (9) | MM (09) | * | month | M | MMM (Sep) | MMMM (September) | M (9) | MM (09) |
* | day | d | - | - | - | d (3) | dd (03) | * | day | d | - | - | d (3) | dd (03) |
* | weekday | E | E (S) | EEE (Sun) | EEEE (Sunday) | - | - | * | weekday | E | EEE (Sun) | EEEE (Sunday) | - | - |
* | hour | j | - | - | - | j (13) | jj (13) | * | hour | j | - | - | j (13) | jj (13) |
* | hour12 | h | - | - | - | h (1 PM) | hh (01 PM)| * | hour12 | h | - | - | h (1 PM) | hh (01 PM)|
* | hour24 | H | - | - | - | H (13) | HH (13) | * | hour24 | H | - | - | H (13) | HH (13) |
* | minute | m | - | - | - | m (5) | mm (05) | * | minute | m | - | - | m (5) | mm (05) |
* | second | s | - | - | - | s (9) | ss (09) | * | second | s | - | - | s (9) | ss (09) |
* | timezone | z | - | - | z (Pacific Standard Time)| - | - | * | timezone | z | - | z (Pacific Standard Time)| - | - |
* | timezone | Z | - | Z (GMT-8:00) | - | - | - | * | timezone | Z | Z (GMT-8:00) | - | - | - |
* | timezone | a | - | a (PM) | - | - | - | * | timezone | a | a (PM) | - | - | - |
* *
* In javascript, only the components specified will be respected (not the ordering, * In javascript, only the components specified will be respected (not the ordering,
* punctuations, ...) and details of the formatting will be dependent on the locale. * punctuations, ...) and details of the formatting will be dependent on the locale.
* *
* Timezone of the formatted text will be the local system timezone of the end-user's machine. * Timezone of the formatted text will be the local system timezone of the end-user's machine.
* *
* When the expression is a ISO string without time (e.g. 2016-09-19) the time zone offset is not
* applied and the formatted text will have the same day, month and year of the expression.
*
* WARNINGS: * WARNINGS:
* - this pipe is marked as pure hence it will not be re-evaluated when the input is mutated. * - this pipe is marked as pure hence it will not be re-evaluated when the input is mutated.
* Instead users should treat the date as an immutable object and change the reference when the * Instead users should treat the date as an immutable object and change the reference when the
@ -101,42 +95,22 @@ export class DatePipe implements PipeTransform {
constructor(@Inject(LOCALE_ID) private _locale: string) {} constructor(@Inject(LOCALE_ID) private _locale: string) {}
transform(value: any, pattern: string = 'mediumDate'): string { transform(value: any, pattern: string = 'mediumDate'): string {
let date: Date;
if (isBlank(value)) return null; if (isBlank(value)) return null;
if (typeof value === 'string') { if (!this.supports(value)) {
value = value.trim();
}
if (isDate(value)) {
date = value;
} else if (NumberWrapper.isNumeric(value)) {
date = new Date(parseFloat(value));
} else if (typeof value === 'string' && /^(\d{4}-\d{1,2}-\d{1,2})$/.test(value)) {
/**
* For ISO Strings without time the day, month and year must be extracted from the ISO String
* before Date creation to avoid time offset and errors in the new Date.
* If we only replace '-' with ',' in the ISO String ("2015,01,01"), and try to create a new
* date, some browsers (e.g. IE 9) will throw an invalid Date error
* If we leave the '-' ("2015-01-01") and try to create a new Date("2015-01-01") the timeoffset
* is applied
* Note: ISO months are 0 for January, 1 for February, ...
*/
const [y, m, d] = value.split('-').map((val: string) => parseInt(val, 10));
date = new Date(y, m - 1, d);
} else {
date = new Date(value);
}
if (!isDate(date)) {
throw new InvalidPipeArgumentError(DatePipe, value); throw new InvalidPipeArgumentError(DatePipe, value);
} }
return DateFormatter.format(date, this._locale, DatePipe._ALIASES[pattern] || pattern); if (NumberWrapper.isNumeric(value)) {
value = parseFloat(value);
}
return DateFormatter.format(
new Date(value), this._locale, DatePipe._ALIASES[pattern] || pattern);
}
private supports(obj: any): boolean {
return isDate(obj) || NumberWrapper.isNumeric(obj) ||
(typeof obj === 'string' && isDate(new Date(obj)));
} }
} }
function isBlank(obj: any): boolean {
return obj == null || obj === '';
}

View File

@ -7,7 +7,7 @@
*/ */
import {Pipe, PipeTransform} from '@angular/core'; import {Pipe, PipeTransform} from '@angular/core';
import {isBlank} from '../facade/lang'; import {isBlank, isStringMap} from '../facade/lang';
import {NgLocalization, getPluralCategory} from '../localization'; import {NgLocalization, getPluralCategory} from '../localization';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error'; import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
@ -37,7 +37,7 @@ export class I18nPluralPipe implements PipeTransform {
transform(value: number, pluralMap: {[count: string]: string}): string { transform(value: number, pluralMap: {[count: string]: string}): string {
if (isBlank(value)) return ''; if (isBlank(value)) return '';
if (typeof pluralMap !== 'object' || pluralMap === null) { if (!isStringMap(pluralMap)) {
throw new InvalidPipeArgumentError(I18nPluralPipe, pluralMap); throw new InvalidPipeArgumentError(I18nPluralPipe, pluralMap);
} }

View File

@ -7,6 +7,7 @@
*/ */
import {Pipe, PipeTransform} from '@angular/core'; import {Pipe, PipeTransform} from '@angular/core';
import {isBlank, isStringMap} from '../facade/lang';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error'; import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
/** /**
@ -15,10 +16,9 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
* @howToUse `expression | i18nSelect:mapping` * @howToUse `expression | i18nSelect:mapping`
* @description * @description
* *
* Where `mapping` is an object that indicates the text that should be displayed * Where:
* - `mapping`: is an object that indicates the text that should be displayed
* for different values of the provided `expression`. * for different values of the provided `expression`.
* If none of the keys of the mapping match the value of the `expression`, then the content
* of the `other` key is returned when present, otherwise an empty string is returned.
* *
* ## Example * ## Example
* *
@ -29,20 +29,12 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
@Pipe({name: 'i18nSelect', pure: true}) @Pipe({name: 'i18nSelect', pure: true})
export class I18nSelectPipe implements PipeTransform { export class I18nSelectPipe implements PipeTransform {
transform(value: string, mapping: {[key: string]: string}): string { transform(value: string, mapping: {[key: string]: string}): string {
if (value == null) return ''; if (isBlank(value)) return '';
if (typeof mapping !== 'object' || typeof value !== 'string') { if (!isStringMap(mapping)) {
throw new InvalidPipeArgumentError(I18nSelectPipe, mapping); throw new InvalidPipeArgumentError(I18nSelectPipe, mapping);
} }
if (mapping.hasOwnProperty(value)) { return mapping.hasOwnProperty(value) ? mapping[value] : '';
return mapping[value];
}
if (mapping.hasOwnProperty('other')) {
return mapping['other'];
}
return '';
} }
} }

View File

@ -12,13 +12,14 @@
* This module provides a set of common Pipes. * This module provides a set of common Pipes.
*/ */
import {AsyncPipe} from './async_pipe'; import {AsyncPipe} from './async_pipe';
import {LowerCasePipe, TitleCasePipe, UpperCasePipe} from './case_conversion_pipes';
import {DatePipe} from './date_pipe'; import {DatePipe} from './date_pipe';
import {I18nPluralPipe} from './i18n_plural_pipe'; import {I18nPluralPipe} from './i18n_plural_pipe';
import {I18nSelectPipe} from './i18n_select_pipe'; import {I18nSelectPipe} from './i18n_select_pipe';
import {JsonPipe} from './json_pipe'; import {JsonPipe} from './json_pipe';
import {LowerCasePipe} from './lowercase_pipe';
import {CurrencyPipe, DecimalPipe, PercentPipe} from './number_pipe'; import {CurrencyPipe, DecimalPipe, PercentPipe} from './number_pipe';
import {SlicePipe} from './slice_pipe'; import {SlicePipe} from './slice_pipe';
import {UpperCasePipe} from './uppercase_pipe';
export { export {
AsyncPipe, AsyncPipe,
@ -31,11 +32,9 @@ export {
LowerCasePipe, LowerCasePipe,
PercentPipe, PercentPipe,
SlicePipe, SlicePipe,
TitleCasePipe,
UpperCasePipe UpperCasePipe
}; };
/** /**
* A collection of Angular pipes that are likely to be used in each and every application. * A collection of Angular pipes that are likely to be used in each and every application.
*/ */
@ -47,7 +46,6 @@ export const COMMON_PIPES = [
SlicePipe, SlicePipe,
DecimalPipe, DecimalPipe,
PercentPipe, PercentPipe,
TitleCasePipe,
CurrencyPipe, CurrencyPipe,
DatePipe, DatePipe,
I18nPluralPipe, I18nPluralPipe,

View File

@ -1,226 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export enum NumberFormatStyle {
Decimal,
Percent,
Currency,
}
export class NumberFormatter {
static format(
num: number, locale: string, style: NumberFormatStyle,
{minimumIntegerDigits, minimumFractionDigits, maximumFractionDigits, currency,
currencyAsSymbol = false}: {
minimumIntegerDigits?: number,
minimumFractionDigits?: number,
maximumFractionDigits?: number,
currency?: string,
currencyAsSymbol?: boolean
} = {}): string {
const options: Intl.NumberFormatOptions = {
minimumIntegerDigits,
minimumFractionDigits,
maximumFractionDigits,
style: NumberFormatStyle[style].toLowerCase()
};
if (style == NumberFormatStyle.Currency) {
options.currency = currency;
options.currencyDisplay = currencyAsSymbol ? 'symbol' : 'code';
}
return new Intl.NumberFormat(locale, options).format(num);
}
}
type DateFormatterFn = (date: Date, locale: string) => string;
const DATE_FORMATS_SPLIT =
/((?:[^yMLdHhmsazZEwGjJ']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|J+|j+|m+|s+|a|z|Z|G+|w+))(.*)/;
const PATTERN_ALIASES: {[format: string]: DateFormatterFn} = {
// Keys are quoted so they do not get renamed during closure compilation.
'yMMMdjms': datePartGetterFactory(combine([
digitCondition('year', 1),
nameCondition('month', 3),
digitCondition('day', 1),
digitCondition('hour', 1),
digitCondition('minute', 1),
digitCondition('second', 1),
])),
'yMdjm': datePartGetterFactory(combine([
digitCondition('year', 1), digitCondition('month', 1), digitCondition('day', 1),
digitCondition('hour', 1), digitCondition('minute', 1)
])),
'yMMMMEEEEd': datePartGetterFactory(combine([
digitCondition('year', 1), nameCondition('month', 4), nameCondition('weekday', 4),
digitCondition('day', 1)
])),
'yMMMMd': datePartGetterFactory(
combine([digitCondition('year', 1), nameCondition('month', 4), digitCondition('day', 1)])),
'yMMMd': datePartGetterFactory(
combine([digitCondition('year', 1), nameCondition('month', 3), digitCondition('day', 1)])),
'yMd': datePartGetterFactory(
combine([digitCondition('year', 1), digitCondition('month', 1), digitCondition('day', 1)])),
'jms': datePartGetterFactory(combine(
[digitCondition('hour', 1), digitCondition('second', 1), digitCondition('minute', 1)])),
'jm': datePartGetterFactory(combine([digitCondition('hour', 1), digitCondition('minute', 1)]))
};
const DATE_FORMATS: {[format: string]: DateFormatterFn} = {
// Keys are quoted so they do not get renamed.
'yyyy': datePartGetterFactory(digitCondition('year', 4)),
'yy': datePartGetterFactory(digitCondition('year', 2)),
'y': datePartGetterFactory(digitCondition('year', 1)),
'MMMM': datePartGetterFactory(nameCondition('month', 4)),
'MMM': datePartGetterFactory(nameCondition('month', 3)),
'MM': datePartGetterFactory(digitCondition('month', 2)),
'M': datePartGetterFactory(digitCondition('month', 1)),
'LLLL': datePartGetterFactory(nameCondition('month', 4)),
'L': datePartGetterFactory(nameCondition('month', 1)),
'dd': datePartGetterFactory(digitCondition('day', 2)),
'd': datePartGetterFactory(digitCondition('day', 1)),
'HH': digitModifier(
hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 2), false)))),
'H': hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), false))),
'hh': digitModifier(
hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 2), true)))),
'h': hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), true))),
'jj': datePartGetterFactory(digitCondition('hour', 2)),
'j': datePartGetterFactory(digitCondition('hour', 1)),
'mm': digitModifier(datePartGetterFactory(digitCondition('minute', 2))),
'm': datePartGetterFactory(digitCondition('minute', 1)),
'ss': digitModifier(datePartGetterFactory(digitCondition('second', 2))),
's': datePartGetterFactory(digitCondition('second', 1)),
// while ISO 8601 requires fractions to be prefixed with `.` or `,`
// we can be just safely rely on using `sss` since we currently don't support single or two digit
// fractions
'sss': datePartGetterFactory(digitCondition('second', 3)),
'EEEE': datePartGetterFactory(nameCondition('weekday', 4)),
'EEE': datePartGetterFactory(nameCondition('weekday', 3)),
'EE': datePartGetterFactory(nameCondition('weekday', 2)),
'E': datePartGetterFactory(nameCondition('weekday', 1)),
'a': hourClockExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), true))),
'Z': timeZoneGetter('short'),
'z': timeZoneGetter('long'),
'ww': datePartGetterFactory({}), // Week of year, padded (00-53). Week 01 is the week with the
// first Thursday of the year. not support ?
'w':
datePartGetterFactory({}), // Week of year (0-53). Week 1 is the week with the first Thursday
// of the year not support ?
'G': datePartGetterFactory(nameCondition('era', 1)),
'GG': datePartGetterFactory(nameCondition('era', 2)),
'GGG': datePartGetterFactory(nameCondition('era', 3)),
'GGGG': datePartGetterFactory(nameCondition('era', 4))
};
function digitModifier(inner: DateFormatterFn): DateFormatterFn {
return function(date: Date, locale: string): string {
const result = inner(date, locale);
return result.length == 1 ? '0' + result : result;
};
}
function hourClockExtractor(inner: DateFormatterFn): DateFormatterFn {
return function(date: Date, locale: string): string { return inner(date, locale).split(' ')[1]; };
}
function hourExtractor(inner: DateFormatterFn): DateFormatterFn {
return function(date: Date, locale: string): string { return inner(date, locale).split(' ')[0]; };
}
function intlDateFormat(date: Date, locale: string, options: Intl.DateTimeFormatOptions): string {
return new Intl.DateTimeFormat(locale, options).format(date).replace(/[\u200e\u200f]/g, '');
}
function timeZoneGetter(timezone: string): DateFormatterFn {
// To workaround `Intl` API restriction for single timezone let format with 24 hours
const options = {hour: '2-digit', hour12: false, timeZoneName: timezone};
return function(date: Date, locale: string): string {
const result = intlDateFormat(date, locale, options);
// Then extract first 3 letters that related to hours
return result ? result.substring(3) : '';
};
}
function hour12Modify(
options: Intl.DateTimeFormatOptions, value: boolean): Intl.DateTimeFormatOptions {
options.hour12 = value;
return options;
}
function digitCondition(prop: string, len: number): Intl.DateTimeFormatOptions {
const result: {[k: string]: string} = {};
result[prop] = len === 2 ? '2-digit' : 'numeric';
return result;
}
function nameCondition(prop: string, len: number): Intl.DateTimeFormatOptions {
const result: {[k: string]: string} = {};
if (len < 4) {
result[prop] = len > 1 ? 'short' : 'narrow';
} else {
result[prop] = 'long';
}
return result;
}
function combine(options: Intl.DateTimeFormatOptions[]): Intl.DateTimeFormatOptions {
return (<any>Object).assign({}, ...options);
}
function datePartGetterFactory(ret: Intl.DateTimeFormatOptions): DateFormatterFn {
return (date: Date, locale: string): string => intlDateFormat(date, locale, ret);
}
const DATE_FORMATTER_CACHE = new Map<string, string[]>();
function dateFormatter(format: string, date: Date, locale: string): string {
const fn = PATTERN_ALIASES[format];
if (fn) return fn(date, locale);
const cacheKey = format;
let parts = DATE_FORMATTER_CACHE.get(cacheKey);
if (!parts) {
parts = [];
let match: RegExpExecArray;
DATE_FORMATS_SPLIT.exec(format);
while (format) {
match = DATE_FORMATS_SPLIT.exec(format);
if (match) {
parts = parts.concat(match.slice(1));
format = parts.pop();
} else {
parts.push(format);
format = null;
}
}
DATE_FORMATTER_CACHE.set(cacheKey, parts);
}
return parts.reduce((text, part) => {
const fn = DATE_FORMATS[part];
return text + (fn ? fn(date, locale) : partToTime(part));
}, '');
}
function partToTime(part: string): string {
return part === '\'\'' ? '\'' : part.replace(/(^'|'$)/g, '').replace(/''/g, '\'');
}
export class DateFormatter {
static format(date: Date, locale: string, pattern: string): string {
return dateFormatter(pattern, date, locale);
}
}

View File

@ -8,6 +8,10 @@
import {Pipe, PipeTransform} from '@angular/core'; import {Pipe, PipeTransform} from '@angular/core';
import {Json} from '../facade/lang';
/** /**
* @ngModule CommonModule * @ngModule CommonModule
* @whatItDoes Converts value into JSON string. * @whatItDoes Converts value into JSON string.
@ -23,5 +27,5 @@ import {Pipe, PipeTransform} from '@angular/core';
*/ */
@Pipe({name: 'json', pure: false}) @Pipe({name: 'json', pure: false})
export class JsonPipe implements PipeTransform { export class JsonPipe implements PipeTransform {
transform(value: any): string { return JSON.stringify(value, null, 2); } transform(value: any): string { return Json.stringify(value); }
} }

View File

@ -0,0 +1,37 @@
/**
* @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 {Pipe, PipeTransform} from '@angular/core';
import {isBlank} from '../facade/lang';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
/**
* @ngModule CommonModule
* @whatItDoes Transforms string to lowercase.
* @howToUse `expression | lowercase`
* @description
*
* Converts value into lowercase string using `String.prototype.toLowerCase()`.
*
* ### Example
*
* {@example common/pipes/ts/lowerupper_pipe.ts region='LowerUpperPipe'}
*
* @stable
*/
@Pipe({name: 'lowercase'})
export class LowerCasePipe implements PipeTransform {
transform(value: string): string {
if (isBlank(value)) return value;
if (typeof value !== 'string') {
throw new InvalidPipeArgumentError(LowerCasePipe, value);
}
return value.toLowerCase();
}
}

View File

@ -8,9 +8,9 @@
import {Inject, LOCALE_ID, Pipe, PipeTransform, Type} from '@angular/core'; import {Inject, LOCALE_ID, Pipe, PipeTransform, Type} from '@angular/core';
import {NumberFormatStyle, NumberFormatter} from '../facade/intl';
import {NumberWrapper, isBlank, isPresent} from '../facade/lang'; import {NumberWrapper, isBlank, isPresent} from '../facade/lang';
import {NumberFormatStyle, NumberFormatter} from './intl';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error'; import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
const _NUMBER_FORMAT_REGEXP = /^(\d+)?\.((\d+)(-(\d+))?)?$/; const _NUMBER_FORMAT_REGEXP = /^(\d+)?\.((\d+)(-(\d+))?)?$/;
@ -37,7 +37,7 @@ function formatNumber(
} }
if (digits) { if (digits) {
const parts = digits.match(_NUMBER_FORMAT_REGEXP); let parts = digits.match(_NUMBER_FORMAT_REGEXP);
if (parts === null) { if (parts === null) {
throw new Error(`${digits} is not a valid digit info for number pipes`); throw new Error(`${digits} is not a valid digit info for number pipes`);
} }

View File

@ -0,0 +1,36 @@
/**
* @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 {Pipe, PipeTransform} from '@angular/core';
import {isBlank} from '../facade/lang';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
/**
* @ngModule CommonModule
* @whatItDoes Transforms string to uppercase.
* @howToUse `expression | uppercase`
* @description
*
* Converts value into lowercase string using `String.prototype.toUpperCase()`.
*
* ### Example
*
* {@example common/pipes/ts/lowerupper_pipe.ts region='LowerUpperPipe'}
*
* @stable
*/
@Pipe({name: 'uppercase'})
export class UpperCasePipe implements PipeTransform {
transform(value: string): string {
if (isBlank(value)) return value;
if (typeof value !== 'string') {
throw new InvalidPipeArgumentError(UpperCasePipe, value);
}
return value.toUpperCase();
}
}

View File

@ -1,19 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* @module
* @description
* Entry point for all public APIs of the common package.
*/
import {Version} from '@angular/core';
/**
* @stable
*/
export const VERSION = new Version('0.0.0-PLACEHOLDER');

View File

@ -8,6 +8,7 @@
import {Component} from '@angular/core'; import {Component} from '@angular/core';
import {ComponentFixture, TestBed, async} from '@angular/core/testing'; import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
export function main() { export function main() {
describe('binding to CSS class list', () => { describe('binding to CSS class list', () => {
@ -65,7 +66,7 @@ export function main() {
it('should add and remove classes based on changes to the expression object', async(() => { it('should add and remove classes based on changes to the expression object', async(() => {
fixture = createTestComponent('<div [ngClass]="objExpr"></div>'); fixture = createTestComponent('<div [ngClass]="objExpr"></div>');
const objExpr = getComponent().objExpr; let objExpr = getComponent().objExpr;
detectChangesAndExpectClassName('foo'); detectChangesAndExpectClassName('foo');
@ -134,7 +135,7 @@ export function main() {
it('should add and remove classes based on changes to the expression', async(() => { it('should add and remove classes based on changes to the expression', async(() => {
fixture = createTestComponent('<div [ngClass]="arrExpr"></div>'); fixture = createTestComponent('<div [ngClass]="arrExpr"></div>');
const arrExpr = getComponent().arrExpr; let arrExpr = getComponent().arrExpr;
detectChangesAndExpectClassName('foo'); detectChangesAndExpectClassName('foo');
arrExpr.push('bar'); arrExpr.push('bar');
@ -186,13 +187,6 @@ export function main() {
getComponent().arrExpr = ['foo bar baz foobar']; getComponent().arrExpr = ['foo bar baz foobar'];
detectChangesAndExpectClassName('foo bar baz foobar'); detectChangesAndExpectClassName('foo bar baz foobar');
})); }));
it('should throw with descriptive error message when CSS class is not a string', () => {
fixture = createTestComponent(`<div [ngClass]="['foo', {}]"></div>`);
expect(() => fixture.detectChanges())
.toThrowError(
/NgClass can only toggle CSS classes expressed as strings, got \[object Object\]/);
});
}); });
describe('expressions evaluating to sets', () => { describe('expressions evaluating to sets', () => {
@ -259,7 +253,7 @@ export function main() {
it('should co-operate with the class attribute', async(() => { it('should co-operate with the class attribute', async(() => {
fixture = createTestComponent('<div [ngClass]="objExpr" class="init foo"></div>'); fixture = createTestComponent('<div [ngClass]="objExpr" class="init foo"></div>');
const objExpr = getComponent().objExpr; let objExpr = getComponent().objExpr;
objExpr['bar'] = true; objExpr['bar'] = true;
detectChangesAndExpectClassName('init foo bar'); detectChangesAndExpectClassName('init foo bar');
@ -273,7 +267,7 @@ export function main() {
it('should co-operate with the interpolated class attribute', async(() => { it('should co-operate with the interpolated class attribute', async(() => {
fixture = createTestComponent(`<div [ngClass]="objExpr" class="{{'init foo'}}"></div>`); fixture = createTestComponent(`<div [ngClass]="objExpr" class="{{'init foo'}}"></div>`);
const objExpr = getComponent().objExpr; let objExpr = getComponent().objExpr;
objExpr['bar'] = true; objExpr['bar'] = true;
detectChangesAndExpectClassName(`init foo bar`); detectChangesAndExpectClassName(`init foo bar`);
@ -288,7 +282,7 @@ export function main() {
it('should co-operate with the class attribute and binding to it', async(() => { it('should co-operate with the class attribute and binding to it', async(() => {
fixture = fixture =
createTestComponent(`<div [ngClass]="objExpr" class="init" [class]="'foo'"></div>`); createTestComponent(`<div [ngClass]="objExpr" class="init" [class]="'foo'"></div>`);
const objExpr = getComponent().objExpr; let objExpr = getComponent().objExpr;
objExpr['bar'] = true; objExpr['bar'] = true;
detectChangesAndExpectClassName(`init foo bar`); detectChangesAndExpectClassName(`init foo bar`);
@ -304,7 +298,7 @@ export function main() {
const template = const template =
'<div class="init foo" [ngClass]="objExpr" [class.baz]="condition"></div>'; '<div class="init foo" [ngClass]="objExpr" [class.baz]="condition"></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
const objExpr = getComponent().objExpr; let objExpr = getComponent().objExpr;
detectChangesAndExpectClassName('init foo baz'); detectChangesAndExpectClassName('init foo baz');
@ -322,7 +316,7 @@ export function main() {
async(() => { async(() => {
const template = '<div class="init" [ngClass]="objExpr" [class]="strExpr"></div>'; const template = '<div class="init" [ngClass]="objExpr" [class]="strExpr"></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
const cmp = getComponent(); let cmp = getComponent();
detectChangesAndExpectClassName('init foo'); detectChangesAndExpectClassName('init foo');

View File

@ -294,27 +294,6 @@ export function main() {
})); }));
describe('track by', () => { describe('track by', () => {
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]="value"></template>`;
fixture = createTestComponent(template);
fixture.componentInstance.value = 0;
fixture.detectChanges();
}));
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(() => { it('should set the context to the component instance', async(() => {
const template = const template =
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByContext.bind(this)"></template>`; `<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByContext.bind(this)"></template>`;
@ -354,7 +333,6 @@ export function main() {
getComponent().items = [{'id': 'a', 'color': 'red'}]; getComponent().items = [{'id': 'a', 'color': 'red'}];
detectChangesAndExpectText('red'); detectChangesAndExpectText('red');
})); }));
it('should move items around and keep them updated ', async(() => { it('should move items around and keep them updated ', async(() => {
const template = const template =
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`; `<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`;
@ -390,7 +368,6 @@ class Foo {
@Component({selector: 'test-cmp', template: ''}) @Component({selector: 'test-cmp', template: ''})
class TestComponent { class TestComponent {
@ContentChild(TemplateRef) contentTpl: TemplateRef<Object>; @ContentChild(TemplateRef) contentTpl: TemplateRef<Object>;
value: any;
items: any[] = [1, 2]; items: any[] = [1, 2];
trackById(index: number, item: any): string { return item['id']; } trackById(index: number, item: any): string { return item['id']; }
trackByIndex(index: number, item: any): number { return index; } trackByIndex(index: number, item: any): number { return index; }

View File

@ -153,78 +153,6 @@ export function main() {
expect(getDOM().hasClass(getDOM().querySelector(fixture.nativeElement, 'span'), 'foo')) expect(getDOM().hasClass(getDOM().querySelector(fixture.nativeElement, 'span'), 'foo'))
.toBe(true); .toBe(true);
})); }));
describe('else', () => {
it('should support else', async(() => {
const template = '<div>' +
'<span *ngIf="booleanCondition; else elseBlock">TRUE</span>' +
'<template #elseBlock>FALSE</template>' +
'</div>';
fixture = createTestComponent(template);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('TRUE');
getComponent().booleanCondition = false;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('FALSE');
}));
it('should support then and else', async(() => {
const template = '<div>' +
'<span *ngIf="booleanCondition; then thenBlock; else elseBlock">IGNORE</span>' +
'<template #thenBlock>THEN</template>' +
'<template #elseBlock>ELSE</template>' +
'</div>';
fixture = createTestComponent(template);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('THEN');
getComponent().booleanCondition = false;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('ELSE');
}));
it('should support dynamic else', async(() => {
const template = '<div>' +
'<span *ngIf="booleanCondition; else nestedBooleanCondition ? b1 : b2">TRUE</span>' +
'<template #b1>FALSE1</template>' +
'<template #b2>FALSE2</template>' +
'</div>';
fixture = createTestComponent(template);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('TRUE');
getComponent().booleanCondition = false;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('FALSE1');
getComponent().nestedBooleanCondition = false;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('FALSE2');
}));
it('should support binding to variable', async(() => {
const template = '<div>' +
'<span *ngIf="booleanCondition; else elseBlock; let v">{{v}}</span>' +
'<template #elseBlock let-v>{{v}}</template>' +
'</div>';
fixture = createTestComponent(template);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('true');
getComponent().booleanCondition = false;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('false');
}));
});
}); });
} }

View File

@ -7,8 +7,8 @@
*/ */
import {CommonModule} from '@angular/common'; import {CommonModule} from '@angular/common';
import {Attribute, Component, Directive} from '@angular/core'; import {Component} from '@angular/core';
import {ComponentFixture, TestBed} from '@angular/core/testing'; import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/matchers'; import {expect} from '@angular/platform-browser/testing/matchers';
export function main() { export function main() {
@ -32,7 +32,7 @@ export function main() {
}); });
describe('switch value changes', () => { describe('switch value changes', () => {
it('should switch amongst when values', () => { it('should switch amongst when values', async(() => {
const template = '<div>' + const template = '<div>' +
'<ul [ngSwitch]="switchValue">' + '<ul [ngSwitch]="switchValue">' +
'<template ngSwitchCase="a"><li>when a</li></template>' + '<template ngSwitchCase="a"><li>when a</li></template>' +
@ -48,9 +48,28 @@ export function main() {
getComponent().switchValue = 'b'; getComponent().switchValue = 'b';
detectChangesAndExpectText('when b'); detectChangesAndExpectText('when b');
}); }));
it('should switch amongst when values with fallback to default', () => { // TODO(robwormald): deprecate and remove
it('should switch amongst when values using switchCase', async(() => {
const template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<template ngSwitchCase="a"><li>when a</li></template>' +
'<template ngSwitchCase="b"><li>when b</li></template>' +
'</ul></div>';
fixture = createTestComponent(template);
detectChangesAndExpectText('');
getComponent().switchValue = 'a';
detectChangesAndExpectText('when a');
getComponent().switchValue = 'b';
detectChangesAndExpectText('when b');
}));
it('should switch amongst when values with fallback to default', async(() => {
const template = '<div>' + const template = '<div>' +
'<ul [ngSwitch]="switchValue">' + '<ul [ngSwitch]="switchValue">' +
'<li template="ngSwitchCase \'a\'">when a</li>' + '<li template="ngSwitchCase \'a\'">when a</li>' +
@ -65,12 +84,9 @@ export function main() {
getComponent().switchValue = 'b'; getComponent().switchValue = 'b';
detectChangesAndExpectText('when default'); detectChangesAndExpectText('when default');
}));
getComponent().switchValue = 'c'; it('should support multiple whens with the same value', async(() => {
detectChangesAndExpectText('when default');
});
it('should support multiple whens with the same value', () => {
const template = '<div>' + const template = '<div>' +
'<ul [ngSwitch]="switchValue">' + '<ul [ngSwitch]="switchValue">' +
'<template ngSwitchCase="a"><li>when a1;</li></template>' + '<template ngSwitchCase="a"><li>when a1;</li></template>' +
@ -89,11 +105,11 @@ export function main() {
getComponent().switchValue = 'b'; getComponent().switchValue = 'b';
detectChangesAndExpectText('when b1;when b2;'); detectChangesAndExpectText('when b1;when b2;');
}); }));
}); });
describe('when values changes', () => { describe('when values changes', () => {
it('should switch amongst when values', () => { it('should switch amongst when values', async(() => {
const template = '<div>' + const template = '<div>' +
'<ul [ngSwitch]="switchValue">' + '<ul [ngSwitch]="switchValue">' +
'<template [ngSwitchCase]="when1"><li>when 1;</li></template>' + '<template [ngSwitchCase]="when1"><li>when 1;</li></template>' +
@ -118,67 +134,7 @@ export function main() {
getComponent().when1 = 'd'; getComponent().when1 = 'd';
detectChangesAndExpectText('when default;'); detectChangesAndExpectText('when default;');
}); }));
});
describe('corner cases', () => {
it('should not create the default case if another case matches', () => {
const log: string[] = [];
@Directive({selector: '[test]'})
class TestDirective {
constructor(@Attribute('test') test: string) { log.push(test); }
}
const template = '<div [ngSwitch]="switchValue">' +
'<div *ngSwitchCase="\'a\'" test="aCase"></div>' +
'<div *ngSwitchDefault test="defaultCase"></div>' +
'</div>';
TestBed.configureTestingModule({declarations: [TestDirective]});
TestBed.overrideComponent(TestComponent, {set: {template: template}})
.createComponent(TestComponent);
const fixture = TestBed.createComponent(TestComponent);
fixture.componentInstance.switchValue = 'a';
fixture.detectChanges();
expect(log).toEqual(['aCase']);
});
it('should create the default case if there is no other case', () => {
const template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<template ngSwitchDefault><li>when default1;</li></template>' +
'<template ngSwitchDefault><li>when default2;</li></template>' +
'</ul></div>';
fixture = createTestComponent(template);
detectChangesAndExpectText('when default1;when default2;');
});
it('should allow defaults before cases', () => {
const template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<template ngSwitchDefault><li>when default1;</li></template>' +
'<template ngSwitchDefault><li>when default2;</li></template>' +
'<template ngSwitchCase="a"><li>when a1;</li></template>' +
'<template ngSwitchCase="b"><li>when b1;</li></template>' +
'<template ngSwitchCase="a"><li>when a2;</li></template>' +
'<template ngSwitchCase="b"><li>when b2;</li></template>' +
'</ul></div>';
fixture = createTestComponent(template);
detectChangesAndExpectText('when default1;when default2;');
getComponent().switchValue = 'a';
detectChangesAndExpectText('when a1;when a2;');
getComponent().switchValue = 'b';
detectChangesAndExpectText('when b1;when b2;');
});
}); });
}); });
} }

View File

@ -7,10 +7,13 @@
*/ */
import {LOCALE_ID} from '@angular/core'; import {LOCALE_ID} from '@angular/core';
import {TestBed, inject} from '@angular/core/testing'; import {TestBed} from '@angular/core/testing';
import {beforeEach, describe, inject, it} from '@angular/core/testing/testing_internal';
import {expect} from '@angular/platform-browser/testing/matchers';
import {NgLocaleLocalization, NgLocalization, getPluralCategory} from '../src/localization'; import {NgLocaleLocalization, NgLocalization, getPluralCategory} from '../src/localization';
export function main() { export function main() {
describe('l10n', () => { describe('l10n', () => {
@ -138,23 +141,6 @@ export function main() {
expect(getPluralCategory(5, ['one', 'other', '=5'], l10n)).toEqual('=5'); expect(getPluralCategory(5, ['one', 'other', '=5'], l10n)).toEqual('=5');
expect(getPluralCategory(6, ['one', 'other', '=5'], l10n)).toEqual('other'); expect(getPluralCategory(6, ['one', 'other', '=5'], l10n)).toEqual('other');
}); });
it('should fallback to other when the case is not present', () => {
const l10n = new NgLocaleLocalization('ro');
expect(getPluralCategory(1, ['one', 'other'], l10n)).toEqual('one');
// 2 -> 'few'
expect(getPluralCategory(2, ['one', 'other'], l10n)).toEqual('other');
});
describe('errors', () => {
it('should report an error when the "other" category is not present', () => {
expect(() => {
const l10n = new NgLocaleLocalization('ro');
// 2 -> 'few'
getPluralCategory(2, ['one'], l10n);
}).toThrowError('No plural message found for value "2"');
});
});
}); });
}); });
} }

View File

@ -19,10 +19,10 @@ export function main() {
describe('AsyncPipe', () => { describe('AsyncPipe', () => {
describe('Observable', () => { describe('Observable', () => {
let emitter: EventEmitter<any>; var emitter: EventEmitter<any>;
let pipe: AsyncPipe; var pipe: AsyncPipe;
let ref: any; var ref: any;
const message = {}; var message = {};
beforeEach(() => { beforeEach(() => {
emitter = new EventEmitter(); emitter = new EventEmitter();
@ -62,7 +62,7 @@ export function main() {
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
pipe.transform(emitter); pipe.transform(emitter);
const newEmitter = new EventEmitter(); var newEmitter = new EventEmitter();
expect(pipe.transform(newEmitter)).toBe(null); expect(pipe.transform(newEmitter)).toBe(null);
emitter.emit(message); emitter.emit(message);
@ -104,14 +104,14 @@ export function main() {
}); });
describe('Promise', () => { describe('Promise', () => {
const message = new Object(); var message = new Object();
let pipe: AsyncPipe; var pipe: AsyncPipe;
let resolve: (result: any) => void; var resolve: (result: any) => void;
let reject: (error: any) => void; var reject: (error: any) => void;
let promise: Promise<any>; var promise: Promise<any>;
let ref: SpyChangeDetectorRef; var ref: SpyChangeDetectorRef;
// adds longer timers for passing tests in IE // adds longer timers for passing tests in IE
const timer = (getDOM() && browserDetection.isIE) ? 50 : 10; var timer = (getDOM() && browserDetection.isIE) ? 50 : 10;
beforeEach(() => { beforeEach(() => {
promise = new Promise((res, rej) => { promise = new Promise((res, rej) => {
@ -154,7 +154,7 @@ export function main() {
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
pipe.transform(promise); pipe.transform(promise);
promise = new Promise<any>(() => {}); var promise = new Promise<any>(() => {});
expect(pipe.transform(promise)).toBe(null); expect(pipe.transform(promise)).toBe(null);
// this should not affect the pipe, so it should return WrappedValue // this should not affect the pipe, so it should return WrappedValue
@ -168,7 +168,7 @@ export function main() {
it('should request a change detection check upon receiving a new value', it('should request a change detection check upon receiving a new value',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const markForCheck = ref.spy('markForCheck'); var markForCheck = ref.spy('markForCheck');
pipe.transform(promise); pipe.transform(promise);
resolve(message); resolve(message);
@ -202,14 +202,14 @@ export function main() {
describe('null', () => { describe('null', () => {
it('should return null when given null', () => { it('should return null when given null', () => {
const pipe = new AsyncPipe(null); var pipe = new AsyncPipe(null);
expect(pipe.transform(null)).toEqual(null); expect(pipe.transform(null)).toEqual(null);
}); });
}); });
describe('other types', () => { describe('other types', () => {
it('should throw when given an invalid object', () => { it('should throw when given an invalid object', () => {
const pipe = new AsyncPipe(null); var pipe = new AsyncPipe(null);
expect(() => pipe.transform(<any>'some bogus object')).toThrowError(); expect(() => pipe.transform(<any>'some bogus object')).toThrowError();
}); });
}); });

View File

@ -1,67 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {LowerCasePipe, TitleCasePipe, UpperCasePipe} from '@angular/common';
export function main() {
describe('LowerCasePipe', () => {
let pipe: LowerCasePipe;
beforeEach(() => { pipe = new LowerCasePipe(); });
it('should return lowercase', () => { expect(pipe.transform('FOO')).toEqual('foo'); });
it('should lowercase when there is a new value', () => {
expect(pipe.transform('FOO')).toEqual('foo');
expect(pipe.transform('BAr')).toEqual('bar');
});
it('should not support other objects',
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
});
describe('TitleCasePipe', () => {
let pipe: TitleCasePipe;
beforeEach(() => { pipe = new TitleCasePipe(); });
it('should return titlecase', () => { expect(pipe.transform('foo')).toEqual('Foo'); });
it('should return titlecase for subsequent words',
() => { expect(pipe.transform('one TWO Three fouR')).toEqual('One Two Three Four'); });
it('should support empty strings', () => { expect(pipe.transform('')).toEqual(''); });
it('should persist whitespace',
() => { expect(pipe.transform('one two')).toEqual('One Two'); });
it('should titlecase when there is a new value', () => {
expect(pipe.transform('bar')).toEqual('Bar');
expect(pipe.transform('foo')).toEqual('Foo');
});
it('should not support other objects',
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
});
describe('UpperCasePipe', () => {
let pipe: UpperCasePipe;
beforeEach(() => { pipe = new UpperCasePipe(); });
it('should return uppercase', () => { expect(pipe.transform('foo')).toEqual('FOO'); });
it('should uppercase when there is a new value', () => {
expect(pipe.transform('foo')).toEqual('FOO');
expect(pipe.transform('bar')).toEqual('BAR');
});
it('should not support other objects',
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
});
}

View File

@ -8,18 +8,13 @@
import {DatePipe} from '@angular/common'; import {DatePipe} from '@angular/common';
import {PipeResolver} from '@angular/compiler/src/pipe_resolver'; import {PipeResolver} from '@angular/compiler/src/pipe_resolver';
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
import {browserDetection} from '@angular/platform-browser/testing/browser_util'; import {browserDetection} from '@angular/platform-browser/testing/browser_util';
export function main() { export function main() {
describe('DatePipe', () => { describe('DatePipe', () => {
let date: Date; var date: Date;
const isoStringWithoutTime = '2015-01-01'; var pipe: DatePipe;
let pipe: DatePipe;
// Check the transformation of a date into a pattern
function expectDateFormatAs(date: Date | string, pattern: any, output: string): void {
expect(pipe.transform(date, pattern)).toEqual(output);
}
// TODO: reactivate the disabled expectations once emulators are fixed in SauceLabs // TODO: reactivate the disabled expectations once emulators are fixed in SauceLabs
// In some old versions of Chrome in Android emulators, time formatting returns dates in the // In some old versions of Chrome in Android emulators, time formatting returns dates in the
@ -39,9 +34,7 @@ export function main() {
describe('supports', () => { describe('supports', () => {
it('should support date', () => { expect(() => pipe.transform(date)).not.toThrow(); }); it('should support date', () => { expect(() => pipe.transform(date)).not.toThrow(); });
it('should support int', () => { expect(() => pipe.transform(123456789)).not.toThrow(); }); it('should support int', () => { expect(() => pipe.transform(123456789)).not.toThrow(); });
it('should support numeric strings', it('should support numeric strings',
() => { expect(() => pipe.transform('123456789')).not.toThrow(); }); () => { expect(() => pipe.transform('123456789')).not.toThrow(); });
@ -49,143 +42,85 @@ export function main() {
() => { expect(() => pipe.transform('123456789.11')).not.toThrow(); }); () => { expect(() => pipe.transform('123456789.11')).not.toThrow(); });
it('should support ISO string', it('should support ISO string',
() => expect(() => pipe.transform('2015-06-15T21:43:11Z')).not.toThrow()); () => { expect(() => pipe.transform('2015-06-15T21:43:11Z')).not.toThrow(); });
it('should return null for empty string', () => expect(pipe.transform('')).toEqual(null)); it('should not support other objects', () => {
expect(() => pipe.transform({})).toThrow();
it('should support ISO string without time', expect(() => pipe.transform('')).toThrow();
() => { expect(() => pipe.transform(isoStringWithoutTime)).not.toThrow(); }); });
it('should not support other objects',
() => { expect(() => pipe.transform({})).toThrowError(); });
}); });
describe('transform', () => { describe('transform', () => {
it('should format each component correctly', () => { it('should format each component correctly', () => {
const dateFixtures: any = { expect(pipe.transform(date, 'y')).toEqual('2015');
'y': '2015', expect(pipe.transform(date, 'yy')).toEqual('15');
'yy': '15', expect(pipe.transform(date, 'M')).toEqual('6');
'M': '6', expect(pipe.transform(date, 'MM')).toEqual('06');
'MM': '06', expect(pipe.transform(date, 'MMM')).toEqual('Jun');
'MMM': 'Jun', expect(pipe.transform(date, 'MMMM')).toEqual('June');
'MMMM': 'June', expect(pipe.transform(date, 'd')).toEqual('15');
'd': '15', expect(pipe.transform(date, 'E')).toEqual('Mon');
'dd': '15', expect(pipe.transform(date, 'EEEE')).toEqual('Monday');
'EEE': 'Mon',
'EEEE': 'Monday'
};
const isoStringWithoutTimeFixtures: any = {
'y': '2015',
'yy': '15',
'M': '1',
'MM': '01',
'MMM': 'Jan',
'MMMM': 'January',
'd': '1',
'dd': '01',
'EEE': 'Thu',
'EEEE': 'Thursday'
};
if (!browserDetection.isOldChrome) { if (!browserDetection.isOldChrome) {
dateFixtures['h'] = '9'; expect(pipe.transform(date, 'h')).toEqual('9');
dateFixtures['hh'] = '09'; expect(pipe.transform(date, 'hh')).toEqual('09');
dateFixtures['j'] = '9 AM'; expect(pipe.transform(date, 'j')).toEqual('9 AM');
isoStringWithoutTimeFixtures['h'] = '12';
isoStringWithoutTimeFixtures['hh'] = '12';
isoStringWithoutTimeFixtures['j'] = '12 AM';
} }
// IE and Edge can't format a date to minutes and seconds without hours // IE and Edge can't format a date to minutes and seconds without hours
if (!browserDetection.isEdge && !browserDetection.isIE || if (!browserDetection.isEdge && !browserDetection.isIE ||
!browserDetection.supportsNativeIntlApi) { !browserDetection.supportsNativeIntlApi) {
if (!browserDetection.isOldChrome) { if (!browserDetection.isOldChrome) {
dateFixtures['HH'] = '09'; expect(pipe.transform(date, 'HH')).toEqual('09');
isoStringWithoutTimeFixtures['HH'] = '00';
} }
dateFixtures['E'] = 'M'; expect(pipe.transform(date, 'm')).toEqual('3');
dateFixtures['L'] = 'J'; expect(pipe.transform(date, 's')).toEqual('1');
dateFixtures['m'] = '3'; expect(pipe.transform(date, 'mm')).toEqual('03');
dateFixtures['s'] = '1'; expect(pipe.transform(date, 'ss')).toEqual('01');
dateFixtures['mm'] = '03';
dateFixtures['ss'] = '01';
isoStringWithoutTimeFixtures['m'] = '0';
isoStringWithoutTimeFixtures['s'] = '0';
isoStringWithoutTimeFixtures['mm'] = '00';
isoStringWithoutTimeFixtures['ss'] = '00';
} }
Object.keys(dateFixtures).forEach((pattern: string) => {
expectDateFormatAs(date, pattern, dateFixtures[pattern]);
});
Object.keys(isoStringWithoutTimeFixtures).forEach((pattern: string) => {
expectDateFormatAs(isoStringWithoutTime, pattern, isoStringWithoutTimeFixtures[pattern]);
});
expect(pipe.transform(date, 'Z')).toBeDefined(); expect(pipe.transform(date, 'Z')).toBeDefined();
}); });
it('should format common multi component patterns', () => { it('should format common multi component patterns', () => {
const dateFixtures: any = { expect(pipe.transform(date, 'E, M/d/y')).toEqual('Mon, 6/15/2015');
'EEE, M/d/y': 'Mon, 6/15/2015', expect(pipe.transform(date, 'E, M/d')).toEqual('Mon, 6/15');
'EEE, M/d': 'Mon, 6/15', expect(pipe.transform(date, 'MMM d')).toEqual('Jun 15');
'MMM d': 'Jun 15', expect(pipe.transform(date, 'dd/MM/yyyy')).toEqual('15/06/2015');
'dd/MM/yyyy': '15/06/2015', expect(pipe.transform(date, 'MM/dd/yyyy')).toEqual('06/15/2015');
'MM/dd/yyyy': '06/15/2015', expect(pipe.transform(date, 'yMEd')).toEqual('20156Mon15');
'yMEEEd': '20156Mon15', expect(pipe.transform(date, 'MEd')).toEqual('6Mon15');
'MEEEd': '6Mon15', expect(pipe.transform(date, 'MMMd')).toEqual('Jun15');
'MMMd': 'Jun15', expect(pipe.transform(date, 'yMMMMEEEEd')).toEqual('Monday, June 15, 2015');
'yMMMMEEEEd': 'Monday, June 15, 2015'
};
// IE and Edge can't format a date to minutes and seconds without hours // IE and Edge can't format a date to minutes and seconds without hours
if (!browserDetection.isEdge && !browserDetection.isIE || if (!browserDetection.isEdge && !browserDetection.isIE ||
!browserDetection.supportsNativeIntlApi) { !browserDetection.supportsNativeIntlApi) {
dateFixtures['ms'] = '31'; expect(pipe.transform(date, 'ms')).toEqual('31');
} }
if (!browserDetection.isOldChrome) { if (!browserDetection.isOldChrome) {
dateFixtures['jm'] = '9:03 AM'; expect(pipe.transform(date, 'jm')).toEqual('9:03 AM');
} }
Object.keys(dateFixtures).forEach((pattern: string) => {
expectDateFormatAs(date, pattern, dateFixtures[pattern]);
});
}); });
it('should format with pattern aliases', () => { it('should format with pattern aliases', () => {
const dateFixtures: any = {
'MM/dd/yyyy': '06/15/2015',
'fullDate': 'Monday, June 15, 2015',
'longDate': 'June 15, 2015',
'mediumDate': 'Jun 15, 2015',
'shortDate': '6/15/2015'
};
if (!browserDetection.isOldChrome) { if (!browserDetection.isOldChrome) {
// IE and Edge do not add a coma after the year in these 2 cases // IE and Edge do not add a coma after the year in these 2 cases
if ((browserDetection.isEdge || browserDetection.isIE) && if ((browserDetection.isEdge || browserDetection.isIE) &&
browserDetection.supportsNativeIntlApi) { browserDetection.supportsNativeIntlApi) {
dateFixtures['medium'] = 'Jun 15, 2015 9:03:01 AM'; expect(pipe.transform(date, 'medium')).toEqual('Jun 15, 2015 9:03:01 AM');
dateFixtures['short'] = '6/15/2015 9:03 AM'; expect(pipe.transform(date, 'short')).toEqual('6/15/2015 9:03 AM');
} else { } else {
dateFixtures['medium'] = 'Jun 15, 2015, 9:03:01 AM'; expect(pipe.transform(date, 'medium')).toEqual('Jun 15, 2015, 9:03:01 AM');
dateFixtures['short'] = '6/15/2015, 9:03 AM'; expect(pipe.transform(date, 'short')).toEqual('6/15/2015, 9:03 AM');
} }
} }
expect(pipe.transform(date, 'MM/dd/yyyy')).toEqual('06/15/2015');
expect(pipe.transform(date, 'fullDate')).toEqual('Monday, June 15, 2015');
expect(pipe.transform(date, 'longDate')).toEqual('June 15, 2015');
expect(pipe.transform(date, 'mediumDate')).toEqual('Jun 15, 2015');
expect(pipe.transform(date, 'shortDate')).toEqual('6/15/2015');
if (!browserDetection.isOldChrome) { if (!browserDetection.isOldChrome) {
dateFixtures['mediumTime'] = '9:03:01 AM'; expect(pipe.transform(date, 'mediumTime')).toEqual('9:03:01 AM');
dateFixtures['shortTime'] = '9:03 AM'; expect(pipe.transform(date, 'shortTime')).toEqual('9:03 AM');
} }
Object.keys(dateFixtures).forEach((pattern: string) => {
expectDateFormatAs(date, pattern, dateFixtures[pattern]);
});
}); });
it('should remove bidi control characters', it('should remove bidi control characters',

View File

@ -12,10 +12,10 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_in
export function main() { export function main() {
describe('I18nPluralPipe', () => { describe('I18nPluralPipe', () => {
let localization: NgLocalization; var localization: NgLocalization;
let pipe: I18nPluralPipe; var pipe: I18nPluralPipe;
const mapping = { var mapping = {
'=0': 'No messages.', '=0': 'No messages.',
'=1': 'One message.', '=1': 'One message.',
'many': 'Many messages.', 'many': 'Many messages.',
@ -32,27 +32,27 @@ export function main() {
describe('transform', () => { describe('transform', () => {
it('should return 0 text if value is 0', () => { it('should return 0 text if value is 0', () => {
const val = pipe.transform(0, mapping); var val = pipe.transform(0, mapping);
expect(val).toEqual('No messages.'); expect(val).toEqual('No messages.');
}); });
it('should return 1 text if value is 1', () => { it('should return 1 text if value is 1', () => {
const val = pipe.transform(1, mapping); var val = pipe.transform(1, mapping);
expect(val).toEqual('One message.'); expect(val).toEqual('One message.');
}); });
it('should return category messages', () => { it('should return category messages', () => {
const val = pipe.transform(4, mapping); var val = pipe.transform(4, mapping);
expect(val).toEqual('Many messages.'); expect(val).toEqual('Many messages.');
}); });
it('should interpolate the value into the text where indicated', () => { it('should interpolate the value into the text where indicated', () => {
const val = pipe.transform(6, mapping); var val = pipe.transform(6, mapping);
expect(val).toEqual('There are 6 messages, that is 6.'); expect(val).toEqual('There are 6 messages, that is 6.');
}); });
it('should use "" if value is undefined', () => { it('should use "" if value is undefined', () => {
const val = pipe.transform(void(0), mapping); var val = pipe.transform(void(0), mapping);
expect(val).toEqual(''); expect(val).toEqual('');
}); });

View File

@ -8,35 +8,40 @@
import {I18nSelectPipe} from '@angular/common'; import {I18nSelectPipe} from '@angular/common';
import {PipeResolver} from '@angular/compiler/src/pipe_resolver'; import {PipeResolver} from '@angular/compiler/src/pipe_resolver';
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
export function main() { export function main() {
describe('I18nSelectPipe', () => { describe('I18nSelectPipe', () => {
const pipe: I18nSelectPipe = new I18nSelectPipe(); var pipe: I18nSelectPipe;
const mapping = {'male': 'Invite him.', 'female': 'Invite her.', 'other': 'Invite them.'}; var mapping = {'male': 'Invite him.', 'female': 'Invite her.', 'other': 'Invite them.'};
beforeEach(() => { pipe = new I18nSelectPipe(); });
it('should be marked as pure', it('should be marked as pure',
() => { expect(new PipeResolver().resolve(I18nSelectPipe).pure).toEqual(true); }); () => { expect(new PipeResolver().resolve(I18nSelectPipe).pure).toEqual(true); });
describe('transform', () => { describe('transform', () => {
it('should return the "male" text if value is "male"', () => { it('should return male text if value is male', () => {
const val = pipe.transform('male', mapping); var val = pipe.transform('male', mapping);
expect(val).toEqual('Invite him.'); expect(val).toEqual('Invite him.');
}); });
it('should return the "female" text if value is "female"', () => { it('should return female text if value is female', () => {
const val = pipe.transform('female', mapping); var val = pipe.transform('female', mapping);
expect(val).toEqual('Invite her.'); expect(val).toEqual('Invite her.');
}); });
it('should return the "other" text if value is neither "male" nor "female"', it('should return "" if value is anything other than male or female', () => {
() => { expect(pipe.transform('Anything else', mapping)).toEqual('Invite them.'); }); var val = pipe.transform('Anything else', mapping);
expect(val).toEqual('');
it('should return an empty text if value is null or undefined', () => {
expect(pipe.transform(null, mapping)).toEqual('');
expect(pipe.transform(void 0, mapping)).toEqual('');
}); });
it('should throw on bad arguments', it('should use "" if value is undefined', () => {
var val = pipe.transform(void(0), mapping);
expect(val).toEqual('');
});
it('should not support bad arguments',
() => { expect(() => pipe.transform('male', <any>'hey')).toThrowError(); }); () => { expect(() => pipe.transform('male', <any>'hey')).toThrowError(); });
}); });

View File

@ -11,14 +11,16 @@ import {Component} from '@angular/core';
import {TestBed, async} from '@angular/core/testing'; import {TestBed, async} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/matchers'; import {expect} from '@angular/platform-browser/testing/matchers';
import {Json, StringWrapper} from '../../src/facade/lang';
export function main() { export function main() {
describe('JsonPipe', () => { describe('JsonPipe', () => {
const regNewLine = '\n'; var regNewLine = '\n';
let inceptionObj: any; var inceptionObj: any;
let inceptionObjString: string; var inceptionObjString: string;
let pipe: JsonPipe; var pipe: JsonPipe;
function normalize(obj: string): string { return obj.replace(regNewLine, ''); } function normalize(obj: string): string { return StringWrapper.replace(obj, regNewLine, ''); }
beforeEach(() => { beforeEach(() => {
inceptionObj = {dream: {dream: {dream: 'Limbo'}}}; inceptionObj = {dream: {dream: {dream: 'Limbo'}}};
@ -39,14 +41,14 @@ export function main() {
() => { expect(pipe.transform(inceptionObj)).toEqual(inceptionObjString); }); () => { expect(pipe.transform(inceptionObj)).toEqual(inceptionObjString); });
it('should return JSON-formatted string even when normalized', () => { it('should return JSON-formatted string even when normalized', () => {
const dream1 = normalize(pipe.transform(inceptionObj)); var dream1 = normalize(pipe.transform(inceptionObj));
const dream2 = normalize(inceptionObjString); var dream2 = normalize(inceptionObjString);
expect(dream1).toEqual(dream2); expect(dream1).toEqual(dream2);
}); });
it('should return JSON-formatted string similar to Json.stringify', () => { it('should return JSON-formatted string similar to Json.stringify', () => {
const dream1 = normalize(pipe.transform(inceptionObj)); var dream1 = normalize(pipe.transform(inceptionObj));
const dream2 = normalize(JSON.stringify(inceptionObj, null, 2)); var dream2 = normalize(Json.stringify(inceptionObj));
expect(dream1).toEqual(dream2); expect(dream1).toEqual(dream2);
}); });
}); });
@ -63,8 +65,8 @@ export function main() {
}); });
it('should work with mutable objects', async(() => { it('should work with mutable objects', async(() => {
const fixture = TestBed.createComponent(TestComp); let fixture = TestBed.createComponent(TestComp);
const mutable: number[] = [1]; let mutable: number[] = [1];
fixture.componentInstance.data = mutable; fixture.componentInstance.data = mutable;
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('[\n 1\n]'); expect(fixture.nativeElement).toHaveText('[\n 1\n]');
@ -72,6 +74,7 @@ export function main() {
mutable.push(2); mutable.push(2);
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('[\n 1,\n 2\n]'); expect(fixture.nativeElement).toHaveText('[\n 1,\n 2\n]');
})); }));
}); });
}); });

View File

@ -0,0 +1,42 @@
/**
* @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 {LowerCasePipe} from '@angular/common';
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
export function main() {
describe('LowerCasePipe', () => {
var upper: string;
var lower: string;
var pipe: LowerCasePipe;
beforeEach(() => {
lower = 'something';
upper = 'SOMETHING';
pipe = new LowerCasePipe();
});
describe('transform', () => {
it('should return lowercase', () => {
var val = pipe.transform(upper);
expect(val).toEqual(lower);
});
it('should lowercase when there is a new value', () => {
var val = pipe.transform(upper);
expect(val).toEqual(lower);
var val2 = pipe.transform('WAT');
expect(val2).toEqual('wat');
});
it('should not support other objects',
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
});
});
}

View File

@ -13,7 +13,7 @@ import {browserDetection} from '@angular/platform-browser/testing/browser_util';
export function main() { export function main() {
describe('Number pipes', () => { describe('Number pipes', () => {
describe('DecimalPipe', () => { describe('DecimalPipe', () => {
let pipe: DecimalPipe; var pipe: DecimalPipe;
beforeEach(() => { pipe = new DecimalPipe('en-US'); }); beforeEach(() => { pipe = new DecimalPipe('en-US'); });
@ -44,7 +44,7 @@ export function main() {
}); });
describe('PercentPipe', () => { describe('PercentPipe', () => {
let pipe: PercentPipe; var pipe: PercentPipe;
beforeEach(() => { pipe = new PercentPipe('en-US'); }); beforeEach(() => { pipe = new PercentPipe('en-US'); });
@ -60,7 +60,7 @@ export function main() {
}); });
describe('CurrencyPipe', () => { describe('CurrencyPipe', () => {
let pipe: CurrencyPipe; var pipe: CurrencyPipe;
beforeEach(() => { pipe = new CurrencyPipe('en-US'); }); beforeEach(() => { pipe = new CurrencyPipe('en-US'); });

View File

@ -13,9 +13,9 @@ import {expect} from '@angular/platform-browser/testing/matchers';
export function main() { export function main() {
describe('SlicePipe', () => { describe('SlicePipe', () => {
let list: number[]; var list: number[];
let str: string; var str: string;
let pipe: SlicePipe; var pipe: SlicePipe;
beforeEach(() => { beforeEach(() => {
list = [1, 2, 3, 4, 5]; list = [1, 2, 3, 4, 5];
@ -93,8 +93,8 @@ export function main() {
}); });
it('should work with mutable arrays', async(() => { it('should work with mutable arrays', async(() => {
const fixture = TestBed.createComponent(TestComp); let fixture = TestBed.createComponent(TestComp);
const mutable: number[] = [1, 2]; let mutable: number[] = [1, 2];
fixture.componentInstance.data = mutable; fixture.componentInstance.data = mutable;
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('2'); expect(fixture.nativeElement).toHaveText('2');

View File

@ -0,0 +1,43 @@
/**
* @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 {UpperCasePipe} from '@angular/common';
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
export function main() {
describe('UpperCasePipe', () => {
var upper: string;
var lower: string;
var pipe: UpperCasePipe;
beforeEach(() => {
lower = 'something';
upper = 'SOMETHING';
pipe = new UpperCasePipe();
});
describe('transform', () => {
it('should return uppercase', () => {
var val = pipe.transform(lower);
expect(val).toEqual(upper);
});
it('should uppercase when there is a new value', () => {
var val = pipe.transform(lower);
expect(val).toEqual(upper);
var val2 = pipe.transform('wat');
expect(val2).toEqual('WAT');
});
it('should not support other objects',
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
});
});
}

View File

@ -34,8 +34,8 @@ export class SpyLocation implements Location {
path(): string { return this._history[this._historyIndex].path; } path(): string { return this._history[this._historyIndex].path; }
isCurrentPathEqualTo(path: string, query: string = ''): boolean { isCurrentPathEqualTo(path: string, query: string = ''): boolean {
const givenPath = path.endsWith('/') ? path.substring(0, path.length - 1) : path; var givenPath = path.endsWith('/') ? path.substring(0, path.length - 1) : path;
const currPath = var currPath =
this.path().endsWith('/') ? this.path().substring(0, this.path().length - 1) : this.path(); this.path().endsWith('/') ? this.path().substring(0, this.path().length - 1) : this.path();
return currPath == givenPath + (query.length > 0 ? ('?' + query) : ''); return currPath == givenPath + (query.length > 0 ? ('?' + query) : '');
@ -66,12 +66,12 @@ export class SpyLocation implements Location {
this._history.push(new LocationState(path, query)); this._history.push(new LocationState(path, query));
this._historyIndex = this._history.length - 1; this._historyIndex = this._history.length - 1;
const locationState = this._history[this._historyIndex - 1]; var locationState = this._history[this._historyIndex - 1];
if (locationState.path == path && locationState.query == query) { if (locationState.path == path && locationState.query == query) {
return; return;
} }
const url = path + (query.length > 0 ? ('?' + query) : ''); var url = path + (query.length > 0 ? ('?' + query) : '');
this.urlChanges.push(url); this.urlChanges.push(url);
this._subject.emit({'url': url, 'pop': false}); this._subject.emit({'url': url, 'pop': false});
} }
@ -79,7 +79,7 @@ export class SpyLocation implements Location {
replaceState(path: string, query: string = '') { replaceState(path: string, query: string = '') {
path = this.prepareExternalUrl(path); path = this.prepareExternalUrl(path);
const history = this._history[this._historyIndex]; var history = this._history[this._historyIndex];
if (history.path == path && history.query == query) { if (history.path == path && history.query == query) {
return; return;
} }
@ -87,7 +87,7 @@ export class SpyLocation implements Location {
history.path = path; history.path = path;
history.query = query; history.query = query;
const url = path + (query.length > 0 ? ('?' + query) : ''); var url = path + (query.length > 0 ? ('?' + query) : '');
this.urlChanges.push('replace: ' + url); this.urlChanges.push('replace: ' + url);
} }

View File

@ -44,20 +44,20 @@ export class MockLocationStrategy extends LocationStrategy {
pushState(ctx: any, title: string, path: string, query: string): void { pushState(ctx: any, title: string, path: string, query: string): void {
this.internalTitle = title; this.internalTitle = title;
const url = path + (query.length > 0 ? ('?' + query) : ''); var url = path + (query.length > 0 ? ('?' + query) : '');
this.internalPath = url; this.internalPath = url;
const externalUrl = this.prepareExternalUrl(url); var externalUrl = this.prepareExternalUrl(url);
this.urlChanges.push(externalUrl); this.urlChanges.push(externalUrl);
} }
replaceState(ctx: any, title: string, path: string, query: string): void { replaceState(ctx: any, title: string, path: string, query: string): void {
this.internalTitle = title; this.internalTitle = title;
const url = path + (query.length > 0 ? ('?' + query) : ''); var url = path + (query.length > 0 ? ('?' + query) : '');
this.internalPath = url; this.internalPath = url;
const externalUrl = this.prepareExternalUrl(url); var externalUrl = this.prepareExternalUrl(url);
this.urlChanges.push('replace: ' + externalUrl); this.urlChanges.push('replace: ' + externalUrl);
} }
@ -68,7 +68,7 @@ export class MockLocationStrategy extends LocationStrategy {
back(): void { back(): void {
if (this.urlChanges.length > 0) { if (this.urlChanges.length > 0) {
this.urlChanges.pop(); this.urlChanges.pop();
const nextUrl = this.urlChanges.length > 0 ? this.urlChanges[this.urlChanges.length - 1] : ''; var nextUrl = this.urlChanges.length > 0 ? this.urlChanges[this.urlChanges.length - 1] : '';
this.simulatePopState(nextUrl); this.simulatePopState(nextUrl);
} }
} }

View File

@ -22,7 +22,6 @@
"../../../node_modules/zone.js/dist/zone.js.d.ts" "../../../node_modules/zone.js/dist/zone.js.d.ts"
], ],
"angularCompilerOptions": { "angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true "strictMetadataEmit": true
} }
} }

View File

@ -27,7 +27,7 @@ Then you can add an import statement in the `bootstrap` allowing you to bootstra
generated code: generated code:
```typescript ```typescript
main.module.ts main_module.ts
------------- -------------
import {BrowserModule} from '@angular/platform-browser'; import {BrowserModule} from '@angular/platform-browser';
import {Component, NgModule, ApplicationRef} from '@angular/core'; import {Component, NgModule, ApplicationRef} from '@angular/core';
@ -49,7 +49,7 @@ export class MainModule {
bootstrap.ts bootstrap.ts
------------- -------------
import {MainModuleNgFactory} from './main.module.ngfactory'; import {MainModuleNgFactory} from './main_module.ngfactory';
import {platformBrowser} from '@angular/platform-browser'; import {platformBrowser} from '@angular/platform-browser';
platformBrowser().bootstrapModuleFactory(MainModuleNgFactory); platformBrowser().bootstrapModuleFactory(MainModuleNgFactory);

View File

@ -5,13 +5,9 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
export {AotCompilerHost, AotCompilerHost as StaticReflectorHost, StaticReflector, StaticSymbol} from '@angular/compiler';
export {CodeGenerator} from './src/codegen'; export {CodeGenerator} from './src/codegen';
export {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter, NodeCompilerHostContext} from './src/compiler_host'; export {NodeReflectorHostContext, ReflectorHost, ReflectorHostContext} from './src/reflector_host';
export {Extractor} from './src/extractor'; export {StaticReflector, StaticReflectorHost, StaticSymbol} from './src/static_reflector';
export * from '@angular/tsc-wrapped'; export * from '@angular/tsc-wrapped';
export {VERSION} from './src/version';
// TODO(hansl): moving to Angular 4 need to update this API.
export {NgTools_InternalApi_NG_2 as __NGTOOLS_PRIVATE_API_2} from './src/ngtools_api'

View File

@ -1,6 +0,0 @@
# Overview
This folder will be filled with the benchmark sources
so that we can do offline compilation for them.

View File

@ -1,3 +0,0 @@
:host {
background-color: blue;
}

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