{{analysisForPhase.phase}}
{{#if analysisForPhase.isSignificant}}
{{#if (isFaster analysisForPhase)}}
({{abs analysisForPhase.hlDiff}} ms faster)
{{else}}
({{abs analysisForPhase.hlDiff}} ms slower)
{{/if}}
{{else}}
(No/Borderline Difference)
{{/if}}
Based on the P-value of this benchmark the evidence for a metric shift is {{#if (getQuality analysisForPhase.pValue 0.1)}}
{{#if (getQuality analysisForPhase.pValue 0.05)}}
very strong.
{{else}}
strong.
{{/if}}
{{else}}
weak.
{{/if}} TracerBench has determined the
{{#if analysisForPhase.isSignificant}}
results are significant meaning they are worth looking at.
{{else}}
results are not significant.
{{/if}}
{{#if analysisForPhase.isSignificant}}
A statistics estimator
(Hodges–Lehmann
estimator)
was used to determine "{{analysisForPhase.servers.[1].name}}" is
{{#if (isFaster analysisForPhase)}}
faster
{{else}}
slower
{{/if}}
by {{abs analysisForPhase.hlDiff}} ms. TracerBench is 95% confident "{{analysisForPhase.servers.[1].name}}" is
{{#if (isFaster analysisForPhase)}}
faster
{{else}}
slower
{{/if}}
between {{absSort analysisForPhase.ciMax analysisForPhase.ciMin 0}} ms to
{{absSort analysisForPhase.ciMax analysisForPhase.ciMin 1}} ms based on
{{analysisForPhase.sampleCount}} samples using a (confidence interval).
{{/if}}
The chart below shows the finish times (a point in the page load duration) of the sub-phases for experiment
and control. It gives a high level view on what changed (if any).
You can view more details about the sub-phases in the section below "Isolated sub-phases of duration".
Isolated sub-phases of duration
{{#each subPhaseSections as |analysisForPhase|}}
{{>phaseDetailSection analysisForPhase=analysisForPhase}}
{{/each}}
{{!-- TRACERBENCH-CHART-JS --}}
{{>phaseChartJSSection analysisForPhase=durationSection}}
{{#each subPhaseSections as |analysisForPhase|}}
{{>phaseChartJSSection analysisForPhase=analysisForPhase}}
{{/each}}
================================================
FILE: packages/cli/src/static/tb-bootstrap.css
================================================
header {
margin: 0 auto 1.2rem;
align-items: center;
display: flex;
border-bottom: 1px solid rgba(0,0,0,0.1);
}
header div {
margin-left: auto;
}
header .title {
padding: 1.2rem 0;
margin-left: initial;
}
header .logo {
content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANwAAABTCAYAAAGDu1kaAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAA3KADAAQAAAABAAAAUwAAAAB6HLccAAAvEklEQVR4Ae19CWBU1bn/uXdmsi9AEgIkYKBYIRsIRcIeFAmbta3iq+9pq+W5lNpKt6f9//23vNb2ubR9blX0WYu1tdU8a2tZElSIgqwikBUFBDGAkIXsyczc5f/7zp1zc2dyM0kgK85J7pxzvvOd7zvnO/vOWEgFSGDRHbec3t/4o4IAcLetcrcxfYjrnriqWPi5+dNPC7Ke//M+Ye+O7uwOUmc4x8/X5UVfld2Zsy1csoX6gLW1tXcGc/9pa+v3qpqa2TNJiU8E4o0YMeK5QNjnxK7ruhSVtVin6CblrowRZqsePSXvdnKPyc7jYiI34U5wqxLwhCkrUoRZuMdkL1ktS5KkO3Uvh7VVVTe2lGyRhmcsnEN6bMZC3aF5WfOhwj8QgqR6riPdoXlYrMRiyByoDH+5iEALC9OVr1ndZdXLeLEIU5o5PILpsYmTrtIdavMOAkRoLayxbJtEMLKHq82jDL2FqZ66JjILt8RJMzkOwRrLiqRI1srqSre+LtwJHqFxL2TsnoqZsvS7uxtXP9Y97F7A2l2/+vyFkulRDVPS/J9HYhxj9ghmc0tK7hXm7ug9YmYl+O9nzxaMiIxeaoV1Zb7gqqz07Lm8rogHugdl1qEac7NHP3L87L8J/pDXW1bT0so22VR1fVKF9TTNAmPat/aY7OWZovqKyl58irhZqyYyR2fn/ZV0gefDmR6VnXccfnaIKio6K+8QucVm5V1PulDCXQ5ztGlUdVH15FA9YwiBzKIqC2PKgubiwq8TLC7r6iKCU3WG6mm/U3XDj3cOVWOkZN3DaxIHU1l89tXTOBA/wt0Z5jGqEaqeXI7wcdHpczJSIi4P27//OW/S5JwHmbf5B6h2IiW46zqbL6FlrKrYY1RjaitzRkjjlTbteMRkVFlKy6FGEK8reesfxMioxsBfRdVHgP5QF1PrXEz4LrgS6Q5TquGOtj5RQLXc/qb/6NBxQ+nJjc9ePL47tC4EJ2g9EoxghzrGBvm0/lycQ4tJdSCnOPEJPw97vd9rVhTW6vGylPynbLtTNuRYT+unoH09OwY9gVHKRcrDj7WpDczNGtj0mEeWkH9qOs58eCJSGpPQ2uT1sqLJkzm8J7S7g9un2dIvALpmWkmicePHtMrUJWoHm+69ZbjgbNmdAGRF/+xywtvVsLpiVtzTk4UfXp/iR6OY9Z94Bfv+0ak5RkO1ra+4mQMBwYD61qIFJBgawKVo5MyaznCTPm0pKRwn/FjxA/0H2skPwUgX/gLtAgcRP9hcUjiV7KSopQ6TXe/XHNpgtvyGi0GT6AlafOBBFtnpyCAk4UDm6IT4WCYxBV2EzWQnRZ6dLglZTR87ffqdLgGTXa508mv1zz0E/LjCHBMJFHPl0iTSJSZ9BuFVUzeE7J0pIQThHpe9bGZ09pJrBE8rX8KlT9O13/EyF4Neg1dXMQ4yVISC9r3KwyI1txNsawk+YvIs9AgamaSwI+TphFJCfRIpknAVVh6eMbtQ0vV51eW7osk/Ar0VvZ7nyS/hUDmTVGmnE92OmpJdVbCyCLWxraZ85/jE9NncmfBqKnaZEY1UGxkNv0TgXUrD3yPDIlNrirfuSUB44GbiEj2r/4SM2asJ1meKGui9TavNbNVnjDoh3Kd1VX3xluMakxd0wrvPwX0aORH66ClLejwEE34vRu/TyB1vW1cwTLps6c73Znx/V/09R6wBnVVSUkR2mjuwwnvT3KeR27SlMoECq9t3Q6aQW9OhzU+S3hfqgnooogPcVYAaIzdFetSmVBeqXDdzxwl/v2hzf6/F6z014cjR0mOtrRmbUlPu6opWTzvNXdG7aHfKloebHy2YeuNXa3ZbsuU3Tp0uuOH4JwUpt6w5nVNcQs1An6g+zZYixC+uz9wXGIMjL78xLvOB1eZ8ucDtTf2CsmVPAxBY5vZv25kXe10uq/e4zc50T2l2B79fIicFdP2nL5xd2IyBaoPbzRo97j5rJvy6L92RBuGIiqE7+Gccr149Wr1pqxXXV6GwFo+HHenDCuWCImcNaDAzDWfe2nP5ullRT71ixaMKpdnjZk1uL2XNvN3ZWX0aDivvfjGjET/f14z6pbbs60h8Lun7jbpJAsj8FWiTzCpajKXITQwarbCYKXkLNU0XFQa8OpZjyncT4ZMiXOGPA8KkMS37C89YYM8Bh/dQLDDuLzpr8WfNJVv4Qhn5JXfBG/Pt72KQO4/TlKQ7WooLn4/OXlzcXLyFr/USrowpBJ41MbgsFGbyYBKZkvcwJ2D5oSE8WWkE7YuY7ghrFwi5ES07esyjnyZ3Uj4e5lKxJEvrCSZ4Q1LJYjHA8GH8YsFgu6RLTwrciEj5Vau7MPP1TZ9FRwTNjgQtrhJcZ/qHpIPJBtKRtKdoCE9GSA/TVzyQcuP+LYfBzCzD5CbcCcdOxWQtWWmFY27gNpK4NQURoCbQ+acVD1Kd21RSkC9gtXs2N5AZg/wsq38eGFrZcGBFg5SsK3wVpK2qqtEpsZyWQ1te4HDNs9yped9uKd6SSisipPjKic/MAfhxoovM4br3XYeuvGvgYbHXtxAsUoVWRiiATl3ZEJ+1aBHhOZnil3JEBxNRWGVxZ8RlLPyNlS/hByoXU0pEahJ9HjmX5sYqshG5MK2VubQ2Fq67X3Z6G3cLAgTDdw3NpYTBnJg+p4EWkcmcgDkQzIOsxbyGFg4hGf7b1oXrrevIP9EXuCPSZ28kWJhuLAuNSNRWuvTW2wjmUttIMxXRIdVQWjTBqbWtIl6kwvW2fPB/hlvwk5gxZyGZnUqrALEw8OSRkzSFSZrKHWRkRBmTRZjo+TfSk7BURCvZDswY0+RNLT5ZwoyYpsRygq7YGPIP+88k4NDaFfljqvYyU9WXCYfbocP9LllTluXm5jqJH9Ft+qyltbr0vVsID7DbBD+yC39kBt9hEpiQGWG7ienKafLPw6YZsZKRL4WisAxZFZ993Xgxvbi76Z57B2qZbKAEyHPlQDG/GL71xf88TouxVhqYp/2L1X4pm4dsX3xX/er2usMmhWbFPz1k42YTnQ6gfhmGd+DaC4AYZ/LRGMfoY6rexhQ0aAoaaRW9B0VvZV6t7QvBWNBYy8HY2h1ZWY9TdXvUeyKVpsaD+RlsbkM24QxBagGTvWi0qZzZlLXlR48e0dDAq/igV6uads+CsvJ7iMJkVZs41GYChnjCURpZm2mr2b+MjImNPeZWVeZVVObBDhG3psMMnWBMm+iPPfhtQzbhmpSzE+nrkYjRKmqKIjV9eiYiPGVU6xDuVGNg2I+qJ7O4XQXrtOO5c7GOsVX/8q0NaS/9z4ITCgaGGvOgjWtjbr0uKZAX9mKlemSJeV1hbNhlKVi/11mbLjMF1eonqtKjGeauwtYfS1E2rUFXwRoc7sZe5+RjlFAqpiGoc7L5zdMJGzeeTXnot6Obc+Ke4ruWRGhpn9nb9z2SnfPzNcUeVTOqSFSVHgxmW73eITfb3a8lTgixN3VrG7f42oSaq6+NqrHrVVIOnferHxZTG8csMw+YaOvN4IRo9UQC3Zk5uQMljtaUxnz1jhpaqP7y0Y8LRl6/qia3rKKgLxesexKPnuAO2aqyJ5Ek3BUYDmBrHg0FmKZiWIAWUUVpo+EBhgZDbjjQ0/hfEvg0AB/cZ0kuCTGHImEngUFVVcZNXXG5ontvxtKa5HDIWxoPFOyyC/RggGHd9h2s2y64mLBg9XC6JMmZTt2xvb5k48c9oeW/QG3jk1bvsBhdjr6XuTAeiLZ8UrwzPz/fWPTyOcZk5X0H00lPWXHFSqMVFpW9ZBV6ec9bYRYzLXrLUVlLl1oX1i3uYq3ZXKC3ulnNVt58qRZLwsgcKzVVexHN3OUO2XlF46FNHwk/hIMjX5tlJv2XgJHeWFy4nXTaKo9l5imYujmOFdgJBLMq2jqvMv3vkXJYqtg+T+5RWXlY+dTD4G9P9Ij4RVVF+U2xU5Z9UdPV9zC0fBTb7x8x8Dj/ErGoL2jTGj3fjS4ihO3v6ZqilhEC1sILsDgPYXVUtAWfmEVNXbIMrTpfFd10uKEAmNcKbJq4rfCc4ImG7fSHEcFJ5EbCEPx89r8h0b7K/WFsDAGEcbPvBzsj+BEAKwxmc7dDANy0Ch4oFeuRKN8kBwjsDiw5/4+JxCSvr0R/kcKFrspP4HZ7uztMknS28ZCRUH5wiwVV1mEjI0g7sINinsWpg9HIDPo5JEYyOYrd/74Mk9TBQxCA3zhOQpLbKsSetjEgEVhLdV1jdOYiHB3Agi2yJP7rm4q3mIlG/g+7P/bwOhiO2DYyOR5VgqJr7xNydNa1G5pL3lzB+fgSDRkFOzne9Es0cm86VMgzEkobH3sRf6g7wd/YzcHpv2mwsoQd7kZEEG5yxLe7qWSLmWh8BVlnaVE+PB5vTfcTHMEkVbsR4c0VUiH+iM94CgSNBcGewr2MrDHZ1xYDF56kPyJhvkkwqzzjsvO+ocKPHBGTTm5dKV+YsiguVv46VtfJzUy4MGwG1Hz7CVDv8nMXgjjta8D2GW51hkekKG73KeFWV759mDCTPjxjfqOOZRahhqXPQ38b+yEEgLHldGr/fNm298J1z0Ogez85ER5okZxNNSJz/qra0nd/H47lGl0zZhaRyM+dL3uX7y8yEWHg+x58vjHVvAQ9faoFoCR3Xfm7swyz8Ut7KQA/AbiRCLAR/2Hp8z8AjB+r9eH8b13pO7db/QpzGGZsLON41lT8Jt+nhPifcVGctdZtDpKZbLQgDcWFfwSPF7ExkWQXIeh0phN/ZIyS86Xv+t11MTxjwWpdHNwhz2E0z+cTuHU2gtywowU5jDITJONxNJCAaT8E2WkfBarCZ6or9q5OmpTzS11tM65JkKQ/VVfsuZX8C0W43Kx7dkCXkAA/GT159jqFqScITjRJp7zKf1U32X9P55cp4Th/nd0JPF7iyE7HjgnX6Qs7masP7y2EJiUZNyaE870bsmPR2fJdb5O7i2dQKQHhQXWuZwJ1AQMsIznyqqJywgA98MQGlJyRwPEFiofqXMWeewx3bJoxgsnxxQ/iNJrM4M1rwnDMigpFcgO9KpIDgu6BTO+QnI4DuqrhhDj7NUrSfecq9vI2jvhjL5fwauou1saw/8y0DxrDmOw5V4zOzOm0IzRoAtrNgIy9cs6YYKhr167tfC0qmMeh6mbdX4Ljr0VDNR4XGu6hm9oSW29GWmcLTHPIMHQk0NXGoaETk+6HdOiWuIA40iHfvjwtGcBuwK2XTMJNdKVVOnR1w4BLtJ8C0LG/2U+ML4bNweafvNaq1H+tMxroRf8tJ+7pGzpzvxTg5gB8qEUmJfzKQkxI87GnsZ+yDbu13JhEoL2VxiGDzuJEC6diOx71TpuLC27uDHewwod8VYlJHj79ZAgYRyd8MyzdFri1d9ptTwOPOHQTDil2061FM2hGxbrNTpZ7FqXmQwU0yzLk1JCtKknSr76Uuw9bWvkctCF5TBHaJAFVjQlRkUfbtyqoR3PLK47QpC9tXdiZmXm5jbdBDerXhAvc63ihkjmj/z5N0iLDZc2Jfcwu/kf7KnVM/+EeRrRzqtvKa1nlKZYeE+v20K5lfB7MsHuw28uLhPMqWq/uqaQ49ce+yn5NuN6KEHqVebo0LFpzYPM4OiNerD5oEibJ6dAHdVBYW7OVF0rcs9W6VkmbYDFdjYTDZlhUsV5KQCSkFfdCM1N/++tZg9DfoQvCzzo/jg3KbMub1Qk/WHM0u7MI0biH91vgEasZnHLJjx+dYTfDH4TtoHHq1xLXW7FWNfcXTisH+PVLgubsqxmbfXUSHbEajeUQfkeucBM6Jeqpd/YmJM6dVkOwrEd/vI+qzKGohmTCOeTwYymuyZ8FGcc1d0wMSjaNVb61MyVp7vQa39peR7QhAunXhLN2GC5GPrxzokeHbys6Hv3aP48Of+zh6ZU6loJxcArbYNrQYYnt0DlJkHQc+sDy+4Pfr0XnJNWLe8Y8Tgd84IpHm4vBLyZ8/dFmDtkprxHOidH8JCquifCig0Indbz4NMyaYOYEhz7ap7xoOOA5V8NcI4YxrDh3SBMxi9LBYRAD+rXE9YkcaMDta6a+dVv5jOf+MGGfHR/n/vKKUXOmV4WNHd3qwbEqOrHD9VAbZyeuPoRZdupQr5K2YbywfvI+Gh7YDcIzv3HDSTd2ptEQgJTY/NSHIexT0tRiD13lq+ite2fsEo0iSHMqfDYMfnzb/IZuvBHyfm3jersTICT/oeOBZ69QH+ywZU+4Y+bk2Xkjk8qoevSbOaFBOEruc0lJTwjc3tD7o3PSG+EccBpdbV2gzsm3zpwpGP/NH5ye88f/3Xf9xx8XpNy65nT271/eN6+0zLf/csCj0aMADNnOCd/lhSWZ7s/uy2zugz8sdvumubJ/ek8x3b5AU15DUfVrVTlQAhKrAwotAaEToyLxxJ0nQ3V1YKBkOaB8KSEHNAC9wHxo9yp7QQBDlcTnOuHo1QfrjuihmoihcIckEJJAX0tgUPUq6Vgx9mll40BXCbr5v8fSy6DtROB06XScfN1/oQmUnH1rdKN0dqHEHJ4Zw2duLSpa234eqxtEg54Bx9TQb3Em+Yf8qKwNMQi2CkeOR9o44artxX/BPpyvC7f/uGG2A0eKbGekcPf3CUweXiZwhS6OBHfGH3jPAudueqhU070lwp+fLvkf8UWb9igOBv7IikN3mlszCY4d78Uk2QwrDpmBswG415GZwhR4bpzgQokwizgIeHTWsiy8EFYs7FZd4MLvOsDvEnYrjqB7UZ0TTNQm4Y7yD6yEhdmaaAR79G87/yzcrDoPiE2iWXEuyow706OyljwQSMN4Yk7iByQRB3Ec2w/NIUnzrZ/kkO+zIqia8iE9oG2FBTPHZi+9TiQacsqNlDA8cSTp/wbzZ+fmpNxGDtbL5gWM4MhlpJmKGPFrLVQPv6EAiXel6egzRE9bkq17/QuXLyH9dgwj0fyWYMDrJVw1/htZYWm4seD1QLpkt4aN7IHhQ3A34wz2suisJbdhYvkPhIO1g1/g50Fu9P00HdhMZ0/LgTcBePdb3YRZ3LAg7HZ6q+apjJuVl9Cwq7DWzt0KU3X1Ddj5TRJWOA79/wp2+rqteBWByPu1JWQXnx2lhoMbjtjBTZiivy3MYGAemqcEFXCf/iVhl1zyFCTKN5oPFB5qLCn8BzKIbW0gwiV04T9Qby4pWB8Is7fj6ooLVMgkL5JXpUmvGTP9uqhgZMTLDbFSMr8aPxhud9wuaK4SufT79ltPDZYohYmC+Y++NuvuR17beQe3GwnKbzeInb54kopj1EI1f1BgW+8Ld6GLOp7sLqdzRv2BTe8LN6uOB7bzfZu5CNxmdSMz2rprEIe5COsPkAkKAt3JbuVFduCtQeZ6nMykUIu4kRAxjfrZpjqPuxnXhIQF3sxuYAKX6cvJfLb4JZv9MALLXw/kb3U1E856tYMVgcx4TdMH0plxFQVd2WAoyenM9Rm5Fpu16H6655gria2hDgmukTDsmmYmKPYZXG7ylNgnBoL9r5W/QVdwF/i0cwQ8KZw6W8qvreBmIxyOWEd7O8T36CGEuvYWEsIgoOtLUmetjKzclc+fnJDoUQgoVDsnSBfrd3jkoYHspIgfwSkhqKpUm7QauiYEGcGoxUR4DHSOz8PnswfT6GEL0CEKXAn+FDkDILVfl4E3Rziy4eL/S2+bGMrnERttkP3UxtKtZsKbPjT1v0Qdh+c9eO7EubW1CMhaYhyflXtffUnRw2GS8oEHt9iRgvwu44ZOfvz4S9K7kCjHdKn0VitKHj6Fv6ciEoI7Y+FUKqkv2epXPTv5vhSJPz1CWHQgUte8Hzc0VlMHhRNwYtGVxNZQsm28QanjL171MBZm4UTtW8KUq1M9il4Zl3k1Ne4SuYt8Qb7JbgqeAEGUA9t6STINpdt8EWpHjs3IRdAs92IQYQcCTF+gokQ1PhUpreEAIX+kxjEiYz41+qZKzl4c3Y6r+O4OmafLmnet8C+p6kPkoebQ1lMCl3gPy1zwPZNQgEHgIQOwxtJtC5Bh+FddvO1DA9XDn08hPKeubhb4kurNSszMNdtRwpVR4shdKLq2HnR/ZwjWgOLtYmxlb8cRuFadaMi+bRAEp/iEO+UrCE53phju7TTwuM5fKfy4u2WslY6dmZ5v6Yw/0eC0hUd6N8buJSByJyL0kQfc4zFW2CXN+wDuKTFLi1dp2iTwOHGLP7JzHuBDCUx0kQl+bcI17+N4pKcW3y/x5dMDxIRDSvAj3MSMWTeLD48Q87EW3dFCgic83CzFYqSoBMFfU937UNrNnMvDADyrgjCuJbhQTsrE+IIpEU8rTvWhtz9yOtl0HifwIByhzpdtv5l44K2ekytXruyw1SwpI3eUwDXSod2vgJMu4mVWdfzxH6OmsOJxsyiFVO+Sckr6LTD/iexePDINEBcMhDef3KlOrjpsXBxDdlIj02fdpevqOjJr3rpN0BZUl7/345HpObdqmsrvtoLP4YD/H2sVQ/h+/FX2cnvbxJ6F8z4KhHhkiHhXlhfWjpyU8wZW375M/sGDpMCFheuz+VocXU4DOvGok27QNG8kImAOTTB7Ayo6Xb7zFPk3U13SS86V7yWePEyW/UoE4qq6ePsHozJzFqqKvq29lTLcwmQtXVH18m0lnyig/QouwHlSZo4wjAd/rast04BlyJFX1YafwF9qf9GI+l3aDxxKGDOjm34ogYxEM6Jwrnz3nwFqJAHSlzT5qqakjFlLSGj0h/xda3r2Gc6V73oWfkCHfzyByQm0kNOkXxtoBn+B5/PKebfzB30g0Bd4FI54CzGfO7z7euEfuozE20F23jeBDv/fwSHIW3ATXSQi8RZuI7qK3ElRglAYoL5DHxZd+Yd+xwoOxY8df+H2WenuIoTtK8Iu9DOluytcUSNR20gN8P8vuLlvh6J6t8I8DXI0nw6jMPr4C68W3Yi/BTDwxivmzIkdmTlrdtrUXL/7wQY+ZBcWgqT0mbdfmM+ufRlFqGu8EEYvSYCv/+n6KNR3D1n3y9CF4LjT6zEkSF1O/NPUZITUJSgBs29yCcZtUEZpKF7wMCgFOUQDJaY4hmjwQ8EOSWBoSSDUwg3C9KKtZ5giuB+Dy89CLeIgTKCLCFKowF2E8PrKq29sV9hX9EN0B04CoUmTfpQ93dToYBHZ8Y7UY/SUlsrnujHfjWVEuoxMxUWAtKkAe4nwizsIMF/K7yIgV83zBawqFFuPtfdj0EOsekkCoRaulwTZIzKo5rAeEdQLX5ag6hAGGmhrF1k10qNrWGsY5sRGEnr5XDAPdV+FJPpHDxW4/pGzPxcUtmPH6iMffqz08syMuLpv3zXxJK0Qyrh8k1q1/lSh7mt/Spu2e4TUgEhgXGps2/Urxp66fEJUS/tyfHtho00J2DrYHjZq8rpo5a798KPXCM1Y3W/fMEHHpj2qFxt1YZDk2+eWls8nwhyXfng3Fk99ZWVe0hePU0wHWl2yBa6ysvKLAy3cQP612vMxkuxwMd0Z5QqT2LLFaa26ruB1OiVKwoiOCgbtTzJKFs5d0U4k7NsxNotrLpwHiAkWrxtrz3/tS8kjt+NxZWwvhS/sGqQta3STsaZF7MGRBVxvBruuJdFd4/Q4M21pocuwGlR1XjDagXHpb3tqaio/otLffHub3yVb4AZjAmHSpAn7z7zY+olWDfsEJZoqwbEJCSeSYMKWcBQwulGaCp0xaULohMV0Bfczak3B4oU7JOhoRAv5VtAaUmEjs+oz085EKmBefNQK0i5pFXvI6aIQUsFoc4TQz0VLILTwfdEivAACyN80XkN+D1AoBYB//HFL5L1rPsp+4fnT4wihJ4lk9DoFbZ9PCx8qgKROvrpxXMWDz2S3Vp6ONCCh3/6QwCXbwvWH8HrKgy7+btXqJzYpZycG85uQorOv3RTNxo51jfZoLeZTo3iHvNvn6czxnqWwWXkm5kyrcqUkN0eNTm6zP5lgxQ6Ze0sCoQLXW5LsBh2Hw1HrlFwteJCEn5mkJo53H3mhILNROpxhOsuZJZJGQLVIWdI7HMOwZyvaMdzQv31vwpH8zRNccbHuK9d+lx/0JYpRaWNaw8Ylt1KX0uh42lMKQXtXAiJVe5fqIKA2GCcAatXnvZFyfFWcI/WkitNyHq8ibX2nMn7C+MjWtLQoN8GoANLCNy2K0+K45oN59bZxGOV5g8ULkyYsHIfVaezmQDnS0aOcMP+q1svmzSjTULAUXY0yxnMy6IIDzlbpDhzchBvOprFgtAc6SS+V8eUlW+AGYwKZkybM20ITJZ98Wh/52hvHEzMz4uvuvmP8eWPShAoafcZkCenGJAomTVjXkybvnz0370ILx2CU2YXGZbD6u2QL3GAVuBkutEATJ8S2rntiZrHY2sWPvvoQTnzcFvnYEycvT8+IrPvWvyeeRKkzVgtMAvaGSFdYzZHfvhDjqWuQR19/beuwaekoqNTctc/R0DXGRI6U6aZrCQYk9NuXEggVuL6UbjDaYhFb5Hzgto+8aIo+om3Z8oRTEyY4+TS/OQkSjCbcJo4Y9v74n6/h63D8jnDA+IuiWIvj63BgQjoVOnGXuIKL31Eq87ogHXLuBQmEClwvCLHHJGhJgK6yQaEzuosGBWNrlzFn6MIbatcsGo7nCLEex9fhgCMKaRCGZvm14vrM5EYLBfz6Llplh9J9OreEfvpcApdsgRuMEwC006RJO9vlskBnqS7r4R8HixdNmkTwSRPccUOtGIoX/dJEJLWeVJRp0sSBkkuboang0QI53b0TmjTpTOq9C/fVfb1LNESt5xLojTtN6LWSrOSRhXigi2YkWemLr4+rPXxs2ORVNx6JGpvSSjBya/ykMrLhxKmo4Vdl1epOp867lJqWNxRfqey5pAfWxyXbwg2sWINzp+swFUn+4iTnuK2dXR4bnEJXrtSeYRw4b0ZVzLhRzdEpo9qsXddzOz9IavjoxLCIsckt4SmjW7vTVe2KY8i9exIIFbjuyalXsVTJsQKbGmcf9Z6gDbnHiThWxt5Bd+8dZP6DvcIM3cj48Smt0ZeNbqW9ksYCN63QMZb29RUncR7vpJg0Qd8ypPpJAqEuZT8Juj/YdHo8B8zRnbxSV9UYh+wox824mIwxlgaMORNabA8dz+mPNAoVuP6Q8iDg0dmJ70EQtM9VEEJdys9VcvtHlo8lmfwrXPu8s+nQ5if9XUO2vpBAqMD1hVQHIc1dWVkdbnOe6EqrPKycXI8lhEvicOcgFHsoSCEJhCQQkkBIAp8bCQyfft24z01kbSJKDw/7zmbZuHYBwnUBj+Nh4vsJDQ8NH8Ds86RgXuh6AVzRUYOZmsN4sHNT5PD4P1QV5TcF82PnFj118bVYw33Dzg3T6g+1FG/5T1u3LoAZK1eGffJRw52Ixz140uaKztARjzO6pL8OPvS8DlfRU5Yu1jX1H8LePV16vqWk8LsCN+bKpemaou4X9s508Kfn7M7h0cU9eEkwv+Vg4cbOcAmOtPkVZiHXCBzI3w17M945Oon1iA+cuvynhuLN5vM9Ai9QxwPR2zG7OT0Q3rld2oQnXm+0uuNV0UOgkSJJrtym4o2lVrfumKOmLMaSCntV4EY5IiZWH3jjtLAH02OylqzUJP3fsCRD+0axKSdASZAJk16SHfKTvmdy/RDwIPWTkPkqDtSlUqSd+eSSH6LFgvJ1J/LkYwJEY7iOjIVr1zo9h2gonY5iBaeFiegICCsWiZ2GTL2kuab+CSSi6pSlWQ0HC8yHvgRJOz0+a/lwr6r8E4lG/Doqna1FgdzZfHDLmx0d7SF4c/lehOk3xw/Xd3i5zc4H4jEa8fg23MwChwu2yG8PZam3y48YqfQCl9olDS5HxkYgDJOwb+ubSFSkKatImxQ/tSw/H5ekBCoZFxfhXS6fQlzJjKel9BTs65qF03ffIRrYy3lvU3HBEwKvg26ksUmng3sAAMft7NIISc8SdLzMjrSvDJOleXUHC04EeO3U6tAkB26AMcOga8Fv7KR7N3H72euQVaRxN4yPNL3fiEdpcTFTI/RkyHQ4cPCWmH43Kr27UTHch8bkEf+AIL1Ah2BoMLpMJ8KDTJ38TTSyQPX9pIks/Q5b012olSegdVuI2sU/U8OOA5B7Y6Ys/nrToS2vGMHq/NfLvFSQ7BLS9EStHwrmmPqSjedNoI0hLnspnvxT30Vr5p/xbXAHFIRXdFGg/g7ZJaJkTUWOzQwMDzLxZFQY9XiFd4R4hTcQh9tBC63OeOE2fPrKeLe3oRS0U5ExHsdzWmPxnsGPhXtnOl4lXN98qPD2zty7BQdPPIR7HBXehxKLmt9U/Pq5bvnrBhLSf4LClIMokLEcHYcNcWL++40HC+lFR4jLXiXlroxprm14FAWrT5QTD6EjLduVr1tT1g4xTAhkAd64XhoI78oePTzufmu3MSZ78bex2Pp0oD+0EN8ALGiBQ8I8hsJh06WR3AEtXoSvYH4pkI+wx2QvvRGLwfnCHqCfxIvFtzUdKtwWAOfW2Oy8eTje0pnfQC/PQcZ3BQJ7Yse57Irm4i3ft/pBDbwNtXKuFQZzxPnmxpugvxgA79R6fn9+fUxW3kPYDPaUgaQt6hS5tx2oldHx3jC67zprPosu276IKHlR7Z7NDRfDioYcXtW7RdDAHZ8PoRL5CdmRjwXYVvfl1W/bOvYCsO9buIBA4hlRDJqRvAEKFUplAMjPGpOddwMy+b1+QLLI0k/RD9uBa9+2BrhNRwH9b1QSfhmVcOgt9trmBttMiXdhf9lUXPhAAC0/a2Nx4XYA8HRrd5Q0Mi572Uw7zEiWUErvu9u5BYNNn36n67D3E+rW2qmgcgz0ED1j2Si9Tf15O1w2xxvtMBuTxtJoTGTjYoJkh/Ngw8ENR0yAYWjP8Tp7YWTCmHuras9sQqFbiEpzRluLWo/K5K3RqfKKo5s3oyLtmZq4dGn46VPa30GLe0RL/Ahul+aFrWeUusDWJdewqUvSusBiHl1PsKZTnxc4NM+lGLRHohuUhMChLe9Y2FDtfDL+irjvlpXYBz9qet5ovHfRsfWTpL0thwp/Qb6QSL9Fjf8DKwUk4hoU1B0oQK9Z4XUtDTnoQuEm4gAlsdalV8T9LL84AH5RVv0riq58xY5Em7NmBuDvm24Srmm1KUQA5WK8cxzCSwBCbIXnhOnFakDl/QRq8retsA5mXU/DeM3kolvu7JIk+V/h/y8d/NgAqHWlz8bJBOmadw0sj5sAw2DyJuuJovW4SpNdzbu2nvq3YP4S6C46Xam2IU3zl02Kuzk/P18lXK6oO0ijwE7U2TPqUsjQTFunHrauE9SLAiMQk6g73FMiHQpcGCh4zOfgqTKiyGEEhr9gSkafEIHwQ+G+dXaZ8GlQEnUPkCVpa5jsuLf2UGFZWZBM7vCoRSg8uO5GhIbYSFpERNj1NF1HCgPcH8ZmLVoBvIAbl6VXEqfnja3eX3jGwKRGUWmiRKM4UZhF3OA3YttpNyUWDaR7pJzYAUwX81iVf3jbw25KCcesrcqFTY7eABrc3Riop9nRQ+veiGmDPw6LGfHjzsZuMi6dNdKGx7URsf6TjtIF2iPhkAIBTIOOrp3ycmzmNS8D/s/G0rf5y/bW8NEZO5HGKNzrG0vevt3q3h2zzC9FMjCt3Tvq2gI6Y2Tml5NbpeZ3kDxXwL5yc0XtyrjsResait/ydfNUnPUzKm1/6floKpoXl8QbFvxqckuvjc8dSGNR1pEW5SjW801GnRla5VWYsHqYnClUHQocihsuBjYyDnV3iQHv9mLGI5iS6QhjezxNVKTrS5glutUE+AzI5FvX3Ji7eO3atTZNXjt2fGbu80zzBhQiHqZT7hZv/rCMXLSZBmNkLC+9SmNNSDg5vG5lGyhOElQbDr69Lz5jAe2u8KNLZVCpb3gDBe9q0LCJjaDQUXdgOCLZFRbGnqsve6ebYzgPMpPI0lTNGSJFyUAXVhoHl8usnBFO5pCl+xpK3nmmyeoQYHYibHSlAinIvaaurGh1AApLzl4c3aa6qdqbANTr4jNy/1JfVnSzFc+axpjdszp122xkWp9obSR8rvSNsyA2adjU3DQEezvCnYr7lu5Get2NsRh6M8r74s0Fij8E5sd7WqJSeLCa1cFtGDnA733QVvkhXaBFpvQVcsTd9HV736npitTwjIVNfHbUh0i1nL/yINERU/qoZjR1s9XzRxc2JwJDtZcDH+mGWWWotVe7JG0F1Y7kJnRZ917z+CtvKklZ85cJGoF6Qsa822TNu8rwJ2gauqQpY8FzrqR755JOH2hmOFH8THwKP4VF9V4xImPu81b6EQ59Cfy4eRx5uERcvbnA1Qg/cc6XjRkui8dxWXOHJ6TPeWb45Dl+zZmTuW3jTxmsJ8ovPL5wIaGbUEjScH77jzw+FCd8FFdcmf70iPS5J1Iz8kZ0xkdCBUp06ZPQN7dTZ4u3NKPl38hlR+mvK1mBeA4fDU4Lb9ldiBJ5hPOxr6A42bqDRSfqSovGOhxSFnBrCF/SPf9PVjz/EPmLxz8gEEVFRQryWB7h++L8LchnEyr2jnk9wG9XVmv8JdwD0x1FlZSR543836GFCwMVBYLlipo2TClSEye6XZ0xkRBBo7KhGkfUfuTXyapLt28cdeW8kWqrtxwVY6LZEoKo7lU2Jqbn7EuWxs4tK2tfQ0rKmDVR1zx/oOsA2ptOeJD0Fly4uNUIh+BFuoFF2KgVp0JLJQlThcQlrbJVSRk526vKdr8IMDtb/O7xpIzcRFlv26VpOp9m5/hw86XMKlZ7blXiJMx38CaeaOmsRYFsoAPHYErESCEBbOOvS3cmTp55J8fxDy4xera6fM/d3A0/Yehd8Oc3zNbD8OBjz86X7fhmUnrORrB/xfBjErysTa+rSZqc80BVxe5fCnpClzXf0hyEY9cyjcycmayp0pNMUVYSRYo/Zml9s5aCCmDiHhYSqhnGdvfumBz06KTo0/gEHczf+eKiUrgnjsyaPUtX9S1o3WKs+SvCpkKrKdu+N2XSNYke1rQX2XcCor30qb9uVpPSZxbiLZXVZ0t2fNwZz6SM2Tfi4YWn4Oe35yp2P2LFQ+XP8wJvWc08bsXoaMZzLO1DDYitQ4EjLyKBqemm7hlvwiklgigEkOOZ+LBbu3afHdheBVCSkWE0o1XjiQbCGptxlp10j0zPueVc+e4/U230u1c3b6OMTWExImiEC+abqit2bQwSFAZhJ7il5s+Qsnh/kIfMQNfYehTk96rKdh0lQFVZEfXEspKz54/XPZ6/Y/dINu9Ikhcov/jDzsEUJu7q/+OAVbHKi/B9shNyoM4ihcfsNAas2UoKTQiQP0PYFG+eFhbZV5XvfnXslXN2tLUpONemx1vlA7QHkybN/F54uDyt8tCuU/4hFLy1tKTJM30MKJCIJ+oQChWZUQZqnC7nnNPF733o759sKCkGGsz6baBzmxFGQyLCTDopDCc2oAK4jlt8P4TZWfyteIHmcyU7dwEWOzJjVh7SFd1+Fib4BOKS/dTht6m794VRU3PS1Db9H+CZjUoiT/V6jiHchhcKphEgw05marl4dPiPAbf+UhrD3omrFdMwo5ISuBTvDgXOFabVaW5pPWGLjMJ1iXUyh2jQRVq8LjN5DwkBugEEp7hxkd6qMh8ONGSY5Sh0N6HYL20PtggSW4RaOvXp/MIzyJhvkS/O20g/2OT3uyps5IeEPXLyzGWopv+VbqWSaAMHlHFDlX4bjA+QXShq7WCeQvbUKbNSPF79DkTjOmTmaQQTcuAZSpaPoAYskBwOv0KPKZ1KWZPXB8aftygieoiHf3wkykSmcrq0OhWy54WMoD58LHn4yf7TA++dBp/hyZk5D+uqlITWiNMw4ioxt1t7MDnjqpfOlu3lPQGHJL2PSZL15C5DFoYcKCysGXVeI3LER9jts/tM6e4KMzC2BmkDeJUJ/0bhA29f+lC6W+OPQnywAxldfgNXGB3gcId//Dvg2gDOle0qBDgcvYavQ5Z/tkHxA312cPcJAHjaUkWsOJq/imsDl6NOm4o8lozeEDa7SJVI7+MQYylk9cLp8l1G+PwoQV5M2oVlhnAefyn4MpbwiufJDkPg67kdcvr/Xe0G6tbgrVUAAAAASUVORK5CYII);
}
h1.isolated {
margin-top: 2rem;
}
@media print {
*,
::after,
::before {
text-shadow: none !important;
box-shadow: none !important;
}
a:not(.btn) {
text-decoration: underline;
}
abbr[title]::after {
content: " (" attr(title) ")";
}
pre {
white-space: pre-wrap !important;
}
blockquote,
pre {
border: 1px solid #adb5bd;
page-break-inside: avoid;
}
thead {
display: table-header-group;
}
img,
tr {
page-break-inside: avoid;
}
h2,
h3,
p {
orphans: 3;
widows: 3;
}
h2,
h3 {
page-break-after: avoid;
}
@page {
size: a3;
}
body {
min-width: 1180px !important;
}
.container {
min-width: 1080px !important;
}
.navbar {
display: none;
}
.badge {
border: 1px solid #000;
}
.table {
border-collapse: collapse !important;
}
.table td,
.table th {
background-color: #fff !important;
}
.table-bordered td,
.table-bordered th {
border: 1px solid #dee2e6 !important;
}
.table-dark {
color: inherit;
}
.table-dark tbody + tbody,
.table-dark td,
.table-dark th,
.table-dark thead th {
border-color: #dee2e6;
}
.table .thead-dark th {
color: inherit;
border-color: #dee2e6;
}
.page-break {
page-break-before: always;
}
.avoid-page-break {
page-break-inside: avoid;
}
.d-print-none {
display: none !important;
}
.d-print-inline {
display: inline !important;
}
.d-print-inline-block {
display: inline-block !important;
}
.d-print-block {
display: block !important;
}
.d-print-table {
display: table !important;
}
.d-print-table-row {
display: table-row !important;
}
.d-print-table-cell {
display: table-cell !important;
}
.d-print-flex {
display: -ms-flexbox !important;
display: flex !important;
}
.d-print-inline-flex {
display: -ms-inline-flexbox !important;
display: inline-flex !important;
}
}
================================================
FILE: packages/cli/tb-schema.json
================================================
{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": {},
"definitions": {
"IBenchmarkEnvironmentOverride": {
"additionalProperties": {},
"properties": {
"cpuThrottle": {
"type": "number"
},
"emulateDevice": {
"type": "string"
},
"emulateDeviceOrientation": {
"type": "string"
},
"network": {
"type": [
"string",
"number"
]
}
},
"type": "object"
},
"IHARServer": {
"properties": {
"dist": {
"type": "string"
},
"har": {
"type": "string"
},
"name": {
"type": "string"
},
"socksPort": {
"type": "number"
},
"url": {
"type": "string"
}
},
"type": "object"
},
"Marker": {
"properties": {
"label": {
"type": "string"
},
"start": {
"type": "string"
}
},
"type": "object"
},
"RegressionThresholdStat": {
"enum": [
"ci-lower",
"ci-upper",
"estimator"
],
"type": "string"
}
},
"properties": {
"appName": {
"type": "string"
},
"browserArgs": {
"items": {
"type": "string"
},
"type": "array"
},
"controlBenchmarkEnvironment": {
"$ref": "#/definitions/IBenchmarkEnvironmentOverride"
},
"controlURL": {
"type": "string"
},
"cookiespath": {
"type": "string"
},
"cpuThrottleRate": {
"type": [
"string",
"number"
]
},
"debug": {
"type": "boolean"
},
"emulateDevice": {
"type": "string"
},
"emulateDeviceOrientation": {
"type": "string"
},
"event": {
"type": "string"
},
"experimentBenchmarkEnvironment": {
"$ref": "#/definitions/IBenchmarkEnvironmentOverride"
},
"experimentURL": {
"type": "string"
},
"extends": {
"type": "string"
},
"fidelity": {
"anyOf": [
{
"enum": [
"high",
"low",
"medium",
"test"
],
"type": "string"
},
{
"type": "number"
}
]
},
"filter": {
"type": "string"
},
"headless": {
"type": "boolean"
},
"inputFilePath": {
"type": "string"
},
"isCIEnv": {
"type": [
"string",
"boolean"
]
},
"locations": {
"type": "string"
},
"marker": {
"type": "string"
},
"markers": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"items": {
"$ref": "#/definitions/Marker"
},
"type": "array"
},
{
"items": {
"enum": [
"connectEnd",
"connectStart",
"decodedBodySize",
"domComplete",
"domContentLoadedEventEnd",
"domContentLoadedEventStart",
"domInteractive",
"domainLookupEnd",
"domainLookupStart",
"duration",
"encodedBodySize",
"entryType",
"fetchStart",
"initiatorType",
"loadEventEnd",
"loadEventStart",
"name",
"nextHopProtocol",
"redirectCount",
"redirectEnd",
"redirectStart",
"requestStart",
"responseEnd",
"responseStart",
"secureConnectionStart",
"serverTiming",
"startTime",
"toJSON",
"transferSize",
"type",
"unloadEventEnd",
"unloadEventStart",
"workerStart"
],
"type": "string"
},
"type": "array"
},
{
"type": "string"
}
]
},
"methods": {
"type": "string"
},
"network": {
"type": "string"
},
"outputFilePath": {
"type": "string"
},
"plotTitle": {
"type": "string"
},
"regressionThreshold": {
"type": [
"string",
"number"
]
},
"regressionThresholdStat": {
"$ref": "#/definitions/RegressionThresholdStat"
},
"report": {
"type": "string"
},
"runtimeStats": {
"type": "boolean"
},
"sampleTimeout": {
"type": "number"
},
"servers": {
"items": [
{
"$ref": "#/definitions/IHARServer"
},
{
"$ref": "#/definitions/IHARServer"
}
],
"maxItems": 2,
"minItems": 2,
"type": "array"
},
"socksPorts": {
"items": [
{
"type": "number"
},
{
"type": "number"
}
],
"maxItems": 2,
"minItems": 2,
"type": "array"
},
"tbResultsFolder": {
"type": "string"
},
"traceFrame": {
"type": "string"
},
"url": {
"type": "string"
}
},
"type": "object"
}
================================================
FILE: packages/cli/test/command-config/build-config.test.ts
================================================
import { ICompareFlags } from "../../src/commands/compare";
import { checkEnvironmentSpecificOverride } from "../../src/helpers/utils";
import { getDefaultValue } from "../../src/command-config/default-flag-args";
import { expect } from "chai";
import { describe } from "mocha";
import * as mock from "mock-fs";
import { readConfig } from "../../src/command-config/build-config";
describe("utils", () => {
it(`getDefaultValue() from default`, () => {
const plotTitle = getDefaultValue("plotTitle");
expect(plotTitle).to.equal("TracerBench");
});
});
describe("checkEnvironmentSpecificOverride", () => {
it(`tbConfig missing case`, () => {
const defaultValues = { network: "defaultValue" };
// @ts-ignore
// @ts-nocheck
const result = checkEnvironmentSpecificOverride(
"network",
defaultValues as unknown as ICompareFlags,
"overrideName"
);
expect(result).to.equal("defaultValue");
});
it(`tbConfig exists but environment config missing case`, () => {
const defaultValues = { network: "defaultValue" };
// @ts-ignore
const result = checkEnvironmentSpecificOverride(
"network",
defaultValues as unknown as ICompareFlags,
"overrideName",
{}
);
expect(result).to.equal("defaultValue");
});
it(`tbConfig exists and environment exists but config missing case`, () => {
const defaultValues = { network: "defaultValue" };
const tbConfig = { overrideName: { cpuThrottleRate: 1 } };
// @ts-ignore
const result = checkEnvironmentSpecificOverride(
"network",
defaultValues as unknown as ICompareFlags,
"overrideName",
tbConfig
);
expect(result).to.equal("defaultValue");
});
it(`tbConfig exists and environment exists and config exists case`, () => {
const defaultValues = { cpuThrottleRate: 100 };
const tbConfig = { overrideName: { cpuThrottleRate: 1 } };
// @ts-ignore
const result = checkEnvironmentSpecificOverride(
"cpuThrottleRate",
defaultValues as unknown as ICompareFlags,
"overrideName",
tbConfig
);
expect(result).to.equal(1);
});
});
describe("resolveConfigFile", () => {
const mockFileSystem = {
parent: {
child: {
grandchild: {
"tbconfig.json": Buffer.from(
JSON.stringify({ extends: "../tbconfig.json", cpuThrottleRate: 0 })
),
"shouldfail.json": Buffer.from(
JSON.stringify({ extends: "../lostparent.json" })
)
},
"tbconfig.json": Buffer.from(
JSON.stringify({ extends: "../tbconfig.json", cpuThrottleRate: 1 })
)
},
"tbconfig.json": Buffer.from(
JSON.stringify({
extends: "../tbconfig.json",
cpuThrottleRate: 3,
regressionThreshold: 5
})
)
},
"tbconfig.json": Buffer.from(JSON.stringify({ cpuThrottleRate: 4 }))
};
it("throws when a parent config does not exist", () => {
mock(mockFileSystem);
try {
const shouldThrowError = () => {
readConfig("parent/child/grandchild/shouldfail.json");
};
expect(shouldThrowError).to.throw();
} finally {
mock.restore();
}
});
it("merges the configs correctly", () => {
mock(mockFileSystem);
try {
const config = readConfig("parent/child/grandchild/tbconfig.json");
expect(config).to.deep.equal({
cpuThrottleRate: 0,
regressionThreshold: 5
});
} finally {
mock.restore();
}
});
it("returns undefined when config does not exist", () => {
mock(mockFileSystem);
try {
const config = readConfig("config/does/not/exist.json");
expect(config).to.equal(undefined);
} finally {
mock.restore();
}
});
});
================================================
FILE: packages/cli/test/commands/analyze.test.ts
================================================
import { test } from "@oclif/test";
import { expect, assert } from "chai";
import { dirname, join } from "path";
import { describe } from "mocha";
import CompareAnalyze from "../../src/commands/compare/analyze";
import { COMPARE_JSON } from "../test-helpers";
const regressionThreshold = "10";
describe("compare:analyze", () => {
test.stdout().it(`runs compare:analyze COMPARE`, async (ctx) => {
await CompareAnalyze.run([`${COMPARE_JSON}`]);
expect(ctx.stdout).to.contain(`Initial Render : duration`);
expect(ctx.stdout).to.contain(`Sub Phase of Duration : application`);
expect(ctx.stdout).to.contain(`Hodges–Lehmann estimated delta`);
expect(ctx.stdout).to.contain(`Benchmark Results Summary`);
expect(ctx.stdout).to.contain(`duration phase estimated regression +`);
expect(ctx.stdout).to.contain(`application phase estimated regression +`);
});
});
describe("compare:analyze low fidelity, low threshold", () => {
test
.stdout()
.it(
`runs compare:analyze COMPARE --fidelity=2 --regressionThreshold=10`,
async (ctx) => {
await CompareAnalyze.run([
`${COMPARE_JSON}`,
"--fidelity=2",
`--regressionThreshold=${regressionThreshold}`
]);
expect(ctx.stdout).to.contain(
` WARNING The fidelity setting was set below the recommended for a viable result. Rerun TracerBench with at least "--fidelity=low" OR >= 10`
);
expect(ctx.stdout).to.not.contain(
` ! ALERT Regression found exceeding the set regression threshold of ${regressionThreshold} ms`
);
}
);
});
describe("compare:analyze low threshold with jsonReport", () => {
test
.stdout()
.it(
`runs compare:analyze COMPARE --fidelity=10 --regressionThreshold=10 --jsonReport`,
async (ctx) => {
await CompareAnalyze.run([
`${COMPARE_JSON}`,
"--fidelity=10",
`--regressionThreshold=${regressionThreshold}`,
"--jsonReport"
]);
expect(ctx.stdout).to.contain(
` ! ALERT Regression found exceeding the set regression threshold of ${regressionThreshold} ms`
);
assert.exists(join(dirname(COMPARE_JSON), "report.json"));
}
);
});
================================================
FILE: packages/cli/test/commands/auth.test.ts
================================================
import { test } from "@oclif/test";
import { expect } from "chai";
import { describe } from "mocha";
import RecordHARAuth from "../../src/commands/record-har/auth";
describe("record-har:auth without arg on throw error", () => {
test.stderr().it(`runs record-har:auth`, async () => {
try {
await RecordHARAuth.run([]);
} catch (error) {
expect(`${error}`).to.contain(`Missing required flag`);
}
});
});
================================================
FILE: packages/cli/test/commands/compare.test.ts
================================================
import { test } from "@oclif/test";
import { expect, assert } from "chai";
import { describe } from "mocha";
import Compare from "../../src/commands/compare";
import { FIXTURE_APP, TB_RESULTS_FOLDER } from "../test-helpers";
import { ICompareJSONResults } from "../../src/compare/compare-results";
const fidelity = "test";
const fidelityLow = "10";
const emulateDevice = "iphone-4";
const regressionThreshold = "50";
const network = "FIOS";
const markers = 'navigationStart,load,jqueryLoaded,jquery,emberLoaded,application,startRouting,routing,willTransition,transition,largestContentfulPaint';
describe("compare fixture: A/A", () => {
test
.stdout()
.it(
`runs compare --controlURL ${FIXTURE_APP.control} --experimentURL ${FIXTURE_APP.control} --fidelity ${fidelity} --tbResultsFolder ${TB_RESULTS_FOLDER} --config ${FIXTURE_APP.controlConfig} --network ${network} --headless --debug --report`,
async (ctx) => {
const results = await Compare.run([
"--controlURL",
FIXTURE_APP.control,
"--experimentURL",
FIXTURE_APP.control,
"--fidelity",
fidelity,
"--tbResultsFolder",
TB_RESULTS_FOLDER,
"--config",
FIXTURE_APP.controlConfig,
"--network",
network,
"--headless",
"--debug",
"--report"
]);
const resultsJSON: ICompareJSONResults = await JSON.parse(results);
assert.exists(`${TB_RESULTS_FOLDER}/server-control-settings.json`);
assert.exists(`${TB_RESULTS_FOLDER}/server-experiment-settings.json`);
assert.exists(`${TB_RESULTS_FOLDER}/compare-flags-settings.json`);
assert.exists(`${TB_RESULTS_FOLDER}/traces.zip`);
// results are json and NOT significant
assert.isFalse(resultsJSON.areResultsSignificant);
// regression is below the threshold
assert.isTrue(resultsJSON.isBelowRegressionThreshold);
expect(ctx.stdout).to.contain(`SUCCESS`);
expect(ctx.stdout).to.contain(`Benchmark Reports`);
}
);
});
describe("compare fixture: A/A CI", () => {
test
.stdout()
.it(
`runs compare --controlURL ${FIXTURE_APP.control} --experimentURL ${FIXTURE_APP.control} --fidelity ${fidelity} --tbResultsFolder ${TB_RESULTS_FOLDER} --config ${FIXTURE_APP.controlConfig} --headless --isCIEnv=true`,
async (ctx) => {
await Compare.run([
"--controlURL",
FIXTURE_APP.control,
"--experimentURL",
FIXTURE_APP.control,
"--fidelity",
fidelity,
"--tbResultsFolder",
TB_RESULTS_FOLDER,
"--config",
FIXTURE_APP.controlConfig,
"--headless",
"--isCIEnv=true"
]);
expect(ctx.stdout).to.contain(`SUCCESS`);
expect(ctx.stdout).to.contain(`Benchmark Results Summary`);
expect(ctx.stdout).to.not.contain("Benchmark Reports");
expect(ctx.stdout).to.not.contain("Seven Figure Summary");
expect(ctx.stdout).to.not.contain("Hodges–Lehmann estimated delta");
expect(ctx.stdout).to.not.contain("Sparkline");
}
);
});
describe("compare regression: fixture: A/B trace end at LCP candidate after a marker", () => {
test
.stdout()
.it(
`runs compare --controlURL ${FIXTURE_APP.control} --experimentURL ${FIXTURE_APP.regression} --fidelity ${fidelityLow} --tbResultsFolder ${TB_RESULTS_FOLDER} --config ${FIXTURE_APP.regressionConfig} --regressionThreshold ${regressionThreshold} --markers ${markers} --headless`,
async (ctx) => {
const results = await Compare.run([
"--controlURL",
FIXTURE_APP.control,
"--experimentURL",
FIXTURE_APP.regression,
"--fidelity",
fidelityLow,
"--tbResultsFolder",
TB_RESULTS_FOLDER,
"--config",
FIXTURE_APP.regressionConfig,
"--regressionThreshold",
regressionThreshold,
"--markers",
markers,
"--headless"
]);
const resultsJSON: ICompareJSONResults = await JSON.parse(results);
expect(ctx.stdout).to.contain(
` SUCCESS ${fidelityLow} test samples took`
);
// confirm with headless flag is logging the trace stream
expect(ctx.stdout).to.contain(`duration phase estimated regression +`);
expect(ctx.stdout).to.contain(
` ! ALERT Regression found exceeding the set regression threshold of ${regressionThreshold} ms`
);
assert.isAbove(
parseInt(resultsJSON.benchmarkTableData[0].estimatorDelta, 10),
500
);
assert.isAbove(
parseInt(resultsJSON.benchmarkTableData[0].confidenceInterval[0], 10),
500
);
assert.isAbove(
parseInt(resultsJSON.benchmarkTableData[0].confidenceInterval[1], 10),
500
);
// results are json and are significant
assert.isTrue(resultsJSON.areResultsSignificant);
// regression is over the threshold
assert.isFalse(resultsJSON.isBelowRegressionThreshold);
}
);
});
describe("compare regression: fixture: A/B", () => {
test
.stdout()
.it(
`runs compare --controlURL ${FIXTURE_APP.control} --experimentURL ${FIXTURE_APP.regression} --fidelity ${fidelityLow} --tbResultsFolder ${TB_RESULTS_FOLDER} --config ${FIXTURE_APP.regressionConfig} --regressionThreshold ${regressionThreshold} --headless`,
async (ctx) => {
const results = await Compare.run([
"--controlURL",
FIXTURE_APP.control,
"--experimentURL",
FIXTURE_APP.regression,
"--fidelity",
fidelityLow,
"--tbResultsFolder",
TB_RESULTS_FOLDER,
"--config",
FIXTURE_APP.regressionConfig,
"--regressionThreshold",
regressionThreshold,
"--headless"
]);
const resultsJSON: ICompareJSONResults = await JSON.parse(results);
expect(ctx.stdout).to.contain(
` SUCCESS ${fidelityLow} test samples took`
);
// confirm with headless flag is logging the trace stream
expect(ctx.stdout).to.contain(`duration phase estimated regression +`);
expect(ctx.stdout).to.contain(
` ! ALERT Regression found exceeding the set regression threshold of ${regressionThreshold} ms`
);
assert.isAbove(
parseInt(resultsJSON.benchmarkTableData[0].estimatorDelta, 10),
500
);
assert.isAbove(
parseInt(resultsJSON.benchmarkTableData[0].confidenceInterval[0], 10),
500
);
assert.isAbove(
parseInt(resultsJSON.benchmarkTableData[0].confidenceInterval[1], 10),
500
);
// results are json and are significant
assert.isTrue(resultsJSON.areResultsSignificant);
// regression is over the threshold
assert.isFalse(resultsJSON.isBelowRegressionThreshold);
}
);
});
describe("compare mobile horizontal: fixture: A/A", () => {
test
.stdout()
.it(
`runs compare --controlURL ${FIXTURE_APP.control} --experimentURL ${FIXTURE_APP.experiment} --fidelity ${fidelity} --tbResultsFolder ${TB_RESULTS_FOLDER} --emulateDevice ${emulateDevice} --emulateDeviceOrientation horizontal --headless`,
async (ctx) => {
await Compare.run([
"--controlURL",
FIXTURE_APP.control,
"--experimentURL",
FIXTURE_APP.experiment,
"--fidelity",
fidelity,
"--tbResultsFolder",
TB_RESULTS_FOLDER,
"--emulateDevice",
emulateDevice,
"--emulateDeviceOrientation",
"horizontal",
"--headless"
]);
expect(ctx.stdout).to.contain(`SUCCESS`);
}
);
});
================================================
FILE: packages/cli/test/commands/record-har.test.ts
================================================
import { test } from "@oclif/test";
import { Archive } from "@tracerbench/har";
import { LCP_EVENT_NAME_ALIAS } from "@tracerbench/core";
import { expect, assert } from "chai";
import { describe } from "mocha";
import { readJSONSync } from "fs-extra";
import { join } from "path";
import RecordHAR from "../../src/commands/record-har";
import { COOKIES, TB_RESULTS_FOLDER, URL } from "../test-helpers";
const FILENAME = "foo";
describe("record-har headless", () => {
test
.stdout()
.it(
`runs record-har --url ${URL} --dest ${TB_RESULTS_FOLDER} --cookiespath ${COOKIES} --filename ${FILENAME} --headless --screenshots`,
async (ctx) => {
await RecordHAR.run([
"--url",
URL,
"--dest",
TB_RESULTS_FOLDER,
"--cookiespath",
COOKIES,
"--filename",
FILENAME,
"--headless",
"--screenshots"
]);
const harFile = join(TB_RESULTS_FOLDER, `${FILENAME}.har`);
const screenshotPath = join(
TB_RESULTS_FOLDER,
"record-har-app-screenshot.png"
);
const harJSON: Archive = readJSONSync(harFile);
expect(ctx.stdout).to.contain(`app screenshot:`);
expect(ctx.stdout).to.contain(`Validating HAR`);
expect(ctx.stdout).to.contain(`HAR recorded`);
expect(harJSON.log.entries.length).to.be.gt(1);
expect(harJSON.log.entries[0].request.url).to.contain(`${URL}`);
expect(harJSON.log.entries[0].request.headers.length).eq(1);
expect(harJSON.log.entries[0].response.headers.length).to.be.gt(1);
assert.exists(screenshotPath);
assert.exists(harFile);
assert.equal(harJSON.log.creator.name, "TracerBench");
assert.equal(harJSON.log.entries[0].response.status, 200);
}
);
});
describe("record-har end recording at LCP", () => {
test
.stdout()
.it(
`runs record-har --url ${URL} --dest ${TB_RESULTS_FOLDER} --cookiespath ${COOKIES} --filename ${FILENAME} --marker ${LCP_EVENT_NAME_ALIAS} --headless --screenshots`,
async (ctx) => {
await RecordHAR.run([
"--url",
URL,
"--dest",
TB_RESULTS_FOLDER,
"--cookiespath",
COOKIES,
"--filename",
FILENAME,
"--marker",
LCP_EVENT_NAME_ALIAS,
"--headless",
"--screenshots"
]);
const harFile = join(TB_RESULTS_FOLDER, `${FILENAME}.har`);
const screenshotPath = join(
TB_RESULTS_FOLDER,
"record-har-app-screenshot.png"
);
const harJSON: Archive = readJSONSync(harFile);
expect(ctx.stdout).to.contain(`app screenshot:`);
expect(ctx.stdout).to.contain(`Validating HAR`);
expect(ctx.stdout).to.contain(`HAR recorded`);
expect(harJSON.log.entries.length).to.be.gt(1);
expect(harJSON.log.entries[0].request.url).to.contain(`${URL}`);
expect(harJSON.log.entries[0].request.headers.length).eq(1);
expect(harJSON.log.entries[0].response.headers.length).to.be.gt(1);
assert.exists(screenshotPath);
assert.exists(harFile);
assert.equal(harJSON.log.creator.name, "TracerBench");
assert.equal(harJSON.log.entries[0].response.status, 200);
}
);
});
================================================
FILE: packages/cli/test/commands/report.test.ts
================================================
import { test } from "@oclif/test";
import { expect, assert } from "chai";
import { describe } from "mocha";
import { readJsonSync } from "fs-extra";
import { IHARServer } from "../../src/command-config";
import CompareReport from "../../src/commands/compare/report";
import { Report } from "../../src/index";
import {
TB_CONFIG_FILE,
TB_RESULTS_FOLDER,
COMPARE_JSON,
generateFileStructure
} from "../test-helpers";
const COMPARE = {
"compare.json": JSON.stringify(readJsonSync(COMPARE_JSON))
};
describe("compare:report", () => {
generateFileStructure(COMPARE, TB_RESULTS_FOLDER);
test
.stdout()
.it(
`runs compare:report --config ${TB_CONFIG_FILE} --tbResultsFolder ${TB_RESULTS_FOLDER} `,
async (ctx) => {
await CompareReport.run([
"--tbResultsFolder",
`${TB_RESULTS_FOLDER}`,
"--config",
`${TB_CONFIG_FILE}`
]);
expect(ctx.stdout).to.contain(`JSON:`);
expect(ctx.stdout).to.contain(`PDF:`);
expect(ctx.stdout).to.contain(`HTML:`);
assert.exists(`${TB_RESULTS_FOLDER}/compare.json`);
assert.exists(`${TB_RESULTS_FOLDER}/artifact-1.html`);
assert.exists(`${TB_RESULTS_FOLDER}/artifact-1.pdf`);
}
);
});
describe("report - backwards compat test", () => {
generateFileStructure(COMPARE, TB_RESULTS_FOLDER);
test
.stdout()
.it(
`runs report --config ${TB_CONFIG_FILE} --tbResultsFolder ${TB_RESULTS_FOLDER} `,
async (ctx) => {
await Report.run([
"--tbResultsFolder",
`${TB_RESULTS_FOLDER}`,
"--config",
`${TB_CONFIG_FILE}`
]);
expect(ctx.stdout).to.contain(`JSON:`);
expect(ctx.stdout).to.contain(`PDF:`);
expect(ctx.stdout).to.contain(`HTML:`);
assert.exists(`${TB_RESULTS_FOLDER}/compare.json`);
assert.exists(`${TB_RESULTS_FOLDER}/artifact-1.html`);
assert.exists(`${TB_RESULTS_FOLDER}/artifact-1.pdf`);
}
);
});
describe("compare:report ci", () => {
generateFileStructure(COMPARE, TB_RESULTS_FOLDER);
test
.stdout()
.it(
`runs compare:report --config ${TB_CONFIG_FILE} --tbResultsFolder ${TB_RESULTS_FOLDER} --isCIEnv true`,
async (ctx) => {
await CompareReport.run([
"--tbResultsFolder",
`${TB_RESULTS_FOLDER}`,
"--config",
`${TB_CONFIG_FILE}`,
"--isCIEnv",
`${true}`
]);
expect(ctx.stdout).to.not.contain(`JSON:`);
expect(ctx.stdout).to.not.contain(`PDF:`);
expect(ctx.stdout).to.not.contain(`HTML:`);
assert.exists(`${TB_RESULTS_FOLDER}/compare.json`);
assert.exists(`${TB_RESULTS_FOLDER}/artifact-1.html`);
assert.exists(`${TB_RESULTS_FOLDER}/artifact-1.pdf`);
}
);
});
describe("compare:report", () => {
it(`resolveTitles()`, () => {
const browserVersion = "HeadlessChrome/80.0.3965.0";
const servers: [IHARServer, IHARServer] = [
{
name: "Hello World",
url: "",
dist: "",
socksPort: 0,
har: ""
},
{ name: "Hello World 2", url: "", dist: "", socksPort: 0, har: "" }
];
const resolved = CompareReport.resolveTitles(
{
servers,
plotTitle: "Override"
},
browserVersion
);
expect(resolved.servers[0].name).to.equal("Control: Hello World");
expect(resolved.servers[1].name).to.equal("Experiment: Hello World 2");
expect(resolved.plotTitle).to.equal("Override");
expect(resolved.browserVersion).to.equal(browserVersion);
});
});
describe("compare:report", () => {
it(`resolveTitles():flag-override`, () => {
const browserVersion = "HeadlessChrome/80.0.3965.0";
const servers: [IHARServer, IHARServer] = [
{
name: "Hello World",
url: "",
dist: "",
socksPort: 0,
har: ""
},
{ name: "Hello World 2", url: "", dist: "", socksPort: 0, har: "" }
];
const resolved = CompareReport.resolveTitles(
{
servers,
plotTitle: "Override"
},
browserVersion,
"Flag-Override"
);
expect(resolved.servers[0].name).to.equal("Control: Hello World");
expect(resolved.servers[1].name).to.equal("Experiment: Hello World 2");
expect(resolved.plotTitle).to.equal("Flag-Override");
expect(resolved.browserVersion).to.equal(browserVersion);
});
});
================================================
FILE: packages/cli/test/compare/compare-results.test.ts
================================================
import { expect } from "chai";
import { describe } from "mocha";
import { readJsonSync } from "fs-extra";
import {
GenerateStats,
ITracerBenchTraceResult,
ParsedTitleConfigs
} from "../../src/compare/generate-stats";
import {
CompareResults,
ICompareJSONResults
} from "../../src/compare/compare-results";
import { COMPARE_JSON } from "../test-helpers";
const REPORT_TITLES: ParsedTitleConfigs = {
servers: [],
plotTitle: "Foo Title",
browserVersion: "1.2.3"
};
const tableDataObj = {
heading: "",
phaseName: "",
isSignificant: true,
estimatorDelta: "",
controlSampleCount: 1,
pValue: 1,
experimentSampleCount: 1,
confidenceInterval: [[]],
controlSevenFigureSummary: [{}],
experimentSevenFigureSummary: [{}],
asPercent: {}
};
/*
SORTED AND IN MILLISECONDS
CONTROL_DATA: [
305, 306, 307, 307, 308,
308, 309, 309, 309, 309,
309, 309, 310, 310, 310,
312, 312, 315, 338, 434
]
EXPERIMENT_DATA: [
1299, 1301, 1304, 1305,
1306, 1307, 1308, 1308,
1309, 1309, 1309, 1309,
1309, 1310, 1311, 1312,
1313, 1320, 1325, 1328
]
*/
const DEFAULT_REGRESSION_THRESHOLD = 50;
const CONTROL_DATA: ITracerBenchTraceResult = readJsonSync(COMPARE_JSON)[0];
const EXPERIMENT_DATA: ITracerBenchTraceResult = readJsonSync(COMPARE_JSON)[1];
const stats = new GenerateStats(CONTROL_DATA, EXPERIMENT_DATA, REPORT_TITLES);
const compareResults = new CompareResults(
stats,
20,
DEFAULT_REGRESSION_THRESHOLD
);
describe("compare-results anyResultsSignificant()", () => {
const truthyArr = [true, true, false];
const falsyArr = [false, false, false];
it(`stat-sig results are significant`, () => {
const isSigA = compareResults.anyResultsSignificant(truthyArr, falsyArr);
const isSigB = compareResults.anyResultsSignificant(falsyArr, truthyArr);
expect(isSigA).to.be.true;
expect(isSigB).to.be.true;
});
it(`stat-sig results are not significant`, () => {
const isNotSig = compareResults.anyResultsSignificant(falsyArr, falsyArr);
expect(isNotSig).to.be.false;
});
});
describe("compare-results anyResultsSignificant()", () => {
const truthyArr = [true, true, false];
const falsyArr = [false, false, false];
it(`stat-sig results are significant`, () => {
const isSigA = compareResults.anyResultsSignificant(truthyArr, falsyArr);
const isSigB = compareResults.anyResultsSignificant(falsyArr, truthyArr);
expect(isSigA).to.be.true;
expect(isSigB).to.be.true;
});
it(`stat-sig results are not significant`, () => {
const isNotSig = compareResults.anyResultsSignificant(falsyArr, falsyArr);
expect(isNotSig).to.be.false;
});
});
describe("compare-results allBelowRegressionThreshold()", () => {
it(`regression is below threshold : default regressionThresholdStatistic : estimator`, () => {
compareResults.regressionThreshold = 1100;
const isBelowThreshold = compareResults.allBelowRegressionThreshold();
expect(isBelowThreshold).to.be.true;
compareResults.regressionThreshold = DEFAULT_REGRESSION_THRESHOLD;
});
it(`regression is above threshold : default regressionThresholdStatistic : estimator`, () => {
compareResults.regressionThreshold = 50;
const isBelowThreshold = compareResults.allBelowRegressionThreshold();
expect(isBelowThreshold).to.be.false;
compareResults.regressionThreshold = DEFAULT_REGRESSION_THRESHOLD;
});
it(`regression is below threshold : regressionThresholdStatistic : ci-lower`, () => {
const compareResultsCILower = new CompareResults(
stats,
20,
1002,
"ci-lower"
);
const isBelowThresholdCILower =
compareResultsCILower.allBelowRegressionThreshold();
expect(isBelowThresholdCILower).to.be.true;
});
it(`regression is above threshold : regressionThresholdStatistic : ci-lower`, () => {
const compareResultsCILower = new CompareResults(
stats,
20,
995,
"ci-lower"
);
const isBelowThresholdCILower =
compareResultsCILower.allBelowRegressionThreshold();
expect(isBelowThresholdCILower).to.be.false;
});
it(`regression is below threshold : regressionThresholdStatistic : ci-upper`, () => {
const compareResultsCIUpper = new CompareResults(
stats,
20,
1100,
"ci-upper"
);
const isBelowThresholdCIUpper =
compareResultsCIUpper.allBelowRegressionThreshold();
expect(isBelowThresholdCIUpper).to.be.true;
});
it(`regression is above threshold : regressionThresholdStatistic : ci-upper`, () => {
const compareResultsCIUpper = new CompareResults(
stats,
20,
1000,
"ci-upper"
);
const isBelowThresholdCIUpper =
compareResultsCIUpper.allBelowRegressionThreshold();
expect(isBelowThresholdCIUpper).to.be.false;
});
});
describe("compare-results stringifyJSON()", () => {
it(`trimmed compare results in JSON`, () => {
const compareJSONResults: ICompareJSONResults = JSON.parse(
compareResults.stringifyJSON()
);
const benchmarkTableData = compareJSONResults.benchmarkTableData[0];
const phaseTableData = compareJSONResults.phaseTableData;
// benchmark table data
expect(benchmarkTableData).to.have.all.keys(tableDataObj);
expect(benchmarkTableData.isSignificant).to.be.true;
expect(benchmarkTableData.phaseName).to.eq("duration");
expect(benchmarkTableData.estimatorDelta).to.eq("999ms");
expect(phaseTableData[0].phaseName).to.eq("jquery");
expect(compareJSONResults.areResultsSignificant).to.be.true;
expect(compareJSONResults.isBelowRegressionThreshold).to.be.false;
expect(compareJSONResults.regressionThresholdStat).to.eq("estimator");
// lower eg -5 | 2 | 0
expect(compareJSONResults.benchmarkTableData[0].asPercent.percentMin).to.eq(
322.77
);
// middle eg -3 | 5 | 2
expect(
compareJSONResults.benchmarkTableData[0].asPercent.percentMedian
).to.eq(323.68);
// upper eg 2 | 10 | 3
expect(compareJSONResults.benchmarkTableData[0].asPercent.percentMax).to.eq(
324.38
);
});
});
================================================
FILE: packages/cli/test/compare/generate-stats.test.ts
================================================
import { expect } from "chai";
import { describe } from "mocha";
import { readJsonSync } from "fs-extra";
import {
GenerateStats,
ITracerBenchTraceResult,
ParsedTitleConfigs
} from "../../src/compare/generate-stats";
import { COMPARE_JSON } from "../test-helpers";
const REPORT_TITLES: ParsedTitleConfigs = {
servers: [],
plotTitle: "Foo Title",
browserVersion: "1.2.3"
};
const CONTROL_DATA: ITracerBenchTraceResult = readJsonSync(COMPARE_JSON)[0];
const EXPERIMENT_DATA: ITracerBenchTraceResult = readJsonSync(COMPARE_JSON)[1];
const stats = new GenerateStats(CONTROL_DATA, EXPERIMENT_DATA, REPORT_TITLES);
describe("generate-stats", () => {
it(`generateData() durationSection`, () => {
const { durationSection } = stats;
const { controlFormatedSamples, experimentFormatedSamples } =
durationSection;
// STATS
// duration control in ms and not sorted
expect(durationSection.stats.control[0]).to.eql(305);
expect(durationSection.stats.control[19]).to.eql(434);
// duration experiment in ms and not sorted
expect(durationSection.stats.experiment[0]).to.eql(1299);
expect(durationSection.stats.experiment[19]).to.eql(1328);
// duration control in ms and sorted
expect(durationSection.stats.controlSorted[0]).to.eql(305);
expect(durationSection.stats.controlSorted[19]).to.eql(434);
// duration experiment in ms and sorted
expect(durationSection.stats.experimentSorted[0]).to.eql(1299);
expect(durationSection.stats.experimentSorted[19]).to.eql(1328);
// duration name
expect(durationSection.stats.name).to.eql("duration");
// sample count
expect(durationSection.stats.sampleCount.control).to.eql(20);
expect(durationSection.stats.sampleCount.experiment).to.eql(20);
// duration 95% confidence-interval
expect(durationSection.stats.confidenceInterval.min).to.eql(-1001);
expect(durationSection.stats.confidenceInterval.max).to.eql(-996);
expect(durationSection.stats.confidenceInterval.isSig).to.be.true;
expect(durationSection.stats.confidenceIntervals[95].min).to.eql(-1001);
expect(durationSection.stats.confidenceIntervals[95].max).to.eql(-996);
expect(durationSection.stats.confidenceIntervals[95].isSig).to.be.true;
// duration 99% confidence-interval
expect(durationSection.stats.confidenceIntervals[99].min).to.eql(-1002);
expect(durationSection.stats.confidenceIntervals[99].max).to.eql(-995);
expect(durationSection.stats.confidenceIntervals[99].isSig).to.be.true;
// duration estimator
expect(durationSection.stats.estimator).to.eql(-999);
// duration control outliers
expect(durationSection.stats.outliers.control.outliers.length).to.eq(3);
expect(durationSection.stats.outliers.control.outliers[0]).to.eq(315);
expect(durationSection.stats.outliers.control.outliers[1]).to.eq(338);
expect(durationSection.stats.outliers.control.lowerOutlier).to.eq(304);
expect(durationSection.stats.outliers.control.upperOutlier).to.eq(314);
// duration experiment outliers
expect(durationSection.stats.outliers.experiment.outliers.length).to.eq(4);
expect(durationSection.stats.outliers.experiment.outliers[0]).to.eq(1299);
expect(durationSection.stats.outliers.experiment.outliers[1]).to.eq(1320);
expect(durationSection.stats.outliers.experiment.outliers[2]).to.eq(1325);
expect(durationSection.stats.outliers.experiment.outliers[3]).to.eq(1328);
expect(durationSection.stats.outliers.experiment.lowerOutlier).to.eq(1300);
expect(durationSection.stats.outliers.experiment.upperOutlier).to.eq(1318);
// duration interquartile range
expect(durationSection.stats.outliers.control.IQR).to.eq(2);
expect(durationSection.stats.outliers.experiment.IQR).to.eq(4);
// duration is significant
expect(durationSection.isSignificant).to.be.true;
// duration sample count
expect(durationSection.sampleCount).to.eq(20);
// duration confidence interval min
expect(durationSection.ciMin).to.eq(-1001);
// duration confidence interval max
expect(durationSection.ciMax).to.eq(-996);
// duration estimator
expect(durationSection.hlDiff).to.eq(-999);
// duration control formatted samples
expect(controlFormatedSamples.min).to.eq(305);
expect(controlFormatedSamples.q1).to.eq(308);
expect(controlFormatedSamples.median).to.eq(309);
expect(controlFormatedSamples.q3).to.eq(311);
expect(controlFormatedSamples.max).to.eq(434);
expect(controlFormatedSamples.outliers.length).to.eq(3);
expect(controlFormatedSamples.outliers[0]).to.eq(315);
expect(controlFormatedSamples.outliers[1]).to.eq(338);
expect(controlFormatedSamples.samplesMS[0]).to.eq(305);
expect(controlFormatedSamples.samplesMS[19]).to.eq(434);
// duration experiment formatted samples
expect(experimentFormatedSamples.min).to.eq(1299);
expect(experimentFormatedSamples.q1).to.eq(1307);
expect(experimentFormatedSamples.median).to.eq(1309);
expect(experimentFormatedSamples.q3).to.eq(1311);
expect(experimentFormatedSamples.max).to.eq(1328);
expect(experimentFormatedSamples.outliers.length).to.eq(4);
expect(experimentFormatedSamples.outliers[0]).to.eq(1299);
expect(experimentFormatedSamples.outliers[1]).to.eq(1320);
expect(experimentFormatedSamples.outliers[2]).to.eq(1325);
expect(experimentFormatedSamples.outliers[3]).to.eq(1328);
expect(experimentFormatedSamples.samplesMS[0]).to.eq(1299);
expect(experimentFormatedSamples.samplesMS[3]).to.eq(1305);
expect(experimentFormatedSamples.samplesMS[19]).to.eq(1328);
});
it(`generateData() subPhaseSections`, () => {
const { subPhaseSections } = stats;
const renderPhase = subPhaseSections[5];
const { controlFormatedSamples, experimentFormatedSamples } = renderPhase;
// STATS
// subphase
expect(subPhaseSections.length).to.eql(7);
// render control in ms and not sorted
expect(renderPhase.stats.control[0]).to.eql(20);
expect(renderPhase.stats.control[19]).to.eql(29);
// render experiment in ms and not sorted
expect(renderPhase.stats.experiment[0]).to.eql(20);
expect(renderPhase.stats.experiment[19]).to.eql(23);
// render control in ms and sorted
expect(renderPhase.stats.controlSorted[0]).to.eql(20);
expect(renderPhase.stats.controlSorted[19]).to.eql(29);
// render experiment in ms and sorted
expect(renderPhase.stats.experimentSorted[0]).to.eql(20);
expect(renderPhase.stats.experimentSorted[19]).to.eql(23);
// render estimator
expect(renderPhase.stats.estimator).to.eql(0);
// phase name
expect(renderPhase.phase).to.eq("render");
// render is significant
expect(renderPhase.isSignificant).to.be.false;
// render sample count
expect(renderPhase.sampleCount).to.eq(20);
// render confidence interval min
expect(renderPhase.ciMin).to.eq(0);
// render confidence interval max
expect(renderPhase.ciMax).to.eq(1);
// render estimator
expect(renderPhase.hlDiff).to.eq(0);
// render control formatted samples
expect(controlFormatedSamples.min).to.eq(20);
expect(controlFormatedSamples.q1).to.eq(20);
expect(controlFormatedSamples.median).to.eq(20);
expect(controlFormatedSamples.q3).to.eq(21);
expect(controlFormatedSamples.max).to.eq(29);
expect(controlFormatedSamples.outliers.length).to.eq(2);
expect(controlFormatedSamples.outliers[0]).to.eq(23);
expect(controlFormatedSamples.samplesMS[0]).to.eq(20);
expect(controlFormatedSamples.samplesMS[19]).to.eq(29);
// render experiment formatted samples
expect(experimentFormatedSamples.min).to.eq(20);
expect(experimentFormatedSamples.q1).to.eq(20);
expect(experimentFormatedSamples.median).to.eq(20);
expect(experimentFormatedSamples.q3).to.eq(21);
expect(experimentFormatedSamples.max).to.eq(23);
expect(experimentFormatedSamples.outliers.length).to.eq(2);
expect(experimentFormatedSamples.samplesMS[0]).to.eq(20);
expect(experimentFormatedSamples.samplesMS[12]).to.eq(20);
expect(experimentFormatedSamples.samplesMS[19]).to.eq(23);
});
it(`bucketCumulative() cumulativeData`, () => {
const { cumulativeData } = stats;
const { categories, controlData, experimentData } = cumulativeData;
expect(categories.length).to.eql(7);
expect(categories[0]).to.eq("jquery");
expect(categories[6]).to.eq("afterRender");
// cumulative any sample within the first phase should be lt future phases
// phase 0 < phase 1, phase 1 < phase 2, phase 2 < phase 3 ...
// covers bucketPhaseValues()
expect(controlData[0][0]).to.be.lt(controlData[1][0]);
expect(controlData[1][0]).to.be.lt(controlData[2][0]);
expect(controlData[2][0]).to.be.lt(controlData[3][0]);
expect(controlData[3][0]).to.be.lt(controlData[4][0]);
expect(controlData[4][0]).to.be.lt(controlData[5][0]);
expect(controlData[5][0]).to.be.lt(controlData[6][0]);
expect(experimentData[0][0]).to.be.lt(experimentData[1][0]);
expect(experimentData[1][0]).to.be.lt(experimentData[2][0]);
expect(experimentData[2][0]).to.be.lt(experimentData[3][0]);
expect(experimentData[3][0]).to.be.lt(experimentData[4][0]);
expect(experimentData[4][0]).to.be.lt(experimentData[5][0]);
expect(experimentData[5][0]).to.be.lt(experimentData[6][0]);
});
});
================================================
FILE: packages/cli/test/compare/parse-compare-result.test.ts
================================================
import { expect } from "chai";
import { describe } from "mocha";
import { test } from "@oclif/test";
import parseCompareResult from "../../src/compare/parse-compare-result";
import { COMPARE_JSON } from "../test-helpers";
const jsonResults = {
meta: {
browserVersion: "",
cpus: ""
},
samples: [{}],
set: ""
};
const sample = {
duration: 0,
js: 0,
phases: [{}],
blinkGC: [{}],
gc: [{}]
};
describe("parse-compare-result", () => {
test.stdout().it(`stdout`, async () => {
const { controlData, experimentData } = parseCompareResult(COMPARE_JSON);
expect(controlData).to.have.all.keys(jsonResults);
expect(controlData.samples[0]).to.have.all.keys(sample);
expect(experimentData).to.have.all.keys(jsonResults);
expect(experimentData.samples[0]).to.have.all.keys(sample);
});
});
================================================
FILE: packages/cli/test/compare/print-to-pdf.test.ts
================================================
import { assert } from "chai";
import { describe } from "mocha";
import { join } from "path";
import { pathToFileURL } from "url";
import { FIXTURE_APP, TB_RESULTS_FOLDER } from "../test-helpers";
import printToPDF from "../../src/compare/print-to-pdf";
describe("printToPDF", () => {
it(`it outputs pdf`, async () => {
const outputPath = join(TB_RESULTS_FOLDER, "/pdf-test-file.pdf");
await printToPDF(pathToFileURL(FIXTURE_APP.control).toString(), outputPath);
assert.exists(outputPath);
});
});
================================================
FILE: packages/cli/test/compare/tb-table.test.ts
================================================
import { expect } from "chai";
import { describe } from "mocha";
import {
Stats,
roundFloatAndConvertMicrosecondsToMS
} from "@tracerbench/stats";
import TBTable from "../../src/compare/tb-table";
import {
REGRESSION_RESULTS,
HIGH_VARIANCE_RESULTS,
ON_LINE_RESULTS,
IMPROVEMENT_RESULTS
} from "../fixtures/results/stats";
const TABLE = new TBTable("Foo Table");
const statsHighVarianceMS = new Stats(
{
control: HIGH_VARIANCE_RESULTS.control,
experiment: HIGH_VARIANCE_RESULTS.experiment,
name: "stats-high-variance-ms-test"
},
roundFloatAndConvertMicrosecondsToMS
);
const statsMS = new Stats(
{
control: REGRESSION_RESULTS.control,
experiment: REGRESSION_RESULTS.experiment,
name: "stats-regression-ms-test"
},
roundFloatAndConvertMicrosecondsToMS
);
const statsOnLineMS = new Stats(
{
control: ON_LINE_RESULTS.control,
experiment: ON_LINE_RESULTS.experiment,
name: "stats-on-line-ms-test"
},
roundFloatAndConvertMicrosecondsToMS
);
const statsImprovementMS = new Stats(
{
control: IMPROVEMENT_RESULTS.control,
experiment: IMPROVEMENT_RESULTS.experiment,
name: "stats-improvement-ms-test"
},
roundFloatAndConvertMicrosecondsToMS
);
TABLE.display.push(
statsHighVarianceMS,
statsMS,
statsOnLineMS,
statsImprovementMS
);
describe("tb-table", () => {
it(`generates new CLI-Table-3`, () => {
expect(TABLE.heading).to.equal("Foo Table");
expect(TABLE.display.length).to.equal(4);
expect(TABLE.isSigArray.length).to.equal(0);
expect(TABLE.estimatorDeltas.length).to.equal(0);
});
it(`render() and setTableData()`, () => {
const tableRendered = TABLE.render();
expect(tableRendered).to.contain("Control Sparkline");
expect(tableRendered).to.contain("Experiment Sparkline");
});
it(`getData()`, () => {
const tableData = TABLE.getData();
expect(tableData.length).to.equal(4);
// not a regression
expect(tableData[0].phaseName).to.equal("stats-high-variance-ms-test");
expect(tableData[0].asPercent.percentMin).to.equal(-3.33);
expect(tableData[0].asPercent.percentMedian).to.equal(0.08);
expect(tableData[0].asPercent.percentMax).to.equal(3.77);
expect(tableData[0].estimatorDelta).to.equal("2ms");
expect(tableData[0].pValue).to.equal(0.967);
expect(tableData[0].confidenceInterval[0]).to.equal("-84ms");
expect(tableData[0].confidenceInterval[1]).to.equal("95ms");
expect(tableData[0].isSignificant).to.be.false;
// regression
expect(tableData[1].phaseName).to.equal("stats-regression-ms-test");
expect(tableData[1].asPercent.percentMin).to.equal(2081.14);
expect(tableData[1].asPercent.percentMedian).to.equal(2083.26);
expect(tableData[1].asPercent.percentMax).to.equal(2086.2);
expect(tableData[1].estimatorDelta).to.equal("1080ms");
expect(tableData[1].pValue).to.equal(1.416e-9);
expect(tableData[1].confidenceInterval[0]).to.equal("1078ms");
expect(tableData[1].confidenceInterval[1]).to.equal("1081ms");
expect(tableData[1].isSignificant).to.be.true;
// on-the-line
expect(tableData[2].phaseName).to.equal("stats-on-line-ms-test");
expect(tableData[2].asPercent.percentMin).to.equal(-1.15);
expect(tableData[2].asPercent.percentMedian).to.equal(-0.27);
expect(tableData[2].asPercent.percentMax).to.equal(0.59);
expect(tableData[2].estimatorDelta).to.equal("-7ms");
expect(tableData[2].pValue).to.equal(0.4778);
expect(tableData[2].confidenceInterval[0]).to.equal("-29ms");
expect(tableData[2].confidenceInterval[1]).to.equal("15ms");
expect(tableData[2].isSignificant).to.be.false;
// improvement
expect(tableData[3].phaseName).to.equal("stats-improvement-ms-test");
expect(tableData[3].asPercent.percentMin).to.equal(-4.14);
expect(tableData[3].asPercent.percentMedian).to.equal(-3.14);
expect(tableData[3].asPercent.percentMax).to.equal(-1.87);
expect(tableData[3].estimatorDelta).to.equal("-90ms");
expect(tableData[3].pValue).to.equal(0.00000177);
expect(tableData[3].confidenceInterval[0]).to.equal("-118ms");
expect(tableData[3].confidenceInterval[1]).to.equal("-53ms");
expect(tableData[3].isSignificant).to.be.true;
});
});
================================================
FILE: packages/cli/test/fixtures/experiment/app.js
================================================
Ember.TEMPLATES["application"] = Ember.HTMLBars.template({
id: null,
block:
'{"symbols":[],"statements":[[7,"h1"],[9],[0,"Welcome To Ember"],[10],[0,"\\n"],[7,"div"],[18,"style",[21,"color"]],[9],[0,"\\n"],[1,[21,"outlet"],false],[0,"\\n"],[10]],"hasEval":false}',
meta: {},
});
var MyApp;
(function () {
"use strict";
function renderEnd() {
performance.mark("renderEnd");
requestAnimationFrame(function () {
performance.mark("beforePaint");
requestAnimationFrame(function () {
performance.mark("afterPaint");
performance.measure("document", "navigationStart", "domLoading");
performance.measure("jquery", "domLoading", "jqueryLoaded");
performance.measure("ember", "jqueryLoaded", "emberLoaded");
performance.measure("application", "emberLoaded", "startRouting");
performance.measure("routing", "startRouting", "willTransition");
performance.measure("transition", "willTransition", "didTransition");
performance.measure("render", "didTransition", "renderEnd");
performance.measure("afterRender", "renderEnd", "beforePaint");
performance.measure("paint", "beforePaint", "afterPaint");
});
});
}
MyApp = Ember.Application.extend({
autoboot: false,
}).create();
MyApp.Router = Ember.Router.extend({
location: "none",
setupRouter: function () {
performance.mark("startRouting");
this.on("willTransition", function () {
performance.mark("willTransition");
});
this.on("didTransition", function () {
performance.mark("didTransition");
Ember.run.schedule("afterRender", renderEnd);
});
this._super.apply(this, arguments);
},
});
Ember.run(MyApp, "visit", "/");
})();
================================================
FILE: packages/cli/test/fixtures/experiment/ember.prod.js
================================================
(function() {
/*!
* @overview Ember - JavaScript Application Framework
* @copyright Copyright 2011-2018 Tilde Inc. and contributors
* Portions Copyright 2006-2011 Strobe Inc.
* Portions Copyright 2008-2011 Apple Inc. All rights reserved.
* @license Licensed under MIT license
* See https://raw.github.com/emberjs/ember.js/master/LICENSE
* @version 3.7.0-release+df84897d
*/
/*globals process */
var enifed, requireModule, Ember;
// Used in @ember/-internals/environment/lib/global.js
mainContext = this; // eslint-disable-line no-undef
(function() {
function missingModule(name, referrerName) {
if (referrerName) {
throw new Error('Could not find module ' + name + ' required by: ' + referrerName);
} else {
throw new Error('Could not find module ' + name);
}
}
function internalRequire(_name, referrerName) {
var name = _name;
var mod = registry[name];
if (!mod) {
name = name + '/index';
mod = registry[name];
}
var exports = seen[name];
if (exports !== undefined) {
return exports;
}
exports = seen[name] = {};
if (!mod) {
missingModule(_name, referrerName);
}
var deps = mod.deps;
var callback = mod.callback;
var reified = new Array(deps.length);
for (var i = 0; i < deps.length; i++) {
if (deps[i] === 'exports') {
reified[i] = exports;
} else if (deps[i] === 'require') {
reified[i] = requireModule;
} else {
reified[i] = internalRequire(deps[i], name);
}
}
callback.apply(this, reified);
return exports;
}
var isNode =
typeof window === 'undefined' &&
typeof process !== 'undefined' &&
{}.toString.call(process) === '[object process]';
if (!isNode) {
Ember = this.Ember = this.Ember || {};
}
if (typeof Ember === 'undefined') {
Ember = {};
}
if (typeof Ember.__loader === 'undefined') {
var registry = Object.create(null);
var seen = Object.create(null);
enifed = function(name, deps, callback) {
var value = {};
if (!callback) {
value.deps = [];
value.callback = deps;
} else {
value.deps = deps;
value.callback = callback;
}
registry[name] = value;
};
requireModule = function(name) {
return internalRequire(name, null);
};
// setup `require` module
requireModule['default'] = requireModule;
requireModule.has = function registryHas(moduleName) {
return !!registry[moduleName] || !!registry[moduleName + '/index'];
};
requireModule._eak_seen = registry;
Ember.__loader = {
define: enifed,
require: requireModule,
registry: registry,
};
} else {
enifed = Ember.__loader.define;
requireModule = Ember.__loader.require;
}
})();
enifed('@ember/-internals/browser-environment', ['exports'], function (exports) {
'use strict';
// check if window exists and actually is the global
var hasDom = typeof self === 'object' && self !== null && self.Object === Object && typeof Window !== 'undefined' && self.constructor === Window && typeof document === 'object' && document !== null && self.document === document && typeof location === 'object' && location !== null && self.location === location && typeof history === 'object' && history !== null && self.history === history && typeof navigator === 'object' && navigator !== null && self.navigator === navigator && typeof navigator.userAgent === 'string';
const window = hasDom ? self : null;
const location$1 = hasDom ? self.location : null;
const history$1 = hasDom ? self.history : null;
const userAgent = hasDom ? self.navigator.userAgent : 'Lynx (textmode)';
const isChrome = hasDom ? !!window.chrome && !window.opera : false;
const isFirefox = hasDom ? typeof InstallTrigger !== 'undefined' : false;
exports.window = window;
exports.location = location$1;
exports.history = history$1;
exports.userAgent = userAgent;
exports.isChrome = isChrome;
exports.isFirefox = isFirefox;
exports.hasDOM = hasDom;
});
enifed('@ember/-internals/console/index', ['exports', '@ember/debug', '@ember/deprecated-features'], function (exports, _debug, _deprecatedFeatures) {
'use strict';
// Deliver message that the function is deprecated
const DEPRECATION_MESSAGE = 'Use of Ember.Logger is deprecated. Please use `console` for logging.';
const DEPRECATION_ID = 'ember-console.deprecate-logger';
const DEPRECATION_URL = 'https://emberjs.com/deprecations/v3.x#toc_use-console-rather-than-ember-logger';
/**
@module ember
*/
/**
Inside Ember-Metal, simply uses the methods from `imports.console`.
Override this to provide more robust logging functionality.
@class Logger
@deprecated Use 'console' instead
@namespace Ember
@public
*/
let DEPRECATED_LOGGER;
if (_deprecatedFeatures.LOGGER) {
DEPRECATED_LOGGER = {
/**
Logs the arguments to the console.
You can pass as many arguments as you want and they will be joined together with a space.
```javascript
var foo = 1;
Ember.Logger.log('log value of foo:', foo);
// "log value of foo: 1" will be printed to the console
```
@method log
@for Ember.Logger
@param {*} arguments
@public
*/
log() {
false && !false && (0, _debug.deprecate)(DEPRECATION_MESSAGE, false, {
id: DEPRECATION_ID,
until: '4.0.0',
url: DEPRECATION_URL
});
return console.log(...arguments); // eslint-disable-line no-console
},
/**
Prints the arguments to the console with a warning icon.
You can pass as many arguments as you want and they will be joined together with a space.
```javascript
Ember.Logger.warn('Something happened!');
// "Something happened!" will be printed to the console with a warning icon.
```
@method warn
@for Ember.Logger
@param {*} arguments
@public
*/
warn() {
false && !false && (0, _debug.deprecate)(DEPRECATION_MESSAGE, false, {
id: DEPRECATION_ID,
until: '4.0.0',
url: DEPRECATION_URL
});
return console.warn(...arguments); // eslint-disable-line no-console
},
/**
Prints the arguments to the console with an error icon, red text and a stack trace.
You can pass as many arguments as you want and they will be joined together with a space.
```javascript
Ember.Logger.error('Danger! Danger!');
// "Danger! Danger!" will be printed to the console in red text.
```
@method error
@for Ember.Logger
@param {*} arguments
@public
*/
error() {
false && !false && (0, _debug.deprecate)(DEPRECATION_MESSAGE, false, {
id: DEPRECATION_ID,
until: '4.0.0',
url: DEPRECATION_URL
});
return console.error(...arguments); // eslint-disable-line no-console
},
/**
Logs the arguments to the console.
You can pass as many arguments as you want and they will be joined together with a space.
```javascript
var foo = 1;
Ember.Logger.info('log value of foo:', foo);
// "log value of foo: 1" will be printed to the console
```
@method info
@for Ember.Logger
@param {*} arguments
@public
*/
info() {
false && !false && (0, _debug.deprecate)(DEPRECATION_MESSAGE, false, {
id: DEPRECATION_ID,
until: '4.0.0',
url: DEPRECATION_URL
});
return console.info(...arguments); // eslint-disable-line no-console
},
/**
Logs the arguments to the console in blue text.
You can pass as many arguments as you want and they will be joined together with a space.
```javascript
var foo = 1;
Ember.Logger.debug('log value of foo:', foo);
// "log value of foo: 1" will be printed to the console
```
@method debug
@for Ember.Logger
@param {*} arguments
@public
*/
debug() {
false && !false && (0, _debug.deprecate)(DEPRECATION_MESSAGE, false, {
id: DEPRECATION_ID,
until: '4.0.0',
url: DEPRECATION_URL
});
/* eslint-disable no-console */
if (console.debug) {
return console.debug(...arguments);
}
return console.info(...arguments);
/* eslint-enable no-console */
},
/**
If the value passed into `Ember.Logger.assert` is not truthy it will throw an error with a stack trace.
```javascript
Ember.Logger.assert(true); // undefined
Ember.Logger.assert(true === false); // Throws an Assertion failed error.
Ember.Logger.assert(true === false, 'Something invalid'); // Throws an Assertion failed error with message.
```
@method assert
@for Ember.Logger
@param {Boolean} bool Value to test
@param {String} message Assertion message on failed
@public
*/
assert() {
false && !false && (0, _debug.deprecate)(DEPRECATION_MESSAGE, false, {
id: DEPRECATION_ID,
until: '4.0.0',
url: DEPRECATION_URL
});
return console.assert(...arguments); // eslint-disable-line no-console
}
};
}
exports.default = DEPRECATED_LOGGER;
});
enifed('@ember/-internals/container', ['exports', '@ember/-internals/owner', '@ember/-internals/utils', '@ember/debug', '@ember/polyfills'], function (exports, _owner, _utils, _debug, _polyfills) {
'use strict';
exports.FACTORY_FOR = exports.Container = exports.privatize = exports.Registry = undefined;
let leakTracking;
let containers;
if (false /* DEBUG */) {
// requires v8
// chrome --js-flags="--allow-natives-syntax --expose-gc"
// node --allow-natives-syntax --expose-gc
try {
if (typeof gc === 'function') {
leakTracking = (() => {
// avoid syntax errors when --allow-natives-syntax not present
let GetWeakSetValues = new Function('weakSet', 'return %GetWeakSetValues(weakSet, 0)');
containers = new WeakSet();
return {
hasContainers() {
gc();
return GetWeakSetValues(containers).length > 0;
},
reset() {
let values = GetWeakSetValues(containers);
for (let i = 0; i < values.length; i++) {
containers.delete(values[i]);
}
}
};
})();
}
} catch (e) {
// ignore
}
}
/**
A container used to instantiate and cache objects.
Every `Container` must be associated with a `Registry`, which is referenced
to determine the factory and options that should be used to instantiate
objects.
The public API for `Container` is still in flux and should not be considered
stable.
@private
@class Container
*/
class Container {
constructor(registry, options = {}) {
this.registry = registry;
this.owner = options.owner || null;
this.cache = (0, _utils.dictionary)(options.cache || null);
this.factoryManagerCache = (0, _utils.dictionary)(options.factoryManagerCache || null);
this.isDestroyed = false;
this.isDestroying = false;
if (false /* DEBUG */) {
this.validationCache = (0, _utils.dictionary)(options.validationCache || null);
if (containers !== undefined) {
containers.add(this);
}
}
}
/**
@private
@property registry
@type Registry
@since 1.11.0
*/
/**
@private
@property cache
@type InheritingDict
*/
/**
@private
@property validationCache
@type InheritingDict
*/
/**
Given a fullName return a corresponding instance.
The default behavior is for lookup to return a singleton instance.
The singleton is scoped to the container, allowing multiple containers
to all have their own locally scoped singletons.
```javascript
let registry = new Registry();
let container = registry.container();
registry.register('api:twitter', Twitter);
let twitter = container.lookup('api:twitter');
twitter instanceof Twitter; // => true
// by default the container will return singletons
let twitter2 = container.lookup('api:twitter');
twitter2 instanceof Twitter; // => true
twitter === twitter2; //=> true
```
If singletons are not wanted, an optional flag can be provided at lookup.
```javascript
let registry = new Registry();
let container = registry.container();
registry.register('api:twitter', Twitter);
let twitter = container.lookup('api:twitter', { singleton: false });
let twitter2 = container.lookup('api:twitter', { singleton: false });
twitter === twitter2; //=> false
```
@private
@method lookup
@param {String} fullName
@param {Object} [options]
@param {String} [options.source] The fullname of the request source (used for local lookup)
@return {any}
*/
lookup(fullName, options) {
false && !!this.isDestroyed && (0, _debug.assert)('expected container not to be destroyed', !this.isDestroyed);
false && !this.registry.isValidFullName(fullName) && (0, _debug.assert)('fullName must be a proper full name', this.registry.isValidFullName(fullName));
return lookup(this, this.registry.normalize(fullName), options);
}
/**
A depth first traversal, destroying the container, its descendant containers and all
their managed objects.
@private
@method destroy
*/
destroy() {
destroyDestroyables(this);
this.isDestroying = true;
}
finalizeDestroy() {
resetCache(this);
this.isDestroyed = true;
}
/**
Clear either the entire cache or just the cache for a particular key.
@private
@method reset
@param {String} fullName optional key to reset; if missing, resets everything
*/
reset(fullName) {
if (this.isDestroyed) return;
if (fullName === undefined) {
destroyDestroyables(this);
resetCache(this);
} else {
resetMember(this, this.registry.normalize(fullName));
}
}
/**
Returns an object that can be used to provide an owner to a
manually created instance.
@private
@method ownerInjection
@returns { Object }
*/
ownerInjection() {
return { [_owner.OWNER]: this.owner };
}
/**
Given a fullName, return the corresponding factory. The consumer of the factory
is responsible for the destruction of any factory instances, as there is no
way for the container to ensure instances are destroyed when it itself is
destroyed.
@public
@method factoryFor
@param {String} fullName
@param {Object} [options]
@param {String} [options.source] The fullname of the request source (used for local lookup)
@return {any}
*/
factoryFor(fullName, options = {}) {
false && !!this.isDestroyed && (0, _debug.assert)('expected container not to be destroyed', !this.isDestroyed);
let normalizedName = this.registry.normalize(fullName);
false && !this.registry.isValidFullName(normalizedName) && (0, _debug.assert)('fullName must be a proper full name', this.registry.isValidFullName(normalizedName));
false && !(false /* EMBER_MODULE_UNIFICATION */ || !options.namespace) && (0, _debug.assert)('EMBER_MODULE_UNIFICATION must be enabled to pass a namespace option to factoryFor', false || !options.namespace);
if (options.source || options.namespace) {
normalizedName = this.registry.expandLocalLookup(fullName, options);
if (!normalizedName) {
return;
}
}
return factoryFor(this, normalizedName, fullName);
}
}
if (false /* DEBUG */) {
Container._leakTracking = leakTracking;
}
/*
* Wrap a factory manager in a proxy which will not permit properties to be
* set on the manager.
*/
function wrapManagerInDeprecationProxy(manager) {
if (_utils.HAS_NATIVE_PROXY) {
let validator = {
set(_obj, prop) {
throw new Error(`You attempted to set "${prop}" on a factory manager created by container#factoryFor. A factory manager is a read-only construct.`);
}
};
// Note:
// We have to proxy access to the manager here so that private property
// access doesn't cause the above errors to occur.
let m = manager;
let proxiedManager = {
class: m.class,
create(props) {
return m.create(props);
}
};
let proxy = new Proxy(proxiedManager, validator);
FACTORY_FOR.set(proxy, manager);
}
return manager;
}
function isSingleton(container, fullName) {
return container.registry.getOption(fullName, 'singleton') !== false;
}
function isInstantiatable(container, fullName) {
return container.registry.getOption(fullName, 'instantiate') !== false;
}
function lookup(container, fullName, options = {}) {
false && !(false /* EMBER_MODULE_UNIFICATION */ || !options.namespace) && (0, _debug.assert)('EMBER_MODULE_UNIFICATION must be enabled to pass a namespace option to lookup', false || !options.namespace);
let normalizedName = fullName;
if (options.source || options.namespace) {
normalizedName = container.registry.expandLocalLookup(fullName, options);
if (!normalizedName) {
return;
}
}
if (options.singleton !== false) {
let cached = container.cache[normalizedName];
if (cached !== undefined) {
return cached;
}
}
return instantiateFactory(container, normalizedName, fullName, options);
}
function factoryFor(container, normalizedName, fullName) {
let cached = container.factoryManagerCache[normalizedName];
if (cached !== undefined) {
return cached;
}
let factory = container.registry.resolve(normalizedName);
if (factory === undefined) {
return;
}
if (false /* DEBUG */ && factory && typeof factory._onLookup === 'function') {
factory._onLookup(fullName);
}
let manager = new FactoryManager(container, factory, fullName, normalizedName);
if (false /* DEBUG */) {
manager = wrapManagerInDeprecationProxy(manager);
}
container.factoryManagerCache[normalizedName] = manager;
return manager;
}
function isSingletonClass(container, fullName, { instantiate, singleton }) {
return singleton !== false && !instantiate && isSingleton(container, fullName) && !isInstantiatable(container, fullName);
}
function isSingletonInstance(container, fullName, { instantiate, singleton }) {
return singleton !== false && instantiate !== false && isSingleton(container, fullName) && isInstantiatable(container, fullName);
}
function isFactoryClass(container, fullname, { instantiate, singleton }) {
return instantiate === false && (singleton === false || !isSingleton(container, fullname)) && !isInstantiatable(container, fullname);
}
function isFactoryInstance(container, fullName, { instantiate, singleton }) {
return instantiate !== false && (singleton !== false || isSingleton(container, fullName)) && isInstantiatable(container, fullName);
}
function instantiateFactory(container, normalizedName, fullName, options) {
let factoryManager = factoryFor(container, normalizedName, fullName);
if (factoryManager === undefined) {
return;
}
// SomeClass { singleton: true, instantiate: true } | { singleton: true } | { instantiate: true } | {}
// By default majority of objects fall into this case
if (isSingletonInstance(container, fullName, options)) {
return container.cache[normalizedName] = factoryManager.create();
}
// SomeClass { singleton: false, instantiate: true }
if (isFactoryInstance(container, fullName, options)) {
return factoryManager.create();
}
// SomeClass { singleton: true, instantiate: false } | { instantiate: false } | { singleton: false, instantiation: false }
if (isSingletonClass(container, fullName, options) || isFactoryClass(container, fullName, options)) {
return factoryManager.class;
}
throw new Error('Could not create factory');
}
function processInjections(container, injections, result) {
if (false /* DEBUG */) {
container.registry.validateInjections(injections);
}
let hash = result.injections;
if (hash === undefined) {
hash = result.injections = {};
}
for (let i = 0; i < injections.length; i++) {
let { property, specifier, source } = injections[i];
if (source) {
hash[property] = lookup(container, specifier, { source });
} else {
hash[property] = lookup(container, specifier);
}
if (!result.isDynamic) {
result.isDynamic = !isSingleton(container, specifier);
}
}
}
function buildInjections(container, typeInjections, injections) {
let result = {
injections: undefined,
isDynamic: false
};
if (typeInjections !== undefined) {
processInjections(container, typeInjections, result);
}
if (injections !== undefined) {
processInjections(container, injections, result);
}
return result;
}
function injectionsFor(container, fullName) {
let registry = container.registry;
let [type] = fullName.split(':');
let typeInjections = registry.getTypeInjections(type);
let injections = registry.getInjections(fullName);
return buildInjections(container, typeInjections, injections);
}
function destroyDestroyables(container) {
let cache = container.cache;
let keys = Object.keys(cache);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
let value = cache[key];
if (value.destroy) {
value.destroy();
}
}
}
function resetCache(container) {
container.cache = (0, _utils.dictionary)(null);
container.factoryManagerCache = (0, _utils.dictionary)(null);
}
function resetMember(container, fullName) {
let member = container.cache[fullName];
delete container.factoryManagerCache[fullName];
if (member) {
delete container.cache[fullName];
if (member.destroy) {
member.destroy();
}
}
}
const FACTORY_FOR = new WeakMap();
class FactoryManager {
constructor(container, factory, fullName, normalizedName) {
this.container = container;
this.owner = container.owner;
this.class = factory;
this.fullName = fullName;
this.normalizedName = normalizedName;
this.madeToString = undefined;
this.injections = undefined;
FACTORY_FOR.set(this, this);
}
toString() {
if (this.madeToString === undefined) {
this.madeToString = this.container.registry.makeToString(this.class, this.fullName);
}
return this.madeToString;
}
create(options) {
let injectionsCache = this.injections;
if (injectionsCache === undefined) {
let { injections, isDynamic } = injectionsFor(this.container, this.normalizedName);
injectionsCache = injections;
if (!isDynamic) {
this.injections = injections;
}
}
let props = injectionsCache;
if (options !== undefined) {
props = (0, _polyfills.assign)({}, injectionsCache, options);
}
if (false /* DEBUG */) {
let lazyInjections;
let validationCache = this.container.validationCache;
// Ensure that all lazy injections are valid at instantiation time
if (!validationCache[this.fullName] && this.class && typeof this.class._lazyInjections === 'function') {
lazyInjections = this.class._lazyInjections();
lazyInjections = this.container.registry.normalizeInjectionsHash(lazyInjections);
this.container.registry.validateInjections(lazyInjections);
}
validationCache[this.fullName] = true;
}
if (!this.class.create) {
throw new Error(`Failed to create an instance of '${this.normalizedName}'. Most likely an improperly defined class or` + ` an invalid module export.`);
}
// required to allow access to things like
// the customized toString, _debugContainerKey,
// owner, etc. without a double extend and without
// modifying the objects properties
if (typeof this.class._initFactory === 'function') {
this.class._initFactory(this);
} else {
// in the non-EmberObject case we need to still setOwner
// this is required for supporting glimmer environment and
// template instantiation which rely heavily on
// `options[OWNER]` being passed into `create`
// TODO: clean this up, and remove in future versions
if (options === undefined || props === undefined) {
// avoid mutating `props` here since they are the cached injections
props = (0, _polyfills.assign)({}, props);
}
(0, _owner.setOwner)(props, this.owner);
}
let instance = this.class.create(props);
FACTORY_FOR.set(instance, this);
return instance;
}
}
const VALID_FULL_NAME_REGEXP = /^[^:]+:[^:]+$/;
/**
A registry used to store factory and option information keyed
by type.
A `Registry` stores the factory and option information needed by a
`Container` to instantiate and cache objects.
The API for `Registry` is still in flux and should not be considered stable.
@private
@class Registry
@since 1.11.0
*/
class Registry {
constructor(options = {}) {
this.fallback = options.fallback || null;
this.resolver = options.resolver || null;
this.registrations = (0, _utils.dictionary)(options.registrations || null);
this._typeInjections = (0, _utils.dictionary)(null);
this._injections = (0, _utils.dictionary)(null);
this._localLookupCache = Object.create(null);
this._normalizeCache = (0, _utils.dictionary)(null);
this._resolveCache = (0, _utils.dictionary)(null);
this._failSet = new Set();
this._options = (0, _utils.dictionary)(null);
this._typeOptions = (0, _utils.dictionary)(null);
}
/**
A backup registry for resolving registrations when no matches can be found.
@private
@property fallback
@type Registry
*/
/**
An object that has a `resolve` method that resolves a name.
@private
@property resolver
@type Resolver
*/
/**
@private
@property registrations
@type InheritingDict
*/
/**
@private
@property _typeInjections
@type InheritingDict
*/
/**
@private
@property _injections
@type InheritingDict
*/
/**
@private
@property _normalizeCache
@type InheritingDict
*/
/**
@private
@property _resolveCache
@type InheritingDict
*/
/**
@private
@property _options
@type InheritingDict
*/
/**
@private
@property _typeOptions
@type InheritingDict
*/
/**
Creates a container based on this registry.
@private
@method container
@param {Object} options
@return {Container} created container
*/
container(options) {
return new Container(this, options);
}
/**
Registers a factory for later injection.
Example:
```javascript
let registry = new Registry();
registry.register('model:user', Person, {singleton: false });
registry.register('fruit:favorite', Orange);
registry.register('communication:main', Email, {singleton: false});
```
@private
@method register
@param {String} fullName
@param {Function} factory
@param {Object} options
*/
register(fullName, factory, options = {}) {
false && !this.isValidFullName(fullName) && (0, _debug.assert)('fullName must be a proper full name', this.isValidFullName(fullName));
false && !(factory !== undefined) && (0, _debug.assert)(`Attempting to register an unknown factory: '${fullName}'`, factory !== undefined);
let normalizedName = this.normalize(fullName);
false && !!this._resolveCache[normalizedName] && (0, _debug.assert)(`Cannot re-register: '${fullName}', as it has already been resolved.`, !this._resolveCache[normalizedName]);
this._failSet.delete(normalizedName);
this.registrations[normalizedName] = factory;
this._options[normalizedName] = options;
}
/**
Unregister a fullName
```javascript
let registry = new Registry();
registry.register('model:user', User);
registry.resolve('model:user').create() instanceof User //=> true
registry.unregister('model:user')
registry.resolve('model:user') === undefined //=> true
```
@private
@method unregister
@param {String} fullName
*/
unregister(fullName) {
false && !this.isValidFullName(fullName) && (0, _debug.assert)('fullName must be a proper full name', this.isValidFullName(fullName));
let normalizedName = this.normalize(fullName);
this._localLookupCache = Object.create(null);
delete this.registrations[normalizedName];
delete this._resolveCache[normalizedName];
delete this._options[normalizedName];
this._failSet.delete(normalizedName);
}
/**
Given a fullName return the corresponding factory.
By default `resolve` will retrieve the factory from
the registry.
```javascript
let registry = new Registry();
registry.register('api:twitter', Twitter);
registry.resolve('api:twitter') // => Twitter
```
Optionally the registry can be provided with a custom resolver.
If provided, `resolve` will first provide the custom resolver
the opportunity to resolve the fullName, otherwise it will fallback
to the registry.
```javascript
let registry = new Registry();
registry.resolver = function(fullName) {
// lookup via the module system of choice
};
// the twitter factory is added to the module system
registry.resolve('api:twitter') // => Twitter
```
@private
@method resolve
@param {String} fullName
@param {Object} [options]
@param {String} [options.source] the fullname of the request source (used for local lookups)
@return {Function} fullName's factory
*/
resolve(fullName, options) {
let factory = resolve(this, this.normalize(fullName), options);
if (factory === undefined && this.fallback !== null) {
factory = this.fallback.resolve(...arguments);
}
return factory;
}
/**
A hook that can be used to describe how the resolver will
attempt to find the factory.
For example, the default Ember `.describe` returns the full
class name (including namespace) where Ember's resolver expects
to find the `fullName`.
@private
@method describe
@param {String} fullName
@return {string} described fullName
*/
describe(fullName) {
if (this.resolver !== null && this.resolver.lookupDescription) {
return this.resolver.lookupDescription(fullName);
} else if (this.fallback !== null) {
return this.fallback.describe(fullName);
} else {
return fullName;
}
}
/**
A hook to enable custom fullName normalization behavior
@private
@method normalizeFullName
@param {String} fullName
@return {string} normalized fullName
*/
normalizeFullName(fullName) {
if (this.resolver !== null && this.resolver.normalize) {
return this.resolver.normalize(fullName);
} else if (this.fallback !== null) {
return this.fallback.normalizeFullName(fullName);
} else {
return fullName;
}
}
/**
Normalize a fullName based on the application's conventions
@private
@method normalize
@param {String} fullName
@return {string} normalized fullName
*/
normalize(fullName) {
return this._normalizeCache[fullName] || (this._normalizeCache[fullName] = this.normalizeFullName(fullName));
}
/**
@method makeToString
@private
@param {any} factory
@param {string} fullName
@return {function} toString function
*/
makeToString(factory, fullName) {
if (this.resolver !== null && this.resolver.makeToString) {
return this.resolver.makeToString(factory, fullName);
} else if (this.fallback !== null) {
return this.fallback.makeToString(factory, fullName);
} else {
return factory.toString();
}
}
/**
Given a fullName check if the container is aware of its factory
or singleton instance.
@private
@method has
@param {String} fullName
@param {Object} [options]
@param {String} [options.source] the fullname of the request source (used for local lookups)
@return {Boolean}
*/
has(fullName, options) {
if (!this.isValidFullName(fullName)) {
return false;
}
let source = options && options.source && this.normalize(options.source);
let namespace = options && options.namespace || undefined;
return has(this, this.normalize(fullName), source, namespace);
}
/**
Allow registering options for all factories of a type.
```javascript
let registry = new Registry();
let container = registry.container();
// if all of type `connection` must not be singletons
registry.optionsForType('connection', { singleton: false });
registry.register('connection:twitter', TwitterConnection);
registry.register('connection:facebook', FacebookConnection);
let twitter = container.lookup('connection:twitter');
let twitter2 = container.lookup('connection:twitter');
twitter === twitter2; // => false
let facebook = container.lookup('connection:facebook');
let facebook2 = container.lookup('connection:facebook');
facebook === facebook2; // => false
```
@private
@method optionsForType
@param {String} type
@param {Object} options
*/
optionsForType(type, options) {
this._typeOptions[type] = options;
}
getOptionsForType(type) {
let optionsForType = this._typeOptions[type];
if (optionsForType === undefined && this.fallback !== null) {
optionsForType = this.fallback.getOptionsForType(type);
}
return optionsForType;
}
/**
@private
@method options
@param {String} fullName
@param {Object} options
*/
options(fullName, options) {
let normalizedName = this.normalize(fullName);
this._options[normalizedName] = options;
}
getOptions(fullName) {
let normalizedName = this.normalize(fullName);
let options = this._options[normalizedName];
if (options === undefined && this.fallback !== null) {
options = this.fallback.getOptions(fullName);
}
return options;
}
getOption(fullName, optionName) {
let options = this._options[fullName];
if (options !== undefined && options[optionName] !== undefined) {
return options[optionName];
}
let type = fullName.split(':')[0];
options = this._typeOptions[type];
if (options && options[optionName] !== undefined) {
return options[optionName];
} else if (this.fallback !== null) {
return this.fallback.getOption(fullName, optionName);
}
return undefined;
}
/**
Used only via `injection`.
Provides a specialized form of injection, specifically enabling
all objects of one type to be injected with a reference to another
object.
For example, provided each object of type `controller` needed a `router`.
one would do the following:
```javascript
let registry = new Registry();
let container = registry.container();
registry.register('router:main', Router);
registry.register('controller:user', UserController);
registry.register('controller:post', PostController);
registry.typeInjection('controller', 'router', 'router:main');
let user = container.lookup('controller:user');
let post = container.lookup('controller:post');
user.router instanceof Router; //=> true
post.router instanceof Router; //=> true
// both controllers share the same router
user.router === post.router; //=> true
```
@private
@method typeInjection
@param {String} type
@param {String} property
@param {String} fullName
*/
typeInjection(type, property, fullName) {
false && !this.isValidFullName(fullName) && (0, _debug.assert)('fullName must be a proper full name', this.isValidFullName(fullName));
let fullNameType = fullName.split(':')[0];
false && !(fullNameType !== type) && (0, _debug.assert)(`Cannot inject a '${fullName}' on other ${type}(s).`, fullNameType !== type);
let injections = this._typeInjections[type] || (this._typeInjections[type] = []);
injections.push({ property, specifier: fullName });
}
/**
Defines injection rules.
These rules are used to inject dependencies onto objects when they
are instantiated.
Two forms of injections are possible:
* Injecting one fullName on another fullName
* Injecting one fullName on a type
Example:
```javascript
let registry = new Registry();
let container = registry.container();
registry.register('source:main', Source);
registry.register('model:user', User);
registry.register('model:post', Post);
// injecting one fullName on another fullName
// eg. each user model gets a post model
registry.injection('model:user', 'post', 'model:post');
// injecting one fullName on another type
registry.injection('model', 'source', 'source:main');
let user = container.lookup('model:user');
let post = container.lookup('model:post');
user.source instanceof Source; //=> true
post.source instanceof Source; //=> true
user.post instanceof Post; //=> true
// and both models share the same source
user.source === post.source; //=> true
```
@private
@method injection
@param {String} factoryName
@param {String} property
@param {String} injectionName
*/
injection(fullName, property, injectionName) {
false && !this.isValidFullName(injectionName) && (0, _debug.assert)(`Invalid injectionName, expected: 'type:name' got: ${injectionName}`, this.isValidFullName(injectionName));
let normalizedInjectionName = this.normalize(injectionName);
if (fullName.indexOf(':') === -1) {
return this.typeInjection(fullName, property, normalizedInjectionName);
}
false && !this.isValidFullName(fullName) && (0, _debug.assert)('fullName must be a proper full name', this.isValidFullName(fullName));
let normalizedName = this.normalize(fullName);
let injections = this._injections[normalizedName] || (this._injections[normalizedName] = []);
injections.push({ property, specifier: normalizedInjectionName });
}
/**
@private
@method knownForType
@param {String} type the type to iterate over
*/
knownForType(type) {
let localKnown = (0, _utils.dictionary)(null);
let registeredNames = Object.keys(this.registrations);
for (let index = 0; index < registeredNames.length; index++) {
let fullName = registeredNames[index];
let itemType = fullName.split(':')[0];
if (itemType === type) {
localKnown[fullName] = true;
}
}
let fallbackKnown, resolverKnown;
if (this.fallback !== null) {
fallbackKnown = this.fallback.knownForType(type);
}
if (this.resolver !== null && this.resolver.knownForType) {
resolverKnown = this.resolver.knownForType(type);
}
return (0, _polyfills.assign)({}, fallbackKnown, localKnown, resolverKnown);
}
isValidFullName(fullName) {
return VALID_FULL_NAME_REGEXP.test(fullName);
}
getInjections(fullName) {
let injections = this._injections[fullName];
if (this.fallback !== null) {
let fallbackInjections = this.fallback.getInjections(fullName);
if (fallbackInjections !== undefined) {
injections = injections === undefined ? fallbackInjections : injections.concat(fallbackInjections);
}
}
return injections;
}
getTypeInjections(type) {
let injections = this._typeInjections[type];
if (this.fallback !== null) {
let fallbackInjections = this.fallback.getTypeInjections(type);
if (fallbackInjections !== undefined) {
injections = injections === undefined ? fallbackInjections : injections.concat(fallbackInjections);
}
}
return injections;
}
/**
Given a fullName and a source fullName returns the fully resolved
fullName. Used to allow for local lookup.
```javascript
let registry = new Registry();
// the twitter factory is added to the module system
registry.expandLocalLookup('component:post-title', { source: 'template:post' }) // => component:post/post-title
```
@private
@method expandLocalLookup
@param {String} fullName
@param {Object} [options]
@param {String} [options.source] the fullname of the request source (used for local lookups)
@return {String} fullName
*/
expandLocalLookup(fullName, options) {
if (this.resolver !== null && this.resolver.expandLocalLookup) {
false && !this.isValidFullName(fullName) && (0, _debug.assert)('fullName must be a proper full name', this.isValidFullName(fullName));
false && !(!options.source || this.isValidFullName(options.source)) && (0, _debug.assert)('options.source must be a proper full name', !options.source || this.isValidFullName(options.source));
let normalizedFullName = this.normalize(fullName);
let normalizedSource = this.normalize(options.source);
return expandLocalLookup(this, normalizedFullName, normalizedSource, options.namespace);
} else if (this.fallback !== null) {
return this.fallback.expandLocalLookup(fullName, options);
} else {
return null;
}
}
}
if (false /* DEBUG */) {
const proto = Registry.prototype;
proto.normalizeInjectionsHash = function (hash) {
let injections = [];
for (let key in hash) {
if (hash.hasOwnProperty(key)) {
let { specifier, source, namespace } = hash[key];
false && !this.isValidFullName(specifier) && (0, _debug.assert)(`Expected a proper full name, given '${specifier}'`, this.isValidFullName(specifier));
injections.push({
property: key,
specifier,
source,
namespace
});
}
}
return injections;
};
proto.validateInjections = function (injections) {
if (!injections) {
return;
}
for (let i = 0; i < injections.length; i++) {
let { specifier, source, namespace } = injections[i];
false && !this.has(specifier, { source, namespace }) && (0, _debug.assert)(`Attempting to inject an unknown injection: '${specifier}'`, this.has(specifier, { source, namespace }));
}
};
}
function expandLocalLookup(registry, normalizedName, normalizedSource, namespace) {
let cache = registry._localLookupCache;
let normalizedNameCache = cache[normalizedName];
if (!normalizedNameCache) {
normalizedNameCache = cache[normalizedName] = Object.create(null);
}
let cacheKey = namespace || normalizedSource;
let cached = normalizedNameCache[cacheKey];
if (cached !== undefined) {
return cached;
}
let expanded = registry.resolver.expandLocalLookup(normalizedName, normalizedSource, namespace);
return normalizedNameCache[cacheKey] = expanded;
}
function resolve(registry, _normalizedName, options) {
let normalizedName = _normalizedName;
// when `source` is provided expand normalizedName
// and source into the full normalizedName
if (options !== undefined && (options.source || options.namespace)) {
normalizedName = registry.expandLocalLookup(_normalizedName, options);
if (!normalizedName) {
return;
}
}
let cached = registry._resolveCache[normalizedName];
if (cached !== undefined) {
return cached;
}
if (registry._failSet.has(normalizedName)) {
return;
}
let resolved;
if (registry.resolver) {
resolved = registry.resolver.resolve(normalizedName);
}
if (resolved === undefined) {
resolved = registry.registrations[normalizedName];
}
if (resolved === undefined) {
registry._failSet.add(normalizedName);
} else {
registry._resolveCache[normalizedName] = resolved;
}
return resolved;
}
function has(registry, fullName, source, namespace) {
return registry.resolve(fullName, { source, namespace }) !== undefined;
}
const privateNames = (0, _utils.dictionary)(null);
const privateSuffix = `${Math.random()}${Date.now()}`.replace('.', '');
function privatize([fullName]) {
let name = privateNames[fullName];
if (name) {
return name;
}
let [type, rawName] = fullName.split(':');
return privateNames[fullName] = (0, _utils.intern)(`${type}:${rawName}-${privateSuffix}`);
}
/*
Public API for the container is still in flux.
The public API, specified on the application namespace should be considered the stable API.
// @module container
@private
*/
exports.Registry = Registry;
exports.privatize = privatize;
exports.Container = Container;
exports.FACTORY_FOR = FACTORY_FOR;
});
enifed('@ember/-internals/environment', ['exports'], function (exports) {
'use strict';
// from lodash to catch fake globals
function checkGlobal(value) {
return value && value.Object === Object ? value : undefined;
}
// element ids can ruin global miss checks
function checkElementIdShadowing(value) {
return value && value.nodeType === undefined ? value : undefined;
}
// export real global
var global$1 = checkGlobal(checkElementIdShadowing(typeof global === 'object' && global)) || checkGlobal(typeof self === 'object' && self) || checkGlobal(typeof window === 'object' && window) || typeof mainContext !== 'undefined' && mainContext || // set before strict mode in Ember loader/wrapper
new Function('return this')(); // eval outside of strict mode
// legacy imports/exports/lookup stuff (should we keep this??)
const context = function (global, Ember) {
return Ember === undefined ? { imports: global, exports: global, lookup: global } : {
// import jQuery
imports: Ember.imports || global,
// export Ember
exports: Ember.exports || global,
// search for Namespaces
lookup: Ember.lookup || global
};
}(global$1, global$1.Ember);
function getLookup() {
return context.lookup;
}
function setLookup(value) {
context.lookup = value;
}
/**
The hash of environment variables used to control various configuration
settings. To specify your own or override default settings, add the
desired properties to a global hash named `EmberENV` (or `ENV` for
backwards compatibility with earlier versions of Ember). The `EmberENV`
hash must be created before loading Ember.
@class EmberENV
@type Object
@public
*/
const ENV = {
ENABLE_OPTIONAL_FEATURES: false,
/**
Determines whether Ember should add to `Array`, `Function`, and `String`
native object prototypes, a few extra methods in order to provide a more
friendly API.
We generally recommend leaving this option set to true however, if you need
to turn it off, you can add the configuration property
`EXTEND_PROTOTYPES` to `EmberENV` and set it to `false`.
Note, when disabled (the default configuration for Ember Addons), you will
instead have to access all methods and functions from the Ember
namespace.
@property EXTEND_PROTOTYPES
@type Boolean
@default true
@for EmberENV
@public
*/
EXTEND_PROTOTYPES: {
Array: true,
Function: true,
String: true
},
/**
The `LOG_STACKTRACE_ON_DEPRECATION` property, when true, tells Ember to log
a full stack trace during deprecation warnings.
@property LOG_STACKTRACE_ON_DEPRECATION
@type Boolean
@default true
@for EmberENV
@public
*/
LOG_STACKTRACE_ON_DEPRECATION: true,
/**
The `LOG_VERSION` property, when true, tells Ember to log versions of all
dependent libraries in use.
@property LOG_VERSION
@type Boolean
@default true
@for EmberENV
@public
*/
LOG_VERSION: true,
RAISE_ON_DEPRECATION: false,
STRUCTURED_PROFILE: false,
/**
Whether to insert a `` wrapper around the
application template. See RFC #280.
This is not intended to be set directly, as the implementation may change in
the future. Use `@ember/optional-features` instead.
@property _APPLICATION_TEMPLATE_WRAPPER
@for EmberENV
@type Boolean
@default true
@private
*/
_APPLICATION_TEMPLATE_WRAPPER: true,
/**
Whether to use Glimmer Component semantics (as opposed to the classic "Curly"
components semantics) for template-only components. See RFC #278.
This is not intended to be set directly, as the implementation may change in
the future. Use `@ember/optional-features` instead.
@property _TEMPLATE_ONLY_GLIMMER_COMPONENTS
@for EmberENV
@type Boolean
@default false
@private
*/
_TEMPLATE_ONLY_GLIMMER_COMPONENTS: false,
/**
Whether the app is using jQuery. See RFC #294.
This is not intended to be set directly, as the implementation may change in
the future. Use `@ember/optional-features` instead.
@property _JQUERY_INTEGRATION
@for EmberENV
@type Boolean
@default true
@private
*/
_JQUERY_INTEGRATION: true,
EMBER_LOAD_HOOKS: {},
FEATURES: {}
};
(EmberENV => {
if (typeof EmberENV !== 'object' || EmberENV === null) return;
for (let flag in EmberENV) {
if (!EmberENV.hasOwnProperty(flag) || flag === 'EXTEND_PROTOTYPES' || flag === 'EMBER_LOAD_HOOKS') continue;
let defaultValue = ENV[flag];
if (defaultValue === true) {
ENV[flag] = EmberENV[flag] !== false;
} else if (defaultValue === false) {
ENV[flag] = EmberENV[flag] === true;
}
}
let { EXTEND_PROTOTYPES } = EmberENV;
if (EXTEND_PROTOTYPES !== undefined) {
if (typeof EXTEND_PROTOTYPES === 'object' && EXTEND_PROTOTYPES !== null) {
ENV.EXTEND_PROTOTYPES.String = EXTEND_PROTOTYPES.String !== false;
ENV.EXTEND_PROTOTYPES.Function = EXTEND_PROTOTYPES.Function !== false;
ENV.EXTEND_PROTOTYPES.Array = EXTEND_PROTOTYPES.Array !== false;
} else {
let isEnabled = EXTEND_PROTOTYPES !== false;
ENV.EXTEND_PROTOTYPES.String = isEnabled;
ENV.EXTEND_PROTOTYPES.Function = isEnabled;
ENV.EXTEND_PROTOTYPES.Array = isEnabled;
}
}
// TODO this does not seem to be used by anything,
// can we remove it? do we need to deprecate it?
let { EMBER_LOAD_HOOKS } = EmberENV;
if (typeof EMBER_LOAD_HOOKS === 'object' && EMBER_LOAD_HOOKS !== null) {
for (let hookName in EMBER_LOAD_HOOKS) {
if (!EMBER_LOAD_HOOKS.hasOwnProperty(hookName)) continue;
let hooks = EMBER_LOAD_HOOKS[hookName];
if (Array.isArray(hooks)) {
ENV.EMBER_LOAD_HOOKS[hookName] = hooks.filter(hook => typeof hook === 'function');
}
}
}
let { FEATURES } = EmberENV;
if (typeof FEATURES === 'object' && FEATURES !== null) {
for (let feature in FEATURES) {
if (!FEATURES.hasOwnProperty(feature)) continue;
ENV.FEATURES[feature] = FEATURES[feature] === true;
}
}
})(global$1.EmberENV || global$1.ENV);
function getENV() {
return ENV;
}
exports.global = global$1;
exports.context = context;
exports.getLookup = getLookup;
exports.setLookup = setLookup;
exports.ENV = ENV;
exports.getENV = getENV;
});
enifed("@ember/-internals/error-handling/index", ["exports"], function (exports) {
"use strict";
exports.getOnerror = getOnerror;
exports.setOnerror = setOnerror;
exports.getDispatchOverride = getDispatchOverride;
exports.setDispatchOverride = setDispatchOverride;
let onerror;
const onErrorTarget = exports.onErrorTarget = {
get onerror() {
return onerror;
}
};
// Ember.onerror getter
function getOnerror() {
return onerror;
}
// Ember.onerror setter
function setOnerror(handler) {
onerror = handler;
}
let dispatchOverride;
// allows testing adapter to override dispatch
function getDispatchOverride() {
return dispatchOverride;
}
function setDispatchOverride(handler) {
dispatchOverride = handler;
}
});
enifed('@ember/-internals/extension-support/index', ['exports', '@ember/-internals/extension-support/lib/data_adapter', '@ember/-internals/extension-support/lib/container_debug_adapter'], function (exports, _data_adapter, _container_debug_adapter) {
'use strict';
Object.defineProperty(exports, 'DataAdapter', {
enumerable: true,
get: function () {
return _data_adapter.default;
}
});
Object.defineProperty(exports, 'ContainerDebugAdapter', {
enumerable: true,
get: function () {
return _container_debug_adapter.default;
}
});
});
enifed('@ember/-internals/extension-support/lib/container_debug_adapter', ['exports', '@ember/string', '@ember/-internals/runtime'], function (exports, _string, _runtime) {
'use strict';
exports.default = _runtime.Object.extend({
/**
The resolver instance of the application
being debugged. This property will be injected
on creation.
@property resolver
@default null
@public
*/
resolver: null,
/**
Returns true if it is possible to catalog a list of available
classes in the resolver for a given type.
@method canCatalogEntriesByType
@param {String} type The type. e.g. "model", "controller", "route".
@return {boolean} whether a list is available for this type.
@public
*/
canCatalogEntriesByType(type) {
if (type === 'model' || type === 'template') {
return false;
}
return true;
},
/**
Returns the available classes a given type.
@method catalogEntriesByType
@param {String} type The type. e.g. "model", "controller", "route".
@return {Array} An array of strings.
@public
*/
catalogEntriesByType(type) {
let namespaces = (0, _runtime.A)(_runtime.Namespace.NAMESPACES);
let types = (0, _runtime.A)();
let typeSuffixRegex = new RegExp(`${(0, _string.classify)(type)}$`);
namespaces.forEach(namespace => {
for (let key in namespace) {
if (!namespace.hasOwnProperty(key)) {
continue;
}
if (typeSuffixRegex.test(key)) {
let klass = namespace[key];
if ((0, _runtime.typeOf)(klass) === 'class') {
types.push((0, _string.dasherize)(key.replace(typeSuffixRegex, '')));
}
}
}
});
return types;
}
});
});
enifed('@ember/-internals/extension-support/lib/data_adapter', ['exports', '@ember/-internals/owner', '@ember/runloop', '@ember/-internals/metal', '@ember/string', '@ember/-internals/runtime'], function (exports, _owner, _runloop, _metal, _string, _runtime) {
'use strict';
exports.default = _runtime.Object.extend({
init() {
this._super(...arguments);
this.releaseMethods = (0, _runtime.A)();
},
/**
The container-debug-adapter which is used
to list all models.
@property containerDebugAdapter
@default undefined
@since 1.5.0
@public
**/
containerDebugAdapter: undefined,
/**
The number of attributes to send
as columns. (Enough to make the record
identifiable).
@private
@property attributeLimit
@default 3
@since 1.3.0
*/
attributeLimit: 3,
/**
Ember Data > v1.0.0-beta.18
requires string model names to be passed
around instead of the actual factories.
This is a stamp for the Ember Inspector
to differentiate between the versions
to be able to support older versions too.
@public
@property acceptsModelName
*/
acceptsModelName: true,
/**
Stores all methods that clear observers.
These methods will be called on destruction.
@private
@property releaseMethods
@since 1.3.0
*/
releaseMethods: (0, _runtime.A)(),
/**
Specifies how records can be filtered.
Records returned will need to have a `filterValues`
property with a key for every name in the returned array.
@public
@method getFilters
@return {Array} List of objects defining filters.
The object should have a `name` and `desc` property.
*/
getFilters() {
return (0, _runtime.A)();
},
/**
Fetch the model types and observe them for changes.
@public
@method watchModelTypes
@param {Function} typesAdded Callback to call to add types.
Takes an array of objects containing wrapped types (returned from `wrapModelType`).
@param {Function} typesUpdated Callback to call when a type has changed.
Takes an array of objects containing wrapped types.
@return {Function} Method to call to remove all observers
*/
watchModelTypes(typesAdded, typesUpdated) {
let modelTypes = this.getModelTypes();
let releaseMethods = (0, _runtime.A)();
let typesToSend;
typesToSend = modelTypes.map(type => {
let klass = type.klass;
let wrapped = this.wrapModelType(klass, type.name);
releaseMethods.push(this.observeModelType(type.name, typesUpdated));
return wrapped;
});
typesAdded(typesToSend);
let release = () => {
releaseMethods.forEach(fn => fn());
this.releaseMethods.removeObject(release);
};
this.releaseMethods.pushObject(release);
return release;
},
_nameToClass(type) {
if (typeof type === 'string') {
let owner = (0, _owner.getOwner)(this);
let Factory = owner.factoryFor(`model:${type}`);
type = Factory && Factory.class;
}
return type;
},
/**
Fetch the records of a given type and observe them for changes.
@public
@method watchRecords
@param {String} modelName The model name.
@param {Function} recordsAdded Callback to call to add records.
Takes an array of objects containing wrapped records.
The object should have the following properties:
columnValues: {Object} The key and value of a table cell.
object: {Object} The actual record object.
@param {Function} recordsUpdated Callback to call when a record has changed.
Takes an array of objects containing wrapped records.
@param {Function} recordsRemoved Callback to call when a record has removed.
Takes the following parameters:
index: The array index where the records were removed.
count: The number of records removed.
@return {Function} Method to call to remove all observers.
*/
watchRecords(modelName, recordsAdded, recordsUpdated, recordsRemoved) {
let releaseMethods = (0, _runtime.A)();
let klass = this._nameToClass(modelName);
let records = this.getRecords(klass, modelName);
let release;
function recordUpdated(updatedRecord) {
recordsUpdated([updatedRecord]);
}
let recordsToSend = records.map(record => {
releaseMethods.push(this.observeRecord(record, recordUpdated));
return this.wrapRecord(record);
});
let contentDidChange = (array, idx, removedCount, addedCount) => {
for (let i = idx; i < idx + addedCount; i++) {
let record = (0, _metal.objectAt)(array, i);
let wrapped = this.wrapRecord(record);
releaseMethods.push(this.observeRecord(record, recordUpdated));
recordsAdded([wrapped]);
}
if (removedCount) {
recordsRemoved(idx, removedCount);
}
};
let observer = {
didChange: contentDidChange,
willChange() {
return this;
}
};
(0, _metal.addArrayObserver)(records, this, observer);
release = () => {
releaseMethods.forEach(fn => fn());
(0, _metal.removeArrayObserver)(records, this, observer);
this.releaseMethods.removeObject(release);
};
recordsAdded(recordsToSend);
this.releaseMethods.pushObject(release);
return release;
},
/**
Clear all observers before destruction
@private
@method willDestroy
*/
willDestroy() {
this._super(...arguments);
this.releaseMethods.forEach(fn => fn());
},
/**
Detect whether a class is a model.
Test that against the model class
of your persistence library.
@public
@method detect
@return boolean Whether the class is a model class or not.
*/
detect() {
return false;
},
/**
Get the columns for a given model type.
@public
@method columnsForType
@return {Array} An array of columns of the following format:
name: {String} The name of the column.
desc: {String} Humanized description (what would show in a table column name).
*/
columnsForType() {
return (0, _runtime.A)();
},
/**
Adds observers to a model type class.
@private
@method observeModelType
@param {String} modelName The model type name.
@param {Function} typesUpdated Called when a type is modified.
@return {Function} The function to call to remove observers.
*/
observeModelType(modelName, typesUpdated) {
let klass = this._nameToClass(modelName);
let records = this.getRecords(klass, modelName);
function onChange() {
typesUpdated([this.wrapModelType(klass, modelName)]);
}
let observer = {
didChange(array, idx, removedCount, addedCount) {
// Only re-fetch records if the record count changed
// (which is all we care about as far as model types are concerned).
if (removedCount > 0 || addedCount > 0) {
(0, _runloop.scheduleOnce)('actions', this, onChange);
}
},
willChange() {
return this;
}
};
(0, _metal.addArrayObserver)(records, this, observer);
let release = () => (0, _metal.removeArrayObserver)(records, this, observer);
return release;
},
/**
Wraps a given model type and observes changes to it.
@private
@method wrapModelType
@param {Class} klass A model class.
@param {String} modelName Name of the class.
@return {Object} Contains the wrapped type and the function to remove observers
Format:
type: {Object} The wrapped type.
The wrapped type has the following format:
name: {String} The name of the type.
count: {Integer} The number of records available.
columns: {Columns} An array of columns to describe the record.
object: {Class} The actual Model type class.
release: {Function} The function to remove observers.
*/
wrapModelType(klass, name) {
let records = this.getRecords(klass, name);
let typeToSend;
typeToSend = {
name,
count: (0, _metal.get)(records, 'length'),
columns: this.columnsForType(klass),
object: klass
};
return typeToSend;
},
/**
Fetches all models defined in the application.
@private
@method getModelTypes
@return {Array} Array of model types.
*/
getModelTypes() {
let containerDebugAdapter = this.get('containerDebugAdapter');
let types;
if (containerDebugAdapter.canCatalogEntriesByType('model')) {
types = containerDebugAdapter.catalogEntriesByType('model');
} else {
types = this._getObjectsOnNamespaces();
}
// New adapters return strings instead of classes.
types = (0, _runtime.A)(types).map(name => {
return {
klass: this._nameToClass(name),
name
};
});
types = (0, _runtime.A)(types).filter(type => this.detect(type.klass));
return (0, _runtime.A)(types);
},
/**
Loops over all namespaces and all objects
attached to them.
@private
@method _getObjectsOnNamespaces
@return {Array} Array of model type strings.
*/
_getObjectsOnNamespaces() {
let namespaces = (0, _runtime.A)(_runtime.Namespace.NAMESPACES);
let types = (0, _runtime.A)();
namespaces.forEach(namespace => {
for (let key in namespace) {
if (!namespace.hasOwnProperty(key)) {
continue;
}
// Even though we will filter again in `getModelTypes`,
// we should not call `lookupFactory` on non-models
if (!this.detect(namespace[key])) {
continue;
}
let name = (0, _string.dasherize)(key);
types.push(name);
}
});
return types;
},
/**
Fetches all loaded records for a given type.
@public
@method getRecords
@return {Array} An array of records.
This array will be observed for changes,
so it should update when new records are added/removed.
*/
getRecords() {
return (0, _runtime.A)();
},
/**
Wraps a record and observers changes to it.
@private
@method wrapRecord
@param {Object} record The record instance.
@return {Object} The wrapped record. Format:
columnValues: {Array}
searchKeywords: {Array}
*/
wrapRecord(record) {
let recordToSend = { object: record };
recordToSend.columnValues = this.getRecordColumnValues(record);
recordToSend.searchKeywords = this.getRecordKeywords(record);
recordToSend.filterValues = this.getRecordFilterValues(record);
recordToSend.color = this.getRecordColor(record);
return recordToSend;
},
/**
Gets the values for each column.
@public
@method getRecordColumnValues
@return {Object} Keys should match column names defined
by the model type.
*/
getRecordColumnValues() {
return {};
},
/**
Returns keywords to match when searching records.
@public
@method getRecordKeywords
@return {Array} Relevant keywords for search.
*/
getRecordKeywords() {
return (0, _runtime.A)();
},
/**
Returns the values of filters defined by `getFilters`.
@public
@method getRecordFilterValues
@param {Object} record The record instance.
@return {Object} The filter values.
*/
getRecordFilterValues() {
return {};
},
/**
Each record can have a color that represents its state.
@public
@method getRecordColor
@param {Object} record The record instance
@return {String} The records color.
Possible options: black, red, blue, green.
*/
getRecordColor() {
return null;
},
/**
Observes all relevant properties and re-sends the wrapped record
when a change occurs.
@public
@method observerRecord
@return {Function} The function to call to remove all observers.
*/
observeRecord() {
return function () {};
}
});
});
enifed('@ember/-internals/glimmer', ['exports', '@glimmer/runtime', '@glimmer/util', '@glimmer/node', 'node-module', '@ember/-internals/owner', '@glimmer/opcode-compiler', '@ember/-internals/runtime', '@ember/-internals/utils', '@glimmer/reference', '@ember/-internals/metal', '@ember/-internals/views', '@ember/debug', '@ember/-internals/browser-environment', '@ember/instrumentation', '@ember/service', '@ember/-internals/environment', '@ember/polyfills', '@ember/string', '@glimmer/wire-format', '@ember/-internals/container', '@ember/deprecated-features', '@ember/runloop', 'rsvp', '@ember/-internals/routing'], function (exports, _runtime, _util, _node, _nodeModule, _owner, _opcodeCompiler, _runtime2, _utils, _reference, _metal, _views, _debug, _browserEnvironment, _instrumentation, _service, _environment2, _polyfills, _string, _wireFormat, _container, _deprecatedFeatures, _runloop, _rsvp, _routing) {
'use strict';
exports.modifierCapabilties = exports.getModifierManager = exports.setModifierManager = exports.getComponentManager = exports.setComponentManager = exports.capabilities = exports.OutletView = exports.DebugStack = exports.iterableFor = exports.INVOKE = exports.UpdatableReference = exports.AbstractComponentManager = exports._experimentalMacros = exports._registerMacros = exports.setupApplicationRegistry = exports.setupEngineRegistry = exports.setTemplates = exports.getTemplates = exports.hasTemplate = exports.setTemplate = exports.getTemplate = exports.renderSettled = exports._resetRenderers = exports.InteractiveRenderer = exports.InertRenderer = exports.Renderer = exports.isHTMLSafe = exports.htmlSafe = exports.escapeExpression = exports.SafeString = exports.Environment = exports.helper = exports.Helper = exports.ROOT_REF = exports.Component = exports.LinkComponent = exports.TextArea = exports.TextField = exports.Checkbox = exports.template = exports.RootTemplate = exports.NodeDOMTreeConstruction = exports.isSerializationFirstNode = exports.DOMTreeConstruction = exports.DOMChanges = undefined;
Object.defineProperty(exports, 'DOMChanges', {
enumerable: true,
get: function () {
return _runtime.DOMChanges;
}
});
Object.defineProperty(exports, 'DOMTreeConstruction', {
enumerable: true,
get: function () {
return _runtime.DOMTreeConstruction;
}
});
Object.defineProperty(exports, 'isSerializationFirstNode', {
enumerable: true,
get: function () {
return _util.isSerializationFirstNode;
}
});
Object.defineProperty(exports, 'NodeDOMTreeConstruction', {
enumerable: true,
get: function () {
return _node.NodeDOMTreeConstruction;
}
});
function template(json) {
return new FactoryWrapper((0, _opcodeCompiler.templateFactory)(json));
}
class FactoryWrapper {
constructor(factory) {
this.factory = factory;
this.id = factory.id;
this.meta = factory.meta;
}
create(injections) {
const owner = (0, _owner.getOwner)(injections);
return this.factory.create(injections.compiler, { owner });
}
}
var RootTemplate = template({ "id": "HlDcU23A", "block": "{\"symbols\":[],\"statements\":[[1,[27,\"component\",[[22,0,[]]],null],false]],\"hasEval\":false}", "meta": { "moduleName": "packages/@ember/-internals/glimmer/lib/templates/root.hbs" } });
/**
@module @ember/component
*/
const RECOMPUTE_TAG = (0, _utils.symbol)('RECOMPUTE_TAG');
function isHelperFactory(helper) {
return typeof helper === 'object' && helper !== null && helper.class && helper.class.isHelperFactory;
}
function isSimpleHelper(helper) {
return helper.destroy === undefined;
}
/**
Ember Helpers are functions that can compute values, and are used in templates.
For example, this code calls a helper named `format-currency`:
```handlebars
{{format-currency cents currency="$"}}
```
Additionally a helper can be called as a nested helper (sometimes called a
subexpression). In this example, the computed value of a helper is passed
to a component named `show-money`:
```handlebars
{{show-money amount=(format-currency cents currency="$")}}
```
Helpers defined using a class must provide a `compute` function. For example:
```app/helpers/format-currency.js
import Helper from '@ember/component/helper';
export default Helper.extend({
compute([cents], { currency }) {
return `${currency}${cents * 0.01}`;
}
});
```
Each time the input to a helper changes, the `compute` function will be
called again.
As instances, these helpers also have access to the container and will accept
injected dependencies.
Additionally, class helpers can call `recompute` to force a new computation.
@class Helper
@public
@since 1.13.0
*/
let Helper = _runtime2.FrameworkObject.extend({
init() {
this._super(...arguments);
this[RECOMPUTE_TAG] = _reference.DirtyableTag.create();
},
/**
On a class-based helper, it may be useful to force a recomputation of that
helpers value. This is akin to `rerender` on a component.
For example, this component will rerender when the `currentUser` on a
session service changes:
```app/helpers/current-user-email.js
import Helper from '@ember/component/helper'
import { inject as service } from '@ember/service'
import { observer } from '@ember/object'
export default Helper.extend({
session: service(),
onNewUser: observer('session.currentUser', function() {
this.recompute();
}),
compute() {
return this.get('session.currentUser.email');
}
});
```
@method recompute
@public
@since 1.13.0
*/
recompute() {
this[RECOMPUTE_TAG].inner.dirty();
}
});
Helper.isHelperFactory = true;
class Wrapper {
constructor(compute) {
this.compute = compute;
this.isHelperFactory = true;
}
create() {
// needs new instance or will leak containers
return {
compute: this.compute
};
}
}
/**
In many cases, the ceremony of a full `Helper` class is not required.
The `helper` method create pure-function helpers without instances. For
example:
```app/helpers/format-currency.js
import { helper } from '@ember/component/helper';
export default helper(function(params, hash) {
let cents = params[0];
let currency = hash.currency;
return `${currency}${cents * 0.01}`;
});
```
@static
@param {Function} helper The helper function
@method helper
@for @ember/component/helper
@public
@since 1.13.0
*/
function helper(helperFn) {
return new Wrapper(helperFn);
}
function toBool(predicate) {
if ((0, _runtime2.isArray)(predicate)) {
return predicate.length !== 0;
} else {
return !!predicate;
}
}
const UPDATE = (0, _utils.symbol)('UPDATE');
const INVOKE = (0, _utils.symbol)('INVOKE');
const ACTION = (0, _utils.symbol)('ACTION');
let maybeFreeze;
if (false /* DEBUG */) {
// gaurding this in a DEBUG gaurd (as well as all invocations)
// so that it is properly stripped during the minification's
// dead code elimination
maybeFreeze = obj => {
// re-freezing an already frozen object introduces a significant
// performance penalty on Chrome (tested through 59).
//
// See: https://bugs.chromium.org/p/v8/issues/detail?id=6450
if (!Object.isFrozen(obj)) {
Object.freeze(obj);
}
};
}
class EmberPathReference {
get(key) {
return PropertyReference.create(this, key);
}
}
class CachedReference$1 extends EmberPathReference {
constructor() {
super();
this._lastRevision = null;
this._lastValue = null;
}
value() {
let { tag, _lastRevision, _lastValue } = this;
if (_lastRevision === null || !tag.validate(_lastRevision)) {
_lastValue = this._lastValue = this.compute();
this._lastRevision = tag.value();
}
return _lastValue;
}
}
class RootReference extends _reference.ConstReference {
constructor(value) {
super(value);
this.children = Object.create(null);
}
get(propertyKey) {
let ref = this.children[propertyKey];
if (ref === undefined) {
ref = this.children[propertyKey] = new RootPropertyReference(this.inner, propertyKey);
}
return ref;
}
}
let TwoWayFlushDetectionTag;
if (false /* DEBUG */) {
TwoWayFlushDetectionTag = class {
static create(tag, key, ref) {
return new _reference.TagWrapper(tag.type, new TwoWayFlushDetectionTag(tag, key, ref));
}
constructor(tag, key, ref) {
this.tag = tag;
this.parent = null;
this.key = key;
this.ref = ref;
}
value() {
return this.tag.value();
}
validate(ticket) {
let { parent, key } = this;
let isValid = this.tag.validate(ticket);
if (isValid && parent) {
(0, _metal.didRender)(parent, key, this.ref);
}
return isValid;
}
didCompute(parent) {
this.parent = parent;
(0, _metal.didRender)(parent, this.key, this.ref);
}
};
}
class PropertyReference extends CachedReference$1 {
static create(parentReference, propertyKey) {
if ((0, _reference.isConst)(parentReference)) {
return new RootPropertyReference(parentReference.value(), propertyKey);
} else {
return new NestedPropertyReference(parentReference, propertyKey);
}
}
get(key) {
return new NestedPropertyReference(this, key);
}
}
class RootPropertyReference extends PropertyReference {
constructor(parentValue, propertyKey) {
super();
this._parentValue = parentValue;
this._propertyKey = propertyKey;
if (false /* DEBUG */) {
this.tag = TwoWayFlushDetectionTag.create((0, _metal.tagForProperty)(parentValue, propertyKey), propertyKey, this);
} else {
this.tag = (0, _metal.tagForProperty)(parentValue, propertyKey);
}
if (false /* DEBUG */) {
(0, _metal.watchKey)(parentValue, propertyKey);
}
}
compute() {
let { _parentValue, _propertyKey } = this;
if (false /* DEBUG */) {
this.tag.inner.didCompute(_parentValue);
}
return (0, _metal.get)(_parentValue, _propertyKey);
}
[UPDATE](value) {
(0, _metal.set)(this._parentValue, this._propertyKey, value);
}
}
class NestedPropertyReference extends PropertyReference {
constructor(parentReference, propertyKey) {
super();
let parentReferenceTag = parentReference.tag;
let parentObjectTag = _reference.UpdatableTag.create(_reference.CONSTANT_TAG);
this._parentReference = parentReference;
this._parentObjectTag = parentObjectTag;
this._propertyKey = propertyKey;
if (false /* DEBUG */) {
let tag = (0, _reference.combine)([parentReferenceTag, parentObjectTag]);
this.tag = TwoWayFlushDetectionTag.create(tag, propertyKey, this);
} else {
this.tag = (0, _reference.combine)([parentReferenceTag, parentObjectTag]);
}
}
compute() {
let { _parentReference, _parentObjectTag, _propertyKey } = this;
let parentValue = _parentReference.value();
_parentObjectTag.inner.update((0, _metal.tagForProperty)(parentValue, _propertyKey));
let parentValueType = typeof parentValue;
if (parentValueType === 'string' && _propertyKey === 'length') {
return parentValue.length;
}
if (parentValueType === 'object' && parentValue !== null || parentValueType === 'function') {
if (false /* DEBUG */) {
(0, _metal.watchKey)(parentValue, _propertyKey);
}
if (false /* DEBUG */) {
this.tag.inner.didCompute(parentValue);
}
return (0, _metal.get)(parentValue, _propertyKey);
} else {
return undefined;
}
}
[UPDATE](value) {
let parent = this._parentReference.value();
(0, _metal.set)(parent, this._propertyKey, value);
}
}
class UpdatableReference extends EmberPathReference {
constructor(value) {
super();
this.tag = _reference.DirtyableTag.create();
this._value = value;
}
value() {
return this._value;
}
update(value) {
let { _value } = this;
if (value !== _value) {
this.tag.inner.dirty();
this._value = value;
}
}
}
class ConditionalReference$1 extends _runtime.ConditionalReference {
static create(reference) {
if ((0, _reference.isConst)(reference)) {
let value = reference.value();
if ((0, _utils.isProxy)(value)) {
return new RootPropertyReference(value, 'isTruthy');
} else {
return _runtime.PrimitiveReference.create(toBool(value));
}
}
return new ConditionalReference$1(reference);
}
constructor(reference) {
super(reference);
this.objectTag = _reference.UpdatableTag.create(_reference.CONSTANT_TAG);
this.tag = (0, _reference.combine)([reference.tag, this.objectTag]);
}
toBool(predicate) {
if ((0, _utils.isProxy)(predicate)) {
this.objectTag.inner.update((0, _metal.tagForProperty)(predicate, 'isTruthy'));
return (0, _metal.get)(predicate, 'isTruthy');
} else {
this.objectTag.inner.update((0, _metal.tagFor)(predicate));
return toBool(predicate);
}
}
}
class SimpleHelperReference extends CachedReference$1 {
static create(helper$$1, args) {
if ((0, _reference.isConst)(args)) {
let { positional, named } = args;
let positionalValue = positional.value();
let namedValue = named.value();
if (false /* DEBUG */) {
maybeFreeze(positionalValue);
maybeFreeze(namedValue);
}
let result = helper$$1(positionalValue, namedValue);
return valueToRef(result);
} else {
return new SimpleHelperReference(helper$$1, args);
}
}
constructor(helper$$1, args) {
super();
this.tag = args.tag;
this.helper = helper$$1;
this.args = args;
}
compute() {
let { helper: helper$$1, args: { positional, named } } = this;
let positionalValue = positional.value();
let namedValue = named.value();
if (false /* DEBUG */) {
maybeFreeze(positionalValue);
maybeFreeze(namedValue);
}
return helper$$1(positionalValue, namedValue);
}
}
class ClassBasedHelperReference extends CachedReference$1 {
static create(instance, args) {
return new ClassBasedHelperReference(instance, args);
}
constructor(instance, args) {
super();
this.tag = (0, _reference.combine)([instance[RECOMPUTE_TAG], args.tag]);
this.instance = instance;
this.args = args;
}
compute() {
let { instance, args: { positional, named } } = this;
let positionalValue = positional.value();
let namedValue = named.value();
if (false /* DEBUG */) {
maybeFreeze(positionalValue);
maybeFreeze(namedValue);
}
return instance.compute(positionalValue, namedValue);
}
}
class InternalHelperReference extends CachedReference$1 {
constructor(helper$$1, args) {
super();
this.tag = args.tag;
this.helper = helper$$1;
this.args = args;
}
compute() {
let { helper: helper$$1, args } = this;
return helper$$1(args);
}
}
class UnboundReference extends _reference.ConstReference {
static create(value) {
return valueToRef(value, false);
}
get(key) {
return valueToRef((0, _metal.get)(this.inner, key), false);
}
}
class ReadonlyReference extends CachedReference$1 {
constructor(inner) {
super();
this.inner = inner;
}
get tag() {
return this.inner.tag;
}
get [INVOKE]() {
return this.inner[INVOKE];
}
compute() {
return this.inner.value();
}
get(key) {
return this.inner.get(key);
}
}
function referenceFromParts(root, parts) {
let reference = root;
for (let i = 0; i < parts.length; i++) {
reference = reference.get(parts[i]);
}
return reference;
}
function valueToRef(value, bound = true) {
if (value !== null && typeof value === 'object') {
// root of interop with ember objects
return bound ? new RootReference(value) : new UnboundReference(value);
}
// ember doesn't do observing with functions
if (typeof value === 'function') {
return new UnboundReference(value);
}
return _runtime.PrimitiveReference.create(value);
}
const DIRTY_TAG = (0, _utils.symbol)('DIRTY_TAG');
const ARGS = (0, _utils.symbol)('ARGS');
const ROOT_REF = (0, _utils.symbol)('ROOT_REF');
const IS_DISPATCHING_ATTRS = (0, _utils.symbol)('IS_DISPATCHING_ATTRS');
const HAS_BLOCK = (0, _utils.symbol)('HAS_BLOCK');
const BOUNDS = (0, _utils.symbol)('BOUNDS');
/**
@module @ember/component
*/
/**
A `Component` is a view that is completely
isolated. Properties accessed in its templates go
to the view object and actions are targeted at
the view object. There is no access to the
surrounding context or outer controller; all
contextual information must be passed in.
The easiest way to create a `Component` is via
a template. If you name a template
`app/templates/components/my-foo.hbs`, you will be able to use
`{{my-foo}}` in other templates, which will make
an instance of the isolated component.
```app/templates/components/my-foo.hbs
{{person-profile person=currentUser}}
```
```app/templates/components/person-profile.hbs
{{person.title}}
{{person.signature}}
```
You can use `yield` inside a template to
include the **contents** of any block attached to
the component. The block will be executed in the
context of the surrounding context or outer controller:
```handlebars
{{#person-profile person=currentUser}}
Admin mode
{{! Executed in the controller's context. }}
{{/person-profile}}
```
```app/templates/components/person-profile.hbs
{{person.title}}
{{! Executed in the component's context. }}
{{yield}} {{! block contents }}
```
If you want to customize the component, in order to
handle events or actions, you implement a subclass
of `Component` named after the name of the
component.
For example, you could implement the action
`hello` for the `person-profile` component:
```app/components/person-profile.js
import Component from '@ember/component';
export default Component.extend({
actions: {
hello(name) {
console.log("Hello", name);
}
}
});
```
And then use it in the component's template:
```app/templates/components/person-profile.hbs
{{person.title}}
{{yield}}
```
Components must have a `-` in their name to avoid
conflicts with built-in controls that wrap HTML
elements. This is consistent with the same
requirement in web components.
## HTML Tag
The default HTML tag name used for a component's DOM representation is `div`.
This can be customized by setting the `tagName` property.
The following component class:
```app/components/emphasized-paragraph.js
import Component from '@ember/component';
export default Component.extend({
tagName: 'em'
});
```
Would result in instances with the following HTML:
```html
```
## HTML `class` Attribute
The HTML `class` attribute of a component's tag can be set by providing a
`classNames` property that is set to an array of strings:
```app/components/my-widget.js
import Component from '@ember/component';
export default Component.extend({
classNames: ['my-class', 'my-other-class']
});
```
Will result in component instances with an HTML representation of:
```html
```
`class` attribute values can also be set by providing a `classNameBindings`
property set to an array of properties names for the component. The return value
of these properties will be added as part of the value for the components's `class`
attribute. These properties can be computed properties:
```app/components/my-widget.js
import Component from '@ember/component';
import { computed } from '@ember/object';
export default Component.extend({
classNameBindings: ['propertyA', 'propertyB'],
propertyA: 'from-a',
propertyB: computed(function() {
if (someLogic) { return 'from-b'; }
})
});
```
Will result in component instances with an HTML representation of:
```html
```
If the value of a class name binding returns a boolean the property name
itself will be used as the class name if the property is true.
The class name will not be added if the value is `false` or `undefined`.
```app/components/my-widget.js
import Component from '@ember/component';
export default Component.extend({
classNameBindings: ['hovered'],
hovered: true
});
```
Will result in component instances with an HTML representation of:
```html
```
When using boolean class name bindings you can supply a string value other
than the property name for use as the `class` HTML attribute by appending the
preferred value after a ":" character when defining the binding:
```app/components/my-widget.js
import Component from '@ember/component';
export default Component.extend({
classNameBindings: ['awesome:so-very-cool'],
awesome: true
});
```
Will result in component instances with an HTML representation of:
```html
```
Boolean value class name bindings whose property names are in a
camelCase-style format will be converted to a dasherized format:
```app/components/my-widget.js
import Component from '@ember/component';
export default Component.extend({
classNameBindings: ['isUrgent'],
isUrgent: true
});
```
Will result in component instances with an HTML representation of:
```html
```
Class name bindings can also refer to object values that are found by
traversing a path relative to the component itself:
```app/components/my-widget.js
import Component from '@ember/component';
import EmberObject from '@ember/object';
export default Component.extend({
classNameBindings: ['messages.empty'],
messages: EmberObject.create({
empty: true
})
});
```
Will result in component instances with an HTML representation of:
```html
```
If you want to add a class name for a property which evaluates to true and
and a different class name if it evaluates to false, you can pass a binding
like this:
```app/components/my-widget.js
import Component from '@ember/component';
export default Component.extend({
classNameBindings: ['isEnabled:enabled:disabled'],
isEnabled: true
});
```
Will result in component instances with an HTML representation of:
```html
```
When isEnabled is `false`, the resulting HTML representation looks like
this:
```html
```
This syntax offers the convenience to add a class if a property is `false`:
```app/components/my-widget.js
import Component from '@ember/component';
// Applies no class when isEnabled is true and class 'disabled' when isEnabled is false
export default Component.extend({
classNameBindings: ['isEnabled::disabled'],
isEnabled: true
});
```
Will result in component instances with an HTML representation of:
```html
```
When the `isEnabled` property on the component is set to `false`, it will result
in component instances with an HTML representation of:
```html
```
Updates to the value of a class name binding will result in automatic
update of the HTML `class` attribute in the component's rendered HTML
representation. If the value becomes `false` or `undefined` the class name
will be removed.
Both `classNames` and `classNameBindings` are concatenated properties. See
[EmberObject](/api/ember/release/classes/EmberObject) documentation for more
information about concatenated properties.
## HTML Attributes
The HTML attribute section of a component's tag can be set by providing an
`attributeBindings` property set to an array of property names on the component.
The return value of these properties will be used as the value of the component's
HTML associated attribute:
```app/components/my-anchor.js
import Component from '@ember/component';
export default Component.extend({
tagName: 'a',
attributeBindings: ['href'],
href: 'http://google.com'
});
```
Will result in component instances with an HTML representation of:
```html
```
One property can be mapped on to another by placing a ":" between
the source property and the destination property:
```app/components/my-anchor.js
import Component from '@ember/component';
export default Component.extend({
tagName: 'a',
attributeBindings: ['url:href'],
url: 'http://google.com'
});
```
Will result in component instances with an HTML representation of:
```html
```
Namespaced attributes (e.g. `xlink:href`) are supported, but have to be
mapped, since `:` is not a valid character for properties in Javascript:
```app/components/my-use.js
import Component from '@ember/component';
export default Component.extend({
tagName: 'use',
attributeBindings: ['xlinkHref:xlink:href'],
xlinkHref: '#triangle'
});
```
Will result in component instances with an HTML representation of:
```html
```
If the return value of an `attributeBindings` monitored property is a boolean
the attribute will be present or absent depending on the value:
```app/components/my-text-input.js
import Component from '@ember/component';
export default Component.extend({
tagName: 'input',
attributeBindings: ['disabled'],
disabled: false
});
```
Will result in a component instance with an HTML representation of:
```html
```
`attributeBindings` can refer to computed properties:
```app/components/my-text-input.js
import Component from '@ember/component';
import { computed } from '@ember/object';
export default Component.extend({
tagName: 'input',
attributeBindings: ['disabled'],
disabled: computed(function() {
if (someLogic) {
return true;
} else {
return false;
}
})
});
```
To prevent setting an attribute altogether, use `null` or `undefined` as the
return value of the `attributeBindings` monitored property:
```app/components/my-text-input.js
import Component from '@ember/component';
export default Component.extend({
tagName: 'form',
attributeBindings: ['novalidate'],
novalidate: null
});
```
Updates to the property of an attribute binding will result in automatic
update of the HTML attribute in the component's rendered HTML representation.
`attributeBindings` is a concatenated property. See [EmberObject](/api/ember/release/classes/EmberObject)
documentation for more information about concatenated properties.
## Layouts
See [Ember.Templates.helpers.yield](/api/ember/release/classes/Ember.Templates.helpers/methods/yield?anchor=yield)
for more information.
Layout can be used to wrap content in a component. In addition
to wrapping content in a Component's template, you can also use
the public layout API in your Component JavaScript.
```app/templates/components/person-profile.hbs
Person's Title
{{yield}}
```
```app/components/person-profile.js
import Component from '@ember/component';
import layout from '../templates/components/person-profile';
export default Component.extend({
layout
});
```
If you call the `person-profile` component like so:
```
{{#person-profile}}
Chief Basket Weaver
Fisherman Industries
{{/person-profile}}
It will result in the following HTML output:
```html
Person's Title
Chief Basket Weaver
Fisherman Industries
```
## Responding to Browser Events
Components can respond to user-initiated events in one of three ways: method
implementation, through an event manager, and through `{{action}}` helper use
in their template or layout.
### Method Implementation
Components can respond to user-initiated events by implementing a method that
matches the event name. A `jQuery.Event` object will be passed as the
argument to this method.
```app/components/my-widget.js
import Component from '@ember/component';
export default Component.extend({
click(event) {
// will be called when an instance's
// rendered element is clicked
}
});
```
### `{{action}}` Helper
See [Ember.Templates.helpers.action](/api/ember/release/classes/Ember.Templates.helpers/methods/yield?anchor=yield).
### Event Names
All of the event handling approaches described above respond to the same set
of events. The names of the built-in events are listed below. (The hash of
built-in events exists in `Ember.EventDispatcher`.) Additional, custom events
can be registered by using `Application.customEvents`.
Touch events:
* `touchStart`
* `touchMove`
* `touchEnd`
* `touchCancel`
Keyboard events:
* `keyDown`
* `keyUp`
* `keyPress`
Mouse events:
* `mouseDown`
* `mouseUp`
* `contextMenu`
* `click`
* `doubleClick`
* `mouseMove`
* `focusIn`
* `focusOut`
* `mouseEnter`
* `mouseLeave`
Form events:
* `submit`
* `change`
* `focusIn`
* `focusOut`
* `input`
HTML5 drag and drop events:
* `dragStart`
* `drag`
* `dragEnter`
* `dragLeave`
* `dragOver`
* `dragEnd`
* `drop`
@class Component
@extends Ember.CoreView
@uses Ember.TargetActionSupport
@uses Ember.ClassNamesSupport
@uses Ember.ActionSupport
@uses Ember.ViewMixin
@uses Ember.ViewStateSupport
@public
*/
const Component = _views.CoreView.extend(_views.ChildViewsSupport, _views.ViewStateSupport, _views.ClassNamesSupport, _runtime2.TargetActionSupport, _views.ActionSupport, _views.ViewMixin, {
isComponent: true,
init() {
this._super(...arguments);
this[IS_DISPATCHING_ATTRS] = false;
this[DIRTY_TAG] = _reference.DirtyableTag.create();
this[ROOT_REF] = new RootReference(this);
this[BOUNDS] = null;
// If in a tagless component, assert that no event handlers are defined
false && !(this.tagName !== '' || !this.renderer._destinedForDOM || !(() => {
let eventDispatcher = (0, _owner.getOwner)(this).lookup('event_dispatcher:main');
let events = eventDispatcher && eventDispatcher._finalEvents || {};
// eslint:disable-next-line:forin
for (let key in events) {
let methodName = events[key];
if (typeof this[methodName] === 'function') {
return true; // indicate that the assertion should be triggered
}
}
return false;
})()) && (0, _debug.assert)(
// eslint:disable-next-line:max-line-length
`You can not define a function that handles DOM events in the \`${this}\` tagless component since it doesn't have any DOM element.`, this.tagName !== '' || !this.renderer._destinedForDOM || !(() => {
let eventDispatcher = (0, _owner.getOwner)(this).lookup('event_dispatcher:main');let events = eventDispatcher && eventDispatcher._finalEvents || {};for (let key in events) {
let methodName = events[key];if (typeof this[methodName] === 'function') {
return true;
}
}return false;
})());
},
rerender() {
this[DIRTY_TAG].inner.dirty();
this._super();
},
[_metal.PROPERTY_DID_CHANGE](key) {
if (this[IS_DISPATCHING_ATTRS]) {
return;
}
let args = this[ARGS];
let reference = args !== undefined ? args[key] : undefined;
if (reference !== undefined && reference[UPDATE] !== undefined) {
reference[UPDATE]((0, _metal.get)(this, key));
}
},
getAttr(key) {
// TODO Intimate API should be deprecated
return this.get(key);
},
/**
Normally, Ember's component model is "write-only". The component takes a
bunch of attributes that it got passed in, and uses them to render its
template.
One nice thing about this model is that if you try to set a value to the
same thing as last time, Ember (through HTMLBars) will avoid doing any
work on the DOM.
This is not just a performance optimization. If an attribute has not
changed, it is important not to clobber the element's "hidden state".
For example, if you set an input's `value` to the same value as before,
it will clobber selection state and cursor position. In other words,
setting an attribute is not **always** idempotent.
This method provides a way to read an element's attribute and also
update the last value Ember knows about at the same time. This makes
setting an attribute idempotent.
In particular, what this means is that if you get an `` element's
`value` attribute and then re-render the template with the same value,
it will avoid clobbering the cursor and selection position.
Since most attribute sets are idempotent in the browser, you typically
can get away with reading attributes using jQuery, but the most reliable
way to do so is through this method.
@method readDOMAttr
@param {String} name the name of the attribute
@return String
@public
*/
readDOMAttr(name) {
// TODO revisit this
let element = (0, _views.getViewElement)(this);
let isSVG = element.namespaceURI === _runtime.SVG_NAMESPACE;
let { type, normalized } = (0, _runtime.normalizeProperty)(element, name);
if (isSVG || type === 'attr') {
return element.getAttribute(normalized);
}
return element[normalized];
},
/**
The WAI-ARIA role of the control represented by this view. For example, a
button may have a role of type 'button', or a pane may have a role of
type 'alertdialog'. This property is used by assistive software to help
visually challenged users navigate rich web applications.
The full list of valid WAI-ARIA roles is available at:
[http://www.w3.org/TR/wai-aria/roles#roles_categorization](http://www.w3.org/TR/wai-aria/roles#roles_categorization)
@property ariaRole
@type String
@default null
@public
*/
/**
Enables components to take a list of parameters as arguments.
For example, a component that takes two parameters with the names
`name` and `age`:
```app/components/my-component.js
import Component from '@ember/component';
let MyComponent = Component.extend();
MyComponent.reopenClass({
positionalParams: ['name', 'age']
});
export default MyComponent;
```
It can then be invoked like this:
```hbs
{{my-component "John" 38}}
```
The parameters can be referred to just like named parameters:
```hbs
Name: {{name}}, Age: {{age}}.
```
Using a string instead of an array allows for an arbitrary number of
parameters:
```app/components/my-component.js
import Component from '@ember/component';
let MyComponent = Component.extend();
MyComponent.reopenClass({
positionalParams: 'names'
});
export default MyComponent;
```
It can then be invoked like this:
```hbs
{{my-component "John" "Michael" "Scott"}}
```
The parameters can then be referred to by enumerating over the list:
```hbs
{{#each names as |name|}}{{name}}{{/each}}
```
@static
@public
@property positionalParams
@since 1.13.0
*/
/**
Called when the attributes passed into the component have been updated.
Called both during the initial render of a container and during a rerender.
Can be used in place of an observer; code placed here will be executed
every time any attribute updates.
@method didReceiveAttrs
@public
@since 1.13.0
*/
didReceiveAttrs() {},
/**
Called when the attributes passed into the component have been updated.
Called both during the initial render of a container and during a rerender.
Can be used in place of an observer; code placed here will be executed
every time any attribute updates.
@event didReceiveAttrs
@public
@since 1.13.0
*/
/**
Called after a component has been rendered, both on initial render and
in subsequent rerenders.
@method didRender
@public
@since 1.13.0
*/
didRender() {},
/**
Called after a component has been rendered, both on initial render and
in subsequent rerenders.
@event didRender
@public
@since 1.13.0
*/
/**
Called before a component has been rendered, both on initial render and
in subsequent rerenders.
@method willRender
@public
@since 1.13.0
*/
willRender() {},
/**
Called before a component has been rendered, both on initial render and
in subsequent rerenders.
@event willRender
@public
@since 1.13.0
*/
/**
Called when the attributes passed into the component have been changed.
Called only during a rerender, not during an initial render.
@method didUpdateAttrs
@public
@since 1.13.0
*/
didUpdateAttrs() {},
/**
Called when the attributes passed into the component have been changed.
Called only during a rerender, not during an initial render.
@event didUpdateAttrs
@public
@since 1.13.0
*/
/**
Called when the component is about to update and rerender itself.
Called only during a rerender, not during an initial render.
@method willUpdate
@public
@since 1.13.0
*/
willUpdate() {},
/**
Called when the component is about to update and rerender itself.
Called only during a rerender, not during an initial render.
@event willUpdate
@public
@since 1.13.0
*/
/**
Called when the component has updated and rerendered itself.
Called only during a rerender, not during an initial render.
@method didUpdate
@public
@since 1.13.0
*/
didUpdate() {}
});
Component.toString = () => '@ember/component';
Component.reopenClass({
isComponentFactory: true,
positionalParams: []
});
var layout = template({ "id": "hvtsz7RF", "block": "{\"symbols\":[],\"statements\":[],\"hasEval\":false}", "meta": { "moduleName": "packages/@ember/-internals/glimmer/lib/templates/empty.hbs" } });
/**
@module @ember/component
*/
/**
The internal class used to create text inputs when the `{{input}}`
helper is used with `type` of `checkbox`.
See [Ember.Templates.helpers.input](/api/ember/release/classes/Ember.Templates.helpers/methods/input?anchor=input) for usage details.
## Direct manipulation of `checked`
The `checked` attribute of an `Checkbox` object should always be set
through the Ember object or by interacting with its rendered element
representation via the mouse, keyboard, or touch. Updating the value of the
checkbox via jQuery will result in the checked value of the object and its
element losing synchronization.
## Layout and LayoutName properties
Because HTML `input` elements are self closing `layout` and `layoutName`
properties will not be applied.
@class Checkbox
@extends Component
@public
*/
const Checkbox = Component.extend({
layout,
classNames: ['ember-checkbox'],
tagName: 'input',
attributeBindings: ['type', 'checked', 'indeterminate', 'disabled', 'tabindex', 'name', 'autofocus', 'required', 'form'],
type: 'checkbox',
disabled: false,
indeterminate: false,
didInsertElement() {
this._super(...arguments);
(0, _metal.get)(this, 'element').indeterminate = !!(0, _metal.get)(this, 'indeterminate');
},
change() {
(0, _metal.set)(this, 'checked', this.element.checked);
}
});
Checkbox.toString = () => '@ember/component/checkbox';
/**
@module @ember/component
*/
const inputTypes = Object.create(null);
function canSetTypeOfInput(type) {
if (type in inputTypes) {
return inputTypes[type];
}
// if running in outside of a browser always return the
// original type
if (!_browserEnvironment.hasDOM) {
inputTypes[type] = type;
return type;
}
let inputTypeTestElement = document.createElement('input');
try {
inputTypeTestElement.type = type;
} catch (e) {
// ignored
}
return inputTypes[type] = inputTypeTestElement.type === type;
}
/**
The internal class used to create text inputs when the `{{input}}`
helper is used with `type` of `text`.
See [Ember.Templates.helpers.input](/api/ember/release/classes/Ember.Templates.helpers/methods/input?anchor=input) for usage details.
## Layout and LayoutName properties
Because HTML `input` elements are self closing `layout` and `layoutName`
properties will not be applied.
@class TextField
@extends Component
@uses Ember.TextSupport
@public
*/
const TextField = Component.extend(_views.TextSupport, {
layout,
classNames: ['ember-text-field'],
tagName: 'input',
attributeBindings: ['accept', 'autocomplete', 'autosave', 'dir', 'formaction', 'formenctype', 'formmethod', 'formnovalidate', 'formtarget', 'height', 'inputmode', 'lang', 'list', 'type', 'max', 'min', 'multiple', 'name', 'pattern', 'size', 'step', 'value', 'width'],
/**
The `value` attribute of the input element. As the user inputs text, this
property is updated live.
@property value
@type String
@default ""
@public
*/
value: '',
/**
The `type` attribute of the input element.
@property type
@type String
@default "text"
@public
*/
type: (0, _metal.computed)({
get() {
return 'text';
},
set(_key, value) {
let type = 'text';
if (canSetTypeOfInput(value)) {
type = value;
}
return type;
}
}),
/**
The `size` of the text field in characters.
@property size
@type String
@default null
@public
*/
size: null,
/**
The `pattern` attribute of input element.
@property pattern
@type String
@default null
@public
*/
pattern: null,
/**
The `min` attribute of input element used with `type="number"` or `type="range"`.
@property min
@type String
@default null
@since 1.4.0
@public
*/
min: null,
/**
The `max` attribute of input element used with `type="number"` or `type="range"`.
@property max
@type String
@default null
@since 1.4.0
@public
*/
max: null
});
TextField.toString = () => '@ember/component/text-field';
/**
@module @ember/component
*/
/**
`{{textarea}}` inserts a new instance of `