Compare commits
873 Commits
2.0.0-rc.3
...
2.0.1
Author | SHA1 | Date | |
---|---|---|---|
712d1a7c37 | |||
16601f9359 | |||
b81e2e7a31 | |||
98fac36706 | |||
3e780c032e | |||
e09882180e | |||
0e18c57a17 | |||
51e2b9c073 | |||
f218e240d3 | |||
af6b219f8e | |||
20addf5f9f | |||
2860418a3c | |||
39e251eea7 | |||
d7d716d5db | |||
a95d65241c | |||
fdb22bd185 | |||
51d73d3e4e | |||
bf81b06a28 | |||
0621f07a2c | |||
1225ecfb14 | |||
5509453e72 | |||
70488ed382 | |||
03aedbe54b | |||
8395aab25d | |||
0dc15eb64a | |||
cba885a1fb | |||
fa4723a208 | |||
5bf08b886f | |||
89802316b9 | |||
2300c23332 | |||
fa39965a37 | |||
115f0fa842 | |||
734b8b8c13 | |||
54b41f57be | |||
df4254ae89 | |||
14ee75924b | |||
bd4045b6e7 | |||
255099aa61 | |||
1c24096650 | |||
32aeb1052d | |||
838d4bbf6c | |||
c4114c2f66 | |||
37b8691c8c | |||
93054d4e3d | |||
cfc12c6539 | |||
c0bdd89b5d | |||
d5515473bf | |||
ffe5c49c3e | |||
ae1dd5bfd0 | |||
cb657c4b55 | |||
42f60ca303 | |||
e33037a2f1 | |||
9cee8bcc83 | |||
003294d5df | |||
785292f44f | |||
15c2912527 | |||
096ae7c404 | |||
5972fdc817 | |||
2c42a50fc3 | |||
caa1cd2470 | |||
5fad37df69 | |||
727c2b38a4 | |||
b6287ccc51 | |||
69e8ace884 | |||
85d9db6bc4 | |||
0a2132ef10 | |||
d299ce4bcf | |||
0b9425bbb4 | |||
1a035a0dc7 | |||
84b4338ab5 | |||
b847257b16 | |||
c65d139081 | |||
57f0269491 | |||
4e6c41b3a1 | |||
7105021c41 | |||
f7313db0be | |||
1d2e70e3a4 | |||
21516c32e6 | |||
00a24b63da | |||
e71558ba89 | |||
7ac47acc1c | |||
60e49a7e4b | |||
c71e35cbf5 | |||
1348c65b0c | |||
ff03d87cdd | |||
a2bf334e6e | |||
f8690caa98 | |||
aa713d1dd9 | |||
a2519c6164 | |||
fa994810d5 | |||
c54580a4af | |||
730415e048 | |||
42a287fabf | |||
42d442dcd5 | |||
cc2873a94d | |||
63e15ffaec | |||
1b15170c89 | |||
26d1423ae9 | |||
61aad7925f | |||
79055f727b | |||
220d8377fe | |||
cc7780adf7 | |||
051a6ebe12 | |||
c9513b713a | |||
66e38b6754 | |||
7b82877ee5 | |||
c9ad5e46d6 | |||
2cdd051109 | |||
57cb82052b | |||
dd8204a655 | |||
cdda4082de | |||
0614c8c99d | |||
a343a8e1c2 | |||
a41c1bbdf4 | |||
f2c6157e74 | |||
32564ece27 | |||
3eee62fa71 | |||
617475005f | |||
0822066175 | |||
82f30e09f0 | |||
c649a5c5ab | |||
53f0c2206d | |||
0bce3907b8 | |||
2170379251 | |||
5a4e46db20 | |||
f5d44a42c9 | |||
673de004d2 | |||
f386cb4ba9 | |||
71e9cae1d0 | |||
df6762a170 | |||
d296298282 | |||
077e0be1e7 | |||
a52d076912 | |||
dae7cfc454 | |||
436af15d63 | |||
7b24028437 | |||
6a2bbffe10 | |||
f78e184822 | |||
78ad9adc1a | |||
9e2ec7a1aa | |||
643afa4b15 | |||
ed2ebeb52a | |||
567900e550 | |||
cc958c74ad | |||
62af613741 | |||
3ff816afa6 | |||
dd03bf12e1 | |||
645108f25b | |||
882efd125e | |||
d91e92c2f5 | |||
8858ebc4ab | |||
df4c0a3d1f | |||
b4363bc8af | |||
d26a827494 | |||
ea95c391c1 | |||
aa9b617c9d | |||
7192fec841 | |||
ee88c3c976 | |||
1ff0add29e | |||
5ee0f09b92 | |||
70b0ab457b | |||
c25d1f7ecc | |||
a45769a0a2 | |||
109dc99d32 | |||
2371d22d49 | |||
6f4b6edfea | |||
8c09933803 | |||
d309f7799c | |||
93deff6c33 | |||
c31535982c | |||
f5101782d9 | |||
5e5ae3cde6 | |||
53cf71430f | |||
04d02b55d1 | |||
043493cb62 | |||
2581c0851a | |||
27d72e87c3 | |||
eef4c22e87 | |||
4287f1716d | |||
ebc8e808a9 | |||
c9e5b599e4 | |||
e42a057048 | |||
0bb94df1da | |||
50e171c09b | |||
96697029c9 | |||
7c3b1367bc | |||
18be339ee9 | |||
ddda62b1f2 | |||
0ddae9b727 | |||
625b105d3a | |||
f9eb1f33f4 | |||
046c1a8a25 | |||
08e48c8f73 | |||
1b5e2b5129 | |||
562c8263dc | |||
99c0a7fae2 | |||
f4f6f4b4d8 | |||
cc89ef6c8c | |||
6ea5b05e7c | |||
f7b5478e9f | |||
873233e825 | |||
942104d9ac | |||
6dceaf209d | |||
2ab07d9418 | |||
1ef122988e | |||
db280fc67e | |||
ef0f29c372 | |||
1818056912 | |||
1df69cb4d2 | |||
2b20db6c5a | |||
174c016104 | |||
71ae2c4525 | |||
0f68351979 | |||
c74a438f0c | |||
c350ba29f6 | |||
6e40ef0f6d | |||
24e046fd6a | |||
979657989b | |||
8cb1046ce9 | |||
d53a898f46 | |||
f9f80003c8 | |||
d59ee3caaa | |||
b8ea71afb6 | |||
e2241a2f92 | |||
e8a1566065 | |||
d2ad871279 | |||
0b665c0ece | |||
875e66409c | |||
d7de5c4f8e | |||
51877ef4ed | |||
c377e80670 | |||
61002733bc | |||
5ff14de1f3 | |||
38069aba35 | |||
7dee1ee4cf | |||
af63378fa0 | |||
abad6673e6 | |||
75553200c0 | |||
6c77d7182a | |||
4a44832114 | |||
27539c8b80 | |||
e220a80093 | |||
3dd85cb7f1 | |||
511fe3d9f8 | |||
9ce8ef76bf | |||
7c07bfff97 | |||
86ba072758 | |||
fc1e45db92 | |||
a2deafc50f | |||
2fc5c57b31 | |||
93f323cfa2 | |||
bb9dfbc578 | |||
0bb516fae2 | |||
2ffecc0e14 | |||
b9647b7347 | |||
f25c97671a | |||
0a053a4cd5 | |||
4d7d2a2daa | |||
0cf5ece7f8 | |||
66df335998 | |||
fc2fe00d16 | |||
811962b2bb | |||
b867764b0d | |||
ce08982f78 | |||
cbe0976426 | |||
515ff61fcb | |||
d7c82f5c0f | |||
566d4361e2 | |||
ea2e5521e8 | |||
eb7d8c702c | |||
5d294624fa | |||
3aaf064d11 | |||
f38a700e35 | |||
501b83441d | |||
c03e25a7b7 | |||
1f5a5895e5 | |||
8a2324f86a | |||
6335b31702 | |||
6ef7a76e39 | |||
cc79dcac7f | |||
dc6f72e963 | |||
2b313e4979 | |||
4f8f8cfc66 | |||
8b782818f5 | |||
bd510ccdbb | |||
f1ce7607a6 | |||
7dfcaac730 | |||
c7a874dd2f | |||
aa5c8ca61f | |||
5c93a8800a | |||
05bbb8efcf | |||
14a30f3ca0 | |||
5ddecb18a7 | |||
c02325dd06 | |||
4a740f23a4 | |||
a782232ca3 | |||
a29f9f3ab8 | |||
3c2b2ff332 | |||
939d318242 | |||
39a2c39cef | |||
45e8e73670 | |||
ca41b4f5ff | |||
3c561475c8 | |||
23a27776e2 | |||
01111b04ff | |||
8560e1e4bf | |||
e0fbca9fb0 | |||
a7b76826a0 | |||
ece7985b8a | |||
9883e19e2e | |||
c631cfc2fd | |||
53c99cfc95 | |||
c56f3f2246 | |||
cc0e3d2296 | |||
917d43e108 | |||
bb7d55244d | |||
8a5eb08672 | |||
477e425f57 | |||
654ff6115a | |||
628d06c17c | |||
91980382e8 | |||
2f41b5c8a0 | |||
cd8cbd3762 | |||
292ccf882a | |||
a0e13b9797 | |||
a5c0349d88 | |||
c48021ab97 | |||
beb79e75bf | |||
0b62b6f783 | |||
895c542a20 | |||
c4fd862e15 | |||
6f18bd18bb | |||
00e157dc3b | |||
4be863c223 | |||
3009be8d6e | |||
4829fbb95c | |||
40e160c22c | |||
951ecb4d90 | |||
9318033294 | |||
4648b3e5de | |||
740e80492d | |||
cc22b051e3 | |||
75405ae0f5 | |||
f12d51992d | |||
6fd5bc075d | |||
675e582ffd | |||
0a22e8eefd | |||
44a4814766 | |||
3c23238129 | |||
916ed55d82 | |||
2451eb9ded | |||
ed639784d4 | |||
b77a3794b8 | |||
73a9ee4a05 | |||
9adf80385b | |||
a86c554a8e | |||
62078eee45 | |||
24e280a21a | |||
f7ff6c5a12 | |||
f6a7d6504c | |||
96bf42261b | |||
72bb38f83b | |||
4c9900dc3a | |||
6b26102931 | |||
bec5c5fdad | |||
04c11bb749 | |||
3f5331be9d | |||
48751cceae | |||
4a9745ef78 | |||
acc0fe6cf9 | |||
b238414984 | |||
231ed69507 | |||
60b10134df | |||
73c0a9daaf | |||
398bbb6aa9 | |||
05d1312306 | |||
bc6d1c87a6 | |||
712c7d5c3b | |||
e9479b30e8 | |||
7f6685e451 | |||
ce4eae65a7 | |||
4df48b202c | |||
33ced7088f | |||
3329977ec9 | |||
f84c3fdc5f | |||
44e1b23813 | |||
12b0a3d0e5 | |||
d2825077b1 | |||
156a52e390 | |||
bb7221f922 | |||
2eb4ee8393 | |||
87fe47737a | |||
9317056138 | |||
a235ae16ed | |||
79afcf0766 | |||
161a4dd15f | |||
6580d67875 | |||
04c6b2fe85 | |||
203b2ba637 | |||
5b99b8c18a | |||
6a011f4e8e | |||
f0c1f9ef39 | |||
97f35714f7 | |||
00e5b7d30a | |||
39c0f9ebb3 | |||
e60c765280 | |||
91dd672aa4 | |||
a5d2aadecb | |||
cc6749c158 | |||
f48142e679 | |||
947f9c3f56 | |||
7cd4741fcb | |||
2d520ae7e7 | |||
5d59c6e80f | |||
50345b8c36 | |||
7606c96c80 | |||
3466232f8b | |||
6e842fc5bf | |||
50c795280f | |||
51b22cf14f | |||
2291929a15 | |||
7fac4efede | |||
b96869afd2 | |||
6f4ee6101c | |||
6db27153ef | |||
9a11ec2624 | |||
aff1bc9f2d | |||
43512aa5eb | |||
c7f3aa71fb | |||
f9da3c98d6 | |||
a20a420be6 | |||
beadf6167a | |||
c8da7e995f | |||
8be6b12c2b | |||
4728f29f67 | |||
2a942e49ac | |||
e3aa19049f | |||
ef1eadadcd | |||
f444c11d21 | |||
d75502eeee | |||
ebcd14f8e9 | |||
b65f66feff | |||
dd68ae3ef1 | |||
1b04d70626 | |||
01bca41168 | |||
94e1ab33ce | |||
0b08dd8674 | |||
b2b47177cd | |||
f08257ff4a | |||
d1f4222c83 | |||
d21331e902 | |||
37f138e83d | |||
5dab0bad3c | |||
85e70a4cde | |||
6b564ecda5 | |||
d4cceff0ef | |||
a415613457 | |||
1a41bd1ca4 | |||
5a99393355 | |||
afcb3c0035 | |||
d2d36c61f3 | |||
0bd97ecda2 | |||
46bbcefb36 | |||
74b57dfa7d | |||
8c9c0986e9 | |||
4028fcaa51 | |||
7a8ef1eae5 | |||
e811a5d97f | |||
df44e3e425 | |||
cdb1a237e5 | |||
fcafdff10b | |||
c586656d43 | |||
3a307c2794 | |||
4f17dbc721 | |||
99989f5d3f | |||
6baf3baedd | |||
0d1f3c3b07 | |||
83e2d3d1cb | |||
b4613ab2d2 | |||
0ca05eee45 | |||
26c9e1dc70 | |||
797cb5ae7b | |||
63b82cd730 | |||
2b704f0586 | |||
ce5ba80792 | |||
8b18ef4ba2 | |||
cd18de7a21 | |||
fd19671c07 | |||
3fcd6fd93f | |||
c8d53d71a3 | |||
790362e243 | |||
2eda7a5293 | |||
9925aa89dc | |||
6195a45ae2 | |||
422d380b3e | |||
550ab31bd0 | |||
5fceb21549 | |||
29caa37943 | |||
8efbcc996a | |||
91c64d2b8d | |||
82e7ecd611 | |||
3d53b33391 | |||
34624b2db2 | |||
1ab5eb0844 | |||
630028350a | |||
7e4fd7d7da | |||
af2e80e068 | |||
8e6091de6c | |||
ecdaded25f | |||
c161ed415d | |||
ff3b71f7b3 | |||
16cc9b46aa | |||
3ce11ed58c | |||
7db75fa361 | |||
d6d4568830 | |||
a55d796c4b | |||
73f02c7861 | |||
8c8754e573 | |||
c977a906b3 | |||
e0eea6c2f4 | |||
3e377f520e | |||
8dc82a0080 | |||
4624a35845 | |||
8d4499959a | |||
2dfc9c653b | |||
106db0aba8 | |||
28c4852cd6 | |||
13c8211065 | |||
e73d0511cf | |||
e18626b7a2 | |||
4df7b1cfbc | |||
f9573ece41 | |||
93ade740e2 | |||
a46437c57d | |||
633c7d1ebe | |||
251953218c | |||
0d6cc17252 | |||
c8989c900f | |||
6134320f16 | |||
7f647822bd | |||
e34a04d2ad | |||
44093905e2 | |||
3e2900f74b | |||
11fd2eccec | |||
0eee1d5de3 | |||
1b77604ee2 | |||
cc5cfe87c3 | |||
48f230a951 | |||
2be50bdbb0 | |||
f7258ea52a | |||
28e8b2faab | |||
3c3e9ddb10 | |||
5162fb6d52 | |||
2fdb39e60a | |||
43c71ae103 | |||
f0bd528d77 | |||
50a024b42f | |||
b48f7bcb8d | |||
763ca60f5b | |||
0eca7abdd8 | |||
d0a95e35af | |||
acc6c8d0b7 | |||
3dbc66c1ac | |||
4ad6bcce54 | |||
0988cc82b0 | |||
20b03bad11 | |||
81d27daf0d | |||
bb8b82b3f5 | |||
915a6666f8 | |||
72da547d6a | |||
7c76a75452 | |||
a32c4ad2f0 | |||
fb3608aa5d | |||
0a46f37444 | |||
9b39e499ac | |||
a67cc8229d | |||
b58e9ea775 | |||
422effdd18 | |||
3b690b68a6 | |||
43349dd373 | |||
e44e8668ea | |||
69e72c0786 | |||
553344739c | |||
367f0fd142 | |||
58d9e7fc5a | |||
9d9e9c6ff1 | |||
ba88db5141 | |||
62e7c0f464 | |||
fc83bbbe98 | |||
482c019199 | |||
b449467940 | |||
0aba42ae5b | |||
0d1bf8148b | |||
b42411ba1f | |||
5a21f168d6 | |||
00b726f695 | |||
f02da4e91a | |||
d6b65db9a7 | |||
6f4e49ed53 | |||
46b212706b | |||
ca16fc29a6 | |||
9edea0b139 | |||
d15a1d64e1 | |||
c87847974a | |||
6f68330fa5 | |||
2b63330a36 | |||
06e4ca4bb3 | |||
43437c175a | |||
8d90a5a4cf | |||
93a4ca652a | |||
41178367d1 | |||
54f2edbb90 | |||
b652a7fc9f | |||
e34eb4520f | |||
190bcc89c1 | |||
64fc4648b7 | |||
bdb59129d0 | |||
d455942389 | |||
cdb3678fe3 | |||
e73ac1e992 | |||
51f3d22e4f | |||
00aa7a76b6 | |||
27b87ef535 | |||
44709e0dca | |||
31a7709ece | |||
a441b5b8fe | |||
76b8a49bfb | |||
db54a84d14 | |||
eb6ff65af7 | |||
23ee29b6a2 | |||
73a69895d8 | |||
2799e7a3ca | |||
b43f95435b | |||
450f61d384 | |||
f3dd91e1d7 | |||
979946c062 | |||
51e661eb74 | |||
921a17960c | |||
7a4f6621ed | |||
83bc5c97ef | |||
3f08efa35d | |||
0914dc35e8 | |||
b6746cce9c | |||
8cd97c2054 | |||
32d8cde9c6 | |||
1803ed2512 | |||
f08060b0b0 | |||
b77a4a40a4 | |||
e1109d52e1 | |||
0668ba50e8 | |||
44ff005ce3 | |||
aa88438b54 | |||
85be729c70 | |||
a5dc5705a3 | |||
9e3d13f61f | |||
961c9d48ae | |||
9229bbbc80 | |||
34feecf60e | |||
4c762a6be3 | |||
0426325ef7 | |||
0b54e3cf0a | |||
5cf58971f1 | |||
6518ff88b2 | |||
42b0c1d8a2 | |||
4a965052f9 | |||
5725c5925c | |||
a46291b67c | |||
4ac76ca281 | |||
e7a8e2757b | |||
1266460386 | |||
0ccb6e0dfc | |||
3050ae155c | |||
402fd934d0 | |||
6c86e8d80a | |||
60e6f91a53 | |||
e676fded21 | |||
25e070dd65 | |||
da8eb9f8b8 | |||
806a25413c | |||
5af1e891cd | |||
79eda30f0f | |||
6d02d2f107 | |||
ded518d47f | |||
27436270fd | |||
b4ea0b1601 | |||
7b31178546 | |||
2ff83324af | |||
4ef86891a3 | |||
eb5763c23f | |||
d1a3e3aff1 | |||
93d0a01d3d | |||
9af2d8b810 | |||
94dc632a6d | |||
29231877e6 | |||
e68252a79b | |||
4ec2a30942 | |||
e6b24437a9 | |||
a05f7b2d76 | |||
61e18434d3 | |||
57473e72ec | |||
c3bdd504d0 | |||
daa9da4047 | |||
245b0910ed | |||
a77db44129 | |||
34b3c534e7 | |||
fa47890032 | |||
d84a43c828 | |||
9a1babb30c | |||
30a332ee36 | |||
426b002897 | |||
2de8364de2 | |||
eacc9e6541 | |||
749dec7dfb | |||
96a9e66616 | |||
46e105f3ab | |||
f7a0e9ecb6 | |||
93025d1bc6 | |||
98d49d4ce3 | |||
1426f680f5 | |||
c7fc51a185 | |||
b7e69bc1a1 | |||
72544ba551 | |||
c43dd5a655 | |||
7f4954bed6 | |||
f1fc1dc669 | |||
cbe85a0893 | |||
7073cf74fe | |||
9d265b6f61 | |||
776a83f9da | |||
f29457f3f0 | |||
a005d1595e | |||
8d746e3f67 | |||
37e6da6dfb | |||
8aa2a0c1b2 | |||
6bfd514caf | |||
ad3f18c0dd | |||
39d04b4a15 | |||
6fbe56dbf2 | |||
8ebb8e44c8 | |||
6fcf962fb5 | |||
2708ce6a17 | |||
30bec78da3 | |||
9a04fcd061 | |||
ae62f082fd | |||
9cc3b2ca9e | |||
3fe1cb0253 | |||
0ed7773223 | |||
3f55aa609f | |||
74b45dfbf8 | |||
5c9f871b21 | |||
77dc6ef411 | |||
5eca6e4e40 | |||
0c65d5cf2b | |||
f65ebec3ed | |||
81bf3f66ca | |||
3cbded6694 | |||
137fff9632 | |||
695c08b9dd | |||
119794249b | |||
afb72164e4 | |||
9fee5630fd | |||
01de58d650 | |||
dabf214f17 | |||
fb2539e1d5 | |||
ad9f02a73e | |||
2d73583253 | |||
73f017bad9 | |||
055282f156 | |||
fe7de53b89 | |||
17e4cfc748 | |||
1608d91728 | |||
a3b90411aa | |||
5781b96490 | |||
f208ee0d57 | |||
8aa388de6c | |||
51d4c9dcbd | |||
e81dea695c | |||
3fec27961e | |||
3784696b9e | |||
8c45aebc18 | |||
810c722413 | |||
e2116c53f3 | |||
296a447e3c | |||
0961bd1eff | |||
9340e1b065 | |||
ae4fa56ee9 | |||
2d9d7f1310 | |||
5ee84fe0f6 | |||
1620426393 | |||
bf598d6b8b | |||
24eb8389d2 | |||
fcfddbf79c | |||
dc64e90ab9 | |||
e12b1277df | |||
797914e948 | |||
e0b0a594bb | |||
ed0ade6f34 | |||
5cc7b41f39 | |||
f2f1ec0117 | |||
e913d9954d | |||
d20488752b | |||
855f3afb28 | |||
3f44377f2f | |||
90295e3252 | |||
a620f95891 | |||
db66509e66 | |||
6605eb30e9 | |||
3644eef860 | |||
fb2509675d | |||
eef9512ce6 | |||
9f00a1b902 | |||
c369bc747d | |||
c03e1f2f59 | |||
17dcbf66b9 | |||
40b907a657 | |||
a33195dcf3 | |||
c693c03f1d | |||
de127109f9 | |||
83208983b3 | |||
327d04c9c6 | |||
54edce2bab | |||
1a145ac500 | |||
9f978cf49d | |||
41b781107b | |||
dcf75126bf | |||
1143b0389a | |||
97a2119596 | |||
fbd2dd9ca2 | |||
f463e09b9f | |||
42a5b6cbda | |||
0ad1215a92 | |||
7733c97df3 | |||
8a9e9c7bd3 | |||
3d8eb8cbca | |||
894747c34c | |||
8d5a312585 | |||
8eb81b3741 | |||
22d8f73bc9 | |||
249a6bdd98 | |||
3ad81b1beb | |||
5ab0534164 | |||
5150344213 | |||
98cef76931 | |||
6c5b653593 | |||
9ed8f2d26e | |||
33a2f86b28 | |||
fed1672a43 | |||
54dbed4f48 | |||
df759b8d4b | |||
6edf0474cc | |||
826f89f862 | |||
c43aec2182 | |||
ae75e3640a | |||
a5f2cc73f6 | |||
e1e5c40ef7 | |||
6420f75320 | |||
5face35ae5 | |||
398060d5ff | |||
638fd744aa | |||
098b461b69 | |||
9decc3d823 | |||
a5f2e205ef | |||
8899b83927 | |||
f6a410a4a8 | |||
3d5bb23184 | |||
ef37d2ae0b | |||
2eb234bc63 | |||
758ee95880 | |||
40e1112a8e | |||
397f5e2390 | |||
1a212259af | |||
f114dd300b | |||
5954a26bce |
6
.gitattributes
vendored
6
.gitattributes
vendored
@ -1,5 +1,9 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
|
||||
# JS files must always use LF for tools to work
|
||||
# JS and TS files must always use LF for tools to work
|
||||
*.js eol=lf
|
||||
*.ts eol=lf
|
||||
|
||||
# Must keep Windows line ending to be parsed correctly
|
||||
scripts/windows/packages.txt eol=crlf
|
||||
|
32
.github/ISSUE_TEMPLATE.md
vendored
32
.github/ISSUE_TEMPLATE.md
vendored
@ -1,33 +1,31 @@
|
||||
**I'm submitting a ...** (check one with "x")
|
||||
```
|
||||
[ ] bug report
|
||||
[ ] bug report => search github for a similar issue or PR before submitting
|
||||
[ ] feature request
|
||||
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
|
||||
```
|
||||
|
||||
**Current behavior**
|
||||
|
||||
|
||||
**Expected/desired behavior**
|
||||
**Current behavior**
|
||||
<!-- Describe how the bug manifests. -->
|
||||
|
||||
**Expected behavior**
|
||||
<!-- Describe what the behavior would be without the bug. -->
|
||||
|
||||
**Reproduction of the problem**
|
||||
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).
|
||||
|
||||
|
||||
|
||||
**What is the expected behavior?**
|
||||
|
||||
|
||||
<!-- 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). -->
|
||||
|
||||
**What is the motivation / use case for changing the behavior?**
|
||||
|
||||
|
||||
<!-- Describe the motivation or the concrete use case -->
|
||||
|
||||
**Please tell us about your environment:**
|
||||
<!-- Operating system, IDE, package manager, HTTP server, ... -->
|
||||
|
||||
* **Angular version:** 2.0.X
|
||||
<!-- Check whether this is still an issue in the most recent Angular version -->
|
||||
|
||||
* **Angular version:** 2.0.0-rc.X
|
||||
|
||||
* **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 | Dart]
|
||||
* **Language:** [all | TypeScript X.X | ES6/7 | ES5]
|
||||
|
||||
* **Node (for AoT issues):** `node --version` =
|
||||
|
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,5 +1,5 @@
|
||||
**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)
|
||||
- [ ] Docs have been added / updated (for bug fixes / features)
|
||||
|
||||
|
22
.gitignore
vendored
22
.gitignore
vendored
@ -1,26 +1,9 @@
|
||||
.DS_STORE
|
||||
|
||||
# Don’t commit the following directories created by pub.
|
||||
packages
|
||||
pubspec.lock
|
||||
.pub
|
||||
.packages
|
||||
|
||||
/dist/
|
||||
.buildlog
|
||||
node_modules
|
||||
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.
|
||||
pubspec.lock
|
||||
.c9
|
||||
@ -37,13 +20,8 @@ modules/.vscode
|
||||
# Ignore npm debug log
|
||||
npm-debug.log
|
||||
|
||||
/docs/bower_components/
|
||||
|
||||
# build-analytics
|
||||
.build-analytics
|
||||
|
||||
# built dart payload tests
|
||||
/modules_dart/payload/**/build
|
||||
|
||||
# rollup-test output
|
||||
/modules/rollup-test/dist/
|
||||
|
149
.travis.yml
149
.travis.yml
@ -1,7 +1,7 @@
|
||||
language: node_js
|
||||
sudo: false
|
||||
node_js:
|
||||
- '5.4.1'
|
||||
- '6.6.0'
|
||||
|
||||
addons:
|
||||
# firefox: "38.0"
|
||||
@ -20,20 +20,9 @@ cache:
|
||||
directories:
|
||||
- ./node_modules
|
||||
- ./.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:
|
||||
global:
|
||||
# - KARMA_JS_BROWSERS=ChromeNoSandbox
|
||||
# - E2E_BROWSERS=ChromeOnTravis
|
||||
# - LOGS_DIR=/tmp/angular-build/logs
|
||||
# - ARCH=linux-x64
|
||||
|
||||
# GITHUB_TOKEN_ANGULAR
|
||||
# This is needed for the e2e Travis matrix task to publish packages to github for continuous packages delivery.
|
||||
- secure: "fq/U7VDMWO8O8SnAQkdbkoSe2X92PVqg4d044HmRYVmcf6YbO48+xeGJ8yOk0pCBwl3ISO4Q2ot0x546kxfiYBuHkZetlngZxZCtQiFT9kyId8ZKcYdXaIW9OVdw3Gh3tQyUwDucfkVhqcs52D6NZjyE2aWZ4/d1V4kWRO/LMgo="
|
||||
@ -47,150 +36,16 @@ env:
|
||||
- CI_MODE=browserstack_optional
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- env: "CI_MODE=saucelabs_optional"
|
||||
- env: "CI_MODE=browserstack_optional"
|
||||
|
||||
|
||||
install:
|
||||
- ./scripts/ci-lite/install.sh
|
||||
|
||||
before_script:
|
||||
|
||||
|
||||
script:
|
||||
- ./scripts/ci-lite/build.sh && ./scripts/ci-lite/test.sh
|
||||
|
||||
after_script:
|
||||
- ./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=
|
||||
|
1038
CHANGELOG.md
1038
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@ -18,18 +18,17 @@ Help us keep Angular open and inclusive. Please read and follow our [Code of Con
|
||||
## <a name="question"></a> Got a Question or Problem?
|
||||
|
||||
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?
|
||||
If you find a bug in the source code or a mistake in the documentation, you can help us by
|
||||
If you find a bug in the source code, you can help us by
|
||||
[submitting an issue](#submit-issue) to our [GitHub Repository][github]. Even better, you can
|
||||
[submit a Pull Request](#submit-pr) with a fix.
|
||||
|
||||
## <a name="feature"></a> Want a Feature?
|
||||
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
|
||||
a proposal for your work first, to be sure that we can use it. Angular 2 is in developer preview
|
||||
and we are not ready to accept major contributions ahead of the full release.
|
||||
a proposal for your work first, to be sure that we can use it.
|
||||
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
|
||||
@ -95,7 +94,7 @@ Before you submit your Pull Request (PR) consider the following guidelines:
|
||||
* In GitHub, send a pull request to `angular:master`.
|
||||
* If we suggest changes then:
|
||||
* Make the required updates.
|
||||
* Re-run the Angular 2 test suites for JS and Dart to ensure tests are still passing.
|
||||
* Re-run the Angular 2 test suites to ensure tests are still passing.
|
||||
* Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
|
||||
|
||||
```shell
|
||||
|
316
DEVELOPER.md
316
DEVELOPER.md
@ -1,19 +1,13 @@
|
||||
# Building and Testing Angular 2 for JS and Dart
|
||||
# Building and Testing Angular 2 for JS
|
||||
|
||||
This document describes how to set up your development environment to build and test Angular, both
|
||||
JS and Dart versions. It also explains the basic mechanics of using `git`, `node`, and `npm`.
|
||||
This document describes how to set up your development environment to build and test Angular 2 JS version.
|
||||
It also explains the basic mechanics of using `git`, `node`, and `npm`.
|
||||
|
||||
* [Prerequisite Software](#prerequisite-software)
|
||||
* [Getting the Sources](#getting-the-sources)
|
||||
* [Environment Variable Setup](#environment-variable-setup)
|
||||
* [Installing NPM Modules and Dart Packages](#installing-npm-modules-and-dart-packages)
|
||||
* [Build commands](#build-commands)
|
||||
* [Installing NPM Modules](#installing-npm-modules)
|
||||
* [Building](#building)
|
||||
* [Running Tests Locally](#running-tests-locally)
|
||||
* [Code Style](#code-style)
|
||||
* [Project Information](#project-information)
|
||||
* [CI using Travis](#ci-using-travis)
|
||||
* [Transforming Dart code](#transforming-dart-code)
|
||||
* [Debugging](#debugging)
|
||||
|
||||
See the [contribution guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md)
|
||||
if you'd like to contribute to Angular.
|
||||
@ -32,15 +26,6 @@ following products on your development machine:
|
||||
(version `>=3.5.3 <4.0`), which comes with Node. Depending on your system, you can install Node either from
|
||||
source or as a pre-packaged bundle.
|
||||
|
||||
* *Optional*: [Dart](https://www.dartlang.org) (version `>=1.13.2 <2.0.0`), specifically the Dart SDK and
|
||||
Dartium (a version of [Chromium](http://www.chromium.org) with native support for Dart through
|
||||
the Dart VM). Visit Dart's [Downloads page](https://www.dartlang.org/downloads) page for
|
||||
instructions. You can also download both **stable** and **dev** channel versions from the
|
||||
[download archive](https://www.dartlang.org/downloads/archive/). In that case, on Windows, Dart
|
||||
must be added to the `PATH` (e.g. `path-to-dart-sdk-folder\bin`) and a new `DARTIUM_BIN`
|
||||
environment variable must be created, pointing to the executable (e.g.
|
||||
`path-to-dartium-folder\chrome.exe`).
|
||||
|
||||
* [Java Development Kit](http://www.oracle.com/technetwork/es/java/javase/downloads/index.html) which is used
|
||||
to execute the selenium standalone server for e2e testing.
|
||||
|
||||
@ -65,44 +50,9 @@ cd angular
|
||||
# Add the main Angular repository as an upstream remote to your repository:
|
||||
git remote add upstream https://github.com/angular/angular.git
|
||||
```
|
||||
## Installing NPM Modules
|
||||
|
||||
## Environment Variable Setup
|
||||
|
||||
Define the environment variables listed below. These are mainly needed for the testing. The
|
||||
notation shown here is for [`bash`](http://www.gnu.org/software/bash); adapt as appropriate for
|
||||
your favorite shell.
|
||||
|
||||
Examples given below of possible values for initializing the environment variables assume **Mac OS
|
||||
X** and that you have installed the Dart Editor in the directory named by
|
||||
`DART_EDITOR_DIR=/Applications/dart`. This is only for illustrative purposes.
|
||||
|
||||
```shell
|
||||
# DARTIUM_BIN: path to a Dartium browser executable; used by Karma to run Dart tests
|
||||
export DARTIUM_BIN="$DART_EDITOR_DIR/chromium/Chromium.app/Contents/MacOS/Chromium"
|
||||
```
|
||||
|
||||
Add the Dart SDK `bin` directory to your path and/or define `DART_SDK` (this is also detailed
|
||||
[here](https://www.dartlang.org/tools/pub/installing.html)):
|
||||
|
||||
```shell
|
||||
# DART_SDK: path to a Dart SDK directory
|
||||
export DART_SDK="$DART_EDITOR_DIR/dart-sdk"
|
||||
|
||||
# Update PATH to include the Dart SDK bin directory
|
||||
PATH+=":$DART_SDK/bin"
|
||||
```
|
||||
|
||||
And specify where the pub’s dependencies are downloaded. By default, this directory is located under .pub_cache
|
||||
in your home directory (on Mac and Linux), or in AppData\Roaming\Pub\Cache (on Windows).
|
||||
|
||||
```shell
|
||||
# PUB_CACHE: location of pub dependencies
|
||||
export PUB_CACHE="/Users/<user>/.pub-cache"
|
||||
```
|
||||
|
||||
## Installing NPM Modules and Dart Packages
|
||||
|
||||
Next, install the JavaScript modules and Dart packages needed to build and test Angular:
|
||||
Next, install the JavaScript modules needed to build and test Angular:
|
||||
|
||||
```shell
|
||||
# Install Angular project dependencies (package.json)
|
||||
@ -124,243 +74,67 @@ use in these instructions.
|
||||
*Option 2*: defining a bash alias like `alias nbin='PATH=$(npm bin):$PATH'` as detailed in this
|
||||
[Stackoverflow answer](http://stackoverflow.com/questions/9679932/how-to-use-package-installed-locally-in-node-modules/15157360#15157360) and used like this: e.g., `nbin gulp build`.
|
||||
|
||||
## Build commands
|
||||
|
||||
To build Angular and prepare tests, run:
|
||||
## Windows only
|
||||
|
||||
In order to create the right symlinks, run **as administrator**:
|
||||
```shell
|
||||
$(npm bin)/gulp build
|
||||
./scripts/windows/create-symlinks.sh
|
||||
```
|
||||
|
||||
Notes:
|
||||
* Results are put in the `dist` folder.
|
||||
* This will also run `pub get` for the subfolders in `modules` and run `dartanalyzer` for
|
||||
every file that matches `<module>/src/<module>.dart`, e.g. `di/src/di.dart`.
|
||||
Before submitting a PR, do not forget to remove them:
|
||||
```shell
|
||||
./scripts/windows/remove-symlinks.sh
|
||||
```
|
||||
|
||||
You can selectively build either the JS or Dart versions as follows:
|
||||
## Building
|
||||
|
||||
* `$(npm bin)/gulp build.js`
|
||||
* `$(npm bin)/gulp build.dart`
|
||||
|
||||
To clean out the `dist` folder, run:
|
||||
To build Angular run:
|
||||
|
||||
```shell
|
||||
$(npm bin)/gulp clean
|
||||
./build.sh
|
||||
```
|
||||
|
||||
* Results are put in the dist folder.
|
||||
|
||||
## Running Tests Locally
|
||||
|
||||
### Full test suite
|
||||
|
||||
* `npm test`: full test suite for both JS and Dart versions of Angular. These are the same tests
|
||||
that run on Travis.
|
||||
|
||||
You can selectively run either the JS or Dart versions as follows:
|
||||
|
||||
* `$(npm bin)/gulp test.all.js`
|
||||
* `$(npm bin)/gulp test.all.dart`
|
||||
|
||||
### Unit tests
|
||||
|
||||
You can run just the unit tests as follows:
|
||||
|
||||
* `$(npm bin)/gulp test.unit.js`: JS tests in a browser; runs in **watch mode** (i.e.
|
||||
watches the test files for changes and re-runs tests when files are updated).
|
||||
* `$(npm bin)/gulp test.unit.cjs`: JS tests in NodeJS; runs in **watch mode**.
|
||||
* `$(npm bin)/gulp test.unit.dart`: Dart tests in Dartium; runs in **watch mode**.
|
||||
|
||||
If you prefer running tests in "single-run" mode rather than watch mode use:
|
||||
|
||||
* `$(npm bin)/gulp test.unit.js/ci`
|
||||
* `$(npm bin)/gulp test.unit.cjs/ci`
|
||||
* `$(npm bin)/gulp test.unit.dart/ci`
|
||||
|
||||
The task updates the dist folder with transpiled code whenever a source or test file changes, and
|
||||
Karma is run against the new output.
|
||||
|
||||
**Note**: If you want to only run a single test you can alter the test you wish to run by changing
|
||||
`it` to `iit` or `describe` to `ddescribe`. This will only run that individual test and make it
|
||||
much easier to debug. `xit` and `xdescribe` can also be useful to exclude a test and a group of
|
||||
tests respectively.
|
||||
|
||||
**Note**: **watch mode** needs symlinks to work, so if you're using Windows, ensure you have the
|
||||
rights to built them in your operating system. On Windows, only administrators can create symbolic links by default, but you may change the policy. (see [here](https://technet.microsoft.com/library/cc766301.aspx?f=255&MSPPError=-2147217396).)
|
||||
|
||||
### Unit tests with Sauce Labs or Browser Stack
|
||||
|
||||
First, in a terminal, create a tunnel with [Sauce Connect](https://docs.saucelabs.com/reference/sauce-connect/) or [Browser Stack Local](https://www.browserstack.com/local-testing#command-line), and valid credentials.
|
||||
|
||||
Then, in another terminal:
|
||||
- Define the credentials as environment variables, e.g.:
|
||||
```
|
||||
export SAUCE_USERNAME='my_user'; export SAUCE_ACCESS_KEY='my_key';
|
||||
export BROWSER_STACK_USERNAME='my_user'; export BROWSER_STACK_ACCESS_KEY='my_key';
|
||||
```
|
||||
- Then run `gulp test.unit.js.(sauce|browserstack) --browsers=option1,option2,..,optionN`
|
||||
The options are any mix of browsers and aliases which are defined in the [browser-providers.conf.js](https://github.com/angular/angular/blob/master/browser-providers.conf.js) file.
|
||||
They are case insensitive, and the `SL_` or `BS_` prefix must not be added for browsers.
|
||||
|
||||
Some examples of commands:
|
||||
```
|
||||
gulp test.unit.js.sauce --browsers=Safari8,ie11 //run in Sauce Labs with Safari 8 and IE11
|
||||
gulp test.unit.js.browserstack --browsers=Safari,IE //run in Browser Stack with Safari 7, Safari 8, Safari 9, IE 9, IE 10 and IE 11
|
||||
gulp test.unit.js.sauce --browsers=IOS,safari8,android5.1 //run in Sauce Labs with iOS 7, iOS 8, iOs 9, Safari 8 and Android 5.1
|
||||
```
|
||||
|
||||
### E2E tests
|
||||
|
||||
1. `$(npm bin)/gulp build.js.cjs` (builds benchpress and tests into `dist/js/cjs` folder).
|
||||
2. `$(npm bin)/gulp serve.js.prod serve.dart` (runs a local webserver).
|
||||
3. `$(npm bin)/protractor protractor-js.conf.js`: JS e2e tests.
|
||||
4. `$(npm bin)/protractor protractor-dart2js.conf.js`: dart2js e2e tests.
|
||||
|
||||
Angular specific command line options when running protractor:
|
||||
- `$(npm bin)/protractor protractor-{js|dart2js}-conf.js --ng-help`
|
||||
|
||||
### Performance tests
|
||||
|
||||
1. `$(npm bin)/gulp build.js.cjs` (builds benchpress and tests into `dist/js/cjs` folder)
|
||||
2. `$(npm bin)/gulp serve.js.prod serve.dart` (runs a local webserver)
|
||||
3. `$(npm bin)/protractor protractor-js.conf.js --benchmark`: JS performance tests
|
||||
4. `$(npm bin)/protractor protractor-dart2js.conf.js --benchmark`: dart2js performance tests
|
||||
|
||||
Angular specific command line options when running protractor (e.g. force gc, ...):
|
||||
`$(npm bin)/protractor protractor-{js|dart2js}-conf.js --ng-help`
|
||||
|
||||
## Code Style
|
||||
|
||||
### Formatting with <a name="clang-format">clang-format</a>
|
||||
|
||||
We use [clang-format](http://clang.llvm.org/docs/ClangFormat.html) to automatically enforce code
|
||||
style for our TypeScript code. This allows us to focus our code reviews more on the content, and
|
||||
less on style nit-picking. It also lets us encode our style guide in the `.clang-format` file in the
|
||||
repository, allowing many tools and editors to share our settings.
|
||||
|
||||
To check the formatting of your code, run
|
||||
|
||||
gulp lint
|
||||
|
||||
Note that the continuous build on CircleCI will fail the build if files aren't formatted according
|
||||
to the style guide.
|
||||
|
||||
Your life will be easier if you include the formatter in your standard workflow. Otherwise, you'll
|
||||
likely forget to check the formatting, and waste time waiting for a build on Travis that fails due
|
||||
to some whitespace difference.
|
||||
|
||||
* Use `gulp format` to format everything.
|
||||
* Use `gulp lint` to check if your code is `clang-format` clean. This also gives
|
||||
you a command line to format your code.
|
||||
* `clang-format` also includes a git hook, run `git clang-format` to format all files you
|
||||
touched.
|
||||
* You can run this as a **git pre-commit hook** to automatically format your delta regions when you
|
||||
commit a change. In the angular repo, run
|
||||
|
||||
```
|
||||
$ echo -e '#!/bin/sh\nexec git clang-format --style file' > .git/hooks/pre-commit
|
||||
$ chmod u+x !$
|
||||
```
|
||||
|
||||
**NOTE**: To use ```git clang-format``` use have to make sure that ```git-clang-format``` is in your
|
||||
```PATH```. The easiest way is probably to just ```npm install -g clang-format``` as it comes with
|
||||
```git-clang-format```.
|
||||
|
||||
* **WebStorm** can run clang-format on the current file.
|
||||
1. Under Preferences, open Tools > External Tools.
|
||||
1. Plus icon to Create Tool
|
||||
1. Fill in the form:
|
||||
- Name: clang-format
|
||||
- Description: Format
|
||||
- Synchronize files after execution: checked
|
||||
- Open console: not checked
|
||||
- Show in: Editor menu
|
||||
- Program: `$ProjectFileDir$/node_modules/.bin/clang-format`
|
||||
- Parameters: `-i -style=file $FilePath$`
|
||||
- Working directory: `$ProjectFileDir$`
|
||||
* `clang-format` integrations are also available for many popular editors (`vim`, `emacs`,
|
||||
`Sublime Text`, etc.).
|
||||
|
||||
### Linting
|
||||
|
||||
We use [tslint](https://github.com/palantir/tslint) for linting. See linting rules in [gulpfile](gulpfile.js). To lint, run
|
||||
To run tests:
|
||||
|
||||
```shell
|
||||
$ gulp lint
|
||||
$ ./test.sh node # Run all angular tests on node
|
||||
|
||||
$ ./test.sh browser # Run all angular tests in browser
|
||||
$ ./test.sh browserNoRouter # Optionally run all angular tests without router in browser
|
||||
|
||||
$ ./test.sh tools # Run angular tooling (not framework) tests
|
||||
```
|
||||
|
||||
## Generating the API documentation
|
||||
You should execute the 3 test suites before submitting a PR to github.
|
||||
|
||||
The following gulp task will generate the API docs in the `dist/angular.io/partials/api/angular2`:
|
||||
All the tests are executed on our Continuous Integration infrastructure and a PR could only be merged once the tests pass.
|
||||
|
||||
```shell
|
||||
$(npm bin)/gulp docs/angular.io
|
||||
- CircleCI fails if your code is not formatted properly,
|
||||
- Travis CI fails if any of the test suite describe above fails.
|
||||
|
||||
## Update the public API tests
|
||||
|
||||
If you happen to modify the public API of Angular, API golden files must be updated using:
|
||||
|
||||
``` shell
|
||||
$ gulp public-api:update
|
||||
```
|
||||
|
||||
You can serve the generated documentation to check how it would render on [angular.io](https://angular.io/):
|
||||
- check out the [angular.io repo](https://github.com/angular/angular.io) locally,
|
||||
- install dependencies as described in the [angular.io README](https://github.com/angular/angular.io/blob/master/README.md),
|
||||
- copy the generated documentation from your local angular repo at `angular/dist/angular.io/partials/api/angular2` to your local angular.io repo at `angular.io/public/docs/js/latest/api`,
|
||||
- run `harp compile` at the root of the angular.io repo to check the generated documentation for errors,
|
||||
- run `harp server` and open a browser at `http://localhost:9000/docs/js/latest/api/` to check the rendered documentation.
|
||||
Note: The command `./test.sh tools` fails when the API doesn't match the golden files.
|
||||
|
||||
## Project Information
|
||||
## Formatting your source code
|
||||
|
||||
### Folder structure
|
||||
Angular uses [clang-format](http://clang.llvm.org/docs/ClangFormat.html) to format the source code. If the source code
|
||||
is not properly formatted, the CI will fail and the PR can not be merged.
|
||||
|
||||
* `modules/*`: modules that will be loaded in the browser
|
||||
* `tools/*`: tools that are needed to build Angular
|
||||
* `dist/*`: build files are placed here.
|
||||
You can automatically format your code by running:
|
||||
|
||||
### File suffixes
|
||||
``` shell
|
||||
$ gulp format
|
||||
```
|
||||
|
||||
* `*.ts`: TypeScript files that get transpiled to Dart and EcmaScript 5/6
|
||||
* `*.dart`: Dart files that don't get transpiled
|
||||
|
||||
## CI using Travis
|
||||
|
||||
For instructions on setting up Continuous Integration using Travis, see the instructions given
|
||||
[here](https://github.com/angular/angular.dart/blob/master/travis.md).
|
||||
|
||||
## Transforming Dart code
|
||||
|
||||
See the [wiki](//github.com/angular/angular/wiki/Angular-2-Dart-Transformer).
|
||||
|
||||
## Debugging
|
||||
|
||||
### Debug the transpiler
|
||||
|
||||
If you need to debug the transpiler:
|
||||
|
||||
- add a `debugger;` statement in the transpiler code,
|
||||
- from the root folder, execute `node debug $(npm bin)/gulp build` to enter the node
|
||||
debugger
|
||||
- press "c" to execute the program until you reach the `debugger;` statement,
|
||||
- you can then type "repl" to enter the REPL and inspect variables in the context.
|
||||
|
||||
See the [Node.js manual](http://nodejs.org/api/debugger.html) for more information.
|
||||
|
||||
Notes:
|
||||
- You can also execute `node $(npm bin)/karma start karma-dart.conf.js` depending on which
|
||||
code you want to debug (the former will process the "modules" folder while the later processes
|
||||
the transpiler specs).
|
||||
- You can also add `debugger;` statements in the specs (JavaScript). The execution will halt when
|
||||
the developer tools are opened in the browser running Karma.
|
||||
|
||||
### Debug the tests
|
||||
|
||||
If you need to debug the tests:
|
||||
|
||||
- add a `debugger;` statement to the test you want to debug (or the source code),
|
||||
- execute karma `$(npm bin)/gulp test.js`,
|
||||
- press the top right "DEBUG" button,
|
||||
- open the DevTools and press F5,
|
||||
- the execution halts at the `debugger;` statement
|
||||
|
||||
**Note (WebStorm users)**:
|
||||
|
||||
1. Create a Karma run config from WebStorm.
|
||||
2. Then in the "Run" menu, press "Debug 'karma-js.conf.js'", and WebStorm will stop in the generated
|
||||
code on the `debugger;` statement.
|
||||
3. You can then step into the code and add watches.
|
||||
|
||||
The `debugger;` statement is needed because WebStorm will stop in a transpiled file. Breakpoints in
|
||||
the original source files are not supported at the moment.
|
||||
|
12
README.md
12
README.md
@ -6,15 +6,17 @@
|
||||
[](https://badge.fury.io/js/%40angular%2Fcore)
|
||||
[](https://npmjs.org/package/angular2)
|
||||
|
||||
[](https://saucelabs.com/u/angular2-ci)
|
||||
[](https://saucelabs.com/u/angular2-ci)
|
||||
*Safari (7+), iOS (7+), Edge (14) and IE mobile (11) are tested on [BrowserStack][browserstack].*
|
||||
|
||||
Angular
|
||||
=========
|
||||
|
||||
Angular is a development platform for building mobile and desktop web applications. This is the
|
||||
repository for [Angular 2][ng2], both the JavaScript (JS) and [Dart][dart] versions.
|
||||
repository for [Angular 2][ng2] Typescript/JavaScript (JS).
|
||||
|
||||
Angular2 for [Dart][dart] can be found at [dart-lang/angular2][ng2dart].
|
||||
|
||||
Angular 2 is currently in **Release Candidate**.
|
||||
|
||||
## Quickstart
|
||||
|
||||
@ -26,11 +28,11 @@ Angular 2 is currently in **Release Candidate**.
|
||||
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).
|
||||
|
||||
|
||||
[browserstack]: https://www.browserstack.com/
|
||||
[contributing]: http://github.com/angular/angular/blob/master/CONTRIBUTING.md
|
||||
[dart]: http://www.dartlang.org
|
||||
[dartium]: http://www.dartlang.org/tools/dartium
|
||||
[quickstart]: https://angular.io/docs/ts/latest/quickstart.html
|
||||
[ng2]: http://angular.io
|
||||
[ngDart]: http://angulardart.org
|
||||
[ngJS]: http://angularjs.org
|
||||
[ng2dart]: https://github.com/dart-lang/angular2
|
||||
|
140
TOOLS.md
140
TOOLS.md
@ -1,4 +1,140 @@
|
||||
# Developer Tools for Angular 2
|
||||
|
||||
- [JavaScript](TOOLS_JS.md)
|
||||
- [Dart](TOOLS_DART.md)
|
||||
Here you will find a collection of tools and tips for keeping your application
|
||||
perform well and contain fewer bugs.
|
||||
|
||||
## Angular debug tools in the dev console
|
||||
|
||||
Angular provides a set of debug tools that are accessible from any browser's
|
||||
developer console. In Chrome the dev console can be accessed by pressing
|
||||
Ctrl + Shift + j.
|
||||
|
||||
### Enabling debug tools
|
||||
|
||||
By default the debug tools are disabled. You can enable debug tools as follows:
|
||||
|
||||
```typescript
|
||||
import {enableDebugTools} from '@angular/platform-browser';
|
||||
|
||||
bootstrap(Application).then((appRef) => {
|
||||
enableDebugTools(appRef);
|
||||
});
|
||||
```
|
||||
|
||||
### Using debug tools
|
||||
|
||||
In the browser open the developer console (Ctrl + Shift + j in Chrome). The
|
||||
top level object is called `ng` and contains more specific tools inside it.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
ng.profiler.timeChangeDetection();
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
### Change detection profiler
|
||||
|
||||
If your application is janky (it misses frames) or is slow according to other
|
||||
metrics it is important to find the root cause of the issue. Change detection
|
||||
is a phase in Angular's lifecycle that detects changes in values that are
|
||||
bound to UI, and if it finds a change it performs the corresponding UI update.
|
||||
However, sometimes it is hard to tell if the slowness is due to the act of
|
||||
computing the changes being slow, or due to the act of applying those changes
|
||||
to the UI. For your application to be performant it is important that the
|
||||
process of computing changes is very fast. For best results it should be under
|
||||
3 milliseconds in order to leave room for the application logic, the UI updates
|
||||
and browser's rendering pipeline to fit withing the 16 millisecond frame
|
||||
(assuming the 60 FPS target frame rate).
|
||||
|
||||
Change detection profiler repeatedly performs change detection without invoking
|
||||
any user actions, such as clicking buttons or entering text in input fields. It
|
||||
then computes the average amount of time it took to perform a single cycle of
|
||||
change detection in milliseconds and prints it to the console. This number
|
||||
depends on the current state of the UI. You will likely see different numbers
|
||||
as you go from one screen in your application to another.
|
||||
|
||||
#### Running the profiler
|
||||
|
||||
Enable debug tools (see above), then in the dev console enter the following:
|
||||
|
||||
```javascript
|
||||
ng.profiler.timeChangeDetection();
|
||||
```
|
||||
|
||||
The results will be printed to the console.
|
||||
|
||||
#### Recording CPU profile
|
||||
|
||||
Pass `{record: true}` an argument:
|
||||
|
||||
```javascript
|
||||
ng.profiler.timeChangeDetection({record: true});
|
||||
```
|
||||
|
||||
Then open the "Profiles" tab. You will see the recorded profile titled
|
||||
"Change Detection". In Chrome, if you record the profile repeatedly, all the
|
||||
profiles will be nested under "Change Detection".
|
||||
|
||||
#### Interpreting the numbers
|
||||
|
||||
In a properly-designed application repeated attempts to detect changes without
|
||||
any user actions should result in no changes to be applied on the UI. It is
|
||||
also desirable to have the cost of a user action be proportional to the amount
|
||||
of UI changes required. For example, popping up a menu with 5 items should be
|
||||
vastly faster than rendering a table of 500 rows and 10 columns. Therefore,
|
||||
change detection with no UI updates should be as fast as possible. Ideally the
|
||||
number printed by the profiler should be well below the length of a single
|
||||
animation frame (16ms). A good rule of thumb is to keep it under 3ms.
|
||||
|
||||
#### Investigating slow change detection
|
||||
|
||||
So you found a screen in your application on which the profiler reports a very
|
||||
high number (i.e. >3ms). This is where a recorded CPU profile can help. Enable
|
||||
recording while profiling:
|
||||
|
||||
```javascript
|
||||
ng.profiler.timeChangeDetection({record: true});
|
||||
```
|
||||
|
||||
Then look for hot spots using
|
||||
[Chrome CPU profiler](https://developer.chrome.com/devtools/docs/cpu-profiling).
|
||||
|
||||
#### Reducing change detection cost
|
||||
|
||||
There are many reasons for slow change detection. To gain intuition about
|
||||
possible causes it would help to understand how change detection works. Such a
|
||||
discussion is outside the scope of this document (TODO link to docs), but here
|
||||
are some key concepts in brief.
|
||||
|
||||
By default Angular uses "dirty checking" mechanism for finding model changes.
|
||||
This mechanism involves evaluating every bound expression that's active on the
|
||||
UI. These usually include text interpolation via `{{expression}}` and property
|
||||
bindings via `[prop]="expression"`. If any of the evaluated expressions are
|
||||
costly to compute they could contribute to slow change detection. A good way to
|
||||
speed things up is to use plain class fields in your expressions and avoid any
|
||||
kinds of computation. Example:
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
template: '<button [enabled]="isEnabled">{{title}}</button>'
|
||||
})
|
||||
class FancyButton {
|
||||
// GOOD: no computation, just return the value
|
||||
isEnabled: boolean;
|
||||
|
||||
// BAD: computes the final value upon request
|
||||
_title: String;
|
||||
get title(): String { return this._title.trim().toUpperCase(); }
|
||||
}
|
||||
```
|
||||
|
||||
Most cases like these could be solved by precomputing the value and storing the
|
||||
final value in a field.
|
||||
|
||||
Angular also supports a second type of change detection - the "push" model. In
|
||||
this model Angular does not poll your component for changes. Instead, the
|
||||
component "tells" Angular when it changes and only then does Angular perform
|
||||
the update. This model is suitable in situations when your data model uses
|
||||
observable or immutable objects (also a discussion for another time).
|
||||
|
376
TOOLS_DART.md
376
TOOLS_DART.md
@ -1,376 +0,0 @@
|
||||
# Developer Tools for Dart
|
||||
|
||||
Use these tools and techniques to increase your app's performance
|
||||
and reliability.
|
||||
|
||||
* [Angular debugging tools](#angular-debugging-tools)
|
||||
* [Code size](#code-size)
|
||||
* [Performance](#performance)
|
||||
|
||||
|
||||
## Angular debugging tools
|
||||
|
||||
Starting with alpha.38, Angular provides a set of debugging tools
|
||||
that are accessible from any browser's developer console.
|
||||
In Chrome, you can get to the dev console by pressing
|
||||
Ctrl + Shift + J (on Mac: Cmd + Opt + J).
|
||||
|
||||
### Enabling the debugging tools
|
||||
|
||||
By default the debugging tools are disabled.
|
||||
Enable the debugging tools as follows:
|
||||
|
||||
```dart
|
||||
import 'package:angular2/platform/browser.dart';
|
||||
|
||||
main() async {
|
||||
var appRef = await bootstrap(Application);
|
||||
enableDebugTools(appRef);
|
||||
}
|
||||
```
|
||||
|
||||
<!-- Change function name to enableDebuggingTools? -->
|
||||
|
||||
|
||||
### Using the debugging tools
|
||||
|
||||
In the browser, open the dev console. The top-level object is called `ng` and
|
||||
contains more specific tools inside it.
|
||||
|
||||
For example, to run the change detection profiler on your app:
|
||||
|
||||
```javascript
|
||||
// In the dev console:
|
||||
ng.profiler.timeChangeDetection();
|
||||
```
|
||||
|
||||
The [Change detection profiler](#change-detection-profiler) section
|
||||
has more details.
|
||||
<!-- Point to API docs when they're published, if they're useful.
|
||||
They should be under
|
||||
http://www.dartdocs.org/documentation/angular2/latest
|
||||
and/or
|
||||
https://angular.io/docs/js/latest/api/. -->
|
||||
|
||||
|
||||
## Code size
|
||||
|
||||
Code must be downloaded, parsed, and executed. Too much code can lead to
|
||||
slow application start-up time, especially on slow networks and low-end devices.
|
||||
The tools and techniques in this section can help you to identify
|
||||
unnecessarily large code and to reduce code size.
|
||||
|
||||
### Finding contributors to code size
|
||||
|
||||
Options for investigating code size include the `--dump-info` dart2js option,
|
||||
ng2soyc, `reflector.trackUsage()`, and code coverage information
|
||||
from the Dart VM.
|
||||
|
||||
#### Use --dump-info
|
||||
|
||||
The `--dump-info` option of `dart2js` outputs information about what happened
|
||||
during compilation. You can specify `--dump-info` in `pubspec.yaml`:
|
||||
|
||||
```yaml
|
||||
transformers:
|
||||
...
|
||||
- $dart2js:
|
||||
commandLineOptions:
|
||||
- --dump-info
|
||||
```
|
||||
|
||||
The [Dump Info Visualizer](https://github.com/dart-lang/dump-info-visualizer)
|
||||
can help you analyze the output.
|
||||
For more information, see the
|
||||
[dart2js_info API reference](http://dart-lang.github.io/dart2js_info/doc/api/).
|
||||
|
||||
#### Use ng2soyc.dart
|
||||
|
||||
[ng2soyc](https://github.com/angular/ng2soyc.dart) is a utility for analyzing
|
||||
code size contributors in Angular 2 applications. It groups code size by
|
||||
library and, assuming your library names follow
|
||||
[standard naming conventions](https://www.dartlang.org/articles/style-guide/#do-prefix-library-names-with-the-package-name-and-a-dot-separated-path)
|
||||
(package.library.sublibrary...), gives the code size breakdown at
|
||||
each level. To reduce noise in the output of very large apps, ng2soyc provides
|
||||
an option to hide libraries that are too small, so you can focus on the biggest
|
||||
contributors.
|
||||
|
||||
#### Find unused reflection data
|
||||
|
||||
Your app might have types that are annotated with `@Component` or `@Injectable`
|
||||
but never used.
|
||||
To find these unused types, use `reflector.trackUsage()` and then,
|
||||
after exercising your app, `reflector.listUnusedKeys()`.
|
||||
For example:
|
||||
|
||||
```
|
||||
import 'package:angular2/src/core/reflection/reflection.dart';
|
||||
...
|
||||
main() async {
|
||||
reflector.trackUsage();
|
||||
await bootstrap(AppComponent);
|
||||
print('Unused keys: ${reflector.listUnusedKeys()}');
|
||||
}
|
||||
```
|
||||
|
||||
When you run that code (in Dartium or another browser),
|
||||
you'll see a list of types that Angular _can_ inject but hasn't needed to.
|
||||
Consider removing those types or their `@Component`/`@Injectable` annotation
|
||||
to decrease your app's code size.
|
||||
|
||||
Three conditions must be true for `listUnusedKeys()` to return helpful data:
|
||||
|
||||
1. The angular2 transformer must run on the app.
|
||||
2. If you're running a JavaScript version of the app,
|
||||
the app must not be minified, so that the names are readable.
|
||||
3. You must exercise your app in as many ways as possible
|
||||
before calling `listUnusedKeys()`.
|
||||
Otherwise, you might get false positives:
|
||||
keys that haven't been used only because you didn't exercise
|
||||
the relevant feature of the app.
|
||||
|
||||
To run the angular2 transformer, first specify it in `pubspec.yaml`:
|
||||
|
||||
```
|
||||
name: hello_world
|
||||
...
|
||||
transformers:
|
||||
- angular2:
|
||||
entry_points: web/main.dart
|
||||
```
|
||||
|
||||
Then use pub to run the transformer. If you use `pub serve`,
|
||||
it provides both Dart and unminified (by default) JavaScript versions.
|
||||
If you want to serve actual files, then use `pub build` in debug mode
|
||||
to generate Dart and unminified JavaScript files:
|
||||
`pub build --mode=debug`.
|
||||
|
||||
The `reflector.trackUsage()` method makes Angular track the reflection
|
||||
information used by the app. Reflection information (`ReflectionInfo`) is a data
|
||||
structure that stores information that Angular uses for locating DI factories
|
||||
and for generating change detectors and other code related to a
|
||||
given type.
|
||||
|
||||
#### Use code coverage to find dead code
|
||||
|
||||
When running in Dartium (or in the Dart VM, in general) you can request code
|
||||
coverage information from the VM. You can either use
|
||||
[observatory](https://www.dartlang.org/tools/observatory/) or download
|
||||
the coverage file and use your own tools to inspect it. Lines of code that are
|
||||
not covered are top candidates for dead code.
|
||||
|
||||
Keep in mind, however, that uncovered code is not sufficient evidence of dead
|
||||
code, only necessary evidence. It is perfectly possible that you simply didn't
|
||||
exercise your application in a way that triggers the execution of uncovered
|
||||
code. A common example is error handling code. Just because your testing never
|
||||
encountered an error does not mean the error won't happen in production. You
|
||||
therefore don't have to rush and remove all the `catch` blocks.
|
||||
|
||||
### Reducing code size
|
||||
|
||||
To reduce code size, you can disable reflection,
|
||||
enable minification, and manually remove dead code.
|
||||
You can also try less safe options such as
|
||||
telling dart2js to trust type annotations.
|
||||
|
||||
|
||||
#### Disable reflection
|
||||
|
||||
`dart:mirrors` allows discovering program metadata at runtime. However, this
|
||||
means that `dart2js` needs to retain that metadata and thus increase the size
|
||||
of resulting JS output. In practice, however, it is possible to extract most
|
||||
metadata necessary for your metaprogramming tasks statically using a
|
||||
transformer and `package:analyzer`, and act on it before compiling to JS.
|
||||
|
||||
#### Enable minification
|
||||
|
||||
Minification shortens all your `longMethodNames` into 2- or 3-letter long
|
||||
symbols. `dart2js` ensures that this kind of renaming is done safely, without
|
||||
breaking the functionality of your programs. You can enable it in `pubspec.yaml`
|
||||
under `$dart2js` transformer:
|
||||
|
||||
```yaml
|
||||
transformers:
|
||||
...
|
||||
- $dart2js:
|
||||
minify: true
|
||||
```
|
||||
|
||||
#### Manually remove dead code
|
||||
|
||||
`dart2js` comes with dead code elimination out-of-the-box. However, it may not
|
||||
always be able to tell if a piece of code could be used. Consider the following
|
||||
example:
|
||||
|
||||
```dart
|
||||
/// This function decides which serialization format to use
|
||||
void setupSerializers() {
|
||||
if (server.doYouSupportProtocolBuffers()) {
|
||||
useProtobufSerializers();
|
||||
} else {
|
||||
useJsonSerializers();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this example the application asks the server what kind of serialization
|
||||
format it uses and dynamically chooses one or the other. `dart2js` can't
|
||||
tell whether the server responds with yes or no, so it must retain both
|
||||
kinds of serializers. However, if you know that your server supports
|
||||
protocol buffers, you can remove that `if` block entirely and default to
|
||||
protocol buffers.
|
||||
|
||||
Code coverage (see above) is a good way to find dead code in your app.
|
||||
|
||||
#### Unsafe options
|
||||
|
||||
Dart also provides more aggressive optimization options. However, you have to
|
||||
be careful when using them and as of today the benefits aren't that clear. If
|
||||
your type annotations are inaccurate you may end up with non-Darty runtime
|
||||
behavior, including the classic "undefined is not a function" tautology, as
|
||||
well as the "keep on truckin'" behavior, e.g. `null + 1 == 1` and
|
||||
`{} + [] == 0`.
|
||||
|
||||
`--trust-type-annotations` tells `dart2js` to trust that your type annotations
|
||||
are correct. So if you have a function `foo(Bar bar)` the compiler can omit the
|
||||
check that `bar` is truly `Bar` when calling methods on it.
|
||||
|
||||
`--trust-primitives` tells `dart2js` that primitive types, such as numbers and
|
||||
booleans are never `null` when performing arithmetic, and that your program
|
||||
does not run into range error when operating on lists, letting the compiler
|
||||
remove some of the error checking code.
|
||||
|
||||
Specify these options in `pubspec.yaml`.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
transformers:
|
||||
...
|
||||
- $dart2js:
|
||||
commandLineOptions:
|
||||
- --trust-type-annotations
|
||||
- --trust-primitives
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
### Change detection profiler
|
||||
|
||||
If your application is janky (it misses frames) or is slow according to other
|
||||
metrics, you need to find out why. This tool helps by measuring the average
|
||||
speed of _change detection_, a phase in Angular's
|
||||
lifecycle that detects changes in values that are bound to the UI.
|
||||
Janky UI updates can result from slowness either in _computing_ the changes or
|
||||
in _applying_ those changes to the UI.
|
||||
|
||||
For your app to be performant, the process of _computing_ changes must be very
|
||||
fast—preferably **under 3 milliseconds**.
|
||||
Fast change computation leaves room for
|
||||
the application logic, UI updates, and browser rendering pipeline
|
||||
to fit within a 16 ms frame (assuming a target frame rate of 60 FPS).
|
||||
|
||||
The change detection profiler repeatedly performs change detection
|
||||
without invoking any user actions, such as clicking buttons or entering
|
||||
text in input fields. It then computes the average amount of time
|
||||
(in milliseconds) to perform a single cycle of change detection and
|
||||
prints that to the console. This number depends on the current state of the UI. You are likely to see different numbers
|
||||
as you go from one screen in your application to another.
|
||||
|
||||
#### Running the profiler
|
||||
|
||||
Before running the profiler, enable the debugging tools
|
||||
and put the app into the state you want to measure:
|
||||
|
||||
1. If you haven't already done so,
|
||||
[enable the debugging tools](#enabling-the-debugging-tools).
|
||||
2. Navigate the app to a screen whose performance you want to profile.
|
||||
3. Make sure the screen is in a state that you want to measure.
|
||||
For example, you might want to profile the screen several times,
|
||||
with different amounts and kinds of data.
|
||||
|
||||
To run the profiler, enter the following in the dev console:
|
||||
|
||||
```javascript
|
||||
ng.profiler.timeChangeDetection();
|
||||
```
|
||||
|
||||
The results are visible in the console.
|
||||
|
||||
|
||||
#### Recording CPU profiles
|
||||
|
||||
To record a profile, pass `{record: true}` to `timeChangeDetection()`:
|
||||
|
||||
```javascript
|
||||
ng.profiler.timeChangeDetection({record: true});
|
||||
```
|
||||
|
||||
Then open the **Profiles** tab. The recorded profile has the title
|
||||
**Change Detection**. In Chrome, if you record the profile repeatedly, all the
|
||||
profiles are nested under Change Detection.
|
||||
|
||||
|
||||
#### Interpreting the numbers
|
||||
|
||||
In a properly designed application, repeated attempts to detect changes without
|
||||
any user actions result in no changes to the UI. It is
|
||||
also desirable to have the cost of a user action be proportional to the amount
|
||||
of UI changes required. For example, popping up a menu with 5 items should be
|
||||
vastly faster than rendering a table of 500 rows and 10 columns. Therefore,
|
||||
change detection with no UI updates should be as fast as possible.
|
||||
|
||||
#### Investigating slow change detection
|
||||
|
||||
So you found a screen in your application on which the profiler reports a very
|
||||
high number (i.e. >3ms). This is where a recorded CPU profile can help. Enable
|
||||
recording while profiling:
|
||||
|
||||
```javascript
|
||||
ng.profiler.timeChangeDetection({record: true});
|
||||
```
|
||||
|
||||
Then look for hot spots using
|
||||
[Chrome CPU profiler](https://developer.chrome.com/devtools/docs/cpu-profiling).
|
||||
|
||||
#### Reducing change detection cost
|
||||
|
||||
There are many reasons for slow change detection. To gain intuition about
|
||||
possible causes it helps to understand how change detection works. Such a
|
||||
discussion is outside the scope of this document,
|
||||
but here are some key concepts.
|
||||
|
||||
<!-- TODO: link to change detection docs -->
|
||||
|
||||
By default, Angular uses a _dirty checking_ mechanism to find model changes.
|
||||
This mechanism involves evaluating every bound expression that's active on the
|
||||
UI. These usually include text interpolation via `{{expression}}` and property
|
||||
bindings via `[prop]="expression"`. If any of the evaluated expressions are
|
||||
costly to compute, they might contribute to slow change detection. A good way to
|
||||
speed things up is to use plain class fields in your expressions and avoid any
|
||||
kind of computation. For example:
|
||||
|
||||
```dart
|
||||
@View(
|
||||
template: '<button [enabled]="isEnabled">{{title}}</button>'
|
||||
)
|
||||
class FancyButton {
|
||||
// GOOD: no computation, just returns the value
|
||||
bool isEnabled;
|
||||
|
||||
// BAD: computes the final value upon request
|
||||
String _title;
|
||||
String get title => _title.trim().toUpperCase();
|
||||
}
|
||||
```
|
||||
|
||||
Most cases like these can be solved by precomputing the value and storing the
|
||||
final value in a field.
|
||||
|
||||
Angular also supports a second type of change detection: the _push_ model. In
|
||||
this model, Angular does not poll your component for changes. Instead, the
|
||||
component tells Angular when it changes, and only then does Angular perform
|
||||
the update. This model is suitable in situations when your data model uses
|
||||
observable or immutable objects.
|
||||
|
||||
<!-- TODO: link to discussion of push model -->
|
140
TOOLS_JS.md
140
TOOLS_JS.md
@ -1,140 +0,0 @@
|
||||
# Developer Tools for JavaScript
|
||||
|
||||
Here you will find a collection of tools and tips for keeping your application
|
||||
perform well and contain fewer bugs.
|
||||
|
||||
## Angular debug tools in the dev console
|
||||
|
||||
Angular provides a set of debug tools that are accessible from any browser's
|
||||
developer console. In Chrome the dev console can be accessed by pressing
|
||||
Ctrl + Shift + j.
|
||||
|
||||
### Enabling debug tools
|
||||
|
||||
By default the debug tools are disabled. You can enable debug tools as follows:
|
||||
|
||||
```typescript
|
||||
import {enableDebugTools} from '@angular/platform-browser';
|
||||
|
||||
bootstrap(Application).then((appRef) => {
|
||||
enableDebugTools(appRef);
|
||||
});
|
||||
```
|
||||
|
||||
### Using debug tools
|
||||
|
||||
In the browser open the developer console (Ctrl + Shift + j in Chrome). The
|
||||
top level object is called `ng` and contains more specific tools inside it.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
ng.profiler.timeChangeDetection();
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
### Change detection profiler
|
||||
|
||||
If your application is janky (it misses frames) or is slow according to other
|
||||
metrics it is important to find the root cause of the issue. Change detection
|
||||
is a phase in Angular's lifecycle that detects changes in values that are
|
||||
bound to UI, and if it finds a change it performs the corresponding UI update.
|
||||
However, sometimes it is hard to tell if the slowness is due to the act of
|
||||
computing the changes being slow, or due to the act of applying those changes
|
||||
to the UI. For your application to be performant it is important that the
|
||||
process of computing changes is very fast. For best results it should be under
|
||||
3 milliseconds in order to leave room for the application logic, the UI updates
|
||||
and browser's rendering pipeline to fit withing the 16 millisecond frame
|
||||
(assuming the 60 FPS target frame rate).
|
||||
|
||||
Change detection profiler repeatedly performs change detection without invoking
|
||||
any user actions, such as clicking buttons or entering text in input fields. It
|
||||
then computes the average amount of time it took to perform a single cycle of
|
||||
change detection in milliseconds and prints it to the console. This number
|
||||
depends on the current state of the UI. You will likely see different numbers
|
||||
as you go from one screen in your application to another.
|
||||
|
||||
#### Running the profiler
|
||||
|
||||
Enable debug tools (see above), then in the dev console enter the following:
|
||||
|
||||
```javascript
|
||||
ng.profiler.timeChangeDetection();
|
||||
```
|
||||
|
||||
The results will be printed to the console.
|
||||
|
||||
#### Recording CPU profile
|
||||
|
||||
Pass `{record: true}` an argument:
|
||||
|
||||
```javascript
|
||||
ng.profiler.timeChangeDetection({record: true});
|
||||
```
|
||||
|
||||
Then open the "Profiles" tab. You will see the recorded profile titled
|
||||
"Change Detection". In Chrome, if you record the profile repeatedly, all the
|
||||
profiles will be nested under "Change Detection".
|
||||
|
||||
#### Interpreting the numbers
|
||||
|
||||
In a properly-designed application repeated attempts to detect changes without
|
||||
any user actions should result in no changes to be applied on the UI. It is
|
||||
also desirable to have the cost of a user action be proportional to the amount
|
||||
of UI changes required. For example, popping up a menu with 5 items should be
|
||||
vastly faster than rendering a table of 500 rows and 10 columns. Therefore,
|
||||
change detection with no UI updates should be as fast as possible. Ideally the
|
||||
number printed by the profiler should be well below the length of a single
|
||||
animation frame (16ms). A good rule of thumb is to keep it under 3ms.
|
||||
|
||||
#### Investigating slow change detection
|
||||
|
||||
So you found a screen in your application on which the profiler reports a very
|
||||
high number (i.e. >3ms). This is where a recorded CPU profile can help. Enable
|
||||
recording while profiling:
|
||||
|
||||
```javascript
|
||||
ng.profiler.timeChangeDetection({record: true});
|
||||
```
|
||||
|
||||
Then look for hot spots using
|
||||
[Chrome CPU profiler](https://developer.chrome.com/devtools/docs/cpu-profiling).
|
||||
|
||||
#### Reducing change detection cost
|
||||
|
||||
There are many reasons for slow change detection. To gain intuition about
|
||||
possible causes it would help to understand how change detection works. Such a
|
||||
discussion is outside the scope of this document (TODO link to docs), but here
|
||||
are some key concepts in brief.
|
||||
|
||||
By default Angular uses "dirty checking" mechanism for finding model changes.
|
||||
This mechanism involves evaluating every bound expression that's active on the
|
||||
UI. These usually include text interpolation via `{{expression}}` and property
|
||||
bindings via `[prop]="expression"`. If any of the evaluated expressions are
|
||||
costly to compute they could contribute to slow change detection. A good way to
|
||||
speed things up is to use plain class fields in your expressions and avoid any
|
||||
kinds of computation. Example:
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
template: '<button [enabled]="isEnabled">{{title}}</button>'
|
||||
})
|
||||
class FancyButton {
|
||||
// GOOD: no computation, just return the value
|
||||
isEnabled: boolean;
|
||||
|
||||
// BAD: computes the final value upon request
|
||||
_title: String;
|
||||
get title(): String { return this._title.trim().toUpperCase(); }
|
||||
}
|
||||
```
|
||||
|
||||
Most cases like these could be solved by precomputing the value and storing the
|
||||
final value in a field.
|
||||
|
||||
Angular also supports a second type of change detection - the "push" model. In
|
||||
this model Angular does not poll your component for changes. Instead, the
|
||||
component "tells" Angular when it changes and only then does Angular perform
|
||||
the update. This model is suitable in situations when your data model uses
|
||||
observable or immutable objects (also a discussion for another time).
|
@ -1,25 +1,72 @@
|
||||
# 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 new 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
|
||||
## Triaged vs Untriaged Issues
|
||||
Once this process is implemented and in use, we will revisit it to see
|
||||
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.
|
||||
* `component` -- Which area of Angular knowledge this relates to.
|
||||
* `effort` -- Rough assessment of how much work this issue is. E.g. `effort: easy` means
|
||||
"probably a few hours of work".
|
||||
* `type` -- Whether this issue is a bug, feature, or other kind of task.
|
||||
A caretaker should be able to determine which component the issue
|
||||
belongs to. The components have a clear piece of source code associated
|
||||
with it.
|
||||
|
||||
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: zone`: `@mhevery`
|
||||
|
||||
You can view a report of untriaged issues here, in our
|
||||
[Angular Triage Dashboard](http://mhevery.github.io/github_issues/).
|
||||
There are few components which are cross-cutting. They don't have
|
||||
a clear location in the source tree. We will treat them as a component
|
||||
even thought no specific source tree is associated with them.
|
||||
|
||||
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.
|
||||
* `comp: documentation`: `@naomiblack`
|
||||
* `comp: packaging`: `@mhevery`
|
||||
* `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: *` and `type: *`
|
||||
to each new issue as they come in. The reason why we limit the
|
||||
responsibility of the caretaker to these two labels is that it is
|
||||
unlikely that without domain knowledge the caretaker could add any
|
||||
additional labels of value.
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
### Assigning Issues to Milestones
|
||||
|
||||
@ -37,7 +84,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)
|
||||
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
|
||||
closing or reviewing PRs is a top priority ahead of other ongoing work.
|
||||
@ -63,90 +113,6 @@ uncontroversial change.
|
||||
PRs do not need to be assigned to milestones, unless a milestone release should be held for that
|
||||
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 → *tbosch*, *crossj*
|
||||
* `comp: build/dev-productivity` -- build process, e.g. CLI and related tasks → *iminar*, *caitp*
|
||||
* `comp: build/pipeline` -- build pipeline, e.g. ts2dart → *mprobst*, *alexeagle*
|
||||
* `comp: core` -- general core Angular issues, not related to a sub-category (see below) →
|
||||
*mhevery*
|
||||
* `comp: core/animations` -- animations framework → *matsko*
|
||||
* `comp: core/change_detection` -- change detection → *vsavkin*
|
||||
* `comp: core/di` -- dependency injection → *vicb*, *rkirov*
|
||||
* `comp: core/directives` -- directives
|
||||
* `comp: core/forms` -- forms → *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 → *kegluneq*, *jakemac*
|
||||
* `comp: data-access` -- → *jeffbcross*
|
||||
* `comp: docs` -- API docs and doc generation → *naomiblack*, *petebacondarwin*
|
||||
* `comp: material-components` -- Angular Material components built in Angular 2 → *jelbourn*
|
||||
* `comp: router` -- Component Router → *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
|
||||
|
||||
@ -160,9 +126,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
|
||||
`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
|
||||
|
||||
Only used on closed issues, to indicate to the reporter why we closed it.
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular2",
|
||||
"dependencies": {
|
||||
"polymer": "Polymer/polymer"
|
||||
"polymer": "Polymer/polymer#^1.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ var CIconfiguration = {
|
||||
'IE9': { unitTest: {target: 'SL', required: false}, 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}},
|
||||
'Edge': { unitTest: {target: 'SL', 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.2': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
|
||||
'Android4.3': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
|
||||
@ -22,9 +22,11 @@ var CIconfiguration = {
|
||||
'Safari7': { 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}},
|
||||
'Safari10': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
|
||||
'iOS7': { unitTest: {target: 'BS', required: true}, e2e: {target: null, required: true}},
|
||||
'iOS8': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
|
||||
'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}}
|
||||
};
|
||||
|
||||
@ -38,7 +40,7 @@ var customLaunchers = {
|
||||
'SL_CHROME': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'chrome',
|
||||
version: '50'
|
||||
version: '52'
|
||||
},
|
||||
'SL_CHROMEBETA': {
|
||||
base: 'SauceLabs',
|
||||
@ -53,7 +55,7 @@ var customLaunchers = {
|
||||
'SL_FIREFOX': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'firefox',
|
||||
version: '45'
|
||||
version: '46'
|
||||
},
|
||||
'SL_FIREFOXBETA': {
|
||||
base: 'SauceLabs',
|
||||
@ -69,13 +71,13 @@ var customLaunchers = {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'safari',
|
||||
platform: 'OS X 10.9',
|
||||
version: '7'
|
||||
version: '7.0'
|
||||
},
|
||||
'SL_SAFARI8': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'safari',
|
||||
platform: 'OS X 10.10',
|
||||
version: '8'
|
||||
version: '8.0'
|
||||
},
|
||||
'SL_SAFARI9': {
|
||||
base: 'SauceLabs',
|
||||
@ -83,6 +85,12 @@ var customLaunchers = {
|
||||
platform: 'OS X 10.11',
|
||||
version: '9.0'
|
||||
},
|
||||
'SL_SAFARI10': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'safari',
|
||||
platform: 'OS X 10.12',
|
||||
version: '10.0'
|
||||
},
|
||||
'SL_IOS7': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'iphone',
|
||||
@ -99,7 +107,13 @@ var customLaunchers = {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'iphone',
|
||||
platform: 'OS X 10.10',
|
||||
version: '9.1'
|
||||
version: '9.3'
|
||||
},
|
||||
'SL_IOS10': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'iphone',
|
||||
platform: 'OS X 10.10',
|
||||
version: '10.0'
|
||||
},
|
||||
'SL_IE9': {
|
||||
base: 'SauceLabs',
|
||||
@ -186,6 +200,12 @@ var customLaunchers = {
|
||||
os: 'OS X',
|
||||
os_version: 'El Capitan'
|
||||
},
|
||||
'BS_SAFARI10': {
|
||||
base: 'BrowserStack',
|
||||
browser: 'safari',
|
||||
os: 'OS X',
|
||||
os_version: 'Sierra'
|
||||
},
|
||||
'BS_IOS7': {
|
||||
base: 'BrowserStack',
|
||||
device: 'iPhone 5S',
|
||||
@ -202,7 +222,13 @@ var customLaunchers = {
|
||||
base: 'BrowserStack',
|
||||
device: 'iPhone 6S',
|
||||
os: 'ios',
|
||||
os_version: '9.0'
|
||||
os_version: '9.1'
|
||||
},
|
||||
'BS_IOS10': {
|
||||
base: 'BrowserStack',
|
||||
device: 'iPhone SE',
|
||||
os: 'ios',
|
||||
os_version: '10.0'
|
||||
},
|
||||
'BS_IE9': {
|
||||
base: 'BrowserStack',
|
||||
@ -271,12 +297,12 @@ var customLaunchers = {
|
||||
|
||||
var sauceAliases = {
|
||||
'ALL': Object.keys(customLaunchers).filter(function(item) {return customLaunchers[item].base == 'SauceLabs';}),
|
||||
'DESKTOP': ['SL_CHROME', 'SL_FIREFOX', 'SL_IE9', 'SL_IE10', 'SL_IE11', 'SL_EDGE', 'SL_SAFARI7', 'SL_SAFARI8', 'SL_SAFARI9'],
|
||||
'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'],
|
||||
'IE': ['SL_IE9', 'SL_IE10', 'SL_IE11'],
|
||||
'IOS': ['SL_IOS7', 'SL_IOS8', 'SL_IOS9'],
|
||||
'SAFARI': ['SL_SAFARI7', 'SL_SAFARI8', 'SL_SAFARI9'],
|
||||
'IOS': ['SL_IOS7', 'SL_IOS8', 'SL_IOS9', 'SL_IOS10'],
|
||||
'SAFARI': ['SL_SAFARI7', 'SL_SAFARI8', 'SL_SAFARI9', 'SL_SAFARI10'],
|
||||
'BETA': ['SL_CHROMEBETA', 'SL_FIREFOXBETA'],
|
||||
'DEV': ['SL_CHROMEDEV', 'SL_FIREFOXDEV'],
|
||||
'CI_REQUIRED': buildConfiguration('unitTest', 'SL', true),
|
||||
@ -285,12 +311,12 @@ var sauceAliases = {
|
||||
|
||||
var browserstackAliases = {
|
||||
'ALL': Object.keys(customLaunchers).filter(function(item) {return customLaunchers[item].base == 'BrowserStack';}),
|
||||
'DESKTOP': ['BS_CHROME', 'BS_FIREFOX', 'BS_IE9', 'BS_IE10', 'BS_IE11', 'BS_EDGE', 'BS_SAFARI7', 'BS_SAFARI8', 'BS_SAFARI9'],
|
||||
'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'],
|
||||
'IE': ['BS_IE9', 'BS_IE10', 'BS_IE11'],
|
||||
'IOS': ['BS_IOS7', 'BS_IOS8', 'BS_IOS9'],
|
||||
'SAFARI': ['BS_SAFARI7', 'BS_SAFARI8', 'BS_SAFARI9'],
|
||||
'IOS': ['BS_IOS7', 'BS_IOS8', 'BS_IOS9', 'BS_IOS10'],
|
||||
'SAFARI': ['BS_SAFARI7', 'BS_SAFARI8', 'BS_SAFARI9', 'BS_SAFARI10'],
|
||||
'CI_REQUIRED': buildConfiguration('unitTest', 'BS', true),
|
||||
'CI_OPTIONAL': buildConfiguration('unitTest', 'BS', false)
|
||||
};
|
||||
|
207
build.sh
207
build.sh
@ -4,117 +4,162 @@ set -e -o pipefail
|
||||
|
||||
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
|
||||
|
||||
|
||||
rm -rf ./dist/all/
|
||||
mkdir -p ./dist/all/
|
||||
|
||||
TSC="node --max-old-space-size=3000 dist/tools/@angular/tsc-wrapped/src/main"
|
||||
UGLIFYJS=`pwd`/node_modules/.bin/uglifyjs
|
||||
TSCONFIG=./tools/tsconfig.json
|
||||
echo "====== (all)COMPILING: \$(npm bin)/tsc -p ${TSCONFIG} ====="
|
||||
echo "====== (tools)COMPILING: \$(npm bin)/tsc -p ${TSCONFIG} ====="
|
||||
rm -rf ./dist/tools/
|
||||
mkdir -p ./dist/tools/
|
||||
$(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/es6-shim/es6-shim.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 -
|
||||
if [[ ${BUILD_ALL} == true ]]; then
|
||||
rm -rf ./dist/all/
|
||||
mkdir -p ./dist/all/
|
||||
|
||||
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 -
|
||||
|
||||
TSCONFIG=./modules/tsconfig.json
|
||||
echo "====== (all)COMPILING: \$(npm bin)/tsc -p ${TSCONFIG} ====="
|
||||
# compile ts code
|
||||
TSC="node dist/tools/@angular/tsc-wrapped/src/main"
|
||||
$TSC -p modules/tsconfig.json
|
||||
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 -
|
||||
|
||||
rm -rf ./dist/packages-dist
|
||||
TSCONFIG=./modules/tsconfig.json
|
||||
echo "====== (all)COMPILING: \$(npm bin)/tsc -p ${TSCONFIG} ====="
|
||||
# compile ts code
|
||||
$TSC -p modules/tsconfig.json
|
||||
|
||||
for PACKAGE in \
|
||||
core \
|
||||
compiler \
|
||||
common \
|
||||
forms \
|
||||
platform-browser \
|
||||
platform-browser-dynamic \
|
||||
platform-server \
|
||||
http \
|
||||
router \
|
||||
router-deprecated \
|
||||
upgrade \
|
||||
compiler-cli
|
||||
rm -rf ./dist/packages-dist
|
||||
fi
|
||||
|
||||
for PACKAGE in ${PACKAGES[@]}
|
||||
do
|
||||
SRCDIR=./modules/@angular/${PACKAGE}
|
||||
DESTDIR=./dist/packages-dist/${PACKAGE}
|
||||
UMD_ES6_PATH=${DESTDIR}/esm/${PACKAGE}.umd.js
|
||||
PWD=`pwd`
|
||||
SRCDIR=${PWD}/modules/@angular/${PACKAGE}
|
||||
DESTDIR=${PWD}/dist/packages-dist/${PACKAGE}
|
||||
UMD_ES5_PATH=${DESTDIR}/bundles/${PACKAGE}.umd.js
|
||||
UMD_TESTING_ES5_PATH=${DESTDIR}/bundles/${PACKAGE}-testing.umd.js
|
||||
UMD_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}.umd.min.js
|
||||
LICENSE_BANNER=${PWD}/modules/@angular/license-banner.txt
|
||||
|
||||
if [[ ${PACKAGE} == "router-deprecated" ]]; then
|
||||
echo "====== COMPILING: \$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es5.json ====="
|
||||
$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es5.json
|
||||
else
|
||||
echo "====== COMPILING: ${TSC} -p ${SRCDIR}/tsconfig-es5.json ====="
|
||||
$TSC -p ${SRCDIR}/tsconfig-es5.json
|
||||
fi
|
||||
rm -rf ${DESTDIR}
|
||||
|
||||
echo "====== COMPILING: ${TSC} -p ${SRCDIR}/tsconfig-build.json ====="
|
||||
$TSC -p ${SRCDIR}/tsconfig-build.json
|
||||
|
||||
cp ${SRCDIR}/package.json ${DESTDIR}/
|
||||
cp ${PWD}/modules/@angular/README.md ${DESTDIR}/
|
||||
|
||||
if [[ -e ${SRCDIR}/tsconfig-testing.json ]]; then
|
||||
echo "====== COMPILING TESTING: ${TSC} -p ${SRCDIR}/tsconfig-testing.json"
|
||||
$TSC -p ${SRCDIR}/tsconfig-testing.json
|
||||
fi
|
||||
|
||||
echo "====== TSC 1.8 d.ts compat for ${DESTDIR} ====="
|
||||
# safely strips 'readonly' specifier from d.ts files to make them compatible with tsc 1.8
|
||||
if [[ ${TRAVIS} ]]; then
|
||||
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i -e 's/\(^ *(static |private )*\)*readonly */\1/g'
|
||||
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i -E 's/^( +)abstract ([[:alnum:]]+\:)/\1\2/g'
|
||||
else
|
||||
if [ "$(uname)" == "Darwin" ]; then
|
||||
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i '' -e 's/\(^ *(static |private )*\)*readonly */\1/g'
|
||||
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i '' -e 's/\/\/\/ <reference types="node" \/>//g'
|
||||
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i '' -E 's/^( +)abstract ([[:alnum:]]+\:)/\1\2/g'
|
||||
else
|
||||
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i -e 's/\(^ *(static |private )*\)*readonly */\1/g'
|
||||
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i -e 's/\/\/\/ <reference types="node" \/>//g'
|
||||
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i -E 's/^( +)abstract ([[:alnum:]]+\:)/\1\2/g'
|
||||
fi
|
||||
|
||||
if [[ ${PACKAGE} != compiler-cli ]]; then
|
||||
if [[ ${PACKAGE} == benchpress ]]; then
|
||||
cp ${SRCDIR}/*.md ${DESTDIR}
|
||||
cp -r ${SRCDIR}/docs ${DESTDIR}
|
||||
fi
|
||||
|
||||
if [[ ${PACKAGE} == "router-deprecated" ]]; then
|
||||
echo "====== (esm)COMPILING: \$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es2015.json ====="
|
||||
$(npm bin)/tsc --emitDecoratorMetadata -p ${SRCDIR}/tsconfig-es2015.json
|
||||
else
|
||||
echo "====== (esm)COMPILING: $TSC -p ${SRCDIR}/tsconfig-es2015.json ====="
|
||||
$TSC -p ${SRCDIR}/tsconfig-es2015.json
|
||||
fi
|
||||
if [[ ${BUNDLE} == true && ${PACKAGE} != compiler-cli && ${PACKAGE} != benchpress ]]; then
|
||||
|
||||
echo "====== BUNDLING: ${SRCDIR} ====="
|
||||
mkdir ${DESTDIR}/bundles
|
||||
|
||||
if [[ ${PACKAGE} != router ]]; then
|
||||
(
|
||||
cd ${SRCDIR}
|
||||
echo "..." # here just to have grep match something and not exit with 1
|
||||
../../../node_modules/.bin/rollup -c rollup.config.js
|
||||
) 2>&1 | grep -v "as external dependency"
|
||||
|
||||
$(npm bin)/tsc \
|
||||
--out ${UMD_ES5_PATH} \
|
||||
--target es5 \
|
||||
--lib "es6,dom" \
|
||||
--allowJs \
|
||||
${UMD_ES6_PATH}
|
||||
|
||||
rm ${UMD_ES6_PATH}
|
||||
|
||||
cat ./modules/@angular/license-banner.txt > ${UMD_ES5_PATH}.tmp
|
||||
(
|
||||
cd ${SRCDIR}
|
||||
echo "====== Rollup ${PACKAGE} index"
|
||||
../../../node_modules/.bin/rollup -c rollup.config.js
|
||||
cat ${LICENSE_BANNER} > ${UMD_ES5_PATH}.tmp
|
||||
cat ${UMD_ES5_PATH} >> ${UMD_ES5_PATH}.tmp
|
||||
mv ${UMD_ES5_PATH}.tmp ${UMD_ES5_PATH}
|
||||
$UGLIFYJS -c --screw-ie8 --comments -o ${UMD_ES5_MIN_PATH} ${UMD_ES5_PATH}
|
||||
|
||||
|
||||
if [[ -e rollup-testing.config.js ]]; then
|
||||
echo "====== Rollup ${PACKAGE} testing"
|
||||
../../../node_modules/.bin/rollup -c rollup-testing.config.js
|
||||
echo "{\"main\": \"../bundles/${PACKAGE}-testing.umd.js\"}" > ${DESTDIR}/testing/package.json
|
||||
cat ${LICENSE_BANNER} > ${UMD_TESTING_ES5_PATH}.tmp
|
||||
cat ${UMD_TESTING_ES5_PATH} >> ${UMD_TESTING_ES5_PATH}.tmp
|
||||
mv ${UMD_TESTING_ES5_PATH}.tmp ${UMD_TESTING_ES5_PATH}
|
||||
fi
|
||||
) 2>&1 | grep -v "as external dependency"
|
||||
|
||||
$(npm bin)/uglifyjs -c --screw-ie8 -o ${UMD_ES5_MIN_PATH} ${UMD_ES5_PATH}
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
./modules/@angular/examples/build.sh
|
||||
|
181
gulpfile.js
181
gulpfile.js
@ -2,15 +2,26 @@
|
||||
|
||||
// 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.
|
||||
require('./tools/check-environment')(
|
||||
{requiredNpmVersion: '>=3.5.3 <4.0.0', requiredNodeVersion: '>=5.4.1 <6.0.0'});
|
||||
|
||||
require('./tools/check-environment')({
|
||||
requiredNpmVersion: '>=3.5.3 <4.0.0',
|
||||
requiredNodeVersion: '>=5.4.1 <7.0.0',
|
||||
});
|
||||
|
||||
const gulp = require('gulp');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
const srcsToFmt = ['tools/**/*.ts', 'modules/@angular/**/*.ts'];
|
||||
// clang-format entry points
|
||||
const srcsToFmt = [
|
||||
'modules/@angular/**/*.ts',
|
||||
'modules/benchmarks/**/*.ts',
|
||||
'modules/e2e_util/**/*.ts',
|
||||
'modules/playground/**/*.ts',
|
||||
'tools/**/*.ts',
|
||||
'!tools/public_api_guard/**/*.d.ts',
|
||||
];
|
||||
|
||||
// Check source code for formatting errors (clang-format)
|
||||
gulp.task('format:enforce', () => {
|
||||
const format = require('gulp-clang-format');
|
||||
const clangFormat = require('clang-format');
|
||||
@ -18,6 +29,7 @@ gulp.task('format:enforce', () => {
|
||||
format.checkFormat('file', clangFormat, {verbose: true, fail: true}));
|
||||
});
|
||||
|
||||
// Format the source code with clang-format (see .clang-format)
|
||||
gulp.task('format', () => {
|
||||
const format = require('gulp-clang-format');
|
||||
const clangFormat = require('clang-format');
|
||||
@ -25,43 +37,180 @@ gulp.task('format', () => {
|
||||
format.format('file', clangFormat)).pipe(gulp.dest('.'));
|
||||
});
|
||||
|
||||
gulp.task('lint', ['format:enforce', 'tools:build'], () => {
|
||||
const entrypoints = [
|
||||
'dist/packages-dist/core/index.d.ts',
|
||||
'dist/packages-dist/core/testing/index.d.ts',
|
||||
'dist/packages-dist/common/index.d.ts',
|
||||
'dist/packages-dist/common/testing/index.d.ts',
|
||||
// The API surface of the compiler is currently unstable - all of the important APIs are exposed
|
||||
// via @angular/core, @angular/platform-browser or @angular/platform-browser-dynamic instead.
|
||||
//'dist/packages-dist/compiler/index.d.ts',
|
||||
//'dist/packages-dist/compiler/testing.d.ts',
|
||||
'dist/packages-dist/upgrade/index.d.ts',
|
||||
'dist/packages-dist/platform-browser/index.d.ts',
|
||||
'dist/packages-dist/platform-browser/testing/index.d.ts',
|
||||
'dist/packages-dist/platform-browser-dynamic/index.d.ts',
|
||||
'dist/packages-dist/platform-browser-dynamic/testing/index.d.ts',
|
||||
'dist/packages-dist/platform-webworker/index.d.ts',
|
||||
'dist/packages-dist/platform-webworker-dynamic/index.d.ts',
|
||||
'dist/packages-dist/platform-server/index.d.ts',
|
||||
'dist/packages-dist/platform-server/testing/index.d.ts',
|
||||
'dist/packages-dist/http/index.d.ts',
|
||||
'dist/packages-dist/http/testing/index.d.ts',
|
||||
'dist/packages-dist/forms/index.d.ts',
|
||||
'dist/packages-dist/router/index.d.ts',
|
||||
];
|
||||
const publicApiDir = path.normalize('tools/public_api_guard');
|
||||
const publicApiArgs = [
|
||||
'--rootDir', 'dist/packages-dist',
|
||||
'--stripExportPattern', '^__',
|
||||
'--allowModuleIdentifiers', 'jasmine',
|
||||
'--allowModuleIdentifiers', 'protractor',
|
||||
'--allowModuleIdentifiers', 'angular',
|
||||
'--onStabilityMissing', 'error',
|
||||
].concat(entrypoints);
|
||||
|
||||
// Build angular
|
||||
gulp.task('build.sh', (done) => {
|
||||
const childProcess = require('child_process');
|
||||
|
||||
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
|
||||
gulp.task('public-api:enforce', (done) => {
|
||||
const childProcess = require('child_process');
|
||||
|
||||
childProcess
|
||||
.spawn(
|
||||
path.join(__dirname, platformScriptPath(`/node_modules/.bin/ts-api-guardian`)),
|
||||
['--verifyDir', publicApiDir].concat(publicApiArgs), {stdio: 'inherit'})
|
||||
.on('close', (errorCode) => {
|
||||
if (errorCode !== 0) {
|
||||
done(new Error(
|
||||
'Public API differs from golden file. Please run `gulp public-api:update`.'));
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Generate the public API golden files
|
||||
gulp.task('public-api:update', ['build.sh'], (done) => {
|
||||
const childProcess = require('child_process');
|
||||
|
||||
childProcess
|
||||
.spawn(
|
||||
path.join(__dirname, platformScriptPath(`/node_modules/.bin/ts-api-guardian`)),
|
||||
['--outDir', publicApiDir].concat(publicApiArgs), {stdio: 'inherit'})
|
||||
.on('close', done);
|
||||
});
|
||||
|
||||
// Checks tests for presence of ddescribe, fdescribe, fit, iit and fails the build if one of the focused tests is found.
|
||||
// Currently xdescribe and xit are _not_ reported as errors since there are a couple of excluded tests in our code base.
|
||||
gulp.task('check-tests', function() {
|
||||
const ddescribeIit = require('gulp-ddescribe-iit');
|
||||
return gulp.src([
|
||||
'modules/**/*.spec.ts',
|
||||
'modules/**/*_spec.ts',
|
||||
]).pipe(ddescribeIit({allowDisabledTests: true}));
|
||||
});
|
||||
|
||||
// Check the coding standards and programming errors
|
||||
gulp.task('lint', ['check-tests', 'format:enforce', 'tools:build'], () => {
|
||||
const tslint = require('gulp-tslint');
|
||||
// Built-in rules are at
|
||||
// https://github.com/palantir/tslint#supported-rules
|
||||
const tslintConfig = require('./tslint.json');
|
||||
return gulp.src(['modules/@angular/**/*.ts', '!modules/@angular/*/test/**'])
|
||||
return gulp.src(['modules/@angular/**/*.ts', 'modules/benchpress/**/*.ts'])
|
||||
.pipe(tslint({
|
||||
tslint: require('tslint').default,
|
||||
configuration: tslintConfig,
|
||||
rulesDirectory: 'dist/tools/tslint'
|
||||
rulesDirectory: 'dist/tools/tslint',
|
||||
formatter: 'prose',
|
||||
}))
|
||||
.pipe(tslint.report('prose', {emitError: true}));
|
||||
.pipe(tslint.report({emitError: true}));
|
||||
});
|
||||
|
||||
gulp.task('tools:build', (done) => { tsc('tools/', done); });
|
||||
|
||||
// Check for circular dependency in the source code
|
||||
gulp.task('check-cycle', (done) => {
|
||||
const madge = require('madge');
|
||||
|
||||
const dependencyObject = madge(['dist/all/'], {
|
||||
format: 'cjs',
|
||||
extensions: ['.js'],
|
||||
onParseFile: function(data) { data.src = data.src.replace(/\/\* circular \*\//g, "//"); }
|
||||
});
|
||||
const circularDependencies = dependencyObject.circular().getArray();
|
||||
if (circularDependencies.length > 0) {
|
||||
console.log('Found circular dependencies!');
|
||||
console.log(circularDependencies);
|
||||
process.exit(1);
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
// Serve the built files
|
||||
gulp.task('serve', () => {
|
||||
let connect = require('gulp-connect');
|
||||
let cors = require('cors');
|
||||
const connect = require('gulp-connect');
|
||||
const cors = require('cors');
|
||||
|
||||
connect.server({
|
||||
root: `${__dirname}/dist`,
|
||||
port: 8000,
|
||||
livereload: 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()],
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function tsc(projectPath, done) {
|
||||
let child_process = require('child_process');
|
||||
// Update the changelog with the latest changes
|
||||
gulp.task('changelog', () => {
|
||||
const conventionalChangelog = require('gulp-conventional-changelog');
|
||||
|
||||
child_process
|
||||
return gulp.src('CHANGELOG.md')
|
||||
.pipe(conventionalChangelog({
|
||||
preset: 'angular',
|
||||
releaseCount: 1
|
||||
}, {
|
||||
// Conventional Changelog Context
|
||||
// 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
|
||||
currentTag: require('./package.json').version
|
||||
}))
|
||||
.pipe(gulp.dest('./'));
|
||||
});
|
||||
|
||||
function tsc(projectPath, done) {
|
||||
const childProcess = require('child_process');
|
||||
|
||||
childProcess
|
||||
.spawn(
|
||||
`${__dirname}/node_modules/.bin/tsc`, ['-p', path.join(__dirname, projectPath)],
|
||||
path.normalize(platformScriptPath(`${__dirname}/node_modules/.bin/tsc`)),
|
||||
['-p', path.join(__dirname, projectPath)],
|
||||
{stdio: 'inherit'})
|
||||
.on('close', (errorCode) => done(errorCode));
|
||||
.on('close', done);
|
||||
}
|
||||
|
||||
// returns the script path for the current platform
|
||||
function platformScriptPath(path) {
|
||||
return /^win/.test(os.platform()) ? `${path}.cmd` : path;
|
||||
}
|
||||
|
1583
gulpfile.js.old
1583
gulpfile.js.old
File diff suppressed because it is too large
Load Diff
@ -13,12 +13,14 @@ module.exports = function(config) {
|
||||
// Loaded through the System loader, in `test-main.js`.
|
||||
{pattern: 'dist/all/@angular/**/*.js', included: false, watched: true},
|
||||
|
||||
'node_modules/es6-shim/es6-shim.js',
|
||||
'node_modules/core-js/client/core.js',
|
||||
// include Angular v1 for upgrade module testing
|
||||
'node_modules/angular/angular.min.js',
|
||||
|
||||
'node_modules/zone.js/dist/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/jasmine-patch.js',
|
||||
'node_modules/zone.js/dist/async-test.js',
|
||||
'node_modules/zone.js/dist/fake-async-test.js',
|
||||
@ -37,10 +39,12 @@ module.exports = function(config) {
|
||||
|
||||
exclude: [
|
||||
'dist/all/@angular/**/e2e_test/**',
|
||||
'dist/all/@angular/examples/**',
|
||||
'dist/all/@angular/router/**',
|
||||
'dist/all/@angular/compiler-cli/**',
|
||||
'dist/all/@angular/benchpress/**',
|
||||
'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,
|
||||
|
@ -1,6 +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)
|
||||
|
||||
License: Apache MIT 2.0
|
@ -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
|
6
modules/@angular/README.md
Normal file
6
modules/@angular/README.md
Normal 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
|
@ -3,6 +3,10 @@
|
||||
Benchpress is a framework for e2e performance tests.
|
||||
See [here for an example project](https://github.com/angular/benchpress-tree).
|
||||
|
||||
The sources for this package are in the main [Angular2](https://github.com/angular/angular) repo. Please file issues and pull requests against that repo.
|
||||
|
||||
License: Apache MIT 2.0
|
||||
|
||||
# Why?
|
||||
|
||||
There are so called "micro benchmarks" that essentially use a stop watch in the browser to measure time
|
||||
@ -158,7 +162,7 @@ runner.sample({
|
||||
````
|
||||
|
||||
When looking into the DevTools Timeline, we see a marker as well:
|
||||

|
||||

|
||||
|
||||
### Custom Metrics Without Using `console.time`
|
||||
|
||||
@ -185,8 +189,8 @@ describe('home page load', function() {
|
||||
userMetrics: {
|
||||
timeToBootstrap: 'The time in milliseconds to bootstrap'
|
||||
},
|
||||
bindings: [
|
||||
bind(RegressionSlopeValidator.METRIC).toValue('timeToBootstrap')
|
||||
providers: [
|
||||
{provide: RegressionSlopeValidator.METRIC, useValue: 'timeToBootstrap'}
|
||||
]
|
||||
}).then(done);
|
||||
});
|
||||
@ -208,9 +212,9 @@ Benchpress can also measure the "smoothness" of scrolling and animations. In ord
|
||||
|
||||
To collect these metrics, you need to execute `console.time('frameCapture')` and `console.timeEnd('frameCapture')` either in your benchmark application or in you benchmark driver via webdriver. The metrics mentioned above will only be collected between those two calls and it is recommended to wrap the time/timeEnd calls as closely as possible around the action you want to evaluate to get accurate measurements.
|
||||
|
||||
In addition to that, one extra binding needs to be passed to benchpress in tests that want to collect these metrics:
|
||||
In addition to that, one extra provider needs to be passed to benchpress in tests that want to collect these metrics:
|
||||
|
||||
benchpress.sample(providers: [bp.bind(bp.Options.CAPTURE_FRAMES).toValue(true)], ... )
|
||||
benchpress.sample(providers: [{provide: bp.Options.CAPTURE_FRAMES, useValue: true}], ... )
|
||||
|
||||
# Requests Metrics
|
||||
|
||||
@ -222,8 +226,8 @@ Benchpress can also record the number of requests sent and count the received "e
|
||||
To collect these metrics, you need the following corresponding extra providers:
|
||||
|
||||
benchpress.sample(providers: [
|
||||
bp.bind(bp.Options.RECEIVED_DATA).toValue(true),
|
||||
bp.bind(bp.Options.REQUEST_COUNT).toValue(true)
|
||||
{provide: bp.Options.RECEIVED_DATA, useValue: true},
|
||||
{provide: bp.Options.REQUEST_COUNT, useValue: true}
|
||||
], ... )
|
||||
|
||||
# Best practices
|
||||
@ -256,7 +260,7 @@ To collect these metrics, you need the following corresponding extra providers:
|
||||
|
||||
# Detailed overview
|
||||
|
||||

|
||||

|
||||
|
||||
Definitions:
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
@ -1,23 +1,34 @@
|
||||
export {Sampler, SampleState} from './src/sampler';
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
// Must be imported first, because angular2 decorators throws on load.
|
||||
import 'reflect-metadata';
|
||||
|
||||
export {Injector, OpaqueToken, Provider, ReflectiveInjector} from '@angular/core';
|
||||
export {Options} from './src/common_options';
|
||||
export {MeasureValues} from './src/measure_values';
|
||||
export {Metric} from './src/metric';
|
||||
export {Validator} from './src/validator';
|
||||
export {MultiMetric} from './src/metric/multi_metric';
|
||||
export {PerflogMetric} from './src/metric/perflog_metric';
|
||||
export {UserMetric} from './src/metric/user_metric';
|
||||
export {Reporter} from './src/reporter';
|
||||
export {WebDriverExtension, PerfLogFeatures} from './src/web_driver_extension';
|
||||
export {WebDriverAdapter} from './src/web_driver_adapter';
|
||||
export {SizeValidator} from './src/validator/size_validator';
|
||||
export {RegressionSlopeValidator} from './src/validator/regression_slope_validator';
|
||||
export {ConsoleReporter} from './src/reporter/console_reporter';
|
||||
export {JsonFileReporter} from './src/reporter/json_file_reporter';
|
||||
export {MultiReporter} from './src/reporter/multi_reporter';
|
||||
export {Runner} from './src/runner';
|
||||
export {SampleDescription} from './src/sample_description';
|
||||
export {PerflogMetric} from './src/metric/perflog_metric';
|
||||
export {SampleState, Sampler} from './src/sampler';
|
||||
export {Validator} from './src/validator';
|
||||
export {RegressionSlopeValidator} from './src/validator/regression_slope_validator';
|
||||
export {SizeValidator} from './src/validator/size_validator';
|
||||
export {WebDriverAdapter} from './src/web_driver_adapter';
|
||||
export {PerfLogEvent, PerfLogFeatures, WebDriverExtension} from './src/web_driver_extension';
|
||||
export {ChromeDriverExtension} from './src/webdriver/chrome_driver_extension';
|
||||
export {FirefoxDriverExtension} from './src/webdriver/firefox_driver_extension';
|
||||
export {IOsDriverExtension} from './src/webdriver/ios_driver_extension';
|
||||
export {Runner} from './src/runner';
|
||||
export {Options} from './src/common_options';
|
||||
export {MeasureValues} from './src/measure_values';
|
||||
export {MultiMetric} from './src/metric/multi_metric';
|
||||
export {UserMetric} from './src/metric/user_metric';
|
||||
export {MultiReporter} from './src/reporter/multi_reporter';
|
||||
|
||||
export {bind, provide, Injector, ReflectiveInjector, OpaqueToken} from '@angular/core/src/di';
|
||||
export {SeleniumWebDriverAdapter} from './src/webdriver/selenium_webdriver_adapter';
|
31
modules/@angular/benchpress/package.json
Normal file
31
modules/@angular/benchpress/package.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "@angular/benchpress",
|
||||
"version": "0.1.0",
|
||||
"description": "Benchpress - a framework for e2e performance tests",
|
||||
"main": "index.js",
|
||||
"typings": "index.d.ts",
|
||||
"dependencies": {
|
||||
"@angular/core": "^2.0.0-rc.7",
|
||||
"reflect-metadata": "^0.1.2",
|
||||
"rxjs": "5.0.0-beta.12",
|
||||
"jpm": "1.1.4",
|
||||
"firefox-profile": "0.4.0",
|
||||
"selenium-webdriver": "^2.53.3"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/angular/angular.git"
|
||||
},
|
||||
"keywords": [
|
||||
"angular",
|
||||
"benchmarks"
|
||||
],
|
||||
"contributors": [
|
||||
"Tobias Bosch <tbosch@google.com> (https://angular.io/)"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/angular/angular/issues"
|
||||
},
|
||||
"homepage": "https://github.com/angular/angular/tree/master/modules/@angular/compiler-cli"
|
||||
}
|
16
modules/@angular/benchpress/publish.sh
Executable file
16
modules/@angular/benchpress/publish.sh
Executable 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
|
55
modules/@angular/benchpress/src/common_options.ts
Normal file
55
modules/@angular/benchpress/src/common_options.ts
Normal file
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {OpaqueToken} from '@angular/core';
|
||||
import * as fs from 'fs';
|
||||
|
||||
import {DateWrapper} from './facade/lang';
|
||||
|
||||
export class Options {
|
||||
static SAMPLE_ID = new OpaqueToken('Options.sampleId');
|
||||
static DEFAULT_DESCRIPTION = new OpaqueToken('Options.defaultDescription');
|
||||
static SAMPLE_DESCRIPTION = new OpaqueToken('Options.sampleDescription');
|
||||
static FORCE_GC = new OpaqueToken('Options.forceGc');
|
||||
static NO_PREPARE = () => true;
|
||||
static PREPARE = new OpaqueToken('Options.prepare');
|
||||
static EXECUTE = new OpaqueToken('Options.execute');
|
||||
static CAPABILITIES = new OpaqueToken('Options.capabilities');
|
||||
static USER_AGENT = new OpaqueToken('Options.userAgent');
|
||||
static MICRO_METRICS = new OpaqueToken('Options.microMetrics');
|
||||
static USER_METRICS = new OpaqueToken('Options.userMetrics');
|
||||
static NOW = new OpaqueToken('Options.now');
|
||||
static WRITE_FILE = new OpaqueToken('Options.writeFile');
|
||||
static RECEIVED_DATA = new OpaqueToken('Options.receivedData');
|
||||
static REQUEST_COUNT = new OpaqueToken('Options.requestCount');
|
||||
static CAPTURE_FRAMES = new OpaqueToken('Options.frameCapture');
|
||||
static DEFAULT_PROVIDERS = [
|
||||
{provide: Options.DEFAULT_DESCRIPTION, useValue: {}},
|
||||
{provide: Options.SAMPLE_DESCRIPTION, useValue: {}},
|
||||
{provide: Options.FORCE_GC, useValue: false},
|
||||
{provide: Options.PREPARE, useValue: Options.NO_PREPARE},
|
||||
{provide: Options.MICRO_METRICS, useValue: {}}, {provide: Options.USER_METRICS, useValue: {}},
|
||||
{provide: Options.NOW, useValue: () => DateWrapper.now()},
|
||||
{provide: Options.RECEIVED_DATA, useValue: false},
|
||||
{provide: Options.REQUEST_COUNT, useValue: false},
|
||||
{provide: Options.CAPTURE_FRAMES, useValue: false},
|
||||
{provide: Options.WRITE_FILE, useValue: writeFile}
|
||||
];
|
||||
}
|
||||
|
||||
function writeFile(filename: string, content: string): Promise<any> {
|
||||
return new Promise(function(resolve, reject) {
|
||||
fs.writeFile(filename, content, (error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
declare var exportFunction: any;
|
||||
declare var unsafeWindow: any;
|
||||
|
||||
exportFunction(function() {
|
||||
var curTime = unsafeWindow.performance.now();
|
||||
(<any>self).port.emit('startProfiler', curTime);
|
||||
}, unsafeWindow, {defineAs: 'startProfiler'});
|
||||
|
||||
exportFunction(function() {
|
||||
(<any>self).port.emit('stopProfiler');
|
||||
}, unsafeWindow, {defineAs: 'stopProfiler'});
|
||||
|
||||
exportFunction(function(cb: Function) {
|
||||
(<any>self).port.once('perfProfile', cb);
|
||||
(<any>self).port.emit('getProfile');
|
||||
}, unsafeWindow, {defineAs: 'getProfile'});
|
||||
|
||||
exportFunction(function() {
|
||||
(<any>self).port.emit('forceGC');
|
||||
}, unsafeWindow, {defineAs: 'forceGC'});
|
||||
|
||||
exportFunction(function(name: string) {
|
||||
var curTime = unsafeWindow.performance.now();
|
||||
(<any>self).port.emit('markStart', name, curTime);
|
||||
}, unsafeWindow, {defineAs: 'markStart'});
|
||||
|
||||
exportFunction(function(name: string) {
|
||||
var curTime = unsafeWindow.performance.now();
|
||||
(<any>self).port.emit('markEnd', name, curTime);
|
||||
}, unsafeWindow, {defineAs: 'markEnd'});
|
@ -1,15 +1,23 @@
|
||||
/**
|
||||
* @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 {Cc, Ci, Cu} = require('chrome');
|
||||
var os = Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService);
|
||||
var ParserUtil = require('./parser_util');
|
||||
|
||||
class Profiler {
|
||||
private _profiler;
|
||||
private _profiler: any;
|
||||
private _markerEvents: any[];
|
||||
private _profilerStartTime: number;
|
||||
|
||||
constructor() { this._profiler = Cc['@mozilla.org/tools/profiler;1'].getService(Ci.nsIProfiler); }
|
||||
|
||||
start(entries, interval, features, timeStarted) {
|
||||
start(entries: any, interval: any, features: any, timeStarted: any) {
|
||||
this._profiler.StartProfiler(entries, interval, features, features.length);
|
||||
this._profilerStartTime = timeStarted;
|
||||
this._markerEvents = [];
|
||||
@ -21,21 +29,24 @@ class Profiler {
|
||||
var profileData = this._profiler.getProfileData();
|
||||
var perfEvents = ParserUtil.convertPerfProfileToEvents(profileData);
|
||||
perfEvents = this._mergeMarkerEvents(perfEvents);
|
||||
perfEvents.sort(function(event1, event2) { return event1.ts - event2.ts; }); // Sort by ts
|
||||
perfEvents.sort(function(event1: any, event2: any) {
|
||||
return event1.ts - event2.ts;
|
||||
}); // Sort by ts
|
||||
return perfEvents;
|
||||
}
|
||||
|
||||
_mergeMarkerEvents(perfEvents: any[]): any[] {
|
||||
/** @internal */
|
||||
private _mergeMarkerEvents(perfEvents: any[]): any[] {
|
||||
this._markerEvents.forEach(function(markerEvent) { perfEvents.push(markerEvent); });
|
||||
return perfEvents;
|
||||
}
|
||||
|
||||
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) {
|
||||
this._markerEvents.push({ph: 'e', ts: timeEnded - this._profilerStartTime, name: name});
|
||||
this._markerEvents.push({ph: 'E', ts: timeEnded - this._profilerStartTime, name: name});
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,15 +61,18 @@ var profiler = new Profiler();
|
||||
mod.PageMod({
|
||||
include: ['*'],
|
||||
contentScriptFile: data.url('installed_script.js'),
|
||||
onAttach: worker => {
|
||||
worker.port.on('startProfiler',
|
||||
(timeStarted) => profiler.start(/* = profiler memory */ 3000000, 0.1,
|
||||
['leaf', 'js', 'stackwalk', 'gc'], timeStarted));
|
||||
onAttach: (worker: any) => {
|
||||
worker.port.on(
|
||||
'startProfiler',
|
||||
(timeStarted: any) => profiler.start(
|
||||
/* = profiler memory */ 3000000, 0.1, ['leaf', 'js', 'stackwalk', 'gc'], timeStarted));
|
||||
worker.port.on('stopProfiler', () => profiler.stop());
|
||||
worker.port.on('getProfile',
|
||||
() => worker.port.emit('perfProfile', profiler.getProfilePerfEvents()));
|
||||
worker.port.on(
|
||||
'getProfile', () => worker.port.emit('perfProfile', profiler.getProfilePerfEvents()));
|
||||
worker.port.on('forceGC', forceGC);
|
||||
worker.port.on('markStart', (name, timeStarted) => profiler.addStartEvent(name, timeStarted));
|
||||
worker.port.on('markEnd', (name, timeEnded) => profiler.addEndEvent(name, timeEnded));
|
||||
worker.port.on(
|
||||
'markStart', (name: string, timeStarted: any) => profiler.addStartEvent(name, timeStarted));
|
||||
worker.port.on(
|
||||
'markEnd', (name: string, timeEnded: any) => profiler.addEndEvent(name, timeEnded));
|
||||
}
|
||||
});
|
@ -1,14 +1,22 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Object} perfProfile The perf profile JSON object.
|
||||
* @return {Object[]} An array of recognized events that are captured
|
||||
* within the perf profile.
|
||||
*/
|
||||
export function convertPerfProfileToEvents(perfProfile: any): any[] {
|
||||
var inProgressEvents = new Map(); // map from event name to start time
|
||||
var finishedEvents = []; // Event[] finished events
|
||||
var addFinishedEvent = function(eventName, startTime, endTime) {
|
||||
var inProgressEvents = new Map(); // map from event name to start time
|
||||
var finishedEvents: {[key: string]: any}[] = []; // Event[] finished events
|
||||
var addFinishedEvent = function(eventName: string, startTime: number, endTime: number) {
|
||||
var categorizedEventName = categorizeEvent(eventName);
|
||||
var args = undefined;
|
||||
var args: {[key: string]: any} = undefined;
|
||||
if (categorizedEventName == 'gc') {
|
||||
// TODO: We cannot measure heap size at the moment
|
||||
args = {usedHeapSize: 0};
|
||||
@ -34,7 +42,9 @@ export function convertPerfProfileToEvents(perfProfile: any): any[] {
|
||||
// Add all the frames into a set so it's easier/faster to find the set
|
||||
// differences
|
||||
var sampleFrames = new Set();
|
||||
sample.frames.forEach(function(frame) { sampleFrames.add(frame.location); });
|
||||
sample.frames.forEach(function(frame: {[key: string]: any}) {
|
||||
sampleFrames.add(frame['location']);
|
||||
});
|
||||
|
||||
// If an event is in the inProgressEvents map, but not in the current sample,
|
||||
// then it must have just finished. We add this event to the finishedEvents
|
||||
@ -65,7 +75,7 @@ export function convertPerfProfileToEvents(perfProfile: any): any[] {
|
||||
});
|
||||
|
||||
// Remove all the unknown categories.
|
||||
return finishedEvents.filter(function(event) { return event.name != 'unknown'; });
|
||||
return finishedEvents.filter(function(event) { return event['name'] != 'unknown'; });
|
||||
}
|
||||
|
||||
// TODO: this is most likely not exhaustive.
|
@ -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 q = require('q');
|
||||
var FirefoxProfile = require('firefox-profile');
|
||||
var jpm = require('jpm/lib/xpi');
|
||||
@ -5,7 +13,7 @@ var pathUtil = require('path');
|
||||
|
||||
var PERF_ADDON_PACKAGE_JSON_DIR = '..';
|
||||
|
||||
exports.getAbsolutePath = function(path) {
|
||||
exports.getAbsolutePath = function(path: string) {
|
||||
var normalizedPath = pathUtil.normalize(path);
|
||||
if (pathUtil.resolve(normalizedPath) == normalizedPath) {
|
||||
// Already absolute path
|
||||
@ -15,12 +23,12 @@ exports.getAbsolutePath = function(path) {
|
||||
}
|
||||
};
|
||||
|
||||
exports.getFirefoxProfile = function(extensionPath) {
|
||||
exports.getFirefoxProfile = function(extensionPath: string) {
|
||||
var deferred = q.defer();
|
||||
|
||||
var firefoxProfile = new FirefoxProfile();
|
||||
firefoxProfile.addExtensions([extensionPath], () => {
|
||||
firefoxProfile.encoded(encodedProfile => {
|
||||
firefoxProfile.encoded((encodedProfile: any) => {
|
||||
var multiCapabilities = [{browserName: 'firefox', firefox_profile: encodedProfile}];
|
||||
deferred.resolve(multiCapabilities);
|
||||
});
|
||||
@ -36,9 +44,8 @@ exports.getFirefoxProfileWithExtension = function() {
|
||||
var savedCwd = process.cwd();
|
||||
process.chdir(absPackageJsonDir);
|
||||
|
||||
return jpm(packageJson)
|
||||
.then(xpiPath => {
|
||||
process.chdir(savedCwd);
|
||||
return exports.getFirefoxProfile(xpiPath);
|
||||
});
|
||||
return jpm(packageJson).then((xpiPath: string) => {
|
||||
process.chdir(savedCwd);
|
||||
return exports.getFirefoxProfile(xpiPath);
|
||||
});
|
||||
};
|
20
modules/@angular/benchpress/src/measure_values.ts
Normal file
20
modules/@angular/benchpress/src/measure_values.ts
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @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 class MeasureValues {
|
||||
constructor(
|
||||
public runIndex: number, public timeStamp: Date, public values: {[key: string]: any}) {}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
'timeStamp': this.timeStamp.toJSON(),
|
||||
'runIndex': this.runIndex,
|
||||
'values': this.values,
|
||||
};
|
||||
}
|
||||
}
|
@ -1,28 +1,31 @@
|
||||
import {BaseException, WrappedException} from '@angular/facade';
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* A metric is measures values
|
||||
*/
|
||||
export abstract class Metric {
|
||||
static bindTo(delegateToken): any[] {
|
||||
return [{provide: Metric, useFactory: (delegate) => delegate, deps: [delegateToken]}];
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts measuring
|
||||
*/
|
||||
beginMeasure(): Promise<any> { throw new BaseException('NYI'); }
|
||||
beginMeasure(): Promise<any> { throw new Error('NYI'); }
|
||||
|
||||
/**
|
||||
* Ends measuring and reports the data
|
||||
* since the begin call.
|
||||
* @param restart: Whether to restart right after this.
|
||||
*/
|
||||
endMeasure(restart: boolean): Promise<{[key: string]: any}> { throw new BaseException('NYI'); }
|
||||
endMeasure(restart: boolean): Promise<{[key: string]: any}> { throw new Error('NYI'); }
|
||||
|
||||
/**
|
||||
* Describes the metrics provided by this metric implementation.
|
||||
* (e.g. units, ...)
|
||||
*/
|
||||
describe(): {[key: string]: any} { throw new BaseException('NYI'); }
|
||||
describe(): {[key: string]: string} { throw new Error('NYI'); }
|
||||
}
|
@ -1,20 +1,27 @@
|
||||
import {Injector, OpaqueToken} from '@angular/core/src/di';
|
||||
import {StringMapWrapper} from '@angular/facade';
|
||||
import {PromiseWrapper} from '@angular/facade';
|
||||
/**
|
||||
* @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 {Injector, OpaqueToken} from '@angular/core';
|
||||
import {StringMapWrapper} from '../facade/collection';
|
||||
|
||||
import {Metric} from '../metric';
|
||||
|
||||
export class MultiMetric extends Metric {
|
||||
static createBindings(childTokens: any[]): any[] {
|
||||
static provideWith(childTokens: any[]): any[] {
|
||||
return [
|
||||
{
|
||||
provide: _CHILDREN,
|
||||
useFactory:(injector: Injector) => childTokens.map(token => injector.get(token)),
|
||||
useFactory: (injector: Injector) => childTokens.map(token => injector.get(token)),
|
||||
deps: [Injector]
|
||||
},
|
||||
{
|
||||
provide: MultiMetric,
|
||||
useFactory: children => new MultiMetric(children),
|
||||
useFactory: (children: Metric[]) => new MultiMetric(children),
|
||||
deps: [_CHILDREN]
|
||||
}
|
||||
];
|
||||
@ -26,7 +33,7 @@ export class MultiMetric extends Metric {
|
||||
* Starts measuring
|
||||
*/
|
||||
beginMeasure(): Promise<any> {
|
||||
return PromiseWrapper.all(this._metrics.map(metric => metric.beginMeasure()));
|
||||
return Promise.all(this._metrics.map(metric => metric.beginMeasure()));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -35,8 +42,8 @@ export class MultiMetric extends Metric {
|
||||
* @param restart: Whether to restart right after this.
|
||||
*/
|
||||
endMeasure(restart: boolean): Promise<{[key: string]: any}> {
|
||||
return PromiseWrapper.all(this._metrics.map(metric => metric.endMeasure(restart)))
|
||||
.then(values => mergeStringMaps(values));
|
||||
return Promise.all(this._metrics.map(metric => metric.endMeasure(restart)))
|
||||
.then(values => mergeStringMaps(<any>values));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -48,8 +55,8 @@ export class MultiMetric extends Metric {
|
||||
}
|
||||
}
|
||||
|
||||
function mergeStringMaps(maps: { [key: string]: string }[]): Object {
|
||||
var result = {};
|
||||
function mergeStringMaps(maps: {[key: string]: string}[]): {[key: string]: string} {
|
||||
var result: {[key: string]: string} = {};
|
||||
maps.forEach(
|
||||
map => { StringMapWrapper.forEach(map, (value, prop) => { result[prop] = value; }); });
|
||||
return result;
|
371
modules/@angular/benchpress/src/metric/perflog_metric.ts
Normal file
371
modules/@angular/benchpress/src/metric/perflog_metric.ts
Normal file
@ -0,0 +1,371 @@
|
||||
/**
|
||||
* @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 {Inject, Injectable, OpaqueToken} from '@angular/core';
|
||||
|
||||
import {Options} from '../common_options';
|
||||
import {Metric} from '../metric';
|
||||
import {PerfLogEvent, PerfLogFeatures, WebDriverExtension} from '../web_driver_extension';
|
||||
|
||||
|
||||
/**
|
||||
* A metric that reads out the performance log
|
||||
*/
|
||||
@Injectable()
|
||||
export class PerflogMetric extends Metric {
|
||||
static SET_TIMEOUT = new OpaqueToken('PerflogMetric.setTimeout');
|
||||
static PROVIDERS = [
|
||||
PerflogMetric, {
|
||||
provide: PerflogMetric.SET_TIMEOUT,
|
||||
useValue: (fn: Function, millis: number) => <any>setTimeout(fn, millis)
|
||||
}
|
||||
];
|
||||
|
||||
private _remainingEvents: PerfLogEvent[];
|
||||
private _measureCount: number;
|
||||
private _perfLogFeatures: PerfLogFeatures;
|
||||
|
||||
/**
|
||||
* @param driverExtension
|
||||
* @param setTimeout
|
||||
* @param microMetrics Name and description of metrics provided via console.time / console.timeEnd
|
||||
**/
|
||||
constructor(
|
||||
private _driverExtension: WebDriverExtension,
|
||||
@Inject(PerflogMetric.SET_TIMEOUT) private _setTimeout: Function,
|
||||
@Inject(Options.MICRO_METRICS) private _microMetrics: {[key: string]: string},
|
||||
@Inject(Options.FORCE_GC) private _forceGc: boolean,
|
||||
@Inject(Options.CAPTURE_FRAMES) private _captureFrames: boolean,
|
||||
@Inject(Options.RECEIVED_DATA) private _receivedData: boolean,
|
||||
@Inject(Options.REQUEST_COUNT) private _requestCount: boolean) {
|
||||
super();
|
||||
|
||||
this._remainingEvents = [];
|
||||
this._measureCount = 0;
|
||||
this._perfLogFeatures = _driverExtension.perfLogFeatures();
|
||||
if (!this._perfLogFeatures.userTiming) {
|
||||
// User timing is needed for navigationStart.
|
||||
this._receivedData = false;
|
||||
this._requestCount = false;
|
||||
}
|
||||
}
|
||||
|
||||
describe(): {[key: string]: string} {
|
||||
var res: {[key: string]: any} = {
|
||||
'scriptTime': 'script execution time in ms, including gc and render',
|
||||
'pureScriptTime': 'script execution time in ms, without gc nor render'
|
||||
};
|
||||
if (this._perfLogFeatures.render) {
|
||||
res['renderTime'] = 'render time in ms';
|
||||
}
|
||||
if (this._perfLogFeatures.gc) {
|
||||
res['gcTime'] = 'gc time in ms';
|
||||
res['gcAmount'] = 'gc amount in kbytes';
|
||||
res['majorGcTime'] = 'time of major gcs in ms';
|
||||
if (this._forceGc) {
|
||||
res['forcedGcTime'] = 'forced gc time in ms';
|
||||
res['forcedGcAmount'] = 'forced gc amount in kbytes';
|
||||
}
|
||||
}
|
||||
if (this._receivedData) {
|
||||
res['receivedData'] = 'encoded bytes received since navigationStart';
|
||||
}
|
||||
if (this._requestCount) {
|
||||
res['requestCount'] = 'count of requests sent since navigationStart';
|
||||
}
|
||||
if (this._captureFrames) {
|
||||
if (!this._perfLogFeatures.frameCapture) {
|
||||
var warningMsg = 'WARNING: Metric requested, but not supported by driver';
|
||||
// using dot syntax for metric name to keep them grouped together in console reporter
|
||||
res['frameTime.mean'] = warningMsg;
|
||||
res['frameTime.worst'] = warningMsg;
|
||||
res['frameTime.best'] = warningMsg;
|
||||
res['frameTime.smooth'] = warningMsg;
|
||||
} else {
|
||||
res['frameTime.mean'] = 'mean frame time in ms (target: 16.6ms for 60fps)';
|
||||
res['frameTime.worst'] = 'worst frame time in ms';
|
||||
res['frameTime.best'] = 'best frame time in ms';
|
||||
res['frameTime.smooth'] = 'percentage of frames that hit 60fps';
|
||||
}
|
||||
}
|
||||
for (let name in this._microMetrics) {
|
||||
res[name] = this._microMetrics[name];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
beginMeasure(): Promise<any> {
|
||||
var resultPromise = Promise.resolve(null);
|
||||
if (this._forceGc) {
|
||||
resultPromise = resultPromise.then((_) => this._driverExtension.gc());
|
||||
}
|
||||
return resultPromise.then((_) => this._beginMeasure());
|
||||
}
|
||||
|
||||
endMeasure(restart: boolean): Promise<{[key: string]: number}> {
|
||||
if (this._forceGc) {
|
||||
return this._endPlainMeasureAndMeasureForceGc(restart);
|
||||
} else {
|
||||
return this._endMeasure(restart);
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
private _endPlainMeasureAndMeasureForceGc(restartMeasure: boolean) {
|
||||
return this._endMeasure(true).then((measureValues) => {
|
||||
// disable frame capture for measurements during forced gc
|
||||
var originalFrameCaptureValue = this._captureFrames;
|
||||
this._captureFrames = false;
|
||||
return this._driverExtension.gc()
|
||||
.then((_) => this._endMeasure(restartMeasure))
|
||||
.then((forceGcMeasureValues) => {
|
||||
this._captureFrames = originalFrameCaptureValue;
|
||||
measureValues['forcedGcTime'] = forceGcMeasureValues['gcTime'];
|
||||
measureValues['forcedGcAmount'] = forceGcMeasureValues['gcAmount'];
|
||||
return measureValues;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _beginMeasure(): Promise<any> {
|
||||
return this._driverExtension.timeBegin(this._markName(this._measureCount++));
|
||||
}
|
||||
|
||||
private _endMeasure(restart: boolean): Promise<{[key: string]: number}> {
|
||||
var markName = this._markName(this._measureCount - 1);
|
||||
var nextMarkName = restart ? this._markName(this._measureCount++) : null;
|
||||
return this._driverExtension.timeEnd(markName, nextMarkName)
|
||||
.then((_) => this._readUntilEndMark(markName));
|
||||
}
|
||||
|
||||
private _readUntilEndMark(
|
||||
markName: string, loopCount: number = 0, startEvent: PerfLogEvent = null) {
|
||||
if (loopCount > _MAX_RETRY_COUNT) {
|
||||
throw new Error(`Tried too often to get the ending mark: ${loopCount}`);
|
||||
}
|
||||
return this._driverExtension.readPerfLog().then((events) => {
|
||||
this._addEvents(events);
|
||||
var result = this._aggregateEvents(this._remainingEvents, markName);
|
||||
if (result) {
|
||||
this._remainingEvents = events;
|
||||
return result;
|
||||
}
|
||||
var resolve: (result: any) => void;
|
||||
var promise = new Promise(res => { resolve = res; });
|
||||
this._setTimeout(() => resolve(this._readUntilEndMark(markName, loopCount + 1)), 100);
|
||||
return promise;
|
||||
});
|
||||
}
|
||||
|
||||
private _addEvents(events: PerfLogEvent[]) {
|
||||
var needSort = false;
|
||||
events.forEach(event => {
|
||||
if (event['ph'] === 'X') {
|
||||
needSort = true;
|
||||
var startEvent: PerfLogEvent = {};
|
||||
var endEvent: PerfLogEvent = {};
|
||||
for (let prop in event) {
|
||||
startEvent[prop] = event[prop];
|
||||
endEvent[prop] = event[prop];
|
||||
}
|
||||
startEvent['ph'] = 'B';
|
||||
endEvent['ph'] = 'E';
|
||||
endEvent['ts'] = startEvent['ts'] + startEvent['dur'];
|
||||
this._remainingEvents.push(startEvent);
|
||||
this._remainingEvents.push(endEvent);
|
||||
} else {
|
||||
this._remainingEvents.push(event);
|
||||
}
|
||||
});
|
||||
if (needSort) {
|
||||
// Need to sort because of the ph==='X' events
|
||||
this._remainingEvents.sort((a, b) => {
|
||||
var diff = a['ts'] - b['ts'];
|
||||
return diff > 0 ? 1 : diff < 0 ? -1 : 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _aggregateEvents(events: PerfLogEvent[], markName: string): {[key: string]: number} {
|
||||
var result: {[key: string]: number} = {'scriptTime': 0, 'pureScriptTime': 0};
|
||||
if (this._perfLogFeatures.gc) {
|
||||
result['gcTime'] = 0;
|
||||
result['majorGcTime'] = 0;
|
||||
result['gcAmount'] = 0;
|
||||
}
|
||||
if (this._perfLogFeatures.render) {
|
||||
result['renderTime'] = 0;
|
||||
}
|
||||
if (this._captureFrames) {
|
||||
result['frameTime.mean'] = 0;
|
||||
result['frameTime.best'] = 0;
|
||||
result['frameTime.worst'] = 0;
|
||||
result['frameTime.smooth'] = 0;
|
||||
}
|
||||
for (let name in this._microMetrics) {
|
||||
result[name] = 0;
|
||||
}
|
||||
if (this._receivedData) {
|
||||
result['receivedData'] = 0;
|
||||
}
|
||||
if (this._requestCount) {
|
||||
result['requestCount'] = 0;
|
||||
}
|
||||
|
||||
var markStartEvent: 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 renderTimeInScript = 0;
|
||||
|
||||
var frameTimestamps: number[] = [];
|
||||
var frameTimes: number[] = [];
|
||||
var frameCaptureStartEvent: PerfLogEvent = null;
|
||||
var frameCaptureEndEvent: PerfLogEvent = null;
|
||||
|
||||
var intervalStarts: {[key: string]: PerfLogEvent} = {};
|
||||
var intervalStartCount: {[key: string]: number} = {};
|
||||
|
||||
var inMeasureRange = false;
|
||||
events.forEach((event) => {
|
||||
var ph = event['ph'];
|
||||
var name = event['name'];
|
||||
var microIterations = 1;
|
||||
var microIterationsMatch = name.match(_MICRO_ITERATIONS_REGEX);
|
||||
if (microIterationsMatch) {
|
||||
name = microIterationsMatch[1];
|
||||
microIterations = parseInt(microIterationsMatch[2], 10);
|
||||
}
|
||||
if (event === markStartEvent) {
|
||||
inMeasureRange = true;
|
||||
} else if (event === markEndEvent) {
|
||||
inMeasureRange = false;
|
||||
}
|
||||
if (!inMeasureRange || event['pid'] !== markStartEvent['pid']) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._requestCount && name === 'sendRequest') {
|
||||
result['requestCount'] += 1;
|
||||
} else if (this._receivedData && name === 'receivedData' && ph === 'I') {
|
||||
result['receivedData'] += event['args']['encodedDataLength'];
|
||||
}
|
||||
if (ph === 'B' && name === _MARK_NAME_FRAME_CAPUTRE) {
|
||||
if (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 (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 (ph === 'B') {
|
||||
if (!intervalStarts[name]) {
|
||||
intervalStartCount[name] = 1;
|
||||
intervalStarts[name] = event;
|
||||
} else {
|
||||
intervalStartCount[name]++;
|
||||
}
|
||||
} else if ((ph === 'E') && intervalStarts[name]) {
|
||||
intervalStartCount[name]--;
|
||||
if (intervalStartCount[name] === 0) {
|
||||
var startEvent = intervalStarts[name];
|
||||
var duration = (event['ts'] - startEvent['ts']);
|
||||
intervalStarts[name] = null;
|
||||
if (name === 'gc') {
|
||||
result['gcTime'] += duration;
|
||||
var amount =
|
||||
(startEvent['args']['usedHeapSize'] - event['args']['usedHeapSize']) / 1000;
|
||||
result['gcAmount'] += amount;
|
||||
var majorGc = event['args']['majorGc'];
|
||||
if (majorGc && majorGc) {
|
||||
result['majorGcTime'] += duration;
|
||||
}
|
||||
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 (frameCaptureStartEvent && !frameCaptureEndEvent) {
|
||||
throw new Error('missing end event for frame capture');
|
||||
}
|
||||
if (this._captureFrames && !frameCaptureStartEvent) {
|
||||
throw new Error('frame capture requested in benchpress, but no start event was found');
|
||||
}
|
||||
if (frameTimes.length > 0) {
|
||||
this._addFrameMetrics(result, frameTimes);
|
||||
}
|
||||
result['pureScriptTime'] = result['scriptTime'] - gcTimeInScript - renderTimeInScript;
|
||||
return result;
|
||||
}
|
||||
|
||||
private _addFrameMetrics(result: {[key: string]: number}, frameTimes: any[]) {
|
||||
result['frameTime.mean'] = frameTimes.reduce((a, b) => a + b, 0) / frameTimes.length;
|
||||
var firstFrame = frameTimes[0];
|
||||
result['frameTime.worst'] = frameTimes.reduce((a, b) => a > b ? a : b, firstFrame);
|
||||
result['frameTime.best'] = frameTimes.reduce((a, b) => a < b ? a : b, firstFrame);
|
||||
result['frameTime.smooth'] =
|
||||
frameTimes.filter(t => t < _FRAME_TIME_SMOOTH_THRESHOLD).length / frameTimes.length;
|
||||
}
|
||||
|
||||
private _markName(index: number) { return `${_MARK_NAME_PREFIX}${index}`; }
|
||||
}
|
||||
|
||||
var _MICRO_ITERATIONS_REGEX = /(.+)\*(\d+)$/;
|
||||
|
||||
var _MAX_RETRY_COUNT = 20;
|
||||
var _MARK_NAME_PREFIX = 'benchpress';
|
||||
|
||||
var _MARK_NAME_FRAME_CAPUTRE = 'frameCapture';
|
||||
// using 17ms as a somewhat looser threshold, instead of 16.6666ms
|
||||
var _FRAME_TIME_SMOOTH_THRESHOLD = 17;
|
71
modules/@angular/benchpress/src/metric/user_metric.ts
Normal file
71
modules/@angular/benchpress/src/metric/user_metric.ts
Normal file
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* @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 {Inject, Injectable} from '@angular/core';
|
||||
|
||||
import {Options} from '../common_options';
|
||||
import {StringMapWrapper} from '../facade/collection';
|
||||
import {isNumber} from '../facade/lang';
|
||||
import {Metric} from '../metric';
|
||||
import {WebDriverAdapter} from '../web_driver_adapter';
|
||||
|
||||
@Injectable()
|
||||
export class UserMetric extends Metric {
|
||||
static PROVIDERS = [UserMetric];
|
||||
|
||||
constructor(
|
||||
@Inject(Options.USER_METRICS) private _userMetrics: {[key: string]: string},
|
||||
private _wdAdapter: WebDriverAdapter) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts measuring
|
||||
*/
|
||||
beginMeasure(): Promise<any> { return Promise.resolve(true); }
|
||||
|
||||
/**
|
||||
* Ends measuring.
|
||||
*/
|
||||
endMeasure(restart: boolean): Promise<{[key: string]: any}> {
|
||||
let resolve: (result: any) => void;
|
||||
let reject: (error: any) => void;
|
||||
let promise = new Promise((res, rej) => {
|
||||
resolve = res;
|
||||
reject = rej;
|
||||
});
|
||||
let adapter = this._wdAdapter;
|
||||
let names = StringMapWrapper.keys(this._userMetrics);
|
||||
|
||||
function getAndClearValues() {
|
||||
Promise.all(names.map(name => adapter.executeScript(`return window.${name}`)))
|
||||
.then((values: any[]) => {
|
||||
if (values.every(isNumber)) {
|
||||
Promise.all(names.map(name => adapter.executeScript(`delete window.${name}`)))
|
||||
.then((_: any[]) => {
|
||||
let map: {[k: string]: any} = {};
|
||||
for (let i = 0, n = names.length; i < n; i++) {
|
||||
map[names[i]] = values[i];
|
||||
}
|
||||
resolve(map);
|
||||
}, reject);
|
||||
} else {
|
||||
<any>setTimeout(getAndClearValues, 100);
|
||||
}
|
||||
}, reject);
|
||||
}
|
||||
getAndClearValues();
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the metrics provided by this metric implementation.
|
||||
* (e.g. units, ...)
|
||||
*/
|
||||
describe(): {[key: string]: any} { return this._userMetrics; }
|
||||
}
|
@ -1,17 +1,20 @@
|
||||
import {BaseException, WrappedException} from '@angular/facade';
|
||||
/**
|
||||
* @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 {MeasureValues} from './measure_values';
|
||||
|
||||
/**
|
||||
* A reporter reports measure values and the valid sample.
|
||||
*/
|
||||
export abstract class Reporter {
|
||||
static bindTo(delegateToken): any[] {
|
||||
return [{provide: Reporter, useFactory: (delegate) => delegate, deps: [delegateToken]}];
|
||||
}
|
||||
|
||||
reportMeasureValues(values: MeasureValues): Promise<any> { throw new BaseException('NYI'); }
|
||||
reportMeasureValues(values: MeasureValues): Promise<any> { throw new Error('NYI'); }
|
||||
|
||||
reportSample(completeSample: MeasureValues[], validSample: MeasureValues[]): Promise<any> {
|
||||
throw new BaseException('NYI');
|
||||
throw new Error('NYI');
|
||||
}
|
||||
}
|
86
modules/@angular/benchpress/src/reporter/console_reporter.ts
Normal file
86
modules/@angular/benchpress/src/reporter/console_reporter.ts
Normal file
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* @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 {Inject, Injectable, OpaqueToken} from '@angular/core';
|
||||
|
||||
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 {Reporter} from '../reporter';
|
||||
import {SampleDescription} from '../sample_description';
|
||||
|
||||
import {formatNum, formatStats, sortedProps} from './util';
|
||||
|
||||
|
||||
/**
|
||||
* A reporter for the console
|
||||
*/
|
||||
@Injectable()
|
||||
export class ConsoleReporter extends Reporter {
|
||||
static PRINT = new OpaqueToken('ConsoleReporter.print');
|
||||
static COLUMN_WIDTH = new OpaqueToken('ConsoleReporter.columnWidth');
|
||||
static PROVIDERS = [
|
||||
ConsoleReporter, {provide: ConsoleReporter.COLUMN_WIDTH, useValue: 18},
|
||||
{provide: ConsoleReporter.PRINT, useValue: print}
|
||||
];
|
||||
|
||||
private static _lpad(value: string, columnWidth: number, fill = ' ') {
|
||||
var result = '';
|
||||
for (var i = 0; i < columnWidth - value.length; i++) {
|
||||
result += fill;
|
||||
}
|
||||
return result + value;
|
||||
}
|
||||
|
||||
private _metricNames: string[];
|
||||
|
||||
constructor(
|
||||
@Inject(ConsoleReporter.COLUMN_WIDTH) private _columnWidth: number,
|
||||
sampleDescription: SampleDescription,
|
||||
@Inject(ConsoleReporter.PRINT) private _print: Function) {
|
||||
super();
|
||||
this._metricNames = sortedProps(sampleDescription.metrics);
|
||||
this._printDescription(sampleDescription);
|
||||
}
|
||||
|
||||
private _printDescription(sampleDescription: SampleDescription) {
|
||||
this._print(`BENCHMARK ${sampleDescription.id}`);
|
||||
this._print('Description:');
|
||||
var props = sortedProps(sampleDescription.description);
|
||||
props.forEach((prop) => { this._print(`- ${prop}: ${sampleDescription.description[prop]}`); });
|
||||
this._print('Metrics:');
|
||||
this._metricNames.forEach((metricName) => {
|
||||
this._print(`- ${metricName}: ${sampleDescription.metrics[metricName]}`);
|
||||
});
|
||||
this._print('');
|
||||
this._printStringRow(this._metricNames);
|
||||
this._printStringRow(this._metricNames.map((_) => ''), '-');
|
||||
}
|
||||
|
||||
reportMeasureValues(measureValues: MeasureValues): Promise<any> {
|
||||
var formattedValues = this._metricNames.map(metricName => {
|
||||
var value = measureValues.values[metricName];
|
||||
return formatNum(value);
|
||||
});
|
||||
this._printStringRow(formattedValues);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
reportSample(completeSample: MeasureValues[], validSamples: MeasureValues[]): Promise<any> {
|
||||
this._printStringRow(this._metricNames.map((_) => ''), '=');
|
||||
this._printStringRow(
|
||||
this._metricNames.map(metricName => formatStats(validSamples, metricName)));
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
private _printStringRow(parts: any[], fill = ' ') {
|
||||
this._print(
|
||||
parts.map(part => ConsoleReporter._lpad(part, this._columnWidth, fill)).join(' | '));
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* @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 {Inject, Injectable, OpaqueToken} from '@angular/core';
|
||||
|
||||
import {Options} from '../common_options';
|
||||
import {DateWrapper, Json, isBlank, isPresent} from '../facade/lang';
|
||||
import {MeasureValues} from '../measure_values';
|
||||
import {Reporter} from '../reporter';
|
||||
import {SampleDescription} from '../sample_description';
|
||||
|
||||
import {formatStats, sortedProps} from './util';
|
||||
|
||||
|
||||
/**
|
||||
* A reporter that writes results into a json file.
|
||||
*/
|
||||
@Injectable()
|
||||
export class JsonFileReporter extends Reporter {
|
||||
static PATH = new OpaqueToken('JsonFileReporter.path');
|
||||
static PROVIDERS = [JsonFileReporter, {provide: JsonFileReporter.PATH, useValue: '.'}];
|
||||
|
||||
constructor(
|
||||
private _description: SampleDescription, @Inject(JsonFileReporter.PATH) private _path: string,
|
||||
@Inject(Options.WRITE_FILE) private _writeFile: Function,
|
||||
@Inject(Options.NOW) private _now: Function) {
|
||||
super();
|
||||
}
|
||||
|
||||
reportMeasureValues(measureValues: MeasureValues): Promise<any> { return Promise.resolve(null); }
|
||||
|
||||
reportSample(completeSample: MeasureValues[], validSample: MeasureValues[]): Promise<any> {
|
||||
const stats: {[key: string]: string} = {};
|
||||
sortedProps(this._description.metrics).forEach((metricName) => {
|
||||
stats[metricName] = formatStats(validSample, metricName);
|
||||
});
|
||||
var content = Json.stringify({
|
||||
'description': this._description,
|
||||
'stats': stats,
|
||||
'completeSample': completeSample,
|
||||
'validSample': validSample,
|
||||
});
|
||||
var filePath =
|
||||
`${this._path}/${this._description.id}_${DateWrapper.toMillis(this._now())}.json`;
|
||||
return this._writeFile(filePath, content);
|
||||
}
|
||||
}
|
@ -1,35 +1,40 @@
|
||||
import {Injector, OpaqueToken} from '@angular/core/src/di';
|
||||
import {PromiseWrapper} from '@angular/facade';
|
||||
/**
|
||||
* @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 {Injector, OpaqueToken} from '@angular/core';
|
||||
|
||||
import {MeasureValues} from '../measure_values';
|
||||
import {Reporter} from '../reporter';
|
||||
|
||||
export class MultiReporter extends Reporter {
|
||||
static createBindings(childTokens: any[]): any[] {
|
||||
static provideWith(childTokens: any[]): any[] {
|
||||
return [
|
||||
{
|
||||
provide: _CHILDREN,
|
||||
useFactory: (injector: Injector) => childTokens.map(token => injector.get(token)),
|
||||
deps: [Injector],
|
||||
},
|
||||
{provide: MultiReporter, useFactory: children => new MultiReporter(children), deps: [_CHILDREN]}
|
||||
{
|
||||
provide: MultiReporter,
|
||||
useFactory: (children: Reporter[]) => new MultiReporter(children),
|
||||
deps: [_CHILDREN]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
_reporters: Reporter[];
|
||||
|
||||
constructor(reporters) {
|
||||
super();
|
||||
this._reporters = reporters;
|
||||
}
|
||||
constructor(private _reporters: Reporter[]) { super(); }
|
||||
|
||||
reportMeasureValues(values: MeasureValues): Promise<any[]> {
|
||||
return PromiseWrapper.all(
|
||||
this._reporters.map(reporter => reporter.reportMeasureValues(values)));
|
||||
return Promise.all(this._reporters.map(reporter => reporter.reportMeasureValues(values)));
|
||||
}
|
||||
|
||||
reportSample(completeSample: MeasureValues[], validSample: MeasureValues[]): Promise<any[]> {
|
||||
return PromiseWrapper.all(
|
||||
return Promise.all(
|
||||
this._reporters.map(reporter => reporter.reportSample(completeSample, validSample)));
|
||||
}
|
||||
}
|
33
modules/@angular/benchpress/src/reporter/util.ts
Normal file
33
modules/@angular/benchpress/src/reporter/util.ts
Normal file
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* @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 {StringMapWrapper} from '../facade/collection';
|
||||
import {NumberWrapper} from '../facade/lang';
|
||||
import {MeasureValues} from '../measure_values';
|
||||
import {Statistic} from '../statistic';
|
||||
|
||||
export function formatNum(n: number) {
|
||||
return NumberWrapper.toFixed(n, 2);
|
||||
}
|
||||
|
||||
export function sortedProps(obj: {[key: string]: any}) {
|
||||
var props: string[] = [];
|
||||
StringMapWrapper.forEach(obj, (value, prop) => props.push(prop));
|
||||
props.sort();
|
||||
return props;
|
||||
}
|
||||
|
||||
export function formatStats(validSamples: MeasureValues[], metricName: string): string {
|
||||
var samples = validSamples.map(measureValues => measureValues.values[metricName]);
|
||||
var mean = Statistic.calculateMean(samples);
|
||||
var cv = Statistic.calculateCoefficientOfVariation(samples, mean);
|
||||
var formattedMean = formatNum(mean);
|
||||
// Note: Don't use the unicode character for +- as it might cause
|
||||
// hickups for consoles...
|
||||
return NumberWrapper.isNaN(cv) ? formattedMean : `${formattedMean}+-${Math.floor(cv)}%`;
|
||||
}
|
@ -1,46 +1,52 @@
|
||||
import {ReflectiveInjector} from '@angular/core';
|
||||
import {isPresent, isBlank} from '@angular/facade';
|
||||
import {PromiseWrapper} from '@angular/facade';
|
||||
/**
|
||||
* @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 {Sampler, SampleState} from './sampler';
|
||||
import {Provider, ReflectiveInjector} from '@angular/core';
|
||||
|
||||
import {Options} from './common_options';
|
||||
import {isBlank, isPresent} from './facade/lang';
|
||||
import {Metric} from './metric';
|
||||
import {MultiMetric} from './metric/multi_metric';
|
||||
import {PerflogMetric} from './metric/perflog_metric';
|
||||
import {UserMetric} from './metric/user_metric';
|
||||
import {Reporter} from './reporter';
|
||||
import {ConsoleReporter} from './reporter/console_reporter';
|
||||
import {MultiReporter} from './reporter/multi_reporter';
|
||||
import {SampleDescription} from './sample_description';
|
||||
import {SampleState, Sampler} from './sampler';
|
||||
import {Validator} from './validator';
|
||||
import {RegressionSlopeValidator} from './validator/regression_slope_validator';
|
||||
import {SizeValidator} from './validator/size_validator';
|
||||
import {Validator} from './validator';
|
||||
import {PerflogMetric} from './metric/perflog_metric';
|
||||
import {MultiMetric} from './metric/multi_metric';
|
||||
import {UserMetric} from './metric/user_metric';
|
||||
import {WebDriverAdapter} from './web_driver_adapter';
|
||||
import {WebDriverExtension} from './web_driver_extension';
|
||||
import {ChromeDriverExtension} from './webdriver/chrome_driver_extension';
|
||||
import {FirefoxDriverExtension} from './webdriver/firefox_driver_extension';
|
||||
import {IOsDriverExtension} from './webdriver/ios_driver_extension';
|
||||
import {WebDriverExtension} from './web_driver_extension';
|
||||
import {SampleDescription} from './sample_description';
|
||||
import {WebDriverAdapter} from './web_driver_adapter';
|
||||
import {Reporter} from './reporter';
|
||||
import {Metric} from './metric';
|
||||
import {Options} from './common_options';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The Runner is the main entry point for executing a sample run.
|
||||
* It provides defaults, creates the injector and calls the sampler.
|
||||
*/
|
||||
export class Runner {
|
||||
private _defaultProviders: any[];
|
||||
constructor(defaultProviders: any[] = null) {
|
||||
if (isBlank(defaultProviders)) {
|
||||
defaultProviders = [];
|
||||
}
|
||||
this._defaultProviders = defaultProviders;
|
||||
}
|
||||
constructor(private _defaultProviders: Provider[] = []) {}
|
||||
|
||||
sample({id, execute, prepare, microMetrics, providers, userMetrics}:
|
||||
{id: string, execute?: any, prepare?: any, microMetrics?: any, providers?: any, userMetrics?: any}):
|
||||
Promise<SampleState> {
|
||||
var sampleProviders = [
|
||||
_DEFAULT_PROVIDERS,
|
||||
this._defaultProviders,
|
||||
{provide: Options.SAMPLE_ID, useValue: id},
|
||||
sample({id, execute, prepare, microMetrics, providers, userMetrics}: {
|
||||
id: string,
|
||||
execute?: Function,
|
||||
prepare?: Function,
|
||||
microMetrics?: {[key: string]: string},
|
||||
providers?: Provider[],
|
||||
userMetrics?: {[key: string]: string}
|
||||
}): Promise<SampleState> {
|
||||
var sampleProviders: Provider[] = [
|
||||
_DEFAULT_PROVIDERS, this._defaultProviders, {provide: Options.SAMPLE_ID, useValue: id},
|
||||
{provide: Options.EXECUTE, useValue: execute}
|
||||
];
|
||||
if (isPresent(prepare)) {
|
||||
@ -57,9 +63,9 @@ export class Runner {
|
||||
}
|
||||
|
||||
var inj = ReflectiveInjector.resolveAndCreate(sampleProviders);
|
||||
var adapter = inj.get(WebDriverAdapter);
|
||||
var adapter: WebDriverAdapter = inj.get(WebDriverAdapter);
|
||||
|
||||
return PromiseWrapper
|
||||
return Promise
|
||||
.all([adapter.capabilities(), adapter.executeScript('return window.navigator.userAgent;')])
|
||||
.then((args) => {
|
||||
var capabilities = args[0];
|
||||
@ -71,8 +77,7 @@ export class Runner {
|
||||
// TODO vsavkin consider changing it when toAsyncFactory is added back or when child
|
||||
// injectors are handled better.
|
||||
var injector = ReflectiveInjector.resolveAndCreate([
|
||||
sampleProviders,
|
||||
{provide: Options.CAPABILITIES, useValue: capabilities},
|
||||
sampleProviders, {provide: Options.CAPABILITIES, useValue: capabilities},
|
||||
{provide: Options.USER_AGENT, useValue: userAgent},
|
||||
{provide: WebDriverAdapter, useValue: adapter}
|
||||
]);
|
||||
@ -93,12 +98,13 @@ var _DEFAULT_PROVIDERS = [
|
||||
FirefoxDriverExtension.PROVIDERS,
|
||||
IOsDriverExtension.PROVIDERS,
|
||||
PerflogMetric.PROVIDERS,
|
||||
UserMetric.BINDINGS,
|
||||
UserMetric.PROVIDERS,
|
||||
SampleDescription.PROVIDERS,
|
||||
MultiReporter.createBindings([ConsoleReporter]),
|
||||
MultiMetric.createBindings([PerflogMetric, UserMetric]),
|
||||
Reporter.bindTo(MultiReporter),
|
||||
Validator.bindTo(RegressionSlopeValidator),
|
||||
WebDriverExtension.bindTo([ChromeDriverExtension, FirefoxDriverExtension, IOsDriverExtension]),
|
||||
Metric.bindTo(MultiMetric),
|
||||
MultiReporter.provideWith([ConsoleReporter]),
|
||||
MultiMetric.provideWith([PerflogMetric, UserMetric]),
|
||||
{provide: Reporter, useExisting: MultiReporter},
|
||||
{provide: Validator, useExisting: RegressionSlopeValidator},
|
||||
WebDriverExtension.provideFirstSupported(
|
||||
[ChromeDriverExtension, FirefoxDriverExtension, IOsDriverExtension]),
|
||||
{provide: Metric, useExisting: MultiMetric},
|
||||
];
|
50
modules/@angular/benchpress/src/sample_description.ts
Normal file
50
modules/@angular/benchpress/src/sample_description.ts
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @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 {OpaqueToken} from '@angular/core';
|
||||
|
||||
import {Options} from './common_options';
|
||||
import {StringMapWrapper} from './facade/collection';
|
||||
import {Metric} from './metric';
|
||||
import {Validator} from './validator';
|
||||
|
||||
|
||||
/**
|
||||
* SampleDescription merges all available descriptions about a sample
|
||||
*/
|
||||
export class SampleDescription {
|
||||
static PROVIDERS = [{
|
||||
provide: SampleDescription,
|
||||
useFactory:
|
||||
(metric: Metric, id: string, forceGc: boolean, userAgent: string, validator: Validator,
|
||||
defaultDesc: {[key: string]: string}, userDesc: {[key: string]: string}) =>
|
||||
new SampleDescription(
|
||||
id,
|
||||
[
|
||||
{'forceGc': forceGc, 'userAgent': userAgent}, validator.describe(), defaultDesc,
|
||||
userDesc
|
||||
],
|
||||
metric.describe()),
|
||||
deps: [
|
||||
Metric, Options.SAMPLE_ID, Options.FORCE_GC, Options.USER_AGENT, Validator,
|
||||
Options.DEFAULT_DESCRIPTION, Options.SAMPLE_DESCRIPTION
|
||||
]
|
||||
}];
|
||||
description: {[key: string]: any};
|
||||
|
||||
constructor(
|
||||
public id: string, descriptions: Array<{[key: string]: any}>,
|
||||
public metrics: {[key: string]: any}) {
|
||||
this.description = {};
|
||||
descriptions.forEach(description => {
|
||||
StringMapWrapper.forEach(description, (value, prop) => this.description[prop] = value);
|
||||
});
|
||||
}
|
||||
|
||||
toJson() { return {'id': this.id, 'description': this.description, 'metrics': this.metrics}; }
|
||||
}
|
81
modules/@angular/benchpress/src/sampler.ts
Normal file
81
modules/@angular/benchpress/src/sampler.ts
Normal file
@ -0,0 +1,81 @@
|
||||
/**
|
||||
* @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 {Inject, Injectable, OpaqueToken} from '@angular/core';
|
||||
|
||||
import {Options} from './common_options';
|
||||
import {Date, DateWrapper, isBlank, isPresent} from './facade/lang';
|
||||
import {MeasureValues} from './measure_values';
|
||||
import {Metric} from './metric';
|
||||
import {Reporter} from './reporter';
|
||||
import {Validator} from './validator';
|
||||
import {WebDriverAdapter} from './web_driver_adapter';
|
||||
|
||||
|
||||
/**
|
||||
* The Sampler owns the sample loop:
|
||||
* 1. calls the prepare/execute callbacks,
|
||||
* 2. gets data from the metric
|
||||
* 3. asks the validator for a valid sample
|
||||
* 4. reports the new data to the reporter
|
||||
* 5. loop until there is a valid sample
|
||||
*/
|
||||
@Injectable()
|
||||
export class Sampler {
|
||||
static PROVIDERS = [Sampler];
|
||||
|
||||
constructor(
|
||||
private _driver: WebDriverAdapter, private _metric: Metric, private _reporter: Reporter,
|
||||
private _validator: Validator, @Inject(Options.PREPARE) private _prepare: Function,
|
||||
@Inject(Options.EXECUTE) private _execute: Function,
|
||||
@Inject(Options.NOW) private _now: Function) {}
|
||||
|
||||
sample(): Promise<SampleState> {
|
||||
const loop = (lastState: SampleState): Promise<SampleState> => {
|
||||
return this._iterate(lastState).then((newState) => {
|
||||
if (isPresent(newState.validSample)) {
|
||||
return newState;
|
||||
} else {
|
||||
return loop(newState);
|
||||
}
|
||||
});
|
||||
};
|
||||
return loop(new SampleState([], null));
|
||||
}
|
||||
|
||||
private _iterate(lastState: SampleState): Promise<SampleState> {
|
||||
var resultPromise: Promise<SampleState>;
|
||||
if (this._prepare !== Options.NO_PREPARE) {
|
||||
resultPromise = this._driver.waitFor(this._prepare);
|
||||
} else {
|
||||
resultPromise = Promise.resolve(null);
|
||||
}
|
||||
if (this._prepare !== Options.NO_PREPARE || lastState.completeSample.length === 0) {
|
||||
resultPromise = resultPromise.then((_) => this._metric.beginMeasure());
|
||||
}
|
||||
return resultPromise.then((_) => this._driver.waitFor(this._execute))
|
||||
.then((_) => this._metric.endMeasure(this._prepare === Options.NO_PREPARE))
|
||||
.then((measureValues) => this._report(lastState, measureValues));
|
||||
}
|
||||
|
||||
private _report(state: SampleState, metricValues: {[key: string]: any}): Promise<SampleState> {
|
||||
var measureValues = new MeasureValues(state.completeSample.length, this._now(), metricValues);
|
||||
var completeSample = state.completeSample.concat([measureValues]);
|
||||
var validSample = this._validator.validate(completeSample);
|
||||
var resultPromise = this._reporter.reportMeasureValues(measureValues);
|
||||
if (isPresent(validSample)) {
|
||||
resultPromise =
|
||||
resultPromise.then((_) => this._reporter.reportSample(completeSample, validSample));
|
||||
}
|
||||
return resultPromise.then((_) => new SampleState(completeSample, validSample));
|
||||
}
|
||||
}
|
||||
|
||||
export class SampleState {
|
||||
constructor(public completeSample: MeasureValues[], public validSample: MeasureValues[]) {}
|
||||
}
|
@ -1,7 +1,15 @@
|
||||
import {Math} from '@angular/facade';
|
||||
/**
|
||||
* @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 {Math} from './facade/math';
|
||||
|
||||
export class Statistic {
|
||||
static calculateCoefficientOfVariation(sample, mean) {
|
||||
static calculateCoefficientOfVariation(sample: number[], mean: number) {
|
||||
return Statistic.calculateStandardDeviation(sample, mean) / mean * 100;
|
||||
}
|
||||
|
||||
@ -12,7 +20,7 @@ export class Statistic {
|
||||
return total / samples.length;
|
||||
}
|
||||
|
||||
static calculateStandardDeviation(samples: number[], mean) {
|
||||
static calculateStandardDeviation(samples: number[], mean: number) {
|
||||
var deviation = 0;
|
||||
// TODO: use reduce
|
||||
samples.forEach(x => deviation += Math.pow(x - mean, 2));
|
||||
@ -21,8 +29,8 @@ export class Statistic {
|
||||
return deviation;
|
||||
}
|
||||
|
||||
static calculateRegressionSlope(xValues: number[], xMean: number, yValues: number[],
|
||||
yMean: number) {
|
||||
static calculateRegressionSlope(
|
||||
xValues: number[], xMean: number, yValues: number[], yMean: number) {
|
||||
// See http://en.wikipedia.org/wiki/Simple_linear_regression
|
||||
var dividendSum = 0;
|
||||
var divisorSum = 0;
|
@ -1,4 +1,10 @@
|
||||
import {BaseException, WrappedException} from '@angular/facade';
|
||||
/**
|
||||
* @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 {MeasureValues} from './measure_values';
|
||||
|
||||
@ -8,18 +14,14 @@ import {MeasureValues} from './measure_values';
|
||||
* in the correct way.
|
||||
*/
|
||||
export abstract class Validator {
|
||||
static bindTo(delegateToken): any[] {
|
||||
return [{provide: Validator, useFactory: (delegate) => delegate, deps: [delegateToken]}];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a valid sample out of the complete sample
|
||||
*/
|
||||
validate(completeSample: MeasureValues[]): MeasureValues[] { throw new BaseException('NYI'); }
|
||||
validate(completeSample: MeasureValues[]): MeasureValues[] { throw new Error('NYI'); }
|
||||
|
||||
/**
|
||||
* Returns a Map that describes the properties of the validator
|
||||
* (e.g. sample size, ...)
|
||||
*/
|
||||
describe(): {[key: string]: any} { throw new BaseException('NYI'); }
|
||||
describe(): {[key: string]: any} { throw new Error('NYI'); }
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @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 {Inject, Injectable, OpaqueToken} from '@angular/core';
|
||||
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {MeasureValues} from '../measure_values';
|
||||
import {Statistic} from '../statistic';
|
||||
import {Validator} from '../validator';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A validator that checks the regression slope of a specific metric.
|
||||
* Waits for the regression slope to be >=0.
|
||||
*/
|
||||
@Injectable()
|
||||
export class RegressionSlopeValidator extends Validator {
|
||||
static SAMPLE_SIZE = new OpaqueToken('RegressionSlopeValidator.sampleSize');
|
||||
static METRIC = new OpaqueToken('RegressionSlopeValidator.metric');
|
||||
static PROVIDERS = [
|
||||
RegressionSlopeValidator, {provide: RegressionSlopeValidator.SAMPLE_SIZE, useValue: 10},
|
||||
{provide: RegressionSlopeValidator.METRIC, useValue: 'scriptTime'}
|
||||
];
|
||||
|
||||
constructor(
|
||||
@Inject(RegressionSlopeValidator.SAMPLE_SIZE) private _sampleSize: number,
|
||||
@Inject(RegressionSlopeValidator.METRIC) private _metric: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
describe(): {[key: string]: any} {
|
||||
return {'sampleSize': this._sampleSize, 'regressionSlopeMetric': this._metric};
|
||||
}
|
||||
|
||||
validate(completeSample: MeasureValues[]): MeasureValues[] {
|
||||
if (completeSample.length >= this._sampleSize) {
|
||||
var latestSample = ListWrapper.slice(
|
||||
completeSample, completeSample.length - this._sampleSize, completeSample.length);
|
||||
var xValues: number[] = [];
|
||||
var yValues: number[] = [];
|
||||
for (var i = 0; i < latestSample.length; i++) {
|
||||
// For now, we only use the array index as x value.
|
||||
// TODO(tbosch): think about whether we should use time here instead
|
||||
xValues.push(i);
|
||||
yValues.push(latestSample[i].values[this._metric]);
|
||||
}
|
||||
var regressionSlope = Statistic.calculateRegressionSlope(
|
||||
xValues, Statistic.calculateMean(xValues), yValues, Statistic.calculateMean(yValues));
|
||||
return regressionSlope >= 0 ? latestSample : null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
37
modules/@angular/benchpress/src/validator/size_validator.ts
Normal file
37
modules/@angular/benchpress/src/validator/size_validator.ts
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Inject, Injectable, OpaqueToken} from '@angular/core';
|
||||
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {MeasureValues} from '../measure_values';
|
||||
import {Validator} from '../validator';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A validator that waits for the sample to have a certain size.
|
||||
*/
|
||||
@Injectable()
|
||||
export class SizeValidator extends Validator {
|
||||
static SAMPLE_SIZE = new OpaqueToken('SizeValidator.sampleSize');
|
||||
static PROVIDERS = [SizeValidator, {provide: SizeValidator.SAMPLE_SIZE, useValue: 10}];
|
||||
|
||||
constructor(@Inject(SizeValidator.SAMPLE_SIZE) private _sampleSize: number) { super(); }
|
||||
|
||||
describe(): {[key: string]: any} { return {'sampleSize': this._sampleSize}; }
|
||||
|
||||
validate(completeSample: MeasureValues[]): MeasureValues[] {
|
||||
if (completeSample.length >= this._sampleSize) {
|
||||
return ListWrapper.slice(
|
||||
completeSample, completeSample.length - this._sampleSize, completeSample.length);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
22
modules/@angular/benchpress/src/web_driver_adapter.ts
Normal file
22
modules/@angular/benchpress/src/web_driver_adapter.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A WebDriverAdapter bridges API differences between different WebDriver clients,
|
||||
* e.g. JS vs Dart Async vs Dart Sync webdriver.
|
||||
* Needs one implementation for every supported WebDriver client.
|
||||
*/
|
||||
export abstract class WebDriverAdapter {
|
||||
waitFor(callback: Function): Promise<any> { throw new Error('NYI'); }
|
||||
executeScript(script: string): Promise<any> { throw new Error('NYI'); }
|
||||
executeAsyncScript(script: string): Promise<any> { throw new Error('NYI'); }
|
||||
capabilities(): Promise<{[key: string]: any}> { throw new Error('NYI'); }
|
||||
logs(type: string): Promise<any[]> { throw new Error('NYI'); }
|
||||
}
|
@ -1,9 +1,32 @@
|
||||
import {Injector, OpaqueToken} from '@angular/core/src/di';
|
||||
/**
|
||||
* @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 {isBlank, isPresent} from '@angular/facade';
|
||||
import {BaseException, WrappedException} from '@angular/facade';
|
||||
import {Injector, OpaqueToken} from '@angular/core';
|
||||
|
||||
import {Options} from './common_options';
|
||||
import {isBlank, isPresent} from './facade/lang';
|
||||
|
||||
export type PerfLogEvent = {
|
||||
[key: string]: any
|
||||
} & {
|
||||
ph?: 'X' | 'B' | 'E' | 'I',
|
||||
ts?: number,
|
||||
dur?: number,
|
||||
name?: string,
|
||||
pid?: string,
|
||||
args?: {
|
||||
encodedDataLength?: number,
|
||||
usedHeapSize?: number,
|
||||
majorGc?: boolean,
|
||||
url?: string,
|
||||
method?: string
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A WebDriverExtension implements extended commands of the webdriver protocol
|
||||
@ -11,7 +34,7 @@ import {Options} from './common_options';
|
||||
* Needs one implementation for every supported Browser.
|
||||
*/
|
||||
export abstract class WebDriverExtension {
|
||||
static bindTo(childTokens: any[]): any[] {
|
||||
static provideFirstSupported(childTokens: any[]): any[] {
|
||||
var res = [
|
||||
{
|
||||
provide: _CHILDREN,
|
||||
@ -20,15 +43,15 @@ export abstract class WebDriverExtension {
|
||||
},
|
||||
{
|
||||
provide: WebDriverExtension,
|
||||
useFactory: (children:WebDriverExtension[], capabilities) => {
|
||||
var delegate;
|
||||
useFactory: (children: WebDriverExtension[], capabilities: {[key: string]: any}) => {
|
||||
var delegate: WebDriverExtension;
|
||||
children.forEach(extension => {
|
||||
if (extension.supports(capabilities)) {
|
||||
delegate = extension;
|
||||
}
|
||||
});
|
||||
if (isBlank(delegate)) {
|
||||
throw new BaseException('Could not find a delegate for given capabilities!');
|
||||
throw new Error('Could not find a delegate for given capabilities!');
|
||||
}
|
||||
return delegate;
|
||||
},
|
||||
@ -38,18 +61,17 @@ export abstract class WebDriverExtension {
|
||||
return res;
|
||||
}
|
||||
|
||||
gc(): Promise<any> { throw new BaseException('NYI'); }
|
||||
gc(): Promise<any> { throw new Error('NYI'); }
|
||||
|
||||
timeBegin(name: string): Promise<any> { throw new BaseException('NYI'); }
|
||||
timeBegin(name: string): Promise<any> { throw new Error('NYI'); }
|
||||
|
||||
timeEnd(name: string, restartName: string): Promise<any> { throw new BaseException('NYI'); }
|
||||
timeEnd(name: string, restartName: string): Promise<any> { throw new Error('NYI'); }
|
||||
|
||||
/**
|
||||
* Format:
|
||||
* - cat: category of the event
|
||||
* - name: event name: 'script', 'gc', 'render', ...
|
||||
* - ph: phase: 'B' (begin), 'E' (end), 'b' (nestable start), 'e' (nestable end), 'X' (Complete
|
||||
*event)
|
||||
* - ph: phase: 'B' (begin), 'E' (end), 'X' (Complete event), 'I' (Instant event)
|
||||
* - ts: timestamp in ms, e.g. 12345
|
||||
* - pid: process id
|
||||
* - args: arguments, e.g. {heapSize: 1234}
|
||||
@ -57,9 +79,9 @@ export abstract class WebDriverExtension {
|
||||
* Based on [Chrome Trace Event
|
||||
*Format](https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/edit)
|
||||
**/
|
||||
readPerfLog(): Promise<any[]> { throw new BaseException('NYI'); }
|
||||
readPerfLog(): Promise<PerfLogEvent[]> { throw new Error('NYI'); }
|
||||
|
||||
perfLogFeatures(): PerfLogFeatures { throw new BaseException('NYI'); }
|
||||
perfLogFeatures(): PerfLogFeatures { throw new Error('NYI'); }
|
||||
|
||||
supports(capabilities: {[key: string]: any}): boolean { return true; }
|
||||
}
|
@ -0,0 +1,208 @@
|
||||
/**
|
||||
* @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 {Inject, Injectable} from '@angular/core';
|
||||
|
||||
import {Options} from '../common_options';
|
||||
import {WebDriverAdapter} from '../web_driver_adapter';
|
||||
import {PerfLogEvent, PerfLogFeatures, WebDriverExtension} from '../web_driver_extension';
|
||||
|
||||
/**
|
||||
* Set the following 'traceCategories' to collect metrics in Chrome:
|
||||
* 'v8,blink.console,disabled-by-default-devtools.timeline,devtools.timeline,blink.user_timing'
|
||||
*
|
||||
* In order to collect the frame rate related metrics, add 'benchmark'
|
||||
* to the list above.
|
||||
*/
|
||||
@Injectable()
|
||||
export class ChromeDriverExtension extends WebDriverExtension {
|
||||
static PROVIDERS = [ChromeDriverExtension];
|
||||
|
||||
private _majorChromeVersion: number;
|
||||
|
||||
constructor(private _driver: WebDriverAdapter, @Inject(Options.USER_AGENT) userAgent: string) {
|
||||
super();
|
||||
this._majorChromeVersion = this._parseChromeVersion(userAgent);
|
||||
}
|
||||
|
||||
private _parseChromeVersion(userAgent: string): number {
|
||||
if (!userAgent) {
|
||||
return -1;
|
||||
}
|
||||
var v = userAgent.split(/Chrom(e|ium)\//g)[2];
|
||||
if (!v) {
|
||||
return -1;
|
||||
}
|
||||
v = v.split('.')[0];
|
||||
if (!v) {
|
||||
return -1;
|
||||
}
|
||||
return parseInt(v, 10);
|
||||
}
|
||||
|
||||
gc() { return this._driver.executeScript('window.gc()'); }
|
||||
|
||||
timeBegin(name: string): Promise<any> {
|
||||
return this._driver.executeScript(`console.time('${name}');`);
|
||||
}
|
||||
|
||||
timeEnd(name: string, restartName: string = null): Promise<any> {
|
||||
var script = `console.timeEnd('${name}');`;
|
||||
if (restartName) {
|
||||
script += `console.time('${restartName}');`;
|
||||
}
|
||||
return this._driver.executeScript(script);
|
||||
}
|
||||
|
||||
// See [Chrome Trace Event
|
||||
// Format](https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/edit)
|
||||
readPerfLog(): Promise<PerfLogEvent[]> {
|
||||
// TODO(tbosch): Chromedriver bug https://code.google.com/p/chromedriver/issues/detail?id=1098
|
||||
// Need to execute at least one command so that the browser logs can be read out!
|
||||
return this._driver.executeScript('1+1')
|
||||
.then((_) => this._driver.logs('performance'))
|
||||
.then((entries) => {
|
||||
var events: PerfLogEvent[] = [];
|
||||
entries.forEach(entry => {
|
||||
var message = JSON.parse(entry['message'])['message'];
|
||||
if (message['method'] === 'Tracing.dataCollected') {
|
||||
events.push(message['params']);
|
||||
}
|
||||
if (message['method'] === 'Tracing.bufferUsage') {
|
||||
throw new Error('The DevTools trace buffer filled during the test!');
|
||||
}
|
||||
});
|
||||
return this._convertPerfRecordsToEvents(events);
|
||||
});
|
||||
}
|
||||
|
||||
private _convertPerfRecordsToEvents(
|
||||
chromeEvents: Array<{[key: string]: any}>, normalizedEvents: PerfLogEvent[] = null) {
|
||||
if (!normalizedEvents) {
|
||||
normalizedEvents = [];
|
||||
}
|
||||
chromeEvents.forEach((event) => {
|
||||
const categories = this._parseCategories(event['cat']);
|
||||
const normalizedEvent = this._convertEvent(event, categories);
|
||||
if (normalizedEvent != null) normalizedEvents.push(normalizedEvent);
|
||||
});
|
||||
return normalizedEvents;
|
||||
}
|
||||
|
||||
private _convertEvent(event: {[key: string]: any}, categories: string[]) {
|
||||
var name = event['name'];
|
||||
var args = event['args'];
|
||||
if (this._isEvent(categories, name, ['blink.console'])) {
|
||||
return 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) {
|
||||
return 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')) {
|
||||
return normalizeEvent(event, {'name': 'render'});
|
||||
} else if (this._isEvent(categories, name, ['devtools.timeline', 'v8'], 'MajorGC')) {
|
||||
var normArgs = {
|
||||
'majorGc': true,
|
||||
'usedHeapSize': args['usedHeapSizeAfter'] !== undefined ? args['usedHeapSizeAfter'] :
|
||||
args['usedHeapSizeBefore']
|
||||
};
|
||||
return normalizeEvent(event, {'name': 'gc', 'args': normArgs});
|
||||
} else if (this._isEvent(categories, name, ['devtools.timeline', 'v8'], 'MinorGC')) {
|
||||
var normArgs = {
|
||||
'majorGc': false,
|
||||
'usedHeapSize': args['usedHeapSizeAfter'] !== undefined ? args['usedHeapSizeAfter'] :
|
||||
args['usedHeapSizeBefore']
|
||||
};
|
||||
return normalizeEvent(event, {'name': 'gc', 'args': normArgs});
|
||||
} else if (
|
||||
this._isEvent(categories, name, ['devtools.timeline'], 'FunctionCall') &&
|
||||
(!args || !args['data'] ||
|
||||
(args['data']['scriptName'] !== 'InjectedScript' && args['data']['scriptName'] !== ''))) {
|
||||
return normalizeEvent(event, {'name': 'script'});
|
||||
} else if (this._isEvent(categories, name, ['devtools.timeline'], 'EvaluateScript')) {
|
||||
return normalizeEvent(event, {'name': 'script'});
|
||||
} else if (this._isEvent(
|
||||
categories, name, ['devtools.timeline', 'blink'], 'UpdateLayoutTree')) {
|
||||
return normalizeEvent(event, {'name': 'render'});
|
||||
} else if (
|
||||
this._isEvent(categories, name, ['devtools.timeline'], 'UpdateLayerTree') ||
|
||||
this._isEvent(categories, name, ['devtools.timeline'], 'Layout') ||
|
||||
this._isEvent(categories, name, ['devtools.timeline'], 'Paint')) {
|
||||
return normalizeEvent(event, {'name': 'render'});
|
||||
} else if (this._isEvent(categories, name, ['devtools.timeline'], 'ResourceReceivedData')) {
|
||||
let normArgs = {'encodedDataLength': args['data']['encodedDataLength']};
|
||||
return normalizeEvent(event, {'name': 'receivedData', 'args': normArgs});
|
||||
} else if (this._isEvent(categories, name, ['devtools.timeline'], 'ResourceSendRequest')) {
|
||||
let data = args['data'];
|
||||
let normArgs = {'url': data['url'], 'method': data['requestMethod']};
|
||||
return normalizeEvent(event, {'name': 'sendRequest', 'args': normArgs});
|
||||
} else if (this._isEvent(categories, name, ['blink.user_timing'], 'navigationStart')) {
|
||||
return normalizeEvent(event, {'name': 'navigationStart'});
|
||||
}
|
||||
return null; // nothing useful in this event
|
||||
}
|
||||
|
||||
private _parseCategories(categories: string): string[] { return categories.split(','); }
|
||||
|
||||
private _isEvent(
|
||||
eventCategories: string[], eventName: string, expectedCategories: string[],
|
||||
expectedName: string = null): boolean {
|
||||
var hasCategories = expectedCategories.reduce(
|
||||
(value, cat) => { return value && eventCategories.indexOf(cat) !== -1; }, true);
|
||||
return !expectedName ? hasCategories : hasCategories && eventName === expectedName;
|
||||
}
|
||||
|
||||
perfLogFeatures(): PerfLogFeatures {
|
||||
return new PerfLogFeatures({render: true, gc: true, frameCapture: true, userTiming: true});
|
||||
}
|
||||
|
||||
supports(capabilities: {[key: string]: any}): boolean {
|
||||
return this._majorChromeVersion >= 44 && capabilities['browserName'].toLowerCase() === 'chrome';
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeEvent(chromeEvent: {[key: string]: any}, data: PerfLogEvent): PerfLogEvent {
|
||||
var ph = chromeEvent['ph'].toUpperCase();
|
||||
if (ph === 'S') {
|
||||
ph = 'B';
|
||||
} else if (ph === 'F') {
|
||||
ph = 'E';
|
||||
} else if (ph === 'R') {
|
||||
// mark events from navigation timing
|
||||
ph = 'I';
|
||||
}
|
||||
var result: {[key: string]: any} =
|
||||
{'pid': chromeEvent['pid'], 'ph': ph, 'cat': 'timeline', 'ts': chromeEvent['ts'] / 1000};
|
||||
if (ph === 'X') {
|
||||
var dur = chromeEvent['dur'];
|
||||
if (dur === undefined) {
|
||||
dur = chromeEvent['tdur'];
|
||||
}
|
||||
result['dur'] = !dur ? 0.0 : dur / 1000;
|
||||
}
|
||||
for (let prop in data) {
|
||||
result[prop] = data[prop];
|
||||
}
|
||||
return result;
|
||||
}
|
@ -1,9 +1,20 @@
|
||||
import {isPresent, StringWrapper} from '@angular/facade';
|
||||
import {WebDriverExtension, PerfLogFeatures} from '../web_driver_extension';
|
||||
import {WebDriverAdapter} from '../web_driver_adapter';
|
||||
/**
|
||||
* @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 {Injectable} from '@angular/core';
|
||||
|
||||
import {StringWrapper, isPresent} from '../facade/lang';
|
||||
import {WebDriverAdapter} from '../web_driver_adapter';
|
||||
import {PerfLogEvent, PerfLogFeatures, WebDriverExtension} from '../web_driver_extension';
|
||||
|
||||
@Injectable()
|
||||
export class FirefoxDriverExtension extends WebDriverExtension {
|
||||
static get PROVIDERS(): any[] { return _PROVIDERS; }
|
||||
static PROVIDERS = [FirefoxDriverExtension];
|
||||
|
||||
private _profilerStarted: boolean;
|
||||
|
||||
@ -30,7 +41,7 @@ export class FirefoxDriverExtension extends WebDriverExtension {
|
||||
return this._driver.executeScript(script);
|
||||
}
|
||||
|
||||
readPerfLog(): Promise<any> {
|
||||
readPerfLog(): Promise<PerfLogEvent> {
|
||||
return this._driver.executeAsyncScript('var cb = arguments[0]; window.getProfile(cb);');
|
||||
}
|
||||
|
||||
@ -40,11 +51,3 @@ export class FirefoxDriverExtension extends WebDriverExtension {
|
||||
return StringWrapper.equals(capabilities['browserName'].toLowerCase(), 'firefox');
|
||||
}
|
||||
}
|
||||
|
||||
var _PROVIDERS = [
|
||||
{
|
||||
provide: FirefoxDriverExtension,
|
||||
useFactory: (driver) => new FirefoxDriverExtension(driver),
|
||||
deps: [WebDriverAdapter]
|
||||
}
|
||||
];
|
@ -1,16 +1,24 @@
|
||||
import {Json, isPresent, isBlank, RegExpWrapper, StringWrapper} from '@angular/facade';
|
||||
import {BaseException, WrappedException} from '@angular/facade';
|
||||
/**
|
||||
* @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 {WebDriverExtension, PerfLogFeatures} from '../web_driver_extension';
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
import {StringWrapper, isBlank, isPresent} from '../facade/lang';
|
||||
import {WebDriverAdapter} from '../web_driver_adapter';
|
||||
import {PerfLogEvent, PerfLogFeatures, WebDriverExtension} from '../web_driver_extension';
|
||||
|
||||
@Injectable()
|
||||
export class IOsDriverExtension extends WebDriverExtension {
|
||||
// TODO(tbosch): use static values when our transpiler supports them
|
||||
static get PROVIDERS(): any[] { return _PROVIDERS; }
|
||||
static PROVIDERS = [IOsDriverExtension];
|
||||
|
||||
constructor(private _driver: WebDriverAdapter) { super(); }
|
||||
|
||||
gc(): Promise<any> { throw new BaseException('Force GC is not supported on iOS'); }
|
||||
gc(): Promise<any> { throw new Error('Force GC is not supported on iOS'); }
|
||||
|
||||
timeBegin(name: string): Promise<any> {
|
||||
return this._driver.executeScript(`console.time('${name}');`);
|
||||
@ -19,7 +27,7 @@ export class IOsDriverExtension extends WebDriverExtension {
|
||||
timeEnd(name: string, restartName: string = null): Promise<any> {
|
||||
var script = `console.timeEnd('${name}');`;
|
||||
if (isPresent(restartName)) {
|
||||
script += `console.time('${restartName}');`
|
||||
script += `console.time('${restartName}');`;
|
||||
}
|
||||
return this._driver.executeScript(script);
|
||||
}
|
||||
@ -31,9 +39,9 @@ export class IOsDriverExtension extends WebDriverExtension {
|
||||
return this._driver.executeScript('1+1')
|
||||
.then((_) => this._driver.logs('performance'))
|
||||
.then((entries) => {
|
||||
var records = [];
|
||||
var records: any[] = [];
|
||||
entries.forEach(entry => {
|
||||
var message = Json.parse(entry['message'])['message'];
|
||||
var message = JSON.parse(entry['message'])['message'];
|
||||
if (StringWrapper.equals(message['method'], 'Timeline.eventRecorded')) {
|
||||
records.push(message['params']['record']);
|
||||
}
|
||||
@ -42,12 +50,13 @@ export class IOsDriverExtension extends WebDriverExtension {
|
||||
});
|
||||
}
|
||||
|
||||
_convertPerfRecordsToEvents(records: any[], events: any[] = null) {
|
||||
/** @internal */
|
||||
private _convertPerfRecordsToEvents(records: any[], events: PerfLogEvent[] = null) {
|
||||
if (isBlank(events)) {
|
||||
events = [];
|
||||
}
|
||||
records.forEach((record) => {
|
||||
var endEvent = null;
|
||||
var endEvent: PerfLogEvent = null;
|
||||
var type = record['type'];
|
||||
var data = record['data'];
|
||||
var startTime = record['startTime'];
|
||||
@ -61,11 +70,11 @@ export class IOsDriverExtension extends WebDriverExtension {
|
||||
events.push(createMarkStartEvent(data['message'], startTime));
|
||||
} else if (StringWrapper.equals(type, 'TimeEnd')) {
|
||||
events.push(createMarkEndEvent(data['message'], startTime));
|
||||
} else if (StringWrapper.equals(type, 'RecalculateStyles') ||
|
||||
StringWrapper.equals(type, 'Layout') ||
|
||||
StringWrapper.equals(type, 'UpdateLayerTree') ||
|
||||
StringWrapper.equals(type, 'Paint') || StringWrapper.equals(type, 'Rasterize') ||
|
||||
StringWrapper.equals(type, 'CompositeLayers')) {
|
||||
} else if (
|
||||
StringWrapper.equals(type, 'RecalculateStyles') || StringWrapper.equals(type, 'Layout') ||
|
||||
StringWrapper.equals(type, 'UpdateLayerTree') || StringWrapper.equals(type, 'Paint') ||
|
||||
StringWrapper.equals(type, 'Rasterize') ||
|
||||
StringWrapper.equals(type, 'CompositeLayers')) {
|
||||
events.push(createStartEvent('render', startTime));
|
||||
endEvent = createEndEvent('render', endTime);
|
||||
}
|
||||
@ -87,8 +96,9 @@ export class IOsDriverExtension extends WebDriverExtension {
|
||||
}
|
||||
}
|
||||
|
||||
function createEvent(ph, name, time, args = null) {
|
||||
var result = {
|
||||
function createEvent(
|
||||
ph: 'X' | 'B' | 'E' | 'B' | 'E', name: string, time: number, args: any = null) {
|
||||
var result: PerfLogEvent = {
|
||||
'cat': 'timeline',
|
||||
'name': name,
|
||||
'ts': time,
|
||||
@ -103,22 +113,18 @@ function createEvent(ph, name, time, args = null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function createStartEvent(name, time, args = null) {
|
||||
function createStartEvent(name: string, time: number, args: any = null) {
|
||||
return createEvent('B', name, time, args);
|
||||
}
|
||||
|
||||
function createEndEvent(name, time, args = null) {
|
||||
function createEndEvent(name: string, time: number, args: any = null) {
|
||||
return createEvent('E', name, time, args);
|
||||
}
|
||||
|
||||
function createMarkStartEvent(name, time) {
|
||||
return createEvent('b', name, time);
|
||||
function createMarkStartEvent(name: string, time: number) {
|
||||
return createEvent('B', name, time);
|
||||
}
|
||||
|
||||
function createMarkEndEvent(name, time) {
|
||||
return createEvent('e', name, time);
|
||||
function createMarkEndEvent(name: string, time: number) {
|
||||
return createEvent('E', name, time);
|
||||
}
|
||||
|
||||
var _PROVIDERS = [
|
||||
{provide: IOsDriverExtension, useFactory: (driver) => new IOsDriverExtension(driver), deps: [WebDriverAdapter]}
|
||||
];
|
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* @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 {WebDriverAdapter} from '../web_driver_adapter';
|
||||
|
||||
/**
|
||||
* Adapter for the selenium-webdriver.
|
||||
*/
|
||||
export class SeleniumWebDriverAdapter extends WebDriverAdapter {
|
||||
static PROTRACTOR_PROVIDERS = [{
|
||||
provide: WebDriverAdapter,
|
||||
useFactory: () => new SeleniumWebDriverAdapter((<any>global).browser)
|
||||
}];
|
||||
|
||||
constructor(private _driver: any) { super(); }
|
||||
|
||||
waitFor(callback: () => any): Promise<any> { return this._driver.call(callback); }
|
||||
|
||||
executeScript(script: string): Promise<any> { return this._driver.executeScript(script); }
|
||||
|
||||
executeAsyncScript(script: string): Promise<any> {
|
||||
return this._driver.executeAsyncScript(script);
|
||||
}
|
||||
|
||||
capabilities(): Promise<{[key: string]: any}> {
|
||||
return this._driver.getCapabilities().then((capsObject: any) => {
|
||||
const localData: {[key: string]: any} = {};
|
||||
capsObject.forEach((value: any, key: string) => { localData[key] = value; });
|
||||
return localData;
|
||||
});
|
||||
}
|
||||
|
||||
logs(type: string): Promise<any> {
|
||||
// Needed as selenium-webdriver does not forward
|
||||
// performance logs in the correct way via manage().logs
|
||||
return this._driver.schedule(
|
||||
new Command('getLog').setParameter('type', type),
|
||||
'WebDriver.manage().logs().get(' + type + ')');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy of the `Command` class of webdriver as
|
||||
* it is not exposed via index.js in selenium-webdriver.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
setParameters(parameters: {[key: string]: any}) {
|
||||
this.parameters_ = parameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
getParameter(key: string) { return this.parameters_[key]; }
|
||||
|
||||
getParameters() { return this.parameters_; }
|
||||
}
|
@ -1,4 +1,12 @@
|
||||
require('es6-shim/es6-shim.js');
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
require('core-js');
|
||||
require('reflect-metadata');
|
||||
var testHelper = require('../../src/firefox_extension/lib/test_helper.js');
|
||||
|
@ -0,0 +1,100 @@
|
||||
/**
|
||||
* @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 {convertPerfProfileToEvents} from '../../src/firefox_extension/lib/parser_util';
|
||||
|
||||
function assertEventsEqual(actualEvents: any[], expectedEvents: any[]) {
|
||||
expect(actualEvents.length == expectedEvents.length);
|
||||
for (var i = 0; i < actualEvents.length; ++i) {
|
||||
var actualEvent = actualEvents[i];
|
||||
var expectedEvent = expectedEvents[i];
|
||||
for (var key in actualEvent) {
|
||||
expect(actualEvent[key]).toEqual(expectedEvent[key]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function main() {
|
||||
describe('convertPerfProfileToEvents', function() {
|
||||
it('should convert single instantaneous event', function() {
|
||||
var profileData = {
|
||||
threads: [
|
||||
{samples: [{time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]}]}
|
||||
]
|
||||
};
|
||||
var perfEvents = convertPerfProfileToEvents(profileData);
|
||||
assertEventsEqual(perfEvents, [{ph: 'X', ts: 1, name: 'script'}]);
|
||||
});
|
||||
|
||||
it('should convert single non-instantaneous event', function() {
|
||||
var profileData = {
|
||||
threads: [{
|
||||
samples: [
|
||||
{time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]},
|
||||
{time: 2, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]},
|
||||
{time: 100, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]}
|
||||
]
|
||||
}]
|
||||
};
|
||||
var perfEvents = convertPerfProfileToEvents(profileData);
|
||||
assertEventsEqual(
|
||||
perfEvents, [{ph: 'B', ts: 1, name: 'script'}, {ph: 'E', ts: 100, name: 'script'}]);
|
||||
});
|
||||
|
||||
it('should convert multiple instantaneous events', function() {
|
||||
var profileData = {
|
||||
threads: [{
|
||||
samples: [
|
||||
{time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]},
|
||||
{time: 2, frames: [{location: 'PresShell::Paint'}]}
|
||||
]
|
||||
}]
|
||||
};
|
||||
var perfEvents = convertPerfProfileToEvents(profileData);
|
||||
assertEventsEqual(
|
||||
perfEvents, [{ph: 'X', ts: 1, name: 'script'}, {ph: 'X', ts: 2, name: 'render'}]);
|
||||
});
|
||||
|
||||
it('should convert multiple mixed events', function() {
|
||||
var profileData = {
|
||||
threads: [{
|
||||
samples: [
|
||||
{time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]},
|
||||
{time: 2, frames: [{location: 'PresShell::Paint'}]},
|
||||
{time: 5, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]},
|
||||
{time: 10, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]}
|
||||
]
|
||||
}]
|
||||
};
|
||||
var perfEvents = convertPerfProfileToEvents(profileData);
|
||||
assertEventsEqual(perfEvents, [
|
||||
{ph: 'X', ts: 1, name: 'script'}, {ph: 'X', ts: 2, name: 'render'},
|
||||
{ph: 'B', ts: 5, name: 'script'}, {ph: 'E', ts: 10, name: 'script'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should add args to gc events', function() {
|
||||
var profileData = {threads: [{samples: [{time: 1, frames: [{location: 'forceGC'}]}]}]};
|
||||
var perfEvents = convertPerfProfileToEvents(profileData);
|
||||
assertEventsEqual(perfEvents, [{ph: 'X', ts: 1, name: 'gc', args: {usedHeapSize: 0}}]);
|
||||
});
|
||||
|
||||
it('should skip unknown events', function() {
|
||||
var profileData = {
|
||||
threads: [{
|
||||
samples: [
|
||||
{time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]},
|
||||
{time: 2, frames: [{location: 'foo'}]}
|
||||
]
|
||||
}]
|
||||
};
|
||||
var perfEvents = convertPerfProfileToEvents(profileData);
|
||||
assertEventsEqual(perfEvents, [{ph: 'X', ts: 1, name: 'script'}]);
|
||||
});
|
||||
});
|
||||
};
|
@ -1,7 +1,15 @@
|
||||
/**
|
||||
* @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 benchpress = require('../../index.js');
|
||||
var runner = new benchpress.Runner([
|
||||
// use protractor as Webdriver client
|
||||
benchpress.SeleniumWebDriverAdapter.PROTRACTOR_BINDINGS,
|
||||
benchpress.SeleniumWebDriverAdapter.PROTRACTOR_PROVIDERS,
|
||||
// use RegressionSlopeValidator to validate samples
|
||||
benchpress.Validator.bindTo(benchpress.RegressionSlopeValidator),
|
||||
// use 10 samples to calculate slope regression
|
||||
@ -21,12 +29,12 @@ describe('deep tree baseline', function() {
|
||||
* Benchpress will log the collected metrics after each sample is collected, and will stop
|
||||
* sampling as soon as the calculated regression slope for last 20 samples is stable.
|
||||
*/
|
||||
runner.sample({
|
||||
id: 'baseline',
|
||||
execute: function() { $('button')
|
||||
.click(); },
|
||||
providers: [benchpress.bind(benchpress.Options.SAMPLE_DESCRIPTION).toValue({depth: 9})]
|
||||
})
|
||||
runner
|
||||
.sample({
|
||||
id: 'baseline',
|
||||
execute: function() { $('button').click(); },
|
||||
providers: [benchpress.bind(benchpress.Options.SAMPLE_DESCRIPTION).toValue({depth: 9})]
|
||||
})
|
||||
.then(done, done.fail);
|
||||
});
|
||||
});
|
@ -1,4 +1,12 @@
|
||||
var assertEventsContainsName = function(events, eventName) {
|
||||
/**
|
||||
* @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 assertEventsContainsName = function(events: any[], eventName: string) {
|
||||
var found = false;
|
||||
for (var i = 0; i < events.length; ++i) {
|
||||
if (events[i].name == eventName) {
|
||||
@ -17,16 +25,17 @@ describe('firefox extension', function() {
|
||||
|
||||
browser.driver.get(TEST_URL);
|
||||
|
||||
browser.executeScript('window.startProfiler()')
|
||||
.then(function() { console.log('started measuring perf'); });
|
||||
browser.executeScript('window.startProfiler()').then(function() {
|
||||
console.log('started measuring perf');
|
||||
});
|
||||
|
||||
browser.executeAsyncScript('setTimeout(arguments[0], 1000);');
|
||||
browser.executeScript('window.forceGC()');
|
||||
|
||||
browser.executeAsyncScript('var cb = arguments[0]; window.getProfile(cb);')
|
||||
.then(function(profile) {
|
||||
.then(function(profile: any) {
|
||||
assertEventsContainsName(profile, 'gc');
|
||||
assertEventsContainsName(profile, 'script');
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
69
modules/@angular/benchpress/test/metric/multi_metric_spec.ts
Normal file
69
modules/@angular/benchpress/test/metric/multi_metric_spec.ts
Normal file
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* @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 {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
import {Metric, MultiMetric, ReflectiveInjector} from '../../index';
|
||||
|
||||
export function main() {
|
||||
function createMetric(ids: any[]) {
|
||||
var m = ReflectiveInjector
|
||||
.resolveAndCreate([
|
||||
ids.map(id => { return {provide: id, useValue: new MockMetric(id)}; }),
|
||||
MultiMetric.provideWith(ids)
|
||||
])
|
||||
.get(MultiMetric);
|
||||
return Promise.resolve(m);
|
||||
}
|
||||
|
||||
describe('multi metric', () => {
|
||||
it('should merge descriptions', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createMetric(['m1', 'm2']).then((m) => {
|
||||
expect(m.describe()).toEqual({'m1': 'describe', 'm2': 'describe'});
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should merge all beginMeasure calls',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createMetric(['m1', 'm2']).then((m) => m.beginMeasure()).then((values) => {
|
||||
expect(values).toEqual(['m1_beginMeasure', 'm2_beginMeasure']);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
[false, true].forEach((restartFlag) => {
|
||||
it(`should merge all endMeasure calls for restart=${restartFlag}`,
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createMetric(['m1', 'm2']).then((m) => m.endMeasure(restartFlag)).then((values) => {
|
||||
expect(values).toEqual(
|
||||
{'m1': {'restart': restartFlag}, 'm2': {'restart': restartFlag}});
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
class MockMetric extends Metric {
|
||||
constructor(private _id: string) { super(); }
|
||||
|
||||
beginMeasure(): Promise<string> { return Promise.resolve(`${this._id}_beginMeasure`); }
|
||||
|
||||
endMeasure(restart: boolean): Promise<{[key: string]: any}> {
|
||||
var result: {[key: string]: any} = {};
|
||||
result[this._id] = {'restart': restart};
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
describe(): {[key: string]: string} {
|
||||
var result: {[key: string]: string} = {};
|
||||
result[this._id] = 'describe';
|
||||
return result;
|
||||
}
|
||||
}
|
701
modules/@angular/benchpress/test/metric/perflog_metric_spec.ts
Normal file
701
modules/@angular/benchpress/test/metric/perflog_metric_spec.ts
Normal file
@ -0,0 +1,701 @@
|
||||
/**
|
||||
* @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 {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {Metric, Options, PerfLogEvent, PerfLogFeatures, PerflogMetric, ReflectiveInjector, WebDriverExtension} from '../../index';
|
||||
import {StringMapWrapper} from '../../src/facade/collection';
|
||||
import {isBlank, isPresent} from '../../src/facade/lang';
|
||||
import {TraceEventFactory} from '../trace_event_factory';
|
||||
|
||||
export function main() {
|
||||
var commandLog: any[];
|
||||
var eventFactory = new TraceEventFactory('timeline', 'pid0');
|
||||
|
||||
function createMetric(
|
||||
perfLogs: PerfLogEvent[], perfLogFeatures: PerfLogFeatures,
|
||||
{microMetrics, forceGc, captureFrames, receivedData, requestCount}: {
|
||||
microMetrics?: {[key: string]: string},
|
||||
forceGc?: boolean,
|
||||
captureFrames?: boolean,
|
||||
receivedData?: boolean,
|
||||
requestCount?: boolean
|
||||
} = {}): Metric {
|
||||
commandLog = [];
|
||||
if (isBlank(perfLogFeatures)) {
|
||||
perfLogFeatures =
|
||||
new PerfLogFeatures({render: true, gc: true, frameCapture: true, userTiming: true});
|
||||
}
|
||||
if (isBlank(microMetrics)) {
|
||||
microMetrics = {};
|
||||
}
|
||||
var providers: Provider[] = [
|
||||
Options.DEFAULT_PROVIDERS, PerflogMetric.PROVIDERS,
|
||||
{provide: Options.MICRO_METRICS, useValue: microMetrics}, {
|
||||
provide: PerflogMetric.SET_TIMEOUT,
|
||||
useValue: (fn: Function, millis: number) => {
|
||||
commandLog.push(['setTimeout', millis]);
|
||||
fn();
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: WebDriverExtension,
|
||||
useValue: new MockDriverExtension(perfLogs, commandLog, perfLogFeatures)
|
||||
}
|
||||
];
|
||||
if (isPresent(forceGc)) {
|
||||
providers.push({provide: Options.FORCE_GC, useValue: forceGc});
|
||||
}
|
||||
if (isPresent(captureFrames)) {
|
||||
providers.push({provide: Options.CAPTURE_FRAMES, useValue: captureFrames});
|
||||
}
|
||||
if (isPresent(receivedData)) {
|
||||
providers.push({provide: Options.RECEIVED_DATA, useValue: receivedData});
|
||||
}
|
||||
if (isPresent(requestCount)) {
|
||||
providers.push({provide: Options.REQUEST_COUNT, useValue: requestCount});
|
||||
}
|
||||
return ReflectiveInjector.resolveAndCreate(providers).get(PerflogMetric);
|
||||
}
|
||||
|
||||
describe('perflog metric', () => {
|
||||
|
||||
function sortedKeys(stringMap: {[key: string]: any}) {
|
||||
var res: string[] = [];
|
||||
StringMapWrapper.forEach(stringMap, (_, key) => { res.push(key); });
|
||||
res.sort();
|
||||
return res;
|
||||
}
|
||||
|
||||
it('should describe itself based on the perfLogFeatrues', () => {
|
||||
expect(sortedKeys(createMetric([[]], new PerfLogFeatures()).describe())).toEqual([
|
||||
'pureScriptTime', 'scriptTime'
|
||||
]);
|
||||
|
||||
expect(
|
||||
sortedKeys(createMetric([[]], new PerfLogFeatures({render: true, gc: false})).describe()))
|
||||
.toEqual(['pureScriptTime', 'renderTime', 'scriptTime']);
|
||||
|
||||
expect(sortedKeys(createMetric([[]], null).describe())).toEqual([
|
||||
'gcAmount', 'gcTime', 'majorGcTime', 'pureScriptTime', 'renderTime', 'scriptTime'
|
||||
]);
|
||||
|
||||
expect(sortedKeys(createMetric([[]], new PerfLogFeatures({render: true, gc: true}), {
|
||||
forceGc: true
|
||||
}).describe()))
|
||||
.toEqual([
|
||||
'forcedGcAmount', 'forcedGcTime', 'gcAmount', 'gcTime', 'majorGcTime', 'pureScriptTime',
|
||||
'renderTime', 'scriptTime'
|
||||
]);
|
||||
|
||||
|
||||
expect(sortedKeys(createMetric([[]], new PerfLogFeatures({userTiming: true}), {
|
||||
receivedData: true,
|
||||
requestCount: true
|
||||
}).describe()))
|
||||
.toEqual(['pureScriptTime', 'receivedData', 'requestCount', 'scriptTime']);
|
||||
});
|
||||
|
||||
it('should describe itself based on micro metrics', () => {
|
||||
var description =
|
||||
createMetric([[]], null, {microMetrics: {'myMicroMetric': 'someDesc'}}).describe();
|
||||
expect(description['myMicroMetric']).toEqual('someDesc');
|
||||
});
|
||||
|
||||
it('should describe itself if frame capture is requested and available', () => {
|
||||
var description = createMetric([[]], new PerfLogFeatures({frameCapture: true}), {
|
||||
captureFrames: true
|
||||
}).describe();
|
||||
expect(description['frameTime.mean']).not.toContain('WARNING');
|
||||
expect(description['frameTime.best']).not.toContain('WARNING');
|
||||
expect(description['frameTime.worst']).not.toContain('WARNING');
|
||||
expect(description['frameTime.smooth']).not.toContain('WARNING');
|
||||
});
|
||||
|
||||
it('should describe itself if frame capture is requested and not available', () => {
|
||||
var description = createMetric([[]], new PerfLogFeatures({frameCapture: false}), {
|
||||
captureFrames: true
|
||||
}).describe();
|
||||
expect(description['frameTime.mean']).toContain('WARNING');
|
||||
expect(description['frameTime.best']).toContain('WARNING');
|
||||
expect(description['frameTime.worst']).toContain('WARNING');
|
||||
expect(description['frameTime.smooth']).toContain('WARNING');
|
||||
});
|
||||
|
||||
describe('beginMeasure', () => {
|
||||
|
||||
it('should not force gc and mark the timeline',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var metric = createMetric([[]], null);
|
||||
metric.beginMeasure().then((_) => {
|
||||
expect(commandLog).toEqual([['timeBegin', 'benchpress0']]);
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should force gc and mark the timeline',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var metric = createMetric([[]], null, {forceGc: true});
|
||||
metric.beginMeasure().then((_) => {
|
||||
expect(commandLog).toEqual([['gc'], ['timeBegin', 'benchpress0']]);
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('endMeasure', () => {
|
||||
|
||||
it('should mark and aggregate events in between the marks',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var events = [[
|
||||
eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 4),
|
||||
eventFactory.end('script', 6), eventFactory.markEnd('benchpress0', 10)
|
||||
]];
|
||||
var metric = createMetric(events, null);
|
||||
metric.beginMeasure().then((_) => metric.endMeasure(false)).then((data) => {
|
||||
expect(commandLog).toEqual([
|
||||
['timeBegin', 'benchpress0'], ['timeEnd', 'benchpress0', null], 'readPerfLog'
|
||||
]);
|
||||
expect(data['scriptTime']).toBe(2);
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
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) => {
|
||||
var events = [
|
||||
[
|
||||
eventFactory.markStart('benchpress0', 0),
|
||||
eventFactory.markEnd('benchpress0', 1),
|
||||
eventFactory.markStart('benchpress1', 2),
|
||||
],
|
||||
[eventFactory.markEnd('benchpress1', 3)]
|
||||
];
|
||||
var metric = createMetric(events, null);
|
||||
metric.beginMeasure()
|
||||
.then((_) => metric.endMeasure(true))
|
||||
.then((_) => metric.endMeasure(true))
|
||||
.then((_) => {
|
||||
expect(commandLog).toEqual([
|
||||
['timeBegin', 'benchpress0'], ['timeEnd', 'benchpress0', 'benchpress1'],
|
||||
'readPerfLog', ['timeEnd', 'benchpress1', 'benchpress2'], 'readPerfLog'
|
||||
]);
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should loop and aggregate until the end mark is present',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var events = [
|
||||
[eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 1)],
|
||||
[eventFactory.end('script', 2)],
|
||||
[
|
||||
eventFactory.start('script', 3), eventFactory.end('script', 5),
|
||||
eventFactory.markEnd('benchpress0', 10)
|
||||
]
|
||||
];
|
||||
var metric = createMetric(events, null);
|
||||
metric.beginMeasure().then((_) => metric.endMeasure(false)).then((data) => {
|
||||
expect(commandLog).toEqual([
|
||||
['timeBegin', 'benchpress0'], ['timeEnd', 'benchpress0', null], 'readPerfLog',
|
||||
['setTimeout', 100], 'readPerfLog', ['setTimeout', 100], 'readPerfLog'
|
||||
]);
|
||||
expect(data['scriptTime']).toBe(3);
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should store events after the end mark for the next call',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var events = [
|
||||
[
|
||||
eventFactory.markStart('benchpress0', 0), eventFactory.markEnd('benchpress0', 1),
|
||||
eventFactory.markStart('benchpress1', 1), eventFactory.start('script', 1),
|
||||
eventFactory.end('script', 2)
|
||||
],
|
||||
[
|
||||
eventFactory.start('script', 3), eventFactory.end('script', 5),
|
||||
eventFactory.markEnd('benchpress1', 6)
|
||||
]
|
||||
];
|
||||
var metric = createMetric(events, null);
|
||||
metric.beginMeasure()
|
||||
.then((_) => metric.endMeasure(true))
|
||||
.then((data) => {
|
||||
expect(data['scriptTime']).toBe(0);
|
||||
return metric.endMeasure(true);
|
||||
})
|
||||
.then((data) => {
|
||||
expect(commandLog).toEqual([
|
||||
['timeBegin', 'benchpress0'], ['timeEnd', 'benchpress0', 'benchpress1'],
|
||||
'readPerfLog', ['timeEnd', 'benchpress1', 'benchpress2'], 'readPerfLog'
|
||||
]);
|
||||
expect(data['scriptTime']).toBe(3);
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
describe('with forced gc', () => {
|
||||
var events: PerfLogEvent[][];
|
||||
beforeEach(() => {
|
||||
events = [[
|
||||
eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 4),
|
||||
eventFactory.end('script', 6), eventFactory.markEnd('benchpress0', 10),
|
||||
eventFactory.markStart('benchpress1', 11),
|
||||
eventFactory.start('gc', 12, {'usedHeapSize': 2500}),
|
||||
eventFactory.end('gc', 15, {'usedHeapSize': 1000}),
|
||||
eventFactory.markEnd('benchpress1', 20)
|
||||
]];
|
||||
});
|
||||
|
||||
it('should measure forced gc', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var metric = createMetric(events, null, {forceGc: true});
|
||||
metric.beginMeasure().then((_) => metric.endMeasure(false)).then((data) => {
|
||||
expect(commandLog).toEqual([
|
||||
['gc'], ['timeBegin', 'benchpress0'], ['timeEnd', 'benchpress0', 'benchpress1'],
|
||||
'readPerfLog', ['gc'], ['timeEnd', 'benchpress1', null], 'readPerfLog'
|
||||
]);
|
||||
expect(data['forcedGcTime']).toBe(3);
|
||||
expect(data['forcedGcAmount']).toBe(1.5);
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should restart after the forced gc if needed',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var metric = createMetric(events, null, {forceGc: true});
|
||||
metric.beginMeasure().then((_) => metric.endMeasure(true)).then((data) => {
|
||||
expect(commandLog[5]).toEqual(['timeEnd', 'benchpress1', 'benchpress2']);
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('aggregation', () => {
|
||||
|
||||
function aggregate(events: any[], {microMetrics, captureFrames, receivedData, requestCount}: {
|
||||
microMetrics?: {[key: string]: string},
|
||||
captureFrames?: boolean,
|
||||
receivedData?: boolean,
|
||||
requestCount?: boolean
|
||||
} = {}) {
|
||||
events.unshift(eventFactory.markStart('benchpress0', 0));
|
||||
events.push(eventFactory.markEnd('benchpress0', 10));
|
||||
var metric = createMetric([events], null, {
|
||||
microMetrics: microMetrics,
|
||||
captureFrames: captureFrames,
|
||||
receivedData: receivedData,
|
||||
requestCount: requestCount
|
||||
});
|
||||
return metric.beginMeasure().then((_) => metric.endMeasure(false));
|
||||
}
|
||||
|
||||
describe('frame metrics', () => {
|
||||
it('should calculate mean frame time',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
aggregate(
|
||||
[
|
||||
eventFactory.markStart('frameCapture', 0), eventFactory.instant('frame', 1),
|
||||
eventFactory.instant('frame', 3), eventFactory.instant('frame', 4),
|
||||
eventFactory.markEnd('frameCapture', 5)
|
||||
],
|
||||
{captureFrames: true})
|
||||
.then((data) => {
|
||||
expect(data['frameTime.mean']).toBe(((3 - 1) + (4 - 3)) / 2);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should throw if no start event',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
|
||||
aggregate(
|
||||
[eventFactory.instant('frame', 4), eventFactory.markEnd('frameCapture', 5)],
|
||||
{captureFrames: true})
|
||||
.catch((err): any => {
|
||||
expect(() => {
|
||||
throw err;
|
||||
}).toThrowError('missing start event for frame capture');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should throw if no end event',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
|
||||
aggregate(
|
||||
[eventFactory.markStart('frameCapture', 3), eventFactory.instant('frame', 4)],
|
||||
{captureFrames: true})
|
||||
.catch((err): any => {
|
||||
expect(() => { throw err; }).toThrowError('missing end event for frame capture');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should throw if trying to capture twice',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
|
||||
aggregate(
|
||||
[
|
||||
eventFactory.markStart('frameCapture', 3),
|
||||
eventFactory.markStart('frameCapture', 4)
|
||||
],
|
||||
{captureFrames: true})
|
||||
.catch((err): any => {
|
||||
expect(() => {
|
||||
throw err;
|
||||
}).toThrowError('can capture frames only once per benchmark run');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should throw if trying to capture when frame capture is disabled',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
aggregate([eventFactory.markStart('frameCapture', 3)]).catch((err) => {
|
||||
expect(() => { throw err; })
|
||||
.toThrowError(
|
||||
'found start event for frame capture, but frame capture was not requested in benchpress');
|
||||
async.done();
|
||||
return null;
|
||||
});
|
||||
}));
|
||||
|
||||
it('should throw if frame capture is enabled, but nothing is captured',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
aggregate([], {captureFrames: true}).catch((err): any => {
|
||||
expect(() => { throw err; })
|
||||
.toThrowError(
|
||||
'frame capture requested in benchpress, but no start event was found');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should calculate best and worst frame time',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
aggregate(
|
||||
[
|
||||
eventFactory.markStart('frameCapture', 0), eventFactory.instant('frame', 1),
|
||||
eventFactory.instant('frame', 9), eventFactory.instant('frame', 15),
|
||||
eventFactory.instant('frame', 18), eventFactory.instant('frame', 28),
|
||||
eventFactory.instant('frame', 32), eventFactory.markEnd('frameCapture', 10)
|
||||
],
|
||||
{captureFrames: true})
|
||||
.then((data) => {
|
||||
expect(data['frameTime.worst']).toBe(10);
|
||||
expect(data['frameTime.best']).toBe(3);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should calculate percentage of smoothness to be good',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
aggregate(
|
||||
[
|
||||
eventFactory.markStart('frameCapture', 0), eventFactory.instant('frame', 1),
|
||||
eventFactory.instant('frame', 2), eventFactory.instant('frame', 3),
|
||||
eventFactory.markEnd('frameCapture', 4)
|
||||
],
|
||||
{captureFrames: true})
|
||||
.then((data) => {
|
||||
expect(data['frameTime.smooth']).toBe(1.0);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should calculate percentage of smoothness to be bad',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
aggregate(
|
||||
[
|
||||
eventFactory.markStart('frameCapture', 0), eventFactory.instant('frame', 1),
|
||||
eventFactory.instant('frame', 2), eventFactory.instant('frame', 22),
|
||||
eventFactory.instant('frame', 23), eventFactory.instant('frame', 24),
|
||||
eventFactory.markEnd('frameCapture', 4)
|
||||
],
|
||||
{captureFrames: true})
|
||||
.then((data) => {
|
||||
expect(data['frameTime.smooth']).toBe(0.75);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
it('should report a single interval',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
aggregate([
|
||||
eventFactory.start('script', 0), eventFactory.end('script', 5)
|
||||
]).then((data) => {
|
||||
expect(data['scriptTime']).toBe(5);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should sum up multiple intervals',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
aggregate([
|
||||
eventFactory.start('script', 0), eventFactory.end('script', 5),
|
||||
eventFactory.start('script', 10), eventFactory.end('script', 17)
|
||||
]).then((data) => {
|
||||
expect(data['scriptTime']).toBe(12);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should ignore not started intervals',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
aggregate([eventFactory.end('script', 10)]).then((data) => {
|
||||
expect(data['scriptTime']).toBe(0);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should ignore not ended intervals',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
aggregate([eventFactory.start('script', 10)]).then((data) => {
|
||||
expect(data['scriptTime']).toBe(0);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should ignore nested intervals',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
aggregate([
|
||||
eventFactory.start('script', 0), eventFactory.start('script', 5),
|
||||
eventFactory.end('script', 10), eventFactory.end('script', 17)
|
||||
]).then((data) => {
|
||||
expect(data['scriptTime']).toBe(17);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should ignore events from different processed as the start mark',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var otherProcessEventFactory = new TraceEventFactory('timeline', 'pid1');
|
||||
var metric = createMetric(
|
||||
[[
|
||||
eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 0, null),
|
||||
eventFactory.end('script', 5, null),
|
||||
otherProcessEventFactory.start('script', 10, null),
|
||||
otherProcessEventFactory.end('script', 17, null),
|
||||
eventFactory.markEnd('benchpress0', 20)
|
||||
]],
|
||||
null);
|
||||
metric.beginMeasure().then((_) => metric.endMeasure(false)).then((data) => {
|
||||
expect(data['scriptTime']).toBe(5);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should support scriptTime metric',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
aggregate([
|
||||
eventFactory.start('script', 0), eventFactory.end('script', 5)
|
||||
]).then((data) => {
|
||||
expect(data['scriptTime']).toBe(5);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should support renderTime metric',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
aggregate([
|
||||
eventFactory.start('render', 0), eventFactory.end('render', 5)
|
||||
]).then((data) => {
|
||||
expect(data['renderTime']).toBe(5);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should support gcTime/gcAmount metric',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
aggregate([
|
||||
eventFactory.start('gc', 0, {'usedHeapSize': 2500}),
|
||||
eventFactory.end('gc', 5, {'usedHeapSize': 1000})
|
||||
]).then((data) => {
|
||||
expect(data['gcTime']).toBe(5);
|
||||
expect(data['gcAmount']).toBe(1.5);
|
||||
expect(data['majorGcTime']).toBe(0);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should support majorGcTime metric',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
aggregate([
|
||||
eventFactory.start('gc', 0, {'usedHeapSize': 2500}),
|
||||
eventFactory.end('gc', 5, {'usedHeapSize': 1000, 'majorGc': true})
|
||||
]).then((data) => {
|
||||
expect(data['gcTime']).toBe(5);
|
||||
expect(data['majorGcTime']).toBe(5);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should support pureScriptTime = scriptTime-gcTime-renderTime',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
aggregate([
|
||||
eventFactory.start('script', 0), eventFactory.start('gc', 1, {'usedHeapSize': 1000}),
|
||||
eventFactory.end('gc', 4, {'usedHeapSize': 0}), eventFactory.start('render', 4),
|
||||
eventFactory.end('render', 5), eventFactory.end('script', 6)
|
||||
]).then((data) => {
|
||||
expect(data['scriptTime']).toBe(6);
|
||||
expect(data['pureScriptTime']).toBe(2);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
describe('receivedData', () => {
|
||||
it('should report received data since last navigationStart',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
aggregate(
|
||||
[
|
||||
eventFactory.instant('receivedData', 0, {'encodedDataLength': 1}),
|
||||
eventFactory.instant('navigationStart', 1),
|
||||
eventFactory.instant('receivedData', 2, {'encodedDataLength': 2}),
|
||||
eventFactory.instant('navigationStart', 3),
|
||||
eventFactory.instant('receivedData', 4, {'encodedDataLength': 4}),
|
||||
eventFactory.instant('receivedData', 5, {'encodedDataLength': 8})
|
||||
],
|
||||
{receivedData: true})
|
||||
.then((data) => {
|
||||
expect(data['receivedData']).toBe(12);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('requestCount', () => {
|
||||
it('should report count of requests sent since last navigationStart',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
aggregate(
|
||||
[
|
||||
eventFactory.instant('sendRequest', 0),
|
||||
eventFactory.instant('navigationStart', 1),
|
||||
eventFactory.instant('sendRequest', 2),
|
||||
eventFactory.instant('navigationStart', 3),
|
||||
eventFactory.instant('sendRequest', 4), eventFactory.instant('sendRequest', 5)
|
||||
],
|
||||
{requestCount: true})
|
||||
.then((data) => {
|
||||
expect(data['requestCount']).toBe(2);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('microMetrics', () => {
|
||||
|
||||
it('should report micro metrics',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
aggregate(
|
||||
[
|
||||
eventFactory.markStart('mm1', 0),
|
||||
eventFactory.markEnd('mm1', 5),
|
||||
],
|
||||
{microMetrics: {'mm1': 'micro metric 1'}})
|
||||
.then((data) => {
|
||||
expect(data['mm1']).toBe(5.0);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should ignore micro metrics that were not specified',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
aggregate([
|
||||
eventFactory.markStart('mm1', 0),
|
||||
eventFactory.markEnd('mm1', 5),
|
||||
]).then((data) => {
|
||||
expect(data['mm1']).toBeFalsy();
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should report micro metric averages',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
aggregate(
|
||||
[
|
||||
eventFactory.markStart('mm1*20', 0),
|
||||
eventFactory.markEnd('mm1*20', 5),
|
||||
],
|
||||
{microMetrics: {'mm1': 'micro metric 1'}})
|
||||
.then((data) => {
|
||||
expect(data['mm1']).toBe(5 / 20);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
class MockDriverExtension extends WebDriverExtension {
|
||||
constructor(
|
||||
private _perfLogs: any[], private _commandLog: any[],
|
||||
private _perfLogFeatures: PerfLogFeatures) {
|
||||
super();
|
||||
}
|
||||
|
||||
timeBegin(name: string): Promise<any> {
|
||||
this._commandLog.push(['timeBegin', name]);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
timeEnd(name: string, restartName: string): Promise<any> {
|
||||
this._commandLog.push(['timeEnd', name, restartName]);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
perfLogFeatures(): PerfLogFeatures { return this._perfLogFeatures; }
|
||||
|
||||
readPerfLog(): Promise<any> {
|
||||
this._commandLog.push('readPerfLog');
|
||||
if (this._perfLogs.length > 0) {
|
||||
var next = this._perfLogs[0];
|
||||
this._perfLogs.shift();
|
||||
return Promise.resolve(next);
|
||||
} else {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
}
|
||||
|
||||
gc(): Promise<any> {
|
||||
this._commandLog.push(['gc']);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
84
modules/@angular/benchpress/test/metric/user_metric_spec.ts
Normal file
84
modules/@angular/benchpress/test/metric/user_metric_spec.ts
Normal file
@ -0,0 +1,84 @@
|
||||
/**
|
||||
* @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, ReflectiveInjector} from '@angular/core';
|
||||
import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {Injector, Metric, MultiMetric, Options, PerfLogEvent, PerfLogFeatures, PerflogMetric, UserMetric, WebDriverAdapter, WebDriverExtension} from '../../index';
|
||||
import {StringMapWrapper} from '../../src/facade/collection';
|
||||
import {Json, isBlank, isPresent} from '../../src/facade/lang';
|
||||
|
||||
export function main() {
|
||||
var wdAdapter: MockDriverAdapter;
|
||||
|
||||
function createMetric(
|
||||
perfLogs: PerfLogEvent[], perfLogFeatures: PerfLogFeatures,
|
||||
{userMetrics}: {userMetrics?: {[key: string]: string}} = {}): UserMetric {
|
||||
if (isBlank(perfLogFeatures)) {
|
||||
perfLogFeatures =
|
||||
new PerfLogFeatures({render: true, gc: true, frameCapture: true, userTiming: true});
|
||||
}
|
||||
if (isBlank(userMetrics)) {
|
||||
userMetrics = {};
|
||||
}
|
||||
wdAdapter = new MockDriverAdapter();
|
||||
var providers: Provider[] = [
|
||||
Options.DEFAULT_PROVIDERS, UserMetric.PROVIDERS,
|
||||
{provide: Options.USER_METRICS, useValue: userMetrics},
|
||||
{provide: WebDriverAdapter, useValue: wdAdapter}
|
||||
];
|
||||
return ReflectiveInjector.resolveAndCreate(providers).get(UserMetric);
|
||||
}
|
||||
|
||||
describe('user metric', () => {
|
||||
|
||||
it('should describe itself based on userMetrics', () => {
|
||||
expect(createMetric([[]], new PerfLogFeatures(), {
|
||||
userMetrics: {'loadTime': 'time to load'}
|
||||
}).describe())
|
||||
.toEqual({'loadTime': 'time to load'});
|
||||
});
|
||||
|
||||
describe('endMeasure', () => {
|
||||
it('should stop measuring when all properties have numeric values',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
let metric = createMetric(
|
||||
[[]], new PerfLogFeatures(),
|
||||
{userMetrics: {'loadTime': 'time to load', 'content': 'time to see content'}});
|
||||
metric.beginMeasure()
|
||||
.then((_) => metric.endMeasure(true))
|
||||
.then((values: {[key: string]: string}) => {
|
||||
expect(values['loadTime']).toBe(25);
|
||||
expect(values['content']).toBe(250);
|
||||
async.done();
|
||||
});
|
||||
|
||||
wdAdapter.data['loadTime'] = 25;
|
||||
// Wait before setting 2nd property.
|
||||
setTimeout(() => { wdAdapter.data['content'] = 250; }, 50);
|
||||
|
||||
}), 600);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class MockDriverAdapter extends WebDriverAdapter {
|
||||
data: any = {};
|
||||
|
||||
executeScript(script: string): any {
|
||||
// Just handles `return window.propName` ignores `delete window.propName`.
|
||||
if (script.indexOf('return window.') == 0) {
|
||||
let metricName = script.substring('return window.'.length);
|
||||
return Promise.resolve(this.data[metricName]);
|
||||
} else if (script.indexOf('delete window.') == 0) {
|
||||
return Promise.resolve(null);
|
||||
} else {
|
||||
return Promise.reject(`Unexpected syntax: ${script}`);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,32 +1,29 @@
|
||||
import {
|
||||
describe,
|
||||
ddescribe,
|
||||
it,
|
||||
iit,
|
||||
xit,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach
|
||||
} from '@angular/testing/testing_internal';
|
||||
/**
|
||||
* @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 {isBlank, isPresent, Date, DateWrapper} from '@angular/facade';
|
||||
import {Provider} from '@angular/core';
|
||||
import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {
|
||||
SampleState,
|
||||
Reporter,
|
||||
ReflectiveInjector,
|
||||
ConsoleReporter,
|
||||
SampleDescription,
|
||||
MeasureValues
|
||||
} from 'benchpress/common';
|
||||
import {ConsoleReporter, MeasureValues, ReflectiveInjector, Reporter, SampleDescription, SampleState} from '../../index';
|
||||
import {Date, DateWrapper, isBlank, isPresent} from '../../src/facade/lang';
|
||||
|
||||
export function main() {
|
||||
describe('console reporter', () => {
|
||||
var reporter;
|
||||
var reporter: ConsoleReporter;
|
||||
var log: string[];
|
||||
|
||||
function createReporter({columnWidth = null, sampleId = null, descriptions = null,
|
||||
metrics = null}: {columnWidth?, sampleId?, descriptions?, metrics?}) {
|
||||
function createReporter(
|
||||
{columnWidth = null, sampleId = null, descriptions = null, metrics = null}: {
|
||||
columnWidth?: number,
|
||||
sampleId?: string,
|
||||
descriptions?: {[key: string]: any}[],
|
||||
metrics?: {[key: string]: any}
|
||||
}) {
|
||||
log = [];
|
||||
if (isBlank(descriptions)) {
|
||||
descriptions = [];
|
||||
@ -34,15 +31,17 @@ export function main() {
|
||||
if (isBlank(sampleId)) {
|
||||
sampleId = 'null';
|
||||
}
|
||||
var bindings = [
|
||||
ConsoleReporter.PROVIDERS,
|
||||
{provide: SampleDescription, useValue: new SampleDescription(sampleId, descriptions, metrics)},
|
||||
{provide: ConsoleReporter.PRINT, useValue: (line) => log.push(line)}
|
||||
var providers: Provider[] = [
|
||||
ConsoleReporter.PROVIDERS, {
|
||||
provide: SampleDescription,
|
||||
useValue: new SampleDescription(sampleId, descriptions, metrics)
|
||||
},
|
||||
{provide: ConsoleReporter.PRINT, useValue: (line: string) => log.push(line)}
|
||||
];
|
||||
if (isPresent(columnWidth)) {
|
||||
bindings.push({provide: ConsoleReporter.COLUMN_WIDTH, useValue(columnWidth)};
|
||||
providers.push({provide: ConsoleReporter.COLUMN_WIDTH, useValue: columnWidth});
|
||||
}
|
||||
reporter = ReflectiveInjector.resolveAndCreate(bindings).get(ConsoleReporter);
|
||||
reporter = ReflectiveInjector.resolveAndCreate(providers).get(ConsoleReporter);
|
||||
}
|
||||
|
||||
it('should print the sample id, description and table header', () => {
|
||||
@ -90,6 +89,6 @@ export function main() {
|
||||
});
|
||||
}
|
||||
|
||||
function mv(runIndex, time, values) {
|
||||
function mv(runIndex: number, time: number, values: {[key: string]: number}) {
|
||||
return new MeasureValues(runIndex, DateWrapper.fromMillis(time), values);
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/**
|
||||
* @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 {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {JsonFileReporter, MeasureValues, Options, ReflectiveInjector, SampleDescription} from '../../index';
|
||||
import {DateWrapper, Json, isPresent} from '../../src/facade/lang';
|
||||
|
||||
export function main() {
|
||||
describe('file reporter', () => {
|
||||
var loggedFile: any;
|
||||
|
||||
function createReporter({sampleId, descriptions, metrics, path}: {
|
||||
sampleId: string,
|
||||
descriptions: {[key: string]: any}[],
|
||||
metrics: {[key: string]: string},
|
||||
path: string
|
||||
}) {
|
||||
var providers = [
|
||||
JsonFileReporter.PROVIDERS, {
|
||||
provide: SampleDescription,
|
||||
useValue: new SampleDescription(sampleId, descriptions, metrics)
|
||||
},
|
||||
{provide: JsonFileReporter.PATH, useValue: path},
|
||||
{provide: Options.NOW, useValue: () => DateWrapper.fromMillis(1234)}, {
|
||||
provide: Options.WRITE_FILE,
|
||||
useValue: (filename: string, content: string) => {
|
||||
loggedFile = {'filename': filename, 'content': content};
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
];
|
||||
return ReflectiveInjector.resolveAndCreate(providers).get(JsonFileReporter);
|
||||
}
|
||||
|
||||
it('should write all data into a file',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createReporter({
|
||||
sampleId: 'someId',
|
||||
descriptions: [{'a': 2}],
|
||||
path: 'somePath',
|
||||
metrics: {'a': 'script time', 'b': 'render time'}
|
||||
})
|
||||
.reportSample(
|
||||
[mv(0, 0, {'a': 3, 'b': 6})],
|
||||
[mv(0, 0, {'a': 3, 'b': 6}), mv(1, 1, {'a': 5, 'b': 9})]);
|
||||
var regExp = /somePath\/someId_\d+\.json/;
|
||||
expect(isPresent(loggedFile['filename'].match(regExp))).toBe(true);
|
||||
var parsedContent = Json.parse(loggedFile['content']);
|
||||
expect(parsedContent).toEqual({
|
||||
'description': {
|
||||
'id': 'someId',
|
||||
'description': {'a': 2},
|
||||
'metrics': {'a': 'script time', 'b': 'render time'}
|
||||
},
|
||||
'stats': {'a': '4.00+-25%', 'b': '7.50+-20%'},
|
||||
'completeSample': [
|
||||
{'timeStamp': '1970-01-01T00:00:00.000Z', 'runIndex': 0, 'values': {'a': 3, 'b': 6}}
|
||||
],
|
||||
'validSample': [
|
||||
{'timeStamp': '1970-01-01T00:00:00.000Z', 'runIndex': 0, 'values': {'a': 3, 'b': 6}}, {
|
||||
'timeStamp': '1970-01-01T00:00:00.001Z',
|
||||
'runIndex': 1,
|
||||
'values': {'a': 5, 'b': 9}
|
||||
}
|
||||
]
|
||||
});
|
||||
async.done();
|
||||
}));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function mv(runIndex: number, time: number, values: {[key: string]: number}) {
|
||||
return new MeasureValues(runIndex, DateWrapper.fromMillis(time), values);
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* @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 {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {MeasureValues, MultiReporter, ReflectiveInjector, Reporter} from '../../index';
|
||||
import {DateWrapper} from '../../src/facade/lang';
|
||||
|
||||
export function main() {
|
||||
function createReporters(ids: any[]) {
|
||||
var r = ReflectiveInjector
|
||||
.resolveAndCreate([
|
||||
ids.map(id => { return {provide: id, useValue: new MockReporter(id)}; }),
|
||||
MultiReporter.provideWith(ids)
|
||||
])
|
||||
.get(MultiReporter);
|
||||
return Promise.resolve(r);
|
||||
}
|
||||
|
||||
describe('multi reporter', () => {
|
||||
|
||||
it('should reportMeasureValues to all',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var mv = new MeasureValues(0, DateWrapper.now(), {});
|
||||
createReporters(['m1', 'm2']).then((r) => r.reportMeasureValues(mv)).then((values) => {
|
||||
|
||||
expect(values).toEqual([{'id': 'm1', 'values': mv}, {'id': 'm2', 'values': mv}]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should reportSample to call', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var completeSample = [
|
||||
new MeasureValues(0, DateWrapper.now(), {}), new MeasureValues(1, DateWrapper.now(), {})
|
||||
];
|
||||
var validSample = [completeSample[1]];
|
||||
|
||||
createReporters(['m1', 'm2'])
|
||||
.then((r) => r.reportSample(completeSample, validSample))
|
||||
.then((values) => {
|
||||
|
||||
expect(values).toEqual([
|
||||
{'id': 'm1', 'completeSample': completeSample, 'validSample': validSample},
|
||||
{'id': 'm2', 'completeSample': completeSample, 'validSample': validSample}
|
||||
]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
class MockReporter extends Reporter {
|
||||
constructor(private _id: string) { super(); }
|
||||
|
||||
reportMeasureValues(values: MeasureValues): Promise<{[key: string]: any}> {
|
||||
return Promise.resolve({'id': this._id, 'values': values});
|
||||
}
|
||||
|
||||
reportSample(completeSample: MeasureValues[], validSample: MeasureValues[]):
|
||||
Promise<{[key: string]: any}> {
|
||||
return Promise.resolve(
|
||||
{'id': this._id, 'completeSample': completeSample, 'validSample': validSample});
|
||||
}
|
||||
}
|
141
modules/@angular/benchpress/test/runner_spec.ts
Normal file
141
modules/@angular/benchpress/test/runner_spec.ts
Normal file
@ -0,0 +1,141 @@
|
||||
/**
|
||||
* @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 {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {Injector, Metric, Options, ReflectiveInjector, Runner, SampleDescription, SampleState, Sampler, Validator, WebDriverAdapter} from '../index';
|
||||
import {isBlank} from '../src/facade/lang';
|
||||
|
||||
export function main() {
|
||||
describe('runner', () => {
|
||||
var injector: ReflectiveInjector;
|
||||
var runner: Runner;
|
||||
|
||||
function createRunner(defaultProviders: any[] = null): Runner {
|
||||
if (isBlank(defaultProviders)) {
|
||||
defaultProviders = [];
|
||||
}
|
||||
runner = new Runner([
|
||||
defaultProviders, {
|
||||
provide: Sampler,
|
||||
useFactory: (_injector: ReflectiveInjector) => {
|
||||
injector = _injector;
|
||||
return new MockSampler();
|
||||
},
|
||||
deps: [Injector]
|
||||
},
|
||||
{provide: Metric, useFactory: () => new MockMetric(), deps: []},
|
||||
{provide: Validator, useFactory: () => new MockValidator(), deps: []},
|
||||
{provide: WebDriverAdapter, useFactory: () => new MockWebDriverAdapter(), deps: []}
|
||||
]);
|
||||
return runner;
|
||||
}
|
||||
|
||||
it('should set SampleDescription.id',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createRunner()
|
||||
.sample({id: 'someId'})
|
||||
.then((_) => injector.get(SampleDescription))
|
||||
.then((desc) => {
|
||||
expect(desc.id).toBe('someId');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should merge SampleDescription.description',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createRunner([{provide: Options.DEFAULT_DESCRIPTION, useValue: {'a': 1}}])
|
||||
.sample({
|
||||
id: 'someId',
|
||||
providers: [{provide: Options.SAMPLE_DESCRIPTION, useValue: {'b': 2}}]
|
||||
})
|
||||
.then((_) => injector.get(SampleDescription))
|
||||
.then((desc) => {
|
||||
expect(desc.description)
|
||||
.toEqual(
|
||||
{'forceGc': false, 'userAgent': 'someUserAgent', 'a': 1, 'b': 2, 'v': 11});
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should fill SampleDescription.metrics from the Metric',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createRunner()
|
||||
.sample({id: 'someId'})
|
||||
.then((_) => injector.get(SampleDescription))
|
||||
.then((desc) => {
|
||||
|
||||
expect(desc.metrics).toEqual({'m1': 'some metric'});
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should provide Options.EXECUTE',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var execute = () => {};
|
||||
createRunner().sample({id: 'someId', execute: execute}).then((_) => {
|
||||
expect(injector.get(Options.EXECUTE)).toEqual(execute);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should provide Options.PREPARE',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var prepare = () => {};
|
||||
createRunner().sample({id: 'someId', prepare: prepare}).then((_) => {
|
||||
expect(injector.get(Options.PREPARE)).toEqual(prepare);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should provide Options.MICRO_METRICS',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createRunner().sample({id: 'someId', microMetrics: {'a': 'b'}}).then((_) => {
|
||||
expect(injector.get(Options.MICRO_METRICS)).toEqual({'a': 'b'});
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should overwrite providers per sample call',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createRunner([{provide: Options.DEFAULT_DESCRIPTION, useValue: {'a': 1}}])
|
||||
.sample({
|
||||
id: 'someId',
|
||||
providers: [{provide: Options.DEFAULT_DESCRIPTION, useValue: {'a': 2}}]
|
||||
})
|
||||
.then((_) => injector.get(SampleDescription))
|
||||
.then((desc) => {
|
||||
|
||||
expect(desc.description['a']).toBe(2);
|
||||
async.done();
|
||||
});
|
||||
|
||||
}));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
class MockWebDriverAdapter extends WebDriverAdapter {
|
||||
executeScript(script: string): Promise<string> { return Promise.resolve('someUserAgent'); }
|
||||
capabilities(): Promise<Map<string, any>> { return null; }
|
||||
}
|
||||
|
||||
class MockValidator extends Validator {
|
||||
constructor() { super(); }
|
||||
describe() { return {'v': 11}; }
|
||||
}
|
||||
|
||||
class MockMetric extends Metric {
|
||||
constructor() { super(); }
|
||||
describe() { return {'m1': 'some metric'}; }
|
||||
}
|
||||
|
||||
class MockSampler extends Sampler {
|
||||
constructor() { super(null, null, null, null, null, null, null); }
|
||||
sample(): Promise<SampleState> { return Promise.resolve(new SampleState([], [])); }
|
||||
}
|
@ -1,29 +1,15 @@
|
||||
import {
|
||||
afterEach,
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit,
|
||||
} from '@angular/testing/testing_internal';
|
||||
/**
|
||||
* @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 {isBlank, isPresent, stringify, Date, DateWrapper} from '@angular/facade';
|
||||
import {PromiseWrapper} from '@angular/facade';
|
||||
import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {
|
||||
Sampler,
|
||||
WebDriverAdapter,
|
||||
Validator,
|
||||
Metric,
|
||||
Reporter,
|
||||
ReflectiveInjector,
|
||||
Options,
|
||||
MeasureValues
|
||||
} from 'benchpress/common';
|
||||
import {MeasureValues, Metric, Options, ReflectiveInjector, Reporter, Sampler, Validator, WebDriverAdapter} from '../index';
|
||||
import {Date, DateWrapper, isBlank, isPresent, stringify} from '../src/facade/lang';
|
||||
|
||||
export function main() {
|
||||
var EMPTY_EXECUTE = () => {};
|
||||
@ -50,13 +36,9 @@ export function main() {
|
||||
driver = new MockDriverAdapter([]);
|
||||
}
|
||||
var providers = [
|
||||
Options.DEFAULT_PROVIDERS,
|
||||
Sampler.PROVIDERS,
|
||||
{provide: Metric, useValue: metric},
|
||||
{provide: Reporter, useValue: reporter},
|
||||
{provide: WebDriverAdapter, useValue: driver},
|
||||
{provide: Options.EXECUTE, useValue: execute},
|
||||
{provide: Validator, useValue: validator},
|
||||
Options.DEFAULT_PROVIDERS, Sampler.PROVIDERS, {provide: Metric, useValue: metric},
|
||||
{provide: Reporter, useValue: reporter}, {provide: WebDriverAdapter, useValue: driver},
|
||||
{provide: Options.EXECUTE, useValue: execute}, {provide: Validator, useValue: validator},
|
||||
{provide: Options.NOW, useValue: () => DateWrapper.fromMillis(time++)}
|
||||
];
|
||||
if (isPresent(prepare)) {
|
||||
@ -67,13 +49,13 @@ export function main() {
|
||||
}
|
||||
|
||||
it('should call the prepare and execute callbacks using WebDriverAdapter.waitFor',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
var log = [];
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var log: any[] = [];
|
||||
var count = 0;
|
||||
var driver = new MockDriverAdapter([], (callback) => {
|
||||
var driver = new MockDriverAdapter([], (callback: Function) => {
|
||||
var result = callback();
|
||||
log.push(result);
|
||||
return PromiseWrapper.resolve(result);
|
||||
return Promise.resolve(result);
|
||||
});
|
||||
createSampler({
|
||||
driver: driver,
|
||||
@ -90,9 +72,9 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should call prepare, beginMeasure, execute, endMeasure for every iteration',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var workCount = 0;
|
||||
var log = [];
|
||||
var log: any[] = [];
|
||||
createSampler({
|
||||
metric: createCountingMetric(log),
|
||||
validator: createCountingValidator(2),
|
||||
@ -115,8 +97,8 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should call execute, endMeasure for every iteration if there is no prepare callback',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
var log = [];
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var log: any[] = [];
|
||||
var workCount = 0;
|
||||
createSampler({
|
||||
metric: createCountingMetric(log),
|
||||
@ -137,17 +119,18 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should only collect metrics for execute and ignore metrics from prepare',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var scriptTime = 0;
|
||||
var iterationCount = 1;
|
||||
createSampler({
|
||||
validator: createCountingValidator(2),
|
||||
metric: new MockMetric([],
|
||||
() => {
|
||||
var result = PromiseWrapper.resolve({'script': scriptTime});
|
||||
scriptTime = 0;
|
||||
return result;
|
||||
}),
|
||||
metric: new MockMetric(
|
||||
[],
|
||||
() => {
|
||||
var result = Promise.resolve({'script': scriptTime});
|
||||
scriptTime = 0;
|
||||
return result;
|
||||
}),
|
||||
prepare: () => { scriptTime = 1 * iterationCount; },
|
||||
execute: () => {
|
||||
scriptTime = 10 * iterationCount;
|
||||
@ -163,9 +146,9 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should call the validator for every execution and store the valid sample',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
var log = [];
|
||||
var validSample = [{}];
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var log: any[] = [];
|
||||
var validSample = [mv(null, null, {})];
|
||||
|
||||
createSampler({
|
||||
metric: createCountingMetric(),
|
||||
@ -189,9 +172,10 @@ export function main() {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should report the metric values', inject([AsyncTestCompleter], (async) => {
|
||||
var log = [];
|
||||
var validSample = [{}];
|
||||
it('should report the metric values',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var log: any[] = [];
|
||||
var validSample = [mv(null, null, {})];
|
||||
createSampler({
|
||||
validator: createCountingValidator(2, validSample),
|
||||
metric: createCountingMetric(),
|
||||
@ -209,9 +193,7 @@ export function main() {
|
||||
expect(log[0]).toEqual(['reportMeasureValues', mv(0, 1000, {'script': 0})]);
|
||||
expect(log[1]).toEqual(['reportMeasureValues', mv(1, 1001, {'script': 1})]);
|
||||
expect(log[2]).toEqual([
|
||||
'reportSample',
|
||||
[mv(0, 1000, {'script': 0}), mv(1, 1001, {'script': 1})],
|
||||
validSample
|
||||
'reportSample', [mv(0, 1000, {'script': 0}), mv(1, 1001, {'script': 1})], validSample
|
||||
]);
|
||||
|
||||
async.done();
|
||||
@ -221,12 +203,13 @@ export function main() {
|
||||
});
|
||||
}
|
||||
|
||||
function mv(runIndex, time, values) {
|
||||
function mv(runIndex: number, time: number, values: {[key: string]: number}) {
|
||||
return new MeasureValues(runIndex, DateWrapper.fromMillis(time), values);
|
||||
}
|
||||
|
||||
function createCountingValidator(count, validSample = null, log = null) {
|
||||
return new MockValidator(log, (completeSample) => {
|
||||
function createCountingValidator(
|
||||
count: number, validSample: MeasureValues[] = null, log: any[] = []) {
|
||||
return new MockValidator(log, (completeSample: MeasureValues[]) => {
|
||||
count--;
|
||||
if (count === 0) {
|
||||
return isPresent(validSample) ? validSample : completeSample;
|
||||
@ -236,41 +219,25 @@ function createCountingValidator(count, validSample = null, log = null) {
|
||||
});
|
||||
}
|
||||
|
||||
function createCountingMetric(log = null) {
|
||||
function createCountingMetric(log: any[] = []) {
|
||||
var scriptTime = 0;
|
||||
return new MockMetric(log, () => { return {'script': scriptTime++}; });
|
||||
}
|
||||
|
||||
class MockDriverAdapter extends WebDriverAdapter {
|
||||
private _log: any[];
|
||||
private _waitFor: Function;
|
||||
constructor(log = null, waitFor = null) {
|
||||
super();
|
||||
if (isBlank(log)) {
|
||||
log = [];
|
||||
}
|
||||
this._log = log;
|
||||
this._waitFor = waitFor;
|
||||
}
|
||||
constructor(private _log: any[] = [], private _waitFor: Function = null) { super(); }
|
||||
waitFor(callback: Function): Promise<any> {
|
||||
if (isPresent(this._waitFor)) {
|
||||
return this._waitFor(callback);
|
||||
} else {
|
||||
return PromiseWrapper.resolve(callback());
|
||||
return Promise.resolve(callback());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MockValidator extends Validator {
|
||||
private _log: any[];
|
||||
constructor(log = null, private _validate: Function = null) {
|
||||
super();
|
||||
if (isBlank(log)) {
|
||||
log = [];
|
||||
}
|
||||
this._log = log;
|
||||
}
|
||||
constructor(private _log: any[] = [], private _validate: Function = null) { super(); }
|
||||
validate(completeSample: MeasureValues[]): MeasureValues[] {
|
||||
var stableSample = isPresent(this._validate) ? this._validate(completeSample) : completeSample;
|
||||
this._log.push(['validate', completeSample, stableSample]);
|
||||
@ -279,40 +246,26 @@ class MockValidator extends Validator {
|
||||
}
|
||||
|
||||
class MockMetric extends Metric {
|
||||
private _log: any[];
|
||||
constructor(log = null, private _endMeasure: Function = null) {
|
||||
super();
|
||||
if (isBlank(log)) {
|
||||
log = [];
|
||||
}
|
||||
this._log = log;
|
||||
}
|
||||
constructor(private _log: any[] = [], private _endMeasure: Function = null) { super(); }
|
||||
beginMeasure() {
|
||||
this._log.push(['beginMeasure']);
|
||||
return PromiseWrapper.resolve(null);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
endMeasure(restart) {
|
||||
endMeasure(restart: boolean) {
|
||||
var measureValues = isPresent(this._endMeasure) ? this._endMeasure() : {};
|
||||
this._log.push(['endMeasure', restart, measureValues]);
|
||||
return PromiseWrapper.resolve(measureValues);
|
||||
return Promise.resolve(measureValues);
|
||||
}
|
||||
}
|
||||
|
||||
class MockReporter extends Reporter {
|
||||
_log: any[];
|
||||
constructor(log = null) {
|
||||
super();
|
||||
if (isBlank(log)) {
|
||||
log = [];
|
||||
}
|
||||
this._log = log;
|
||||
}
|
||||
reportMeasureValues(values): Promise<any> {
|
||||
constructor(private _log: any[] = []) { super(); }
|
||||
reportMeasureValues(values: MeasureValues): Promise<any> {
|
||||
this._log.push(['reportMeasureValues', values]);
|
||||
return PromiseWrapper.resolve(null);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
reportSample(completeSample, validSample): Promise<any> {
|
||||
reportSample(completeSample: MeasureValues[], validSample: MeasureValues[]): Promise<any> {
|
||||
this._log.push(['reportSample', completeSample, validSample]);
|
||||
return PromiseWrapper.resolve(null);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
@ -1,17 +1,13 @@
|
||||
import {
|
||||
describe,
|
||||
ddescribe,
|
||||
it,
|
||||
iit,
|
||||
xit,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach
|
||||
} from '@angular/testing/testing_internal';
|
||||
/**
|
||||
* @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 {Statistic} from 'benchpress/src/statistic';
|
||||
|
||||
import {NaN} from '@angular/facade';
|
||||
import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
import {Statistic} from '../src/statistic';
|
||||
|
||||
export function main() {
|
||||
describe('statistic', () => {
|
41
modules/@angular/benchpress/test/trace_event_factory.ts
Normal file
41
modules/@angular/benchpress/test/trace_event_factory.ts
Normal file
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @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 {PerfLogEvent} from '../index';
|
||||
import {isPresent} from '../src/facade/lang';
|
||||
|
||||
export class TraceEventFactory {
|
||||
constructor(private _cat: string, private _pid: string) {}
|
||||
|
||||
create(ph: any, name: string, time: number, args: any = null) {
|
||||
var res:
|
||||
PerfLogEvent = {'name': name, 'cat': this._cat, 'ph': ph, 'ts': time, 'pid': this._pid};
|
||||
if (isPresent(args)) {
|
||||
res['args'] = args;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
markStart(name: string, time: number) { return this.create('B', 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); }
|
||||
|
||||
end(name: string, time: number, args: any = null) { return this.create('E', name, time, args); }
|
||||
|
||||
instant(name: string, time: number, args: any = null) {
|
||||
return this.create('I', name, time, args);
|
||||
}
|
||||
|
||||
complete(name: string, time: number, duration: number, args: any = null) {
|
||||
var res = this.create('X', name, time, args);
|
||||
res['dur'] = duration;
|
||||
return res;
|
||||
}
|
||||
}
|
@ -1,32 +1,28 @@
|
||||
import {
|
||||
describe,
|
||||
ddescribe,
|
||||
it,
|
||||
iit,
|
||||
xit,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach
|
||||
} from '@angular/testing/testing_internal';
|
||||
import {Date, DateWrapper} from '@angular/facade';
|
||||
import {ListWrapper} from '@angular/facade';
|
||||
/**
|
||||
* @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 {
|
||||
RegressionSlopeValidator,
|
||||
ReflectiveInjector,
|
||||
MeasureValues
|
||||
} from 'benchpress/common';
|
||||
import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {MeasureValues, ReflectiveInjector, RegressionSlopeValidator} from '../../index';
|
||||
import {ListWrapper} from '../../src/facade/collection';
|
||||
import {Date, DateWrapper} from '../../src/facade/lang';
|
||||
|
||||
export function main() {
|
||||
describe('regression slope validator', () => {
|
||||
var validator;
|
||||
var validator: RegressionSlopeValidator;
|
||||
|
||||
function createValidator({size, metric}) {
|
||||
validator = ReflectiveInjector.resolveAndCreate([
|
||||
RegressionSlopeValidator.PROVIDERS,
|
||||
{provide: RegressionSlopeValidator.METRIC, useValue(metric)},
|
||||
{provide: RegressionSlopeValidator.SAMPLE_SIZE, useValue(size)}
|
||||
])
|
||||
function createValidator({size, metric}: {size: number, metric: string}) {
|
||||
validator = ReflectiveInjector
|
||||
.resolveAndCreate([
|
||||
RegressionSlopeValidator.PROVIDERS,
|
||||
{provide: RegressionSlopeValidator.METRIC, useValue: metric},
|
||||
{provide: RegressionSlopeValidator.SAMPLE_SIZE, useValue: size}
|
||||
])
|
||||
.get(RegressionSlopeValidator);
|
||||
}
|
||||
|
||||
@ -65,6 +61,6 @@ export function main() {
|
||||
});
|
||||
}
|
||||
|
||||
function mv(runIndex, time, values) {
|
||||
function mv(runIndex: number, time: number, values: {[key: string]: number}) {
|
||||
return new MeasureValues(runIndex, DateWrapper.fromMillis(time), values);
|
||||
}
|
@ -1,33 +1,27 @@
|
||||
import {
|
||||
describe,
|
||||
ddescribe,
|
||||
it,
|
||||
iit,
|
||||
xit,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach
|
||||
} from '@angular/testing/testing_internal';
|
||||
import {Date, DateWrapper} from '@angular/facade';
|
||||
import {ListWrapper} from '@angular/facade';
|
||||
/**
|
||||
* @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 {
|
||||
Validator,
|
||||
SizeValidator,
|
||||
ReflectiveInjector,
|
||||
MeasureValues
|
||||
} from 'benchpress/common';
|
||||
import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {MeasureValues, ReflectiveInjector, SizeValidator, Validator} from '../../index';
|
||||
import {ListWrapper} from '../../src/facade/collection';
|
||||
import {Date, DateWrapper} from '../../src/facade/lang';
|
||||
|
||||
export function main() {
|
||||
describe('size validator', () => {
|
||||
var validator;
|
||||
var validator: SizeValidator;
|
||||
|
||||
function createValidator(size) {
|
||||
validator = ReflectiveInjector.resolveAndCreate([
|
||||
SizeValidator.PROVIDERS,
|
||||
{provide: SizeValidator.SAMPLE_SIZE, useValue: size}
|
||||
])
|
||||
.get(SizeValidator);
|
||||
function createValidator(size: number) {
|
||||
validator =
|
||||
ReflectiveInjector
|
||||
.resolveAndCreate(
|
||||
[SizeValidator.PROVIDERS, {provide: SizeValidator.SAMPLE_SIZE, useValue: size}])
|
||||
.get(SizeValidator);
|
||||
}
|
||||
|
||||
it('should return sampleSize as description', () => {
|
||||
@ -52,6 +46,6 @@ export function main() {
|
||||
});
|
||||
}
|
||||
|
||||
function mv(runIndex, time, values) {
|
||||
function mv(runIndex: number, time: number, values: {[key: string]: number}) {
|
||||
return new MeasureValues(runIndex, DateWrapper.fromMillis(time), values);
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @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 {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {Options, ReflectiveInjector, WebDriverExtension} from '../index';
|
||||
import {StringWrapper, isPresent} from '../src/facade/lang';
|
||||
|
||||
export function main() {
|
||||
function createExtension(ids: any[], caps: any) {
|
||||
return new Promise<any>((res, rej) => {
|
||||
try {
|
||||
res(ReflectiveInjector
|
||||
.resolveAndCreate([
|
||||
ids.map((id) => { return {provide: id, useValue: new MockExtension(id)}; }),
|
||||
{provide: Options.CAPABILITIES, useValue: caps},
|
||||
WebDriverExtension.provideFirstSupported(ids)
|
||||
])
|
||||
.get(WebDriverExtension));
|
||||
} catch (e) {
|
||||
rej(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe('WebDriverExtension.provideFirstSupported', () => {
|
||||
|
||||
it('should provide the extension that matches the capabilities',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension(['m1', 'm2', 'm3'], {'browser': 'm2'}).then((m) => {
|
||||
expect(m.id).toEqual('m2');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should throw if there is no match',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension(['m1'], {'browser': 'm2'}).catch((err) => {
|
||||
expect(isPresent(err)).toBe(true);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
class MockExtension extends WebDriverExtension {
|
||||
constructor(public id: string) { super(); }
|
||||
|
||||
supports(capabilities: {[key: string]: any}): boolean {
|
||||
return StringWrapper.equals(capabilities['browser'], this.id);
|
||||
}
|
||||
}
|
@ -0,0 +1,408 @@
|
||||
/**
|
||||
* @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 {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {ChromeDriverExtension, Options, ReflectiveInjector, WebDriverAdapter, WebDriverExtension} from '../../index';
|
||||
import {Json, isBlank} from '../../src/facade/lang';
|
||||
import {TraceEventFactory} from '../trace_event_factory';
|
||||
|
||||
export function main() {
|
||||
describe('chrome driver extension', () => {
|
||||
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"';
|
||||
|
||||
var log: any[];
|
||||
var extension: ChromeDriverExtension;
|
||||
|
||||
var blinkEvents = new TraceEventFactory('blink.console', 'pid0');
|
||||
var v8Events = new TraceEventFactory('v8', 'pid0');
|
||||
var v8EventsOtherProcess = new TraceEventFactory('v8', 'pid1');
|
||||
var chromeTimelineEvents =
|
||||
new TraceEventFactory('disabled-by-default-devtools.timeline', 'pid0');
|
||||
var chrome45TimelineEvents = new TraceEventFactory('devtools.timeline', 'pid0');
|
||||
var chromeTimelineV8Events = new TraceEventFactory('devtools.timeline,v8', 'pid0');
|
||||
var chromeBlinkTimelineEvents = new TraceEventFactory('blink,devtools.timeline', 'pid0');
|
||||
var chromeBlinkUserTimingEvents = new TraceEventFactory('blink.user_timing', 'pid0');
|
||||
var benchmarkEvents = new TraceEventFactory('benchmark', 'pid0');
|
||||
var normEvents = new TraceEventFactory('timeline', 'pid0');
|
||||
|
||||
function createExtension(
|
||||
perfRecords: any[] = null, userAgent: string = null,
|
||||
messageMethod = 'Tracing.dataCollected'): WebDriverExtension {
|
||||
if (isBlank(perfRecords)) {
|
||||
perfRecords = [];
|
||||
}
|
||||
if (isBlank(userAgent)) {
|
||||
userAgent = CHROME45_USER_AGENT;
|
||||
}
|
||||
log = [];
|
||||
extension = ReflectiveInjector
|
||||
.resolveAndCreate([
|
||||
ChromeDriverExtension.PROVIDERS, {
|
||||
provide: WebDriverAdapter,
|
||||
useValue: new MockDriverAdapter(log, perfRecords, messageMethod)
|
||||
},
|
||||
{provide: Options.USER_AGENT, useValue: userAgent}
|
||||
])
|
||||
.get(ChromeDriverExtension);
|
||||
return extension;
|
||||
}
|
||||
|
||||
it('should force gc via window.gc()',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension().gc().then((_) => {
|
||||
expect(log).toEqual([['executeScript', 'window.gc()']]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should mark the timeline via console.time()',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension().timeBegin('someName').then((_) => {
|
||||
expect(log).toEqual([['executeScript', `console.time('someName');`]]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should mark the timeline via console.timeEnd()',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension().timeEnd('someName', null).then((_) => {
|
||||
expect(log).toEqual([['executeScript', `console.timeEnd('someName');`]]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should mark the timeline via console.time() and console.timeEnd()',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension().timeEnd('name1', 'name2').then((_) => {
|
||||
expect(log).toEqual(
|
||||
[['executeScript', `console.timeEnd('name1');console.time('name2');`]]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should normalize times to ms and forward ph and pid event properties',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension([chromeTimelineV8Events.complete('FunctionCall', 1100, 5500, null)])
|
||||
.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]).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)])
|
||||
.readPerfLog()
|
||||
.then((events) => {
|
||||
expect(events).toEqual([
|
||||
normEvents.start('script', 0),
|
||||
]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should report EvaluateScript events as "script"',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension([chromeTimelineV8Events.start('EvaluateScript', 0)])
|
||||
.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}),
|
||||
])
|
||||
.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}),
|
||||
], )
|
||||
.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)
|
||||
], )
|
||||
.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) => {
|
||||
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)', () => {
|
||||
|
||||
it('should execute a dummy script before reading them',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
// TODO(tbosch): This seems to be a bug in ChromeDriver:
|
||||
// Sometimes it does not report the newest events of the performance log
|
||||
// to the WebDriver client unless a script is executed...
|
||||
createExtension([]).readPerfLog().then((_) => {
|
||||
expect(log).toEqual([['executeScript', '1+1'], ['logs', 'performance']]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
['Rasterize', 'CompositeLayers'].forEach((recordType) => {
|
||||
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();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('frame metrics', () => {
|
||||
it('should report ImplThreadRenderingStats as frame event',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension([benchmarkEvents.instant(
|
||||
'BenchmarkInstrumentation::ImplThreadRenderingStats', 1100,
|
||||
{'data': {'frame_count': 1}})])
|
||||
.readPerfLog()
|
||||
.then((events) => {
|
||||
expect(events).toEqual([
|
||||
normEvents.instant('frame', 1.1),
|
||||
]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should not report ImplThreadRenderingStats with zero frames',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension([benchmarkEvents.instant(
|
||||
'BenchmarkInstrumentation::ImplThreadRenderingStats', 1100,
|
||||
{'data': {'frame_count': 0}})])
|
||||
.readPerfLog()
|
||||
.then((events) => {
|
||||
expect(events).toEqual([]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should throw when ImplThreadRenderingStats contains more than one frame',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
|
||||
createExtension([benchmarkEvents.instant(
|
||||
'BenchmarkInstrumentation::ImplThreadRenderingStats', 1100,
|
||||
{'data': {'frame_count': 2}})])
|
||||
.readPerfLog()
|
||||
.catch((err): any => {
|
||||
expect(() => {
|
||||
throw err;
|
||||
}).toThrowError('multi-frame render stats not supported');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
it('should report begin timestamps',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension([blinkEvents.create('S', 'someName', 1000)])
|
||||
.readPerfLog()
|
||||
.then((events) => {
|
||||
expect(events).toEqual([normEvents.markStart('someName', 1.0)]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should report end timestamps',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension([blinkEvents.create('F', 'someName', 1000)])
|
||||
.readPerfLog()
|
||||
.then((events) => {
|
||||
expect(events).toEqual([normEvents.markEnd('someName', 1.0)]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should throw an error on buffer overflow',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
|
||||
createExtension(
|
||||
[
|
||||
chromeTimelineEvents.start('FunctionCall', 1234),
|
||||
],
|
||||
CHROME45_USER_AGENT, 'Tracing.bufferUsage')
|
||||
.readPerfLog()
|
||||
.catch((err): any => {
|
||||
expect(() => {
|
||||
throw err;
|
||||
}).toThrowError('The DevTools trace buffer filled during the test!');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should match chrome browsers', () => {
|
||||
expect(createExtension().supports({'browserName': 'chrome'})).toBe(true);
|
||||
|
||||
expect(createExtension().supports({'browserName': 'Chrome'})).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
class MockDriverAdapter extends WebDriverAdapter {
|
||||
constructor(private _log: any[], private _events: any[], private _messageMethod: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
executeScript(script: string) {
|
||||
this._log.push(['executeScript', script]);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
logs(type: string) {
|
||||
this._log.push(['logs', type]);
|
||||
if (type === 'performance') {
|
||||
return Promise.resolve(this._events.map((event) => {
|
||||
return {
|
||||
'message': Json.stringify({'message': {'method': this._messageMethod, 'params': event}})
|
||||
};
|
||||
}));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,195 @@
|
||||
/**
|
||||
* @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 {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {IOsDriverExtension, ReflectiveInjector, WebDriverAdapter, WebDriverExtension} from '../../index';
|
||||
import {Json, isBlank, isPresent} from '../../src/facade/lang';
|
||||
import {TraceEventFactory} from '../trace_event_factory';
|
||||
|
||||
export function main() {
|
||||
describe('ios driver extension', () => {
|
||||
var log: any[];
|
||||
var extension: IOsDriverExtension;
|
||||
|
||||
var normEvents = new TraceEventFactory('timeline', 'pid0');
|
||||
|
||||
function createExtension(perfRecords: any[] = null): WebDriverExtension {
|
||||
if (isBlank(perfRecords)) {
|
||||
perfRecords = [];
|
||||
}
|
||||
log = [];
|
||||
extension =
|
||||
ReflectiveInjector
|
||||
.resolveAndCreate([
|
||||
IOsDriverExtension.PROVIDERS,
|
||||
{provide: WebDriverAdapter, useValue: new MockDriverAdapter(log, perfRecords)}
|
||||
])
|
||||
.get(IOsDriverExtension);
|
||||
return extension;
|
||||
}
|
||||
|
||||
it('should throw on forcing gc', () => {
|
||||
expect(() => createExtension().gc()).toThrowError('Force GC is not supported on iOS');
|
||||
});
|
||||
|
||||
it('should mark the timeline via console.time()',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension().timeBegin('someName').then((_) => {
|
||||
expect(log).toEqual([['executeScript', `console.time('someName');`]]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should mark the timeline via console.timeEnd()',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension().timeEnd('someName', null).then((_) => {
|
||||
expect(log).toEqual([['executeScript', `console.timeEnd('someName');`]]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should mark the timeline via console.time() and console.timeEnd()',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension().timeEnd('name1', 'name2').then((_) => {
|
||||
expect(log).toEqual(
|
||||
[['executeScript', `console.timeEnd('name1');console.time('name2');`]]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
describe('readPerfLog', () => {
|
||||
|
||||
it('should execute a dummy script before reading them',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
// TODO(tbosch): This seems to be a bug in ChromeDriver:
|
||||
// Sometimes it does not report the newest events of the performance log
|
||||
// to the WebDriver client unless a script is executed...
|
||||
createExtension([]).readPerfLog().then((_) => {
|
||||
expect(log).toEqual([['executeScript', '1+1'], ['logs', 'performance']]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should report FunctionCall records as "script"',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension([durationRecord('FunctionCall', 1, 5)]).readPerfLog().then((events) => {
|
||||
expect(events).toEqual([normEvents.start('script', 1), normEvents.end('script', 5)]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should ignore FunctionCalls from webdriver',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension([internalScriptRecord(1, 5)]).readPerfLog().then((events) => {
|
||||
expect(events).toEqual([]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should report begin time', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension([timeBeginRecord('someName', 12)]).readPerfLog().then((events) => {
|
||||
expect(events).toEqual([normEvents.markStart('someName', 12)]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should report end timestamps',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension([timeEndRecord('someName', 12)]).readPerfLog().then((events) => {
|
||||
expect(events).toEqual([normEvents.markEnd('someName', 12)]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
['RecalculateStyles', 'Layout', 'UpdateLayerTree', 'Paint', 'Rasterize', 'CompositeLayers']
|
||||
.forEach((recordType) => {
|
||||
it(`should report ${recordType}`,
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension([durationRecord(recordType, 0, 1)])
|
||||
.readPerfLog()
|
||||
.then((events) => {
|
||||
expect(events).toEqual([
|
||||
normEvents.start('render', 0),
|
||||
normEvents.end('render', 1),
|
||||
]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
it('should walk children', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension([durationRecord('FunctionCall', 1, 5, [timeBeginRecord('someName', 2)])])
|
||||
.readPerfLog()
|
||||
.then((events) => {
|
||||
expect(events).toEqual([
|
||||
normEvents.start('script', 1), normEvents.markStart('someName', 2),
|
||||
normEvents.end('script', 5)
|
||||
]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should match safari browsers', () => {
|
||||
expect(createExtension().supports({'browserName': 'safari'})).toBe(true);
|
||||
|
||||
expect(createExtension().supports({'browserName': 'Safari'})).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function timeBeginRecord(name: string, time: number) {
|
||||
return {'type': 'Time', 'startTime': time, 'data': {'message': name}};
|
||||
}
|
||||
|
||||
function timeEndRecord(name: string, time: number) {
|
||||
return {'type': 'TimeEnd', 'startTime': time, 'data': {'message': name}};
|
||||
}
|
||||
|
||||
function durationRecord(type: string, startTime: number, endTime: number, children: any[] = null) {
|
||||
if (isBlank(children)) {
|
||||
children = [];
|
||||
}
|
||||
return {'type': type, 'startTime': startTime, 'endTime': endTime, 'children': children};
|
||||
}
|
||||
|
||||
function internalScriptRecord(startTime: number, endTime: number) {
|
||||
return {
|
||||
'type': 'FunctionCall',
|
||||
'startTime': startTime,
|
||||
'endTime': endTime,
|
||||
'data': {'scriptName': 'InjectedScript'}
|
||||
};
|
||||
}
|
||||
|
||||
class MockDriverAdapter extends WebDriverAdapter {
|
||||
constructor(private _log: any[], private _perfRecords: any[]) { super(); }
|
||||
|
||||
executeScript(script: string) {
|
||||
this._log.push(['executeScript', script]);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
logs(type: string) {
|
||||
this._log.push(['logs', type]);
|
||||
if (type === 'performance') {
|
||||
return Promise.resolve(this._perfRecords.map(function(record) {
|
||||
return {
|
||||
'message': Json.stringify(
|
||||
{'message': {'method': 'Timeline.eventRecorded', 'params': {'record': record}}})
|
||||
};
|
||||
}));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
26
modules/@angular/benchpress/tsconfig-build.json
Normal file
26
modules/@angular/benchpress/tsconfig-build.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"noImplicitAny": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@angular/core": ["../../../dist/packages-dist/core"]
|
||||
},
|
||||
"experimentalDecorators": true,
|
||||
"rootDir": ".",
|
||||
"sourceRoot": ".",
|
||||
"outDir": "../../../dist/packages-dist/benchpress",
|
||||
"declaration": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"exclude": ["integrationtest"],
|
||||
"files": [
|
||||
"index.ts",
|
||||
"../../../node_modules/@types/node/index.d.ts",
|
||||
"../../../node_modules/@types/jasmine/index.d.ts",
|
||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||
]
|
||||
}
|
@ -1 +0,0 @@
|
||||
export 'index.dart';
|
@ -1,5 +1,19 @@
|
||||
export * from './src/pipes';
|
||||
export * from './src/directives';
|
||||
export * from './src/forms-deprecated';
|
||||
export * from './src/common_directives';
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module
|
||||
* @description
|
||||
* Entry point for all public APIs of the common package.
|
||||
*/
|
||||
export * from './src/location';
|
||||
export {NgLocalization} from './src/localization';
|
||||
export {CommonModule} from './src/common_module';
|
||||
|
||||
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';
|
@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "@angular/common",
|
||||
"version": "0.0.0-PLACEHOLDER",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"jsnext:main": "esm/index.js",
|
||||
"description": "Angular - commonly needed directives and services",
|
||||
"main": "bundles/common.umd.js",
|
||||
"module": "index.js",
|
||||
"typings": "index.d.ts",
|
||||
"author": "angular",
|
||||
"license": "MIT",
|
||||
|
13
modules/@angular/common/rollup-testing.config.js
Normal file
13
modules/@angular/common/rollup-testing.config.js
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
export default {
|
||||
entry: '../../../dist/packages-dist/common/testing/index.js',
|
||||
dest: '../../../dist/packages-dist/common/bundles/common-testing.umd.js',
|
||||
format: 'umd',
|
||||
moduleName: 'ng.common.testing',
|
||||
globals: {
|
||||
'@angular/core': 'ng.core',
|
||||
'@angular/common': 'ng.common',
|
||||
'rxjs/Observable': 'Rx',
|
||||
'rxjs/Subject': 'Rx'
|
||||
}
|
||||
}
|
@ -1,17 +1,12 @@
|
||||
|
||||
export default {
|
||||
entry: '../../../dist/packages-dist/common/esm/index.js',
|
||||
dest: '../../../dist/packages-dist/common/esm/common.umd.js',
|
||||
entry: '../../../dist/packages-dist/common/index.js',
|
||||
dest: '../../../dist/packages-dist/common/bundles/common.umd.js',
|
||||
format: 'umd',
|
||||
moduleName: 'ng.common',
|
||||
globals: {
|
||||
'@angular/core': 'ng.core',
|
||||
'rxjs/Subject': 'Rx',
|
||||
'rxjs/observable/PromiseObservable': 'Rx', // this is wrong, but this stuff has changed in rxjs b.6 so we need to fix it when we update.
|
||||
'rxjs/operator/toPromise': 'Rx.Observable.prototype',
|
||||
'rxjs/Observable': 'Rx'
|
||||
},
|
||||
plugins: [
|
||||
// nodeResolve({ jsnext: true, main: true }),
|
||||
]
|
||||
'rxjs/Observable': 'Rx',
|
||||
'rxjs/Subject': 'Rx'
|
||||
}
|
||||
}
|
||||
|
@ -1,52 +0,0 @@
|
||||
import {Type} from '@angular/core';
|
||||
|
||||
import {CORE_DIRECTIVES} from './directives';
|
||||
import {FORM_DIRECTIVES} from './forms-deprecated';
|
||||
|
||||
|
||||
/**
|
||||
* 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, NgSwitchWhen, NgSwitchDefault, NgModel, NgForm} from
|
||||
* '@angular/common';
|
||||
* import {OtherDirective} from './myDirectives';
|
||||
*
|
||||
* @Component({
|
||||
* selector: 'my-component',
|
||||
* templateUrl: 'myComponent.html',
|
||||
* directives: [NgClass, NgIf, NgFor, NgSwitch, NgSwitchWhen, 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: Type[][] = /*@ts2dart_const*/[CORE_DIRECTIVES, FORM_DIRECTIVES];
|
30
modules/@angular/common/src/common_module.ts
Normal file
30
modules/@angular/common/src/common_module.ts
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* @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 {NgModule} from '@angular/core';
|
||||
|
||||
import {COMMON_DIRECTIVES} from './directives/index';
|
||||
import {NgLocaleLocalization, NgLocalization} from './localization';
|
||||
import {COMMON_PIPES} from './pipes/index';
|
||||
|
||||
// Note: This does not contain the location providers,
|
||||
// as they need some platform specific implementations to work.
|
||||
/**
|
||||
* The module that includes all the basic Angular directives like {@link NgIf}, {@link NgFor}, ...
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@NgModule({
|
||||
declarations: [COMMON_DIRECTIVES, COMMON_PIPES],
|
||||
exports: [COMMON_DIRECTIVES, COMMON_PIPES],
|
||||
providers: [
|
||||
{provide: NgLocalization, useClass: NgLocaleLocalization},
|
||||
],
|
||||
})
|
||||
export class CommonModule {
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
/**
|
||||
* @module
|
||||
* @description
|
||||
* Common directives shipped with Angular.
|
||||
*/
|
||||
export {CORE_DIRECTIVES} from './directives/core_directives';
|
||||
export {NgClass} from './directives/ng_class';
|
||||
export {NgFor} from './directives/ng_for';
|
||||
export {NgIf} from './directives/ng_if';
|
||||
export {NgLocalization, 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';
|
@ -1,66 +0,0 @@
|
||||
import {Type} from '../facade/lang';
|
||||
|
||||
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, NgSwitchWhen, NgSwitchDefault} from '@angular/common';
|
||||
* import {OtherDirective} from './myDirectives';
|
||||
*
|
||||
* @Component({
|
||||
* selector: 'my-component',
|
||||
* templateUrl: 'myComponent.html',
|
||||
* directives: [NgClass, NgIf, NgFor, NgSwitch, NgSwitchWhen, 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[] = /*@ts2dart_const*/[
|
||||
NgClass,
|
||||
NgFor,
|
||||
NgIf,
|
||||
NgTemplateOutlet,
|
||||
NgStyle,
|
||||
NgSwitch,
|
||||
NgSwitchCase,
|
||||
NgSwitchDefault,
|
||||
NgPlural,
|
||||
NgPluralCase,
|
||||
];
|
47
modules/@angular/common/src/directives/index.ts
Normal file
47
modules/@angular/common/src/directives/index.ts
Normal 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,
|
||||
];
|
@ -1,134 +1,108 @@
|
||||
import {CollectionChangeRecord, Directive, DoCheck, ElementRef, IterableDiffer, IterableDiffers, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, OnDestroy, Renderer} from '@angular/core';
|
||||
/**
|
||||
* @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 {CollectionChangeRecord, Directive, DoCheck, ElementRef, Input, IterableDiffer, IterableDiffers, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
|
||||
|
||||
import {isListLikeIterable} from '../facade/collection';
|
||||
import {isPresent} from '../facade/lang';
|
||||
|
||||
import {StringMapWrapper, isListLikeIterable} from '../facade/collection';
|
||||
import {isArray, isPresent, isString} from '../facade/lang';
|
||||
|
||||
|
||||
/**
|
||||
* The `NgClass` directive conditionally adds and removes CSS classes on an HTML element based on
|
||||
* an expression's evaluation result.
|
||||
* @ngModule CommonModule
|
||||
*
|
||||
* The result of an expression evaluation is interpreted differently depending on type of
|
||||
* 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)):
|
||||
* @whatItDoes Adds and removes CSS classes on an HTML element.
|
||||
*
|
||||
* @howToUse
|
||||
* ```
|
||||
* import {Component} from '@angular/core';
|
||||
* import {NgClass} from '@angular/common';
|
||||
* <some-element [ngClass]="'first second'">...</some-element>
|
||||
*
|
||||
* @Component({
|
||||
* 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;
|
||||
* }
|
||||
* <some-element [ngClass]="['first', 'second']">...</some-element>
|
||||
*
|
||||
* .active {
|
||||
* background-color: red;
|
||||
* }
|
||||
* <some-element [ngClass]="{'first': true, 'second': true, 'third': false}">...</some-element>
|
||||
*
|
||||
* .disabled {
|
||||
* color: gray;
|
||||
* border: medium solid gray;
|
||||
* }
|
||||
* `],
|
||||
* directives: [NgClass]
|
||||
* })
|
||||
* class ToggleButton {
|
||||
* isOn = false;
|
||||
* isDisabled = false;
|
||||
*
|
||||
* toggle(newState) {
|
||||
* if (!this.isDisabled) {
|
||||
* this.isOn = newState;
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* <some-element [ngClass]="stringExp|arrayExp|objExp">...</some-element>
|
||||
* ```
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
@Directive({selector: '[ngClass]', inputs: ['rawClass: ngClass', 'initialClasses: class']})
|
||||
export class NgClass implements DoCheck, OnDestroy {
|
||||
@Directive({selector: '[ngClass]'})
|
||||
export class NgClass implements DoCheck {
|
||||
private _iterableDiffer: IterableDiffer;
|
||||
private _keyValueDiffer: KeyValueDiffer;
|
||||
private _initialClasses: string[] = [];
|
||||
private _rawClass: string[]|Set<string>;
|
||||
private _rawClass: string[]|Set<string>|{[klass: string]: any};
|
||||
|
||||
constructor(
|
||||
private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers,
|
||||
private _ngEl: ElementRef, private _renderer: Renderer) {}
|
||||
|
||||
set initialClasses(v: string) {
|
||||
|
||||
@Input('class')
|
||||
set klass(v: string) {
|
||||
this._applyInitialClasses(true);
|
||||
this._initialClasses = isPresent(v) && isString(v) ? v.split(' ') : [];
|
||||
this._initialClasses = typeof v === 'string' ? v.split(/\s+/) : [];
|
||||
this._applyInitialClasses(false);
|
||||
this._applyClasses(this._rawClass, false);
|
||||
}
|
||||
|
||||
set rawClass(v: string|string[]|Set<string>|{[key: string]: any}) {
|
||||
@Input()
|
||||
set ngClass(v: string|string[]|Set<string>|{[klass: string]: any}) {
|
||||
this._cleanupClasses(this._rawClass);
|
||||
|
||||
if (isString(v)) {
|
||||
v = (<string>v).split(' ');
|
||||
}
|
||||
|
||||
this._rawClass = <string[]|Set<string>>v;
|
||||
this._iterableDiffer = null;
|
||||
this._keyValueDiffer = null;
|
||||
if (isPresent(v)) {
|
||||
if (isListLikeIterable(v)) {
|
||||
this._iterableDiffer = this._iterableDiffers.find(v).create(null);
|
||||
|
||||
this._rawClass = typeof v === 'string' ? v.split(/\s+/) : v;
|
||||
|
||||
if (this._rawClass) {
|
||||
if (isListLikeIterable(this._rawClass)) {
|
||||
this._iterableDiffer = this._iterableDiffers.find(this._rawClass).create(null);
|
||||
} else {
|
||||
this._keyValueDiffer = this._keyValueDiffers.find(v).create(null);
|
||||
this._keyValueDiffer = this._keyValueDiffers.find(this._rawClass).create(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngDoCheck(): void {
|
||||
if (isPresent(this._iterableDiffer)) {
|
||||
var changes = this._iterableDiffer.diff(this._rawClass);
|
||||
if (isPresent(changes)) {
|
||||
if (this._iterableDiffer) {
|
||||
const changes = this._iterableDiffer.diff(this._rawClass);
|
||||
if (changes) {
|
||||
this._applyIterableChanges(changes);
|
||||
}
|
||||
}
|
||||
if (isPresent(this._keyValueDiffer)) {
|
||||
var changes = this._keyValueDiffer.diff(this._rawClass);
|
||||
if (isPresent(changes)) {
|
||||
} else if (this._keyValueDiffer) {
|
||||
const changes = this._keyValueDiffer.diff(this._rawClass);
|
||||
if (changes) {
|
||||
this._applyKeyValueChanges(changes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void { this._cleanupClasses(this._rawClass); }
|
||||
|
||||
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._applyInitialClasses(false);
|
||||
}
|
||||
|
||||
private _applyKeyValueChanges(changes: any): void {
|
||||
changes.forEachAddedItem(
|
||||
(record: KeyValueChangeRecord) => { this._toggleClass(record.key, record.currentValue); });
|
||||
(record: KeyValueChangeRecord) => this._toggleClass(record.key, record.currentValue));
|
||||
|
||||
changes.forEachChangedItem(
|
||||
(record: KeyValueChangeRecord) => { this._toggleClass(record.key, record.currentValue); });
|
||||
(record: KeyValueChangeRecord) => this._toggleClass(record.key, record.currentValue));
|
||||
|
||||
changes.forEachRemovedItem((record: KeyValueChangeRecord) => {
|
||||
if (record.previousValue) {
|
||||
this._toggleClass(record.key, false);
|
||||
@ -138,42 +112,34 @@ export class NgClass implements DoCheck, OnDestroy {
|
||||
|
||||
private _applyIterableChanges(changes: any): void {
|
||||
changes.forEachAddedItem(
|
||||
(record: CollectionChangeRecord) => { this._toggleClass(record.item, true); });
|
||||
(record: CollectionChangeRecord) => this._toggleClass(record.item, true));
|
||||
|
||||
changes.forEachRemovedItem(
|
||||
(record: CollectionChangeRecord) => { this._toggleClass(record.item, false); });
|
||||
(record: CollectionChangeRecord) => this._toggleClass(record.item, false));
|
||||
}
|
||||
|
||||
private _applyInitialClasses(isCleanup: boolean) {
|
||||
this._initialClasses.forEach(className => this._toggleClass(className, !isCleanup));
|
||||
this._initialClasses.forEach(klass => this._toggleClass(klass, !isCleanup));
|
||||
}
|
||||
|
||||
private _applyClasses(
|
||||
rawClassVal: string[]|Set<string>|{[key: string]: any}, isCleanup: boolean) {
|
||||
if (isPresent(rawClassVal)) {
|
||||
if (isArray(rawClassVal)) {
|
||||
(<string[]>rawClassVal).forEach(className => this._toggleClass(className, !isCleanup));
|
||||
} else if (rawClassVal instanceof Set) {
|
||||
(<Set<string>>rawClassVal).forEach(className => this._toggleClass(className, !isCleanup));
|
||||
if (rawClassVal) {
|
||||
if (Array.isArray(rawClassVal) || rawClassVal instanceof Set) {
|
||||
(<any>rawClassVal).forEach((klass: string) => this._toggleClass(klass, !isCleanup));
|
||||
} else {
|
||||
StringMapWrapper.forEach(
|
||||
<{[k: string]: any}>rawClassVal, (expVal: any, className: string) => {
|
||||
if (isPresent(expVal)) this._toggleClass(className, !isCleanup);
|
||||
});
|
||||
Object.keys(rawClassVal).forEach(klass => {
|
||||
if (isPresent(rawClassVal[klass])) this._toggleClass(klass, !isCleanup);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleClass(className: string, enabled: boolean): void {
|
||||
className = className.trim();
|
||||
if (className.length > 0) {
|
||||
if (className.indexOf(' ') > -1) {
|
||||
var classes = className.split(/\s+/g);
|
||||
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);
|
||||
}
|
||||
private _toggleClass(klass: string, enabled: boolean): void {
|
||||
klass = klass.trim();
|
||||
if (klass) {
|
||||
klass.split(/\s+/g).forEach(
|
||||
klass => { this._renderer.setElementClass(this._ngEl.nativeElement, klass, enabled); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,14 @@
|
||||
import {ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, Directive, DoCheck, EmbeddedViewRef, IterableDiffer, IterableDiffers, TemplateRef, TrackByFn, ViewContainerRef} from '@angular/core';
|
||||
/**
|
||||
* @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 {BaseException} from '../facade/exceptions';
|
||||
import {getTypeNameForDebugging, isBlank, isPresent} from '../facade/lang';
|
||||
import {ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, Directive, DoCheck, EmbeddedViewRef, Input, IterableDiffer, IterableDiffers, OnChanges, SimpleChanges, TemplateRef, TrackByFn, ViewContainerRef} from '@angular/core';
|
||||
|
||||
import {getTypeNameForDebugging} from '../facade/lang';
|
||||
|
||||
export class NgForRow {
|
||||
constructor(public $implicit: any, public index: number, public count: number) {}
|
||||
@ -55,11 +62,22 @@ export class NgForRow {
|
||||
* elements were deleted and all new elements inserted). This is an expensive operation and should
|
||||
* be avoided if possible.
|
||||
*
|
||||
* To customize the default tracking algorithm, `NgFor` supports `trackBy` option.
|
||||
* `trackBy` takes a function which has two arguments: `index` and `item`.
|
||||
* If `trackBy` is given, Angular tracks changes by the return value of the function.
|
||||
*
|
||||
* ### Syntax
|
||||
*
|
||||
* - `<li *ngFor="let item of items; let i = index">...</li>`
|
||||
* - `<li template="ngFor let item of items; let i = index">...</li>`
|
||||
* - `<template ngFor let-item [ngForOf]="items" let-i="index"><li>...</li></template>`
|
||||
* - `<li *ngFor="let item of items; let i = index; trackBy: trackByFn">...</li>`
|
||||
* - `<li template="ngFor let item of items; let i = index; trackBy: trackByFn">...</li>`
|
||||
*
|
||||
* With `<template>` element:
|
||||
*
|
||||
* ```
|
||||
* <template ngFor let-item [ngForOf]="items" let-i="index" [ngForTrackBy]="trackByFn">
|
||||
* <li>...</li>
|
||||
* </template>
|
||||
* ```
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
@ -68,77 +86,77 @@ export class NgForRow {
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Directive({selector: '[ngFor][ngForOf]', inputs: ['ngForTrackBy', 'ngForOf', 'ngForTemplate']})
|
||||
export class NgFor implements DoCheck {
|
||||
/** @internal */
|
||||
_ngForOf: any;
|
||||
/** @internal */
|
||||
_ngForTrackBy: TrackByFn;
|
||||
private _differ: IterableDiffer;
|
||||
@Directive({selector: '[ngFor][ngForOf]'})
|
||||
export class NgFor implements DoCheck, OnChanges {
|
||||
@Input() ngForOf: any;
|
||||
@Input() ngForTrackBy: TrackByFn;
|
||||
|
||||
private _differ: IterableDiffer = null;
|
||||
|
||||
constructor(
|
||||
private _viewContainer: ViewContainerRef, private _templateRef: TemplateRef<NgForRow>,
|
||||
private _iterableDiffers: IterableDiffers, private _cdr: ChangeDetectorRef) {}
|
||||
private _viewContainer: ViewContainerRef, private _template: TemplateRef<NgForRow>,
|
||||
private _differs: IterableDiffers, private _cdr: ChangeDetectorRef) {}
|
||||
|
||||
set ngForOf(value: any) {
|
||||
this._ngForOf = value;
|
||||
if (isBlank(this._differ) && isPresent(value)) {
|
||||
try {
|
||||
this._differ = this._iterableDiffers.find(value).create(this._cdr, this._ngForTrackBy);
|
||||
} catch (e) {
|
||||
throw new BaseException(
|
||||
`Cannot find a differ supporting object '${value}' of type '${getTypeNameForDebugging(value)}'. NgFor only supports binding to Iterables such as Arrays.`);
|
||||
@Input()
|
||||
set ngForTemplate(value: TemplateRef<NgForRow>) {
|
||||
if (value) {
|
||||
this._template = value;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if ('ngForOf' in changes) {
|
||||
// React on ngForOf changes only once all inputs have been initialized
|
||||
const value = changes['ngForOf'].currentValue;
|
||||
if (!this._differ && value) {
|
||||
try {
|
||||
this._differ = this._differs.find(value).create(this._cdr, this.ngForTrackBy);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Cannot find a differ supporting object '${value}' of type '${getTypeNameForDebugging(value)}'. NgFor only supports binding to Iterables such as Arrays.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set ngForTemplate(value: TemplateRef<NgForRow>) {
|
||||
if (isPresent(value)) {
|
||||
this._templateRef = value;
|
||||
}
|
||||
}
|
||||
|
||||
set ngForTrackBy(value: TrackByFn) { this._ngForTrackBy = value; }
|
||||
|
||||
ngDoCheck() {
|
||||
if (isPresent(this._differ)) {
|
||||
var changes = this._differ.diff(this._ngForOf);
|
||||
if (isPresent(changes)) this._applyChanges(changes);
|
||||
if (this._differ) {
|
||||
const changes = this._differ.diff(this.ngForOf);
|
||||
if (changes) this._applyChanges(changes);
|
||||
}
|
||||
}
|
||||
|
||||
private _applyChanges(changes: DefaultIterableDiffer) {
|
||||
// TODO(rado): check if change detection can produce a change record that is
|
||||
// easier to consume than current.
|
||||
var recordViewTuples: RecordViewTuple[] = [];
|
||||
changes.forEachRemovedItem(
|
||||
(removedRecord: CollectionChangeRecord) =>
|
||||
recordViewTuples.push(new RecordViewTuple(removedRecord, null)));
|
||||
const insertTuples: RecordViewTuple[] = [];
|
||||
changes.forEachOperation(
|
||||
(item: CollectionChangeRecord, adjustedPreviousIndex: number, currentIndex: number) => {
|
||||
if (item.previousIndex == null) {
|
||||
const view = this._viewContainer.createEmbeddedView(
|
||||
this._template, new NgForRow(null, null, null), currentIndex);
|
||||
const tuple = new RecordViewTuple(item, view);
|
||||
insertTuples.push(tuple);
|
||||
} else if (currentIndex == null) {
|
||||
this._viewContainer.remove(adjustedPreviousIndex);
|
||||
} else {
|
||||
const view = this._viewContainer.get(adjustedPreviousIndex);
|
||||
this._viewContainer.move(view, currentIndex);
|
||||
const tuple = new RecordViewTuple(item, <EmbeddedViewRef<NgForRow>>view);
|
||||
insertTuples.push(tuple);
|
||||
}
|
||||
});
|
||||
|
||||
changes.forEachMovedItem(
|
||||
(movedRecord: CollectionChangeRecord) =>
|
||||
recordViewTuples.push(new RecordViewTuple(movedRecord, null)));
|
||||
|
||||
var insertTuples = this._bulkRemove(recordViewTuples);
|
||||
|
||||
changes.forEachAddedItem(
|
||||
(addedRecord: CollectionChangeRecord) =>
|
||||
insertTuples.push(new RecordViewTuple(addedRecord, null)));
|
||||
|
||||
this._bulkInsert(insertTuples);
|
||||
|
||||
for (var i = 0; i < insertTuples.length; i++) {
|
||||
for (let i = 0; i < insertTuples.length; i++) {
|
||||
this._perViewChange(insertTuples[i].view, insertTuples[i].record);
|
||||
}
|
||||
|
||||
for (var i = 0, ilen = this._viewContainer.length; i < ilen; i++) {
|
||||
var viewRef = <EmbeddedViewRef<NgForRow>>this._viewContainer.get(i);
|
||||
for (let i = 0, ilen = this._viewContainer.length; i < ilen; i++) {
|
||||
let viewRef = <EmbeddedViewRef<NgForRow>>this._viewContainer.get(i);
|
||||
viewRef.context.index = i;
|
||||
viewRef.context.count = ilen;
|
||||
}
|
||||
|
||||
changes.forEachIdentityChange((record: any /** TODO #9100 */) => {
|
||||
var viewRef = <EmbeddedViewRef<NgForRow>>this._viewContainer.get(record.currentIndex);
|
||||
changes.forEachIdentityChange((record: any) => {
|
||||
let viewRef = <EmbeddedViewRef<NgForRow>>this._viewContainer.get(record.currentIndex);
|
||||
viewRef.context.$implicit = record.item;
|
||||
});
|
||||
}
|
||||
@ -146,46 +164,8 @@ export class NgFor implements DoCheck {
|
||||
private _perViewChange(view: EmbeddedViewRef<NgForRow>, record: CollectionChangeRecord) {
|
||||
view.context.$implicit = record.item;
|
||||
}
|
||||
|
||||
private _bulkRemove(tuples: RecordViewTuple[]): RecordViewTuple[] {
|
||||
tuples.sort(
|
||||
(a: RecordViewTuple, b: RecordViewTuple) =>
|
||||
a.record.previousIndex - b.record.previousIndex);
|
||||
var movedTuples: RecordViewTuple[] = [];
|
||||
for (var i = tuples.length - 1; i >= 0; i--) {
|
||||
var tuple = tuples[i];
|
||||
// separate moved views from removed views.
|
||||
if (isPresent(tuple.record.currentIndex)) {
|
||||
tuple.view =
|
||||
<EmbeddedViewRef<NgForRow>>this._viewContainer.detach(tuple.record.previousIndex);
|
||||
movedTuples.push(tuple);
|
||||
} else {
|
||||
this._viewContainer.remove(tuple.record.previousIndex);
|
||||
}
|
||||
}
|
||||
return movedTuples;
|
||||
}
|
||||
|
||||
private _bulkInsert(tuples: RecordViewTuple[]): RecordViewTuple[] {
|
||||
tuples.sort((a, b) => a.record.currentIndex - b.record.currentIndex);
|
||||
for (var i = 0; i < tuples.length; i++) {
|
||||
var tuple = tuples[i];
|
||||
if (isPresent(tuple.view)) {
|
||||
this._viewContainer.insert(tuple.view, tuple.record.currentIndex);
|
||||
} else {
|
||||
tuple.view = this._viewContainer.createEmbeddedView(
|
||||
this._templateRef, new NgForRow(null, null, null), tuple.record.currentIndex);
|
||||
}
|
||||
}
|
||||
return tuples;
|
||||
}
|
||||
}
|
||||
|
||||
class RecordViewTuple {
|
||||
view: EmbeddedViewRef<NgForRow>;
|
||||
record: any;
|
||||
constructor(record: any, view: EmbeddedViewRef<NgForRow>) {
|
||||
this.record = record;
|
||||
this.view = view;
|
||||
}
|
||||
constructor(public record: any, public view: EmbeddedViewRef<NgForRow>) {}
|
||||
}
|
||||
|
@ -1,7 +1,12 @@
|
||||
import {Directive, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
|
||||
import {isBlank} from '../facade/lang';
|
||||
/**
|
||||
* @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 {Directive, Input, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
|
||||
/**
|
||||
* Removes or recreates a portion of the DOM tree based on an {expression}.
|
||||
@ -27,19 +32,19 @@ import {isBlank} from '../facade/lang';
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Directive({selector: '[ngIf]', inputs: ['ngIf']})
|
||||
@Directive({selector: '[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>) {}
|
||||
|
||||
set ngIf(newCondition: any /* boolean */) {
|
||||
if (newCondition && (isBlank(this._prevCondition) || !this._prevCondition)) {
|
||||
this._prevCondition = true;
|
||||
this._viewContainer.createEmbeddedView(this._templateRef);
|
||||
} else if (!newCondition && (isBlank(this._prevCondition) || this._prevCondition)) {
|
||||
this._prevCondition = false;
|
||||
@Input()
|
||||
set ngIf(condition: any) {
|
||||
if (condition && !this._hasView) {
|
||||
this._hasView = true;
|
||||
this._viewContainer.createEmbeddedView(this._template);
|
||||
} else if (!condition && this._hasView) {
|
||||
this._hasView = false;
|
||||
this._viewContainer.clear();
|
||||
}
|
||||
}
|
||||
|
@ -1,95 +1,55 @@
|
||||
import {AfterContentInit, Attribute, ContentChildren, Directive, Input, QueryList, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
/**
|
||||
* @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 {Map} from '../facade/collection';
|
||||
import {NumberWrapper, isPresent} from '../facade/lang';
|
||||
import {Attribute, Directive, Host, Input, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
|
||||
import {NgLocalization, getPluralCategory} from '../localization';
|
||||
|
||||
import {SwitchView} from './ng_switch';
|
||||
|
||||
const _CATEGORY_DEFAULT = 'other';
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
export abstract class NgLocalization { abstract getPluralCategory(value: any): string; }
|
||||
|
||||
/**
|
||||
* `ngPlural` is an i18n directive that displays DOM sub-trees that match the switch expression
|
||||
* value, or failing that, DOM sub-trees that match the switch expression's pluralization category.
|
||||
* @ngModule CommonModule
|
||||
*
|
||||
* To use this directive, you must provide an extension of `NgLocalization` that maps values to
|
||||
* category names. You then define a container element that sets the `[ngPlural]` attribute to a
|
||||
* switch expression.
|
||||
* - Inner elements defined with an `[ngPluralCase]` attribute will display based on their
|
||||
* expression.
|
||||
* - If `[ngPluralCase]` is set to a value starting with `=`, it will only display if the value
|
||||
* matches the switch expression exactly.
|
||||
* - 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 using the `getPluralCategory`
|
||||
* function provided.
|
||||
*
|
||||
* If no matching views are found for a switch expression, inner elements marked
|
||||
* `[ngPluralCase]="other"` will be displayed.
|
||||
*
|
||||
* ```typescript
|
||||
* class MyLocalization extends NgLocalization {
|
||||
* getPluralCategory(value: any) {
|
||||
* if(value < 5) {
|
||||
* return 'few';
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @Component({
|
||||
* selector: 'app',
|
||||
* providers: [{provide: NgLocalization, useClass: MyLocalization}]
|
||||
* })
|
||||
* @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;
|
||||
* }
|
||||
* }
|
||||
* @whatItDoes Adds / removes DOM sub-trees based on a numeric value. Tailored for pluralization.
|
||||
*
|
||||
* @howToUse
|
||||
* ```
|
||||
* @experimental
|
||||
*/
|
||||
|
||||
@Directive({selector: '[ngPluralCase]'})
|
||||
export class NgPluralCase {
|
||||
/** @internal */
|
||||
_view: SwitchView;
|
||||
constructor(
|
||||
@Attribute('ngPluralCase') public value: string, template: TemplateRef<Object>,
|
||||
viewContainer: ViewContainerRef) {
|
||||
this._view = new SwitchView(viewContainer, template);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <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 a switch expression. Inner elements with a `[ngPluralCase]` will display based on their
|
||||
* expression:
|
||||
* - if `[ngPluralCase]` is set to a value starting with `=`, it will only display if the value
|
||||
* matches the switch expression exactly,
|
||||
* - 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.
|
||||
*
|
||||
* See http://cldr.unicode.org/index/cldr-spec/plural-rules
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({selector: '[ngPlural]'})
|
||||
export class NgPlural implements AfterContentInit {
|
||||
export class NgPlural {
|
||||
private _switchValue: number;
|
||||
private _activeView: SwitchView;
|
||||
private _caseViews = new Map<any, SwitchView>();
|
||||
@ContentChildren(NgPluralCase) cases: QueryList<NgPluralCase> = null;
|
||||
private _caseViews: {[k: string]: SwitchView} = {};
|
||||
|
||||
constructor(private _localization: NgLocalization) {}
|
||||
|
||||
@ -99,50 +59,51 @@ export class NgPlural implements AfterContentInit {
|
||||
this._updateView();
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
this.cases.forEach((pluralCase: NgPluralCase): void => {
|
||||
this._caseViews.set(this._formatValue(pluralCase), pluralCase._view);
|
||||
});
|
||||
this._updateView();
|
||||
}
|
||||
addCase(value: string, switchView: SwitchView): void { this._caseViews[value] = switchView; }
|
||||
|
||||
/** @internal */
|
||||
_updateView(): void {
|
||||
private _updateView(): void {
|
||||
this._clearViews();
|
||||
|
||||
var view: SwitchView = this._caseViews.get(this._switchValue);
|
||||
if (!isPresent(view)) view = this._getCategoryView(this._switchValue);
|
||||
|
||||
this._activateView(view);
|
||||
const cases = Object.keys(this._caseViews);
|
||||
const key = getPluralCategory(this._switchValue, cases, this._localization);
|
||||
this._activateView(this._caseViews[key]);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_clearViews() {
|
||||
if (isPresent(this._activeView)) this._activeView.destroy();
|
||||
private _clearViews() {
|
||||
if (this._activeView) this._activeView.destroy();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_activateView(view: SwitchView) {
|
||||
if (!isPresent(view)) return;
|
||||
this._activeView = view;
|
||||
this._activeView.create();
|
||||
private _activateView(view: SwitchView) {
|
||||
if (view) {
|
||||
this._activeView = view;
|
||||
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
|
||||
*/
|
||||
@Directive({selector: '[ngPluralCase]'})
|
||||
export class NgPluralCase {
|
||||
constructor(
|
||||
@Attribute('ngPluralCase') public value: string, template: TemplateRef<Object>,
|
||||
viewContainer: ViewContainerRef, @Host() ngPlural: NgPlural) {
|
||||
ngPlural.addCase(value, new SwitchView(viewContainer, template));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_getCategoryView(value: number): SwitchView {
|
||||
var category: string = this._localization.getPluralCategory(value);
|
||||
var categoryView: SwitchView = this._caseViews.get(category);
|
||||
return isPresent(categoryView) ? categoryView : this._caseViews.get(_CATEGORY_DEFAULT);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_isValueView(pluralCase: NgPluralCase): boolean { return pluralCase.value[0] === '='; }
|
||||
|
||||
/** @internal */
|
||||
_formatValue(pluralCase: NgPluralCase): any {
|
||||
return this._isValueView(pluralCase) ? this._stripValue(pluralCase.value) : pluralCase.value;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_stripValue(value: string): number { return NumberWrapper.parseInt(value.substring(1), 10); }
|
||||
}
|
||||
|
@ -1,97 +1,74 @@
|
||||
import {Directive, DoCheck, ElementRef, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
|
||||
|
||||
import {isBlank, isPresent} from '../facade/lang';
|
||||
/**
|
||||
* @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 {Directive, DoCheck, ElementRef, Input, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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': style}"></div>`
|
||||
* - `<div [ngStyle]="styleExp"></div>` - here the `styleExp` must evaluate to an object
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/YamGS6GkUh9GqWNQhCyM?p=preview)):
|
||||
* @whatItDoes Update an HTML element styles.
|
||||
*
|
||||
* @howToUse
|
||||
* ```
|
||||
* import {Component} from '@angular/core';
|
||||
* import {NgStyle} from '@angular/common';
|
||||
* <some-element [ngStyle]="{'font-style': styleExp}">...</some-element>
|
||||
*
|
||||
* @Component({
|
||||
* selector: 'ngStyle-example',
|
||||
* template: `
|
||||
* <h1 [ngStyle]="{'font-style': style, 'font-size': size, 'font-weight': weight}">
|
||||
* Change style of this text!
|
||||
* </h1>
|
||||
* <some-element [ngStyle]="{'max-width.px': widthExp}">...</some-element>
|
||||
*
|
||||
* <hr>
|
||||
*
|
||||
* <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';
|
||||
* }
|
||||
* }
|
||||
* <some-element [ngStyle]="objExp">...</some-element>
|
||||
* ```
|
||||
*
|
||||
* In this example the `font-style`, `font-size` and `font-weight` styles will be updated
|
||||
* based on the `style` property's value changes.
|
||||
* @description
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
@Directive({selector: '[ngStyle]', inputs: ['rawStyle: ngStyle']})
|
||||
@Directive({selector: '[ngStyle]'})
|
||||
export class NgStyle implements DoCheck {
|
||||
/** @internal */
|
||||
_rawStyle: {[key: string]: string};
|
||||
/** @internal */
|
||||
_differ: KeyValueDiffer;
|
||||
private _ngStyle: {[key: string]: string};
|
||||
private _differ: KeyValueDiffer;
|
||||
|
||||
constructor(
|
||||
private _differs: KeyValueDiffers, private _ngEl: ElementRef, private _renderer: Renderer) {}
|
||||
|
||||
set rawStyle(v: {[key: string]: string}) {
|
||||
this._rawStyle = v;
|
||||
if (isBlank(this._differ) && isPresent(v)) {
|
||||
this._differ = this._differs.find(this._rawStyle).create(null);
|
||||
@Input()
|
||||
set ngStyle(v: {[key: string]: string}) {
|
||||
this._ngStyle = v;
|
||||
if (!this._differ && v) {
|
||||
this._differ = this._differs.find(v).create(null);
|
||||
}
|
||||
}
|
||||
|
||||
ngDoCheck() {
|
||||
if (isPresent(this._differ)) {
|
||||
var changes = this._differ.diff(this._rawStyle);
|
||||
if (isPresent(changes)) {
|
||||
if (this._differ) {
|
||||
const changes = this._differ.diff(this._ngStyle);
|
||||
if (changes) {
|
||||
this._applyChanges(changes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _applyChanges(changes: any): void {
|
||||
changes.forEachRemovedItem((record: KeyValueChangeRecord) => this._setStyle(record.key, null));
|
||||
|
||||
changes.forEachAddedItem(
|
||||
(record: KeyValueChangeRecord) => { this._setStyle(record.key, record.currentValue); });
|
||||
(record: KeyValueChangeRecord) => this._setStyle(record.key, record.currentValue));
|
||||
|
||||
changes.forEachChangedItem(
|
||||
(record: KeyValueChangeRecord) => { this._setStyle(record.key, record.currentValue); });
|
||||
changes.forEachRemovedItem(
|
||||
(record: KeyValueChangeRecord) => { this._setStyle(record.key, null); });
|
||||
(record: KeyValueChangeRecord) => this._setStyle(record.key, record.currentValue));
|
||||
}
|
||||
|
||||
private _setStyle(name: string, val: string): void {
|
||||
this._renderer.setElementStyle(this._ngEl.nativeElement, name, val);
|
||||
private _setStyle(nameAndUnit: string, value: string): void {
|
||||
const [name, unit] = nameAndUnit.split('.');
|
||||
value = value !== null && value !== void(0) && unit ? `${value}${unit}` : value;
|
||||
|
||||
this._renderer.setElementStyle(this._ngEl.nativeElement, name, value);
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,16 @@
|
||||
import {Directive, Host, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
/**
|
||||
* @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 {ListWrapper, Map} from '../facade/collection';
|
||||
import {isBlank, isPresent, normalizeBlank} from '../facade/lang';
|
||||
import {Directive, Host, Input, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
|
||||
const _CASE_DEFAULT = /*@ts2dart_const*/ new Object();
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
|
||||
const _CASE_DEFAULT = new Object();
|
||||
|
||||
export class SwitchView {
|
||||
constructor(
|
||||
@ -15,80 +22,65 @@ 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
|
||||
* preserved at the location as specified in the template.
|
||||
* @whatItDoes Adds / removes DOM sub-trees when the nest match expressions matches the switch
|
||||
* expression.
|
||||
*
|
||||
* `NgSwitch` simply inserts nested elements based on which match expression matches the value
|
||||
* 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>> 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>> 2, STOP!</template>
|
||||
* </p>
|
||||
* `,
|
||||
* directives: [NgSwitch, ngSwitchCase, NgSwitchDefault]
|
||||
* })
|
||||
* export class App {
|
||||
* value = 'init';
|
||||
*
|
||||
* inc() {
|
||||
* this.value = this.value === 'init' ? 0 : this.value + 1;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* bootstrap(App).catch(err => console.error(err));
|
||||
* @howToUse
|
||||
* ```
|
||||
* <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
|
||||
*
|
||||
* @experimental
|
||||
* `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
|
||||
*/
|
||||
@Directive({selector: '[ngSwitch]', inputs: ['ngSwitch']})
|
||||
@Directive({selector: '[ngSwitch]'})
|
||||
export class NgSwitch {
|
||||
private _switchValue: any;
|
||||
private _useDefault: boolean = false;
|
||||
private _valueViews = new Map<any, SwitchView[]>();
|
||||
private _activeViews: SwitchView[] = [];
|
||||
|
||||
@Input()
|
||||
set ngSwitch(value: any) {
|
||||
// Empty the currently active ViewContainers
|
||||
this._emptyAllActiveViews();
|
||||
|
||||
// Add the ViewContainers matching the value (with a fallback to default)
|
||||
this._useDefault = false;
|
||||
var views = this._valueViews.get(value);
|
||||
if (isBlank(views)) {
|
||||
let views = this._valueViews.get(value);
|
||||
if (!views) {
|
||||
this._useDefault = true;
|
||||
views = normalizeBlank(this._valueViews.get(_CASE_DEFAULT));
|
||||
views = this._valueViews.get(_CASE_DEFAULT) || null;
|
||||
}
|
||||
this._activateViews(views);
|
||||
|
||||
@ -119,19 +111,16 @@ export class NgSwitch {
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_emptyAllActiveViews(): void {
|
||||
var activeContainers = this._activeViews;
|
||||
private _emptyAllActiveViews(): void {
|
||||
const activeContainers = this._activeViews;
|
||||
for (var i = 0; i < activeContainers.length; i++) {
|
||||
activeContainers[i].destroy();
|
||||
}
|
||||
this._activeViews = [];
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_activateViews(views: SwitchView[]): void {
|
||||
// TODO(vicb): assert(this._activeViews.length === 0);
|
||||
if (isPresent(views)) {
|
||||
private _activateViews(views: SwitchView[]): void {
|
||||
if (views) {
|
||||
for (var i = 0; i < views.length; i++) {
|
||||
views[i].create();
|
||||
}
|
||||
@ -141,19 +130,18 @@ export class NgSwitch {
|
||||
|
||||
/** @internal */
|
||||
_registerView(value: any, view: SwitchView): void {
|
||||
var views = this._valueViews.get(value);
|
||||
if (isBlank(views)) {
|
||||
let views = this._valueViews.get(value);
|
||||
if (!views) {
|
||||
views = [];
|
||||
this._valueViews.set(value, views);
|
||||
}
|
||||
views.push(view);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_deregisterView(value: any, view: SwitchView): void {
|
||||
private _deregisterView(value: any, view: SwitchView): void {
|
||||
// `_CASE_DEFAULT` is used a marker for non-registered cases
|
||||
if (value === _CASE_DEFAULT) return;
|
||||
var views = this._valueViews.get(value);
|
||||
const views = this._valueViews.get(value);
|
||||
if (views.length == 1) {
|
||||
this._valueViews.delete(value);
|
||||
} else {
|
||||
@ -163,25 +151,34 @@ export class NgSwitch {
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the sub-tree when the `ngSwitchCase` expression evaluates to the same value as the
|
||||
* enclosing switch expression.
|
||||
* @ngModule CommonModule
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @experimental
|
||||
* @stable
|
||||
*/
|
||||
@Directive({selector: '[ngSwitchCase],[ngSwitchWhen]', inputs: ['ngSwitchCase', 'ngSwitchWhen']})
|
||||
@Directive({selector: '[ngSwitchCase]'})
|
||||
export class NgSwitchCase {
|
||||
// `_CASE_DEFAULT` is used as a marker for a not yet initialized value
|
||||
/** @internal */
|
||||
_value: any = _CASE_DEFAULT;
|
||||
/** @internal */
|
||||
_view: SwitchView;
|
||||
// TODO: remove when fully deprecated
|
||||
/** @internal */
|
||||
_warned: boolean;
|
||||
private _value: any = _CASE_DEFAULT;
|
||||
private _view: SwitchView;
|
||||
private _switch: NgSwitch;
|
||||
|
||||
constructor(
|
||||
@ -191,28 +188,35 @@ export class NgSwitchCase {
|
||||
this._view = new SwitchView(viewContainer, templateRef);
|
||||
}
|
||||
|
||||
@Input()
|
||||
set ngSwitchCase(value: any) {
|
||||
this._switch._onCaseValueChanged(this._value, value, this._view);
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
set ngSwitchWhen(value: any) {
|
||||
if (!this._warned) {
|
||||
this._warned = true;
|
||||
console.warn('*ngSwitchWhen is deprecated and will be removed. Use *ngSwitchCase instead');
|
||||
}
|
||||
this._switch._onCaseValueChanged(this._value, value, this._view);
|
||||
this._value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default case statements are displayed when no match expression matches the switch expression
|
||||
* value.
|
||||
* @ngModule CommonModule
|
||||
* @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.
|
||||
*
|
||||
* @experimental
|
||||
* @stable
|
||||
*/
|
||||
@Directive({selector: '[ngSwitchDefault]'})
|
||||
export class NgSwitchDefault {
|
||||
|
@ -1,25 +1,37 @@
|
||||
import {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
|
||||
import {isPresent} from '../facade/lang';
|
||||
|
||||
/**
|
||||
* @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 {Directive, EmbeddedViewRef, Input, OnChanges, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
|
||||
/**
|
||||
* Creates and inserts an embedded view based on a prepared `TemplateRef`.
|
||||
* @ngModule CommonModule
|
||||
*
|
||||
* @whatItDoes Inserts an embedded view from a prepared `TemplateRef`
|
||||
*
|
||||
* @howToUse
|
||||
* ```
|
||||
* <template [ngTemplateOutlet]="templateRefExpression"
|
||||
* [ngOutletContext]="objectExpression">
|
||||
* </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.
|
||||
*
|
||||
* ### Syntax
|
||||
* - `<template [ngTemplateOutlet]="templateRefExpression"
|
||||
* [ngOutletContext]="objectExpression"></template>`
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({selector: '[ngTemplateOutlet]'})
|
||||
export class NgTemplateOutlet {
|
||||
export class NgTemplateOutlet implements OnChanges {
|
||||
private _viewRef: EmbeddedViewRef<any>;
|
||||
private _context: Object;
|
||||
private _templateRef: TemplateRef<any>;
|
||||
@ -27,29 +39,17 @@ export class NgTemplateOutlet {
|
||||
constructor(private _viewContainerRef: ViewContainerRef) {}
|
||||
|
||||
@Input()
|
||||
set ngOutletContext(context: Object) {
|
||||
if (this._context !== context) {
|
||||
this._context = context;
|
||||
if (isPresent(this._viewRef)) {
|
||||
this.createView();
|
||||
}
|
||||
}
|
||||
}
|
||||
set ngOutletContext(context: Object) { this._context = context; }
|
||||
|
||||
@Input()
|
||||
set ngTemplateOutlet(templateRef: TemplateRef<Object>) {
|
||||
if (this._templateRef !== templateRef) {
|
||||
this._templateRef = templateRef;
|
||||
this.createView();
|
||||
}
|
||||
}
|
||||
set ngTemplateOutlet(templateRef: TemplateRef<Object>) { this._templateRef = templateRef; }
|
||||
|
||||
private createView() {
|
||||
if (isPresent(this._viewRef)) {
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (this._viewRef) {
|
||||
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._viewRef));
|
||||
}
|
||||
|
||||
if (isPresent(this._templateRef)) {
|
||||
if (this._templateRef) {
|
||||
this._viewRef = this._viewContainerRef.createEmbeddedView(this._templateRef, this._context);
|
||||
}
|
||||
}
|
||||
|
@ -1,63 +0,0 @@
|
||||
library angular2.directives.observable_list_iterable_diff;
|
||||
|
||||
import 'package:observe/observe.dart' show ObservableList;
|
||||
import 'package:angular2/core.dart';
|
||||
import 'package:angular2/src/core/change_detection/differs/default_iterable_differ.dart';
|
||||
import 'dart:async';
|
||||
|
||||
class ObservableListDiff extends DefaultIterableDiffer {
|
||||
ChangeDetectorRef _ref;
|
||||
ObservableListDiff(this._ref);
|
||||
|
||||
bool _updated = true;
|
||||
ObservableList _collection;
|
||||
StreamSubscription _subscription;
|
||||
|
||||
onDestroy() {
|
||||
if (this._subscription != null) {
|
||||
this._subscription.cancel();
|
||||
this._subscription = null;
|
||||
this._collection = null;
|
||||
}
|
||||
}
|
||||
|
||||
DefaultIterableDiffer diff(ObservableList collection) {
|
||||
if (collection is! ObservableList) {
|
||||
throw "Cannot change the type of a collection";
|
||||
}
|
||||
|
||||
// A new collection instance is passed in.
|
||||
// - We need to set up a listener.
|
||||
// - We need to diff collection.
|
||||
if (!identical(_collection, collection)) {
|
||||
_collection = collection;
|
||||
|
||||
if (_subscription != null) _subscription.cancel();
|
||||
_subscription = collection.changes.listen((_) {
|
||||
_updated = true;
|
||||
_ref.markForCheck();
|
||||
});
|
||||
_updated = false;
|
||||
return super.diff(collection);
|
||||
|
||||
// An update has been registered since the last change detection check.
|
||||
// - We reset the flag.
|
||||
// - We diff the collection.
|
||||
} else if (_updated) {
|
||||
_updated = false;
|
||||
return super.diff(collection);
|
||||
|
||||
// No updates has been registered.
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ObservableListDiffFactory implements IterableDifferFactory {
|
||||
const ObservableListDiffFactory();
|
||||
bool supports(obj) => obj is ObservableList;
|
||||
IterableDiffer create(ChangeDetectorRef cdRef, [Function trackByFn]) {
|
||||
return new ObservableListDiff(cdRef);
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
/**
|
||||
* @module
|
||||
* @description
|
||||
* This module is used for handling user input, by defining and building a {@link ControlGroup} that
|
||||
* consists of
|
||||
* {@link Control} objects, and mapping them onto the DOM. {@link Control} objects can then be used
|
||||
* to read information
|
||||
* from the form DOM elements.
|
||||
*
|
||||
* Forms providers are not included in default providers; you must import these providers
|
||||
* explicitly.
|
||||
*/
|
||||
import {Type} from '@angular/core';
|
||||
|
||||
import {RadioControlRegistry} from './forms-deprecated/directives/radio_control_value_accessor';
|
||||
import {FormBuilder} from './forms-deprecated/form_builder';
|
||||
|
||||
export {FORM_DIRECTIVES, RadioButtonState} from './forms-deprecated/directives';
|
||||
export {AbstractControlDirective} from './forms-deprecated/directives/abstract_control_directive';
|
||||
export {CheckboxControlValueAccessor} from './forms-deprecated/directives/checkbox_value_accessor';
|
||||
export {ControlContainer} from './forms-deprecated/directives/control_container';
|
||||
export {ControlValueAccessor, NG_VALUE_ACCESSOR} from './forms-deprecated/directives/control_value_accessor';
|
||||
export {DefaultValueAccessor} from './forms-deprecated/directives/default_value_accessor';
|
||||
export {Form} from './forms-deprecated/directives/form_interface';
|
||||
export {NgControl} from './forms-deprecated/directives/ng_control';
|
||||
export {NgControlGroup} from './forms-deprecated/directives/ng_control_group';
|
||||
export {NgControlName} from './forms-deprecated/directives/ng_control_name';
|
||||
export {NgControlStatus} from './forms-deprecated/directives/ng_control_status';
|
||||
export {NgForm} from './forms-deprecated/directives/ng_form';
|
||||
export {NgFormControl} from './forms-deprecated/directives/ng_form_control';
|
||||
export {NgFormModel} from './forms-deprecated/directives/ng_form_model';
|
||||
export {NgModel} from './forms-deprecated/directives/ng_model';
|
||||
export {NgSelectOption, SelectControlValueAccessor} from './forms-deprecated/directives/select_control_value_accessor';
|
||||
export {MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator, Validator} from './forms-deprecated/directives/validators';
|
||||
export {FormBuilder} from './forms-deprecated/form_builder';
|
||||
export {AbstractControl, Control, ControlArray, ControlGroup} from './forms-deprecated/model';
|
||||
export {NG_ASYNC_VALIDATORS, NG_VALIDATORS, Validators} from './forms-deprecated/validators';
|
||||
|
||||
|
||||
/**
|
||||
* Shorthand set of providers used for building Angular forms.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```typescript
|
||||
* bootstrap(MyApp, [FORM_PROVIDERS]);
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export const FORM_PROVIDERS: Type[] = /*@ts2dart_const*/[FormBuilder, RadioControlRegistry];
|
@ -1,76 +0,0 @@
|
||||
import {Type} from '@angular/core';
|
||||
|
||||
import {CheckboxControlValueAccessor} from './directives/checkbox_value_accessor';
|
||||
import {DefaultValueAccessor} from './directives/default_value_accessor';
|
||||
import {NgControlGroup} from './directives/ng_control_group';
|
||||
import {NgControlName} from './directives/ng_control_name';
|
||||
import {NgControlStatus} from './directives/ng_control_status';
|
||||
import {NgForm} from './directives/ng_form';
|
||||
import {NgFormControl} from './directives/ng_form_control';
|
||||
import {NgFormModel} from './directives/ng_form_model';
|
||||
import {NgModel} from './directives/ng_model';
|
||||
import {NumberValueAccessor} from './directives/number_value_accessor';
|
||||
import {RadioControlValueAccessor} from './directives/radio_control_value_accessor';
|
||||
import {NgSelectOption, SelectControlValueAccessor} from './directives/select_control_value_accessor';
|
||||
import {NgSelectMultipleOption, SelectMultipleControlValueAccessor} from './directives/select_multiple_control_value_accessor';
|
||||
import {MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator} from './directives/validators';
|
||||
|
||||
export {CheckboxControlValueAccessor} from './directives/checkbox_value_accessor';
|
||||
export {ControlValueAccessor} from './directives/control_value_accessor';
|
||||
export {DefaultValueAccessor} from './directives/default_value_accessor';
|
||||
export {NgControl} from './directives/ng_control';
|
||||
export {NgControlGroup} from './directives/ng_control_group';
|
||||
export {NgControlName} from './directives/ng_control_name';
|
||||
export {NgControlStatus} from './directives/ng_control_status';
|
||||
export {NgForm} from './directives/ng_form';
|
||||
export {NgFormControl} from './directives/ng_form_control';
|
||||
export {NgFormModel} from './directives/ng_form_model';
|
||||
export {NgModel} from './directives/ng_model';
|
||||
export {NumberValueAccessor} from './directives/number_value_accessor';
|
||||
export {RadioButtonState, RadioControlValueAccessor} from './directives/radio_control_value_accessor';
|
||||
export {NgSelectOption, SelectControlValueAccessor} from './directives/select_control_value_accessor';
|
||||
export {NgSelectMultipleOption, SelectMultipleControlValueAccessor} from './directives/select_multiple_control_value_accessor';
|
||||
export {MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator} from './directives/validators';
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* A list of all the form directives used as part of a `@Component` annotation.
|
||||
*
|
||||
* This is a shorthand for importing them each individually.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: 'my-app',
|
||||
* directives: [FORM_DIRECTIVES]
|
||||
* })
|
||||
* class MyApp {}
|
||||
* ```
|
||||
* @experimental
|
||||
*/
|
||||
export const FORM_DIRECTIVES: Type[] = /*@ts2dart_const*/[
|
||||
NgControlName,
|
||||
NgControlGroup,
|
||||
|
||||
NgFormControl,
|
||||
NgModel,
|
||||
NgFormModel,
|
||||
NgForm,
|
||||
|
||||
NgSelectOption,
|
||||
NgSelectMultipleOption,
|
||||
DefaultValueAccessor,
|
||||
NumberValueAccessor,
|
||||
CheckboxControlValueAccessor,
|
||||
SelectControlValueAccessor,
|
||||
SelectMultipleControlValueAccessor,
|
||||
RadioControlValueAccessor,
|
||||
NgControlStatus,
|
||||
|
||||
RequiredValidator,
|
||||
MinLengthValidator,
|
||||
MaxLengthValidator,
|
||||
PatternValidator,
|
||||
];
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user