Compare commits

..

253 Commits

Author SHA1 Message Date
ef621a2f00 docs(changelog): tweak changelog 2016-10-06 06:43:17 -07:00
df9761951b docs(changelog): added changelog for 2.1.0-rc.0 2016-10-05 16:48:22 -07:00
f786c560f1 doc(triage): add info about user pain, frequency and severity labels 2016-10-05 15:58:45 -07:00
c5557de3e7 doc(triage): correct existing incorrect info 2016-10-05 15:58:45 -07:00
ec3a5b54de 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 11:37:28 -07:00
cf269d9ff4 refactor: add license header to JS files & format files (#12081) 2016-10-04 20:39:20 -07:00
5fa5ffb82a refactor(benchmarks): refactor to support AOT bootstrap in G3 (#12075) 2016-10-04 16:27:45 -07:00
4a57dcfd8d fix(forms): properly validate empty strings with patterns (#11450) 2016-10-04 16:14:23 -07:00
43923ffcf5 docs(traige): update triaging doc 2016-10-04 16:13:32 -07:00
50c37d45dc refactor: simplify arrow functions (#12057) 2016-10-04 15:57:37 -07:00
a63359689f fix(ShadowCss): fix attribute selectors in :host and :host-context (#12056)
Fix a regression introduced in #11917 while fixing #6249
2016-10-04 15:40:31 -07:00
43d3a84df3 Revert "refactor: add license header to JS files & format files (#12035)"
This reverts commit 8310c91823.
2016-10-04 14:06:41 -07:00
8310c91823 refactor: add license header to JS files & format files (#12035) 2016-10-04 13:15:49 -07:00
b64b5ece65 refactor(facade): Remove most of StringMapWrapper facade. (#12022)
This change mostly automated by
12012b07a2
with some manual fixes.
2016-10-03 16:46:05 -07:00
ed9c2b6281 fix(Header): preserve case of the first init, set() or append() (#12023)
fixes #11624
2016-10-03 15:27:56 -07:00
1cf5f5fa38 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-03 10:19:03 -07:00
a32078f85e docs(DEVELOPER.md): fix typos on "Tests" section (#12029) 2016-10-02 14:19:47 -07:00
decd129a4d refactor(facade): remove DateWrapper (#12027) 2016-10-02 14:12:14 -07:00
c3c9ecb302 text(offline compiler): fix expected output 2016-09-30 17:59:43 -07:00
af520947aa test(AstSerializer): fix serializing void tags 2016-09-30 17:59:43 -07:00
040bf57966 fix(xlif): fix <x> ctype names
fixes #12000
see http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html#ctype
2016-09-30 17:59:43 -07:00
65a60b7456 style(I18N): Carriage returns in serialized files 2016-09-30 17:59:43 -07:00
756ef09d12 docs(gh): try to improve the issue template (#11891) 2016-09-30 16:40:56 -07:00
9316f95467 fix(ShadowCss): support @page and @document CSS rules (#11878)
fixes #11860
2016-09-30 16:26:24 -07:00
83d94b7504 fix(ShadowCss): support quoted attribute values
fixes #6085
2016-09-30 14:37:41 -07:00
a121136fae refactor(ShadowCss): add missing types 2016-09-30 14:37:41 -07:00
a6bb84e02b fix(ShadowCss): fix :host(tag) and :host-context(tag)
fixes #11972
2016-09-30 14:37:41 -07:00
3898dc488e fix(BrowserAdapter): correctly removes styles on IE
fixes #7916
2016-09-30 11:18:17 -07:00
ca3f9926f9 refactor(BrowserAdapter): cleanup 2016-09-30 11:18:17 -07:00
1c012a035f refactor(CssSelector): misc cleanup 2016-09-30 11:06:24 -07:00
38c5304b7f docs(CssSelector): [name*=value] is not supported
fixes #6042
2016-09-30 11:06:24 -07:00
9a049be67f feat(Parse5): update to the latest version 2.2.1
fixes #6237
2016-09-30 10:46:49 -07:00
2045c9e8ee docs: update docs for ng2_ftl benchmark 2016-09-30 10:42:21 -07:00
6c4ec05a4a fix(ShadowCss): support [attr="value with space"]
fixes #6249
2016-09-30 10:27:35 -07:00
f7bfda31ff refactor(ShadowCss): cleanup 2016-09-30 10:27:35 -07:00
a92b573309 test(DirectiveResolver): test that a prop can have both @Input and @HostBinding 2016-09-30 10:08:52 -07:00
4fd13d71c8 refactor(DirectiveResolver): cleanup 2016-09-30 10:08:52 -07:00
bf7b82b658 fix(UrlSearchParams): change a behavior when a param value is null or undefined (#11990) 2016-09-30 09:57:26 -07:00
c143fee849 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-09-30 09:42:54 -07:00
0286956107 refactor(facade): Inline isBlank called with object-type argument (#11992) 2016-09-30 09:26:53 -07:00
e884f4854d feat(animations): provide aliases for :enter and :leave transitions (#11991) 2016-09-30 09:15:56 -07:00
df1822fc2a 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-09-30 09:09:31 -07:00
42b4b6d21b 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-09-29 09:45:28 -07:00
36bc2ff269 docs(forms): Added FormControl initialization information (#11948) 2016-09-28 13:59:08 -07:00
1564042fe8 fix(ngc): allow ReflectorHost passed as argument to CodeGenerator#create (#11951) 2016-09-27 17:12:57 -07:00
41c8c30973 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-09-27 17:12:25 -07:00
61129fa12d fix(compiler): move detection of unsafe properties for binding to ElementSchemaRegistry (#11378) 2016-09-27 17:10:02 -07:00
3a5b4882bc fix(compiler): Do not embed templateUrl in view factories in non-debug mode. (#11818)
Fixes #11117.
2016-09-27 17:09:44 -07:00
425c1e6042 refactor: remove dead code 2016-09-27 16:13:09 -07:00
58605cf350 refactor(facade): remove useless facades 2016-09-27 16:13:09 -07:00
34b31dea7c docs(upgrade): rename undeclared Ng2 to Ng2Component (#11950) 2016-09-27 16:11:41 -07:00
a241ab7c07 (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-09-27 10:10:45 -07:00
745e10e6d2 docs(router_config): add missing quote (#11925) 2016-09-27 10:10:12 -07:00
33340dbbd1 docs: remove outdated docs (#11875) 2016-09-24 08:23:28 +09:00
52812c08e2 chore(CHANGELOG): fix wrong issue link (#11871) 2016-09-24 07:13:24 +09:00
52f5ae1961 chore(compiler): followup fix for PR#11846 (#11870)
Original PR set [] to any, but any[], is a tighter type and still
works for SNC enabled consumers of the emit.
2016-09-24 07:13:05 +09:00
9be895b6da docs(ExceptionHandler): fix API docs (#11772)
fixes #11769
2016-09-24 07:05:43 +09:00
9f1c82537e ci(travis): increase node's heap size to prevent OOM on travis (#11869) 2016-09-24 06:04:29 +09:00
5ab5cc77bb 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-09-24 05:44:01 +09:00
f1b6c6efa1 refactor(animations): ensure animation input/outputs are managed within the template parser (#11782)
Closes #11782
Closes #11601
Related #11707
2016-09-24 05:37:04 +09:00
45ad13560b docs(changelog): remove info about an internal change 2016-09-23 12:02:56 -07:00
2045268cec chore(release): v2.1.0-beta.0 2016-09-23 11:41:35 -07:00
fb1076b44a docs(changelog): release notes for 2.1.0-beta.0 2016-09-23 11:37:28 -07:00
6fc46526ae fix(upgrade): allow attribute selectors for components in ng2 which are not part of upgrade (#11808)
fixes #11280
2016-09-24 02:47:16 +09:00
3ef5ede6d6 chore(compiler): emit ([] as any[]) instead of purely []. (#11846)
In SNC mode `[]` has type of never[], so we cast it to any[] to
typecheck correctly see
https://github.com/Microsoft/TypeScript/issues/10479.

This is temporary workaround, until we fully
migrate the framework to TS 2.0 and strictNullChecks.
2016-09-24 02:21:59 +09:00
136621ebc9 docs(Component): API docs for .encapsulation and .interpolation 2016-09-22 11:01:16 -07:00
f23b22a0f4 refactor: misc cleanup 2016-09-22 11:01:16 -07:00
0ca971c5bd refactor(common): cleanup (#11668) 2016-09-22 10:34:00 -07:00
3a6fcee0e6 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-22 10:32:17 -07:00
8972137c29 docs(contributing): remove preview references (#11795) 2016-09-22 10:31:56 -07:00
cc6481077f ci(BrowserStack): add Safari 10 (#11796) 2016-09-22 10:31:38 -07:00
c041b93418 refactor(TemplateParser): clearer error message for on* binding (#11802)
fixes #11756
2016-09-22 10:31:18 -07:00
bc33765913 chore(ISSUE_TEMPLATE): update Angular version field (#11821) 2016-09-22 10:29:12 -07:00
31dce72b7b fix(router): update the router not to reset router state when updating root component (#11799) 2016-09-21 11:37:43 -07:00
212f8dbde7 fix(forms): disable all radios with disable() 2016-09-20 15:00:12 -07:00
44da4984f9 fix(forms): support unbound disabled in ngModel (#11736) 2016-09-20 14:55:47 -07:00
d95344430c chore(zone.js): update to 0.6.25 (#11725) 2016-09-20 14:55:07 -07:00
131626fc61 fix(compiler): Safe property access expressions work in event bindings (#11724) 2016-09-20 14:54:53 -07:00
676bb0fa7d feat(router): update dts files 2016-09-20 14:53:52 -07:00
5a849829c4 feat(router): add router preloader to optimistically preload routes 2016-09-20 14:53:52 -07:00
671f73448c refactor: misc cleanup (#11654) 2016-09-19 17:15:57 -07:00
51d73d3e4e fix(forms): make setDisabledState optional for reactive form directives (#11731)
Closes #11719
2016-09-19 16:26:33 -07:00
bf81b06a28 docs(forms): add select control examples (#11728) 2016-09-19 16:25:33 -07:00
0621f07a2c refactor: misc cleanup 2016-09-19 16:24:31 -07:00
1225ecfb14 chore(node): allow current node version
node current is 6.6.0
see https://github.com/nodejs/LTS#lts_schedule
2016-09-19 16:24:31 -07:00
5509453e72 refactor(common): pipe code cleanup 2016-09-19 16:19:28 -07:00
70488ed382 fix(OfflineCompiler): support older TS versions (#11734) 2016-09-19 15:36:25 -07:00
03aedbe54b fix(OfflineCompiler): Do not provide I18N values when they're not specified
fixes #11643
2016-09-19 10:44:33 -07:00
8395aab25d refactor(OfflineCompiler): cleanup 2016-09-19 10:44:33 -07:00
0dc15eb64a fix(ContentChild): query descendants by default
fixes #1645
2016-09-19 10:42:46 -07:00
cba885a1fb refactor: code cleanup 2016-09-19 10:42:46 -07:00
fa4723a208 docs(forms): add radio button examples (#11676) 2016-09-19 10:41:20 -07:00
5bf08b886f build(npm): update fsevents to 1.0.14 (#11686)
fixes #11685
2016-09-18 16:04:46 -07:00
89802316b9 docs(injector): API docs - remove lone code-block backticks (#11653)
The triple backticks in the markdown of the API entry are unbalanced.
2016-09-18 16:04:04 -07:00
2300c23332 fix(docs): Fixed wording for NgModule schemas (#11620) 2016-09-18 16:03:43 -07:00
fa39965a37 build(gulp): fail on ddescribe / iit left in tests
Closes #10524
2016-09-18 16:01:50 -07:00
115f0fa842 refactor(gulpfile): cleanup, add comments 2016-09-18 16:01:50 -07:00
734b8b8c13 fix(compiler): [attribute~=value] selector (#11696)
Change the seperator regular expression to ignore tildes which are followed by an equal sign.

Closes #9644
2016-09-18 15:58:19 -07:00
54b41f57be docs(Host): fix the API example (#11684)
fixes #11681
2016-09-18 15:56:13 -07:00
df4254ae89 refactor(facade): move isPromise to core private (#10573) 2016-09-18 15:55:08 -07:00
14ee75924b fix(common): fix ngOnChanges signature of NgTemplateOutlet directive 2016-09-15 11:00:30 -07:00
bd4045b6e7 fix(MetadataResolver): throw Component.moduleId is not a string
fixes #11590
2016-09-15 10:57:37 -07:00
255099aa61 refactor(MetadataResolver): cleanup 2016-09-15 10:57:37 -07:00
1c24096650 refactor(benchpress): add more types 2016-09-15 10:17:10 -07:00
32aeb1052d refactor(benchpress): normalize phase b into B and e into E
This simplifies the perflog metrics and prevents future errors.
2016-09-15 10:17:10 -07:00
838d4bbf6c fix(benchpress): support measuring scriptTime and other metrics of page reload.
E.g. for benchmarks that measure page start time
2016-09-15 10:17:10 -07:00
c4114c2f66 finished refactoring 2016-09-15 10:17:10 -07:00
37b8691c8c refactor(benchpress): remove chrome < v44 support 2016-09-15 10:17:10 -07:00
93054d4e3d refactor(benchpress): remove facades from chrome_driver_extension 2016-09-15 10:17:10 -07:00
cfc12c6539 docs(api): changes to correct jade errors in API doc gen (#11619) 2016-09-15 09:09:00 -07:00
c0bdd89b5d docs(readme): update the note about RC 2016-09-14 19:37:19 -07:00
d5515473bf docs: update README.md for npm packages 2016-09-14 17:14:02 -07:00
ffe5c49c3e chore(release): v2.0.0 proprioception-reinforcement 2016-09-14 16:49:10 -07:00
ae1dd5bfd0 docs(changelog): add changelog for bug fixes that landed between rc.7 and 2.0.0 2016-09-14 16:45:16 -07:00
cb657c4b55 docs: update descriptions in package.jsons 2016-09-14 16:44:39 -07:00
42f60ca303 docs(core): update dts file 2016-09-14 15:27:33 -07:00
e33037a2f1 docs(core): docs for Directive and Component 2016-09-14 15:27:33 -07:00
9cee8bcc83 docs(common): add directives docs
Closes #11581
2016-09-14 15:24:01 -07:00
003294d5df docs(core): fix examples 2016-09-14 14:53:30 -07:00
785292f44f chore(core): reexport query metadata decorators 2016-09-14 14:53:30 -07:00
15c2912527 chore(core): update public api file 2016-09-14 14:53:30 -07:00
096ae7c404 docs(core): updates query decorator docs 2016-09-14 14:53:30 -07:00
5972fdc817 docs(core): extract how to examples 2016-09-14 14:53:30 -07:00
2c42a50fc3 docs(pipes): updated pipe documentation 2016-09-14 14:32:09 -07:00
caa1cd2470 docs(pipes): move pipe examples to the common folder 2016-09-14 14:26:00 -07:00
5fad37df69 Revert "chore(core): update public api file"
This reverts commit 727c2b38a4.

Revert "docs(core): updates query decorator docs"

This reverts commit b6287ccc51.

Revert "docs(core): extract how to examples"

This reverts commit 69e8ace884.
2016-09-14 13:34:25 -07:00
727c2b38a4 chore(core): update public api file 2016-09-14 13:22:09 -07:00
b6287ccc51 docs(core): updates query decorator docs 2016-09-14 13:22:09 -07:00
69e8ace884 docs(core): extract how to examples 2016-09-14 13:22:09 -07:00
85d9db6bc4 fix(platform-browser): provide Title service as part of the module (#11605)
Fixes #11600
2016-09-14 13:21:23 -07:00
0a2132ef10 docs(di): update docs on di 2016-09-14 11:57:31 -07:00
d299ce4bcf docs(lifecycle): update docs for lifecycle hooks 2016-09-14 11:51:03 -07:00
0b9425bbb4 fix(examples): make them work with noImplicitAny and declarations:true 2016-09-14 11:29:31 -07:00
1a035a0dc7 build(examples): include in main tsconfig.json
Also rename `examples/tsconfig.json` into `examples/tsconfig-build.json`
so that it does not shadow the main `tsconfig.json` in editors

Also adds `noImplicitAny` and `declarations`
`examples/tsconfig.json`.
2016-09-14 11:29:31 -07:00
84b4338ab5 build(example): fix tsconfig (#11593) 2016-09-14 07:40:58 -07:00
b847257b16 refactor(ShadowCss): remove a comment that trigger an issue with webpack (#11587)
fixes #11584
2016-09-13 21:59:11 -07:00
c65d139081 build: remove JS suffix from the license banner 2016-09-13 21:48:58 -07:00
57f0269491 build(examples): fail build.sh if errors are found 2016-09-13 21:48:58 -07:00
4e6c41b3a1 build(examples): work around protractor typings issues and fix existing type errors
This works around the typings issues until we have a build of protractor with typings that don't
polute global types via ambient type definitions
2016-09-13 21:48:58 -07:00
7105021c41 docs(forms): add docs for FormArray 2016-09-13 14:00:52 -07:00
f7313db0be docs(forms): add docs for FormGroup 2016-09-13 14:00:52 -07:00
1d2e70e3a4 docs(forms): add docs for FormControl 2016-09-13 14:00:52 -07:00
21516c32e6 docs(forms): add docs for AbstractControl 2016-09-13 14:00:52 -07:00
00a24b63da build(npm): roll back jasmine 2.5 upgrade due to console reporter issues (#11573)
2.5.1 no longer prints dots in the './test.sh node' mode
2016-09-13 13:32:58 -07:00
e71558ba89 docs(forms): update docs for FormBuilder (#11548) 2016-09-13 13:23:31 -07:00
7ac47acc1c docs(core): updates docs for query metadata 2016-09-13 11:28:12 -07:00
60e49a7e4b docs(core): add an example of using ViewChildren 2016-09-13 11:28:12 -07:00
c71e35cbf5 docs(core): add an example of using ViewChild 2016-09-13 11:28:12 -07:00
1348c65b0c docs(core): add an example of using ContentChildren 2016-09-13 11:28:12 -07:00
ff03d87cdd docs(core): add an example of using ContentChild 2016-09-13 11:28:12 -07:00
a2bf334e6e chore(benchpress): update package.json and add publish script 2016-09-13 10:49:16 -07:00
f8690caa98 chore: refactor build script to allow to build individual packages 2016-09-13 10:49:16 -07:00
aa713d1dd9 docs(readme): fixed visual of browserstack link (#11552)
Instead of using a variable for the link of browserstack it was using the direct https link.
That caused to show the text and the link (incorrect markdown).
Moved the https link to the bottom with the other link assignments. It will now only show the blue text with the link to the browserstack.
2016-09-13 10:06:02 -07:00
a2519c6164 fix(upgrade): correct the main entry path in package.json 2016-09-13 10:03:45 -07:00
fa994810d5 chore(release): v2.0.0-rc.7 2016-09-12 23:25:28 -07:00
c54580a4af docs(changelog): add release notes for 2.0.0-rc.7 2016-09-12 23:25:05 -07:00
730415e048 build: temporarily disable building and testing of documentation examples
This is due to protractor typings issue that breaks the compilation.
2016-09-12 23:06:38 -07:00
42a287fabf fix(core): make name in Pipe non optional 2016-09-12 22:47:54 -07:00
42d442dcd5 refactor(core): add a name to all decorators and other fixes 2016-09-12 22:47:54 -07:00
cc2873a94d chore: update typings
Note that the typings don’t reflect the shape of the metadata
due to a bug in the public-api-guard
2016-09-12 22:47:54 -07:00
63e15ffaec refactor(core): remove …Metadata for all decorators and use the decorator directly.
BREAKING CHANGE:
- all `…Metadata` classes have been removed. Use the corresponding decorator
  as constructor or for `instanceof` checks instead.
- Example:
  * Before: `new ComponentMetadata(…)`
  * After: `new Component(…)`
- Note: `new Component(…)` worked before as well.
2016-09-12 22:47:54 -07:00
1b15170c89 refactor(core): simplify decorators
Every decorator now is made of the following:
- a function that can be used
as a decorator or as a constructor. This function
also can be used for `instanceof` checks.
- a type for this function (callable and newable)
- a type that describes the shape of the data
  that the user needs to pass to the decorator
  as well as the instance of the metadata

The docs for decorators live at the followig places
so that IDEs can discover them correctly:
- General description of the decorator is placed on the
  `...Decorator` interface on the callable function
  definition
- Property descriptions are placed on the interface
  that describes the metadata produces by the decorator
2016-09-12 22:47:54 -07:00
26d1423ae9 docs(forms): update docs for NgForm (#11547) 2016-09-12 17:01:04 -07:00
61aad7925f fix(forms): fix resetting radios (#11546)
Closes #11516
2016-09-12 15:15:58 -07:00
79055f727b fix(forms): support dots in control names in contains (#11542)
Closes #11535
2016-09-12 15:15:50 -07:00
220d8377fe build(npm): update to jasmine@2.5.1
Closes #11390
2016-09-12 12:54:52 -07:00
cc7780adf7 build(npm): update to rxjs@5.0.0-beta.12
Fixes #11300
2016-09-12 12:05:00 -07:00
051a6ebe12 feat(zone): upgrade to zone.js@0.6.21 2016-09-12 11:48:24 -07:00
c9513b713a docs(forms): add example apps for ngModelGroup (#11525) 2016-09-12 11:45:48 -07:00
66e38b6754 docs(forms): add example apps for ngModel (#11524) 2016-09-12 11:27:29 -07:00
7b82877ee5 fix(Localization): BCP47 uses hyphens as separator (#11514)
https://tools.ietf.org/html/bcp47
2016-09-12 11:27:15 -07:00
c9ad5e46d6 docs(forms): add example app for formArrayName (#11512) 2016-09-12 11:26:43 -07:00
2cdd051109 docs(forms): update example for formGroupName (#11510) 2016-09-12 11:26:18 -07:00
57cb82052b docs(forms): add example app for formControlDirective (#11508) 2016-09-12 11:24:09 -07:00
dd8204a655 docs(forms): update example for formGroupDirective 2016-09-12 11:22:51 -07:00
cdda4082de docs(forms): add example app for formControlName 2016-09-12 11:22:51 -07:00
0614c8c99d chore(router): update publicapi 2016-09-12 10:02:48 -07:00
a343a8e1c2 docs(router): fix typos 2016-09-12 09:47:44 -07:00
a41c1bbdf4 docs(router): update docs of the router lifecycle interfaces 2016-09-10 16:55:14 -07:00
f2c6157e74 docs(router): update docs of RouteModule and RouterTestingModule 2016-09-10 16:55:13 -07:00
32564ece27 docs(router): update RouterState docs 2016-09-10 16:55:13 -07:00
3eee62fa71 docs(router): update router configuration docs 2016-09-10 16:55:13 -07:00
617475005f docs(router): update docs of the Router service 2016-09-10 16:55:13 -07:00
0822066175 docs(router): update docs for router directives 2016-09-10 16:55:13 -07:00
82f30e09f0 refactor(common): cleanup directive tests 2016-09-09 14:30:18 -07:00
c649a5c5ab refactor(common): cleanup directives 2016-09-09 14:30:18 -07:00
53f0c2206d fix(forms): rename validator change fn due to conflict (#11492)
Closes #11479
2016-09-09 14:09:11 -07:00
0bce3907b8 fix(tests): add missing import (#11490) 2016-09-09 14:08:47 -07:00
2170379251 refactor(common): cleanup, strip deprecated doc (#11469) 2016-09-09 12:05:06 -07:00
5a4e46db20 refactor(tests): simplify code (#11485) 2016-09-09 12:04:38 -07:00
f5d44a42c9 refactor(NgClass): cleanup, readability (#11467) 2016-09-09 12:03:51 -07:00
673de004d2 fix(forms): clear errors on disable (#11463)
Closes #11287
2016-09-09 12:00:38 -07:00
f386cb4ba9 Fix benchpress for newest protractor and selenium (#11451)
* chore: update protractor and selenium-webdriver packages

As `karma-jasmine` has a peer dependency on `jasmine-core@2.3`, but `jasmine` and `protractor` are using `jasmine-core@2.4` we need to add `jasmine-core@2.3` explicitly. Previously, the peer dependency was
satisfied by accident because npm deduped the dependency
for `jasmine-core@2.3` as top level dependency.

Note that the shrink-wrap files changes quite a bit because
of the deduping mechanism of npm.

* fix(benchpress): make it work with latest protractor and seleniuv-webdriver

* fix(e2e_tests): make them work with latest protractor
2016-09-09 10:37:47 -07:00
71e9cae1d0 chore(github): update issue template to hide comments (#11473)
Wrap additional descriptions in comments so that they are hidden in created issues (only visible during editing)
2016-09-09 09:36:53 -07:00
df6762a170 docs(TestBed): Fix to current packageing (#11472) 2016-09-09 09:36:38 -07:00
d296298282 fix(build): prevent package tsconfigs from shadowing main tsconfig (#11454) 2016-09-08 15:01:22 -07:00
077e0be1e7 fix(CssSelector): fix getMatchingElementTemplate() for void tags
fixes #11407
2016-09-08 13:55:41 -07:00
a52d076912 refactor(CssSelector): misc 2016-09-08 13:55:41 -07:00
dae7cfc454 chore(github): update pull request template fix link (#11449)
Fix the link to the contribution guildelines -  commit message guidlines
2016-09-08 13:54:30 -07:00
436af15d63 refactor: remove parseFloat from facades (#11446) 2016-09-08 13:54:10 -07:00
7b24028437 fix(forms): fix disabled support for empty form containers (#11427)
Closes #11386
2016-09-08 12:21:48 -07:00
6a2bbffe10 fix(animations): allow group() to be used as entry point for an animation trigger (#11419)
Closes #11312
Closes #11419
2016-09-08 12:20:07 -07:00
f78e184822 docs(FactoryProvider): add missing backtick (#11444) 2016-09-08 09:18:37 -07:00
78ad9adc1a fix(ShadowCss): fix perf regression (#11420)
fixes #11371
2016-09-07 16:48:10 -07:00
9e2ec7a1aa fix(ngc): use the compilerHost to detect file existence (#11418) 2016-09-07 16:24:52 -07:00
643afa4b15 docs(cheatsheet): fix typo NgModule definition (#11377)
`.Class` and not `.class` in js approach for NgModule definition.
2016-09-07 16:05:05 -07:00
ed2ebeb52a fix(build): test example directories with unit and e2e tests (#11296) 2016-09-07 16:04:33 -07:00
567900e550 chore(github): update issue template (#11415) 2016-09-07 14:10:47 -07:00
cc958c74ad docs(router): Fix typo of segment name and odd quote (#11409) 2016-09-07 14:10:19 -07:00
62af613741 chore: update triage and labels process (#11403) 2016-09-07 14:10:01 -07:00
3ff816afa6 style(CompileMetadataResolver): better error message (#11401) 2016-09-07 14:09:25 -07:00
dd03bf12e1 docs: misc fixes.
docs(common_module): Fix macro format
docs(number_pipe): Add missing period sign
docs(date_pipe): Fix suffix consistency
docs(date_pipe): Fix missing quote
docs(number_pipe): Fix incorrect article

Looks like the word "Polyfill" does not start with a vowel pronunciation.

docs(location_strategy): Fix code format

Add missing \`\`\` at start.

docs(i18n_plural_pipe): Fix code format
docs(location): Add missing period sign
refactor(ngSwitch): fix typo on parameter
docs(di): Add missing quote
docs(compiler): Fix typo
docs(compiler): Add missing period sign
docs(directives): Fix description for styles parameter
docs(location_strategy): Add code language

Revert for misunderstanding.
2016-09-06 15:45:37 -07:00
645108f25b test: cleanup playground/src/bootstrap.ts file 2016-09-06 15:35:10 -07:00
882efd125e build: remove obsolete presubmit.sh script 2016-09-06 15:35:10 -07:00
d91e92c2f5 build(test.sh): clear dist directory when the script starts
This is to prevent stale files causing the tests to fail when we run them locally after checking out a new revision.
2016-09-06 15:35:10 -07:00
8858ebc4ab ci: update SauceLabs badge when running CI on master (#11352) 2016-09-06 12:07:48 -07:00
df4c0a3d1f refactor(benchmarks): align tree benchmark with largetable benchmark
- add ng2_switch benchmark to track `ngFor` over `ngSwitch`
- measure create only, createDestroy and update
- simplify the created dom
- always add a style binding
2016-09-06 12:07:12 -07:00
b4363bc8af feat(benchmarks): add targetable benchmarks back 2016-09-06 12:07:12 -07:00
d26a827494 fix(lazy-loading): fix an issue with webpack and lazy loader. (#11387)
The issue was introduced in PR#11049.
2016-09-06 12:06:18 -07:00
ea95c391c1 fix(compiler): error when NgModule.bootstrap contains undefined or null 2016-09-06 11:44:56 -07:00
aa9b617c9d fix(compiler): correctly type event handler proxy functions 2016-09-06 11:44:56 -07:00
7192fec841 refactor(EventManager): remove ListWrapper (#11363) 2016-09-06 11:23:00 -07:00
ee88c3c976 fix typo (#11265)
meesage => message
2016-09-06 10:26:51 -07:00
1ff0add29e docs(CHANGELOG): fix provider syntax (#11258)
fixes #11256
2016-09-06 10:26:20 -07:00
5ee0f09b92 build: update the symlinks scripts for Windows to new packaging (#11192) 2016-09-06 10:25:59 -07:00
70b0ab457b style(dom_renderer): use const (#11229) 2016-09-06 10:25:16 -07:00
c25d1f7ecc test: reactivate the remaining disabled tests in Edge (#11188)
Fixes #4756
2016-09-06 10:24:48 -07:00
a45769a0a2 test(offline_compiler_test.sh): lock down npm dependencies 2016-09-02 15:58:46 -07:00
109dc99d32 build(npm): remove obsolete npm dependencies
I also removed an obsolete bundling script which depended on systemjs-builder that I removed.
2016-09-02 15:58:46 -07:00
2371d22d49 chore: remove obsolete dart related files 2016-09-02 15:58:46 -07:00
6f4b6edfea chore(git): cleanup .gitignore
all obsolete paths have been removed
2016-09-02 15:58:46 -07:00
8c09933803 fix(forms): support rebinding nested controls (#11210) 2016-09-02 15:57:35 -07:00
d309f7799c fix(DomSchema): add missing elements
fixes #11219
2016-09-02 15:35:36 -07:00
93deff6c33 refactor(DomSchema): improve readability by making the schema more explicit using interface names 2016-09-02 15:35:36 -07:00
c31535982c fix(ngc): prepend a rootDir when assuming a file exists (#11291)
Otherwise we'll later try to resolve the file under one of the rootDirs and won't find it.
2016-09-02 14:52:14 -07:00
f5101782d9 docs(router): Fixed examples for router.navigate (#11263) 2016-09-02 13:42:51 -07:00
5e5ae3cde6 fix(ngc): propagate errors to main (#11214) 2016-09-01 16:54:37 -07:00
53cf71430f build(publish): shallow fetch to improve perfs (depth=1) 2016-09-01 16:53:51 -07:00
04d02b55d1 build(publish): replace version placeholders in .min.js files 2016-09-01 16:53:51 -07:00
043493cb62 fix(forms): disabled controls should never be invalid (#11257)
Closes #11253
2016-09-01 16:51:42 -07:00
2581c0851a feat(benchmarks): add incremental-dom version of deep tree benchmark 2016-09-01 14:13:33 -07:00
27d72e87c3 feat(benchmarks): add baseline for deep tree that only used createElement 2016-09-01 14:13:33 -07:00
eef4c22e87 feat(benchmarks): add static tree benchmark 2016-09-01 14:13:33 -07:00
4287f1716d chore: add incremental-dom as dev dependency 2016-09-01 14:13:33 -07:00
ebc8e808a9 feat(router): register NgModuleFactory objects. (#11211)
When lazily loading code, users need to be able to get hold of the
NgModuleFactory. For SystemJS environments, the SystemJS registry serves
this purpose. However other environments, such as modules compiled with
Closure compiler, do not expose exports object or a path based registry.

For these environments, `@NgModule` objects can include an identifier, and
the loading code can then pass `loadModule(id).then(() =>
getNgModule(id))` to the router.
2016-09-01 13:46:08 -07:00
c9e5b599e4 fix(animations): ensure parent animations are triggered before children (#11201) 2016-09-01 13:24:26 -07:00
e42a057048 docs(cheatsheet): complete the copy edit (#11215)
…and general cleanup of the cheatsheet.
2016-09-01 12:06:42 -07:00
0bb94df1da docs(core): docs fixes (#11212) 2016-09-01 11:45:59 -07:00
50e171c09b docs(CHANGELOG): add missing animation feature for RC6 (#11216) 2016-09-01 11:40:36 -07:00
96697029c9 docs(changelog): small fixes 2016-08-31 22:26:31 -07:00
7c3b1367bc docs(changelog): add notes about required SystemJS and Karma config changes 2016-08-31 22:23:54 -07:00
18be339ee9 chore: upgrade chrome to v53 (#11213) 2016-08-31 16:59:17 -07:00
ddda62b1f2 docs(router): add changelog for 3.0.0-rc.2 2016-08-31 16:55:18 -07:00
788 changed files with 20198 additions and 21596 deletions

View File

@ -1,3 +1,7 @@
<!--
IF YOU DON'T FILL OUT THE FOLLOWING INFORMATION WE MIGHT CLOSE YOUR ISSUE WITHOUT INVESTIGATING
-->
**I'm submitting a ...** (check one with "x") **I'm submitting a ...** (check one with "x")
``` ```
[ ] bug report => search github for a similar issue or PR before submitting [ ] bug report => search github for a similar issue or PR before submitting
@ -6,28 +10,30 @@
``` ```
**Current behavior** **Current behavior**
<!-- Describe how the bug manifests. -->
**Expected behavior**
<!-- Describe what the behavior would be without the bug. -->
**Expected/desired behavior** **Minimal reproduction of the problem with instructions**
<!--
If the current behavior is a bug or you can illustrate your feature request better with an example,
**Reproduction of the problem** please provide the *STEPS TO REPRODUCE* and if possible a *MINIMAL DEMO* of the problem via
If the current behavior is a bug or you can illustrate your feature request better with an example, please provide the steps to reproduce and if possible a minimal demo of the problem via https://plnkr.co or similar (you can use this template as a starting point: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5). https://plnkr.co or similar (you can use this template as a starting point: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5).
-->
**What is the expected behavior?**
**What is the motivation / use case for changing the behavior?** **What is the motivation / use case for changing the behavior?**
<!-- Describe the motivation or the concrete use case -->
**Please tell us about your environment:** **Please tell us about your environment:**
<!-- Operating system, IDE, package manager, HTTP server, ... -->
* **Angular version:** 2.0.0-rc.X * **Angular version:** 2.0.X
<!-- Check whether this is still an issue in the most recent Angular version -->
* **Browser:** [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ] * **Browser:** [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]
<!-- All browsers where this could be reproduced -->
* **Language:** [all | TypeScript X.X | ES6/7 | ES5] * **Language:** [all | TypeScript X.X | ES6/7 | ES5]
* **Node (for AoT issues):** `node --version` =

View File

@ -1,5 +1,5 @@
**Please check if the PR fulfills these requirements** **Please check if the PR fulfills these requirements**
- [ ] The commit message follows our guidelines: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit-message-format - [ ] The commit message follows our guidelines: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit
- [ ] Tests for the changes have been added (for bug fixes / features) - [ ] Tests for the changes have been added (for bug fixes / features)
- [ ] Docs have been added / updated (for bug fixes / features) - [ ] Docs have been added / updated (for bug fixes / features)

22
.gitignore vendored
View File

@ -1,26 +1,9 @@
.DS_STORE .DS_STORE
# Dont commit the following directories created by pub.
packages
pubspec.lock
.pub
.packages
/dist/ /dist/
.buildlog
node_modules node_modules
bower_components bower_components
# Or broccoli working directory
tmp
# Or the files created by dart2js.
*.dart.js
*.dart.precompiled.js
*.js_
*.js.deps
*.js.map
# Include when developing application packages. # Include when developing application packages.
pubspec.lock pubspec.lock
.c9 .c9
@ -37,13 +20,8 @@ modules/.vscode
# Ignore npm debug log # Ignore npm debug log
npm-debug.log npm-debug.log
/docs/bower_components/
# build-analytics # build-analytics
.build-analytics .build-analytics
# built dart payload tests
/modules_dart/payload/**/build
# rollup-test output # rollup-test output
/modules/rollup-test/dist/ /modules/rollup-test/dist/

2
.nvmrc
View File

@ -1 +1 @@
5.4.1 6.6.0

View File

@ -1,7 +1,7 @@
language: node_js language: node_js
sudo: false sudo: false
node_js: node_js:
- '5.4.1' - '6.6.0'
addons: addons:
# firefox: "38.0" # firefox: "38.0"
@ -20,20 +20,9 @@ cache:
directories: directories:
- ./node_modules - ./node_modules
- ./.chrome/chromium - ./.chrome/chromium
# - $HOME/.pub-cache
#before_cache:
# # Undo the pollution of the typescript_next build before the cache is primed for future use
# - if [[ "$MODE" == "typescript_next" ]]; then npm install typescript; fi
env: env:
global: global:
# - KARMA_JS_BROWSERS=ChromeNoSandbox
# - E2E_BROWSERS=ChromeOnTravis
# - LOGS_DIR=/tmp/angular-build/logs
# - ARCH=linux-x64
# GITHUB_TOKEN_ANGULAR # GITHUB_TOKEN_ANGULAR
# This is needed for the e2e Travis matrix task to publish packages to github for continuous packages delivery. # This is needed for the e2e Travis matrix task to publish packages to github for continuous packages delivery.
- secure: "fq/U7VDMWO8O8SnAQkdbkoSe2X92PVqg4d044HmRYVmcf6YbO48+xeGJ8yOk0pCBwl3ISO4Q2ot0x546kxfiYBuHkZetlngZxZCtQiFT9kyId8ZKcYdXaIW9OVdw3Gh3tQyUwDucfkVhqcs52D6NZjyE2aWZ4/d1V4kWRO/LMgo=" - secure: "fq/U7VDMWO8O8SnAQkdbkoSe2X92PVqg4d044HmRYVmcf6YbO48+xeGJ8yOk0pCBwl3ISO4Q2ot0x546kxfiYBuHkZetlngZxZCtQiFT9kyId8ZKcYdXaIW9OVdw3Gh3tQyUwDucfkVhqcs52D6NZjyE2aWZ4/d1V4kWRO/LMgo="
@ -52,146 +41,11 @@ matrix:
- env: "CI_MODE=saucelabs_optional" - env: "CI_MODE=saucelabs_optional"
- env: "CI_MODE=browserstack_optional" - env: "CI_MODE=browserstack_optional"
install: install:
- ./scripts/ci-lite/install.sh - ./scripts/ci-lite/install.sh
before_script:
script: script:
- ./scripts/ci-lite/build.sh && ./scripts/ci-lite/test.sh - ./scripts/ci-lite/build.sh && ./scripts/ci-lite/test.sh
after_script: after_script:
- ./scripts/ci-lite/cleanup.sh - ./scripts/ci-lite/cleanup.sh
#branches:
# except:
# - g3_v2_0
#
#cache:
# directories:
# - $HOME/.pub-cache
# - $HOME/.chrome/chromium
#
#before_cache:
# # Undo the pollution of the typescript_next build before the cache is primed for future use
# - if [[ "$MODE" == "typescript_next" ]]; then npm install typescript; fi
#
#env:
# global:
# # Use newer verison of GCC to that is required to compile native npm modules for Node v4+ on Ubuntu Precise
# # more info: https://docs.travis-ci.com/user/languages/javascript-with-nodejs#Node.js-v4-(or-io.js-v3)-compiler-requirements
# - CXX=g++-4.8
# - KARMA_DART_BROWSERS=DartiumWithWebPlatform
# # No sandbox mode is needed for Chromium in Travis, it crashes otherwise: https://sites.google.com/a/chromium.org/chromedriver/help/chrome-doesn-t-start
# - KARMA_JS_BROWSERS=ChromeNoSandbox
# - E2E_BROWSERS=ChromeOnTravis
# - LOGS_DIR=/tmp/angular-build/logs
# - SAUCE_USERNAME=angular-ci
# - SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987
# - BROWSER_STACK_USERNAME=angularteam1
# - BROWSER_STACK_ACCESS_KEY=BWCd4SynLzdDcv8xtzsB
# - ARCH=linux-x64
# - DART_DEV_VERSION=latest
# - DART_STABLE_VERSION=latest
# - DART_CHANNEL=stable
# - DART_VERSION=$DART_STABLE_VERSION
# # Token for tsd to increase github rate limit
# # See https://github.com/DefinitelyTyped/tsd#tsdrc
# # This does not use http://docs.travis-ci.com/user/environment-variables/#Secure-Variables
# # because those are not visible for pull requests, and those should also be reliable.
# # This SSO token belongs to github account angular-github-ratelimit-token which has no access
# # (password is in Valentine)
# - TSDRC='{"token":"ef474500309daea53d5991b3079159a29520a40b"}'
# # GITHUB_TOKEN_ANGULAR
# - secure: "fq/U7VDMWO8O8SnAQkdbkoSe2X92PVqg4d044HmRYVmcf6YbO48+xeGJ8yOk0pCBwl3ISO4Q2ot0x546kxfiYBuHkZetlngZxZCtQiFT9kyId8ZKcYdXaIW9OVdw3Gh3tQyUwDucfkVhqcs52D6NZjyE2aWZ4/d1V4kWRO/LMgo="
# matrix:
# # Order: a slower build first, so that we don't occupy an idle travis worker waiting for others to complete.
# - MODE=dart
# - MODE=dart DART_CHANNEL=dev
# - MODE=saucelabs_required
# - MODE=browserstack_required
# - MODE=saucelabs_optional
# - MODE=browserstack_optional
# - MODE=dart_ddc
# - MODE=js
# - MODE=router
# - MODE=build_only
# - MODE=typescript_next
# - MODE=lint
#
#matrix:
# allow_failures:
# - env: "MODE=saucelabs_optional"
# - env: "MODE=browserstack_optional"
#
#addons:
# firefox: "38.0"
# apt:
# sources:
# - ubuntu-toolchain-r-test
# packages:
# - g++-4.8
#
#before_install:
# - node tools/analytics/build-analytics start ci job
# - node tools/analytics/build-analytics start ci before_install
# - echo ${TSDRC} > .tsdrc
# - export CHROME_BIN=$HOME/.chrome/chromium/chrome-linux/chrome
# - export DISPLAY=:99.0
# - export GIT_SHA=$(git rev-parse HEAD)
# - ./scripts/ci/init_android.sh
# - sh -e /etc/init.d/xvfb start
# # Use a separate SauseLabs account for upstream/master builds in order for Sauce to create a badge representing the status of just upstream/master
# - '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && [ "${TRAVIS_BRANCH}" = "master" ] && SAUCE_USERNAME="angular2-ci" && SAUCE_ACCESS_KEY="693ebc16208a-0b5b-1614-8d66-a2662f4e" || true'
# - node tools/analytics/build-analytics success ci before_install
#
#install:
# - node tools/analytics/build-analytics start ci install
# # Install version of npm that we are locked against
# - npm install -g npm@3.5.3
# # Install version of Chromium that we are locked against
# - ./scripts/ci/install_chromium.sh
# # Install version of Dart based on the matrix build variables
# - ./scripts/ci/install_dart.sh ${DART_CHANNEL} ${DART_VERSION} ${ARCH}
# # Print the size of caches to ease debugging
# - du -sh ./node_modules || true
# # Install npm dependecies
# # check-node-modules will exit(1) if we don't need to install
# # we need to manually kick off the postinstall script if check-node-modules exit(0)s
# - node tools/npm/check-node-modules --purge && npm install || npm run postinstall
# - node tools/analytics/build-analytics success ci install
#
#before_script:
# - node tools/analytics/build-analytics start ci before_script
# - mkdir -p $LOGS_DIR
# - ./scripts/ci/presubmit-queue-setup.sh
# - node tools/analytics/build-analytics success ci before_script
#
#script:
# - node tools/analytics/build-analytics start ci script
# - ./scripts/ci/build_and_test.sh ${MODE}
# - node tools/analytics/build-analytics success ci script
#
#after_script:
# - node tools/analytics/build-analytics start ci after_script
# - ./scripts/ci/print-logs.sh
# - ./scripts/ci/after-script.sh
# - ./scripts/publish/publish-build-artifacts.sh
# - node tools/analytics/build-analytics success ci after_script
# - tools/analytics/build-analytics $TRAVIS_TEST_RESULT ci job
#
#notifications:
# webhooks:
# urls:
# - https://webhooks.gitter.im/e/1ef62e23078036f9cee4
# # trigger Buildtime Trend Service to parse Travis CI log
# - https://buildtimetrend.herokuapp.com/travis
# - http://104.197.9.155:8484/hubot/travis/activity
# on_success: always # options: [always|never|change] default: always
# on_failure: always # options: [always|never|change] default: always
# on_start: never # default: never
# slack:
# secure: EP4MzZ8JMyNQJ4S3cd5LEPWSMjC7ZRdzt3veelDiOeorJ6GwZfCDHncR+4BahDzQAuqyE/yNpZqaLbwRWloDi15qIUsm09vgl/1IyNky1Sqc6lEknhzIXpWSalo4/T9ZP8w870EoDvM/UO+LCV99R3wS8Nm9o99eLoWVb2HIUu0=

View File

@ -1,3 +1,133 @@
<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.0.2"></a>
## [2.0.2](https://github.com/angular/angular/compare/2.0.1...2.0.2) (2016-10-05)
### Bug Fixes
* **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:** 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](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)
* **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/co
* **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:** 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))
mmit/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))
* **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)
<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.1"></a>
## [2.0.1](https://github.com/angular/angular/compare/2.0.0...2.0.1) (2016-09-23)
### Bug Fixes
* **common:** fix ngOnChanges signature of NgTemplateOutlet directive ([14ee759](https://github.com/angular/angular/commit/14ee759))
* **compiler:** `[attribute~=value]` selector ([#11696](https://github.com/angular/angular/issues/11696)) ([734b8b8](https://github.com/angular/angular/commit/734b8b8)), closes [#9644](https://github.com/angular/angular/issues/9644)
* **compiler:** safe property access expressions work in event bindings ([#11724](https://github.com/angular/angular/issues/11724)) ([a95d652](https://github.com/angular/angular/commit/a95d652))
* **compiler:** throw when Component.moduleId is not a string ([bd4045b](https://github.com/angular/angular/commit/bd4045b)), closes [#11590](https://github.com/angular/angular/issues/11590)
* **compiler:** do not provide I18N values when they're not specified ([03aedbe](https://github.com/angular/angular/commit/03aedbe)), closes [#11643](https://github.com/angular/angular/issues/11643)
* **core:** ContentChild descendants should be queried by default ([0dc15eb](https://github.com/angular/angular/commit/0dc15eb)), closes [#11645](https://github.com/angular/angular/issues/11645)
* **forms:** disable all radios with disable() ([2860418](https://github.com/angular/angular/commit/2860418))
* **forms:** make setDisabledState optional for reactive form directives ([#11731](https://github.com/angular/angular/issues/11731)) ([51d73d3](https://github.com/angular/angular/commit/51d73d3)), closes [#11719](https://github.com/angular/angular/issues/11719)
* **forms:** support unbound disabled in ngModel ([#11736](https://github.com/angular/angular/issues/11736)) ([39e251e](https://github.com/angular/angular/commit/39e251e))
* **upgrade:** allow attribute selectors for components in ng2 which are not part of upgrade ([#11808](https://github.com/angular/angular/issues/11808)) ([b81e2e7](https://github.com/angular/angular/commit/b81e2e7)), closes [#11280](https://github.com/angular/angular/issues/11280)
<a name="2.0.0"></a>
# [2.0.0](https://github.com/angular/angular/compare/2.0.0-rc.7...2.0.0) (2016-09-14)
### Bug Fixes
* **platform-browser:** provide Title service as part of the module ([#11605](https://github.com/angular/angular/issues/11605)) ([85d9db6](https://github.com/angular/angular/commit/85d9db6)), closes [#11600](https://github.com/angular/angular/issues/11600)
* **upgrade:** correct the main entry path in package.json ([a2519c6](https://github.com/angular/angular/commit/a2519c6))
<a name="2.0.0-rc.7"></a>
# [2.0.0-rc.7](https://github.com/angular/angular/compare/2.0.0-rc.6...2.0.0-rc.7) (2016-09-13)
### Bug Fixes
* **core:** allow `group()` to be used as entry point for an animation trigger ([#11419](https://github.com/angular/angular/issues/11419)) ([6a2bbff](https://github.com/angular/angular/commit/6a2bbff)), closes [#11312](https://github.com/angular/angular/issues/11312)
* **core:** ensure parent animations are triggered before children ([#11201](https://github.com/angular/angular/issues/11201)) ([c9e5b59](https://github.com/angular/angular/commit/c9e5b59))
* **core:** correctly type event handler proxy functions ([aa9b617](https://github.com/angular/angular/commit/aa9b617))
* **core:** fix error when `NgModule.bootstrap` contains `undefined` or `null` ([ea95c39](https://github.com/angular/angular/commit/ea95c39))
* **core:** fix an issue with webpack and lazy loader. ([#11387](https://github.com/angular/angular/issues/11387)) ([d26a827](https://github.com/angular/angular/commit/d26a827))
* **core:** BCP47 uses hyphens as separator ([#11514](https://github.com/angular/angular/issues/11514)) ([7b82877](https://github.com/angular/angular/commit/7b82877))
* **compiler:** fix `CssSelector#getMatchingElementTemplate()` for void tags ([077e0be](https://github.com/angular/angular/commit/077e0be)), closes [#11407](https://github.com/angular/angular/issues/11407)
* **compiler:** add missing elements to DOMSchema ([d309f77](https://github.com/angular/angular/commit/d309f77)), closes [#11219](https://github.com/angular/angular/issues/11219)
* **compiler:** fix perf regression in ShadowCss ([#11420](https://github.com/angular/angular/issues/11420)) ([78ad9ad](https://github.com/angular/angular/commit/78ad9ad)), closes [#11371](https://github.com/angular/angular/issues/11371)
* **compiler-cli:** prepend a rootDir when assuming a file exists ([#11291](https://github.com/angular/angular/issues/11291)) ([c315359](https://github.com/angular/angular/commit/c315359))
* **compiler-cli:** propagate errors to main ([#11214](https://github.com/angular/angular/issues/11214)) ([5e5ae3c](https://github.com/angular/angular/commit/5e5ae3c))
* **compiler-cli:** use the compilerHost to detect file existence ([#11418](https://github.com/angular/angular/issues/11418)) ([9e2ec7a](https://github.com/angular/angular/commit/9e2ec7a))
* **forms:** clear errors on disable ([#11463](https://github.com/angular/angular/issues/11463)) ([673de00](https://github.com/angular/angular/commit/673de00)), closes [#11287](https://github.com/angular/angular/issues/11287)
* **forms:** disabled controls should never be invalid ([#11257](https://github.com/angular/angular/issues/11257)) ([043493c](https://github.com/angular/angular/commit/043493c)), closes [#11253](https://github.com/angular/angular/issues/11253)
* **forms:** fix disabled support for empty form containers ([#11427](https://github.com/angular/angular/issues/11427)) ([7b24028](https://github.com/angular/angular/commit/7b24028)), closes [#11386](https://github.com/angular/angular/issues/11386)
* **forms:** fix resetting radios ([#11546](https://github.com/angular/angular/issues/11546)) ([61aad79](https://github.com/angular/angular/commit/61aad79)), closes [#11516](https://github.com/angular/angular/issues/11516)
* **forms:** rename validator change fn due to conflict ([#11492](https://github.com/angular/angular/issues/11492)) ([53f0c22](https://github.com/angular/angular/commit/53f0c22)), closes [#11479](https://github.com/angular/angular/issues/11479)
* **forms:** support dots in control names in contains ([#11542](https://github.com/angular/angular/issues/11542)) ([79055f7](https://github.com/angular/angular/commit/79055f7)), closes [#11535](https://github.com/angular/angular/issues/11535)
* **forms:** support rebinding nested controls ([#11210](https://github.com/angular/angular/issues/11210)) ([8c09933](https://github.com/angular/angular/commit/8c09933))
### Code Refactoring
* **core:** remove `…Metadata` for all decorators and use the decorator directly. ([63e15ff](https://github.com/angular/angular/commit/63e15ff))
### PEER-DEPENDENCY UPDATES ###
* **core**: zone.js@0.6.21
* **core**: rxjs@5.0.0-beta.12
### BREAKING CHANGES
* core: - all `…Metadata` classes have been removed. Use the corresponding decorator
as constructor or for `instanceof` checks instead.
- Example:
* Before: `new ComponentMetadata(…)`
* After: `new Component(…)`
- Note: `new Component(…)` worked before as well.
<a name="2.0.0-rc.6"></a> <a name="2.0.0-rc.6"></a>
# [2.0.0-rc.6](https://github.com/angular/angular/compare/2.0.0-rc.5...2.0.0-rc.6) (2016-08-31) # [2.0.0-rc.6](https://github.com/angular/angular/compare/2.0.0-rc.5...2.0.0-rc.6) (2016-08-31)
@ -12,7 +142,7 @@
* **compiler:** make ShadowCSS shim work on Android browser ([#11139](https://github.com/angular/angular/issues/11139)) ([38069ab](https://github.com/angular/angular/commit/38069ab)), closes [#11123](https://github.com/angular/angular/issues/11123) * **compiler:** make ShadowCSS shim work on Android browser ([#11139](https://github.com/angular/angular/issues/11139)) ([38069ab](https://github.com/angular/angular/commit/38069ab)), closes [#11123](https://github.com/angular/angular/issues/11123)
* **compiler:** no longer use assetCacheKey and Function#name for token identity. ([51877ef](https://github.com/angular/angular/commit/51877ef)), closes [#10545](https://github.com/angular/angular/issues/10545) [#10538](https://github.com/angular/angular/issues/10538) * **compiler:** no longer use assetCacheKey and Function#name for token identity. ([51877ef](https://github.com/angular/angular/commit/51877ef)), closes [#10545](https://github.com/angular/angular/issues/10545) [#10538](https://github.com/angular/angular/issues/10538)
* **compiler:** only emit metadata for exported enums ([#10957](https://github.com/angular/angular/issues/10957)) ([a7b7682](https://github.com/angular/angular/commit/a7b7682)) * **compiler:** only emit metadata for exported enums ([#10957](https://github.com/angular/angular/issues/10957)) ([a7b7682](https://github.com/angular/angular/commit/a7b7682))
* **compiler:** throw descriptive error meesage for invalid NgModule providers ([#10947](https://github.com/angular/angular/issues/10947)) ([aa5c8ca](https://github.com/angular/angular/commit/aa5c8ca)), closes [#10714](https://github.com/angular/angular/issues/10714) * **compiler:** throw descriptive error message for invalid NgModule providers ([#10947](https://github.com/angular/angular/issues/10947)) ([aa5c8ca](https://github.com/angular/angular/commit/aa5c8ca)), closes [#10714](https://github.com/angular/angular/issues/10714)
* **compiler:** detect invalid elements in templates via DomSchemaRegistry ([1df69cb](https://github.com/angular/angular/commit/1df69cb)) * **compiler:** detect invalid elements in templates via DomSchemaRegistry ([1df69cb](https://github.com/angular/angular/commit/1df69cb))
* **compiler:** ExtractorMerger returns return errors together with nodes (as a ParseTreeResult) ([39c0f9e](https://github.com/angular/angular/commit/39c0f9e)) * **compiler:** ExtractorMerger returns return errors together with nodes (as a ParseTreeResult) ([39c0f9e](https://github.com/angular/angular/commit/39c0f9e))
* **compiler:** throw better errors when components are passed to imports or modules are passed to declarations. ([#10888](https://github.com/angular/angular/issues/10888)) ([c4fd862](https://github.com/angular/angular/commit/c4fd862)), closes [#10823](https://github.com/angular/angular/issues/10823) * **compiler:** throw better errors when components are passed to imports or modules are passed to declarations. ([#10888](https://github.com/angular/angular/issues/10888)) ([c4fd862](https://github.com/angular/angular/commit/c4fd862)), closes [#10823](https://github.com/angular/angular/issues/10823)
@ -103,6 +233,7 @@
* **compiler:** Added "strictMetadataEmit" option to ngc ([#10951](https://github.com/angular/angular/issues/10951)) ([39a2c39](https://github.com/angular/angular/commit/39a2c39)) * **compiler:** Added "strictMetadataEmit" option to ngc ([#10951](https://github.com/angular/angular/issues/10951)) ([39a2c39](https://github.com/angular/angular/commit/39a2c39))
* **compiler-cli:** allow ngc implementations to provide XHR ([#10708](https://github.com/angular/angular/issues/10708)) ([6e842fc](https://github.com/angular/angular/commit/6e842fc)) * **compiler-cli:** allow ngc implementations to provide XHR ([#10708](https://github.com/angular/angular/issues/10708)) ([6e842fc](https://github.com/angular/angular/commit/6e842fc))
* **compiler-cli:** support pathmapping using a separate reflector ([#10985](https://github.com/angular/angular/issues/10985)) ([e0fbca9](https://github.com/angular/angular/commit/e0fbca9)) * **compiler-cli:** support pathmapping using a separate reflector ([#10985](https://github.com/angular/angular/issues/10985)) ([e0fbca9](https://github.com/angular/angular/commit/e0fbca9))
* **core:** support animation trigger template callbacks ([45e8e73](https://github.com/angular/angular/commit/45e8e73670b96387fc109921fad299742d3f7cbf))
* **core:** make sure animation callback reports the totalTime ([#11022](https://github.com/angular/angular/issues/11022)) ([4f8f8cf](https://github.com/angular/angular/commit/4f8f8cf)) * **core:** make sure animation callback reports the totalTime ([#11022](https://github.com/angular/angular/issues/11022)) ([4f8f8cf](https://github.com/angular/angular/commit/4f8f8cf))
* **core:** add NO_ERRORS_SCHEMA that allows any properties to be set on any element ([#10956](https://github.com/angular/angular/issues/10956)) ([c631cfc](https://github.com/angular/angular/commit/c631cfc)) * **core:** add NO_ERRORS_SCHEMA that allows any properties to be set on any element ([#10956](https://github.com/angular/angular/issues/10956)) ([c631cfc](https://github.com/angular/angular/commit/c631cfc))
* **core:** make ngprobe tokens pluggable ([f48142e](https://github.com/angular/angular/commit/f48142e)) * **core:** make ngprobe tokens pluggable ([f48142e](https://github.com/angular/angular/commit/f48142e))
@ -137,6 +268,16 @@
### BREAKING CHANGES ### BREAKING CHANGES
* npm packages: code in ESM (ES6 Modules) format is now published at the default location in the npm package with `package.json`'s `main` entry pointing to an UMD bundle (primarily for node, and webpack 1 users).
If you are using SystemJS to load Angular, you should adjust your SystemJS configuration to point to the UMD bundles (present in the npm package).
Please see this [example SystemJS config](https://github.com/angular/quickstart/blob/3b7452cc444c49c139ea39523ced0468c2362c16/systemjs.config.js#L17-L34).
* testing config: due to zone.js peer-dependency upgrade, the order in which various zone specs are loaded has changed.
Please see this [example Karma config](https://github.com/angular/quickstart/blob/3b7452cc444c49c139ea39523ced0468c2362c16/karma.conf.js#L31-L38).
* core: `Type` is now `Type<T>` which means that in most cases you have to * core: `Type` is now `Type<T>` which means that in most cases you have to
use `Type<any>` in place of `Type`. use `Type<any>` in place of `Type`.
@ -183,7 +324,7 @@ prefix using `animate-` must now be preixed using `bind-animate-`.
We now only accept: We now only accept:
``` ```
{provider: MyClass, toFactory: ...} {provide: MyClass, useFactory: ...}
``` ```
* core: previously deprecated NgZoneError has been removed * core: previously deprecated NgZoneError has been removed

View File

@ -18,7 +18,7 @@ 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?
If you have questions about how to *use* Angular, please direct them to the [Google Group][angular-group] 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 Angular 2 is still in early developer preview, and the core team's capacity to answer usage questions is limited. We are also available on [Gitter][gitter]. 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].
## <a name="issue"></a> Found an Issue? ## <a name="issue"></a> Found an Issue?
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
@ -28,8 +28,7 @@ If you find a bug in the source code, you can help us by
## <a name="feature"></a> Want 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][github]. 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. Angular 2 is in developer preview a proposal for your work first, to be sure that we can use it.
and we are not ready to accept major contributions ahead of the full release.
Please consider what kind of change it is: Please consider what kind of change it is:
* For a **Major Feature**, first open an issue and outline your proposal so that it can be * For a **Major Feature**, first open an issue and outline your proposal so that it can be

View File

@ -114,7 +114,7 @@ You should execute the 3 test suites before submitting a PR to github.
All the tests are executed on our Continuous Integration infrastructure and a PR could only be merged once the tests pass. All the tests are executed on our Continuous Integration infrastructure and a PR could only be merged once the tests pass.
- CircleCI fails if your code is not formatted properly, - CircleCI fails if your code is not formatted properly,
- Travis CI fails if any of the test suite describe above fails. - Travis CI fails if any of the test suites described above fails.
## Update the public API tests ## Update the public API tests

View File

@ -4,9 +4,9 @@
[![Issue Stats](http://issuestats.com/github/angular/angular/badge/pr?style=flat)](http://issuestats.com/github/angular/angular) [![Issue Stats](http://issuestats.com/github/angular/angular/badge/pr?style=flat)](http://issuestats.com/github/angular/angular)
[![Issue Stats](http://issuestats.com/github/angular/angular/badge/issue?style=flat)](http://issuestats.com/github/angular/angular) [![Issue Stats](http://issuestats.com/github/angular/angular/badge/issue?style=flat)](http://issuestats.com/github/angular/angular)
[![npm version](https://badge.fury.io/js/%40angular%2Fcore.svg)](https://badge.fury.io/js/%40angular%2Fcore) [![npm version](https://badge.fury.io/js/%40angular%2Fcore.svg)](https://badge.fury.io/js/%40angular%2Fcore)
[![Downloads](http://img.shields.io/npm/dm/angular2.svg)](https://npmjs.org/package/angular2)
[![Sauce Test Status](https://saucelabs.com/browser-matrix/angular2-ci.svg)](https://saucelabs.com/u/angular2-ci) [![Sauce Test Status](https://saucelabs.com/browser-matrix/angular2-ci.svg)](https://saucelabs.com/u/angular2-ci)
*Safari (7+), iOS (7+), Edge (14) and IE mobile (11) are tested on [BrowserStack][browserstack].*
Angular Angular
========= =========
@ -16,7 +16,6 @@ repository for [Angular 2][ng2] Typescript/JavaScript (JS).
Angular2 for [Dart][dart] can be found at [dart-lang/angular2][ng2dart]. Angular2 for [Dart][dart] can be found at [dart-lang/angular2][ng2dart].
Angular 2 is currently in **Release Candidate**.
## Quickstart ## Quickstart
@ -28,10 +27,9 @@ Angular 2 is currently in **Release Candidate**.
Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on our Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on our
guidelines for [contributing][contributing] and then check out one of our issues in the [hotlist: community-help](https://github.com/angular/angular/labels/hotlist%3A%20community-help). guidelines for [contributing][contributing] and then check out one of our issues in the [hotlist: community-help](https://github.com/angular/angular/labels/hotlist%3A%20community-help).
[browserstack]: https://www.browserstack.com/
[contributing]: http://github.com/angular/angular/blob/master/CONTRIBUTING.md [contributing]: http://github.com/angular/angular/blob/master/CONTRIBUTING.md
[dart]: http://www.dartlang.org [dart]: http://www.dartlang.org
[dartium]: http://www.dartlang.org/tools/dartium
[quickstart]: https://angular.io/docs/ts/latest/quickstart.html [quickstart]: https://angular.io/docs/ts/latest/quickstart.html
[ng2]: http://angular.io [ng2]: http://angular.io
[ngDart]: http://angulardart.org [ngDart]: http://angulardart.org

View File

@ -1,31 +1,104 @@
# 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 to triage issues on github. This document describes how the Angular team uses labels and milestones
to triage issues on github. The basic idea of the process is that
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
be triaged further.
# Issues and PRs Once this process is implemented and in use, we will revisit it to see
## Triaged vs Untriaged Issues if further labeling is needed.
Every triaged issue must have four attributes assigned to it: ## Components
* `priority` -- P0 through P4. P0 issues are "drop everything and do this now". P4 are nice to have. A caretaker should be able to determine which component the issue
* `component` -- Which area of Angular knowledge this relates to. belongs to. The components have a clear piece of source code associated
* `effort` -- Rough assessment of how much work this issue is. E.g. `effort: easy` means with it.
"probably a few hours of work".
* `type` -- Whether this issue is a bug, feature, or other kind of task.
Untriaged issues are any issues in the queue that don't yet have these four attributes. * `comp: animations`: `@matsko`
* `comp: benchpress`: `@tbosch`
* `comp: build & ci`: `@IgorMinar` -- All build and CI scripts
* `comp: common`: `@mhevery` -- This includes core components / pipes.
* `comp: core & compiler`: `@tbosch` -- Because core and compiler are very
intertwined, we will be treating them as one.
* `comp: forms`: `@kara`
* `comp: http`: `@jeffbcross`
* `comp: i18n`: `@vicb`
* `comp: metadata-extractor`: `@chuckjaz`
* `comp: router`: `@vsavkin`
* `comp: testing`: `@juliemr`
* `comp: upgrade`: `@mhevery`
* `comp: web-worker`: `@vicb`
* `comp: zones`: `@mhevery`
You can view a report of untriaged issues here, in our There are few components which are cross-cutting. They don't have
[Angular Triage Dashboard](http://mhevery.github.io/github_issues/). a clear location in the source tree. We will treat them as a component
even thought no specific source tree is associated with them.
* `comp: docs`: `@naomiblack`
* `comp: packaging`: `@IgorMinar`
* `comp: performance`: `@tbosch`
* `comp: security`: `@IgorMinar`
## Type
What kind of problem is this?
* `type: RFC / discussion / question`
* `type: bug`
* `type: chore`
* `type: feature`
* `type: performance`
* `type: refactor`
## Caretaker Triage Process
It is the caretaker's responsibility to assign `comp: *` to each new
issue as they come in. The reason why we limit the responsibility of the
caretaker to this one label is that it is likely that without domain
knowledge the caretaker could mislabel issues or lack knowledge of
duplicate issues.
## Component's owner Triage Process
At this point we are leaving each component owner to determine their own
process for their component.
It will be up to the component owner to determine the order in which the
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`
Issues should also have a clear action to complete that can be addressed or resolved within the
scope of Angular 2. We'll close issues that don't meet these criteria.
### 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:
@ -37,7 +110,10 @@ We aim to only have at most three milestones open at a time:
The [backlog](https://github.com/angular/angular/issues?q=is%3Aopen+is%3Aissue+no%3Amilestone) The [backlog](https://github.com/angular/angular/issues?q=is%3Aopen+is%3Aissue+no%3Amilestone)
consists of all issues that have been triaged but do not have an assignee or milestone. consists of all issues that have been triaged but do not have an assignee or milestone.
## Triaged vs Untriaged PRs ## Triaged vs Untrained PRs
PRs should also be label with a `comp: *` so that it is clear which
primary area the PR effects.
Because of the cumulative pain associated with rebasing PRs, we triage PRs daily, and Because of the cumulative pain associated with rebasing PRs, we triage PRs daily, and
closing or reviewing PRs is a top priority ahead of other ongoing work. closing or reviewing PRs is a top priority ahead of other ongoing work.
@ -63,90 +139,6 @@ uncontroversial change.
PRs do not need to be assigned to milestones, unless a milestone release should be held for that PRs do not need to be assigned to milestones, unless a milestone release should be held for that
PR to land. PR to land.
Victor (`vsavkin`) and Tobias (`tbosch`) are owners of the PR queue. Here is a list of [current
untriaged PRs](https://github.com/angular/angular/pulls?utf8=%E2%9C%93&q=is%3Aopen+no%3Amilestone+is%3Apr+-label%3A%22pr_action%3A+cleanup%22+-label%3A%22pr_action%3A+merge%22+-label%3A%22pr_action%3A+review%22+-label%3A%22pr_action%3A+discuss%22+-label%3A%22pr_state%3A+blocked%22+-label%3A%22pr_state%3A+WIP%22+).
# Prioritization of Work
What should you be working on?
1. Any PRs that are assigned to you that don't have `pr_state: WIP` or `pr_state: blocked`
1. Any issues that are assigned to you in the lowest-numbered Milestone
1. Any issues that are assigned to you in any Milestone
If there are no issues assigned to you in any Milestone, pick an issue, self-assign it, and add
it to the most appropriate Milestone based on effort.
Here are some suggestions for what to work on next:
* Filter for issues in a component that you are knowledgeable about, and pick something that has a
high priority.
* Filter for any small effort task that has the special `cust: GT` or `cust:Ionic` tags,
and priority > P3.
* Add a new task that's really important, add `component`, `priority`, `effort`, `type` and
assign it to yourself and the most appropriate milestone.
# Labels Used in Triage
## Priority
How urgent is this issue? We use priority to determine what should be worked on in each new
milestone.
* `P0: critical` -- drop everything to work on this
* `P1: urgent` -- resolve quickly in the current milestone. people are blocked
* `P2: required` -- needed for development but not urgent yet. workaround exists, or e.g. new API
* `P3: important` -- must complete before Angular 2 is ready for release
* `P4: nice to have` -- a good idea, but maybe not until after release
## Effort
Rough, non-binding estimate of how much work this issue represents. Please change this assessment
for anything you're working on to better reflect reality.
* `effort: easy` -- straightforward issue that can be resolved in a few hours, e.g. < 1 day of work.
* `effort: medium` -- issue that will be a few days of work. Can be completed within a single
milestone.
* `effort: tough` -- issue that will likely take more than 1 milestone to complete.
<!-- We don't like these label names as
they're not absolute (what is one developer-hour, really?) but decided it wasn't worth arguing
over terms. -->
## Component
Which area of Angular knowledge is this issue most closely related to? Helpful when deciding what
to work on next.
* `comp: benchpress` -- benchmarks and performance testing &rarr; *tbosch*, *crossj*
* `comp: build/dev-productivity` -- build process, e.g. CLI and related tasks &rarr; *iminar*, *caitp*
* `comp: build/pipeline` -- build pipeline, e.g. ts2dart &rarr; *mprobst*, *alexeagle*
* `comp: core` -- general core Angular issues, not related to a sub-category (see below) &rarr;
*mhevery*
* `comp: core/animations` -- animations framework &rarr; *matsko*
* `comp: core/change_detection` -- change detection &rarr; *vsavkin*
* `comp: core/di` -- dependency injection &rarr; *vicb*, *rkirov*
* `comp: core/directives` -- directives
* `comp: core/forms` -- forms &rarr; *vsavkin*
* `comp: core/pipes` -- pipes
* `comp: core/view` -- runtime processing of the `View`s
* `comp: core/view/compiler` -- static analysis of the templates which generate `ProtoView`s.
* `comp: core/testbed` -- e2e tests and support for them
* `comp: core/webworker` -- core web worker infrastructure
* `comp: dart-transformer` -- Dart transforms &rarr; *kegluneq*, *jakemac*
* `comp: data-access` -- &rarr; *jeffbcross*
* `comp: docs` -- API docs and doc generation &rarr; *naomiblack*, *petebacondarwin*
* `comp: material-components` -- Angular Material components built in Angular 2 &rarr; *jelbourn*
* `comp: router` -- Component Router &rarr; *btford*, *igorminar*, *matsko*
* `comp: wrenchjs`
## Type
What kind of problem is this?
* `type RFC / discussion / question`
* `type bug`
* `type chore`
* `type feature`
* `type performance`
* `type refactor`
## Special Labels ## Special Labels
@ -160,9 +152,6 @@ More active discussion is needed before the issue can be worked on further. Typi
Managed by googlebot. Indicates whether a PR has a CLA on file for its author(s). Only issues with Managed by googlebot. Indicates whether a PR has a CLA on file for its author(s). Only issues with
`cla:yes` should be merged into master. `cla:yes` should be merged into master.
### cust
This is an issue causing user pain for early adopter customers `cust: GT` or `cust: Ionic`.
### WORKS_AS_INTENDED ### WORKS_AS_INTENDED
Only used on closed issues, to indicate to the reporter why we closed it. Only used on closed issues, to indicate to the reporter why we closed it.

View File

@ -1,216 +1,98 @@
// Unique place to configure the browsers which are used in the different CI jobs in Sauce Labs (SL) and BrowserStack (BS). /**
* @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
*/
// Unique place to configure the browsers which are used in the different CI jobs in Sauce Labs (SL)
// and BrowserStack (BS).
// If the target is set to null, then the browser is not run anywhere during CI. // If the target is set to null, then the browser is not run anywhere during CI.
// If a category becomes empty (e.g. BS and required), then the corresponding job must be commented out in Travis configuration. // If a category becomes empty (e.g. BS and required), then the corresponding job must be commented
// out in Travis configuration.
var CIconfiguration = { var CIconfiguration = {
'Chrome': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}}, 'Chrome': {unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'Firefox': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}}, 'Firefox': {unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
// FirefoxBeta and ChromeBeta should be target:'BS' or target:'SL', and required:true // FirefoxBeta and ChromeBeta should be target:'BS' or target:'SL', and required:true
// Currently deactivated due to https://github.com/angular/angular/issues/7560 // Currently deactivated due to https://github.com/angular/angular/issues/7560
'ChromeBeta': { unitTest: {target: null, required: true}, e2e: {target: null, required: false}}, 'ChromeBeta': {unitTest: {target: null, required: true}, e2e: {target: null, required: false}},
'FirefoxBeta': { unitTest: {target: null, required: false}, e2e: {target: null, required: false}}, 'FirefoxBeta': {unitTest: {target: null, required: false}, e2e: {target: null, required: false}},
'ChromeDev': { unitTest: {target: null, required: true}, e2e: {target: null, required: true}}, 'ChromeDev': {unitTest: {target: null, required: true}, e2e: {target: null, required: true}},
'FirefoxDev': { unitTest: {target: null, required: true}, e2e: {target: null, required: true}}, 'FirefoxDev': {unitTest: {target: null, required: true}, e2e: {target: null, required: true}},
'IE9': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}}, 'IE9': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'IE10': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}}, 'IE10': {unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'IE11': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}}, 'IE11': {unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'Edge': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}, 'Edge': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'Android4.1': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}}, 'Android4.1': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Android4.2': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}}, 'Android4.2': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Android4.3': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}}, 'Android4.3': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Android4.4': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}}, 'Android4.4': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Android5': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}}, 'Android5': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Safari7': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}, 'Safari7': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'Safari8': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}, 'Safari8': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'Safari9': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}, 'Safari9': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'iOS7': { unitTest: {target: 'BS', required: true}, e2e: {target: null, required: true}}, 'Safari10': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'iOS8': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}, 'iOS7': {unitTest: {target: 'BS', required: true}, e2e: {target: null, required: true}},
'iOS9': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}, 'iOS8': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'WindowsPhone': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}} 'iOS9': {unitTest: {target: 'BS', 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}}
}; };
var customLaunchers = { var customLaunchers = {
'DartiumWithWebPlatform': { 'DartiumWithWebPlatform':
base: 'Dartium', {base: 'Dartium', flags: ['--enable-experimental-web-platform-features']},
flags: ['--enable-experimental-web-platform-features'] }, 'ChromeNoSandbox': {base: 'Chrome', flags: ['--no-sandbox']},
'ChromeNoSandbox': { 'SL_CHROME': {base: 'SauceLabs', browserName: 'chrome', version: '52'},
base: 'Chrome', 'SL_CHROMEBETA': {base: 'SauceLabs', browserName: 'chrome', version: 'beta'},
flags: ['--no-sandbox'] }, 'SL_CHROMEDEV': {base: 'SauceLabs', browserName: 'chrome', version: 'dev'},
'SL_CHROME': { 'SL_FIREFOX': {base: 'SauceLabs', browserName: 'firefox', version: '46'},
base: 'SauceLabs', 'SL_FIREFOXBETA': {base: 'SauceLabs', browserName: 'firefox', version: 'beta'},
browserName: 'chrome', 'SL_FIREFOXDEV': {base: 'SauceLabs', browserName: 'firefox', version: 'dev'},
version: '52' 'SL_SAFARI7': {base: 'SauceLabs', browserName: 'safari', platform: 'OS X 10.9', version: '7.0'},
}, 'SL_SAFARI8': {base: 'SauceLabs', browserName: 'safari', platform: 'OS X 10.10', version: '8.0'},
'SL_CHROMEBETA': { 'SL_SAFARI9': {base: 'SauceLabs', browserName: 'safari', platform: 'OS X 10.11', version: '9.0'},
base: 'SauceLabs', 'SL_SAFARI10':
browserName: 'chrome', {base: 'SauceLabs', browserName: 'safari', platform: 'OS X 10.12', version: '10.0'},
version: 'beta' 'SL_IOS7': {base: 'SauceLabs', browserName: 'iphone', platform: 'OS X 10.10', version: '7.1'},
}, 'SL_IOS8': {base: 'SauceLabs', browserName: 'iphone', platform: 'OS X 10.10', version: '8.4'},
'SL_CHROMEDEV': { 'SL_IOS9': {base: 'SauceLabs', browserName: 'iphone', platform: 'OS X 10.10', version: '9.3'},
base: 'SauceLabs', 'SL_IOS10': {base: 'SauceLabs', browserName: 'iphone', platform: 'OS X 10.10', version: '10.0'},
browserName: 'chrome', 'SL_IE9':
version: 'dev' {base: 'SauceLabs', browserName: 'internet explorer', platform: 'Windows 2008', version: '9'},
},
'SL_FIREFOX': {
base: 'SauceLabs',
browserName: 'firefox',
version: '46'
},
'SL_FIREFOXBETA': {
base: 'SauceLabs',
browserName: 'firefox',
version: 'beta'
},
'SL_FIREFOXDEV': {
base: 'SauceLabs',
browserName: 'firefox',
version: 'dev'
},
'SL_SAFARI7': {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.9',
version: '7.0'
},
'SL_SAFARI8': {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.10',
version: '8.0'
},
'SL_SAFARI9': {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.11',
version: '9.0'
},
'SL_IOS7': {
base: 'SauceLabs',
browserName: 'iphone',
platform: 'OS X 10.10',
version: '7.1'
},
'SL_IOS8': {
base: 'SauceLabs',
browserName: 'iphone',
platform: 'OS X 10.10',
version: '8.4'
},
'SL_IOS9': {
base: 'SauceLabs',
browserName: 'iphone',
platform: 'OS X 10.10',
version: '9.3'
},
'SL_IE9': {
base: 'SauceLabs',
browserName: 'internet explorer',
platform: 'Windows 2008',
version: '9'
},
'SL_IE10': { 'SL_IE10': {
base: 'SauceLabs', base: 'SauceLabs',
browserName: 'internet explorer', browserName: 'internet explorer',
platform: 'Windows 2012', platform: 'Windows 2012',
version: '10' version: '10'
}, },
'SL_IE11': { 'SL_IE11':
base: 'SauceLabs', {base: 'SauceLabs', browserName: 'internet explorer', platform: 'Windows 8.1', version: '11'},
browserName: 'internet explorer',
platform: 'Windows 8.1',
version: '11'
},
'SL_EDGE': { 'SL_EDGE': {
base: 'SauceLabs', base: 'SauceLabs',
browserName: 'MicrosoftEdge', browserName: 'MicrosoftEdge',
platform: 'Windows 10', platform: 'Windows 10',
version: '13.10586' version: '13.10586'
}, },
'SL_ANDROID4.1': { 'SL_ANDROID4.1': {base: 'SauceLabs', browserName: 'android', platform: 'Linux', version: '4.1'},
base: 'SauceLabs', 'SL_ANDROID4.2': {base: 'SauceLabs', browserName: 'android', platform: 'Linux', version: '4.2'},
browserName: 'android', 'SL_ANDROID4.3': {base: 'SauceLabs', browserName: 'android', platform: 'Linux', version: '4.3'},
platform: 'Linux', 'SL_ANDROID4.4': {base: 'SauceLabs', browserName: 'android', platform: 'Linux', version: '4.4'},
version: '4.1' 'SL_ANDROID5': {base: 'SauceLabs', browserName: 'android', platform: 'Linux', version: '5.1'},
},
'SL_ANDROID4.2': {
base: 'SauceLabs',
browserName: 'android',
platform: 'Linux',
version: '4.2'
},
'SL_ANDROID4.3': {
base: 'SauceLabs',
browserName: 'android',
platform: 'Linux',
version: '4.3'
},
'SL_ANDROID4.4': {
base: 'SauceLabs',
browserName: 'android',
platform: 'Linux',
version: '4.4'
},
'SL_ANDROID5': {
base: 'SauceLabs',
browserName: 'android',
platform: 'Linux',
version: '5.1'
},
'BS_CHROME': { 'BS_CHROME': {base: 'BrowserStack', browser: 'chrome', os: 'OS X', os_version: 'Yosemite'},
base: 'BrowserStack', 'BS_FIREFOX': {base: 'BrowserStack', browser: 'firefox', os: 'Windows', os_version: '10'},
browser: 'chrome', 'BS_SAFARI7': {base: 'BrowserStack', browser: 'safari', os: 'OS X', os_version: 'Mavericks'},
os: 'OS X', 'BS_SAFARI8': {base: 'BrowserStack', browser: 'safari', os: 'OS X', os_version: 'Yosemite'},
os_version: 'Yosemite' 'BS_SAFARI9': {base: 'BrowserStack', browser: 'safari', os: 'OS X', os_version: 'El Capitan'},
}, 'BS_SAFARI10': {base: 'BrowserStack', browser: 'safari', os: 'OS X', os_version: 'Sierra'},
'BS_FIREFOX': { 'BS_IOS7': {base: 'BrowserStack', device: 'iPhone 5S', os: 'ios', os_version: '7.0'},
base: 'BrowserStack', 'BS_IOS8': {base: 'BrowserStack', device: 'iPhone 6', os: 'ios', os_version: '8.3'},
browser: 'firefox', 'BS_IOS9': {base: 'BrowserStack', device: 'iPhone 6S', os: 'ios', os_version: '9.1'},
os: 'Windows', 'BS_IOS10': {base: 'BrowserStack', device: 'iPhone SE', os: 'ios', os_version: '10.0'},
os_version: '10' 'BS_IE9':
}, {base: 'BrowserStack', browser: 'ie', browser_version: '9.0', os: 'Windows', os_version: '7'},
'BS_SAFARI7': {
base: 'BrowserStack',
browser: 'safari',
os: 'OS X',
os_version: 'Mavericks'
},
'BS_SAFARI8': {
base: 'BrowserStack',
browser: 'safari',
os: 'OS X',
os_version: 'Yosemite'
},
'BS_SAFARI9': {
base: 'BrowserStack',
browser: 'safari',
os: 'OS X',
os_version: 'El Capitan'
},
'BS_IOS7': {
base: 'BrowserStack',
device: 'iPhone 5S',
os: 'ios',
os_version: '7.0'
},
'BS_IOS8': {
base: 'BrowserStack',
device: 'iPhone 6',
os: 'ios',
os_version: '8.3'
},
'BS_IOS9': {
base: 'BrowserStack',
device: 'iPhone 6S',
os: 'ios',
os_version: '9.1'
},
'BS_IE9': {
base: 'BrowserStack',
browser: 'ie',
browser_version: '9.0',
os: 'Windows',
os_version: '7'
},
'BS_IE10': { 'BS_IE10': {
base: 'BrowserStack', base: 'BrowserStack',
browser: 'ie', browser: 'ie',
@ -225,58 +107,35 @@ var customLaunchers = {
os: 'Windows', os: 'Windows',
os_version: '10' os_version: '10'
}, },
'BS_EDGE': { 'BS_EDGE': {base: 'BrowserStack', browser: 'edge', os: 'Windows', os_version: '10'},
base: 'BrowserStack', 'BS_WINDOWSPHONE':
browser: 'edge', {base: 'BrowserStack', device: 'Nokia Lumia 930', os: 'winphone', os_version: '8.1'},
os: 'Windows', 'BS_ANDROID5': {base: 'BrowserStack', device: 'Google Nexus 5', os: 'android', os_version: '5.0'},
os_version: '10' 'BS_ANDROID4.4': {base: 'BrowserStack', device: 'HTC One M8', os: 'android', os_version: '4.4'},
}, 'BS_ANDROID4.3':
'BS_WINDOWSPHONE' : { {base: 'BrowserStack', device: 'Samsung Galaxy S4', os: 'android', os_version: '4.3'},
base: 'BrowserStack', 'BS_ANDROID4.2':
device: 'Nokia Lumia 930', {base: 'BrowserStack', device: 'Google Nexus 4', os: 'android', os_version: '4.2'},
os: 'winphone', 'BS_ANDROID4.1':
os_version: '8.1' {base: 'BrowserStack', device: 'Google Nexus 7', os: 'android', os_version: '4.1'}
},
'BS_ANDROID5': {
base: 'BrowserStack',
device: 'Google Nexus 5',
os: 'android',
os_version: '5.0'
},
'BS_ANDROID4.4': {
base: 'BrowserStack',
device: 'HTC One M8',
os: 'android',
os_version: '4.4'
},
'BS_ANDROID4.3': {
base: 'BrowserStack',
device: 'Samsung Galaxy S4',
os: 'android',
os_version: '4.3'
},
'BS_ANDROID4.2': {
base: 'BrowserStack',
device: 'Google Nexus 4',
os: 'android',
os_version: '4.2'
},
'BS_ANDROID4.1': {
base: 'BrowserStack',
device: 'Google Nexus 7',
os: 'android',
os_version: '4.1'
}
}; };
var sauceAliases = { var sauceAliases = {
'ALL': Object.keys(customLaunchers).filter(function(item) {return customLaunchers[item].base == 'SauceLabs';}), 'ALL': Object.keys(customLaunchers).filter(function(item) {
'DESKTOP': ['SL_CHROME', 'SL_FIREFOX', 'SL_IE9', 'SL_IE10', 'SL_IE11', 'SL_EDGE', 'SL_SAFARI7', 'SL_SAFARI8', 'SL_SAFARI9'], return customLaunchers[item].base == 'SauceLabs';
'MOBILE': ['SL_ANDROID4.1', 'SL_ANDROID4.2', 'SL_ANDROID4.3', 'SL_ANDROID4.4', 'SL_ANDROID5', 'SL_IOS7', 'SL_IOS8', 'SL_IOS9'], }),
'DESKTOP': [
'SL_CHROME', 'SL_FIREFOX', 'SL_IE9', 'SL_IE10', 'SL_IE11', 'SL_EDGE', 'SL_SAFARI7',
'SL_SAFARI8', 'SL_SAFARI9', 'SL_SAFARI10'
],
'MOBILE': [
'SL_ANDROID4.1', 'SL_ANDROID4.2', 'SL_ANDROID4.3', 'SL_ANDROID4.4', 'SL_ANDROID5', 'SL_IOS7',
'SL_IOS8', 'SL_IOS9', 'SL_IOS10'
],
'ANDROID': ['SL_ANDROID4.1', 'SL_ANDROID4.2', 'SL_ANDROID4.3', 'SL_ANDROID4.4', 'SL_ANDROID5'], 'ANDROID': ['SL_ANDROID4.1', 'SL_ANDROID4.2', 'SL_ANDROID4.3', 'SL_ANDROID4.4', 'SL_ANDROID5'],
'IE': ['SL_IE9', 'SL_IE10', 'SL_IE11'], 'IE': ['SL_IE9', 'SL_IE10', 'SL_IE11'],
'IOS': ['SL_IOS7', 'SL_IOS8', 'SL_IOS9'], 'IOS': ['SL_IOS7', 'SL_IOS8', 'SL_IOS9', 'SL_IOS10'],
'SAFARI': ['SL_SAFARI7', 'SL_SAFARI8', 'SL_SAFARI9'], 'SAFARI': ['SL_SAFARI7', 'SL_SAFARI8', 'SL_SAFARI9', 'SL_SAFARI10'],
'BETA': ['SL_CHROMEBETA', 'SL_FIREFOXBETA'], 'BETA': ['SL_CHROMEBETA', 'SL_FIREFOXBETA'],
'DEV': ['SL_CHROMEDEV', 'SL_FIREFOXDEV'], 'DEV': ['SL_CHROMEDEV', 'SL_FIREFOXDEV'],
'CI_REQUIRED': buildConfiguration('unitTest', 'SL', true), 'CI_REQUIRED': buildConfiguration('unitTest', 'SL', true),
@ -284,13 +143,20 @@ var sauceAliases = {
}; };
var browserstackAliases = { var browserstackAliases = {
'ALL': Object.keys(customLaunchers).filter(function(item) {return customLaunchers[item].base == 'BrowserStack';}), 'ALL': Object.keys(customLaunchers).filter(function(item) {
'DESKTOP': ['BS_CHROME', 'BS_FIREFOX', 'BS_IE9', 'BS_IE10', 'BS_IE11', 'BS_EDGE', 'BS_SAFARI7', 'BS_SAFARI8', 'BS_SAFARI9'], return customLaunchers[item].base == 'BrowserStack';
'MOBILE': ['BS_ANDROID4.3', 'BS_ANDROID4.4', 'BS_IOS7', 'BS_IOS8', 'BS_IOS9', 'BS_WINDOWSPHONE'], }),
'DESKTOP': [
'BS_CHROME', 'BS_FIREFOX', 'BS_IE9', 'BS_IE10', 'BS_IE11', 'BS_EDGE', 'BS_SAFARI7',
'BS_SAFARI8', 'BS_SAFARI9', 'BS_SAFARI10'
],
'MOBILE': [
'BS_ANDROID4.3', 'BS_ANDROID4.4', 'BS_IOS7', 'BS_IOS8', 'BS_IOS9', 'BS_IOS10', 'BS_WINDOWSPHONE'
],
'ANDROID': ['BS_ANDROID4.3', 'BS_ANDROID4.4'], 'ANDROID': ['BS_ANDROID4.3', 'BS_ANDROID4.4'],
'IE': ['BS_IE9', 'BS_IE10', 'BS_IE11'], 'IE': ['BS_IE9', 'BS_IE10', 'BS_IE11'],
'IOS': ['BS_IOS7', 'BS_IOS8', 'BS_IOS9'], 'IOS': ['BS_IOS7', 'BS_IOS8', 'BS_IOS9', 'BS_IOS10'],
'SAFARI': ['BS_SAFARI7', 'BS_SAFARI8', 'BS_SAFARI9'], 'SAFARI': ['BS_SAFARI7', 'BS_SAFARI8', 'BS_SAFARI9', 'BS_SAFARI10'],
'CI_REQUIRED': buildConfiguration('unitTest', 'BS', true), 'CI_REQUIRED': buildConfiguration('unitTest', 'BS', true),
'CI_OPTIONAL': buildConfiguration('unitTest', 'BS', false) 'CI_OPTIONAL': buildConfiguration('unitTest', 'BS', false)
}; };
@ -303,11 +169,9 @@ module.exports = {
function buildConfiguration(type, target, required) { function buildConfiguration(type, target, required) {
return Object.keys(CIconfiguration) return Object.keys(CIconfiguration)
.filter((item) => { .filter((item) => {
var conf = CIconfiguration[item][type]; var conf = CIconfiguration[item][type];
return conf.required === required && conf.target === target; return conf.required === required && conf.target === target;
}) })
.map((item) => { .map((item) => target + '_' + item.toUpperCase());
return target + '_' + item.toUpperCase();
});
} }

164
build.sh
View File

@ -4,73 +4,97 @@ set -e -o pipefail
cd `dirname $0` cd `dirname $0`
PACKAGES=(core
compiler
common
forms
platform-browser
platform-browser-dynamic
platform-server
platform-webworker
platform-webworker-dynamic
http
router
upgrade
compiler-cli
benchpress)
BUILD_ALL=true
BUNDLE=true
for ARG in "$@"; do
case "$ARG" in
--packages=*)
PACKAGES_STR=${ARG#--packages=}
PACKAGES=( ${PACKAGES_STR//,/ } )
BUILD_ALL=false
;;
--bundle=*)
BUNDLE=( "${ARG#--bundle=}" )
;;
*)
echo "Unknown option $ARG."
exit 1
;;
esac
done
export NODE_PATH=${NODE_PATH}:$(pwd)/dist/all:$(pwd)/dist/tools export NODE_PATH=${NODE_PATH}:$(pwd)/dist/all:$(pwd)/dist/tools
rm -rf ./dist/all/
mkdir -p ./dist/all/
TSCONFIG=./tools/tsconfig.json
echo "====== (all)COMPILING: \$(npm bin)/tsc -p ${TSCONFIG} ====="
$(npm bin)/tsc -p ${TSCONFIG}
cp ./tools/@angular/tsc-wrapped/package.json ./dist/tools/@angular/tsc-wrapped
echo "====== Copying files needed for e2e tests ====="
cp -r ./modules/playground ./dist/all/
cp -r ./modules/playground/favicon.ico ./dist/
#rsync -aP ./modules/playground/* ./dist/all/playground/
mkdir ./dist/all/playground/vendor
cd ./dist/all/playground/vendor
ln -s ../../../../node_modules/core-js/client/core.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/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/rxjs .
ln -s ../../../../node_modules/angular/angular.js .
cd -
echo "====== Copying files needed for benchmarks ====="
cp -r ./modules/benchmarks ./dist/all/
cp -r ./modules/benchmarks/favicon.ico ./dist/
mkdir ./dist/all/benchmarks/vendor
cd ./dist/all/benchmarks/vendor
ln -s ../../../../node_modules/core-js/client/core.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/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/rxjs .
ln -s ../../../../node_modules/angular/angular.js .
ln -s ../../../../bower_components/polymer .
cd -
TSCONFIG=./modules/tsconfig.json
echo "====== (all)COMPILING: \$(npm bin)/tsc -p ${TSCONFIG} ====="
# compile ts code
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
$TSC -p modules/tsconfig.json TSCONFIG=./tools/tsconfig.json
echo "====== (tools)COMPILING: \$(npm bin)/tsc -p ${TSCONFIG} ====="
rm -rf ./dist/tools/
mkdir -p ./dist/tools/
$(npm bin)/tsc -p ${TSCONFIG}
rm -rf ./dist/packages-dist cp ./tools/@angular/tsc-wrapped/package.json ./dist/tools/@angular/tsc-wrapped
for PACKAGE in \ if [[ ${BUILD_ALL} == true ]]; then
core \ rm -rf ./dist/all/
compiler \ mkdir -p ./dist/all/
common \
forms \ echo "====== Copying files needed for e2e tests ====="
platform-browser \ cp -r ./modules/playground ./dist/all/
platform-browser-dynamic \ cp -r ./modules/playground/favicon.ico ./dist/
platform-server \ #rsync -aP ./modules/playground/* ./dist/all/playground/
platform-webworker \ mkdir ./dist/all/playground/vendor
platform-webworker-dynamic \ cd ./dist/all/playground/vendor
http \ ln -s ../../../../node_modules/core-js/client/core.js .
router \ ln -s ../../../../node_modules/zone.js/dist/zone.js .
upgrade \ ln -s ../../../../node_modules/zone.js/dist/long-stack-trace-zone.js .
compiler-cli \ ln -s ../../../../node_modules/systemjs/dist/system.src.js .
benchpress ln -s ../../../../node_modules/base64-js/lib/b64.js .
ln -s ../../../../node_modules/reflect-metadata/Reflect.js .
ln -s ../../../../node_modules/rxjs .
ln -s ../../../../node_modules/angular/angular.js .
cd -
echo "====== Copying files needed for benchmarks ====="
cp -r ./modules/benchmarks ./dist/all/
cp -r ./modules/benchmarks/favicon.ico ./dist/
mkdir ./dist/all/benchmarks/vendor
cd ./dist/all/benchmarks/vendor
ln -s ../../../../node_modules/core-js/client/core.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/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/rxjs .
ln -s ../../../../node_modules/angular/angular.js .
ln -s ../../../../bower_components/polymer .
ln -s ../../../../node_modules/incremental-dom/dist/incremental-dom-cjs.js
cd -
TSCONFIG=./modules/tsconfig.json
echo "====== (all)COMPILING: \$(npm bin)/tsc -p ${TSCONFIG} ====="
# compile ts code
$TSC -p modules/tsconfig.json
rm -rf ./dist/packages-dist
fi
for PACKAGE in ${PACKAGES[@]}
do do
PWD=`pwd` PWD=`pwd`
SRCDIR=${PWD}/modules/@angular/${PACKAGE} SRCDIR=${PWD}/modules/@angular/${PACKAGE}
@ -80,10 +104,13 @@ do
UMD_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}.umd.min.js UMD_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}.umd.min.js
LICENSE_BANNER=${PWD}/modules/@angular/license-banner.txt LICENSE_BANNER=${PWD}/modules/@angular/license-banner.txt
echo "====== COMPILING: ${TSC} -p ${SRCDIR}/tsconfig.json =====" rm -rf ${DESTDIR}
$TSC -p ${SRCDIR}/tsconfig.json
echo "====== COMPILING: ${TSC} -p ${SRCDIR}/tsconfig-build.json ====="
$TSC -p ${SRCDIR}/tsconfig-build.json
cp ${SRCDIR}/package.json ${DESTDIR}/ cp ${SRCDIR}/package.json ${DESTDIR}/
cp ${PWD}/modules/@angular/README.md ${DESTDIR}/
if [[ -e ${SRCDIR}/tsconfig-testing.json ]]; then if [[ -e ${SRCDIR}/tsconfig-testing.json ]]; then
echo "====== COMPILING TESTING: ${TSC} -p ${SRCDIR}/tsconfig-testing.json" echo "====== COMPILING TESTING: ${TSC} -p ${SRCDIR}/tsconfig-testing.json"
@ -102,7 +129,12 @@ do
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i -E 's/^( +)abstract ([[:alnum:]]+\:)/\1\2/g' find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i -E 's/^( +)abstract ([[:alnum:]]+\:)/\1\2/g'
fi fi
if [[ ${PACKAGE} != compiler-cli && ${PACKAGE} != benchpress ]]; then if [[ ${PACKAGE} == benchpress ]]; then
cp ${SRCDIR}/*.md ${DESTDIR}
cp -r ${SRCDIR}/docs ${DESTDIR}
fi
if [[ ${BUNDLE} == true && ${PACKAGE} != compiler-cli && ${PACKAGE} != benchpress ]]; then
echo "====== BUNDLING: ${SRCDIR} =====" echo "====== BUNDLING: ${SRCDIR} ====="
mkdir ${DESTDIR}/bundles mkdir ${DESTDIR}/bundles
@ -129,3 +161,5 @@ do
fi fi
done done
./modules/@angular/examples/build.sh

View File

@ -1,31 +1,51 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
'use strict'; 'use strict';
// THIS CHECK SHOULD BE THE FIRST THING IN THIS FILE // THIS CHECK SHOULD BE THE FIRST THING IN THIS FILE
// This is to ensure that we catch env issues before we error while requiring other dependencies. // This is to ensure that we catch env issues before we error while requiring other dependencies.
require('./tools/check-environment')( require('./tools/check-environment')({
{requiredNpmVersion: '>=3.5.3 <4.0.0', requiredNodeVersion: '>=5.4.1 <6.0.0'}); requiredNpmVersion: '>=3.5.3 <4.0.0',
requiredNodeVersion: '>=5.4.1 <7.0.0',
});
const gulp = require('gulp'); const gulp = require('gulp');
const path = require('path'); const path = require('path');
const os = require('os'); const os = require('os');
const srcsToFmt = // clang-format entry points
['tools/**/*.ts', 'modules/@angular/**/*.ts', '!tools/public_api_guard/**/*.d.ts', const srcsToFmt = [
'modules/playground/**/*.ts', 'modules/benchmarks/**/*.ts', 'modules/e2e_util/**/*.ts']; 'modules/@angular/**/*.{js,ts}',
'modules/benchmarks/**/*.{js,ts}',
'modules/e2e_util/**/*.{js,ts}',
'modules/playground/**/*.{js,ts}',
'tools/**/*.{js,ts}',
'!tools/public_api_guard/**/*.d.ts',
'./*.{js,ts}',
'!shims_for_IE.js',
];
// Check source code for formatting errors (clang-format)
gulp.task('format:enforce', () => { gulp.task('format:enforce', () => {
const format = require('gulp-clang-format'); const format = require('gulp-clang-format');
const clangFormat = require('clang-format'); const clangFormat = require('clang-format');
return gulp.src(srcsToFmt).pipe( return gulp.src(srcsToFmt).pipe(
format.checkFormat('file', clangFormat, {verbose: true, fail: true})); format.checkFormat('file', clangFormat, {verbose: true, fail: true}));
}); });
// Format the source code with clang-format (see .clang-format)
gulp.task('format', () => { gulp.task('format', () => {
const format = require('gulp-clang-format'); const format = require('gulp-clang-format');
const clangFormat = require('clang-format'); const clangFormat = require('clang-format');
return gulp.src(srcsToFmt, { base: '.' }).pipe( return gulp.src(srcsToFmt, {base: '.'})
format.format('file', clangFormat)).pipe(gulp.dest('.')); .pipe(format.format('file', clangFormat))
.pipe(gulp.dest('.'));
}); });
const entrypoints = [ const entrypoints = [
@ -49,78 +69,110 @@ const entrypoints = [
'dist/packages-dist/http/index.d.ts', 'dist/packages-dist/http/index.d.ts',
'dist/packages-dist/http/testing/index.d.ts', 'dist/packages-dist/http/testing/index.d.ts',
'dist/packages-dist/forms/index.d.ts', 'dist/packages-dist/forms/index.d.ts',
'dist/packages-dist/router/index.d.ts' 'dist/packages-dist/router/index.d.ts',
]; ];
const publicApiDir = path.normalize('tools/public_api_guard'); const publicApiDir = path.normalize('tools/public_api_guard');
const publicApiArgs = [ const publicApiArgs = [
'--rootDir', 'dist/packages-dist', '--rootDir',
'--stripExportPattern', '^__', 'dist/packages-dist',
'--allowModuleIdentifiers', 'jasmine', '--stripExportPattern',
'--allowModuleIdentifiers', 'protractor', '^__',
'--allowModuleIdentifiers', 'angular', '--allowModuleIdentifiers',
'--onStabilityMissing', 'error' 'jasmine',
'--allowModuleIdentifiers',
'protractor',
'--allowModuleIdentifiers',
'angular',
'--onStabilityMissing',
'error',
].concat(entrypoints); ].concat(entrypoints);
// Build angular
gulp.task('build.sh', (done) => { gulp.task('build.sh', (done) => {
const childProcess = require('child_process'); const childProcess = require('child_process');
childProcess.exec(path.join(__dirname, 'build.sh'), error => done(error)); childProcess.exec(path.join(__dirname, 'build.sh'), done);
}); });
// Enforce that the public API matches the golden files
// Note that these two commands work on built d.ts files instead of the source // Note that these two commands work on built d.ts files instead of the source
gulp.task('public-api:enforce', (done) => { gulp.task('public-api:enforce', (done) => {
const childProcess = require('child_process'); const childProcess = require('child_process');
childProcess childProcess
.spawn( .spawn(
path.join(__dirname, `/node_modules/.bin/ts-api-guardian${/^win/.test(os.platform()) ? '.cmd' : ''}`), path.join(__dirname, platformScriptPath(`/node_modules/.bin/ts-api-guardian`)),
['--verifyDir', publicApiDir].concat(publicApiArgs), {stdio: 'inherit'}) ['--verifyDir', publicApiDir].concat(publicApiArgs), {stdio: 'inherit'})
.on('close', (errorCode) => { .on('close', (errorCode) => {
if (errorCode !== 0) { if (errorCode !== 0) {
done(new Error( done(new Error(
'Public API differs from golden file. Please run `gulp public-api:update`.')); 'Public API differs from golden file. Please run `gulp public-api:update`.'));
} else { } else {
done(); done();
} }
}); });
}); });
// Generate the public API golden files
gulp.task('public-api:update', ['build.sh'], (done) => { gulp.task('public-api:update', ['build.sh'], (done) => {
const childProcess = require('child_process'); const childProcess = require('child_process');
childProcess childProcess
.spawn( .spawn(
path.join(__dirname, `/node_modules/.bin/ts-api-guardian${/^win/.test(os.platform()) ? '.cmd' : ''}`), path.join(__dirname, platformScriptPath(`/node_modules/.bin/ts-api-guardian`)),
['--outDir', publicApiDir].concat(publicApiArgs), {stdio: 'inherit'}) ['--outDir', publicApiDir].concat(publicApiArgs), {stdio: 'inherit'})
.on('close', (errorCode) => done(errorCode)); .on('close', done);
}); });
gulp.task('lint', ['format:enforce', 'tools:build'], () => { // 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
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://github.com/palantir/tslint#supported-rules // https://github.com/palantir/tslint#supported-rules
const tslintConfig = require('./tslint.json'); const tslintConfig = require('./tslint.json');
return gulp.src(['modules/@angular/**/*.ts', 'modules/benchpress/**/*.ts']) return gulp
.pipe(tslint({ .src([
tslint: require('tslint').default, // todo(vicb): add .js files when supported
configuration: tslintConfig, // see https://github.com/palantir/tslint/pull/1515
rulesDirectory: 'dist/tools/tslint', 'modules/@angular/**/*.ts',
formatter: 'prose' 'modules/benchpress/**/*.ts',
})) './*.ts',
.pipe(tslint.report({emitError: true})); ])
.pipe(tslint({
tslint: require('tslint').default,
configuration: tslintConfig,
rulesDirectory: 'dist/tools/tslint',
formatter: 'prose',
}))
.pipe(tslint.report({emitError: true}));
}); });
gulp.task('tools:build', (done) => { tsc('tools/', done); }); gulp.task('tools:build', (done) => { tsc('tools/', done); });
// Check for circular dependency in the source code
gulp.task('check-cycle', (done) => { gulp.task('check-cycle', (done) => {
const madge = require('madge'); const madge = require('madge');
var dependencyObject = madge(['dist/all/'], { const dependencyObject = madge(['dist/all/'], {
format: 'cjs', format: 'cjs',
extensions: ['.js'], extensions: ['.js'],
onParseFile: function(data) { data.src = data.src.replace(/\/\* circular \*\//g, "//"); } onParseFile: function(data) { data.src = data.src.replace(/\/\* circular \*\//g, '//'); }
}); });
var circularDependencies = dependencyObject.circular().getArray(); const circularDependencies = dependencyObject.circular().getArray();
if (circularDependencies.length > 0) { if (circularDependencies.length > 0) {
console.log('Found circular dependencies!'); console.log('Found circular dependencies!');
console.log(circularDependencies); console.log(circularDependencies);
@ -129,34 +181,47 @@ gulp.task('check-cycle', (done) => {
done(); done();
}); });
// Serve the built files
gulp.task('serve', () => { gulp.task('serve', () => {
let connect = require('gulp-connect'); const connect = require('gulp-connect');
let cors = require('cors'); const cors = require('cors');
connect.server({ connect.server({
root: `${__dirname}/dist`, root: `${__dirname}/dist`,
port: 8000, port: 8000,
livereload: false, livereload: false,
open: false, open: false,
middleware: (connect, opt) => [cors()] middleware: (connect, opt) => [cors()],
});
});
// Serve the examples
gulp.task('serve-examples', () => {
const connect = require('gulp-connect');
const cors = require('cors');
connect.server({
root: `${__dirname}/dist/examples`,
port: 8001,
livereload: false,
open: false,
middleware: (connect, opt) => [cors()],
}); });
}); });
// Update the changelog with the latest changes
gulp.task('changelog', () => { gulp.task('changelog', () => {
const conventionalChangelog = require('gulp-conventional-changelog'); const conventionalChangelog = require('gulp-conventional-changelog');
return gulp.src('CHANGELOG.md') return gulp.src('CHANGELOG.md')
.pipe(conventionalChangelog({ .pipe(conventionalChangelog({preset: 'angular', releaseCount: 1}, {
preset: 'angular', // Conventional Changelog Context
releaseCount: 1 // We have to manually set version number so it doesn't get prefixed with `v`
}, { // See https://github.com/conventional-changelog/conventional-changelog-core/issues/10
// Conventional Changelog Context currentTag: require('./package.json').version
// We have to manually set version number so it doesn't get prefixed with `v` }))
// See https://github.com/conventional-changelog/conventional-changelog-core/issues/10 .pipe(gulp.dest('./'));
currentTag: require('./package.json').version
}))
.pipe(gulp.dest('./'));
}); });
function tsc(projectPath, done) { function tsc(projectPath, done) {
@ -164,8 +229,12 @@ function tsc(projectPath, done) {
childProcess childProcess
.spawn( .spawn(
path.normalize(`${__dirname}/node_modules/.bin/tsc`) + (/^win/.test(os.platform()) ? '.cmd' : ''), path.normalize(platformScriptPath(`${__dirname}/node_modules/.bin/tsc`)),
['-p', path.join(__dirname, projectPath)], ['-p', path.join(__dirname, projectPath)], {stdio: 'inherit'})
{stdio: 'inherit'}) .on('close', done);
.on('close', (errorCode) => done(errorCode)); }
// returns the script path for the current platform
function platformScriptPath(path) {
return /^win/.test(os.platform()) ? `${path}.cmd` : path;
} }

View File

@ -1,3 +1,11 @@
/**
* @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
*/
var browserProvidersConf = require('./browser-providers.conf.js'); var browserProvidersConf = require('./browser-providers.conf.js');
var internalAngularReporter = require('./tools/karma/reporter.js'); var internalAngularReporter = require('./tools/karma/reporter.js');
@ -17,24 +25,25 @@ module.exports = function(config) {
// include Angular v1 for upgrade module testing // include Angular v1 for upgrade module testing
'node_modules/angular/angular.min.js', 'node_modules/angular/angular.min.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: 'modules/@angular/platform-browser/test/static_assets/**',
{pattern: 'dist/all/empty.*', included: false, watched: false}, included: false,
{pattern: 'modules/@angular/platform-browser/test/static_assets/**', included: false, watched: false}, watched: false
{pattern: 'modules/@angular/platform-browser/test/browser/static_assets/**', included: false, watched: false} },
{
pattern: 'modules/@angular/platform-browser/test/browser/static_assets/**',
included: false,
watched: false,
}
], ],
exclude: [ exclude: [
@ -43,7 +52,8 @@ module.exports = function(config) {
'dist/all/@angular/compiler-cli/**', 'dist/all/@angular/compiler-cli/**',
'dist/all/@angular/benchpress/**', 'dist/all/@angular/benchpress/**',
'dist/all/angular1_router.js', 'dist/all/angular1_router.js',
'dist/all/@angular/platform-browser/testing/e2e_util.js' 'dist/all/@angular/platform-browser/testing/e2e_util.js',
'dist/examples/**/e2e_test/**',
], ],
customLaunchers: browserProvidersConf.customLaunchers, customLaunchers: browserProvidersConf.customLaunchers,
@ -54,11 +64,11 @@ module.exports = function(config) {
'karma-sauce-launcher', 'karma-sauce-launcher',
'karma-chrome-launcher', 'karma-chrome-launcher',
'karma-sourcemap-loader', 'karma-sourcemap-loader',
internalAngularReporter internalAngularReporter,
], ],
preprocessors: { preprocessors: {
'**/*.js': ['sourcemap'] '**/*.js': ['sourcemap'],
}, },
reporters: ['internal-angular'], reporters: ['internal-angular'],
@ -72,7 +82,7 @@ module.exports = function(config) {
'selenium-version': '2.53.0', 'selenium-version': '2.53.0',
'command-timeout': 600, 'command-timeout': 600,
'idle-timeout': 600, 'idle-timeout': 600,
'max-duration': 5400 'max-duration': 5400,
} }
}, },
@ -81,20 +91,21 @@ module.exports = function(config) {
startTunnel: false, startTunnel: false,
retryLimit: 3, retryLimit: 3,
timeout: 600, timeout: 600,
pollingTimeout: 10000 pollingTimeout: 10000,
}, },
browsers: ['Chrome'], browsers: ['Chrome'],
port: 9876, port: 9876,
captureTimeout: 60000, captureTimeout: 60000,
browserDisconnectTimeout : 60000, browserDisconnectTimeout: 60000,
browserDisconnectTolerance : 3, browserDisconnectTolerance: 3,
browserNoActivityTimeout : 60000, browserNoActivityTimeout: 60000,
}); });
if (process.env.TRAVIS) { if (process.env.TRAVIS) {
var buildId = 'TRAVIS #' + process.env.TRAVIS_BUILD_NUMBER + ' (' + process.env.TRAVIS_BUILD_ID + ')'; var buildId =
'TRAVIS #' + process.env.TRAVIS_BUILD_NUMBER + ' (' + process.env.TRAVIS_BUILD_ID + ')';
if (process.env.CI_MODE.startsWith('saucelabs')) { if (process.env.CI_MODE.startsWith('saucelabs')) {
config.sauceLabs.build = buildId; config.sauceLabs.build = buildId;
config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER; config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;

View File

@ -1,15 +0,0 @@
Angular2
=========
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. This is the repository for the upcoming 2.0 version. If you're looking for the current official version of Angular you should go to [angular/angular.js](https://github.com/angular/angular.js)
This package contains different sources for different users:
1. The files located in the root folder can be consumed using CommonJS.
2. The files under `/es6` are es6 compatible files that can be transpiled to
es5 using any transpiler. This contains:
* `dev/`: a development version that includes runtime type assertions
* `prod/`: a production version that does not include runtime type assertions
3. The files under `/ts` are the TypeScript source files.
License: Apache MIT 2.0

View File

@ -0,0 +1,6 @@
Angular
=======
The sources for this package are in the main [Angular](https://github.com/angular/angular) repo. Please file issues and pull requests against that repo.
License: MIT

View File

@ -1,18 +1,16 @@
{ {
"name": "@angular/benchpress", "name": "@angular/benchpress",
"version": "0.0.0-PLACEHOLDER", "version": "0.1.0",
"description": "Benchpress - a framework for e2e performance tests", "description": "Benchpress - a framework for e2e performance tests",
"main": "index.js", "main": "index.js",
"typings": "index.d.ts", "typings": "index.d.ts",
"dependencies": { "dependencies": {
"@angular/core": "0.0.0-PLACEHOLDER", "@angular/core": "^2.0.0-rc.7",
"reflect-metadata": "^0.1.2", "reflect-metadata": "^0.1.2",
"rxjs": "5.0.0-beta.11" "rxjs": "5.0.0-beta.12",
},
"optionalDependencies": {
"jpm": "1.1.4", "jpm": "1.1.4",
"firefox-profile": "0.4.0", "firefox-profile": "0.4.0",
"selenium-webdriver": "3.0.0-beta-2" "selenium-webdriver": "^2.53.3"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -ex
cd $(dirname $0)/../../..
ROOTDIR=$(pwd)
SRCDIR=${ROOTDIR}/modules/@angular/benchpress
DESTDIR=${ROOTDIR}/dist/packages-dist/benchpress
rm -fr ${DESTDIR}
echo "====== BUILDING... ====="
./build.sh --packages=core,benchpress --bundle=false
echo "====== PUBLISHING: ${DESTDIR} ====="
npm publish ${DESTDIR} --access public

View File

@ -9,8 +9,6 @@
import {OpaqueToken} from '@angular/core'; import {OpaqueToken} from '@angular/core';
import * as fs from 'fs'; import * as fs from 'fs';
import {DateWrapper} from './facade/lang';
export class Options { export class Options {
static SAMPLE_ID = new OpaqueToken('Options.sampleId'); static SAMPLE_ID = new OpaqueToken('Options.sampleId');
static DEFAULT_DESCRIPTION = new OpaqueToken('Options.defaultDescription'); static DEFAULT_DESCRIPTION = new OpaqueToken('Options.defaultDescription');
@ -34,7 +32,7 @@ export class Options {
{provide: Options.FORCE_GC, useValue: false}, {provide: Options.FORCE_GC, useValue: false},
{provide: Options.PREPARE, useValue: Options.NO_PREPARE}, {provide: Options.PREPARE, useValue: Options.NO_PREPARE},
{provide: Options.MICRO_METRICS, useValue: {}}, {provide: Options.USER_METRICS, useValue: {}}, {provide: Options.MICRO_METRICS, useValue: {}}, {provide: Options.USER_METRICS, useValue: {}},
{provide: Options.NOW, useValue: () => DateWrapper.now()}, {provide: Options.NOW, useValue: () => new Date()},
{provide: Options.RECEIVED_DATA, useValue: false}, {provide: Options.RECEIVED_DATA, useValue: false},
{provide: Options.REQUEST_COUNT, useValue: false}, {provide: Options.REQUEST_COUNT, useValue: false},
{provide: Options.CAPTURE_FRAMES, useValue: false}, {provide: Options.CAPTURE_FRAMES, useValue: false},

View File

@ -1,3 +0,0 @@
library benchpress.src.firefox_extension.data.installed_script;
//no dart implementation

View File

@ -42,11 +42,11 @@ class Profiler {
} }
addStartEvent(name: string, timeStarted: number) { addStartEvent(name: string, timeStarted: number) {
this._markerEvents.push({ph: 'b', ts: timeStarted - this._profilerStartTime, name: name}); this._markerEvents.push({ph: 'B', ts: timeStarted - this._profilerStartTime, name: name});
} }
addEndEvent(name: string, timeEnded: number) { addEndEvent(name: string, timeEnded: number) {
this._markerEvents.push({ph: 'e', ts: timeEnded - this._profilerStartTime, name: name}); this._markerEvents.push({ph: 'E', ts: timeEnded - this._profilerStartTime, name: name});
} }
} }

View File

@ -6,18 +6,15 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Map} from './facade/collection';
import {Date, DateWrapper} from './facade/lang';
export class MeasureValues { export class MeasureValues {
constructor( constructor(
public runIndex: number, public timeStamp: Date, public values: {[key: string]: any}) {} public runIndex: number, public timeStamp: Date, public values: {[key: string]: any}) {}
toJson() { toJson() {
return { return {
'timeStamp': DateWrapper.toJson(this.timeStamp), 'timeStamp': this.timeStamp.toJSON(),
'runIndex': this.runIndex, 'runIndex': this.runIndex,
'values': this.values 'values': this.values,
}; };
} }
} }

View File

@ -7,7 +7,6 @@
*/ */
import {Injector, OpaqueToken} from '@angular/core'; import {Injector, OpaqueToken} from '@angular/core';
import {StringMapWrapper} from '../facade/collection';
import {Metric} from '../metric'; import {Metric} from '../metric';
@ -57,8 +56,7 @@ export class MultiMetric extends Metric {
function mergeStringMaps(maps: {[key: string]: string}[]): {[key: string]: string} { function mergeStringMaps(maps: {[key: string]: string}[]): {[key: string]: string} {
var result: {[key: string]: string} = {}; var result: {[key: string]: string} = {};
maps.forEach( maps.forEach(map => { Object.keys(map).forEach(prop => { result[prop] = map[prop]; }); });
map => { StringMapWrapper.forEach(map, (value, prop) => { result[prop] = value; }); });
return result; return result;
} }

View File

@ -9,8 +9,6 @@
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 {ListWrapper, StringMapWrapper} from '../facade/collection';
import {Math, NumberWrapper, StringWrapper, isBlank, isPresent} from '../facade/lang';
import {Metric} from '../metric'; import {Metric} from '../metric';
import {PerfLogEvent, PerfLogFeatures, WebDriverExtension} from '../web_driver_extension'; import {PerfLogEvent, PerfLogFeatures, WebDriverExtension} from '../web_driver_extension';
@ -95,8 +93,9 @@ export class PerflogMetric extends Metric {
res['frameTime.smooth'] = 'percentage of frames that hit 60fps'; res['frameTime.smooth'] = 'percentage of frames that hit 60fps';
} }
} }
StringMapWrapper.forEach( for (let name in this._microMetrics) {
this._microMetrics, (desc, name) => { StringMapWrapper.set(res, name, desc); }); res[name] = this._microMetrics[name];
}
return res; return res;
} }
@ -126,8 +125,8 @@ export class PerflogMetric extends Metric {
.then((_) => this._endMeasure(restartMeasure)) .then((_) => this._endMeasure(restartMeasure))
.then((forceGcMeasureValues) => { .then((forceGcMeasureValues) => {
this._captureFrames = originalFrameCaptureValue; this._captureFrames = originalFrameCaptureValue;
StringMapWrapper.set(measureValues, 'forcedGcTime', forceGcMeasureValues['gcTime']); measureValues['forcedGcTime'] = forceGcMeasureValues['gcTime'];
StringMapWrapper.set(measureValues, 'forcedGcAmount', forceGcMeasureValues['gcAmount']); measureValues['forcedGcAmount'] = forceGcMeasureValues['gcAmount'];
return measureValues; return measureValues;
}); });
}); });
@ -152,7 +151,7 @@ export class PerflogMetric extends Metric {
return this._driverExtension.readPerfLog().then((events) => { return this._driverExtension.readPerfLog().then((events) => {
this._addEvents(events); this._addEvents(events);
var result = this._aggregateEvents(this._remainingEvents, markName); var result = this._aggregateEvents(this._remainingEvents, markName);
if (isPresent(result)) { if (result) {
this._remainingEvents = events; this._remainingEvents = events;
return result; return result;
} }
@ -166,14 +165,14 @@ export class PerflogMetric extends Metric {
private _addEvents(events: PerfLogEvent[]) { private _addEvents(events: PerfLogEvent[]) {
var needSort = false; var needSort = false;
events.forEach(event => { events.forEach(event => {
if (StringWrapper.equals(event['ph'], 'X')) { if (event['ph'] === 'X') {
needSort = true; needSort = true;
var startEvent: PerfLogEvent = {}; var startEvent: PerfLogEvent = {};
var endEvent: PerfLogEvent = {}; var endEvent: PerfLogEvent = {};
StringMapWrapper.forEach(event, (value, prop) => { for (let prop in event) {
(<any>startEvent)[prop] = value; startEvent[prop] = event[prop];
(<any>endEvent)[prop] = value; endEvent[prop] = event[prop];
}); }
startEvent['ph'] = 'B'; startEvent['ph'] = 'B';
endEvent['ph'] = 'E'; endEvent['ph'] = 'E';
endEvent['ts'] = startEvent['ts'] + startEvent['dur']; endEvent['ts'] = startEvent['ts'] + startEvent['dur'];
@ -185,7 +184,7 @@ 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
ListWrapper.sort(this._remainingEvents, (a, b) => { this._remainingEvents.sort((a, b) => {
var 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;
}); });
@ -208,7 +207,9 @@ export class PerflogMetric extends Metric {
result['frameTime.worst'] = 0; result['frameTime.worst'] = 0;
result['frameTime.smooth'] = 0; result['frameTime.smooth'] = 0;
} }
StringMapWrapper.forEach(this._microMetrics, (desc, name) => { result[name] = 0; }); for (let name in this._microMetrics) {
result[name] = 0;
}
if (this._receivedData) { if (this._receivedData) {
result['receivedData'] = 0; result['receivedData'] = 0;
} }
@ -218,6 +219,24 @@ export class PerflogMetric extends Metric {
var markStartEvent: PerfLogEvent = null; var markStartEvent: PerfLogEvent = null;
var markEndEvent: PerfLogEvent = null; var markEndEvent: PerfLogEvent = null;
events.forEach((event) => {
var ph = event['ph'];
var name = event['name'];
if (ph === 'B' && name === markName) {
markStartEvent = event;
} else if (ph === 'I' && name === 'navigationStart') {
// if a benchmark measures reload of a page, use the last
// navigationStart as begin event
markStartEvent = event;
} else if (ph === 'E' && name === markName) {
markEndEvent = event;
}
});
if (!markStartEvent || !markEndEvent) {
// not all events have been received, no further processing for now
return null;
}
var gcTimeInScript = 0; var gcTimeInScript = 0;
var renderTimeInScript = 0; var renderTimeInScript = 0;
@ -228,120 +247,99 @@ export class PerflogMetric extends Metric {
var intervalStarts: {[key: string]: PerfLogEvent} = {}; var intervalStarts: {[key: string]: PerfLogEvent} = {};
var intervalStartCount: {[key: string]: number} = {}; var intervalStartCount: {[key: string]: number} = {};
var inMeasureRange = false;
events.forEach((event) => { events.forEach((event) => {
var ph = event['ph']; var ph = event['ph'];
var name = event['name']; var name = event['name'];
var microIterations = 1; var microIterations = 1;
var microIterationsMatch = name.match(_MICRO_ITERATIONS_REGEX); var microIterationsMatch = name.match(_MICRO_ITERATIONS_REGEX);
if (isPresent(microIterationsMatch)) { if (microIterationsMatch) {
name = microIterationsMatch[1]; name = microIterationsMatch[1];
microIterations = NumberWrapper.parseInt(microIterationsMatch[2], 10); microIterations = parseInt(microIterationsMatch[2], 10);
}
if (event === markStartEvent) {
inMeasureRange = true;
} else if (event === markEndEvent) {
inMeasureRange = false;
}
if (!inMeasureRange || event['pid'] !== markStartEvent['pid']) {
return;
} }
if (StringWrapper.equals(ph, 'b') && StringWrapper.equals(name, markName)) { if (this._requestCount && name === 'sendRequest') {
markStartEvent = event;
} else if (StringWrapper.equals(ph, 'e') && StringWrapper.equals(name, markName)) {
markEndEvent = event;
}
let isInstant = StringWrapper.equals(ph, 'I') || StringWrapper.equals(ph, 'i');
if (this._requestCount && StringWrapper.equals(name, 'sendRequest')) {
result['requestCount'] += 1; result['requestCount'] += 1;
} else if (this._receivedData && StringWrapper.equals(name, 'receivedData') && isInstant) { } else if (this._receivedData && name === 'receivedData' && ph === 'I') {
result['receivedData'] += event['args']['encodedDataLength']; result['receivedData'] += event['args']['encodedDataLength'];
} else if (StringWrapper.equals(name, 'navigationStart')) { }
// We count data + requests since the last navigationStart if (ph === 'B' && name === _MARK_NAME_FRAME_CAPUTRE) {
// (there might be chrome extensions loaded by selenium before our page, so there if (frameCaptureStartEvent) {
// will likely be more than one navigationStart). throw new Error('can capture frames only once per benchmark run');
if (this._receivedData) {
result['receivedData'] = 0;
} }
if (this._requestCount) { if (!this._captureFrames) {
result['requestCount'] = 0; throw new Error(
'found start event for frame capture, but frame capture was not requested in benchpress');
}
frameCaptureStartEvent = event;
} else if (ph === 'E' && name === _MARK_NAME_FRAME_CAPUTRE) {
if (!frameCaptureStartEvent) {
throw new Error('missing start event for frame capture');
}
frameCaptureEndEvent = event;
}
if (ph === 'I' && frameCaptureStartEvent && !frameCaptureEndEvent && name === 'frame') {
frameTimestamps.push(event['ts']);
if (frameTimestamps.length >= 2) {
frameTimes.push(
frameTimestamps[frameTimestamps.length - 1] -
frameTimestamps[frameTimestamps.length - 2]);
} }
} }
if (isPresent(markStartEvent) && isBlank(markEndEvent) &&
event['pid'] === markStartEvent['pid']) {
if (StringWrapper.equals(ph, 'b') && StringWrapper.equals(name, _MARK_NAME_FRAME_CAPUTRE)) {
if (isPresent(frameCaptureStartEvent)) {
throw new Error('can capture frames only once per benchmark run');
}
if (!this._captureFrames) {
throw new Error(
'found start event for frame capture, but frame capture was not requested in benchpress');
}
frameCaptureStartEvent = event;
} else if (
StringWrapper.equals(ph, 'e') && StringWrapper.equals(name, _MARK_NAME_FRAME_CAPUTRE)) {
if (isBlank(frameCaptureStartEvent)) {
throw new Error('missing start event for frame capture');
}
frameCaptureEndEvent = event;
}
if (isInstant) { if (ph === 'B') {
if (isPresent(frameCaptureStartEvent) && isBlank(frameCaptureEndEvent) && if (!intervalStarts[name]) {
StringWrapper.equals(name, 'frame')) { intervalStartCount[name] = 1;
frameTimestamps.push(event['ts']); intervalStarts[name] = event;
if (frameTimestamps.length >= 2) { } else {
frameTimes.push( intervalStartCount[name]++;
frameTimestamps[frameTimestamps.length - 1] -
frameTimestamps[frameTimestamps.length - 2]);
}
}
} }
} else if ((ph === 'E') && intervalStarts[name]) {
if (StringWrapper.equals(ph, 'B') || StringWrapper.equals(ph, 'b')) { intervalStartCount[name]--;
if (isBlank(intervalStarts[name])) { if (intervalStartCount[name] === 0) {
intervalStartCount[name] = 1; var startEvent = intervalStarts[name];
intervalStarts[name] = event; var duration = (event['ts'] - startEvent['ts']);
} else { intervalStarts[name] = null;
intervalStartCount[name]++; if (name === 'gc') {
} result['gcTime'] += duration;
} else if ( var amount =
(StringWrapper.equals(ph, 'E') || StringWrapper.equals(ph, 'e')) && (startEvent['args']['usedHeapSize'] - event['args']['usedHeapSize']) / 1000;
isPresent(intervalStarts[name])) { result['gcAmount'] += amount;
intervalStartCount[name]--; var majorGc = event['args']['majorGc'];
if (intervalStartCount[name] === 0) { if (majorGc && majorGc) {
var startEvent = intervalStarts[name]; result['majorGcTime'] += duration;
var duration = (event['ts'] - startEvent['ts']);
intervalStarts[name] = null;
if (StringWrapper.equals(name, 'gc')) {
result['gcTime'] += duration;
var amount =
(startEvent['args']['usedHeapSize'] - event['args']['usedHeapSize']) / 1000;
result['gcAmount'] += amount;
var majorGc = event['args']['majorGc'];
if (isPresent(majorGc) && majorGc) {
result['majorGcTime'] += duration;
}
if (isPresent(intervalStarts['script'])) {
gcTimeInScript += duration;
}
} else if (StringWrapper.equals(name, 'render')) {
result['renderTime'] += duration;
if (isPresent(intervalStarts['script'])) {
renderTimeInScript += duration;
}
} else if (StringWrapper.equals(name, 'script')) {
result['scriptTime'] += duration;
} else if (isPresent(this._microMetrics[name])) {
(<any>result)[name] += duration / microIterations;
} }
if (intervalStarts['script']) {
gcTimeInScript += duration;
}
} else if (name === 'render') {
result['renderTime'] += duration;
if (intervalStarts['script']) {
renderTimeInScript += duration;
}
} else if (name === 'script') {
result['scriptTime'] += duration;
} else if (this._microMetrics[name]) {
(<any>result)[name] += duration / microIterations;
} }
} }
} }
}); });
if (!isPresent(markStartEvent) || !isPresent(markEndEvent)) {
// not all events have been received, no further processing for now
return null;
}
if (isPresent(markEndEvent) && isPresent(frameCaptureStartEvent) && if (frameCaptureStartEvent && !frameCaptureEndEvent) {
isBlank(frameCaptureEndEvent)) {
throw new Error('missing end event for frame capture'); throw new Error('missing end event for frame capture');
} }
if (this._captureFrames && isBlank(frameCaptureStartEvent)) { if (this._captureFrames && !frameCaptureStartEvent) {
throw new Error('frame capture requested in benchpress, but no start event was found'); throw new Error('frame capture requested in benchpress, but no start event was found');
} }
if (frameTimes.length > 0) { if (frameTimes.length > 0) {

View File

@ -6,10 +6,9 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Inject, Injectable, OpaqueToken, Provider} from '@angular/core'; import {Inject, Injectable} from '@angular/core';
import {Options} from '../common_options'; import {Options} from '../common_options';
import {StringMapWrapper} from '../facade/collection';
import {isNumber} from '../facade/lang'; 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';
@ -40,7 +39,7 @@ export class UserMetric extends Metric {
reject = rej; reject = rej;
}); });
let adapter = this._wdAdapter; let adapter = this._wdAdapter;
let names = StringMapWrapper.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}`)))
@ -48,9 +47,9 @@ export class UserMetric extends Metric {
if (values.every(isNumber)) { 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[]) => {
let map = StringMapWrapper.create(); 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++) {
StringMapWrapper.set(map, names[i], values[i]); map[names[i]] = values[i];
} }
resolve(map); resolve(map);
}, reject); }, reject);

View File

@ -7,10 +7,7 @@
*/ */
import {Inject, Injectable, OpaqueToken} from '@angular/core'; import {Inject, Injectable, OpaqueToken} from '@angular/core';
import {print} from '../facade/lang';
import {ListWrapper, StringMapWrapper} from '../facade/collection';
import {NumberWrapper, isBlank, isPresent, print} from '../facade/lang';
import {Math} from '../facade/math';
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';

View File

@ -9,7 +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 {DateWrapper, Json, isBlank, isPresent} from '../facade/lang'; 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';
@ -45,8 +45,7 @@ export class JsonFileReporter extends Reporter {
'completeSample': completeSample, 'completeSample': completeSample,
'validSample': validSample, 'validSample': validSample,
}); });
var filePath = var filePath = `${this._path}/${this._description.id}_${this._now().getTime()}.json`;
`${this._path}/${this._description.id}_${DateWrapper.toMillis(this._now())}.json`;
return this._writeFile(filePath, content); return this._writeFile(filePath, content);
} }
} }

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 {StringMapWrapper} from '../facade/collection';
import {NumberWrapper} from '../facade/lang'; import {NumberWrapper} from '../facade/lang';
import {MeasureValues} from '../measure_values'; import {MeasureValues} from '../measure_values';
import {Statistic} from '../statistic'; import {Statistic} from '../statistic';
@ -17,7 +17,7 @@ export function formatNum(n: number) {
export function sortedProps(obj: {[key: string]: any}) { export function sortedProps(obj: {[key: string]: any}) {
var props: string[] = []; var props: string[] = [];
StringMapWrapper.forEach(obj, (value, prop) => props.push(prop)); props.push(...Object.keys(obj));
props.sort(); props.sort();
return props; return props;
} }

View File

@ -9,7 +9,7 @@
import {Provider, ReflectiveInjector} from '@angular/core'; import {Provider, ReflectiveInjector} from '@angular/core';
import {Options} from './common_options'; import {Options} from './common_options';
import {isBlank, isPresent} from './facade/lang'; import {isPresent} from './facade/lang';
import {Metric} from './metric'; import {Metric} from './metric';
import {MultiMetric} from './metric/multi_metric'; import {MultiMetric} from './metric/multi_metric';
import {PerflogMetric} from './metric/perflog_metric'; import {PerflogMetric} from './metric/perflog_metric';
@ -63,7 +63,7 @@ export class Runner {
} }
var inj = ReflectiveInjector.resolveAndCreate(sampleProviders); var inj = ReflectiveInjector.resolveAndCreate(sampleProviders);
var adapter = 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;')])

View File

@ -9,7 +9,6 @@
import {OpaqueToken} from '@angular/core'; import {OpaqueToken} from '@angular/core';
import {Options} from './common_options'; import {Options} from './common_options';
import {StringMapWrapper} from './facade/collection';
import {Metric} from './metric'; import {Metric} from './metric';
import {Validator} from './validator'; import {Validator} from './validator';
@ -42,7 +41,7 @@ export class SampleDescription {
public metrics: {[key: string]: any}) { public metrics: {[key: string]: any}) {
this.description = {}; this.description = {};
descriptions.forEach(description => { descriptions.forEach(description => {
StringMapWrapper.forEach(description, (value, prop) => this.description[prop] = value); Object.keys(description).forEach(prop => { this.description[prop] = description[prop]; });
}); });
} }

View File

@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Inject, Injectable, OpaqueToken} from '@angular/core'; import {Inject, Injectable} from '@angular/core';
import {Options} from './common_options'; import {Options} from './common_options';
import {Date, DateWrapper, isBlank, isPresent} from './facade/lang'; import {isPresent} from './facade/lang';
import {MeasureValues} from './measure_values'; import {MeasureValues} from './measure_values';
import {Metric} from './metric'; import {Metric} from './metric';
import {Reporter} from './reporter'; import {Reporter} from './reporter';
@ -49,7 +49,7 @@ export class Sampler {
} }
private _iterate(lastState: SampleState): Promise<SampleState> { private _iterate(lastState: SampleState): Promise<SampleState> {
var resultPromise: Promise<any>; 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 {
@ -77,5 +77,5 @@ export class Sampler {
} }
export class SampleState { export class SampleState {
constructor(public completeSample: any[], public validSample: any[]) {} constructor(public completeSample: MeasureValues[], public validSample: MeasureValues[]) {}
} }

View File

@ -17,6 +17,6 @@ export abstract class WebDriverAdapter {
waitFor(callback: Function): Promise<any> { throw new Error('NYI'); } waitFor(callback: Function): Promise<any> { throw new Error('NYI'); }
executeScript(script: string): Promise<any> { throw new Error('NYI'); } executeScript(script: string): Promise<any> { throw new Error('NYI'); }
executeAsyncScript(script: string): Promise<any> { throw new Error('NYI'); } executeAsyncScript(script: string): Promise<any> { throw new Error('NYI'); }
capabilities(): Promise<Map<string, any>> { throw new Error('NYI'); } capabilities(): Promise<{[key: string]: any}> { throw new Error('NYI'); }
logs(type: string): Promise<any[]> { throw new Error('NYI'); } logs(type: string): Promise<any[]> { throw new Error('NYI'); }
} }

View File

@ -9,16 +9,22 @@
import {Injector, OpaqueToken} from '@angular/core'; import {Injector, OpaqueToken} from '@angular/core';
import {Options} from './common_options'; import {Options} from './common_options';
import {isBlank, isPresent} from './facade/lang';
export type PerfLogEvent = { export type PerfLogEvent = {
cat?: string, [key: string]: any
ph?: 'X' | 'B' | 'E' | 'b' | 'e', } & {
ph?: 'X' | 'B' | 'E' | 'I',
ts?: number, ts?: number,
dur?: number, dur?: number,
name?: string, name?: string,
pid?: string, pid?: string,
args?: {encodedDataLength?: number, usedHeapSize?: number, majorGc?: number} args?: {
encodedDataLength?: number,
usedHeapSize?: number,
majorGc?: boolean,
url?: string,
method?: string
}
}; };
/** /**
@ -36,14 +42,14 @@ export abstract class WebDriverExtension {
}, },
{ {
provide: WebDriverExtension, provide: WebDriverExtension,
useFactory: (children: WebDriverExtension[], capabilities: any) => { useFactory: (children: WebDriverExtension[], capabilities: {[key: string]: any}) => {
var delegate: WebDriverExtension; var delegate: WebDriverExtension;
children.forEach(extension => { children.forEach(extension => {
if (extension.supports(capabilities)) { if (extension.supports(capabilities)) {
delegate = extension; delegate = extension;
} }
}); });
if (isBlank(delegate)) { if (!delegate) {
throw new Error('Could not find a delegate for given capabilities!'); throw new Error('Could not find a delegate for given capabilities!');
} }
return delegate; return delegate;
@ -64,8 +70,7 @@ export abstract class WebDriverExtension {
* Format: * Format:
* - cat: category of the event * - cat: category of the event
* - name: event name: 'script', 'gc', 'render', ... * - name: event name: 'script', 'gc', 'render', ...
* - ph: phase: 'B' (begin), 'E' (end), 'b' (nestable start), 'e' (nestable end), 'X' (Complete * - ph: phase: 'B' (begin), 'E' (end), 'X' (Complete event), 'I' (Instant event)
*event)
* - ts: timestamp in ms, e.g. 12345 * - ts: timestamp in ms, e.g. 12345
* - pid: process id * - pid: process id
* - args: arguments, e.g. {heapSize: 1234} * - args: arguments, e.g. {heapSize: 1234}

View File

@ -9,16 +9,12 @@
import {Inject, Injectable} from '@angular/core'; import {Inject, Injectable} from '@angular/core';
import {Options} from '../common_options'; import {Options} from '../common_options';
import {ListWrapper, StringMapWrapper} from '../facade/collection';
import {NumberWrapper, 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';
/** /**
* Set the following 'traceCategories' to collect metrics in Chrome: * Set the following 'traceCategories' to collect metrics in Chrome:
* 'v8,blink.console,disabled-by-default-devtools.timeline,devtools.timeline' * 'v8,blink.console,disabled-by-default-devtools.timeline,devtools.timeline,blink.user_timing'
* *
* In order to collect the frame rate related metrics, add 'benchmark' * In order to collect the frame rate related metrics, add 'benchmark'
* to the list above. * to the list above.
@ -35,18 +31,18 @@ export class ChromeDriverExtension extends WebDriverExtension {
} }
private _parseChromeVersion(userAgent: string): number { private _parseChromeVersion(userAgent: string): number {
if (isBlank(userAgent)) { if (!userAgent) {
return -1; return -1;
} }
var v = StringWrapper.split(userAgent, /Chrom(e|ium)\//g)[2]; var v = userAgent.split(/Chrom(e|ium)\//g)[2];
if (isBlank(v)) { if (!v) {
return -1; return -1;
} }
v = v.split('.')[0]; v = v.split('.')[0];
if (isBlank(v)) { if (!v) {
return -1; return -1;
} }
return NumberWrapper.parseInt(v, 10); return parseInt(v, 10);
} }
gc() { return this._driver.executeScript('window.gc()'); } gc() { return this._driver.executeScript('window.gc()'); }
@ -57,7 +53,7 @@ export class ChromeDriverExtension extends WebDriverExtension {
timeEnd(name: string, restartName: string = null): Promise<any> { timeEnd(name: string, restartName: string = null): Promise<any> {
var script = `console.timeEnd('${name}');`; var script = `console.timeEnd('${name}');`;
if (isPresent(restartName)) { if (restartName) {
script += `console.time('${restartName}');`; script += `console.time('${restartName}');`;
} }
return this._driver.executeScript(script); return this._driver.executeScript(script);
@ -74,10 +70,10 @@ export class ChromeDriverExtension extends WebDriverExtension {
var events: PerfLogEvent[] = []; var events: PerfLogEvent[] = [];
entries.forEach(entry => { entries.forEach(entry => {
var message = JSON.parse(entry['message'])['message']; var message = JSON.parse(entry['message'])['message'];
if (StringWrapper.equals(message['method'], 'Tracing.dataCollected')) { if (message['method'] === 'Tracing.dataCollected') {
events.push(message['params']); events.push(message['params']);
} }
if (StringWrapper.equals(message['method'], 'Tracing.bufferUsage')) { if (message['method'] === 'Tracing.bufferUsage') {
throw new Error('The DevTools trace buffer filled during the test!'); throw new Error('The DevTools trace buffer filled during the test!');
} }
}); });
@ -87,107 +83,64 @@ export class ChromeDriverExtension extends WebDriverExtension {
private _convertPerfRecordsToEvents( private _convertPerfRecordsToEvents(
chromeEvents: Array<{[key: string]: any}>, normalizedEvents: PerfLogEvent[] = null) { chromeEvents: Array<{[key: string]: any}>, normalizedEvents: PerfLogEvent[] = null) {
if (isBlank(normalizedEvents)) { if (!normalizedEvents) {
normalizedEvents = []; normalizedEvents = [];
} }
var majorGCPids = {};
chromeEvents.forEach((event) => { chromeEvents.forEach((event) => {
var categories = this._parseCategories(event['cat']); const categories = this._parseCategories(event['cat']);
var name = event['name']; const normalizedEvent = this._convertEvent(event, categories);
if (this._isEvent(categories, name, ['blink.console'])) { if (normalizedEvent != null) normalizedEvents.push(normalizedEvent);
normalizedEvents.push(normalizeEvent(event, {'name': name}));
} else if (this._isEvent(
categories, name, ['benchmark'],
'BenchmarkInstrumentation::ImplThreadRenderingStats')) {
// TODO(goderbauer): Instead of BenchmarkInstrumentation::ImplThreadRenderingStats the
// following events should be used (if available) for more accurate measurments:
// 1st choice: vsync_before - ground truth on Android
// 2nd choice: BenchmarkInstrumentation::DisplayRenderingStats - available on systems with
// new surfaces framework (not broadly enabled yet)
// 3rd choice: BenchmarkInstrumentation::ImplThreadRenderingStats - fallback event that is
// always available if something is rendered
var frameCount = event['args']['data']['frame_count'];
if (frameCount > 1) {
throw new Error('multi-frame render stats not supported');
}
if (frameCount == 1) {
normalizedEvents.push(normalizeEvent(event, {'name': 'frame'}));
}
} else if (
this._isEvent(categories, name, ['disabled-by-default-devtools.timeline'], 'Rasterize') ||
this._isEvent(
categories, name, ['disabled-by-default-devtools.timeline'], 'CompositeLayers')) {
normalizedEvents.push(normalizeEvent(event, {'name': 'render'}));
} else if (this._majorChromeVersion < 45) {
var normalizedEvent = this._processAsPreChrome45Event(event, categories, majorGCPids);
if (normalizedEvent != null) normalizedEvents.push(normalizedEvent);
} else {
var normalizedEvent = this._processAsPostChrome44Event(event, categories);
if (normalizedEvent != null) normalizedEvents.push(normalizedEvent);
}
}); });
return normalizedEvents; return normalizedEvents;
} }
private _processAsPreChrome45Event( private _convertEvent(event: {[key: string]: any}, categories: string[]) {
event: {[key: string]: any}, categories: string[], majorGCPids: {[key: string]: any}) {
var name = event['name']; var name = event['name'];
var args = event['args']; var args = event['args'];
var pid = event['pid']; if (this._isEvent(categories, name, ['blink.console'])) {
var ph = event['ph']; return normalizeEvent(event, {'name': name});
if (this._isEvent(
categories, name, ['disabled-by-default-devtools.timeline'], 'FunctionCall') &&
(isBlank(args) || isBlank(args['data']) ||
!StringWrapper.equals(args['data']['scriptName'], 'InjectedScript'))) {
return normalizeEvent(event, {'name': 'script'});
} else if (
this._isEvent(
categories, name, ['disabled-by-default-devtools.timeline'], 'RecalculateStyles') ||
this._isEvent(categories, name, ['disabled-by-default-devtools.timeline'], 'Layout') ||
this._isEvent(
categories, name, ['disabled-by-default-devtools.timeline'], 'UpdateLayerTree') ||
this._isEvent(categories, name, ['disabled-by-default-devtools.timeline'], 'Paint')) {
return normalizeEvent(event, {'name': 'render'});
} else if (this._isEvent( } else if (this._isEvent(
categories, name, ['disabled-by-default-devtools.timeline'], 'GCEvent')) { categories, name, ['benchmark'],
var normArgs: {[key: string]: any} = { 'BenchmarkInstrumentation::ImplThreadRenderingStats')) {
'usedHeapSize': isPresent(args['usedHeapSizeAfter']) ? args['usedHeapSizeAfter'] : // TODO(goderbauer): Instead of BenchmarkInstrumentation::ImplThreadRenderingStats the
args['usedHeapSizeBefore'] // following events should be used (if available) for more accurate measurments:
}; // 1st choice: vsync_before - ground truth on Android
if (StringWrapper.equals(ph, 'E')) { // 2nd choice: BenchmarkInstrumentation::DisplayRenderingStats - available on systems with
normArgs['majorGc'] = isPresent(majorGCPids[pid]) && majorGCPids[pid]; // new surfaces framework (not broadly enabled yet)
// 3rd choice: BenchmarkInstrumentation::ImplThreadRenderingStats - fallback event that is
// always available if something is rendered
var frameCount = event['args']['data']['frame_count'];
if (frameCount > 1) {
throw new Error('multi-frame render stats not supported');
}
if (frameCount == 1) {
return normalizeEvent(event, {'name': 'frame'});
} }
majorGCPids[pid] = false;
return normalizeEvent(event, {'name': 'gc', 'args': normArgs});
} else if ( } else if (
this._isEvent(categories, name, ['v8'], 'majorGC') && StringWrapper.equals(ph, 'B')) { this._isEvent(categories, name, ['disabled-by-default-devtools.timeline'], 'Rasterize') ||
majorGCPids[pid] = true; this._isEvent(
} categories, name, ['disabled-by-default-devtools.timeline'], 'CompositeLayers')) {
return null; // nothing useful in this event return normalizeEvent(event, {'name': 'render'});
} } else if (this._isEvent(categories, name, ['devtools.timeline', 'v8'], 'MajorGC')) {
private _processAsPostChrome44Event(event: {[key: string]: any}, categories: string[]) {
var name = event['name'];
var args = event['args'];
if (this._isEvent(categories, name, ['devtools.timeline', 'v8'], 'MajorGC')) {
var normArgs = { var normArgs = {
'majorGc': true, 'majorGc': true,
'usedHeapSize': isPresent(args['usedHeapSizeAfter']) ? 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')) {
var normArgs = { var normArgs = {
'majorGc': false, 'majorGc': false,
'usedHeapSize': isPresent(args['usedHeapSizeAfter']) ? 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 ( } else if (
this._isEvent(categories, name, ['devtools.timeline'], 'FunctionCall') && this._isEvent(categories, name, ['devtools.timeline'], 'FunctionCall') &&
(isBlank(args) || isBlank(args['data']) || (!args || !args['data'] ||
(!StringWrapper.equals(args['data']['scriptName'], 'InjectedScript') && (args['data']['scriptName'] !== 'InjectedScript' && args['data']['scriptName'] !== ''))) {
!StringWrapper.equals(args['data']['scriptName'], '')))) { return normalizeEvent(event, {'name': 'script'});
} else if (this._isEvent(categories, name, ['devtools.timeline'], 'EvaluateScript')) {
return normalizeEvent(event, {'name': 'script'}); return normalizeEvent(event, {'name': 'script'});
} else if (this._isEvent( } else if (this._isEvent(
categories, name, ['devtools.timeline', 'blink'], 'UpdateLayoutTree')) { categories, name, ['devtools.timeline', 'blink'], 'UpdateLayoutTree')) {
@ -205,7 +158,7 @@ export class ChromeDriverExtension extends WebDriverExtension {
let 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': name}); return normalizeEvent(event, {'name': 'navigationStart'});
} }
return null; // nothing useful in this event return null; // nothing useful in this event
} }
@ -216,9 +169,8 @@ export class ChromeDriverExtension extends WebDriverExtension {
eventCategories: string[], eventName: string, expectedCategories: string[], eventCategories: string[], eventName: string, expectedCategories: string[],
expectedName: string = null): boolean { expectedName: string = null): boolean {
var hasCategories = expectedCategories.reduce( var hasCategories = expectedCategories.reduce(
(value, cat) => { return value && ListWrapper.contains(eventCategories, cat); }, true); (value, cat) => value && eventCategories.indexOf(cat) !== -1, true);
return isBlank(expectedName) ? hasCategories : return !expectedName ? hasCategories : hasCategories && eventName === expectedName;
hasCategories && StringWrapper.equals(eventName, expectedName);
} }
perfLogFeatures(): PerfLogFeatures { perfLogFeatures(): PerfLogFeatures {
@ -226,28 +178,31 @@ export class ChromeDriverExtension extends WebDriverExtension {
} }
supports(capabilities: {[key: string]: any}): boolean { supports(capabilities: {[key: string]: any}): boolean {
return this._majorChromeVersion != -1 && return this._majorChromeVersion >= 44 && capabilities['browserName'].toLowerCase() === 'chrome';
StringWrapper.equals(capabilities['browserName'].toLowerCase(), 'chrome');
} }
} }
function normalizeEvent( function normalizeEvent(chromeEvent: {[key: string]: any}, data: PerfLogEvent): PerfLogEvent {
chromeEvent: {[key: string]: any}, data: {[key: string]: any}): PerfLogEvent { var ph = chromeEvent['ph'].toUpperCase();
var ph = chromeEvent['ph']; if (ph === 'S') {
if (StringWrapper.equals(ph, 'S')) { ph = 'B';
ph = 'b'; } else if (ph === 'F') {
} else if (StringWrapper.equals(ph, 'F')) { ph = 'E';
ph = 'e'; } else if (ph === 'R') {
// mark events from navigation timing
ph = 'I';
} }
var 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 (chromeEvent['ph'] === 'X') { if (ph === 'X') {
var dur = chromeEvent['dur']; var dur = chromeEvent['dur'];
if (isBlank(dur)) { if (dur === undefined) {
dur = chromeEvent['tdur']; dur = chromeEvent['tdur'];
} }
result['dur'] = isBlank(dur) ? 0.0 : dur / 1000; result['dur'] = !dur ? 0.0 : dur / 1000;
}
for (let prop in data) {
result[prop] = data[prop];
} }
StringMapWrapper.forEach(data, (value, prop) => { result[prop] = value; });
return result; return result;
} }

View File

@ -52,7 +52,7 @@ export class IOsDriverExtension extends WebDriverExtension {
/** @internal */ /** @internal */
private _convertPerfRecordsToEvents(records: any[], events: PerfLogEvent[] = null) { private _convertPerfRecordsToEvents(records: any[], events: PerfLogEvent[] = null) {
if (isBlank(events)) { if (!events) {
events = []; events = [];
} }
records.forEach((record) => { records.forEach((record) => {
@ -97,7 +97,7 @@ export class IOsDriverExtension extends WebDriverExtension {
} }
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) {
var result: PerfLogEvent = { var result: PerfLogEvent = {
'cat': 'timeline', 'cat': 'timeline',
'name': name, 'name': name,
@ -122,9 +122,9 @@ function createEndEvent(name: string, time: number, args: any = null) {
} }
function createMarkStartEvent(name: string, time: number) { function createMarkStartEvent(name: string, time: number) {
return createEvent('b', name, time); return createEvent('B', name, time);
} }
function createMarkEndEvent(name: string, time: number) { function createMarkEndEvent(name: string, time: number) {
return createEvent('e', name, time); return createEvent('E', name, time);
} }

View File

@ -6,8 +6,6 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import * as webdriver from 'selenium-webdriver';
import {WebDriverAdapter} from '../web_driver_adapter'; import {WebDriverAdapter} from '../web_driver_adapter';
/** /**
@ -21,51 +19,52 @@ export class SeleniumWebDriverAdapter extends WebDriverAdapter {
constructor(private _driver: any) { super(); } constructor(private _driver: any) { super(); }
/** @internal */ waitFor(callback: () => any): Promise<any> { return this._driver.call(callback); }
private _convertPromise(thenable: PromiseLike<any>) {
var resolve: (result: any) => void;
var reject: (error: any) => void;
var promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
thenable.then(
// selenium-webdriver uses an own Node.js context,
// so we need to convert data into objects of this context.
(data: any) => resolve(convertToLocalProcess(data)), reject);
return promise;
}
waitFor(callback: () => any): Promise<any> { executeScript(script: string): Promise<any> { return this._driver.executeScript(script); }
return this._convertPromise(this._driver.controlFlow().execute(callback));
}
executeScript(script: string): Promise<any> {
return this._convertPromise(this._driver.executeScript(script));
}
executeAsyncScript(script: string): Promise<any> { executeAsyncScript(script: string): Promise<any> {
return this._convertPromise(this._driver.executeAsyncScript(script)); return this._driver.executeAsyncScript(script);
} }
capabilities(): Promise<any> { capabilities(): Promise<{[key: string]: any}> {
return this._convertPromise( return this._driver.getCapabilities().then((capsObject: any) => {
this._driver.getCapabilities().then((capsObject: any) => capsObject.serialize())); const localData: {[key: string]: any} = {};
capsObject.forEach((value: any, key: string) => { localData[key] = value; });
return localData;
});
} }
logs(type: string): Promise<any> { logs(type: string): Promise<any> {
// Needed as selenium-webdriver does not forward // Needed as selenium-webdriver does not forward
// performance logs in the correct way via manage().logs // performance logs in the correct way via manage().logs
return this._convertPromise(this._driver.schedule( return this._driver.schedule(
new webdriver.Command(webdriver.CommandName.GET_LOG).setParameter('type', type), new Command('getLog').setParameter('type', type),
'WebDriver.manage().logs().get(' + type + ')')); 'WebDriver.manage().logs().get(' + type + ')');
} }
} }
function convertToLocalProcess(data: any): Object { /**
var serialized = JSON.stringify(data); * Copy of the `Command` class of webdriver as
if ('' + serialized === 'undefined') { * it is not exposed via index.js in selenium-webdriver.
return undefined; */
class Command {
private parameters_: {[key: string]: any} = {};
constructor(private name_: string) {}
getName() { return this.name_; }
setParameter(name: string, value: any) {
this.parameters_[name] = value;
return this;
} }
return JSON.parse(serialized);
setParameters(parameters: {[key: string]: any}) {
this.parameters_ = parameters;
return this;
}
getParameter(key: string) { return this.parameters_[key]; }
getParameters() { return this.parameters_; }
} }

View File

@ -6,14 +6,14 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal';
import {Metric, MultiMetric, ReflectiveInjector} from '../../index'; import {Metric, MultiMetric, ReflectiveInjector} from '../../index';
export function main() { export function main() {
function createMetric(ids: any[]) { function createMetric(ids: any[]) {
var m = ReflectiveInjector var m = ReflectiveInjector
.resolveAndCreate([ .resolveAndCreate([
ids.map(id => { return {provide: id, useValue: new MockMetric(id)}; }), ids.map(id => ({provide: id, useValue: new MockMetric(id)})),
MultiMetric.provideWith(ids) MultiMetric.provideWith(ids)
]) ])
.get(MultiMetric); .get(MultiMetric);

View File

@ -7,11 +7,10 @@
*/ */
import {Provider} from '@angular/core'; import {Provider} from '@angular/core';
import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@angular/core/testing/testing_internal';
import {Metric, Options, PerfLogEvent, PerfLogFeatures, PerflogMetric, ReflectiveInjector, WebDriverExtension} from '../../index'; import {Metric, Options, PerfLogEvent, PerfLogFeatures, PerflogMetric, ReflectiveInjector, WebDriverExtension} from '../../index';
import {StringMapWrapper} from '../../src/facade/collection'; import {isPresent} from '../../src/facade/lang';
import {isBlank, isPresent} from '../../src/facade/lang';
import {TraceEventFactory} from '../trace_event_factory'; import {TraceEventFactory} from '../trace_event_factory';
export function main() { export function main() {
@ -28,12 +27,12 @@ export function main() {
requestCount?: boolean requestCount?: boolean
} = {}): Metric { } = {}): Metric {
commandLog = []; commandLog = [];
if (isBlank(perfLogFeatures)) { if (!perfLogFeatures) {
perfLogFeatures = perfLogFeatures =
new PerfLogFeatures({render: true, gc: true, frameCapture: true, userTiming: true}); new PerfLogFeatures({render: true, gc: true, frameCapture: true, userTiming: true});
} }
if (isBlank(microMetrics)) { if (!microMetrics) {
microMetrics = StringMapWrapper.create(); microMetrics = {};
} }
var providers: Provider[] = [ var providers: Provider[] = [
Options.DEFAULT_PROVIDERS, PerflogMetric.PROVIDERS, Options.DEFAULT_PROVIDERS, PerflogMetric.PROVIDERS,
@ -68,7 +67,7 @@ export function main() {
function sortedKeys(stringMap: {[key: string]: any}) { function sortedKeys(stringMap: {[key: string]: any}) {
var res: string[] = []; var res: string[] = [];
StringMapWrapper.forEach(stringMap, (_, key) => { res.push(key); }); res.push(...Object.keys(stringMap));
res.sort(); res.sort();
return res; return res;
} }
@ -171,6 +170,22 @@ export function main() {
}); });
})); }));
it('should mark and aggregate events since navigationStart',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var events = [[
eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 4),
eventFactory.end('script', 6), eventFactory.instant('navigationStart', 7),
eventFactory.start('script', 8), eventFactory.end('script', 9),
eventFactory.markEnd('benchpress0', 10)
]];
var metric = createMetric(events, null);
metric.beginMeasure().then((_) => metric.endMeasure(false)).then((data) => {
expect(data['scriptTime']).toBe(1);
async.done();
});
}));
it('should restart timing', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { it('should restart timing', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var events = [ var events = [
[ [

View File

@ -7,11 +7,9 @@
*/ */
import {Provider, ReflectiveInjector} from '@angular/core'; import {Provider, ReflectiveInjector} from '@angular/core';
import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal';
import {Injector, Metric, MultiMetric, Options, PerfLogEvent, PerfLogFeatures, PerflogMetric, UserMetric, WebDriverAdapter, WebDriverExtension} from '../../index'; import {Options, PerfLogEvent, PerfLogFeatures, UserMetric, WebDriverAdapter} from '../../index';
import {StringMapWrapper} from '../../src/facade/collection';
import {Json, isBlank, isPresent} from '../../src/facade/lang';
export function main() { export function main() {
var wdAdapter: MockDriverAdapter; var wdAdapter: MockDriverAdapter;
@ -19,12 +17,12 @@ export function main() {
function createMetric( function createMetric(
perfLogs: PerfLogEvent[], perfLogFeatures: PerfLogFeatures, perfLogs: PerfLogEvent[], perfLogFeatures: PerfLogFeatures,
{userMetrics}: {userMetrics?: {[key: string]: string}} = {}): UserMetric { {userMetrics}: {userMetrics?: {[key: string]: string}} = {}): UserMetric {
if (isBlank(perfLogFeatures)) { if (!perfLogFeatures) {
perfLogFeatures = perfLogFeatures =
new PerfLogFeatures({render: true, gc: true, frameCapture: true, userTiming: true}); new PerfLogFeatures({render: true, gc: true, frameCapture: true, userTiming: true});
} }
if (isBlank(userMetrics)) { if (!userMetrics) {
userMetrics = StringMapWrapper.create(); userMetrics = {};
} }
wdAdapter = new MockDriverAdapter(); wdAdapter = new MockDriverAdapter();
var providers: Provider[] = [ var providers: Provider[] = [

View File

@ -7,10 +7,10 @@
*/ */
import {Provider} from '@angular/core'; import {Provider} from '@angular/core';
import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal'; import {describe, expect, it} from '@angular/core/testing/testing_internal';
import {ConsoleReporter, MeasureValues, ReflectiveInjector, Reporter, SampleDescription, SampleState} from '../../index'; import {ConsoleReporter, MeasureValues, ReflectiveInjector, SampleDescription} from '../../index';
import {Date, DateWrapper, isBlank, isPresent} from '../../src/facade/lang'; import {isBlank, isPresent} from '../../src/facade/lang';
export function main() { export function main() {
describe('console reporter', () => { describe('console reporter', () => {
@ -25,7 +25,7 @@ export function main() {
metrics?: {[key: string]: any} metrics?: {[key: string]: any}
}) { }) {
log = []; log = [];
if (isBlank(descriptions)) { if (!descriptions) {
descriptions = []; descriptions = [];
} }
if (isBlank(sampleId)) { if (isBlank(sampleId)) {
@ -90,5 +90,5 @@ export function main() {
} }
function mv(runIndex: number, time: number, values: {[key: string]: number}) { function mv(runIndex: number, time: number, values: {[key: string]: number}) {
return new MeasureValues(runIndex, DateWrapper.fromMillis(time), values); return new MeasureValues(runIndex, new Date(time), values);
} }

View File

@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} 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 {DateWrapper, Json, isPresent} from '../../src/facade/lang'; import {Json, isPresent} from '../../src/facade/lang';
export function main() { export function main() {
describe('file reporter', () => { describe('file reporter', () => {
@ -27,7 +27,7 @@ export function main() {
useValue: new SampleDescription(sampleId, descriptions, metrics) useValue: new SampleDescription(sampleId, descriptions, metrics)
}, },
{provide: JsonFileReporter.PATH, useValue: path}, {provide: JsonFileReporter.PATH, useValue: path},
{provide: Options.NOW, useValue: () => DateWrapper.fromMillis(1234)}, { {provide: Options.NOW, useValue: () => new Date(1234)}, {
provide: Options.WRITE_FILE, provide: Options.WRITE_FILE,
useValue: (filename: string, content: string) => { useValue: (filename: string, content: string) => {
loggedFile = {'filename': filename, 'content': content}; loggedFile = {'filename': filename, 'content': content};
@ -77,5 +77,5 @@ export function main() {
} }
function mv(runIndex: number, time: number, values: {[key: string]: number}) { function mv(runIndex: number, time: number, values: {[key: string]: number}) {
return new MeasureValues(runIndex, DateWrapper.fromMillis(time), values); return new MeasureValues(runIndex, new Date(time), values);
} }

View File

@ -6,16 +6,15 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal';
import {MeasureValues, MultiReporter, ReflectiveInjector, Reporter} from '../../index'; import {MeasureValues, MultiReporter, ReflectiveInjector, Reporter} from '../../index';
import {DateWrapper} from '../../src/facade/lang';
export function main() { export function main() {
function createReporters(ids: any[]) { function createReporters(ids: any[]) {
var r = ReflectiveInjector var r = ReflectiveInjector
.resolveAndCreate([ .resolveAndCreate([
ids.map(id => { return {provide: id, useValue: new MockReporter(id)}; }), ids.map(id => ({provide: id, useValue: new MockReporter(id)})),
MultiReporter.provideWith(ids) MultiReporter.provideWith(ids)
]) ])
.get(MultiReporter); .get(MultiReporter);
@ -26,7 +25,7 @@ export function main() {
it('should reportMeasureValues to all', it('should reportMeasureValues to all',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var mv = new MeasureValues(0, DateWrapper.now(), {}); 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}]);
@ -35,9 +34,8 @@ export function main() {
})); }));
it('should reportSample to call', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { it('should reportSample to call', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var completeSample = [ var completeSample =
new MeasureValues(0, DateWrapper.now(), {}), new MeasureValues(1, DateWrapper.now(), {}) [new MeasureValues(0, new Date(), {}), new MeasureValues(1, new Date(), {})];
];
var validSample = [completeSample[1]]; var validSample = [completeSample[1]];
createReporters(['m1', 'm2']) createReporters(['m1', 'm2'])

View File

@ -6,10 +6,9 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal';
import {Injector, Metric, Options, ReflectiveInjector, Runner, SampleDescription, SampleState, Sampler, Validator, WebDriverAdapter} from '../index'; import {Injector, Metric, Options, ReflectiveInjector, Runner, SampleDescription, SampleState, Sampler, Validator, WebDriverAdapter} from '../index';
import {isBlank} from '../src/facade/lang';
export function main() { export function main() {
describe('runner', () => { describe('runner', () => {
@ -17,7 +16,7 @@ export function main() {
var runner: Runner; var runner: Runner;
function createRunner(defaultProviders: any[] = null): Runner { function createRunner(defaultProviders: any[] = null): Runner {
if (isBlank(defaultProviders)) { if (!defaultProviders) {
defaultProviders = []; defaultProviders = [];
} }
runner = new Runner([ runner = new Runner([

View File

@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal';
import {MeasureValues, Metric, Options, ReflectiveInjector, Reporter, Sampler, Validator, WebDriverAdapter} from '../index'; import {MeasureValues, Metric, Options, ReflectiveInjector, Reporter, Sampler, Validator, WebDriverAdapter} from '../index';
import {Date, DateWrapper, isBlank, isPresent, stringify} from '../src/facade/lang'; import {isBlank, isPresent} from '../src/facade/lang';
export function main() { export function main() {
var EMPTY_EXECUTE = () => {}; var EMPTY_EXECUTE = () => {};
@ -26,10 +26,10 @@ export function main() {
execute?: any execute?: any
} = {}) { } = {}) {
var time = 1000; var time = 1000;
if (isBlank(metric)) { if (!metric) {
metric = new MockMetric([]); metric = new MockMetric([]);
} }
if (isBlank(reporter)) { if (!reporter) {
reporter = new MockReporter([]); reporter = new MockReporter([]);
} }
if (isBlank(driver)) { if (isBlank(driver)) {
@ -39,7 +39,7 @@ export function main() {
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},
{provide: Options.NOW, useValue: () => DateWrapper.fromMillis(time++)} {provide: Options.NOW, useValue: () => new Date(time++)}
]; ];
if (isPresent(prepare)) { if (isPresent(prepare)) {
providers.push({provide: Options.PREPARE, useValue: prepare}); providers.push({provide: Options.PREPARE, useValue: prepare});
@ -60,8 +60,8 @@ export function main() {
createSampler({ createSampler({
driver: driver, driver: driver,
validator: createCountingValidator(2), validator: createCountingValidator(2),
prepare: () => { return count++; }, prepare: () => count++,
execute: () => { return count++; } execute: () => count++,
}); });
sampler.sample().then((_) => { sampler.sample().then((_) => {
expect(count).toBe(4); expect(count).toBe(4);
@ -204,7 +204,7 @@ export function main() {
} }
function mv(runIndex: number, time: number, values: {[key: string]: number}) { function mv(runIndex: number, time: number, values: {[key: string]: number}) {
return new MeasureValues(runIndex, DateWrapper.fromMillis(time), values); return new MeasureValues(runIndex, new Date(time), values);
} }
function createCountingValidator( function createCountingValidator(
@ -221,7 +221,7 @@ function createCountingValidator(
function createCountingMetric(log: any[] = []) { function createCountingMetric(log: any[] = []) {
var scriptTime = 0; var scriptTime = 0;
return new MockMetric(log, () => { return {'script': scriptTime++}; }); return new MockMetric(log, () => ({'script': scriptTime++}));
} }
class MockDriverAdapter extends WebDriverAdapter { class MockDriverAdapter extends WebDriverAdapter {

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 {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal'; import {describe, expect, it} from '@angular/core/testing/testing_internal';
import {Statistic} from '../src/statistic'; import {Statistic} from '../src/statistic';
export function main() { export function main() {

View File

@ -21,16 +21,16 @@ export class TraceEventFactory {
return res; return res;
} }
markStart(name: string, time: number) { return this.create('b', name, time); } markStart(name: string, time: number) { return this.create('B', name, time); }
markEnd(name: string, time: number) { return this.create('e', name, time); } markEnd(name: string, time: number) { return this.create('E', name, time); }
start(name: string, time: number, args: any = null) { return this.create('B', name, time, args); } start(name: string, time: number, args: any = null) { return this.create('B', name, time, args); }
end(name: string, time: number, args: any = null) { return this.create('E', name, time, args); } end(name: string, time: number, args: any = null) { return this.create('E', name, time, args); }
instant(name: string, time: number, args: any = null) { instant(name: string, time: number, args: any = null) {
return this.create('i', name, time, args); return this.create('I', name, time, args);
} }
complete(name: string, time: number, duration: number, args: any = null) { complete(name: string, time: number, duration: number, args: any = null) {

View File

@ -6,11 +6,10 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} 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'; import {ListWrapper} from '../../src/facade/collection';
import {Date, DateWrapper} from '../../src/facade/lang';
export function main() { export function main() {
describe('regression slope validator', () => { describe('regression slope validator', () => {
@ -62,5 +61,5 @@ export function main() {
} }
function mv(runIndex: number, time: number, values: {[key: string]: number}) { function mv(runIndex: number, time: number, values: {[key: string]: number}) {
return new MeasureValues(runIndex, DateWrapper.fromMillis(time), values); return new MeasureValues(runIndex, new Date(time), values);
} }

View File

@ -6,11 +6,10 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal'; import {describe, expect, it} from '@angular/core/testing/testing_internal';
import {MeasureValues, ReflectiveInjector, SizeValidator, Validator} from '../../index'; import {MeasureValues, ReflectiveInjector, SizeValidator} from '../../index';
import {ListWrapper} from '../../src/facade/collection'; import {ListWrapper} from '../../src/facade/collection';
import {Date, DateWrapper} from '../../src/facade/lang';
export function main() { export function main() {
describe('size validator', () => { describe('size validator', () => {
@ -47,5 +46,5 @@ export function main() {
} }
function mv(runIndex: number, time: number, values: {[key: string]: number}) { function mv(runIndex: number, time: number, values: {[key: string]: number}) {
return new MeasureValues(runIndex, DateWrapper.fromMillis(time), values); return new MeasureValues(runIndex, new Date(time), values);
} }

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 {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} 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 {StringWrapper, isPresent} from '../src/facade/lang'; import {StringWrapper, isPresent} from '../src/facade/lang';
@ -17,7 +17,7 @@ export function main() {
try { try {
res(ReflectiveInjector res(ReflectiveInjector
.resolveAndCreate([ .resolveAndCreate([
ids.map((id) => { return {provide: id, useValue: new MockExtension(id)}; }), ids.map((id) => ({provide: id, useValue: new MockExtension(id)})),
{provide: Options.CAPABILITIES, useValue: caps}, {provide: Options.CAPABILITIES, useValue: caps},
WebDriverExtension.provideFirstSupported(ids) WebDriverExtension.provideFirstSupported(ids)
]) ])

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 {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} 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 {Json, isBlank} from '../../src/facade/lang'; import {Json, isBlank} from '../../src/facade/lang';
@ -14,8 +14,6 @@ import {TraceEventFactory} from '../trace_event_factory';
export function main() { export function main() {
describe('chrome driver extension', () => { describe('chrome driver extension', () => {
var CHROME44_USER_AGENT =
'"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.0 Safari/537.36"';
var 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"';
@ -37,11 +35,11 @@ export function main() {
function createExtension( function createExtension(
perfRecords: any[] = null, userAgent: string = null, perfRecords: any[] = null, userAgent: string = null,
messageMethod = 'Tracing.dataCollected'): WebDriverExtension { messageMethod = 'Tracing.dataCollected'): WebDriverExtension {
if (isBlank(perfRecords)) { if (!perfRecords) {
perfRecords = []; perfRecords = [];
} }
if (isBlank(userAgent)) { if (isBlank(userAgent)) {
userAgent = CHROME44_USER_AGENT; userAgent = CHROME45_USER_AGENT;
} }
log = []; log = [];
extension = ReflectiveInjector extension = ReflectiveInjector
@ -89,228 +87,95 @@ export function main() {
}); });
})); }));
describe('readPerfLog Chrome44', () => { it('should normalize times to ms and forward ph and pid event properties',
it('should normalize times to ms and forward ph and pid event properties', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { createExtension([chromeTimelineV8Events.complete('FunctionCall', 1100, 5500, null)])
createExtension([chromeTimelineEvents.complete('FunctionCall', 1100, 5500, null)]) .readPerfLog()
.readPerfLog() .then((events) => {
.then((events) => { expect(events).toEqual([
expect(events).toEqual([ normEvents.complete('script', 1.1, 5.5, null),
normEvents.complete('script', 1.1, 5.5, null), ]);
]); async.done();
async.done(); });
}); }));
}));
it('should normalize "tdur" to "dur"', it('should normalize "tdur" to "dur"',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var event: any = chromeTimelineEvents.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([
normEvents.complete('script', 1.1, 5.5, null), normEvents.complete('script', 1.1, 5.5, null),
]); ]);
async.done(); async.done();
}); });
})); }));
it('should report FunctionCall events as "script"', it('should report FunctionCall events as "script"',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension([chromeTimelineEvents.start('FunctionCall', 0)]) createExtension([chromeTimelineV8Events.start('FunctionCall', 0)])
.readPerfLog() .readPerfLog()
.then((events) => { .then((events) => {
expect(events).toEqual([ expect(events).toEqual([
normEvents.start('script', 0), normEvents.start('script', 0),
]); ]);
async.done(); async.done();
}); });
})); }));
it('should report gc', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { it('should report EvaluateScript events as "script"',
createExtension([ inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
chromeTimelineEvents.start('GCEvent', 1000, {'usedHeapSizeBefore': 1000}), createExtension([chromeTimelineV8Events.start('EvaluateScript', 0)])
chromeTimelineEvents.end('GCEvent', 2000, {'usedHeapSizeAfter': 0}), .readPerfLog()
]) .then((events) => {
.readPerfLog() expect(events).toEqual([
.then((events) => { normEvents.start('script', 0),
expect(events).toEqual([ ]);
normEvents.start('gc', 1.0, {'usedHeapSize': 1000}), async.done();
normEvents.end('gc', 2.0, {'usedHeapSize': 0, 'majorGc': false}), });
]); }));
async.done();
});
}));
it('should ignore major gc from different processes', it('should report minor gc', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { createExtension([
createExtension([ chromeTimelineV8Events.start('MinorGC', 1000, {'usedHeapSizeBefore': 1000}),
chromeTimelineEvents.start('GCEvent', 1000, {'usedHeapSizeBefore': 1000}), chromeTimelineV8Events.end('MinorGC', 2000, {'usedHeapSizeAfter': 0}),
v8EventsOtherProcess.start('majorGC', 1100, null), ])
v8EventsOtherProcess.end('majorGC', 1200, null), .readPerfLog()
chromeTimelineEvents.end('GCEvent', 2000, {'usedHeapSizeAfter': 0}), .then((events) => {
]) expect(events.length).toEqual(2);
.readPerfLog() expect(events[0]).toEqual(
.then((events) => { normEvents.start('gc', 1.0, {'usedHeapSize': 1000, 'majorGc': false}));
expect(events).toEqual([ expect(events[1]).toEqual(
normEvents.start('gc', 1.0, {'usedHeapSize': 1000}), normEvents.end('gc', 2.0, {'usedHeapSize': 0, 'majorGc': false}));
normEvents.end('gc', 2.0, {'usedHeapSize': 0, 'majorGc': false}), async.done();
]); });
async.done(); }));
});
}));
it('should report major gc', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { it('should report major gc', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension([ createExtension(
chromeTimelineEvents.start('GCEvent', 1000, {'usedHeapSizeBefore': 1000}), [
v8Events.start('majorGC', 1100, null), chromeTimelineV8Events.start('MajorGC', 1000, {'usedHeapSizeBefore': 1000}),
v8Events.end('majorGC', 1200, null), chromeTimelineV8Events.end('MajorGC', 2000, {'usedHeapSizeAfter': 0}),
chromeTimelineEvents.end('GCEvent', 2000, {'usedHeapSizeAfter': 0}), ], )
]) .readPerfLog()
.readPerfLog() .then((events) => {
.then((events) => { expect(events.length).toEqual(2);
expect(events).toEqual([ expect(events[0]).toEqual(
normEvents.start('gc', 1.0, {'usedHeapSize': 1000}), normEvents.start('gc', 1.0, {'usedHeapSize': 1000, 'majorGc': true}));
normEvents.end('gc', 2.0, {'usedHeapSize': 0, 'majorGc': true}), expect(events[1]).toEqual(
]); normEvents.end('gc', 2.0, {'usedHeapSize': 0, 'majorGc': true}));
async.done(); async.done();
}); });
})); }));
['RecalculateStyles', 'Layout', 'UpdateLayerTree', 'Paint'].forEach((recordType) => { ['Layout', 'UpdateLayerTree', 'Paint'].forEach((recordType) => {
it(`should report ${recordType} as "render"`, it(`should report ${recordType} as "render"`,
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension([
chromeTimelineEvents.start(recordType, 1234),
chromeTimelineEvents.end(recordType, 2345)
])
.readPerfLog()
.then((events) => {
expect(events).toEqual([
normEvents.start('render', 1.234),
normEvents.end('render', 2.345),
]);
async.done();
});
}));
});
it('should ignore FunctionCalls from webdriver',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension([chromeTimelineEvents.start(
'FunctionCall', 0, {'data': {'scriptName': 'InjectedScript'}})])
.readPerfLog()
.then((events) => {
expect(events).toEqual([]);
async.done();
});
}));
});
describe('readPerfLog Chrome45', () => {
it('should normalize times to ms and forward ph and pid event properties',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension(
[chromeTimelineV8Events.complete('FunctionCall', 1100, 5500, null)],
CHROME45_USER_AGENT)
.readPerfLog()
.then((events) => {
expect(events).toEqual([
normEvents.complete('script', 1.1, 5.5, null),
]);
async.done();
});
}));
it('should normalize "tdur" to "dur"',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var event: any = chromeTimelineV8Events.create('X', 'FunctionCall', 1100, null);
event['tdur'] = 5500;
createExtension([event], CHROME45_USER_AGENT).readPerfLog().then((events) => {
expect(events).toEqual([
normEvents.complete('script', 1.1, 5.5, null),
]);
async.done();
});
}));
it('should report FunctionCall events as "script"',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension([chromeTimelineV8Events.start('FunctionCall', 0)], CHROME45_USER_AGENT)
.readPerfLog()
.then((events) => {
expect(events).toEqual([
normEvents.start('script', 0),
]);
async.done();
});
}));
it('should report minor gc', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension(
[
chromeTimelineV8Events.start('MinorGC', 1000, {'usedHeapSizeBefore': 1000}),
chromeTimelineV8Events.end('MinorGC', 2000, {'usedHeapSizeAfter': 0}),
],
CHROME45_USER_AGENT)
.readPerfLog()
.then((events) => {
expect(events.length).toEqual(2);
expect(events[0]).toEqual(
normEvents.start('gc', 1.0, {'usedHeapSize': 1000, 'majorGc': false}));
expect(events[1]).toEqual(
normEvents.end('gc', 2.0, {'usedHeapSize': 0, 'majorGc': false}));
async.done();
});
}));
it('should report major gc', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension(
[
chromeTimelineV8Events.start('MajorGC', 1000, {'usedHeapSizeBefore': 1000}),
chromeTimelineV8Events.end('MajorGC', 2000, {'usedHeapSizeAfter': 0}),
],
CHROME45_USER_AGENT)
.readPerfLog()
.then((events) => {
expect(events.length).toEqual(2);
expect(events[0]).toEqual(
normEvents.start('gc', 1.0, {'usedHeapSize': 1000, 'majorGc': true}));
expect(events[1]).toEqual(
normEvents.end('gc', 2.0, {'usedHeapSize': 0, 'majorGc': true}));
async.done();
});
}));
['Layout', 'UpdateLayerTree', 'Paint'].forEach((recordType) => {
it(`should report ${recordType} as "render"`,
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension(
[
chrome45TimelineEvents.start(recordType, 1234),
chrome45TimelineEvents.end(recordType, 2345)
],
CHROME45_USER_AGENT)
.readPerfLog()
.then((events) => {
expect(events).toEqual([
normEvents.start('render', 1.234),
normEvents.end('render', 2.345),
]);
async.done();
});
}));
});
it(`should report UpdateLayoutTree as "render"`,
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension( createExtension(
[ [
chromeBlinkTimelineEvents.start('UpdateLayoutTree', 1234), chrome45TimelineEvents.start(recordType, 1234),
chromeBlinkTimelineEvents.end('UpdateLayoutTree', 2345) chrome45TimelineEvents.end(recordType, 2345)
], ], )
CHROME45_USER_AGENT)
.readPerfLog() .readPerfLog()
.then((events) => { .then((events) => {
expect(events).toEqual([ expect(events).toEqual([
@ -320,70 +185,80 @@ export function main() {
async.done(); async.done();
}); });
})); }));
it('should ignore FunctionCalls from webdriver',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension([chromeTimelineV8Events.start(
'FunctionCall', 0, {'data': {'scriptName': 'InjectedScript'}})])
.readPerfLog()
.then((events) => {
expect(events).toEqual([]);
async.done();
});
}));
it('should ignore FunctionCalls with empty scriptName',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension(
[chromeTimelineV8Events.start('FunctionCall', 0, {'data': {'scriptName': ''}})])
.readPerfLog()
.then((events) => {
expect(events).toEqual([]);
async.done();
});
}));
it('should report navigationStart',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension(
[chromeBlinkUserTimingEvents.start('navigationStart', 1234)], CHROME45_USER_AGENT)
.readPerfLog()
.then((events) => {
expect(events).toEqual([normEvents.start('navigationStart', 1.234)]);
async.done();
});
}));
it('should report receivedData', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension(
[chrome45TimelineEvents.instant(
'ResourceReceivedData', 1234, {'data': {'encodedDataLength': 987}})],
CHROME45_USER_AGENT)
.readPerfLog()
.then((events) => {
expect(events).toEqual(
[normEvents.instant('receivedData', 1.234, {'encodedDataLength': 987})]);
async.done();
});
}));
it('should report sendRequest', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension(
[chrome45TimelineEvents.instant(
'ResourceSendRequest', 1234,
{'data': {'url': 'http://here', 'requestMethod': 'GET'}})],
CHROME45_USER_AGENT)
.readPerfLog()
.then((events) => {
expect(events).toEqual([normEvents.instant(
'sendRequest', 1.234, {'url': 'http://here', 'method': 'GET'})]);
async.done();
});
}));
}); });
it(`should report UpdateLayoutTree as "render"`,
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension(
[
chromeBlinkTimelineEvents.start('UpdateLayoutTree', 1234),
chromeBlinkTimelineEvents.end('UpdateLayoutTree', 2345)
], )
.readPerfLog()
.then((events) => {
expect(events).toEqual([
normEvents.start('render', 1.234),
normEvents.end('render', 2.345),
]);
async.done();
});
}));
it('should ignore FunctionCalls from webdriver',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension([chromeTimelineV8Events.start(
'FunctionCall', 0, {'data': {'scriptName': 'InjectedScript'}})])
.readPerfLog()
.then((events) => {
expect(events).toEqual([]);
async.done();
});
}));
it('should ignore FunctionCalls with empty scriptName',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension(
[chromeTimelineV8Events.start('FunctionCall', 0, {'data': {'scriptName': ''}})])
.readPerfLog()
.then((events) => {
expect(events).toEqual([]);
async.done();
});
}));
it('should report navigationStart',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension([chromeBlinkUserTimingEvents.instant('navigationStart', 1234)])
.readPerfLog()
.then((events) => {
expect(events).toEqual([normEvents.instant('navigationStart', 1.234)]);
async.done();
});
}));
it('should report receivedData', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension([chrome45TimelineEvents.instant(
'ResourceReceivedData', 1234, {'data': {'encodedDataLength': 987}})], )
.readPerfLog()
.then((events) => {
expect(events).toEqual(
[normEvents.instant('receivedData', 1.234, {'encodedDataLength': 987})]);
async.done();
});
}));
it('should report sendRequest', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension([chrome45TimelineEvents.instant(
'ResourceSendRequest', 1234,
{'data': {'url': 'http://here', 'requestMethod': 'GET'}})], )
.readPerfLog()
.then((events) => {
expect(events).toEqual([normEvents.instant(
'sendRequest', 1.234, {'url': 'http://here', 'method': 'GET'})]);
async.done();
});
}));
describe('readPerfLog (common)', () => { describe('readPerfLog (common)', () => {
it('should execute a dummy script before reading them', it('should execute a dummy script before reading them',
@ -404,8 +279,7 @@ export function main() {
[ [
chromeTimelineEvents.start(recordType, 1234), chromeTimelineEvents.start(recordType, 1234),
chromeTimelineEvents.end(recordType, 2345) chromeTimelineEvents.end(recordType, 2345)
], ], )
CHROME45_USER_AGENT)
.readPerfLog() .readPerfLog()
.then((events) => { .then((events) => {
expect(events).toEqual([ expect(events).toEqual([
@ -426,7 +300,7 @@ export function main() {
.readPerfLog() .readPerfLog()
.then((events) => { .then((events) => {
expect(events).toEqual([ expect(events).toEqual([
normEvents.create('i', 'frame', 1.1), normEvents.instant('frame', 1.1),
]); ]);
async.done(); async.done();
}); });
@ -522,11 +396,11 @@ class MockDriverAdapter extends WebDriverAdapter {
logs(type: string) { logs(type: string) {
this._log.push(['logs', type]); this._log.push(['logs', type]);
if (type === 'performance') { if (type === 'performance') {
return Promise.resolve(this._events.map((event) => { return Promise.resolve(this._events.map(
return { (event) => ({
'message': Json.stringify({'message': {'method': this._messageMethod, 'params': event}}) 'message':
}; Json.stringify({'message': {'method': this._messageMethod, 'params': event}})
})); })));
} else { } else {
return null; return null;
} }

View File

@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} 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, isBlank, isPresent} from '../../src/facade/lang'; import {Json} from '../../src/facade/lang';
import {TraceEventFactory} from '../trace_event_factory'; import {TraceEventFactory} from '../trace_event_factory';
export function main() { export function main() {
@ -20,7 +20,7 @@ export function main() {
var normEvents = new TraceEventFactory('timeline', 'pid0'); var normEvents = new TraceEventFactory('timeline', 'pid0');
function createExtension(perfRecords: any[] = null): WebDriverExtension { function createExtension(perfRecords: any[] = null): WebDriverExtension {
if (isBlank(perfRecords)) { if (!perfRecords) {
perfRecords = []; perfRecords = [];
} }
log = []; log = [];
@ -156,7 +156,7 @@ function timeEndRecord(name: string, time: number) {
} }
function durationRecord(type: string, startTime: number, endTime: number, children: any[] = null) { function durationRecord(type: string, startTime: number, endTime: number, children: any[] = null) {
if (isBlank(children)) { if (!children) {
children = []; children = [];
} }
return {'type': type, 'startTime': startTime, 'endTime': endTime, 'children': children}; return {'type': type, 'startTime': startTime, 'endTime': endTime, 'children': children};

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,7 +1,7 @@
{ {
"name": "@angular/common", "name": "@angular/common",
"version": "0.0.0-PLACEHOLDER", "version": "0.0.0-PLACEHOLDER",
"description": "", "description": "Angular - commonly needed directives and services",
"main": "bundles/common.umd.js", "main": "bundles/common.umd.js",
"module": "index.js", "module": "index.js",
"typings": "index.d.ts", "typings": "index.d.ts",

View File

@ -1,3 +1,10 @@
/**
* @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 default { export default {
entry: '../../../dist/packages-dist/common/testing/index.js', entry: '../../../dist/packages-dist/common/testing/index.js',
@ -10,4 +17,4 @@ export default {
'rxjs/Observable': 'Rx', 'rxjs/Observable': 'Rx',
'rxjs/Subject': 'Rx' 'rxjs/Subject': 'Rx'
} }
} };

View File

@ -1,3 +1,10 @@
/**
* @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 default { export default {
entry: '../../../dist/packages-dist/common/index.js', entry: '../../../dist/packages-dist/common/index.js',
@ -7,6 +14,6 @@ export default {
globals: { globals: {
'@angular/core': 'ng.core', '@angular/core': 'ng.core',
'rxjs/Observable': 'Rx', 'rxjs/Observable': 'Rx',
'rxjs/Subject': 'Rx' 'rxjs/Subject': 'Rx',
} }
} };

View File

@ -1,59 +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 {Provider} from '@angular/core';
import {CORE_DIRECTIVES} from './directives/core_directives';
/**
* A collection of Angular core directives that are likely to be used in each and every Angular
* application. This includes core directives (e.g., NgIf and NgFor), and forms directives (e.g.,
* NgModel).
*
* This collection can be used to quickly enumerate all the built-in directives in the `directives`
* property of the `@Component` decorator.
*
* ### Example
*
* Instead of writing:
*
* ```typescript
* import {NgClass, NgIf, NgFor, NgSwitch, NgSwitchCase, NgSwitchDefault, NgModel, NgForm} from
* '@angular/common';
* import {OtherDirective} from './myDirectives';
*
* @Component({
* selector: 'my-component',
* templateUrl: 'myComponent.html',
* directives: [NgClass, NgIf, NgFor, NgSwitch, NgSwitchCase, NgSwitchDefault, NgModel, NgForm,
* OtherDirective]
* })
* export class MyComponent {
* ...
* }
* ```
* one could import all the common directives at once:
*
* ```typescript
* import {COMMON_DIRECTIVES} from '@angular/common';
* import {OtherDirective} from './myDirectives';
*
* @Component({
* selector: 'my-component',
* templateUrl: 'myComponent.html',
* directives: [COMMON_DIRECTIVES, OtherDirective]
* })
* export class MyComponent {
* ...
* }
* ```
*
* @experimental Contains forms which are experimental.
*/
export const COMMON_DIRECTIVES: Provider[] = CORE_DIRECTIVES;

View File

@ -8,14 +8,14 @@
import {NgModule} from '@angular/core'; import {NgModule} from '@angular/core';
import {COMMON_DIRECTIVES} from './common_directives'; import {COMMON_DIRECTIVES} from './directives/index';
import {NgLocaleLocalization, NgLocalization} from './localization'; import {NgLocaleLocalization, NgLocalization} from './localization';
import {COMMON_PIPES} from './pipes/common_pipes'; import {COMMON_PIPES} from './pipes/index';
// Note: This does not contain the location providers, // Note: This does not contain the location providers,
// as they need some platform specific implementations to work. // as they need some platform specific implementations to work.
/** /**
* The module that includes all the basic Angular directives like {@link NgIf}, ${link NgFor}, ... * The module that includes all the basic Angular directives like {@link NgIf}, {@link NgFor}, ...
* *
* @stable * @stable
*/ */

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
* Common directives shipped with Angular.
*/
export {NgClass} from './directives/ng_class';
export {NgFor} from './directives/ng_for';
export {NgIf} from './directives/ng_if';
export {NgPlural, NgPluralCase} from './directives/ng_plural';
export {NgStyle} from './directives/ng_style';
export {NgSwitch, NgSwitchCase, NgSwitchDefault} from './directives/ng_switch';
export {NgTemplateOutlet} from './directives/ng_template_outlet';

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 {Type} from '@angular/core';
import {NgClass} from './ng_class';
import {NgFor} from './ng_for';
import {NgIf} from './ng_if';
import {NgPlural, NgPluralCase} from './ng_plural';
import {NgStyle} from './ng_style';
import {NgSwitch, NgSwitchCase, NgSwitchDefault} from './ng_switch';
import {NgTemplateOutlet} from './ng_template_outlet';
/**
* A collection of Angular core directives that are likely to be used in each and every Angular
* application.
*
* This collection can be used to quickly enumerate all the built-in directives in the `directives`
* property of the `@Component` annotation.
*
* ### Example ([live demo](http://plnkr.co/edit/yakGwpCdUkg0qfzX5m8g?p=preview))
*
* Instead of writing:
*
* ```typescript
* import {NgClass, NgIf, NgFor, NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common';
* import {OtherDirective} from './myDirectives';
*
* @Component({
* selector: 'my-component',
* templateUrl: 'myComponent.html',
* directives: [NgClass, NgIf, NgFor, NgSwitch, NgSwitchCase, NgSwitchDefault, OtherDirective]
* })
* export class MyComponent {
* ...
* }
* ```
* one could import all the core directives at once:
*
* ```typescript
* import {CORE_DIRECTIVES} from '@angular/common';
* import {OtherDirective} from './myDirectives';
*
* @Component({
* selector: 'my-component',
* templateUrl: 'myComponent.html',
* directives: [CORE_DIRECTIVES, OtherDirective]
* })
* export class MyComponent {
* ...
* }
* ```
*
* @stable
*/
export const CORE_DIRECTIVES: Type<any>[] = [
NgClass,
NgFor,
NgIf,
NgTemplateOutlet,
NgStyle,
NgSwitch,
NgSwitchCase,
NgSwitchDefault,
NgPlural,
NgPluralCase,
];

View File

@ -0,0 +1,47 @@
/**
* @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 {Provider} from '@angular/core';
import {NgClass} from './ng_class';
import {NgFor} from './ng_for';
import {NgIf} from './ng_if';
import {NgPlural, NgPluralCase} from './ng_plural';
import {NgStyle} from './ng_style';
import {NgSwitch, NgSwitchCase, NgSwitchDefault} from './ng_switch';
import {NgTemplateOutlet} from './ng_template_outlet';
export {
NgClass,
NgFor,
NgIf,
NgPlural,
NgPluralCase,
NgStyle,
NgSwitch,
NgSwitchCase,
NgSwitchDefault,
NgTemplateOutlet
};
/**
* A collection of Angular directives that are likely to be used in each and every Angular
* application.
*/
export const COMMON_DIRECTIVES: Provider[] = [
NgClass,
NgFor,
NgIf,
NgTemplateOutlet,
NgStyle,
NgSwitch,
NgSwitchCase,
NgSwitchDefault,
NgPlural,
NgPluralCase,
];

View File

@ -8,70 +8,35 @@
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 {StringMapWrapper, isListLikeIterable} from '../facade/collection'; import {isListLikeIterable} from '../facade/collection';
import {isArray, isPresent, isString} from '../facade/lang'; import {isPresent} from '../facade/lang';
/** /**
* The `NgClass` directive conditionally adds and removes CSS classes on an HTML element based on * @ngModule CommonModule
* an expression's evaluation result.
* *
* The result of an expression evaluation is interpreted differently depending on type of * @whatItDoes Adds and removes CSS classes on an HTML element.
* the expression evaluation result:
* - `string` - all the CSS classes listed in a string (space delimited) are added
* - `Array` - all the CSS classes (Array elements) are added
* - `Object` - each key corresponds to a CSS class name while values are interpreted as expressions
* evaluating to `Boolean`. If a given expression evaluates to `true` a corresponding CSS class
* is added - otherwise it is removed.
*
* While the `NgClass` directive can interpret expressions evaluating to `string`, `Array`
* or `Object`, the `Object`-based version is the most often used and has an advantage of keeping
* all the CSS class names in a template.
*
* ### Example ([live demo](http://plnkr.co/edit/a4YdtmWywhJ33uqfpPPn?p=preview)):
* *
* @howToUse
* ``` * ```
* import {Component} from '@angular/core'; * <some-element [ngClass]="'first second'">...</some-element>
* import {NgClass} from '@angular/common';
* *
* @Component({ * <some-element [ngClass]="['first', 'second']">...</some-element>
* selector: 'toggle-button',
* inputs: ['isDisabled'],
* template: `
* <div class="button" [ngClass]="{active: isOn, disabled: isDisabled}"
* (click)="toggle(!isOn)">
* Click me!
* </div>`,
* styles: [`
* .button {
* width: 120px;
* border: medium solid black;
* }
* *
* .active { * <some-element [ngClass]="{'first': true, 'second': true, 'third': false}">...</some-element>
* background-color: red;
* }
* *
* .disabled { * <some-element [ngClass]="stringExp|arrayExp|objExp">...</some-element>
* color: gray;
* border: medium solid gray;
* }
* `],
* directives: [NgClass]
* })
* class ToggleButton {
* isOn = false;
* isDisabled = false;
*
* toggle(newState) {
* if (!this.isDisabled) {
* this.isOn = newState;
* }
* }
* }
* ``` * ```
* *
* @description
*
* The CSS classes are updated as follow depending on the type of the expression evaluation:
* - `string` - the CSS classes listed in a string (space delimited) are added,
* - `Array` - the CSS classes (Array elements) are added,
* - `Object` - keys are CSS class names that get added when the expression given in the value
* evaluates to a truthy value, otherwise class are removed.
*
* @stable * @stable
*/ */
@Directive({selector: '[ngClass]'}) @Directive({selector: '[ngClass]'})
@ -79,7 +44,7 @@ export class NgClass implements DoCheck {
private _iterableDiffer: IterableDiffer; private _iterableDiffer: IterableDiffer;
private _keyValueDiffer: KeyValueDiffer; private _keyValueDiffer: KeyValueDiffer;
private _initialClasses: string[] = []; private _initialClasses: string[] = [];
private _rawClass: string[]|Set<string>; private _rawClass: string[]|Set<string>|{[klass: string]: any};
constructor( constructor(
private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers, private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers,
@ -87,58 +52,57 @@ export class NgClass implements DoCheck {
@Input('class') @Input('class')
set initialClasses(v: string) { set klass(v: string) {
this._applyInitialClasses(true); this._applyInitialClasses(true);
this._initialClasses = isPresent(v) && isString(v) ? v.split(' ') : []; this._initialClasses = typeof v === 'string' ? v.split(/\s+/) : [];
this._applyInitialClasses(false); this._applyInitialClasses(false);
this._applyClasses(this._rawClass, false); this._applyClasses(this._rawClass, false);
} }
@Input() @Input()
set ngClass(v: string|string[]|Set<string>|{[key: string]: any}) { set ngClass(v: string|string[]|Set<string>|{[klass: string]: any}) {
this._cleanupClasses(this._rawClass); this._cleanupClasses(this._rawClass);
if (isString(v)) {
v = (<string>v).split(' ');
}
this._rawClass = <string[]|Set<string>>v;
this._iterableDiffer = null; this._iterableDiffer = null;
this._keyValueDiffer = null; this._keyValueDiffer = null;
if (isPresent(v)) {
if (isListLikeIterable(v)) { this._rawClass = typeof v === 'string' ? v.split(/\s+/) : v;
this._iterableDiffer = this._iterableDiffers.find(v).create(null);
if (this._rawClass) {
if (isListLikeIterable(this._rawClass)) {
this._iterableDiffer = this._iterableDiffers.find(this._rawClass).create(null);
} else { } else {
this._keyValueDiffer = this._keyValueDiffers.find(v).create(null); this._keyValueDiffer = this._keyValueDiffers.find(this._rawClass).create(null);
} }
} }
} }
ngDoCheck(): void { ngDoCheck(): void {
if (isPresent(this._iterableDiffer)) { if (this._iterableDiffer) {
var changes = this._iterableDiffer.diff(this._rawClass); const changes = this._iterableDiffer.diff(this._rawClass);
if (isPresent(changes)) { if (changes) {
this._applyIterableChanges(changes); this._applyIterableChanges(changes);
} }
} } else if (this._keyValueDiffer) {
if (isPresent(this._keyValueDiffer)) { const changes = this._keyValueDiffer.diff(this._rawClass);
var changes = this._keyValueDiffer.diff(this._rawClass); if (changes) {
if (isPresent(changes)) {
this._applyKeyValueChanges(changes); this._applyKeyValueChanges(changes);
} }
} }
} }
private _cleanupClasses(rawClassVal: string[]|Set<string>|{[key: string]: any}): void { private _cleanupClasses(rawClassVal: string[]|Set<string>|{[klass: string]: any}): void {
this._applyClasses(rawClassVal, true); this._applyClasses(rawClassVal, true);
this._applyInitialClasses(false); this._applyInitialClasses(false);
} }
private _applyKeyValueChanges(changes: any): void { private _applyKeyValueChanges(changes: any): void {
changes.forEachAddedItem( changes.forEachAddedItem(
(record: KeyValueChangeRecord) => { this._toggleClass(record.key, record.currentValue); }); (record: KeyValueChangeRecord) => this._toggleClass(record.key, record.currentValue));
changes.forEachChangedItem( changes.forEachChangedItem(
(record: KeyValueChangeRecord) => { this._toggleClass(record.key, record.currentValue); }); (record: KeyValueChangeRecord) => this._toggleClass(record.key, record.currentValue));
changes.forEachRemovedItem((record: KeyValueChangeRecord) => { changes.forEachRemovedItem((record: KeyValueChangeRecord) => {
if (record.previousValue) { if (record.previousValue) {
this._toggleClass(record.key, false); this._toggleClass(record.key, false);
@ -148,42 +112,34 @@ export class NgClass implements DoCheck {
private _applyIterableChanges(changes: any): void { private _applyIterableChanges(changes: any): void {
changes.forEachAddedItem( changes.forEachAddedItem(
(record: CollectionChangeRecord) => { this._toggleClass(record.item, true); }); (record: CollectionChangeRecord) => this._toggleClass(record.item, true));
changes.forEachRemovedItem( changes.forEachRemovedItem(
(record: CollectionChangeRecord) => { this._toggleClass(record.item, false); }); (record: CollectionChangeRecord) => this._toggleClass(record.item, false));
} }
private _applyInitialClasses(isCleanup: boolean) { private _applyInitialClasses(isCleanup: boolean) {
this._initialClasses.forEach(className => this._toggleClass(className, !isCleanup)); this._initialClasses.forEach(klass => this._toggleClass(klass, !isCleanup));
} }
private _applyClasses( private _applyClasses(
rawClassVal: string[]|Set<string>|{[key: string]: any}, isCleanup: boolean) { rawClassVal: string[]|Set<string>|{[key: string]: any}, isCleanup: boolean) {
if (isPresent(rawClassVal)) { if (rawClassVal) {
if (isArray(rawClassVal)) { if (Array.isArray(rawClassVal) || rawClassVal instanceof Set) {
(<string[]>rawClassVal).forEach(className => this._toggleClass(className, !isCleanup)); (<any>rawClassVal).forEach((klass: string) => this._toggleClass(klass, !isCleanup));
} else if (rawClassVal instanceof Set) {
(<Set<string>>rawClassVal).forEach(className => this._toggleClass(className, !isCleanup));
} else { } else {
StringMapWrapper.forEach( Object.keys(rawClassVal).forEach(klass => {
<{[k: string]: any}>rawClassVal, (expVal: any, className: string) => { if (isPresent(rawClassVal[klass])) this._toggleClass(klass, !isCleanup);
if (isPresent(expVal)) this._toggleClass(className, !isCleanup); });
});
} }
} }
} }
private _toggleClass(className: string, enabled: boolean): void { private _toggleClass(klass: string, enabled: boolean): void {
className = className.trim(); klass = klass.trim();
if (className.length > 0) { if (klass) {
if (className.indexOf(' ') > -1) { klass.split(/\s+/g).forEach(
var classes = className.split(/\s+/g); klass => { this._renderer.setElementClass(this._ngEl.nativeElement, klass, enabled); });
for (var i = 0, len = classes.length; i < len; i++) {
this._renderer.setElementClass(this._ngEl.nativeElement, classes[i], enabled);
}
} else {
this._renderer.setElementClass(this._ngEl.nativeElement, className, enabled);
}
} }
} }
} }

View File

@ -8,7 +8,7 @@
import {ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, Directive, DoCheck, EmbeddedViewRef, Input, IterableDiffer, IterableDiffers, OnChanges, SimpleChanges, TemplateRef, TrackByFn, ViewContainerRef} from '@angular/core'; import {ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, Directive, DoCheck, EmbeddedViewRef, Input, IterableDiffer, IterableDiffers, OnChanges, SimpleChanges, TemplateRef, TrackByFn, ViewContainerRef} from '@angular/core';
import {getTypeNameForDebugging, isBlank, isPresent} from '../facade/lang'; import {getTypeNameForDebugging} from '../facade/lang';
export class NgForRow { export class NgForRow {
constructor(public $implicit: any, public index: number, public count: number) {} constructor(public $implicit: any, public index: number, public count: number) {}
@ -91,16 +91,16 @@ export class NgFor implements DoCheck, OnChanges {
@Input() ngForOf: any; @Input() ngForOf: any;
@Input() ngForTrackBy: TrackByFn; @Input() ngForTrackBy: TrackByFn;
private _differ: IterableDiffer; private _differ: IterableDiffer = null;
constructor( constructor(
private _viewContainer: ViewContainerRef, private _templateRef: TemplateRef<NgForRow>, private _viewContainer: ViewContainerRef, private _template: TemplateRef<NgForRow>,
private _iterableDiffers: IterableDiffers, private _cdr: ChangeDetectorRef) {} private _differs: IterableDiffers, private _cdr: ChangeDetectorRef) {}
@Input() @Input()
set ngForTemplate(value: TemplateRef<NgForRow>) { set ngForTemplate(value: TemplateRef<NgForRow>) {
if (isPresent(value)) { if (value) {
this._templateRef = value; this._template = value;
} }
} }
@ -108,9 +108,9 @@ export class NgFor implements DoCheck, OnChanges {
if ('ngForOf' in changes) { if ('ngForOf' in changes) {
// React on ngForOf changes only once all inputs have been initialized // React on ngForOf changes only once all inputs have been initialized
const value = changes['ngForOf'].currentValue; const value = changes['ngForOf'].currentValue;
if (isBlank(this._differ) && isPresent(value)) { if (!this._differ && value) {
try { try {
this._differ = this._iterableDiffers.find(value).create(this._cdr, this.ngForTrackBy); this._differ = this._differs.find(value).create(this._cdr, this.ngForTrackBy);
} catch (e) { } catch (e) {
throw new Error( throw new Error(
`Cannot find a differ supporting object '${value}' of type '${getTypeNameForDebugging(value)}'. NgFor only supports binding to Iterables such as Arrays.`); `Cannot find a differ supporting object '${value}' of type '${getTypeNameForDebugging(value)}'. NgFor only supports binding to Iterables such as Arrays.`);
@ -120,9 +120,9 @@ export class NgFor implements DoCheck, OnChanges {
} }
ngDoCheck() { ngDoCheck() {
if (isPresent(this._differ)) { if (this._differ) {
const changes = this._differ.diff(this.ngForOf); const changes = this._differ.diff(this.ngForOf);
if (isPresent(changes)) this._applyChanges(changes); if (changes) this._applyChanges(changes);
} }
} }
@ -131,16 +131,16 @@ export class NgFor implements DoCheck, OnChanges {
changes.forEachOperation( changes.forEachOperation(
(item: CollectionChangeRecord, adjustedPreviousIndex: number, currentIndex: number) => { (item: CollectionChangeRecord, adjustedPreviousIndex: number, currentIndex: number) => {
if (item.previousIndex == null) { if (item.previousIndex == null) {
let view = this._viewContainer.createEmbeddedView( const view = this._viewContainer.createEmbeddedView(
this._templateRef, new NgForRow(null, null, null), currentIndex); this._template, new NgForRow(null, null, null), currentIndex);
let tuple = new RecordViewTuple(item, view); const tuple = new RecordViewTuple(item, view);
insertTuples.push(tuple); insertTuples.push(tuple);
} else if (currentIndex == null) { } else if (currentIndex == null) {
this._viewContainer.remove(adjustedPreviousIndex); this._viewContainer.remove(adjustedPreviousIndex);
} else { } else {
let view = this._viewContainer.get(adjustedPreviousIndex); const view = this._viewContainer.get(adjustedPreviousIndex);
this._viewContainer.move(view, currentIndex); this._viewContainer.move(view, currentIndex);
let tuple = new RecordViewTuple(item, <EmbeddedViewRef<NgForRow>>view); const tuple = new RecordViewTuple(item, <EmbeddedViewRef<NgForRow>>view);
insertTuples.push(tuple); insertTuples.push(tuple);
} }
}); });
@ -150,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++) {
var 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) => {
var 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

@ -8,10 +8,6 @@
import {Directive, Input, TemplateRef, ViewContainerRef} from '@angular/core'; import {Directive, Input, TemplateRef, ViewContainerRef} from '@angular/core';
import {isBlank} from '../facade/lang';
/** /**
* Removes or recreates a portion of the DOM tree based on an {expression}. * Removes or recreates a portion of the DOM tree based on an {expression}.
* *
@ -38,18 +34,17 @@ import {isBlank} from '../facade/lang';
*/ */
@Directive({selector: '[ngIf]'}) @Directive({selector: '[ngIf]'})
export class NgIf { export class NgIf {
private _prevCondition: boolean = null; private _hasView: boolean = false;
constructor(private _viewContainer: ViewContainerRef, private _templateRef: TemplateRef<Object>) { constructor(private _viewContainer: ViewContainerRef, private _template: TemplateRef<Object>) {}
}
@Input() @Input()
set ngIf(newCondition: any) { set ngIf(condition: any) {
if (newCondition && (isBlank(this._prevCondition) || !this._prevCondition)) { if (condition && !this._hasView) {
this._prevCondition = true; this._hasView = true;
this._viewContainer.createEmbeddedView(this._templateRef); this._viewContainer.createEmbeddedView(this._template);
} else if (!newCondition && (isBlank(this._prevCondition) || this._prevCondition)) { } else if (!condition && this._hasView) {
this._prevCondition = false; this._hasView = false;
this._viewContainer.clear(); this._viewContainer.clear();
} }
} }

View File

@ -6,57 +6,43 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Attribute, Directive, Host, Input, OnInit, TemplateRef, ViewContainerRef} from '@angular/core'; import {Attribute, Directive, Host, Input, TemplateRef, ViewContainerRef} from '@angular/core';
import {isPresent} from '../facade/lang';
import {NgLocalization, getPluralCategory} from '../localization'; import {NgLocalization, getPluralCategory} from '../localization';
import {SwitchView} from './ng_switch'; import {SwitchView} from './ng_switch';
/** /**
* `ngPlural` is an i18n directive that displays DOM sub-trees that match the switch expression * @ngModule CommonModule
* value, or failing that, DOM sub-trees that match the switch expression's pluralization category. *
* @whatItDoes Adds / removes DOM sub-trees based on a numeric value. Tailored for pluralization.
*
* @howToUse
* ```
* <some-element [ngPlural]="value">
* <ng-container *ngPluralCase="'=0'">there is nothing</ng-container>
* <ng-container *ngPluralCase="'=1'">there is one</ng-container>
* <ng-container *ngPluralCase="'few'">there are a few</ng-container>
* <ng-container *ngPluralCase="'other'">there are exactly #</ng-container>
* </some-element>
* ```
*
* @description
*
* Displays DOM sub-trees that match the switch expression value, or failing that, DOM sub-trees
* that match the switch expression's pluralization category.
* *
* To use this directive you must provide a container element that sets the `[ngPlural]` attribute * To use this directive you must provide a container element that sets the `[ngPlural]` attribute
* to a * to a switch expression. Inner elements with a `[ngPluralCase]` will display based on their
* switch expression. * expression:
* - Inner elements defined with an `[ngPluralCase]` attribute will display based on their * - if `[ngPluralCase]` is set to a value starting with `=`, it will only display if the value
* expression. * matches the switch expression exactly,
* - If `[ngPluralCase]` is set to a value starting with `=`, it will only display if the value * - otherwise, the view will be treated as a "category match", and will only display if exact
* matches the switch expression exactly. * value matches aren't found and the value maps to its category for the defined locale.
* - Otherwise, the view will be treated as a "category match", and will only display if exact
* value matches aren't found and the value maps to its category for the defined locale.
* *
* ```typescript * See http://cldr.unicode.org/index/cldr-spec/plural-rules
* @Component({
* selector: 'app',
* // best practice is to define the locale at the application level
* providers: [{provide: LOCALE_ID, useValue: 'en_US'}]
* })
* @View({
* template: `
* <p>Value = {{value}}</p>
* <button (click)="inc()">Increment</button>
* *
* <div [ngPlural]="value">
* <template ngPluralCase="=0">there is nothing</template>
* <template ngPluralCase="=1">there is one</template>
* <template ngPluralCase="few">there are a few</template>
* <template ngPluralCase="other">there is some number</template>
* </div>
* `,
* directives: [NgPlural, NgPluralCase]
* })
* export class App {
* value = 'init';
*
* inc() {
* this.value = this.value === 'init' ? 0 : this.value + 1;
* }
* }
*
* ```
* @experimental * @experimental
*/ */
@Directive({selector: '[ngPlural]'}) @Directive({selector: '[ngPlural]'})
@ -75,29 +61,42 @@ export class NgPlural {
addCase(value: string, switchView: SwitchView): void { this._caseViews[value] = switchView; } addCase(value: string, switchView: SwitchView): void { this._caseViews[value] = switchView; }
/** @internal */ private _updateView(): void {
_updateView(): void {
this._clearViews(); this._clearViews();
var key = const cases = Object.keys(this._caseViews);
getPluralCategory(this._switchValue, Object.keys(this._caseViews), this._localization); const key = getPluralCategory(this._switchValue, cases, this._localization);
this._activateView(this._caseViews[key]); this._activateView(this._caseViews[key]);
} }
/** @internal */ private _clearViews() {
_clearViews() { if (this._activeView) this._activeView.destroy();
if (isPresent(this._activeView)) this._activeView.destroy();
} }
/** @internal */ private _activateView(view: SwitchView) {
_activateView(view: SwitchView) { if (view) {
if (!isPresent(view)) return; this._activeView = view;
this._activeView = view; this._activeView.create();
this._activeView.create(); }
} }
} }
/** /**
* @ngModule CommonModule
*
* @whatItDoes Creates a view that will be added/removed from the parent {@link NgPlural} when the
* given expression matches the plural expression according to CLDR rules.
*
* @howToUse
* ```
* <some-element [ngPlural]="value">
* <ng-container *ngPluralCase="'=0'">...</ng-container>
* <ng-container *ngPluralCase="'other'">...</ng-container>
* </some-element>
*```
*
* See {@link NgPlural} for more details and example.
*
* @experimental * @experimental
*/ */
@Directive({selector: '[ngPluralCase]'}) @Directive({selector: '[ngPluralCase]'})

View File

@ -8,70 +8,32 @@
import {Directive, DoCheck, ElementRef, Input, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core'; import {Directive, DoCheck, ElementRef, Input, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
import {isBlank, isPresent} from '../facade/lang';
/** /**
* The `NgStyle` directive changes styles based on a result of expression evaluation. * @ngModule CommonModule
* *
* An expression assigned to the `ngStyle` property must evaluate to an object and the * @whatItDoes Update an HTML element styles.
* corresponding element styles are updated based on changes to this object. Style names to update
* are taken from the object's keys, and values - from the corresponding object's values.
*
* ### Syntax
*
* - `<div [ngStyle]="{'font-style': styleExp}"></div>`
* - `<div [ngStyle]="{'max-width.px': widthExp}"></div>`
* - `<div [ngStyle]="styleExp"></div>` - here the `styleExp` must evaluate to an object
*
* ### Example ([live demo](http://plnkr.co/edit/YamGS6GkUh9GqWNQhCyM?p=preview)):
* *
* @howToUse
* ``` * ```
* import {Component} from '@angular/core'; * <some-element [ngStyle]="{'font-style': styleExp}">...</some-element>
* import {NgStyle} from '@angular/common';
* *
* @Component({ * <some-element [ngStyle]="{'max-width.px': widthExp}">...</some-element>
* selector: 'ngStyle-example',
* template: `
* <h1 [ngStyle]="{'font-style': style, 'font-size': size, 'font-weight': weight}">
* Change style of this text!
* </h1>
* *
* <hr> * <some-element [ngStyle]="objExp">...</some-element>
*
* <label>Italic: <input type="checkbox" (change)="changeStyle($event)"></label>
* <label>Bold: <input type="checkbox" (change)="changeWeight($event)"></label>
* <label>Size: <input type="text" [value]="size" (change)="size = $event.target.value"></label>
* `,
* directives: [NgStyle]
* })
* export class NgStyleExample {
* style = 'normal';
* weight = 'normal';
* size = '20px';
*
* changeStyle($event: any) {
* this.style = $event.target.checked ? 'italic' : 'normal';
* }
*
* changeWeight($event: any) {
* this.weight = $event.target.checked ? 'bold' : 'normal';
* }
* }
* ``` * ```
* *
* In this example the `font-style`, `font-size` and `font-weight` styles will be updated * @description
* based on the `style` property's value changes. *
* The styles are updated according to the value of the expression evaluation:
* - 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).
* *
* @stable * @stable
*/ */
@Directive({selector: '[ngStyle]'}) @Directive({selector: '[ngStyle]'})
export class NgStyle implements DoCheck { export class NgStyle implements DoCheck {
/** @internal */ private _ngStyle: {[key: string]: string};
_ngStyle: {[key: string]: string}; private _differ: KeyValueDiffer;
/** @internal */
_differ: KeyValueDiffer;
constructor( constructor(
private _differs: KeyValueDiffers, private _ngEl: ElementRef, private _renderer: Renderer) {} private _differs: KeyValueDiffers, private _ngEl: ElementRef, private _renderer: Renderer) {}
@ -79,34 +41,34 @@ export class NgStyle implements DoCheck {
@Input() @Input()
set ngStyle(v: {[key: string]: string}) { set ngStyle(v: {[key: string]: string}) {
this._ngStyle = v; this._ngStyle = v;
if (isBlank(this._differ) && isPresent(v)) { if (!this._differ && v) {
this._differ = this._differs.find(this._ngStyle).create(null); this._differ = this._differs.find(v).create(null);
} }
} }
ngDoCheck() { ngDoCheck() {
if (isPresent(this._differ)) { if (this._differ) {
var changes = this._differ.diff(this._ngStyle); const changes = this._differ.diff(this._ngStyle);
if (isPresent(changes)) { if (changes) {
this._applyChanges(changes); this._applyChanges(changes);
} }
} }
} }
private _applyChanges(changes: any): void { private _applyChanges(changes: any): void {
changes.forEachRemovedItem( changes.forEachRemovedItem((record: KeyValueChangeRecord) => this._setStyle(record.key, null));
(record: KeyValueChangeRecord) => { this._setStyle(record.key, null); });
changes.forEachAddedItem( changes.forEachAddedItem(
(record: KeyValueChangeRecord) => { this._setStyle(record.key, record.currentValue); }); (record: KeyValueChangeRecord) => this._setStyle(record.key, record.currentValue));
changes.forEachChangedItem( changes.forEachChangedItem(
(record: KeyValueChangeRecord) => { this._setStyle(record.key, record.currentValue); }); (record: KeyValueChangeRecord) => this._setStyle(record.key, record.currentValue));
} }
private _setStyle(name: string, val: string): void { private _setStyle(nameAndUnit: string, value: string): void {
const nameParts = name.split('.'); const [name, unit] = nameAndUnit.split('.');
const nameToSet = nameParts[0]; value = value && unit ? `${value}${unit}` : value;
const valToSet = isPresent(val) && nameParts.length === 2 ? `${val}${nameParts[1]}` : val;
this._renderer.setElementStyle(this._ngEl.nativeElement, nameToSet, valToSet); this._renderer.setElementStyle(this._ngEl.nativeElement, name, value);
} }
} }

View File

@ -9,7 +9,6 @@
import {Directive, Host, Input, TemplateRef, ViewContainerRef} from '@angular/core'; import {Directive, Host, Input, TemplateRef, ViewContainerRef} from '@angular/core';
import {ListWrapper} from '../facade/collection'; import {ListWrapper} from '../facade/collection';
import {isBlank, isPresent, normalizeBlank} from '../facade/lang';
const _CASE_DEFAULT = new Object(); const _CASE_DEFAULT = new Object();
@ -23,58 +22,44 @@ export class SwitchView {
} }
/** /**
* Adds or removes DOM sub-trees when their match expressions match the switch expression. * @ngModule CommonModule
* *
* Elements within `NgSwitch` but without `NgSwitchCase` or `NgSwitchDefault` directives will be * @whatItDoes Adds / removes DOM sub-trees when the nest match expressions matches the switch
* preserved at the location as specified in the template. * expression.
* *
* `NgSwitch` simply inserts nested elements based on which match expression matches the value * @howToUse
* obtained from the evaluated switch expression. In other words, you define a container element
* (where you place the directive with a switch expression on the
* `[ngSwitch]="..."` attribute), define any inner elements inside of the directive and
* place a `[ngSwitchCase]` attribute per element.
*
* The `ngSwitchCase` property is used to inform `NgSwitch` which element to display when the
* expression is evaluated. If a matching expression is not found via a `ngSwitchCase` property
* then an element with the `ngSwitchDefault` attribute is displayed.
*
* ### Example ([live demo](http://plnkr.co/edit/DQMTII95CbuqWrl3lYAs?p=preview))
*
* ```typescript
* @Component({
* selector: 'app',
* template: `
* <p>Value = {{value}}</p>
* <button (click)="inc()">Increment</button>
*
* <div [ngSwitch]="value">
* <p *ngSwitchCase="'init'">increment to start</p>
* <p *ngSwitchCase="0">0, increment again</p>
* <p *ngSwitchCase="1">1, increment again</p>
* <p *ngSwitchCase="2">2, stop incrementing</p>
* <p *ngSwitchDefault>&gt; 2, STOP!</p>
* </div>
*
* <!-- alternate syntax -->
*
* <p [ngSwitch]="value">
* <template ngSwitchCase="init">increment to start</template>
* <template [ngSwitchCase]="0">0, increment again</template>
* <template [ngSwitchCase]="1">1, increment again</template>
* <template [ngSwitchCase]="2">2, stop incrementing</template>
* <template ngSwitchDefault>&gt; 2, STOP!</template>
* </p>
* `,
* directives: [NgSwitch, NgSwitchCase, NgSwitchDefault]
* })
* export class App {
* value = 'init';
*
* inc() {
* this.value = this.value === 'init' ? 0 : this.value + 1;
* }
* }
* ``` * ```
* <container-element [ngSwitch]="switch_expression">
* <some-element *ngSwitchCase="match_expression_1">...</some-element>
* <some-element *ngSwitchCase="match_expression_2">...</some-element>
* <some-other-element *ngSwitchCase="match_expression_3">...</some-other-element>
* <ng-container *ngSwitchCase="match_expression_3">
* <!-- use a ng-container to group multiple root nodes -->
* <inner-element></inner-element>
* <inner-other-element></inner-other-element>
* </ng-container>
* <some-element *ngSwitchDefault>...</p>
* </container-element>
* ```
* @description
*
* `NgSwitch` stamps out nested views when their match expression value matches the value of the
* switch expression.
*
* In other words:
* - you define a container element (where you place the directive with a switch expression on the
* `[ngSwitch]="..."` attribute)
* - you define inner views inside the `NgSwitch` and place a `*ngSwitchCase` attribute on the view
* root elements.
*
* Elements within `NgSwitch` but outside of a `NgSwitchCase` or `NgSwitchDefault` directives will
* be
* preserved at the location.
*
* The `ngSwitchCase` directive informs the parent `NgSwitch` of which view to display when the
* expression is evaluated.
* When no matching expression is found on a `ngSwitchCase` view, the `ngSwitchDefault` view is
* stamped out.
* *
* @stable * @stable
*/ */
@ -92,10 +77,10 @@ export class NgSwitch {
// Add the ViewContainers matching the value (with a fallback to default) // Add the ViewContainers matching the value (with a fallback to default)
this._useDefault = false; this._useDefault = false;
var views = this._valueViews.get(value); let views = this._valueViews.get(value);
if (isBlank(views)) { if (!views) {
this._useDefault = true; this._useDefault = true;
views = normalizeBlank(this._valueViews.get(_CASE_DEFAULT)); views = this._valueViews.get(_CASE_DEFAULT) || null;
} }
this._activateViews(views); this._activateViews(views);
@ -126,19 +111,16 @@ export class NgSwitch {
} }
} }
/** @internal */ private _emptyAllActiveViews(): void {
_emptyAllActiveViews(): void { const activeContainers = this._activeViews;
var activeContainers = this._activeViews;
for (var i = 0; i < activeContainers.length; i++) { for (var i = 0; i < activeContainers.length; i++) {
activeContainers[i].destroy(); activeContainers[i].destroy();
} }
this._activeViews = []; this._activeViews = [];
} }
/** @internal */ private _activateViews(views: SwitchView[]): void {
_activateViews(views: SwitchView[]): void { if (views) {
// TODO(vicb): assert(this._activeViews.length === 0);
if (isPresent(views)) {
for (var i = 0; i < views.length; i++) { for (var i = 0; i < views.length; i++) {
views[i].create(); views[i].create();
} }
@ -148,19 +130,18 @@ export class NgSwitch {
/** @internal */ /** @internal */
_registerView(value: any, view: SwitchView): void { _registerView(value: any, view: SwitchView): void {
var views = this._valueViews.get(value); let views = this._valueViews.get(value);
if (isBlank(views)) { if (!views) {
views = []; views = [];
this._valueViews.set(value, views); this._valueViews.set(value, views);
} }
views.push(view); views.push(view);
} }
/** @internal */ private _deregisterView(value: any, view: SwitchView): void {
_deregisterView(value: any, view: SwitchView): void {
// `_CASE_DEFAULT` is used a marker for non-registered cases // `_CASE_DEFAULT` is used a marker for non-registered cases
if (value === _CASE_DEFAULT) return; if (value === _CASE_DEFAULT) return;
var views = this._valueViews.get(value); const views = this._valueViews.get(value);
if (views.length == 1) { if (views.length == 1) {
this._valueViews.delete(value); this._valueViews.delete(value);
} else { } else {
@ -170,10 +151,24 @@ export class NgSwitch {
} }
/** /**
* Insert the sub-tree when the `ngSwitchCase` expression evaluates to the same value as the * @ngModule CommonModule
* enclosing switch expression.
* *
* If multiple match expression match the switch expression value, all of them are displayed. * @whatItDoes Creates a view that will be added/removed from the parent {@link NgSwitch} when the
* given expression evaluate to respectively the same/different value as the switch
* expression.
*
* @howToUse
* ```
* <container-element [ngSwitch]="switch_expression">
* <some-element *ngSwitchCase="match_expression_1">...</some-element>
* </container-element>
*```
* @description
*
* Insert the sub-tree when the expression evaluates to the same value as the enclosing switch
* expression.
*
* If multiple match expressions match the switch expression value, all of them are displayed.
* *
* See {@link NgSwitch} for more details and example. * See {@link NgSwitch} for more details and example.
* *
@ -182,10 +177,8 @@ export class NgSwitch {
@Directive({selector: '[ngSwitchCase]'}) @Directive({selector: '[ngSwitchCase]'})
export class NgSwitchCase { export class NgSwitchCase {
// `_CASE_DEFAULT` is used as a marker for a not yet initialized value // `_CASE_DEFAULT` is used as a marker for a not yet initialized value
/** @internal */ private _value: any = _CASE_DEFAULT;
_value: any = _CASE_DEFAULT; private _view: SwitchView;
/** @internal */
_view: SwitchView;
private _switch: NgSwitch; private _switch: NgSwitch;
constructor( constructor(
@ -203,8 +196,23 @@ export class NgSwitchCase {
} }
/** /**
* Default case statements are displayed when no match expression matches the switch expression * @ngModule CommonModule
* value. * @whatItDoes Creates a view that is added to the parent {@link NgSwitch} when no case expressions
* match the
* switch expression.
*
* @howToUse
* ```
* <container-element [ngSwitch]="switch_expression">
* <some-element *ngSwitchCase="match_expression_1">...</some-element>
* <some-other-element *ngSwitchDefault>...</some-other-element>
* </container-element>
* ```
*
* @description
*
* Insert the sub-tree when no case expressions evaluate to the same value as the enclosing switch
* expression.
* *
* See {@link NgSwitch} for more details and example. * See {@link NgSwitch} for more details and example.
* *

View File

@ -6,24 +6,28 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, EmbeddedViewRef, Input, OnChanges, TemplateRef, ViewContainerRef} from '@angular/core'; import {Directive, EmbeddedViewRef, Input, OnChanges, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
/** /**
* Creates and inserts an embedded view based on a prepared `TemplateRef`. * @ngModule CommonModule
* You can attach a context object to the `EmbeddedViewRef` by setting `[ngOutletContext]`.
* `[ngOutletContext]` should be an object, the object's keys will be the local template variables
* available within the `TemplateRef`.
* *
* Note: using the key `$implicit` in the context object will set it's value as default. * @whatItDoes Inserts an embedded view from a prepared `TemplateRef`
*
* ### Syntax
* *
* @howToUse
* ``` * ```
* <template [ngTemplateOutlet]="templateRefExpression" * <template [ngTemplateOutlet]="templateRefExpression"
* [ngOutletContext]="objectExpression"> * [ngOutletContext]="objectExpression">
* </template> * </template>
* ``` * ```
* *
* @description
*
* You can attach a context object to the `EmbeddedViewRef` by setting `[ngOutletContext]`.
* `[ngOutletContext]` should be an object, the object's keys will be the local template variables
* available within the `TemplateRef`.
*
* Note: using the key `$implicit` in the context object will set it's value as default.
*
* @experimental * @experimental
*/ */
@Directive({selector: '[ngTemplateOutlet]'}) @Directive({selector: '[ngTemplateOutlet]'})
@ -40,7 +44,7 @@ export class NgTemplateOutlet implements OnChanges {
@Input() @Input()
set ngTemplateOutlet(templateRef: TemplateRef<Object>) { this._templateRef = templateRef; } set ngTemplateOutlet(templateRef: TemplateRef<Object>) { this._templateRef = templateRef; }
ngOnChanges() { ngOnChanges(changes: SimpleChanges) {
if (this._viewRef) { if (this._viewRef) {
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._viewRef)); this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._viewRef));
} }

View File

@ -67,7 +67,7 @@ export enum Plural {
Two, Two,
Few, Few,
Many, Many,
Other Other,
} }
/** /**
@ -87,7 +87,7 @@ export function getPluralCase(locale: string, nLike: number | string): Plural {
const f = parseInt(nDecimal, 10); const f = parseInt(nDecimal, 10);
const t = parseInt(n.toString().replace(/^[^.]*\.?|0+$/g, ''), 10) || 0; const t = parseInt(n.toString().replace(/^[^.]*\.?|0+$/g, ''), 10) || 0;
const lang = locale.split('_')[0].toLowerCase(); const lang = locale.split('-')[0].toLowerCase();
switch (lang) { switch (lang) {
case 'af': case 'af':

View File

@ -49,16 +49,20 @@ export class Location {
_subject: EventEmitter<any> = new EventEmitter(); _subject: EventEmitter<any> = new EventEmitter();
/** @internal */ /** @internal */
_baseHref: string; _baseHref: string;
/** @internal */ /** @internal */
_platformStrategy: LocationStrategy; _platformStrategy: LocationStrategy;
constructor(platformStrategy: LocationStrategy) { constructor(platformStrategy: LocationStrategy) {
this._platformStrategy = platformStrategy; this._platformStrategy = platformStrategy;
var browserBaseHref = this._platformStrategy.getBaseHref(); const browserBaseHref = this._platformStrategy.getBaseHref();
this._baseHref = Location.stripTrailingSlash(_stripIndexHtml(browserBaseHref)); this._baseHref = Location.stripTrailingSlash(_stripIndexHtml(browserBaseHref));
this._platformStrategy.onPopState( this._platformStrategy.onPopState((ev) => {
(ev) => { this._subject.emit({'url': this.path(true), 'pop': true, 'type': ev.type}); }); this._subject.emit({
'url': this.path(true),
'pop': true,
'type': ev.type,
});
});
} }
/** /**
@ -79,7 +83,7 @@ export class Location {
/** /**
* Given a string representing a URL, returns the normalized URL path without leading or * Given a string representing a URL, returns the normalized URL path without leading or
* trailing slashes * trailing slashes.
*/ */
normalize(url: string): string { normalize(url: string): string {
return Location.stripTrailingSlash(_stripBaseHref(this._baseHref, _stripIndexHtml(url))); return Location.stripTrailingSlash(_stripBaseHref(this._baseHref, _stripIndexHtml(url)));

View File

@ -49,6 +49,7 @@ export abstract class LocationStrategy {
* *
* ### Example * ### Example
* *
* ```typescript
* import {Component, NgModule} from '@angular/core'; * import {Component, NgModule} from '@angular/core';
* import {APP_BASE_HREF} from '@angular/common'; * import {APP_BASE_HREF} from '@angular/common';
* *

View File

@ -11,13 +11,3 @@
* @description * @description
* This module provides a set of common Pipes. * This module provides a set of common Pipes.
*/ */
export {AsyncPipe} from './pipes/async_pipe';
export {DatePipe} from './pipes/date_pipe';
export {I18nPluralPipe} from './pipes/i18n_plural_pipe';
export {I18nSelectPipe} from './pipes/i18n_select_pipe';
export {JsonPipe} from './pipes/json_pipe';
export {LowerCasePipe} from './pipes/lowercase_pipe';
export {CurrencyPipe, DecimalPipe, PercentPipe} from './pipes/number_pipe';
export {SlicePipe} from './pipes/slice_pipe';
export {UpperCasePipe} from './pipes/uppercase_pipe';

View File

@ -8,7 +8,7 @@
import {ChangeDetectorRef, OnDestroy, Pipe, WrappedValue} from '@angular/core'; import {ChangeDetectorRef, OnDestroy, Pipe, WrappedValue} from '@angular/core';
import {EventEmitter, Observable} from '../facade/async'; import {EventEmitter, Observable} from '../facade/async';
import {isBlank, isPresent, isPromise} from '../facade/lang'; import {isPromise} from '../private_import_core';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error'; import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
interface SubscriptionStrategy { interface SubscriptionStrategy {
@ -37,63 +37,54 @@ class PromiseStrategy implements SubscriptionStrategy {
onDestroy(subscription: any): void {} onDestroy(subscription: any): void {}
} }
var _promiseStrategy = new PromiseStrategy(); const _promiseStrategy = new PromiseStrategy();
var _observableStrategy = new ObservableStrategy(); const _observableStrategy = new ObservableStrategy();
var __unused: Promise<any>; // avoid unused import when Promise union types are erased
/** /**
* @ngModule CommonModule
* @whatItDoes Unwraps a value from an asynchronous primitive.
* @howToUse `observable_or_promise_expression | async`
* @description
* The `async` pipe subscribes to an `Observable` or `Promise` and returns the latest value it has * The `async` pipe subscribes to an `Observable` or `Promise` and returns the latest value it has
* emitted. * emitted. When a new value is emitted, the `async` pipe marks the component to be checked for
* When a new value is emitted, the `async` pipe marks the component to be checked for changes. * changes. When the component gets destroyed, the `async` pipe unsubscribes automatically to avoid
* When the component gets destroyed, the `async` pipe unsubscribes automatically to avoid
* potential memory leaks. * potential memory leaks.
* *
* ## Usage
*
* object | async
*
* where `object` is of type `Observable` or of type `Promise`.
* *
* ## Examples * ## Examples
* *
* This example binds a `Promise` to the view. Clicking the `Resolve` button resolves the * This example binds a `Promise` to the view. Clicking the `Resolve` button resolves the
* promise. * promise.
* *
* {@example core/pipes/ts/async_pipe/async_pipe_example.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. Every 500ms, the `time` Observable updates the view with the current time. * to the view. The Observable continuesly updates the view with the current time.
* *
* {@example core/pipes/ts/async_pipe/async_pipe_example.ts region='AsyncPipeObservable'} * {@example common/pipes/ts/async_pipe.ts region='AsyncPipeObservable'}
* *
* @stable * @stable
*/ */
@Pipe({name: 'async', pure: false}) @Pipe({name: 'async', pure: false})
export class AsyncPipe implements OnDestroy { export class AsyncPipe implements OnDestroy {
/** @internal */ private _latestValue: Object = null;
_latestValue: Object = null; private _latestReturnedValue: Object = null;
/** @internal */
_latestReturnedValue: Object = null;
/** @internal */ private _subscription: Object = null;
_subscription: Object = null; private _obj: Observable<any>|Promise<any>|EventEmitter<any> = null;
/** @internal */
_obj: Observable<any>|Promise<any>|EventEmitter<any> = null;
/** @internal */
_ref: ChangeDetectorRef;
private _strategy: SubscriptionStrategy = null; private _strategy: SubscriptionStrategy = null;
constructor(_ref: ChangeDetectorRef) { this._ref = _ref; } constructor(private _ref: ChangeDetectorRef) {}
ngOnDestroy(): void { ngOnDestroy(): void {
if (isPresent(this._subscription)) { if (this._subscription) {
this._dispose(); this._dispose();
} }
} }
transform(obj: Observable<any>|Promise<any>|EventEmitter<any>): any { transform(obj: Observable<any>|Promise<any>|EventEmitter<any>): any {
if (isBlank(this._obj)) { if (!this._obj) {
if (isPresent(obj)) { if (obj) {
this._subscribe(obj); this._subscribe(obj);
} }
this._latestReturnedValue = this._latestValue; this._latestReturnedValue = this._latestValue;
@ -107,33 +98,32 @@ export class AsyncPipe implements OnDestroy {
if (this._latestValue === this._latestReturnedValue) { if (this._latestValue === this._latestReturnedValue) {
return this._latestReturnedValue; return this._latestReturnedValue;
} else {
this._latestReturnedValue = this._latestValue;
return WrappedValue.wrap(this._latestValue);
} }
this._latestReturnedValue = this._latestValue;
return WrappedValue.wrap(this._latestValue);
} }
/** @internal */ private _subscribe(obj: Observable<any>|Promise<any>|EventEmitter<any>): void {
_subscribe(obj: Observable<any>|Promise<any>|EventEmitter<any>): void {
this._obj = obj; this._obj = obj;
this._strategy = this._selectStrategy(obj); this._strategy = this._selectStrategy(obj);
this._subscription = this._strategy.createSubscription( this._subscription = this._strategy.createSubscription(
obj, (value: Object) => this._updateLatestValue(obj, value)); obj, (value: Object) => this._updateLatestValue(obj, value));
} }
/** @internal */ private _selectStrategy(obj: Observable<any>|Promise<any>|EventEmitter<any>): any {
_selectStrategy(obj: Observable<any>|Promise<any>|EventEmitter<any>): any {
if (isPromise(obj)) { if (isPromise(obj)) {
return _promiseStrategy; return _promiseStrategy;
} else if ((<any>obj).subscribe) {
return _observableStrategy;
} else {
throw new InvalidPipeArgumentError(AsyncPipe, obj);
} }
if ((<any>obj).subscribe) {
return _observableStrategy;
}
throw new InvalidPipeArgumentError(AsyncPipe, obj);
} }
/** @internal */ private _dispose(): void {
_dispose(): void {
this._strategy.dispose(this._subscription); this._strategy.dispose(this._subscription);
this._latestValue = null; this._latestValue = null;
this._latestReturnedValue = null; this._latestReturnedValue = null;
@ -141,8 +131,7 @@ export class AsyncPipe implements OnDestroy {
this._obj = null; this._obj = null;
} }
/** @internal */ private _updateLatestValue(async: any, value: Object) {
_updateLatestValue(async: any, value: Object) {
if (async === this._obj) { if (async === this._obj) {
this._latestValue = value; this._latestValue = value;
this._ref.markForCheck(); this._ref.markForCheck();

View File

@ -7,31 +7,31 @@
*/ */
import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core'; import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
import {StringMapWrapper} from '../facade/collection';
import {DateFormatter} from '../facade/intl'; import {DateFormatter} from '../facade/intl';
import {DateWrapper, NumberWrapper, isBlank, isDate, isString} from '../facade/lang'; import {NumberWrapper, isBlank, isDate} from '../facade/lang';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error'; import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
/** /**
* Formats a date value to a string based on the requested format. * @ngModule CommonModule
* @whatItDoes Formats a date according to locale rules.
* @howToUse `date_expression | date[:format]`
* @description
* *
* WARNINGS: * Where:
* - this pipe is marked as pure hence it will not be re-evaluated when the input is mutated. * - `expression` is a date object or a number (milliseconds since UTC epoch) or an ISO string
* Instead users should treat the date as an immutable object and change the reference when the * (https://www.w3.org/TR/NOTE-datetime).
* pipe needs to re-run (this is to avoid reformatting the date on every change detection run * - `format` indicates which date/time components to include. The format can be predifined as
* which would be an expensive operation). * shown below or custom as shown in the table.
* - this pipe uses the Internationalization API. Therefore it is only reliable in Chrome and Opera * - `'medium'`: equivalent to `'yMMMdjms'` (e.g. `Sep 3, 2010, 12:05:08 PM` for `en-US`)
* browsers. * - `'short'`: equivalent to `'yMdjm'` (e.g. `9/3/2010, 12:05 PM` for `en-US`)
* - `'fullDate'`: equivalent to `'yMMMMEEEEd'` (e.g. `Friday, September 3, 2010` for `en-US`)
* - `'longDate'`: equivalent to `'yMMMMd'` (e.g. `September 3, 2010` for `en-US`)
* - `'mediumDate'`: equivalent to `'yMMMd'` (e.g. `Sep 3, 2010` for `en-US`)
* - `'shortDate'`: equivalent to `'yMd'` (e.g. `9/3/2010` for `en-US`)
* - `'mediumTime'`: equivalent to `'jms'` (e.g. `12:05:08 PM` for `en-US`)
* - `'shortTime'`: equivalent to `'jm'` (e.g. `12:05 PM` for `en-US`)
* *
* ## Usage
*
* expression | date[:format]
*
* where `expression` is a date object or a number (milliseconds since UTC epoch) or an ISO string
* (https://www.w3.org/TR/NOTE-datetime) and `format` indicates which date/time components to
* include:
* *
* | Component | Symbol | Short Form | Long Form | Numeric | 2-digit | * | Component | Symbol | Short Form | Long Form | Numeric | 2-digit |
* |-----------|:------:|--------------|-------------------|-----------|-----------| * |-----------|:------:|--------------|-------------------|-----------|-----------|
@ -52,18 +52,15 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
* 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.
* *
* `format` can also be one of the following predefined formats: * Timezone of the formatted text will be the local system timezone of the end-user's machine.
* *
* - `'medium'`: equivalent to `'yMMMdjms'` (e.g. Sep 3, 2010, 12:05:08 PM for en-US) * WARNINGS:
* - `'short'`: equivalent to `'yMdjm'` (e.g. 9/3/2010, 12:05 PM for en-US) * - this pipe is marked as pure hence it will not be re-evaluated when the input is mutated.
* - `'fullDate'`: equivalent to `'yMMMMEEEEd'` (e.g. Friday, September 3, 2010 for en-US) * Instead users should treat the date as an immutable object and change the reference when the
* - `'longDate'`: equivalent to `'yMMMMd'` (e.g. September 3, 2010) * pipe needs to re-run (this is to avoid reformatting the date on every change detection run
* - `'mediumDate'`: equivalent to `'yMMMd'` (e.g. Sep 3, 2010 for en-US) * which would be an expensive operation).
* - `'shortDate'`: equivalent to `'yMd'` (e.g. 9/3/2010 for en-US) * - this pipe uses the Internationalization API. Therefore it is only reliable in Chrome and Opera
* - `'mediumTime'`: equivalent to `'jms'` (e.g. 12:05:08 PM for en-US) * browsers.
* - `'shortTime'`: equivalent to `'jm'` (e.g. 12:05 PM for en-US)
*
* Timezone of the formatted text will be the local system timezone of the end-users machine.
* *
* ### Examples * ### Examples
* *
@ -77,14 +74,14 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
* {{ dateObj | date:'mmss' }} // output is '43:11' * {{ dateObj | date:'mmss' }} // output is '43:11'
* ``` * ```
* *
* {@example core/pipes/ts/date_pipe/date_pipe_example.ts region='DatePipe'} * {@example common/pipes/ts/date_pipe.ts region='DatePipe'}
* *
* @stable * @stable
*/ */
@Pipe({name: 'date', pure: true}) @Pipe({name: 'date', pure: true})
export class DatePipe implements PipeTransform { export class DatePipe implements PipeTransform {
/** @internal */ /** @internal */
static _ALIASES: {[key: string]: String} = { static _ALIASES: {[key: string]: string} = {
'medium': 'yMMMdjms', 'medium': 'yMMMdjms',
'short': 'yMdjm', 'short': 'yMdjm',
'fullDate': 'yMMMMEEEEd', 'fullDate': 'yMMMMEEEEd',
@ -105,23 +102,15 @@ export class DatePipe implements PipeTransform {
} }
if (NumberWrapper.isNumeric(value)) { if (NumberWrapper.isNumeric(value)) {
value = DateWrapper.fromMillis(parseFloat(value)); value = parseFloat(value);
} else if (isString(value)) {
value = DateWrapper.fromISOString(value);
} }
if (StringMapWrapper.contains(DatePipe._ALIASES, pattern)) {
pattern = <string>StringMapWrapper.get(DatePipe._ALIASES, pattern); return DateFormatter.format(
} new Date(value), this._locale, DatePipe._ALIASES[pattern] || pattern);
return DateFormatter.format(value, this._locale, pattern);
} }
private supports(obj: any): boolean { private supports(obj: any): boolean {
if (isDate(obj) || NumberWrapper.isNumeric(obj)) { return isDate(obj) || NumberWrapper.isNumeric(obj) ||
return true; (typeof obj === 'string' && isDate(new Date(obj)));
}
if (isString(obj) && isDate(DateWrapper.fromISOString(obj))) {
return true;
}
return false;
} }
} }

View File

@ -7,46 +7,26 @@
*/ */
import {Pipe, PipeTransform} from '@angular/core'; import {Pipe, PipeTransform} from '@angular/core';
import {StringWrapper, isBlank, isStringMap} 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';
const _INTERPOLATION_REGEXP: RegExp = /#/g; const _INTERPOLATION_REGEXP: RegExp = /#/g;
/** /**
* Maps a value to a string that pluralizes the value properly. * @ngModule CommonModule
* @whatItDoes Maps a value to a string that pluralizes the value according to locale rules.
* @howToUse `expression | i18nPlural:mapping`
* @description
* *
* ## Usage * Where:
* * - `expression` is a number.
* expression | i18nPlural:mapping * - `mapping` is an object that mimics the ICU format, see
* * http://userguide.icu-project.org/formatparse/messages
* where `expression` is a number and `mapping` is an object that mimics the ICU format,
* see http://userguide.icu-project.org/formatparse/messages
* *
* ## Example * ## Example
* *
* ``` * {@example common/pipes/ts/i18n_pipe.ts region='I18nPluralPipeComponent'}
* @Component({
* selector: 'app',
* template: `
* <div>
* {{ messages.length | i18nPlural: messageMapping }}
* </div>
* `,
* // best practice is to define the locale at the application level
* providers: [{provide: LOCALE_ID, useValue: 'en_US'}]
* })
*
* class MyApp {
* messages: any[];
* messageMapping: {[k:string]: string} = {
* '=0': 'No messages.',
* '=1': 'One message.',
* 'other': '# messages.'
* }
* ...
* }
* ```
* *
* @experimental * @experimental
*/ */
@ -63,6 +43,6 @@ export class I18nPluralPipe implements PipeTransform {
const key = getPluralCategory(value, Object.keys(pluralMap), this._localization); const key = getPluralCategory(value, Object.keys(pluralMap), this._localization);
return StringWrapper.replaceAll(pluralMap[key], _INTERPOLATION_REGEXP, value.toString()); return pluralMap[key].replace(_INTERPOLATION_REGEXP, value.toString());
} }
} }

View File

@ -11,33 +11,18 @@ import {isBlank, isStringMap} from '../facade/lang';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error'; import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
/** /**
* @ngModule CommonModule
* @whatItDoes Generic selector that displays the string that matches the current value.
* @howToUse `expression | i18nSelect:mapping`
* @description
* *
* Generic selector that displays the string that matches the current value. * Where:
* * - `mapping`: is an object that indicates the text that should be displayed
* ## Usage
*
* expression | i18nSelect:mapping
*
* 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`.
* *
* ## Example * ## Example
* *
* ``` * {@example common/pipes/ts/i18n_pipe.ts region='I18nSelectPipeComponent'}
* <div>
* {{ gender | i18nSelect: inviteMap }}
* </div>
*
* class MyApp {
* gender: string = 'male';
* inviteMap: any = {
* 'male': 'Invite him.',
* 'female': 'Invite her.',
* 'other': 'Invite them.'
* }
* ...
* }
* ```
* *
* @experimental * @experimental
*/ */

View File

@ -21,15 +21,22 @@ import {CurrencyPipe, DecimalPipe, PercentPipe} from './number_pipe';
import {SlicePipe} from './slice_pipe'; import {SlicePipe} from './slice_pipe';
import {UpperCasePipe} from './uppercase_pipe'; import {UpperCasePipe} from './uppercase_pipe';
export {
AsyncPipe,
CurrencyPipe,
DatePipe,
DecimalPipe,
I18nPluralPipe,
I18nSelectPipe,
JsonPipe,
LowerCasePipe,
PercentPipe,
SlicePipe,
UpperCasePipe
};
/** /**
* A collection of Angular core pipes that are likely to be used in each and every * A collection of Angular pipes that are likely to be used in each and every application.
* application.
*
* This collection can be used to quickly enumerate all the built-in pipes in the `pipes`
* property of the `@Component` decorator.
*
* @experimental Contains i18n pipes which are experimental
*/ */
export const COMMON_PIPES = [ export const COMMON_PIPES = [
AsyncPipe, AsyncPipe,

View File

@ -13,10 +13,15 @@ import {Json} from '../facade/lang';
/** /**
* Transforms any input value using `JSON.stringify`. Useful for debugging. * @ngModule CommonModule
* @whatItDoes Converts value into JSON string.
* @howToUse `expression | json`
* @description
*
* Converts value into string using `JSON.stringify`. Useful for debugging.
* *
* ### Example * ### Example
* {@example core/pipes/ts/json_pipe/json_pipe_example.ts region='JsonPipe'} * {@example common/pipes/ts/json_pipe.ts region='JsonPipe'}
* *
* @stable * @stable
*/ */

View File

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

View File

@ -9,21 +9,23 @@
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 {NumberFormatStyle, NumberFormatter} from '../facade/intl';
import {NumberWrapper, isBlank, isNumber, isPresent, isString} from '../facade/lang'; import {NumberWrapper, isBlank, isPresent} from '../facade/lang';
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+))?)?$/;
function formatNumber( function formatNumber(
pipe: Type<any>, locale: string, value: number | string, style: NumberFormatStyle, pipe: Type<any>, locale: string, value: number | string, style: NumberFormatStyle,
digits: string, currency: string = null, currencyAsSymbol: boolean = false): string { digits: string, currency: string = null, currencyAsSymbol: boolean = false): string {
if (isBlank(value)) return null; if (isBlank(value)) return null;
// Convert strings to numbers // Convert strings to numbers
value = isString(value) && NumberWrapper.isNumeric(value) ? +value : value; value = typeof value === 'string' && NumberWrapper.isNumeric(value) ? +value : value;
if (!isNumber(value)) { if (typeof value !== 'number') {
throw new InvalidPipeArgumentError(pipe, value); throw new InvalidPipeArgumentError(pipe, value);
} }
let minInt: number; let minInt: number;
let minFraction: number; let minFraction: number;
let maxFraction: number; let maxFraction: number;
@ -34,8 +36,8 @@ function formatNumber(
maxFraction = 3; maxFraction = 3;
} }
if (isPresent(digits)) { if (digits) {
var 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`);
} }
@ -49,41 +51,40 @@ function formatNumber(
maxFraction = NumberWrapper.parseIntAutoRadix(parts[5]); maxFraction = NumberWrapper.parseIntAutoRadix(parts[5]);
} }
} }
return NumberFormatter.format(value as number, locale, style, { return NumberFormatter.format(value as number, locale, style, {
minimumIntegerDigits: minInt, minimumIntegerDigits: minInt,
minimumFractionDigits: minFraction, minimumFractionDigits: minFraction,
maximumFractionDigits: maxFraction, maximumFractionDigits: maxFraction,
currency: currency, currency: currency,
currencyAsSymbol: currencyAsSymbol currencyAsSymbol: currencyAsSymbol,
}); });
} }
/** /**
* WARNING: this pipe uses the Internationalization API. * @ngModule CommonModule
* Therefore it is only reliable in Chrome and Opera browsers. For other browsers please use an * @whatItDoes Formats a number according to locale rules.
* polyfill, for example: [https://github.com/andyearnshaw/Intl.js/]. * @howToUse `number_expression | number[:digitInfo]`
* *
* Formats a number as local text. i.e. group sizing and separator and other locale-specific * Formats a number as text. Group sizing and separator and other locale-specific
* configurations are based on the active locale. * configurations are based on the active locale.
* *
* ### Usage * where `expression` is a number:
* * - `digitInfo` is a `string` which has a following format: <br>
* expression | number[:digitInfo] * <code>{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}</code>
* * - `minIntegerDigits` is the minimum number of integer digits to use. Defaults to `1`.
* where `expression` is a number and `digitInfo` has the following format: * - `minFractionDigits` is the minimum number of digits after fraction. Defaults to `0`.
* * - `maxFractionDigits` is the maximum number of digits after fraction. Defaults to `3`.
* {minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}
*
* - minIntegerDigits is the minimum number of integer digits to use. Defaults to 1.
* - minFractionDigits is the minimum number of digits after fraction. Defaults to 0.
* - maxFractionDigits is the maximum number of digits after fraction. Defaults to 3.
* *
* For more information on the acceptable range for each of these numbers and other * For more information on the acceptable range for each of these numbers and other
* details see your native internationalization library. * details see your native internationalization library.
* *
* WARNING: this pipe uses the Internationalization API which is not yet available in all browsers
* and may require a polyfill. See {@linkDocs guide/browser-support} for details.
*
* ### Example * ### Example
* *
* {@example core/pipes/ts/number_pipe/number_pipe_example.ts region='NumberPipe'} * {@example common/pipes/ts/number_pipe.ts region='NumberPipe'}
* *
* @stable * @stable
*/ */
@ -97,21 +98,22 @@ export class DecimalPipe implements PipeTransform {
} }
/** /**
* WARNING: this pipe uses the Internationalization API. * @ngModule CommonModule
* Therefore it is only reliable in Chrome and Opera browsers. For other browsers please use an * @whatItDoes Formats a number as a percentage according to locale rules.
* polyfill, for example: [https://github.com/andyearnshaw/Intl.js/]. * @howToUse `number_expression | percent[:digitInfo]`
* *
* Formats a number as local percent. * @description
* *
* ### Usage * Formats a number as percentage.
* *
* expression | percent[:digitInfo] * - `digitInfo` See {@link DecimalPipe} for detailed description.
* *
* For more information about `digitInfo` see {@link DecimalPipe} * WARNING: this pipe uses the Internationalization API which is not yet available in all browsers
* and may require a polyfill. See {@linkDocs guide/browser-support} for details.
* *
* ### Example * ### Example
* *
* {@example core/pipes/ts/number_pipe/number_pipe_example.ts region='PercentPipe'} * {@example common/pipes/ts/number_pipe.ts region='PercentPipe'}
* *
* @stable * @stable
*/ */
@ -125,26 +127,26 @@ export class PercentPipe implements PipeTransform {
} }
/** /**
* WARNING: this pipe uses the Internationalization API. * @ngModule CommonModule
* Therefore it is only reliable in Chrome and Opera browsers. For other browsers please use an * @whatItDoes Formats a number as currency using locale rules.
* polyfill, for example: [https://github.com/andyearnshaw/Intl.js/]. * @howToUse `number_expression | currency[:currencyCode[:symbolDisplay[:digitInfo]]]`
* @description
* *
* Use `currency` to format a number as currency.
* *
* Formats a number as local currency. * - `currencyCode` is the [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) currency code, such
* as `USD` for the US dollar and `EUR` for the euro.
* - `symbolDisplay` is a boolean indicating whether to use the currency symbol or code.
* - `true`: use symbol (e.g. `$`).
* - `false`(default): use code (e.g. `USD`).
* - `digitInfo` See {@link DecimalPipe} for detailed description.
* *
* ### Usage * WARNING: this pipe uses the Internationalization API which is not yet available in all browsers
* * and may require a polyfill. See {@linkDocs guide/browser-support} for details.
* expression | currency[:currencyCode[:symbolDisplay[:digitInfo]]]
*
* where `currencyCode` is the ISO 4217 currency code, such as "USD" for the US dollar and
* "EUR" for the euro. `symbolDisplay` is a boolean indicating whether to use the currency
* symbol (e.g. $) or the currency code (e.g. USD) in the output. The default for this value
* is `false`.
* For more information about `digitInfo` see {@link DecimalPipe}
* *
* ### Example * ### Example
* *
* {@example core/pipes/ts/number_pipe/number_pipe_example.ts region='CurrencyPipe'} * {@example common/pipes/ts/number_pipe.ts region='CurrencyPipe'}
* *
* @stable * @stable
*/ */

View File

@ -7,53 +7,41 @@
*/ */
import {Pipe, PipeTransform} from '@angular/core'; import {Pipe, PipeTransform} from '@angular/core';
import {ListWrapper} from '../facade/collection'; import {isBlank} from '../facade/lang';
import {StringWrapper, isArray, isBlank, isString} from '../facade/lang';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error'; import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
/** /**
* Creates a new List or String containing only a subset (slice) of the * @ngModule CommonModule
* elements. * @whatItDoes Creates a new List or String containing a subset (slice) of the elements.
* @howToUse `array_or_string_expression | slice:start[:end]`
* @description
* *
* The starting index of the subset to return is specified by the `start` parameter. * Where the input expression is a `List` or `String`, and:
* - `start`: The starting index of the subset to return.
* - **a positive integer**: return the item at `start` index and all items after
* in the list or string expression.
* - **a negative integer**: return the item at `start` index from the end and all items after
* in the list or string expression.
* - **if positive and greater than the size of the expression**: return an empty list or string.
* - **if negative and greater than the size of the expression**: return entire list or string.
* - `end`: The ending index of the subset to return.
* - **omitted**: return all items until the end.
* - **if positive**: return all items before `end` index of the list or string.
* - **if negative**: return all items before `end` index from the end of the list or string.
* *
* The ending index of the subset to return is specified by the optional `end` parameter. * All behavior is based on the expected behavior of the JavaScript API `Array.prototype.slice()`
* * and `String.prototype.slice()`.
* ### Usage
*
* expression | slice:start[:end]
*
* All behavior is based on the expected behavior of the JavaScript API
* Array.prototype.slice() and String.prototype.slice()
*
* Where the input expression is a [List] or [String], and `start` is:
*
* - **a positive integer**: return the item at _start_ index and all items after
* in the list or string expression.
* - **a negative integer**: return the item at _start_ index from the end and all items after
* in the list or string expression.
* - **`|start|` greater than the size of the expression**: return an empty list or string.
* - **`|start|` negative greater than the size of the expression**: return entire list or
* string expression.
*
* and where `end` is:
*
* - **omitted**: return all items until the end of the input
* - **a positive integer**: return all items before _end_ index of the list or string
* expression.
* - **a negative integer**: return all items before _end_ index from the end of the list
* or string expression.
* *
* When operating on a [List], the returned list is always a copy even when all * When operating on a [List], the returned list is always a copy even when all
* the elements are being returned. * the elements are being returned.
* *
* When operating on a blank value, returns it. * When operating on a blank value, the pipe returns the blank value.
* *
* ## List Example * ## List Example
* *
* This `ngFor` example: * This `ngFor` example:
* *
* {@example core/pipes/ts/slice_pipe/slice_pipe_example.ts region='SlicePipe_list'} * {@example common/pipes/ts/slice_pipe.ts region='SlicePipe_list'}
* *
* produces the following: * produces the following:
* *
@ -62,23 +50,22 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
* *
* ## String Examples * ## String Examples
* *
* {@example core/pipes/ts/slice_pipe/slice_pipe_example.ts region='SlicePipe_string'} * {@example common/pipes/ts/slice_pipe.ts region='SlicePipe_string'}
* *
* @stable * @stable
*/ */
@Pipe({name: 'slice', pure: false}) @Pipe({name: 'slice', pure: false})
export class SlicePipe implements PipeTransform { export class SlicePipe implements PipeTransform {
transform(value: any, start: number, end: number = null): any { transform(value: any, start: number, end?: number): any {
if (isBlank(value)) return value; if (isBlank(value)) return value;
if (!this.supports(value)) { if (!this.supports(value)) {
throw new InvalidPipeArgumentError(SlicePipe, value); throw new InvalidPipeArgumentError(SlicePipe, value);
} }
if (isString(value)) {
return StringWrapper.slice(value, start, end); return value.slice(start, end);
}
return ListWrapper.slice(value, start, end);
} }
private supports(obj: any): boolean { return isString(obj) || isArray(obj); } private supports(obj: any): boolean { return typeof obj === 'string' || Array.isArray(obj); }
} }

View File

@ -7,15 +7,20 @@
*/ */
import {Pipe, PipeTransform} from '@angular/core'; import {Pipe, PipeTransform} from '@angular/core';
import {isBlank, isString} from '../facade/lang'; import {isBlank} from '../facade/lang';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error'; import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
/** /**
* Implements uppercase transforms to text. * @ngModule CommonModule
* @whatItDoes Transforms string to uppercase.
* @howToUse `expression | uppercase`
* @description
*
* Converts value into lowercase string using `String.prototype.toUpperCase()`.
* *
* ### Example * ### Example
* *
* {@example core/pipes/ts/lowerupper_pipe/lowerupper_pipe_example.ts region='LowerUpperPipe'} * {@example common/pipes/ts/lowerupper_pipe.ts region='LowerUpperPipe'}
* *
* @stable * @stable
*/ */
@ -23,7 +28,7 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
export class UpperCasePipe implements PipeTransform { export class UpperCasePipe implements PipeTransform {
transform(value: string): string { transform(value: string): string {
if (isBlank(value)) return value; if (isBlank(value)) return value;
if (!isString(value)) { if (typeof value !== 'string') {
throw new InvalidPipeArgumentError(UpperCasePipe, value); throw new InvalidPipeArgumentError(UpperCasePipe, value);
} }
return value.toUpperCase(); return value.toUpperCase();

View File

@ -5,6 +5,7 @@
* 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 class AnimationOutput {
constructor(public name: string, public phase: string, public fullPropertyName: string) {} import {__core_private__ as r} from '@angular/core';
}
export const isPromise: typeof r.isPromise = r.isPromise;

View File

@ -6,20 +6,23 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {NgClass, NgFor} from '@angular/common';
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, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal'; import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
import {ListWrapper, StringMapWrapper} from '../../src/facade/collection';
function detectChangesAndCheck(fixture: ComponentFixture<any>, classes: string) {
fixture.detectChanges();
expect(fixture.debugElement.children[0].nativeElement.className).toEqual(classes);
}
export function main() { export function main() {
describe('binding to CSS class list', () => { describe('binding to CSS class list', () => {
let fixture: ComponentFixture<any>;
function detectChangesAndExpectClassName(classes: string): void {
fixture.detectChanges();
expect(fixture.debugElement.children[0].nativeElement.className).toEqual(classes);
}
function getComponent(): TestComponent { return fixture.debugElement.componentInstance; }
afterEach(() => { fixture = null; });
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [TestComponent], declarations: [TestComponent],
@ -27,268 +30,221 @@ export function main() {
}); });
it('should clean up when the directive is destroyed', async(() => { it('should clean up when the directive is destroyed', async(() => {
let template = '<div *ngFor="let item of items" [ngClass]="item"></div>'; fixture = createTestComponent('<div *ngFor="let item of items" [ngClass]="item"></div>');
TestBed.overrideComponent(TestComponent, {set: {template: template}});
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.items = [['0']]; getComponent().items = [['0']];
fixture.detectChanges(); fixture.detectChanges();
fixture.debugElement.componentInstance.items = [['1']]; getComponent().items = [['1']];
detectChangesAndExpectClassName('1');
detectChangesAndCheck(fixture, '1');
})); }));
describe('expressions evaluating to objects', () => { describe('expressions evaluating to objects', () => {
it('should add classes specified in an object literal', async(() => { it('should add classes specified in an object literal', async(() => {
let template = '<div [ngClass]="{foo: true, bar: false}"></div>'; fixture = createTestComponent('<div [ngClass]="{foo: true, bar: false}"></div>');
TestBed.overrideComponent(TestComponent, {set: {template: template}});
let fixture = TestBed.createComponent(TestComponent);
detectChangesAndCheck(fixture, 'foo'); detectChangesAndExpectClassName('foo');
})); }));
it('should add classes specified in an object literal without change in class names', it('should add classes specified in an object literal without change in class names',
async(() => { async(() => {
let template = `<div [ngClass]="{'foo-bar': true, 'fooBar': true}"></div>`; fixture =
TestBed.overrideComponent(TestComponent, {set: {template: template}}); createTestComponent(`<div [ngClass]="{'foo-bar': true, 'fooBar': true}"></div>`);
let fixture = TestBed.createComponent(TestComponent);
detectChangesAndCheck(fixture, 'foo-bar fooBar'); detectChangesAndExpectClassName('foo-bar fooBar');
})); }));
it('should add and remove classes based on changes in object literal values', async(() => { it('should add and remove classes based on changes in object literal values', async(() => {
let template = '<div [ngClass]="{foo: condition, bar: !condition}"></div>'; fixture =
TestBed.overrideComponent(TestComponent, {set: {template: template}}); createTestComponent('<div [ngClass]="{foo: condition, bar: !condition}"></div>');
let fixture = TestBed.createComponent(TestComponent);
detectChangesAndCheck(fixture, 'foo'); detectChangesAndExpectClassName('foo');
fixture.debugElement.componentInstance.condition = false; getComponent().condition = false;
detectChangesAndCheck(fixture, 'bar'); detectChangesAndExpectClassName('bar');
})); }));
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(() => {
let template = '<div [ngClass]="objExpr"></div>'; fixture = createTestComponent('<div [ngClass]="objExpr"></div>');
TestBed.overrideComponent(TestComponent, {set: {template: template}}); let objExpr = getComponent().objExpr;
let fixture = TestBed.createComponent(TestComponent);
detectChangesAndCheck(fixture, 'foo'); detectChangesAndExpectClassName('foo');
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true); objExpr['bar'] = true;
detectChangesAndCheck(fixture, 'foo bar'); detectChangesAndExpectClassName('foo bar');
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'baz', true); objExpr['baz'] = true;
detectChangesAndCheck(fixture, 'foo bar baz'); detectChangesAndExpectClassName('foo bar baz');
StringMapWrapper.delete(fixture.debugElement.componentInstance.objExpr, 'bar'); delete (objExpr['bar']);
detectChangesAndCheck(fixture, 'foo baz'); detectChangesAndExpectClassName('foo baz');
})); }));
it('should add and remove classes based on reference changes to the expression object', it('should add and remove classes based on reference changes to the expression object',
async(() => { async(() => {
let template = '<div [ngClass]="objExpr"></div>'; fixture = createTestComponent('<div [ngClass]="objExpr"></div>');
TestBed.overrideComponent(TestComponent, {set: {template: template}});
let fixture = TestBed.createComponent(TestComponent);
detectChangesAndCheck(fixture, 'foo'); detectChangesAndExpectClassName('foo');
fixture.debugElement.componentInstance.objExpr = {foo: true, bar: true}; getComponent().objExpr = {foo: true, bar: true};
detectChangesAndCheck(fixture, 'foo bar'); detectChangesAndExpectClassName('foo bar');
fixture.debugElement.componentInstance.objExpr = {baz: true}; getComponent().objExpr = {baz: true};
detectChangesAndCheck(fixture, 'baz'); detectChangesAndExpectClassName('baz');
})); }));
it('should remove active classes when expression evaluates to null', async(() => { it('should remove active classes when expression evaluates to null', async(() => {
let template = '<div [ngClass]="objExpr"></div>'; fixture = createTestComponent('<div [ngClass]="objExpr"></div>');
TestBed.overrideComponent(TestComponent, {set: {template: template}});
let fixture = TestBed.createComponent(TestComponent);
detectChangesAndCheck(fixture, 'foo'); detectChangesAndExpectClassName('foo');
fixture.debugElement.componentInstance.objExpr = null; getComponent().objExpr = null;
detectChangesAndCheck(fixture, ''); detectChangesAndExpectClassName('');
fixture.debugElement.componentInstance.objExpr = {'foo': false, 'bar': true}; getComponent().objExpr = {'foo': false, 'bar': true};
detectChangesAndCheck(fixture, 'bar'); detectChangesAndExpectClassName('bar');
})); }));
it('should allow multiple classes per expression', async(() => { it('should allow multiple classes per expression', async(() => {
let template = '<div [ngClass]="objExpr"></div>'; fixture = createTestComponent('<div [ngClass]="objExpr"></div>');
TestBed.overrideComponent(TestComponent, {set: {template: template}});
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.objExpr = {'bar baz': true, 'bar1 baz1': true}; getComponent().objExpr = {'bar baz': true, 'bar1 baz1': true};
detectChangesAndCheck(fixture, 'bar baz bar1 baz1'); detectChangesAndExpectClassName('bar baz bar1 baz1');
fixture.debugElement.componentInstance.objExpr = {'bar baz': false, 'bar1 baz1': true}; getComponent().objExpr = {'bar baz': false, 'bar1 baz1': true};
detectChangesAndCheck(fixture, 'bar1 baz1'); detectChangesAndExpectClassName('bar1 baz1');
})); }));
it('should split by one or more spaces between classes', async(() => { it('should split by one or more spaces between classes', async(() => {
let template = '<div [ngClass]="objExpr"></div>'; fixture = createTestComponent('<div [ngClass]="objExpr"></div>');
TestBed.overrideComponent(TestComponent, {set: {template: template}});
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.objExpr = {'foo bar baz': true}; getComponent().objExpr = {'foo bar baz': true};
detectChangesAndCheck(fixture, 'foo bar baz'); detectChangesAndExpectClassName('foo bar baz');
})); }));
}); });
describe('expressions evaluating to lists', () => { describe('expressions evaluating to lists', () => {
it('should add classes specified in a list literal', async(() => { it('should add classes specified in a list literal', async(() => {
let template = `<div [ngClass]="['foo', 'bar', 'foo-bar', 'fooBar']"></div>`; fixture =
TestBed.overrideComponent(TestComponent, {set: {template: template}}); createTestComponent(`<div [ngClass]="['foo', 'bar', 'foo-bar', 'fooBar']"></div>`);
let fixture = TestBed.createComponent(TestComponent);
detectChangesAndCheck(fixture, 'foo bar foo-bar fooBar'); detectChangesAndExpectClassName('foo bar foo-bar fooBar');
})); }));
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(() => {
let template = '<div [ngClass]="arrExpr"></div>'; fixture = createTestComponent('<div [ngClass]="arrExpr"></div>');
TestBed.overrideComponent(TestComponent, {set: {template: template}}); let arrExpr = getComponent().arrExpr;
let fixture = TestBed.createComponent(TestComponent); detectChangesAndExpectClassName('foo');
var arrExpr: string[] = fixture.debugElement.componentInstance.arrExpr;
detectChangesAndCheck(fixture, 'foo');
arrExpr.push('bar'); arrExpr.push('bar');
detectChangesAndCheck(fixture, 'foo bar'); detectChangesAndExpectClassName('foo bar');
arrExpr[1] = 'baz'; arrExpr[1] = 'baz';
detectChangesAndCheck(fixture, 'foo baz'); detectChangesAndExpectClassName('foo baz');
ListWrapper.remove(fixture.debugElement.componentInstance.arrExpr, 'baz'); getComponent().arrExpr = arrExpr.filter((v: string) => v !== 'baz');
detectChangesAndCheck(fixture, 'foo'); detectChangesAndExpectClassName('foo');
})); }));
it('should add and remove classes when a reference changes', async(() => { it('should add and remove classes when a reference changes', async(() => {
let template = '<div [ngClass]="arrExpr"></div>'; fixture = createTestComponent('<div [ngClass]="arrExpr"></div>');
TestBed.overrideComponent(TestComponent, {set: {template: template}}); detectChangesAndExpectClassName('foo');
let fixture = TestBed.createComponent(TestComponent);
detectChangesAndCheck(fixture, 'foo');
fixture.debugElement.componentInstance.arrExpr = ['bar']; getComponent().arrExpr = ['bar'];
detectChangesAndCheck(fixture, 'bar'); detectChangesAndExpectClassName('bar');
})); }));
it('should take initial classes into account when a reference changes', async(() => { it('should take initial classes into account when a reference changes', async(() => {
let template = '<div class="foo" [ngClass]="arrExpr"></div>'; fixture = createTestComponent('<div class="foo" [ngClass]="arrExpr"></div>');
TestBed.overrideComponent(TestComponent, {set: {template: template}}); detectChangesAndExpectClassName('foo');
let fixture = TestBed.createComponent(TestComponent);
detectChangesAndCheck(fixture, 'foo');
fixture.debugElement.componentInstance.arrExpr = ['bar']; getComponent().arrExpr = ['bar'];
detectChangesAndCheck(fixture, 'foo bar'); detectChangesAndExpectClassName('foo bar');
})); }));
it('should ignore empty or blank class names', async(() => { it('should ignore empty or blank class names', async(() => {
let template = '<div class="foo" [ngClass]="arrExpr"></div>'; fixture = createTestComponent('<div class="foo" [ngClass]="arrExpr"></div>');
TestBed.overrideComponent(TestComponent, {set: {template: template}}); getComponent().arrExpr = ['', ' '];
let fixture = TestBed.createComponent(TestComponent); detectChangesAndExpectClassName('foo');
fixture.debugElement.componentInstance.arrExpr = ['', ' '];
detectChangesAndCheck(fixture, 'foo');
})); }));
it('should trim blanks from class names', async(() => { it('should trim blanks from class names', async(() => {
var template = '<div class="foo" [ngClass]="arrExpr"></div>'; fixture = createTestComponent('<div class="foo" [ngClass]="arrExpr"></div>');
TestBed.overrideComponent(TestComponent, {set: {template: template}});
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.arrExpr = [' bar ']; getComponent().arrExpr = [' bar '];
detectChangesAndCheck(fixture, 'foo bar'); detectChangesAndExpectClassName('foo bar');
})); }));
it('should allow multiple classes per item in arrays', async(() => { it('should allow multiple classes per item in arrays', async(() => {
var template = '<div [ngClass]="arrExpr"></div>'; fixture = createTestComponent('<div [ngClass]="arrExpr"></div>');
TestBed.overrideComponent(TestComponent, {set: {template: template}});
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.arrExpr = ['foo bar baz', 'foo1 bar1 baz1']; getComponent().arrExpr = ['foo bar baz', 'foo1 bar1 baz1'];
detectChangesAndCheck(fixture, 'foo bar baz foo1 bar1 baz1'); detectChangesAndExpectClassName('foo bar baz foo1 bar1 baz1');
fixture.debugElement.componentInstance.arrExpr = ['foo bar baz foobar']; getComponent().arrExpr = ['foo bar baz foobar'];
detectChangesAndCheck(fixture, 'foo bar baz foobar'); detectChangesAndExpectClassName('foo bar baz foobar');
})); }));
}); });
describe('expressions evaluating to sets', () => { describe('expressions evaluating to sets', () => {
it('should add and remove classes if the set instance changed', async(() => { it('should add and remove classes if the set instance changed', async(() => {
var template = '<div [ngClass]="setExpr"></div>'; fixture = createTestComponent('<div [ngClass]="setExpr"></div>');
TestBed.overrideComponent(TestComponent, {set: {template: template}}); let setExpr = new Set<string>();
let fixture = TestBed.createComponent(TestComponent);
var setExpr = new Set<string>();
setExpr.add('bar'); setExpr.add('bar');
fixture.debugElement.componentInstance.setExpr = setExpr; getComponent().setExpr = setExpr;
detectChangesAndCheck(fixture, 'bar'); detectChangesAndExpectClassName('bar');
setExpr = new Set<string>(); setExpr = new Set<string>();
setExpr.add('baz'); setExpr.add('baz');
fixture.debugElement.componentInstance.setExpr = setExpr; getComponent().setExpr = setExpr;
detectChangesAndCheck(fixture, 'baz'); detectChangesAndExpectClassName('baz');
})); }));
}); });
describe('expressions evaluating to string', () => { describe('expressions evaluating to string', () => {
it('should add classes specified in a string literal', async(() => { it('should add classes specified in a string literal', async(() => {
var template = `<div [ngClass]="'foo bar foo-bar fooBar'"></div>`; fixture = createTestComponent(`<div [ngClass]="'foo bar foo-bar fooBar'"></div>`);
TestBed.overrideComponent(TestComponent, {set: {template: template}}); detectChangesAndExpectClassName('foo bar foo-bar fooBar');
let fixture = TestBed.createComponent(TestComponent);
detectChangesAndCheck(fixture, 'foo bar foo-bar fooBar');
})); }));
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(() => {
var template = '<div [ngClass]="strExpr"></div>'; fixture = createTestComponent('<div [ngClass]="strExpr"></div>');
TestBed.overrideComponent(TestComponent, {set: {template: template}}); detectChangesAndExpectClassName('foo');
let fixture = TestBed.createComponent(TestComponent);
detectChangesAndCheck(fixture, 'foo');
fixture.debugElement.componentInstance.strExpr = 'foo bar'; getComponent().strExpr = 'foo bar';
detectChangesAndCheck(fixture, 'foo bar'); detectChangesAndExpectClassName('foo bar');
fixture.debugElement.componentInstance.strExpr = 'baz'; getComponent().strExpr = 'baz';
detectChangesAndCheck(fixture, 'baz'); detectChangesAndExpectClassName('baz');
})); }));
it('should remove active classes when switching from string to null', async(() => { it('should remove active classes when switching from string to null', async(() => {
var template = `<div [ngClass]="strExpr"></div>`; fixture = createTestComponent(`<div [ngClass]="strExpr"></div>`);
TestBed.overrideComponent(TestComponent, {set: {template: template}}); detectChangesAndExpectClassName('foo');
let fixture = TestBed.createComponent(TestComponent);
detectChangesAndCheck(fixture, 'foo');
fixture.debugElement.componentInstance.strExpr = null;
detectChangesAndCheck(fixture, '');
getComponent().strExpr = null;
detectChangesAndExpectClassName('');
})); }));
it('should take initial classes into account when switching from string to null', it('should take initial classes into account when switching from string to null',
async(() => { async(() => {
var template = `<div class="foo" [ngClass]="strExpr"></div>`; fixture = createTestComponent(`<div class="foo" [ngClass]="strExpr"></div>`);
TestBed.overrideComponent(TestComponent, {set: {template: template}}); detectChangesAndExpectClassName('foo');
let fixture = TestBed.createComponent(TestComponent);
detectChangesAndCheck(fixture, 'foo');
fixture.debugElement.componentInstance.strExpr = null;
detectChangesAndCheck(fixture, 'foo');
getComponent().strExpr = null;
detectChangesAndExpectClassName('foo');
})); }));
it('should ignore empty and blank strings', async(() => { it('should ignore empty and blank strings', async(() => {
var template = `<div class="foo" [ngClass]="strExpr"></div>`; fixture = createTestComponent(`<div class="foo" [ngClass]="strExpr"></div>`);
TestBed.overrideComponent(TestComponent, {set: {template: template}}); getComponent().strExpr = '';
let fixture = TestBed.createComponent(TestComponent); detectChangesAndExpectClassName('foo');
fixture.debugElement.componentInstance.strExpr = '';
detectChangesAndCheck(fixture, 'foo');
})); }));
}); });
@ -296,83 +252,82 @@ export function main() {
describe('cooperation with other class-changing constructs', () => { describe('cooperation with other class-changing constructs', () => {
it('should co-operate with the class attribute', async(() => { it('should co-operate with the class attribute', async(() => {
var template = '<div [ngClass]="objExpr" class="init foo"></div>'; fixture = createTestComponent('<div [ngClass]="objExpr" class="init foo"></div>');
TestBed.overrideComponent(TestComponent, {set: {template: template}}); let objExpr = getComponent().objExpr;
let fixture = TestBed.createComponent(TestComponent);
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(fixture, 'init foo bar');
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'foo', false); objExpr['bar'] = true;
detectChangesAndCheck(fixture, 'init bar'); detectChangesAndExpectClassName('init foo bar');
fixture.debugElement.componentInstance.objExpr = null; objExpr['foo'] = false;
detectChangesAndCheck(fixture, 'init foo'); detectChangesAndExpectClassName('init bar');
getComponent().objExpr = null;
detectChangesAndExpectClassName('init foo');
})); }));
it('should co-operate with the interpolated class attribute', async(() => { it('should co-operate with the interpolated class attribute', async(() => {
var template = `<div [ngClass]="objExpr" class="{{'init foo'}}"></div>`; fixture = createTestComponent(`<div [ngClass]="objExpr" class="{{'init foo'}}"></div>`);
TestBed.overrideComponent(TestComponent, {set: {template: template}}); let objExpr = getComponent().objExpr;
let fixture = TestBed.createComponent(TestComponent);
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(fixture, `init foo bar`);
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'foo', false); objExpr['bar'] = true;
detectChangesAndCheck(fixture, `init bar`); detectChangesAndExpectClassName(`init foo bar`);
fixture.debugElement.componentInstance.objExpr = null; objExpr['foo'] = false;
detectChangesAndCheck(fixture, `init foo`); detectChangesAndExpectClassName(`init bar`);
getComponent().objExpr = null;
detectChangesAndExpectClassName(`init foo`);
})); }));
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(() => {
var template = `<div [ngClass]="objExpr" class="init" [class]="'foo'"></div>`; fixture =
TestBed.overrideComponent(TestComponent, {set: {template: template}}); createTestComponent(`<div [ngClass]="objExpr" class="init" [class]="'foo'"></div>`);
let fixture = TestBed.createComponent(TestComponent); let objExpr = getComponent().objExpr;
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(fixture, `init foo bar`);
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'foo', false); objExpr['bar'] = true;
detectChangesAndCheck(fixture, `init bar`); detectChangesAndExpectClassName(`init foo bar`);
fixture.debugElement.componentInstance.objExpr = null; objExpr['foo'] = false;
detectChangesAndCheck(fixture, `init foo`); detectChangesAndExpectClassName(`init bar`);
getComponent().objExpr = null;
detectChangesAndExpectClassName(`init foo`);
})); }));
it('should co-operate with the class attribute and class.name binding', async(() => { it('should co-operate with the class attribute and class.name binding', async(() => {
var template = const template =
'<div class="init foo" [ngClass]="objExpr" [class.baz]="condition"></div>'; '<div class="init foo" [ngClass]="objExpr" [class.baz]="condition"></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent); let objExpr = getComponent().objExpr;
detectChangesAndCheck(fixture, 'init foo baz');
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true); detectChangesAndExpectClassName('init foo baz');
detectChangesAndCheck(fixture, 'init foo baz bar');
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'foo', false); objExpr['bar'] = true;
detectChangesAndCheck(fixture, 'init baz bar'); detectChangesAndExpectClassName('init foo baz bar');
fixture.debugElement.componentInstance.condition = false; objExpr['foo'] = false;
detectChangesAndCheck(fixture, 'init bar'); detectChangesAndExpectClassName('init baz bar');
getComponent().condition = false;
detectChangesAndExpectClassName('init bar');
})); }));
it('should co-operate with initial class and class attribute binding when binding changes', it('should co-operate with initial class and class attribute binding when binding changes',
async(() => { async(() => {
var template = '<div class="init" [ngClass]="objExpr" [class]="strExpr"></div>'; const template = '<div class="init" [ngClass]="objExpr" [class]="strExpr"></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent); let cmp = getComponent();
detectChangesAndCheck(fixture, 'init foo');
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true); detectChangesAndExpectClassName('init foo');
detectChangesAndCheck(fixture, 'init foo bar');
fixture.debugElement.componentInstance.strExpr = 'baz'; cmp.objExpr['bar'] = true;
detectChangesAndCheck(fixture, 'init bar baz foo'); detectChangesAndExpectClassName('init foo bar');
fixture.debugElement.componentInstance.objExpr = null; cmp.strExpr = 'baz';
detectChangesAndCheck(fixture, 'init baz'); detectChangesAndExpectClassName('init bar baz foo');
cmp.objExpr = null;
detectChangesAndExpectClassName('init baz');
})); }));
}); });
}); });
@ -384,8 +339,13 @@ class TestComponent {
items: any[]; items: any[];
arrExpr: string[] = ['foo']; arrExpr: string[] = ['foo'];
setExpr: Set<string> = new Set<string>(); setExpr: Set<string> = new Set<string>();
objExpr = {'foo': true, 'bar': false}; objExpr: {[klass: string]: any} = {'foo': true, 'bar': false};
strExpr = 'foo'; strExpr = 'foo';
constructor() { this.setExpr.add('foo'); } constructor() { this.setExpr.add('foo'); }
} }
function createTestComponent(template: string): ComponentFixture<TestComponent> {
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
.createComponent(TestComponent);
}

View File

@ -8,150 +8,135 @@
import {CommonModule} from '@angular/common'; import {CommonModule} from '@angular/common';
import {Component, ContentChild, TemplateRef} from '@angular/core'; import {Component, ContentChild, TemplateRef} from '@angular/core';
import {TestBed, async} from '@angular/core/testing'; import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by'; import {By} from '@angular/platform-browser/src/dom/debug/by';
import {expect} from '@angular/platform-browser/testing/matchers'; import {expect} from '@angular/platform-browser/testing/matchers';
import {ListWrapper} from '../../src/facade/collection';
let thisArg: any; let thisArg: any;
export function main() { export function main() {
describe('ngFor', () => { describe('ngFor', () => {
const TEMPLATE = let fixture: ComponentFixture<any>;
'<div><span template="ngFor let item of items">{{item.toString()}};</span></div>';
function getComponent(): TestComponent { return fixture.componentInstance; }
function detectChangesAndExpectText(text: string): void {
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText(text);
}
afterEach(() => { fixture = null; });
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule( TestBed.configureTestingModule({
{declarations: [TestComponent, ComponentUsingTestComponent], imports: [CommonModule]}); declarations: [
TestComponent,
ComponentUsingTestComponent,
],
imports: [CommonModule],
});
}); });
it('should reflect initial elements', async(() => { it('should reflect initial elements', async(() => {
TestBed.overrideComponent(TestComponent, {set: {template: TEMPLATE}}); fixture = createTestComponent();
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges(); detectChangesAndExpectText('1;2;');
expect(fixture.debugElement.nativeElement).toHaveText('1;2;');
})); }));
it('should reflect added elements', async(() => { it('should reflect added elements', async(() => {
TestBed.overrideComponent(TestComponent, {set: {template: TEMPLATE}}); fixture = createTestComponent();
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges(); fixture.detectChanges();
getComponent().items.push(3);
(<number[]>fixture.debugElement.componentInstance.items).push(3); detectChangesAndExpectText('1;2;3;');
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;2;3;');
})); }));
it('should reflect removed elements', async(() => { it('should reflect removed elements', async(() => {
TestBed.overrideComponent(TestComponent, {set: {template: TEMPLATE}}); fixture = createTestComponent();
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges(); fixture.detectChanges();
getComponent().items.splice(1, 1);
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 1); detectChangesAndExpectText('1;');
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;');
})); }));
it('should reflect moved elements', async(() => { it('should reflect moved elements', async(() => {
TestBed.overrideComponent(TestComponent, {set: {template: TEMPLATE}}); fixture = createTestComponent();
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges(); fixture.detectChanges();
getComponent().items.splice(0, 1);
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 0); getComponent().items.push(1);
(<number[]>fixture.debugElement.componentInstance.items).push(1); detectChangesAndExpectText('2;1;');
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('2;1;');
})); }));
it('should reflect a mix of all changes (additions/removals/moves)', async(() => { it('should reflect a mix of all changes (additions/removals/moves)', async(() => {
TestBed.overrideComponent(TestComponent, {set: {template: TEMPLATE}}); fixture = createTestComponent();
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.items = [0, 1, 2, 3, 4, 5]; getComponent().items = [0, 1, 2, 3, 4, 5];
fixture.detectChanges(); fixture.detectChanges();
fixture.debugElement.componentInstance.items = [6, 2, 7, 0, 4, 8]; getComponent().items = [6, 2, 7, 0, 4, 8];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('6;2;7;0;4;8;'); detectChangesAndExpectText('6;2;7;0;4;8;');
})); }));
it('should iterate over an array of objects', async(() => { it('should iterate over an array of objects', async(() => {
const template = '<ul><li template="ngFor let item of items">{{item["name"]}};</li></ul>'; const template = '<ul><li template="ngFor let item of items">{{item["name"]}};</li></ul>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
// INIT // INIT
fixture.debugElement.componentInstance.items = [{'name': 'misko'}, {'name': 'shyam'}]; getComponent().items = [{'name': 'misko'}, {'name': 'shyam'}];
fixture.detectChanges(); detectChangesAndExpectText('misko;shyam;');
expect(fixture.debugElement.nativeElement).toHaveText('misko;shyam;');
// GROW // GROW
(<any[]>fixture.debugElement.componentInstance.items).push({'name': 'adam'}); getComponent().items.push({'name': 'adam'});
fixture.detectChanges(); detectChangesAndExpectText('misko;shyam;adam;');
expect(fixture.debugElement.nativeElement).toHaveText('misko;shyam;adam;');
// SHRINK // SHRINK
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 2); getComponent().items.splice(2, 1);
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 0); getComponent().items.splice(0, 1);
fixture.detectChanges(); detectChangesAndExpectText('shyam;');
expect(fixture.debugElement.nativeElement).toHaveText('shyam;');
})); }));
it('should gracefully handle nulls', async(() => { it('should gracefully handle nulls', async(() => {
const template = '<ul><li template="ngFor let item of null">{{item}};</li></ul>'; const template = '<ul><li template="ngFor let item of null">{{item}};</li></ul>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges(); detectChangesAndExpectText('');
expect(fixture.debugElement.nativeElement).toHaveText('');
})); }));
it('should gracefully handle ref changing to null and back', async(() => { it('should gracefully handle ref changing to null and back', async(() => {
TestBed.overrideComponent(TestComponent, {set: {template: TEMPLATE}}); fixture = createTestComponent();
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;2;');
fixture.debugElement.componentInstance.items = null; detectChangesAndExpectText('1;2;');
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('');
fixture.debugElement.componentInstance.items = [1, 2, 3]; getComponent().items = null;
fixture.detectChanges(); detectChangesAndExpectText('');
expect(fixture.debugElement.nativeElement).toHaveText('1;2;3;');
getComponent().items = [1, 2, 3];
detectChangesAndExpectText('1;2;3;');
})); }));
it('should throw on non-iterable ref and suggest using an array', async(() => { it('should throw on non-iterable ref and suggest using an array', async(() => {
TestBed.overrideComponent(TestComponent, {set: {template: TEMPLATE}}); fixture = createTestComponent();
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.items = 'whaaa'; getComponent().items = <any>'whaaa';
expect(() => fixture.detectChanges()) expect(() => fixture.detectChanges())
.toThrowError( .toThrowError(
/Cannot find a differ supporting object 'whaaa' of type 'string'. NgFor only supports binding to Iterables such as Arrays/); /Cannot find a differ supporting object 'whaaa' of type 'string'. NgFor only supports binding to Iterables such as Arrays/);
})); }));
it('should throw on ref changing to string', async(() => { it('should throw on ref changing to string', async(() => {
TestBed.overrideComponent(TestComponent, {set: {template: TEMPLATE}}); fixture = createTestComponent();
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;2;');
fixture.debugElement.componentInstance.items = 'whaaa'; detectChangesAndExpectText('1;2;');
getComponent().items = <any>'whaaa';
expect(() => fixture.detectChanges()).toThrowError(); expect(() => fixture.detectChanges()).toThrowError();
})); }));
it('should works with duplicates', async(() => { it('should works with duplicates', async(() => {
TestBed.overrideComponent(TestComponent, {set: {template: TEMPLATE}}); fixture = createTestComponent();
let fixture = TestBed.createComponent(TestComponent);
var a = new Foo(); const a = new Foo();
fixture.debugElement.componentInstance.items = [a, a]; getComponent().items = [a, a];
fixture.detectChanges(); detectChangesAndExpectText('foo;foo;');
expect(fixture.debugElement.nativeElement).toHaveText('foo;foo;');
})); }));
it('should repeat over nested arrays', async(() => { it('should repeat over nested arrays', async(() => {
@ -162,18 +147,13 @@ export function main() {
'</div>|' + '</div>|' +
'</div>' + '</div>' +
'</div>'; '</div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.items = [['a', 'b'], ['c']]; getComponent().items = [['a', 'b'], ['c']];
fixture.detectChanges(); detectChangesAndExpectText('a-2;b-2;|c-1;|');
fixture.detectChanges();
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('a-2;b-2;|c-1;|');
fixture.debugElement.componentInstance.items = [['e'], ['f', 'g']]; getComponent().items = [['e'], ['f', 'g']];
fixture.detectChanges(); detectChangesAndExpectText('e-1;|f-2;g-2;|');
expect(fixture.debugElement.nativeElement).toHaveText('e-1;|f-2;g-2;|');
})); }));
it('should repeat over nested arrays with no intermediate element', async(() => { it('should repeat over nested arrays with no intermediate element', async(() => {
@ -181,16 +161,13 @@ export function main() {
'<div template="ngFor let subitem of item">' + '<div template="ngFor let subitem of item">' +
'{{subitem}}-{{item.length}};' + '{{subitem}}-{{item.length}};' +
'</div></template></div>'; '</div></template></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.items = [['a', 'b'], ['c']]; getComponent().items = [['a', 'b'], ['c']];
fixture.detectChanges(); detectChangesAndExpectText('a-2;b-2;c-1;');
expect(fixture.debugElement.nativeElement).toHaveText('a-2;b-2;c-1;');
fixture.debugElement.componentInstance.items = [['e'], ['f', 'g']]; getComponent().items = [['e'], ['f', 'g']];
fixture.detectChanges(); detectChangesAndExpectText('e-1;f-2;g-2;');
expect(fixture.debugElement.nativeElement).toHaveText('e-1;f-2;g-2;');
})); }));
it('should repeat over nested ngIf that are the last node in the ngFor temlate', async(() => { it('should repeat over nested ngIf that are the last node in the ngFor temlate', async(() => {
@ -198,97 +175,77 @@ export function main() {
`<div><template ngFor let-item [ngForOf]="items" let-i="index"><div>{{i}}|</div>` + `<div><template ngFor let-item [ngForOf]="items" let-i="index"><div>{{i}}|</div>` +
`<div *ngIf="i % 2 == 0">even|</div></template></div>`; `<div *ngIf="i % 2 == 0">even|</div></template></div>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
const el = fixture.debugElement.nativeElement;
const items = [1]; const items = [1];
fixture.debugElement.componentInstance.items = items; getComponent().items = items;
fixture.detectChanges(); detectChangesAndExpectText('0|even|');
expect(el).toHaveText('0|even|');
items.push(1); items.push(1);
fixture.detectChanges(); detectChangesAndExpectText('0|even|1|');
expect(el).toHaveText('0|even|1|');
items.push(1); items.push(1);
fixture.detectChanges(); detectChangesAndExpectText('0|even|1|2|even|');
expect(el).toHaveText('0|even|1|2|even|');
})); }));
it('should display indices correctly', async(() => { it('should display indices correctly', async(() => {
const template = const template =
'<div><span template="ngFor: let item of items; let i=index">{{i.toString()}}</span></div>'; '<div><span template="ngFor: let item of items; let i=index">{{i.toString()}}</span></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; getComponent().items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
fixture.detectChanges(); detectChangesAndExpectText('0123456789');
expect(fixture.debugElement.nativeElement).toHaveText('0123456789');
fixture.debugElement.componentInstance.items = [1, 2, 6, 7, 4, 3, 5, 8, 9, 0]; getComponent().items = [1, 2, 6, 7, 4, 3, 5, 8, 9, 0];
fixture.detectChanges(); detectChangesAndExpectText('0123456789');
expect(fixture.debugElement.nativeElement).toHaveText('0123456789');
})); }));
it('should display first item correctly', async(() => { it('should display first item correctly', async(() => {
const template = const template =
'<div><span template="ngFor: let item of items; let isFirst=first">{{isFirst.toString()}}</span></div>'; '<div><span template="ngFor: let item of items; let isFirst=first">{{isFirst.toString()}}</span></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.items = [0, 1, 2]; getComponent().items = [0, 1, 2];
fixture.detectChanges(); detectChangesAndExpectText('truefalsefalse');
expect(fixture.debugElement.nativeElement).toHaveText('truefalsefalse');
fixture.debugElement.componentInstance.items = [2, 1]; getComponent().items = [2, 1];
fixture.detectChanges(); detectChangesAndExpectText('truefalse');
expect(fixture.debugElement.nativeElement).toHaveText('truefalse');
})); }));
it('should display last item correctly', async(() => { it('should display last item correctly', async(() => {
const template = const template =
'<div><span template="ngFor: let item of items; let isLast=last">{{isLast.toString()}}</span></div>'; '<div><span template="ngFor: let item of items; let isLast=last">{{isLast.toString()}}</span></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.items = [0, 1, 2]; getComponent().items = [0, 1, 2];
fixture.detectChanges(); detectChangesAndExpectText('falsefalsetrue');
expect(fixture.debugElement.nativeElement).toHaveText('falsefalsetrue');
fixture.debugElement.componentInstance.items = [2, 1]; getComponent().items = [2, 1];
fixture.detectChanges(); detectChangesAndExpectText('falsetrue');
expect(fixture.debugElement.nativeElement).toHaveText('falsetrue');
})); }));
it('should display even items correctly', async(() => { it('should display even items correctly', async(() => {
const template = const template =
'<div><span template="ngFor: let item of items; let isEven=even">{{isEven.toString()}}</span></div>'; '<div><span template="ngFor: let item of items; let isEven=even">{{isEven.toString()}}</span></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.items = [0, 1, 2]; getComponent().items = [0, 1, 2];
fixture.detectChanges(); detectChangesAndExpectText('truefalsetrue');
expect(fixture.debugElement.nativeElement).toHaveText('truefalsetrue');
fixture.debugElement.componentInstance.items = [2, 1]; getComponent().items = [2, 1];
fixture.detectChanges(); detectChangesAndExpectText('truefalse');
expect(fixture.debugElement.nativeElement).toHaveText('truefalse');
})); }));
it('should display odd items correctly', async(() => { it('should display odd items correctly', async(() => {
const template = const template =
'<div><span template="ngFor: let item of items; let isOdd=odd">{{isOdd.toString()}}</span></div>'; '<div><span template="ngFor: let item of items; let isOdd=odd">{{isOdd.toString()}}</span></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.items = [0, 1, 2, 3]; getComponent().items = [0, 1, 2, 3];
fixture.detectChanges(); detectChangesAndExpectText('falsetruefalsetrue');
expect(fixture.debugElement.nativeElement).toHaveText('falsetruefalsetrue');
fixture.debugElement.componentInstance.items = [2, 1]; getComponent().items = [2, 1];
fixture.detectChanges(); detectChangesAndExpectText('falsetrue');
expect(fixture.debugElement.nativeElement).toHaveText('falsetrue');
})); }));
it('should allow to use a custom template', async(() => { it('should allow to use a custom template', async(() => {
@ -298,7 +255,7 @@ export function main() {
const cutTemplate = const cutTemplate =
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>'; '<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>';
TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}}); TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}});
let fixture = TestBed.createComponent(ComponentUsingTestComponent); fixture = TestBed.createComponent(ComponentUsingTestComponent);
const testComponent = fixture.debugElement.children[0]; const testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c']; testComponent.componentInstance.items = ['a', 'b', 'c'];
@ -313,7 +270,7 @@ export function main() {
const cutTemplate = const cutTemplate =
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>'; '<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>';
TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}}); TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}});
let fixture = TestBed.createComponent(ComponentUsingTestComponent); fixture = TestBed.createComponent(ComponentUsingTestComponent);
const testComponent = fixture.debugElement.children[0]; const testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c']; testComponent.componentInstance.items = ['a', 'b', 'c'];
@ -328,7 +285,7 @@ export function main() {
const cutTemplate = const cutTemplate =
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>'; '<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>';
TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}}); TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}});
let fixture = TestBed.createComponent(ComponentUsingTestComponent); fixture = TestBed.createComponent(ComponentUsingTestComponent);
const testComponent = fixture.debugElement.children[0]; const testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c']; testComponent.componentInstance.items = ['a', 'b', 'c'];
@ -340,12 +297,11 @@ export function main() {
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>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
thisArg = null; thisArg = null;
fixture.detectChanges(); fixture.detectChanges();
expect(thisArg).toBe(fixture.debugElement.componentInstance); expect(thisArg).toBe(getComponent());
})); }));
it('should not replace tracked items', async(() => { it('should not replace tracked items', async(() => {
@ -353,62 +309,53 @@ export function main() {
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById" let-i="index"> `<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById" let-i="index">
<p>{{items[i]}}</p> <p>{{items[i]}}</p>
</template>`; </template>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
var buildItemList = () => { const buildItemList = () => {
fixture.debugElement.componentInstance.items = [{'id': 'a'}]; getComponent().items = [{'id': 'a'}];
fixture.detectChanges(); fixture.detectChanges();
return fixture.debugElement.queryAll(By.css('p'))[0]; return fixture.debugElement.queryAll(By.css('p'))[0];
}; };
var firstP = buildItemList(); const firstP = buildItemList();
var finalP = buildItemList(); const finalP = buildItemList();
expect(finalP.nativeElement).toBe(firstP.nativeElement); expect(finalP.nativeElement).toBe(firstP.nativeElement);
})); }));
it('should update implicit local variable on view', async(() => { it('should update implicit local variable on view', 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>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.items = [{'id': 'a', 'color': 'blue'}]; getComponent().items = [{'id': 'a', 'color': 'blue'}];
fixture.detectChanges(); detectChangesAndExpectText('blue');
expect(fixture.debugElement.nativeElement).toHaveText('blue');
fixture.debugElement.componentInstance.items = [{'id': 'a', 'color': 'red'}]; getComponent().items = [{'id': 'a', 'color': 'red'}];
fixture.detectChanges(); detectChangesAndExpectText('red');
expect(fixture.debugElement.nativeElement).toHaveText('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>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.items = getComponent().items = [{'id': 'a', 'color': 'blue'}, {'id': 'b', 'color': 'yellow'}];
[{'id': 'a', 'color': 'blue'}, {'id': 'b', 'color': 'yellow'}]; detectChangesAndExpectText('blueyellow');
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('blueyellow'); getComponent().items = [{'id': 'b', 'color': 'orange'}, {'id': 'a', 'color': 'red'}];
fixture.debugElement.componentInstance.items = detectChangesAndExpectText('orangered');
[{'id': 'b', 'color': 'orange'}, {'id': 'a', 'color': 'red'}];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('orangered');
})); }));
it('should handle added and removed items properly when tracking by index', async(() => { it('should handle added and removed items properly when tracking by index', async(() => {
const template = const template =
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByIndex">{{item}}</template></div>`; `<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByIndex">{{item}}</template></div>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.items = ['a', 'b', 'c', 'd']; getComponent().items = ['a', 'b', 'c', 'd'];
fixture.detectChanges(); fixture.detectChanges();
fixture.debugElement.componentInstance.items = ['e', 'f', 'g', 'h']; getComponent().items = ['e', 'f', 'g', 'h'];
fixture.detectChanges(); fixture.detectChanges();
fixture.debugElement.componentInstance.items = ['e', 'f', 'h']; getComponent().items = ['e', 'f', 'h'];
fixture.detectChanges(); detectChangesAndExpectText('efh');
expect(fixture.debugElement.nativeElement).toHaveText('efh');
})); }));
}); });
}); });
@ -421,8 +368,7 @@ 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>;
items: any; items: any[] = [1, 2];
constructor() { this.items = [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; }
trackByContext(): void { thisArg = this; } trackByContext(): void { thisArg = this; }
@ -430,6 +376,12 @@ class TestComponent {
@Component({selector: 'outer-cmp', template: ''}) @Component({selector: 'outer-cmp', template: ''})
class ComponentUsingTestComponent { class ComponentUsingTestComponent {
items: any; items: any = [1, 2];
constructor() { this.items = [1, 2]; } }
const TEMPLATE = '<div><span template="ngFor let item of items">{{item.toString()}};</span></div>';
function createTestComponent(template: string = TEMPLATE): ComponentFixture<TestComponent> {
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
.createComponent(TestComponent);
} }

View File

@ -8,99 +8,94 @@
import {CommonModule} from '@angular/common'; import {CommonModule} from '@angular/common';
import {Component} from '@angular/core'; import {Component} from '@angular/core';
import {TestBed, async} from '@angular/core/testing'; import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {expect} from '@angular/platform-browser/testing/matchers'; import {expect} from '@angular/platform-browser/testing/matchers';
export function main() { export function main() {
describe('ngIf directive', () => { describe('ngIf directive', () => {
let fixture: ComponentFixture<any>;
function getComponent(): TestComponent { return fixture.componentInstance; }
afterEach(() => { fixture = null; });
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({declarations: [TestComponent], imports: [CommonModule]}); TestBed.configureTestingModule({
declarations: [TestComponent],
imports: [CommonModule],
});
}); });
it('should work in a template attribute', async(() => { it('should work in a template attribute', async(() => {
const template = '<div><span template="ngIf booleanCondition">hello</span></div>'; const template = '<div><span template="ngIf booleanCondition">hello</span></div>';
fixture = createTestComponent(template);
TestBed.overrideComponent(TestComponent, {set: {template: template}});
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'span').length) expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
.toEqual(1); expect(fixture.nativeElement).toHaveText('hello');
expect(fixture.debugElement.nativeElement).toHaveText('hello');
})); }));
it('should work in a template element', async(() => { it('should work in a template element', async(() => {
const template = const template =
'<div><template [ngIf]="booleanCondition"><span>hello2</span></template></div>'; '<div><template [ngIf]="booleanCondition"><span>hello2</span></template></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'span').length) expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
.toEqual(1); expect(fixture.nativeElement).toHaveText('hello2');
expect(fixture.debugElement.nativeElement).toHaveText('hello2');
})); }));
it('should toggle node when condition changes', async(() => { it('should toggle node when condition changes', async(() => {
const template = '<div><span template="ngIf booleanCondition">hello</span></div>'; const template = '<div><span template="ngIf booleanCondition">hello</span></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent); getComponent().booleanCondition = false;
fixture.debugElement.componentInstance.booleanCondition = false;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'span').length) expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0);
.toEqual(0); expect(fixture.nativeElement).toHaveText('');
expect(fixture.debugElement.nativeElement).toHaveText('');
fixture.debugElement.componentInstance.booleanCondition = true; getComponent().booleanCondition = true;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'span').length) expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
.toEqual(1); expect(fixture.nativeElement).toHaveText('hello');
expect(fixture.debugElement.nativeElement).toHaveText('hello');
fixture.debugElement.componentInstance.booleanCondition = false; getComponent().booleanCondition = false;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'span').length) expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0);
.toEqual(0); expect(fixture.nativeElement).toHaveText('');
expect(fixture.debugElement.nativeElement).toHaveText('');
})); }));
it('should handle nested if correctly', async(() => { it('should handle nested if correctly', async(() => {
const template = const template =
'<div><template [ngIf]="booleanCondition"><span *ngIf="nestedBooleanCondition">hello</span></template></div>'; '<div><template [ngIf]="booleanCondition"><span *ngIf="nestedBooleanCondition">hello</span></template></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.booleanCondition = false;
fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'span').length)
.toEqual(0);
expect(fixture.debugElement.nativeElement).toHaveText('');
fixture.debugElement.componentInstance.booleanCondition = true; getComponent().booleanCondition = false;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'span').length) expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0);
.toEqual(1); expect(fixture.nativeElement).toHaveText('');
expect(fixture.debugElement.nativeElement).toHaveText('hello');
fixture.debugElement.componentInstance.nestedBooleanCondition = false; getComponent().booleanCondition = true;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'span').length) expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
.toEqual(0); expect(fixture.nativeElement).toHaveText('hello');
expect(fixture.debugElement.nativeElement).toHaveText('');
fixture.debugElement.componentInstance.nestedBooleanCondition = true; getComponent().nestedBooleanCondition = false;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'span').length) expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0);
.toEqual(1); expect(fixture.nativeElement).toHaveText('');
expect(fixture.debugElement.nativeElement).toHaveText('hello');
fixture.debugElement.componentInstance.booleanCondition = false; getComponent().nestedBooleanCondition = true;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'span').length) expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
.toEqual(0); expect(fixture.nativeElement).toHaveText('hello');
expect(fixture.debugElement.nativeElement).toHaveText('');
getComponent().booleanCondition = false;
fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0);
expect(fixture.nativeElement).toHaveText('');
})); }));
it('should update several nodes with if', async(() => { it('should update several nodes with if', async(() => {
@ -110,59 +105,52 @@ export function main() {
'<span template="ngIf functionCondition(stringCondition, numberCondition)">helloFunction</span>' + '<span template="ngIf functionCondition(stringCondition, numberCondition)">helloFunction</span>' +
'</div>'; '</div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'span').length) expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(3);
.toEqual(3); expect(getDOM().getText(fixture.nativeElement))
expect(getDOM().getText(fixture.debugElement.nativeElement))
.toEqual('helloNumberhelloStringhelloFunction'); .toEqual('helloNumberhelloStringhelloFunction');
fixture.debugElement.componentInstance.numberCondition = 0; getComponent().numberCondition = 0;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'span').length) expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
.toEqual(1); expect(fixture.nativeElement).toHaveText('helloString');
expect(fixture.debugElement.nativeElement).toHaveText('helloString');
fixture.debugElement.componentInstance.numberCondition = 1; getComponent().numberCondition = 1;
fixture.debugElement.componentInstance.stringCondition = 'bar'; getComponent().stringCondition = 'bar';
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'span').length) expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
.toEqual(1); expect(fixture.nativeElement).toHaveText('helloNumber');
expect(fixture.debugElement.nativeElement).toHaveText('helloNumber');
})); }));
it('should not add the element twice if the condition goes from true to true (JS)', it('should not add the element twice if the condition goes from true to true (JS)',
async(() => { async(() => {
const template = '<div><span template="ngIf numberCondition">hello</span></div>'; const template = '<div><span template="ngIf numberCondition">hello</span></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'span').length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('hello');
fixture.debugElement.componentInstance.numberCondition = 2;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'span').length) expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
.toEqual(1); expect(fixture.nativeElement).toHaveText('hello');
expect(fixture.debugElement.nativeElement).toHaveText('hello');
getComponent().numberCondition = 2;
fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
expect(fixture.nativeElement).toHaveText('hello');
})); }));
it('should not recreate the element if the condition goes from true to true (JS)', async(() => { it('should not recreate the element if the condition goes from true to true (JS)', async(() => {
const template = '<div><span template="ngIf numberCondition">hello</span></div>'; const template = '<div><span template="ngIf numberCondition">hello</span></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
getDOM().addClass(
getDOM().querySelector(fixture.debugElement.nativeElement, 'span'), 'foo');
fixture.debugElement.componentInstance.numberCondition = 2;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().hasClass( getDOM().addClass(getDOM().querySelector(fixture.nativeElement, 'span'), 'foo');
getDOM().querySelector(fixture.debugElement.nativeElement, 'span'), 'foo'))
getComponent().numberCondition = 2;
fixture.detectChanges();
expect(getDOM().hasClass(getDOM().querySelector(fixture.nativeElement, 'span'), 'foo'))
.toBe(true); .toBe(true);
})); }));
}); });
@ -170,16 +158,14 @@ export function main() {
@Component({selector: 'test-cmp', template: ''}) @Component({selector: 'test-cmp', template: ''})
class TestComponent { class TestComponent {
booleanCondition: boolean; booleanCondition: boolean = true;
nestedBooleanCondition: boolean; nestedBooleanCondition: boolean = true;
numberCondition: number; numberCondition: number = 1;
stringCondition: string; stringCondition: string = 'foo';
functionCondition: Function; functionCondition: Function = (s: any, n: any): boolean => s == 'foo' && n == 1;
constructor() { }
this.booleanCondition = true;
this.nestedBooleanCondition = true; function createTestComponent(template: string): ComponentFixture<TestComponent> {
this.numberCondition = 1; return TestBed.overrideComponent(TestComponent, {set: {template: template}})
this.stringCondition = 'foo'; .createComponent(TestComponent);
this.functionCondition = function(s: any, n: any): boolean { return s == 'foo' && n == 1; };
}
} }

View File

@ -8,11 +8,21 @@
import {CommonModule, NgLocalization} from '@angular/common'; import {CommonModule, NgLocalization} from '@angular/common';
import {Component, Injectable} from '@angular/core'; import {Component, Injectable} from '@angular/core';
import {TestBed, async} 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() {
describe('switch', () => { describe('switch', () => {
let fixture: ComponentFixture<any>;
function getComponent(): TestComponent { return fixture.componentInstance; }
function detectChangesAndExpectText<T>(text: string): void {
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText(text);
}
afterEach(() => { fixture = null; });
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@ -23,104 +33,95 @@ export function main() {
}); });
it('should display the template according to the exact value', async(() => { it('should display the template according to the exact value', async(() => {
var template = '<div>' + const template = '<div>' +
'<ul [ngPlural]="switchValue">' + '<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="=0"><li>you have no messages.</li></template>' + '<template ngPluralCase="=0"><li>you have no messages.</li></template>' +
'<template ngPluralCase="=1"><li>you have one message.</li></template>' + '<template ngPluralCase="=1"><li>you have one message.</li></template>' +
'</ul></div>'; '</ul></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.switchValue = 0;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('you have no messages.');
fixture.debugElement.componentInstance.switchValue = 1; getComponent().switchValue = 0;
fixture.detectChanges(); detectChangesAndExpectText('you have no messages.');
expect(fixture.debugElement.nativeElement).toHaveText('you have one message.');
getComponent().switchValue = 1;
detectChangesAndExpectText('you have one message.');
})); }));
// https://github.com/angular/angular/issues/9868 // https://github.com/angular/angular/issues/9868
// https://github.com/angular/angular/issues/9882 // https://github.com/angular/angular/issues/9882
it('should not throw when ngPluralCase contains expressions', async(() => { it('should not throw when ngPluralCase contains expressions', async(() => {
var template = '<div>' + const template = '<div>' +
'<ul [ngPlural]="switchValue">' + '<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="=0"><li>{{ switchValue }}</li></template>' + '<template ngPluralCase="=0"><li>{{ switchValue }}</li></template>' +
'</ul></div>'; '</ul></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.switchValue = 0; getComponent().switchValue = 0;
expect(() => fixture.detectChanges()).not.toThrow(); expect(() => fixture.detectChanges()).not.toThrow();
})); }));
it('should be applicable to <ng-container> elements', async(() => { it('should be applicable to <ng-container> elements', async(() => {
var template = '<div>' + const template = '<div>' +
'<ng-container [ngPlural]="switchValue">' + '<ng-container [ngPlural]="switchValue">' +
'<template ngPluralCase="=0">you have no messages.</template>' + '<template ngPluralCase="=0">you have no messages.</template>' +
'<template ngPluralCase="=1">you have one message.</template>' + '<template ngPluralCase="=1">you have one message.</template>' +
'</ng-container></div>'; '</ng-container></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.switchValue = 0;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('you have no messages.');
fixture.debugElement.componentInstance.switchValue = 1; getComponent().switchValue = 0;
fixture.detectChanges(); detectChangesAndExpectText('you have no messages.');
expect(fixture.debugElement.nativeElement).toHaveText('you have one message.');
getComponent().switchValue = 1;
detectChangesAndExpectText('you have one message.');
})); }));
it('should display the template according to the category', async(() => { it('should display the template according to the category', async(() => {
var template = '<div>' + const template = '<div>' +
'<ul [ngPlural]="switchValue">' + '<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' + '<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="many"><li>you have many messages.</li></template>' + '<template ngPluralCase="many"><li>you have many messages.</li></template>' +
'</ul></div>'; '</ul></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.switchValue = 2;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('you have a few messages.');
fixture.debugElement.componentInstance.switchValue = 8; getComponent().switchValue = 2;
fixture.detectChanges(); detectChangesAndExpectText('you have a few messages.');
expect(fixture.debugElement.nativeElement).toHaveText('you have many messages.');
getComponent().switchValue = 8;
detectChangesAndExpectText('you have many messages.');
})); }));
it('should default to other when no matches are found', async(() => { it('should default to other when no matches are found', async(() => {
var template = '<div>' + const template = '<div>' +
'<ul [ngPlural]="switchValue">' + '<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' + '<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="other"><li>default message.</li></template>' + '<template ngPluralCase="other"><li>default message.</li></template>' +
'</ul></div>'; '</ul></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.switchValue = 100; getComponent().switchValue = 100;
fixture.detectChanges(); detectChangesAndExpectText('default message.');
expect(fixture.debugElement.nativeElement).toHaveText('default message.');
})); }));
it('should prioritize value matches over category matches', async(() => { it('should prioritize value matches over category matches', async(() => {
var template = '<div>' + const template = '<div>' +
'<ul [ngPlural]="switchValue">' + '<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' + '<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="=2">you have two messages.</template>' + '<template ngPluralCase="=2">you have two messages.</template>' +
'</ul></div>'; '</ul></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.switchValue = 2;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('you have two messages.');
fixture.debugElement.componentInstance.switchValue = 3; getComponent().switchValue = 2;
fixture.detectChanges(); detectChangesAndExpectText('you have two messages.');
expect(fixture.debugElement.nativeElement).toHaveText('you have a few messages.');
getComponent().switchValue = 3;
detectChangesAndExpectText('you have a few messages.');
})); }));
}); });
} }
@ -131,6 +132,7 @@ class TestLocalization extends NgLocalization {
if (value > 1 && value < 4) { if (value > 1 && value < 4) {
return 'few'; return 'few';
} }
if (value >= 4 && value < 10) { if (value >= 4 && value < 10) {
return 'many'; return 'many';
} }
@ -143,3 +145,8 @@ class TestLocalization extends NgLocalization {
class TestComponent { class TestComponent {
switchValue: number = null; switchValue: number = null;
} }
function createTestComponent(template: string): ComponentFixture<TestComponent> {
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
.createComponent(TestComponent);
}

View File

@ -10,69 +10,70 @@ import {CommonModule} from '@angular/common';
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';
function expectNativeEl(fixture: ComponentFixture<any>) {
return <any>expect(fixture.debugElement.children[0].nativeElement);
}
export function main() { export function main() {
describe('binding to CSS styles', () => { describe('NgStyle', () => {
let fixture: ComponentFixture<any>;
function getComponent(): TestComponent { return fixture.componentInstance; }
function expectNativeEl(fixture: ComponentFixture<any>): any {
return expect(fixture.debugElement.children[0].nativeElement);
}
afterEach(() => { fixture = null; });
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({declarations: [TestComponent], imports: [CommonModule]}); TestBed.configureTestingModule({declarations: [TestComponent], imports: [CommonModule]});
}); });
it('should add styles specified in an object literal', async(() => { it('should add styles specified in an object literal', async(() => {
var template = `<div [ngStyle]="{'max-width': '40px'}"></div>`; const template = `<div [ngStyle]="{'max-width': '40px'}"></div>`;
fixture = createTestComponent(template);
TestBed.overrideComponent(TestComponent, {set: {template: template}});
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges(); fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'}); expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
})); }));
it('should add and change styles specified in an object expression', async(() => { it('should add and change styles specified in an object expression', async(() => {
var template = `<div [ngStyle]="expr"></div>`; const template = `<div [ngStyle]="expr"></div>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent); let expr: {[k: string]: string};
var expr: Map<string, any>;
fixture.debugElement.componentInstance.expr = {'max-width': '40px'}; getComponent().expr = {'max-width': '40px'};
fixture.detectChanges(); fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'}); expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
expr = fixture.debugElement.componentInstance.expr; expr = getComponent().expr;
(expr as any)['max-width'] = '30%'; expr['max-width'] = '30%';
fixture.detectChanges(); fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '30%'}); expectNativeEl(fixture).toHaveCssStyle({'max-width': '30%'});
})); }));
it('should add and remove styles specified using style.unit notation', async(() => { it('should add and remove styles specified using style.unit notation', async(() => {
var template = `<div [ngStyle]="{'max-width.px': expr}"></div>`; const template = `<div [ngStyle]="{'max-width.px': expr}"></div>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.expr = '40'; getComponent().expr = '40';
fixture.detectChanges(); fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'}); expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
fixture.debugElement.componentInstance.expr = null; getComponent().expr = null;
fixture.detectChanges(); fixture.detectChanges();
expectNativeEl(fixture).not.toHaveCssStyle('max-width'); expectNativeEl(fixture).not.toHaveCssStyle('max-width');
})); }));
it('should update styles using style.unit notation when unit changes', async(() => { it('should update styles using style.unit notation when unit changes', async(() => {
var template = `<div [ngStyle]="expr"></div>`; const template = `<div [ngStyle]="expr"></div>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.expr = {'max-width.px': '40'}; getComponent().expr = {'max-width.px': '40'};
fixture.detectChanges(); fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'}); expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
fixture.debugElement.componentInstance.expr = {'max-width.em': '40'}; getComponent().expr = {'max-width.em': '40'};
fixture.detectChanges(); fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40em'}); expectNativeEl(fixture).toHaveCssStyle({'max-width': '40em'});
})); }));
@ -81,9 +82,9 @@ export function main() {
it('should change styles specified in an object expression', async(() => { it('should change styles specified in an object expression', async(() => {
const template = `<div [ngStyle]="expr"></div>`; const template = `<div [ngStyle]="expr"></div>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.expr = { getComponent().expr = {
// height, width order is important here // height, width order is important here
height: '10px', height: '10px',
width: '10px' width: '10px'
@ -92,7 +93,7 @@ export function main() {
fixture.detectChanges(); fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'height': '10px', 'width': '10px'}); expectNativeEl(fixture).toHaveCssStyle({'height': '10px', 'width': '10px'});
fixture.debugElement.componentInstance.expr = { getComponent().expr = {
// width, height order is important here // width, height order is important here
width: '5px', width: '5px',
height: '5px', height: '5px',
@ -103,29 +104,29 @@ export function main() {
})); }));
it('should remove styles when deleting a key in an object expression', async(() => { it('should remove styles when deleting a key in an object expression', async(() => {
var template = `<div [ngStyle]="expr"></div>`; const template = `<div [ngStyle]="expr"></div>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.expr = {'max-width': '40px'}; getComponent().expr = {'max-width': '40px'};
fixture.detectChanges(); fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'}); expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
delete fixture.debugElement.componentInstance.expr['max-width']; delete getComponent().expr['max-width'];
fixture.detectChanges(); fixture.detectChanges();
expectNativeEl(fixture).not.toHaveCssStyle('max-width'); expectNativeEl(fixture).not.toHaveCssStyle('max-width');
})); }));
it('should co-operate with the style attribute', async(() => { it('should co-operate with the style attribute', async(() => {
var template = `<div style="font-size: 12px" [ngStyle]="expr"></div>`; const template = `<div style="font-size: 12px" [ngStyle]="expr"></div>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.expr = {'max-width': '40px'}; getComponent().expr = {'max-width': '40px'};
fixture.detectChanges(); fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px', 'font-size': '12px'}); expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px', 'font-size': '12px'});
delete fixture.debugElement.componentInstance.expr['max-width']; delete getComponent().expr['max-width'];
fixture.detectChanges(); fixture.detectChanges();
expectNativeEl(fixture).not.toHaveCssStyle('max-width'); expectNativeEl(fixture).not.toHaveCssStyle('max-width');
expectNativeEl(fixture).toHaveCssStyle({'font-size': '12px'}); expectNativeEl(fixture).toHaveCssStyle({'font-size': '12px'});
@ -133,15 +134,15 @@ export function main() {
it('should co-operate with the style.[styleName]="expr" special-case in the compiler', it('should co-operate with the style.[styleName]="expr" special-case in the compiler',
async(() => { async(() => {
var template = `<div [style.font-size.px]="12" [ngStyle]="expr"></div>`; const template = `<div [style.font-size.px]="12" [ngStyle]="expr"></div>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.debugElement.componentInstance.expr = {'max-width': '40px'}; getComponent().expr = {'max-width': '40px'};
fixture.detectChanges(); fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px', 'font-size': '12px'}); expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px', 'font-size': '12px'});
delete fixture.debugElement.componentInstance.expr['max-width']; delete getComponent().expr['max-width'];
fixture.detectChanges(); fixture.detectChanges();
expectNativeEl(fixture).not.toHaveCssStyle('max-width'); expectNativeEl(fixture).not.toHaveCssStyle('max-width');
expectNativeEl(fixture).toHaveCssStyle({'font-size': '12px'}); expectNativeEl(fixture).toHaveCssStyle({'font-size': '12px'});
@ -153,3 +154,8 @@ export function main() {
class TestComponent { class TestComponent {
expr: any; expr: any;
} }
function createTestComponent(template: string): ComponentFixture<TestComponent> {
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
.createComponent(TestComponent);
}

View File

@ -8,83 +8,86 @@
import {CommonModule} from '@angular/common'; import {CommonModule} from '@angular/common';
import {Component} from '@angular/core'; import {Component} from '@angular/core';
import {TestBed, async} 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() {
describe('switch', () => { describe('NgSwitch', () => {
let fixture: ComponentFixture<any>;
function getComponent(): TestComponent { return fixture.componentInstance; }
function detectChangesAndExpectText(text: string): void {
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText(text);
}
afterEach(() => { fixture = null; });
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({declarations: [TestComponent], imports: [CommonModule]}); TestBed.configureTestingModule({
declarations: [TestComponent],
imports: [CommonModule],
});
}); });
describe('switch value changes', () => { describe('switch value changes', () => {
it('should switch amongst when values', async(() => { it('should switch amongst when values', async(() => {
var 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>' +
'<template ngSwitchCase="b"><li>when b</li></template>' + '<template ngSwitchCase="b"><li>when b</li></template>' +
'</ul></div>'; '</ul></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('');
fixture.debugElement.componentInstance.switchValue = 'a'; detectChangesAndExpectText('');
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when a');
fixture.debugElement.componentInstance.switchValue = 'b'; getComponent().switchValue = 'a';
fixture.detectChanges(); detectChangesAndExpectText('when a');
expect(fixture.debugElement.nativeElement).toHaveText('when b');
getComponent().switchValue = 'b';
detectChangesAndExpectText('when b');
})); }));
// TODO(robwormald): deprecate and remove // TODO(robwormald): deprecate and remove
it('should switch amongst when values using switchCase', async(() => { it('should switch amongst when values using switchCase', async(() => {
var 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>' +
'<template ngSwitchCase="b"><li>when b</li></template>' + '<template ngSwitchCase="b"><li>when b</li></template>' +
'</ul></div>'; '</ul></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('');
fixture.debugElement.componentInstance.switchValue = 'a'; detectChangesAndExpectText('');
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when a');
fixture.debugElement.componentInstance.switchValue = 'b'; getComponent().switchValue = 'a';
fixture.detectChanges(); detectChangesAndExpectText('when a');
expect(fixture.debugElement.nativeElement).toHaveText('when b');
getComponent().switchValue = 'b';
detectChangesAndExpectText('when b');
})); }));
it('should switch amongst when values with fallback to default', async(() => { it('should switch amongst when values with fallback to default', async(() => {
var 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>' +
'<li template="ngSwitchDefault">when default</li>' + '<li template="ngSwitchDefault">when default</li>' +
'</ul></div>'; '</ul></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent); detectChangesAndExpectText('when default');
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when default');
fixture.debugElement.componentInstance.switchValue = 'a'; getComponent().switchValue = 'a';
fixture.detectChanges(); detectChangesAndExpectText('when a');
expect(fixture.debugElement.nativeElement).toHaveText('when a');
fixture.debugElement.componentInstance.switchValue = 'b'; getComponent().switchValue = 'b';
fixture.detectChanges(); detectChangesAndExpectText('when default');
expect(fixture.debugElement.nativeElement).toHaveText('when default');
})); }));
it('should support multiple whens with the same value', async(() => { it('should support multiple whens with the same value', async(() => {
var 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>' +
'<template ngSwitchCase="b"><li>when b1;</li></template>' + '<template ngSwitchCase="b"><li>when b1;</li></template>' +
@ -94,53 +97,43 @@ export function main() {
'<template ngSwitchDefault><li>when default2;</li></template>' + '<template ngSwitchDefault><li>when default2;</li></template>' +
'</ul></div>'; '</ul></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent); detectChangesAndExpectText('when default1;when default2;');
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when default1;when default2;');
fixture.debugElement.componentInstance.switchValue = 'a'; getComponent().switchValue = 'a';
fixture.detectChanges(); detectChangesAndExpectText('when a1;when a2;');
expect(fixture.debugElement.nativeElement).toHaveText('when a1;when a2;');
fixture.debugElement.componentInstance.switchValue = 'b'; getComponent().switchValue = 'b';
fixture.detectChanges(); detectChangesAndExpectText('when b1;when b2;');
expect(fixture.debugElement.nativeElement).toHaveText('when b1;when b2;');
})); }));
}); });
describe('when values changes', () => { describe('when values changes', () => {
it('should switch amongst when values', async(() => { it('should switch amongst when values', async(() => {
var 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>' +
'<template [ngSwitchCase]="when2"><li>when 2;</li></template>' + '<template [ngSwitchCase]="when2"><li>when 2;</li></template>' +
'<template ngSwitchDefault><li>when default;</li></template>' + '<template ngSwitchDefault><li>when default;</li></template>' +
'</ul></div>'; '</ul></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent); getComponent().when1 = 'a';
fixture.debugElement.componentInstance.when1 = 'a'; getComponent().when2 = 'b';
fixture.debugElement.componentInstance.when2 = 'b'; getComponent().switchValue = 'a';
fixture.debugElement.componentInstance.switchValue = 'a'; detectChangesAndExpectText('when 1;');
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when 1;');
fixture.debugElement.componentInstance.switchValue = 'b'; getComponent().switchValue = 'b';
fixture.detectChanges(); detectChangesAndExpectText('when 2;');
expect(fixture.debugElement.nativeElement).toHaveText('when 2;');
fixture.debugElement.componentInstance.switchValue = 'c'; getComponent().switchValue = 'c';
fixture.detectChanges(); detectChangesAndExpectText('when default;');
expect(fixture.debugElement.nativeElement).toHaveText('when default;');
fixture.debugElement.componentInstance.when1 = 'c'; getComponent().when1 = 'c';
fixture.detectChanges(); detectChangesAndExpectText('when 1;');
expect(fixture.debugElement.nativeElement).toHaveText('when 1;');
fixture.debugElement.componentInstance.when1 = 'd'; getComponent().when1 = 'd';
fixture.detectChanges(); detectChangesAndExpectText('when default;');
expect(fixture.debugElement.nativeElement).toHaveText('when default;');
})); }));
}); });
}); });
@ -148,13 +141,12 @@ export function main() {
@Component({selector: 'test-cmp', template: ''}) @Component({selector: 'test-cmp', template: ''})
class TestComponent { class TestComponent {
switchValue: any; switchValue: any = null;
when1: any; when1: any = null;
when2: any; when2: any = null;
}
constructor() {
this.switchValue = null; function createTestComponent(template: string): ComponentFixture<TestComponent> {
this.when1 = null; return TestBed.overrideComponent(TestComponent, {set: {template: template}})
this.when2 = null; .createComponent(TestComponent);
}
} }

View File

@ -8,155 +8,140 @@
import {CommonModule} from '@angular/common'; import {CommonModule} from '@angular/common';
import {Component, ContentChildren, Directive, NO_ERRORS_SCHEMA, QueryList, TemplateRef} from '@angular/core'; import {Component, ContentChildren, Directive, NO_ERRORS_SCHEMA, QueryList, TemplateRef} from '@angular/core';
import {TestBed, async} 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() {
describe('insert', () => { describe('NgTemplateOutlet', () => {
let fixture: ComponentFixture<any>;
function setTplRef(value: any): void { fixture.componentInstance.currentTplRef = value; }
function detectChangesAndExpectText(text: string): void {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText(text);
}
afterEach(() => { fixture = null; });
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule( TestBed.configureTestingModule({
{declarations: [TestComponent, CaptureTplRefs], imports: [CommonModule]}); declarations: [
TestComponent,
CaptureTplRefs,
],
imports: [CommonModule],
});
}); });
it('should do nothing if templateRef is null', async(() => { it('should do nothing if templateRef is null', async(() => {
const template = `<template [ngTemplateOutlet]="null"></template>`; const template = `<template [ngTemplateOutlet]="null"></template>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}}); fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges(); detectChangesAndExpectText('');
expect(fixture.nativeElement).toHaveText('');
})); }));
it('should insert content specified by TemplateRef', async(() => { it('should insert content specified by TemplateRef', async(() => {
const template = const template =
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`; `<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}}) fixture = createTestComponent(template);
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
let fixture = TestBed.createComponent(TestComponent); detectChangesAndExpectText('');
fixture.detectChanges(); const refs = fixture.debugElement.children[0].references['refs'];
expect(fixture.nativeElement).toHaveText('');
var refs = fixture.debugElement.children[0].references['refs']; setTplRef(refs.tplRefs.first);
detectChangesAndExpectText('foo');
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
})); }));
it('should clear content if TemplateRef becomes null', async(() => { it('should clear content if TemplateRef becomes null', async(() => {
const template = const template =
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`; `<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}}) fixture = createTestComponent(template);
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges(); fixture.detectChanges();
var refs = fixture.debugElement.children[0].references['refs']; const refs = fixture.debugElement.children[0].references['refs'];
fixture.componentInstance.currentTplRef = refs.tplRefs.first; setTplRef(refs.tplRefs.first);
fixture.detectChanges(); detectChangesAndExpectText('foo');
expect(fixture.nativeElement).toHaveText('foo');
fixture.componentInstance.currentTplRef = null; setTplRef(null);
fixture.detectChanges(); detectChangesAndExpectText('');
expect(fixture.nativeElement).toHaveText('');
})); }));
it('should swap content if TemplateRef changes', async(() => { it('should swap content if TemplateRef changes', async(() => {
const template = const template =
`<tpl-refs #refs="tplRefs"><template>foo</template><template>bar</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`; `<tpl-refs #refs="tplRefs"><template>foo</template><template>bar</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}}) fixture = createTestComponent(template);
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges(); fixture.detectChanges();
var refs = fixture.debugElement.children[0].references['refs']; const refs = fixture.debugElement.children[0].references['refs'];
fixture.componentInstance.currentTplRef = refs.tplRefs.first; setTplRef(refs.tplRefs.first);
fixture.detectChanges(); detectChangesAndExpectText('foo');
expect(fixture.nativeElement).toHaveText('foo');
fixture.componentInstance.currentTplRef = refs.tplRefs.last; setTplRef(refs.tplRefs.last);
fixture.detectChanges(); detectChangesAndExpectText('bar');
expect(fixture.nativeElement).toHaveText('bar');
})); }));
it('should display template if context is null', async(() => { it('should display template if context is null', async(() => {
const template = const template =
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="null"></template>`; `<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="null"></template>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}}) fixture = createTestComponent(template);
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]}); detectChangesAndExpectText('');
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges(); const refs = fixture.debugElement.children[0].references['refs'];
expect(fixture.nativeElement).toHaveText('');
var refs = fixture.debugElement.children[0].references['refs']; setTplRef(refs.tplRefs.first);
detectChangesAndExpectText('foo');
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
})); }));
it('should reflect initial context and changes', async(() => { it('should reflect initial context and changes', async(() => {
const template = const template =
`<tpl-refs #refs="tplRefs"><template let-foo="foo"><span>{{foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`; `<tpl-refs #refs="tplRefs"><template let-foo="foo"><span>{{foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}}) fixture = createTestComponent(template);
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
var refs = fixture.debugElement.children[0].references['refs'];
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('bar');
const refs = fixture.debugElement.children[0].references['refs'];
setTplRef(refs.tplRefs.first);
detectChangesAndExpectText('bar');
fixture.componentInstance.context.foo = 'alter-bar'; fixture.componentInstance.context.foo = 'alter-bar';
fixture.detectChanges(); detectChangesAndExpectText('alter-bar');
expect(fixture.debugElement.nativeElement).toHaveText('alter-bar');
})); }));
it('should reflect user defined $implicit property in the context', async(() => { it('should reflect user defined $implicit property in the context', async(() => {
const template = const template =
`<tpl-refs #refs="tplRefs"><template let-ctx><span>{{ctx.foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`; `<tpl-refs #refs="tplRefs"><template let-ctx><span>{{ctx.foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}}) fixture = createTestComponent(template);
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges(); fixture.detectChanges();
var refs = fixture.debugElement.children[0].references['refs']; const refs = fixture.debugElement.children[0].references['refs'];
fixture.componentInstance.currentTplRef = refs.tplRefs.first; setTplRef(refs.tplRefs.first);
fixture.componentInstance.context = {$implicit: fixture.componentInstance.context}; fixture.componentInstance.context = {$implicit: fixture.componentInstance.context};
fixture.detectChanges(); detectChangesAndExpectText('bar');
expect(fixture.debugElement.nativeElement).toHaveText('bar');
})); }));
it('should reflect context re-binding', async(() => { it('should reflect context re-binding', async(() => {
const template = const template =
`<tpl-refs #refs="tplRefs"><template let-shawshank="shawshank"><span>{{shawshank}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`; `<tpl-refs #refs="tplRefs"><template let-shawshank="shawshank"><span>{{shawshank}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}}) fixture = createTestComponent(template);
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges(); fixture.detectChanges();
var refs = fixture.debugElement.children[0].references['refs']; const refs = fixture.debugElement.children[0].references['refs'];
fixture.componentInstance.currentTplRef = refs.tplRefs.first; setTplRef(refs.tplRefs.first);
fixture.componentInstance.context = {shawshank: 'brooks'}; fixture.componentInstance.context = {shawshank: 'brooks'};
fixture.detectChanges(); detectChangesAndExpectText('brooks');
expect(fixture.debugElement.nativeElement).toHaveText('brooks');
fixture.componentInstance.context = {shawshank: 'was here'}; fixture.componentInstance.context = {shawshank: 'was here'};
fixture.detectChanges(); detectChangesAndExpectText('was here');
expect(fixture.debugElement.nativeElement).toHaveText('was here');
})); }));
}); });
} }
@ -172,3 +157,9 @@ class TestComponent {
currentTplRef: TemplateRef<any>; currentTplRef: TemplateRef<any>;
context: any = {foo: 'bar'}; context: any = {foo: 'bar'};
} }
function createTestComponent(template: string): ComponentFixture<TestComponent> {
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
.createComponent(TestComponent);
}

View File

@ -8,7 +8,7 @@
import {Component, Directive} from '@angular/core'; import {Component, Directive} from '@angular/core';
import {ElementRef} from '@angular/core/src/linker/element_ref'; import {ElementRef} from '@angular/core/src/linker/element_ref';
import {TestBed, async} from '@angular/core/testing'; import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {expect} from '@angular/platform-browser/testing/matchers'; import {expect} from '@angular/platform-browser/testing/matchers';
@ -22,31 +22,29 @@ export function main() {
}); });
it('should not interpolate children', async(() => { it('should not interpolate children', async(() => {
var template = '<div>{{text}}<span ngNonBindable>{{text}}</span></div>'; const template = '<div>{{text}}<span ngNonBindable>{{text}}</span></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); const fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('foo{{text}}'); expect(fixture.nativeElement).toHaveText('foo{{text}}');
})); }));
it('should ignore directives on child nodes', async(() => { it('should ignore directives on child nodes', async(() => {
var template = '<div ngNonBindable><span id=child test-dec>{{text}}</span></div>'; const template = '<div ngNonBindable><span id=child test-dec>{{text}}</span></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); const fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges(); fixture.detectChanges();
// We must use getDOM().querySelector instead of fixture.query here // We must use getDOM().querySelector instead of fixture.query here
// since the elements inside are not compiled. // since the elements inside are not compiled.
var span = getDOM().querySelector(fixture.debugElement.nativeElement, '#child'); const span = getDOM().querySelector(fixture.nativeElement, '#child');
expect(getDOM().hasClass(span, 'compiled')).toBeFalsy(); expect(getDOM().hasClass(span, 'compiled')).toBeFalsy();
})); }));
it('should trigger directives on the same node', async(() => { it('should trigger directives on the same node', async(() => {
var template = '<div><span id=child ngNonBindable test-dec>{{text}}</span></div>'; const template = '<div><span id=child ngNonBindable test-dec>{{text}}</span></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}}); const fixture = createTestComponent(template);
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges(); fixture.detectChanges();
var span = getDOM().querySelector(fixture.debugElement.nativeElement, '#child'); const span = getDOM().querySelector(fixture.nativeElement, '#child');
expect(getDOM().hasClass(span, 'compiled')).toBeTruthy(); expect(getDOM().hasClass(span, 'compiled')).toBeTruthy();
})); }));
}); });
@ -62,3 +60,8 @@ class TestComponent {
text: string; text: string;
constructor() { this.text = 'foo'; } constructor() { this.text = 'foo'; }
} }
function createTestComponent(template: string): ComponentFixture<TestComponent> {
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
.createComponent(TestComponent);
}

View File

@ -8,7 +8,7 @@
import {LOCALE_ID} from '@angular/core'; import {LOCALE_ID} from '@angular/core';
import {TestBed} from '@angular/core/testing'; import {TestBed} from '@angular/core/testing';
import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, iit, inject, it, xit} from '@angular/core/testing/testing_internal'; import {beforeEach, describe, inject, it} from '@angular/core/testing/testing_internal';
import {expect} from '@angular/platform-browser/testing/matchers'; import {expect} from '@angular/platform-browser/testing/matchers';
import {NgLocaleLocalization, NgLocalization, getPluralCategory} from '../src/localization'; import {NgLocaleLocalization, NgLocalization, getPluralCategory} from '../src/localization';
@ -57,7 +57,7 @@ export function main() {
describe('NgLocaleLocalization', () => { describe('NgLocaleLocalization', () => {
it('should return the correct values for the "en" locale', () => { it('should return the correct values for the "en" locale', () => {
const l10n = new NgLocaleLocalization('en_US'); const l10n = new NgLocaleLocalization('en-US');
expect(l10n.getPluralCategory(0)).toEqual('other'); expect(l10n.getPluralCategory(0)).toEqual('other');
expect(l10n.getPluralCategory(1)).toEqual('one'); expect(l10n.getPluralCategory(1)).toEqual('one');
@ -126,7 +126,7 @@ export function main() {
describe('getPluralCategory', () => { describe('getPluralCategory', () => {
it('should return plural category', () => { it('should return plural category', () => {
const l10n = new FrLocalization(); const l10n = new NgLocaleLocalization('fr');
expect(getPluralCategory(0, ['one', 'other'], l10n)).toEqual('one'); expect(getPluralCategory(0, ['one', 'other'], l10n)).toEqual('one');
expect(getPluralCategory(1, ['one', 'other'], l10n)).toEqual('one'); expect(getPluralCategory(1, ['one', 'other'], l10n)).toEqual('one');
@ -134,7 +134,7 @@ export function main() {
}); });
it('should return discrete cases', () => { it('should return discrete cases', () => {
const l10n = new FrLocalization(); const l10n = new NgLocaleLocalization('fr');
expect(getPluralCategory(0, ['one', 'other', '=0'], l10n)).toEqual('=0'); expect(getPluralCategory(0, ['one', 'other', '=0'], l10n)).toEqual('=0');
expect(getPluralCategory(1, ['one', 'other'], l10n)).toEqual('one'); expect(getPluralCategory(1, ['one', 'other'], l10n)).toEqual('one');
@ -144,15 +144,3 @@ export function main() {
}); });
}); });
} }
class FrLocalization extends NgLocalization {
getPluralCategory(value: number): string {
switch (value) {
case 0:
case 1:
return 'one';
default:
return 'other';
}
}
}

View File

@ -8,12 +8,11 @@
import {AsyncPipe} from '@angular/common'; import {AsyncPipe} from '@angular/common';
import {WrappedValue} from '@angular/core'; import {WrappedValue} from '@angular/core';
import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@angular/core/testing/testing_internal';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {browserDetection} from '@angular/platform-browser/testing/browser_util'; import {browserDetection} from '@angular/platform-browser/testing/browser_util';
import {EventEmitter} from '../../src/facade/async'; import {EventEmitter} from '../../src/facade/async';
import {isBlank} from '../../src/facade/lang';
import {SpyChangeDetectorRef} from '../spies'; import {SpyChangeDetectorRef} from '../spies';
export function main() { export function main() {
@ -112,7 +111,7 @@ export function main() {
var promise: Promise<any>; var promise: Promise<any>;
var ref: SpyChangeDetectorRef; var ref: SpyChangeDetectorRef;
// adds longer timers for passing tests in IE // adds longer timers for passing tests in IE
var timer = (!isBlank(getDOM()) && browserDetection.isIE) ? 50 : 10; var timer = (getDOM() && browserDetection.isIE) ? 50 : 10;
beforeEach(() => { beforeEach(() => {
promise = new Promise((res, rej) => { promise = new Promise((res, rej) => {

View File

@ -8,11 +8,9 @@
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 {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal'; 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';
import {DateWrapper} from '../../src/facade/lang';
export function main() { export function main() {
describe('DatePipe', () => { describe('DatePipe', () => {
var date: Date; var date: Date;
@ -27,7 +25,7 @@ export function main() {
// Tracking issue: https://github.com/angular/angular/issues/11187 // Tracking issue: https://github.com/angular/angular/issues/11187
beforeEach(() => { beforeEach(() => {
date = DateWrapper.create(2015, 6, 15, 9, 3, 1); date = new Date(2015, 5, 15, 9, 3, 1);
pipe = new DatePipe('en-US'); pipe = new DatePipe('en-US');
}); });

View File

@ -8,7 +8,7 @@
import {I18nPluralPipe, NgLocalization} from '@angular/common'; import {I18nPluralPipe, NgLocalization} from '@angular/common';
import {PipeResolver} from '@angular/compiler/src/pipe_resolver'; import {PipeResolver} from '@angular/compiler/src/pipe_resolver';
import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal'; import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
export function main() { export function main() {
describe('I18nPluralPipe', () => { describe('I18nPluralPipe', () => {

View File

@ -8,7 +8,7 @@
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 {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal'; import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
export function main() { export function main() {
describe('I18nSelectPipe', () => { describe('I18nSelectPipe', () => {

View File

@ -67,13 +67,13 @@ export function main() {
it('should work with mutable objects', async(() => { it('should work with mutable objects', async(() => {
let fixture = TestBed.createComponent(TestComp); let fixture = TestBed.createComponent(TestComp);
let mutable: number[] = [1]; let mutable: number[] = [1];
fixture.debugElement.componentInstance.data = mutable; fixture.componentInstance.data = mutable;
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('[\n 1\n]'); expect(fixture.nativeElement).toHaveText('[\n 1\n]');
mutable.push(2); mutable.push(2);
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('[\n 1,\n 2\n]'); expect(fixture.nativeElement).toHaveText('[\n 1,\n 2\n]');
})); }));
}); });

View File

@ -7,7 +7,7 @@
*/ */
import {LowerCasePipe} from '@angular/common'; import {LowerCasePipe} from '@angular/common';
import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal'; import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
export function main() { export function main() {
describe('LowerCasePipe', () => { describe('LowerCasePipe', () => {

View File

@ -7,7 +7,7 @@
*/ */
import {CurrencyPipe, DecimalPipe, PercentPipe} from '@angular/common'; import {CurrencyPipe, DecimalPipe, PercentPipe} from '@angular/common';
import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal'; 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() {

View File

@ -9,7 +9,6 @@
import {CommonModule, SlicePipe} from '@angular/common'; import {CommonModule, SlicePipe} from '@angular/common';
import {Component} from '@angular/core'; import {Component} from '@angular/core';
import {TestBed, async} from '@angular/core/testing'; import {TestBed, async} from '@angular/core/testing';
import {browserDetection} from '@angular/platform-browser/testing/browser_util';
import {expect} from '@angular/platform-browser/testing/matchers'; import {expect} from '@angular/platform-browser/testing/matchers';
export function main() { export function main() {
@ -70,19 +69,15 @@ export function main() {
expect(pipe.transform(str, 99)).toEqual(''); expect(pipe.transform(str, 99)).toEqual('');
}); });
// Makes Edge to disconnect when running the full unit test campaign it('should return entire input if START is negative and greater than input length', () => {
// TODO: remove when issue is solved: https://github.com/angular/angular/issues/4756 expect(pipe.transform(list, -99)).toEqual([1, 2, 3, 4, 5]);
if (!browserDetection.isEdge) { expect(pipe.transform(str, -99)).toEqual('tuvwxyz');
it('should return entire input if START is negative and greater than input length', () => { });
expect(pipe.transform(list, -99)).toEqual([1, 2, 3, 4, 5]);
expect(pipe.transform(str, -99)).toEqual('tuvwxyz');
});
it('should not modify the input list', () => { it('should not modify the input list', () => {
expect(pipe.transform(list, 2)).toEqual([3, 4, 5]); expect(pipe.transform(list, 2)).toEqual([3, 4, 5]);
expect(list).toEqual([1, 2, 3, 4, 5]); expect(list).toEqual([1, 2, 3, 4, 5]);
}); });
}
}); });
@ -100,13 +95,13 @@ export function main() {
it('should work with mutable arrays', async(() => { it('should work with mutable arrays', async(() => {
let fixture = TestBed.createComponent(TestComp); let fixture = TestBed.createComponent(TestComp);
let mutable: number[] = [1, 2]; let mutable: number[] = [1, 2];
fixture.debugElement.componentInstance.data = mutable; fixture.componentInstance.data = mutable;
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('2'); expect(fixture.nativeElement).toHaveText('2');
mutable.push(3); mutable.push(3);
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('2,3'); expect(fixture.nativeElement).toHaveText('2,3');
})); }));
}); });
}); });

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