react-machine
Advanced tools
+6
| { | ||
| "plugins": [ | ||
| "@babel/plugin-transform-modules-commonjs", | ||
| "@babel/plugin-transform-react-jsx" | ||
| ] | ||
| } |
Sorry, the diff of this file is not supported yet
| .DS_Store | ||
| .nyc_output | ||
| node_modules | ||
| coverage | ||
| dist | ||
| docs/public | ||
| docs/resources/_gen |
Sorry, the diff of this file is not supported yet
| {"parent":"d30dcf15-3b68-4096-8b10-b8aae4a08d2e","pid":54389,"argv":["/Users/karolis/.fnm/node-versions/v14.15.1/installation/bin/node","/Users/karolis/projects/react-machine/node_modules/ava/lib/worker/subprocess.js"],"execArgv":[],"cwd":"/Users/karolis/projects/react-machine","time":1608587193491,"ppid":54388,"coverageFilename":"/Users/karolis/projects/react-machine/.nyc_output/b0f01c8c-1044-40b3-84aa-0085265f92cd.json","externalId":"","uuid":"b0f01c8c-1044-40b3-84aa-0085265f92cd","files":[]} |
| {"parent":null,"pid":54388,"argv":["/Users/karolis/.fnm/node-versions/v14.15.1/installation/bin/node","/Users/karolis/projects/react-machine/node_modules/.bin/ava"],"execArgv":[],"cwd":"/Users/karolis/projects/react-machine","time":1608587192771,"ppid":54387,"coverageFilename":"/Users/karolis/projects/react-machine/.nyc_output/d30dcf15-3b68-4096-8b10-b8aae4a08d2e.json","externalId":"","uuid":"d30dcf15-3b68-4096-8b10-b8aae4a08d2e","files":[]} |
| {"parent":"d30dcf15-3b68-4096-8b10-b8aae4a08d2e","pid":54390,"argv":["/Users/karolis/.fnm/node-versions/v14.15.1/installation/bin/node","/Users/karolis/projects/react-machine/node_modules/ava/lib/worker/subprocess.js"],"execArgv":[],"cwd":"/Users/karolis/projects/react-machine","time":1608587193488,"ppid":54388,"coverageFilename":"/Users/karolis/projects/react-machine/.nyc_output/f890e69a-4121-419e-81d7-1a40f9538d72.json","externalId":"","uuid":"f890e69a-4121-419e-81d7-1a40f9538d72","files":[]} |
| {"processes":{"b0f01c8c-1044-40b3-84aa-0085265f92cd":{"parent":"d30dcf15-3b68-4096-8b10-b8aae4a08d2e","children":[]},"d30dcf15-3b68-4096-8b10-b8aae4a08d2e":{"parent":null,"children":["b0f01c8c-1044-40b3-84aa-0085265f92cd","f890e69a-4121-419e-81d7-1a40f9538d72"]},"f890e69a-4121-419e-81d7-1a40f9538d72":{"parent":"d30dcf15-3b68-4096-8b10-b8aae4a08d2e","children":[]}},"files":{},"externalIds":{}} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
| { | ||
| "semi": false, | ||
| "singleQuote": true, | ||
| "jsxSingleQuote": true, | ||
| "printWidth": 100, | ||
| } |
Sorry, the diff of this file is not supported yet
| <?xml version="1.0" encoding="utf-8"?> | ||
| <!-- Generator: Adobe Illustrator 23.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> | ||
| <svg version="1.1" id="no_connection" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1080 1080" style="enable-background:new 0 0 1080 1080;" xml:space="preserve"> | ||
| <title>no connection</title> | ||
| <g> | ||
| <path d="M501,416.3l12.5-20.3c0.2-0.4,0.8-0.5,1.1-0.3s0.5,0.6,0.3,1l-8.6,22.2c-0.6,1.6-2.3,2.3-3.9,1.7 | ||
| c-1.6-0.6-2.3-2.3-1.7-3.9C500.8,416.6,500.9,416.4,501,416.3z" style="fill:#10294C;"/> | ||
| <path d="M514.1,423.6l11.3-3c0.4-0.1,0.9,0.2,1,0.6c0.1,0.3,0,0.7-0.3,0.9l-9.4,6.9c-1.4,1-3.3,0.7-4.3-0.7 | ||
| c-1-1.4-0.7-3.3,0.7-4.3l0,0C513.4,423.8,513.7,423.7,514.1,423.6z" style="fill:#10294C;"/> | ||
| <path d="M487.4,417.6l-1.1-11.6c0-0.5,0.3-0.9,0.8-0.9c0.3,0,0.7,0.2,0.8,0.5l5.3,10.4c0.8,1.5,0.2,3.3-1.3,4.1 | ||
| s-3.3,0.2-4.1-1.3C487.5,418.4,487.4,418,487.4,417.6z" style="fill:#10294C;"/> | ||
| <path d="M493.4,458.4l-12.1,20.5c-0.2,0.4-0.7,0.5-1.1,0.3l0,0c-0.3-0.2-0.5-0.6-0.4-1l8.2-22.4 | ||
| c0.6-1.6,2.3-2.4,3.9-1.8s2.4,2.3,1.8,3.9C493.5,458,493.4,458.2,493.4,458.4z" style="fill:#10294C;"/> | ||
| <path d="M480.1,451.3l-11.2,3.1c-0.5,0.1-0.9-0.2-1-0.6c-0.1-0.3,0-0.7,0.3-0.9l9.3-7c1.3-1,3.3-0.7,4.3,0.6 | ||
| s0.7,3.3-0.6,4.3C480.8,451,480.5,451.2,480.1,451.3z" style="fill:#10294C;"/> | ||
| <path d="M507.9,456.8l1.3,11.6c0,0.5-0.3,0.9-0.8,0.9c-0.3,0-0.7-0.1-0.8-0.5l-5.4-10.3c-0.8-1.5-0.2-3.3,1.3-4.1 | ||
| s3.3-0.2,4.1,1.3C507.7,456,507.8,456.4,507.9,456.8z" style="fill:#10294C;"/> | ||
| </g> | ||
| <g> | ||
| <path d="M713.5,289c-9.2-6.7-20-11.2-30.8-14.8c-11.1-3.7-22.4-6.8-33.8-9c-12.3-2.5-24.7-4.4-37.1-5.9 | ||
| c-13.5-1.5-27.1-2.5-40.7-3c-14.2-0.5-28.4-0.6-42.6-0.1c-13.7,0.5-27.3,1.4-40.9,2.9c-12.5,1.4-25,3.2-37.3,5.7 | ||
| c-11.4,2.2-22.6,5.1-33.6,8.7c-11,3.7-22.1,8.1-31.7,14.8c-4.4,3.1-9,6.9-11.2,12c-2.3,5.4,0.2,10.6,3.9,14.6 | ||
| c3.7,3.8,7.9,7,12.5,9.5c5,2.9,10.2,5.4,15.6,7.5c11.2,4.4,22.7,7.9,34.5,10.5c11.9,2.8,24,4.9,36.1,6.5c13.3,1.8,26.6,3,40,3.7 | ||
| c14.1,0.8,28.3,1.1,42.5,0.8c13.9-0.2,27.7-1,41.5-2.2c12.8-1.2,25.6-2.8,38.3-5.1c11.4-2,22.7-4.6,33.8-7.8 | ||
| c11.7-3.4,23.4-7.5,34-13.6c4.6-2.5,8.8-5.5,12.5-9.1c3.8-3.9,7.1-9,5.2-14.6C722.3,296.1,717.8,292.1,713.5,289z M548.7,346.9 | ||
| c-16.6,0-33.2-0.6-49.8-2c-14.5-1.2-29.1-2.9-43.5-5.5c-11.7-2-23.2-4.7-34.5-8.3c-8.1-2.6-16.3-5.8-23.4-10.6 | ||
| c-4.2-2.9-8.8-6.9-9.6-12.3c-0.8-5,2.4-9.4,6-12.5c5.8-5.1,13.2-8.4,20.3-11.1c10.5-3.9,21.2-6.9,32.2-9 | ||
| c13.7-2.8,27.5-4.8,41.4-6.2c16.1-1.6,32.3-2.6,48.5-2.9c16.9-0.3,33.8,0,50.7,1c15.1,0.9,30.1,2.4,45.1,4.7 | ||
| c12.4,1.8,24.8,4.4,36.9,7.8c9,2.5,18.1,5.6,26.3,10.1c5.1,2.8,10.7,6.5,13.2,11.9c2,4.4,0.8,9.1-2.2,12.7 | ||
| c-4.3,5.3-10.9,8.8-17.1,11.5c-9.5,4.1-19.3,7.3-29.4,9.5c-13.1,3.1-26.3,5.4-39.7,7c-15.6,1.9-31.2,3.1-46.9,3.7 | ||
| C565.2,346.8,556.9,346.9,548.7,346.9L548.7,346.9z" style="fill:#FFFFFF;"/> | ||
| <path d="M668.1,129.7c-1.4-0.1-2.8-0.2-4.2-0.2c-1.6,0-3.1-0.2-4.7-0.4c-2.6-0.4-5.2-1-7.7-2c-3.8-1.6-7-4.1-10.4-6.4 | ||
| c-5.8-3.9-11.9-7.4-18.3-10.5c-8.1-3.9-16.5-7-25.2-9.3c-22.1-5.9-45.3-5.8-67.3,0.5c-6.5,2-12.9,4.3-19.2,6.9 | ||
| c-4.5,1.8-9.4,3.1-14.2,2.3c-8.7-1.4-14-8.8-20.3-14.2c-5.6-4.8-11.7-8.8-18.3-12.2c-8.4-4.3-17.4-7.4-26.6-9.5 | ||
| c-11.5-2.6-23.2-3.8-34.9-3.6c-6.6,0.1-13.1,1.1-19.3,3c-6.5,1.9-12.7,4.6-18.5,8c-6,3.5-11.6,7.6-16.8,12.3 | ||
| c-5.2,4.8-10,10.1-14.1,15.8c-4.2,5.8-7.7,12.1-10.5,18.7s-4.8,13.6-5.9,20.7c-1.1,7.3-1.2,14.6-0.3,21.9c1,7.8,3.1,15.4,6.2,22.6 | ||
| c3.5,8,8.1,15.6,13.6,22.4c6.4,7.9,13.8,15.1,21.9,21.4c4.5,3.5,9.2,6.8,14,9.9c0.9-9.2,2.1-18.4,3.7-27.6 | ||
| c1.2-6.8,2.1-13.8,6.2-19.5c6.5-9.2,18.3-11.6,28.9-10.4c6.4,0.8,12.7,2.6,18.5,5.4c3.2,1.5,6.3,3.2,9.2,5.1 | ||
| c2.8,1.8,5.5,3.7,8.5,5.2c12.3,6.4,26.4,8.4,40.1,7.2c16.4-1.4,32.2-7,46.5-15c5.4-3,10.4-6.6,15.8-9.5c3.6-2,7.4-3.7,11.3-5.2 | ||
| c7.6-3,15.6-4.8,23.8-5.4c7.9-0.6,15.9,0.5,23.3,3.3c7.6,2.9,14.3,7.8,19.5,14.1c6.5,7.9,10.5,17.5,13,27.3 | ||
| c1.5,6.4,2.5,12.8,3,19.3c0.3,3.7,0.4,7.4,0.5,11.1c0,0.5,0,1,0,1.5s-0.1,0.3,0.4,0.6c0.3,0.1,0.5,0.1,0.8,0.1l3.5,0.6 | ||
| c2.3,0.4,4.6,0.9,6.9,1.4c17.3,3.6,34.6,8.3,50.7,15.6c7.6,3.5,15.1,7.7,21.4,13.3c5.8,5.2,10.7,12.3,9.8,20.5 | ||
| c-1,9.1-8.5,16.1-15.7,21c-8.4,5.9-18,10.1-27.6,13.6c-12.5,4.5-25.3,8-38.3,10.6c-15.6,3.2-31.4,5.5-47.2,7 | ||
| c-17.7,1.7-35.5,2.5-53.3,2.6c-18.1,0.1-36.1-0.7-54.1-2.3c-17.6-1.5-35.1-4.1-52.4-7.8c-8.3-1.8-16.5-3.8-24.5-6.3 | ||
| c5.4,2.5,10.9,4.7,16.5,6.6c5.8,2.1,11.6,3.9,17.6,5.6c12.2,3.4,24.6,6.1,37.1,8c12.8,2,25.8,3.4,38.7,4.2s26.2,1.1,39.3,0.9 | ||
| c12.8-0.2,25.5-0.9,38.2-2.1c12.2-1.1,24.4-2.6,36.5-4.6c11.1-1.8,22.1-4,33.1-6.7c9.6-2.4,19.2-5.1,28.5-8.4 | ||
| c7.8-2.7,15.5-5.9,22.9-9.6c5.7-2.8,11-6.3,15.8-10.4c1.8-1.6,3.5-3.4,4.9-5.4c1.2-1.8,2.2-3.8,3-5.9c2.1-5.4,3.7-10.9,5-16.5 | ||
| c1.6-7,2.9-14.1,3.7-21.3c1-8.2,1.5-16.4,1.7-24.6c0.1-8.7-0.3-17.4-1.2-26c-0.9-8.8-2.6-17.5-4.8-26c-2.2-8.4-5.4-16.6-9.3-24.4 | ||
| c-3.9-7.6-8.7-14.7-14.4-21c-5.9-6.5-12.9-12-20.6-16.3c-8.5-4.7-17.7-8-27.3-9.8C678.8,130.6,673.5,130,668.1,129.7z" style="fill:#F04E23;"/> | ||
| <path d="M450.2,352.8c20,3.8,40.2,6.4,60.5,7.6c20.9,1.4,41.9,1.6,62.9,0.7c19.5-0.8,38.9-2.6,58.2-5.6 | ||
| c16.3-2.4,32.4-6.1,48.1-10.9c11.9-3.7,23.8-8.3,34.3-15c8.5-5.4,18.2-13.6,18.4-24.5c0.2-9.4-7-17-14.1-22.2 | ||
| c-7.9-5.8-16.9-10.1-26-13.6c-9.5-3.6-19.2-6.6-29.1-9c-5.1-1.3-10.3-2.4-15.4-3.4c-2.7-0.5-5.4-1-8.1-1.5c-0.3-0.1-0.7,0-0.8-0.3 | ||
| c0-0.3,0-0.5,0-0.8c0-0.5,0-1.1,0-1.6c0-1.1,0-2.3-0.1-3.5c-0.5-14.1-2.6-28.7-8.8-41.6c-4.4-9.2-10.9-17.3-19.7-22.5 | ||
| c-7.9-4.6-16.8-7-25.9-7c-9.4,0-18.8,1.7-27.5,5c-4.5,1.7-8.8,3.7-13,6c-2.2,1.2-4.3,2.5-6.4,3.9c-4.4,2.9-9,5.5-13.7,7.9 | ||
| c-16.3,8.2-34.5,13.4-52.8,12.6c-7.9-0.3-15.6-1.9-23-4.8c-3.5-1.4-6.9-3-10.1-5c-3.4-2.1-6.6-4.4-10.2-6.3 | ||
| c-12-6.3-27.5-10.3-40.5-4.8c-5.5,2.4-10,6.7-12.6,12.1c-1.7,3.7-2.4,7.6-3.2,11.6c-1,5.3-1.9,10.7-2.6,16 | ||
| c-2.6,19.2-4.1,38.6-4.3,58c-0.1,4.6-0.1,9.1-0.1,13.7c0,2.4,0.4,4.8,1.2,7.1c1.7,4.3,4.4,8.2,7.9,11.3c5.8,5.5,13,9.7,20.2,13.1 | ||
| c8.8,4.1,18,7.6,27.4,10.3C430.8,348.6,440.5,350.9,450.2,352.8z M713.5,320.4c-9.2,6.7-20,11.2-30.8,14.9 | ||
| c-11.1,3.7-22.4,6.7-33.8,9c-12.3,2.5-24.7,4.4-37.1,5.9c-13.5,1.5-27.1,2.5-40.7,3c-14.2,0.5-28.4,0.6-42.6,0.1 | ||
| c-13.7-0.5-27.3-1.4-40.9-2.9c-12.5-1.4-25-3.2-37.3-5.7c-11.4-2.2-22.6-5.1-33.6-8.7c-11-3.7-22.1-8.1-31.7-14.8 | ||
| c-4.4-3.1-9-6.9-11.2-12c-2.3-5.4,0.2-10.6,3.9-14.6c3.7-3.7,7.9-6.9,12.5-9.4c5-2.9,10.2-5.4,15.6-7.5 | ||
| c11.2-4.4,22.7-7.9,34.5-10.5c11.9-2.8,24-4.9,36.1-6.5c13.3-1.8,26.6-3,40-3.7c14.1-0.8,28.3-1,42.5-0.8c13.9,0.2,27.7,1,41.5,2.2 | ||
| c12.8,1.2,25.6,2.8,38.3,5.1c11.4,2,22.7,4.6,33.8,7.8c11.7,3.4,23.4,7.5,34,13.6c4.6,2.5,8.8,5.5,12.5,9.1c3.8,3.9,7.1,9,5.2,14.6 | ||
| C722.3,313.3,717.8,317.3,713.5,320.4z" style="fill:#FDB813;"/> | ||
| <path d="M548.7,266.5c-16.6,0-33.2,0.6-49.8,2c-14.5,1.2-29.1,2.9-43.5,5.5c-11.7,2-23.2,4.7-34.5,8.3 | ||
| c-8.1,2.6-16.3,5.8-23.4,10.6c-4.2,2.9-8.8,6.9-9.6,12.3c-0.8,5,2.4,9.4,6,12.5c5.8,5.1,13.2,8.4,20.3,11.1 | ||
| c10.5,3.9,21.2,6.9,32.2,9c13.7,2.8,27.5,4.8,41.4,6.2c16.1,1.6,32.3,2.6,48.5,2.9c16.9,0.3,33.8,0,50.7-1 | ||
| c15.1-0.9,30.1-2.4,45.1-4.7c12.5-1.8,24.8-4.4,37-7.7c9-2.5,18.1-5.6,26.3-10.1c5.1-2.8,10.7-6.5,13.2-11.9 | ||
| c2-4.4,0.8-9.1-2.2-12.7c-4.3-5.3-10.9-8.8-17.1-11.5c-9.5-4.1-19.3-7.3-29.4-9.5c-13.1-3.1-26.3-5.4-39.7-7 | ||
| c-15.6-1.9-31.2-3.1-46.9-3.7C565.2,266.6,556.9,266.4,548.7,266.5z" style="fill:#10294C;"/> | ||
| <path d="M367.8,315.1l-0.2-0.3L367.8,315.1z" style="fill:#FFFFFF;"/> | ||
| <path d="M367,313.7l-0.1-0.3L367,313.7z" style="fill:#FFFFFF;"/> | ||
| <path d="M366.4,312.3l-0.1-0.3L366.4,312.3z" style="fill:#FFFFFF;"/> | ||
| <path d="M369.7,317.9l-0.2-0.3L369.7,317.9z" style="fill:#FFFFFF;"/> | ||
| <path d="M365.8,310.8l-0.1-0.2L365.8,310.8z" style="fill:#FFFFFF;"/> | ||
| <path d="M365.3,309.3v-0.1V309.3z" style="fill:#FFFFFF;"/> | ||
| <path d="M368.7,316.5c-0.1-0.1-0.1-0.2-0.2-0.3C368.6,316.4,368.6,316.4,368.7,316.5z" style="fill:#FFFFFF;"/> | ||
| <path d="M374.7,323.2l-0.1-0.1L374.7,323.2z" style="fill:#FFFFFF;"/> | ||
| <path d="M377.7,325.8L377.7,325.8L377.7,325.8z" style="fill:#FFFFFF;"/> | ||
| <path d="M370.8,319.3l-0.2-0.3L370.8,319.3z" style="fill:#FFFFFF;"/> | ||
| <path d="M376.2,324.5l-0.1-0.1L376.2,324.5z" style="fill:#FFFFFF;"/> | ||
| <path d="M372,320.6l-0.2-0.2L372,320.6z" style="fill:#FFFFFF;"/> | ||
| <path d="M373.3,321.9l-0.2-0.2L373.3,321.9z" style="fill:#FFFFFF;"/> | ||
| <path d="M483.2,102.6c0,0,11.3,15.9,15.6,24.9s7.8,3.3-4.3-17.7C494.5,109.8,487.2,107.3,483.2,102.6z" style="fill:#10294C;"/> | ||
| <path d="M646.8,124.7c6.3,5.3,11.7,11.6,16.1,18.6c5.2,8.5,11,5-3.6-14.2C655,128.1,650.8,126.6,646.8,124.7z" style="fill:#10294C;"/> | ||
| <path d="M413,92.3h0.9c0.6,0,1.4,0,2.5,0c2.9,0.1,5.8,0.4,8.6,0.8c0.9,0.1,1.8,0.3,2.8,0.5s1.9,0.4,2.9,0.6 | ||
| c2,0.4,4.1,1,6.3,1.7s4.4,1.4,6.5,2.3c1.1,0.4,2.2,0.9,3.3,1.4s2.2,1,3.3,1.6s2.1,1.2,3.2,1.7s2.1,1.2,3.1,1.9 | ||
| c2,1.3,3.9,2.7,5.7,4.1s3.4,2.9,4.9,4.3c0.7,0.7,1.5,1.4,2.1,2.2l1.9,2.1c1.2,1.3,2.2,2.7,3.1,3.9s1.6,2.2,2.1,3.1 | ||
| c1.1,1.8,1.7,2.8,1.7,2.8c0.3,0.6,0.1,1.4-0.5,1.7c-0.5,0.3-1.1,0.2-1.5-0.2v-0.1c0,0-0.8-0.8-2.1-2.4c-0.7-0.8-1.5-1.6-2.5-2.6 | ||
| c-0.5-0.5-1-1-1.6-1.5s-1.1-1.1-1.8-1.7l-2-1.8c-0.7-0.6-1.4-1.2-2.2-1.8s-1.6-1.2-2.4-1.8s-1.7-1.2-2.5-1.8s-1.8-1.2-2.7-1.8 | ||
| l-2.8-1.7l-2.9-1.7c-1-0.5-2-1.1-3-1.6c-2-1-4.1-2-6.1-2.8s-4.1-1.7-6.1-2.4s-4-1.4-5.8-1.9s-3.6-1.1-5.3-1.5 | ||
| c-3.3-0.8-6.1-1.5-8.1-1.8l-2.3-0.4l-0.8-0.1h-0.1c-0.9-0.1-1.5-0.8-1.5-1.7C411.4,92.9,412.1,92.2,413,92.3z" style="fill:#10294C;"/> | ||
| <path d="M444.6,122.9c0.2,0,0.4,0.1,0.5,0.1c0.4,0.1,0.9,0.2,1.5,0.4s1.4,0.4,2.3,0.8s1.9,0.7,2.9,1.2l1.6,0.8l1.7,0.9 | ||
| c1.2,0.6,2.3,1.4,3.5,2.1s2.4,1.7,3.5,2.6c1.2,1,2.3,2,3.4,3.1l1.6,1.7c0.5,0.6,1,1.2,1.5,1.8s1,1.2,1.4,1.8l1.2,1.8 | ||
| c0.4,0.6,0.8,1.2,1.1,1.8s0.6,1.2,1,1.8s0.6,1.1,0.9,1.7s0.4,1.1,0.6,1.7c0.4,1.1,0.8,2.1,1.1,3s0.4,1.7,0.5,2.4 | ||
| c0.3,1.3,0.4,2.1,0.4,2.1c0.1,0.7-0.4,1.4-1.1,1.5c-0.5,0.1-1-0.2-1.3-0.6l-0.1-0.1c0,0-0.4-0.6-1-1.7c-0.3-0.6-0.7-1.2-1-2 | ||
| s-1-1.5-1.5-2.4c-0.3-0.4-0.5-0.9-0.9-1.4s-0.7-0.9-1-1.4l-1.1-1.5c-0.4-0.5-0.8-1-1.2-1.5l-1.2-1.5c-0.4-0.5-0.9-1-1.4-1.5 | ||
| l-1.4-1.5l-1.5-1.4c-1-1-2-1.9-3-2.8s-2.1-1.7-3.1-2.6s-2-1.6-3-2.3s-1.9-1.4-2.8-1.9s-1.7-1.1-2.4-1.6s-1.4-0.9-1.9-1.2 | ||
| s-0.9-0.6-1.2-0.7l-0.5-0.3c-0.8-0.5-1-1.4-0.5-2.2C443.4,123.1,444,122.8,444.6,122.9L444.6,122.9z" style="fill:#10294C;"/> | ||
| <path d="M682.4,149.5c1.6,1,3,2.3,4,3.9c1.1,1.5,1.9,3.2,2.4,5s0.7,3.7,0.6,5.6c-0.1,1.9-0.7,3.7-1.6,5.4 | ||
| c-0.4,0.6-1.2,0.8-1.8,0.5c-0.3-0.2-0.5-0.5-0.6-0.8v-0.2c-0.4-1.6-0.6-3.1-1-4.5s-0.7-2.8-1.2-4.2s-0.9-2.7-1.5-4.1 | ||
| s-1.1-2.9-1.5-4.4l-0.1-0.3c-0.2-0.9,0.3-1.8,1.1-2C681.5,149.3,682,149.3,682.4,149.5z" style="fill:#10294C;"/> | ||
| <path d="M730.8,407.4l-0.1-0.1h-0.1l0,0c-5.2-1.9-9.5-5.7-11.9-10.7l-37.6-79.5l0,0c-2-4.3-1.2-11.8,2-16.8l0.1-0.1 | ||
| c0,0,0-0.1,0.1-0.1V300c2.2-3.6,3.3-7.8,3.3-12c0-0.7,0-1.4-0.1-2.1c-11-4.5-25.5-8.5-42.5-11.6c-1.1,1.5-2,3.2-2.7,5l0,0 | ||
| c-2.2,5.8-4.7,9.1-13.5,10.4L533,304.2l0,0c-6.9,1-10.6-3.9-7.6-9.6l0,0l11.5-28c-3.5,0.1-7.3,0.1-10.7,0.3l-12.8,21.9l0,0 | ||
| c-3,5.1-6.9,5.7-13,9h0.2c-7.9,3.3-12.5,11.5-11.2,20c0.2,6.5-1.4,11.5-4.8,14.7L447,368.8l0,0c-4.6,4.4-11.6,1-9.6-6.1l0,0 | ||
| l19.7-89.1c-6,1-11.7,2.2-17.1,3.4l-12.1,81.5c-0.9,6.1-3.5,10.4-11.3,14.3l0,0c-11.8,5.5-16.9,19.5-11.4,31.3 | ||
| c1.7,3.7,4.4,6.9,7.7,9.2c0,0.2-0.1,0.4-0.1,0.6c-0.6,2.9-0.9,5.8-1,8.7c-0.1,3.4,0.3,6.9,1.2,10.2c1.5,5.9,5.3,11,10.6,14.1 | ||
| c1.7,1,3.5,1.8,5.4,2.4l1.4,0.4l0.3,0.1h0.1h0.1l0.3,0.1l0.6,0.1l0.3,0.1l0.4,0.1l0.9,0.1l0.9,0.1h0.8c2.1,0,4.3-0.3,6.3-1 | ||
| c3.4-1.2,6.5-2.9,9.4-5c2.6-1.8,4.8-3.5,7-4.9c1.9-1.3,3.9-2.3,6-3.1c1.6-0.6,3.4-1,5.1-1.1c1.3-0.1,2.7,0,4,0.2 | ||
| c0.6,0.1,1.6,0.2,1.6,0.3l0,0c3,1,3.2-1.9,1.7-2.2c-1.3-0.2-1-2.5,1.1-1.6c2.4,0.9,4.4-3.4,0.7-4.5c-0.8-0.2-2-0.5-3.5-0.8 | ||
| c-2-0.3-4-0.5-6-0.4c-2.6,0.1-5.2,0.6-7.7,1.5c-2.8,1-5.4,2.2-7.9,3.8c-2.5,1.5-5,3.3-7.4,4.8c-2.1,1.5-4.5,2.7-6.9,3.5 | ||
| c-1,0.3-2.1,0.4-3.1,0.4H435h-0.3h-0.3h-0.2L434,440l-0.6-0.1l-0.3-0.1l0,0l-0.9-0.3c-1.2-0.4-2.3-0.9-3.3-1.5 | ||
| c-0.9-0.6-1.8-1.3-2.6-2.1c-0.7-0.8-1.3-1.7-1.8-2.7c-0.3-0.5-0.5-1-0.7-1.5s-0.4-1.1-0.5-1.6c-0.5-2.3-0.7-4.7-0.6-7 | ||
| c0.1-1.9,0.3-3.7,0.7-5.5c1,0.1,2,0.2,3,0.2c12.4,0,22.7-9.6,23.5-22c0.1-0.5,0.2-1.1,0.3-1.6c-0.3-9.3-0.8-16.1,4.2-21l0,0 | ||
| l36.9-35.8h-0.1c3.8-3.7,8.5-4.2,13.2-3.5l0,0c1.3,0.3,2.6,0.4,3.9,0.4c2.7,0,5.4-0.6,7.8-1.7l0,0l0,0c0.6-0.3,1.1-0.5,1.6-0.8 | ||
| c4.5-2.1,9.1-2.1,12.9-0.1l55.9,29.7l0,0c4.3,2,6.7,5.6,6.7,9.7s0,0.4,0,0.4v0.1c0.7,10.4,9.7,18.3,20.1,17.7 | ||
| c2-0.1,3.9-0.6,5.7-1.3c0,0,0.1,0.2,0.2,0.2c5.9-2.4,10.4-2.8,14.6-0.8l0,0l69.5,28.3v-0.1c5,2,6.7,7.4,10,12.4 | ||
| c0.1,0.2,0.3,0.4,0.4,0.6l0.2,0.3c0.2,0.3,0.4,0.6,0.7,0.8c5.1,5.8,13.9,6.3,19.7,1.2c5.8-5.1,6.3-13.9,1.2-19.7 | ||
| C734.2,409.5,732.5,408.3,730.8,407.4L730.8,407.4z M622.5,345.3c-2,3.4-5.5,5.7-10.8,5.9h0.1c-3,0.1-5.9,0.9-8.5,2.3 | ||
| c-0.2,0.1-0.3,0.2-0.5,0.3l0,0c-0.5,0.3-0.9,1-1.4,1.3c-3.1,1.7-6.8,2.1-11.3,0.1l0,0l-55.4-30l0.2-0.2c-5.5-2.9-2.4-6.6,4.6-8 | ||
| v-0.2l88.2-17.7l0,0c7.4-1,14.1-0.1,16.1,2.4l0,0c2,2.5,1.2,7.8-2.6,13.8l-18.7,29.8L622.5,345.3z M707.6,407.5l-68-31.6 | ||
| c-4.4-2.2-7.6-6.2-9-10.9l0,0c-0.3-1.2-0.8-2.4-1.4-3.6c-0.1-0.2-0.2-0.5-0.3-0.7c-1.7-2.5-1.5-7.1,0.6-10.5l19.2-31.3 | ||
| c2.6-4.2,7.1-8,12.7-7.5s9.3,1.6,12.5,8.4l39.1,82.8C714.6,406.5,711.3,409.2,707.6,407.5z" style="fill:#00ACDC;"/> | ||
| <path d="M433.1,402.9c0,3.7-3,6.7-6.7,6.7s-6.7-3-6.7-6.7s3-6.7,6.7-6.7C430.1,396.2,433.1,399.2,433.1,402.9 | ||
| L433.1,402.9z" style="fill:#10294C;"/> | ||
| <path d="M677.1,298.9c0,3.7-3,6.7-6.7,6.7s-6.7-3-6.7-6.7s3-6.7,6.7-6.7l0,0C674.1,292.2,677.1,295.2,677.1,298.9z" style="fill:#10294C;"/> | ||
| <path d="M515.1,310.9c0,3.7-3,6.7-6.7,6.7s-6.7-3-6.7-6.7s3-6.7,6.7-6.7C512.1,304.2,515.1,307.2,515.1,310.9 | ||
| L515.1,310.9z" style="fill:#10294C;"/> | ||
| <path d="M733.1,419.9c0,3.7-3,6.7-6.7,6.7s-6.7-3-6.7-6.7s3-6.7,6.7-6.7l0,0C730.1,413.2,733.1,416.2,733.1,419.9z" style="fill:#10294C;"/> | ||
| <path d="M442.6,380.9c0,2.2-1.8,3.9-3.9,3.9s-3.9-1.8-3.9-3.9s1.8-3.9,3.9-3.9C440.9,376.9,442.6,378.7,442.6,380.9z" style="fill:#10294C;"/> | ||
| <path d="M614.6,361.9c0,2.2-1.8,3.9-3.9,3.9c-2.2,0-3.9-1.8-3.9-3.9c0-2.2,1.8-3.9,3.9-3.9l0,0 | ||
| C612.9,357.9,614.6,359.7,614.6,361.9z" style="fill:#10294C;"/> | ||
| <path d="M624.6,376.9c0,2.2-1.8,3.9-3.9,3.9c-2.2,0-3.9-1.8-3.9-3.9c0-2.2,1.8-3.9,3.9-3.9l0,0 | ||
| C622.9,372.9,624.6,374.7,624.6,376.9z" style="fill:#10294C;"/> | ||
| <path d="M657.6,282.9c0,2.2-1.8,3.9-3.9,3.9c-2.2,0-3.9-1.8-3.9-3.9c0-2.2,1.8-3.9,3.9-3.9l0,0 | ||
| C655.9,278.9,657.6,280.7,657.6,282.9z" style="fill:#10294C;"/> | ||
| <path d="M417.6,390.9c0,2.2-1.8,3.9-3.9,3.9s-3.9-1.8-3.9-3.9s1.8-3.9,3.9-3.9C415.9,386.9,417.6,388.7,417.6,390.9z" style="fill:#10294C;"/> | ||
| </g> | ||
| <g> | ||
| <path d="M750.4,577.5c-1.7-4.7-5.5-8.5-9.4-11.7c-5.7-4.4-11.9-8.1-18.5-11c-8.4-3.9-17.1-7.2-26-9.9 | ||
| c-10.6-3.3-21.3-6-32.2-8.3c-12.2-2.6-24.5-4.6-36.8-6.2c-13.2-1.7-26.5-2.9-39.8-3.6c-13.8-0.7-27.5-0.9-41.3-0.5 | ||
| c-13.9,0.4-27.8,1.4-41.6,3.1c-13.5,1.6-26.9,4-40.1,7.1c-12.6,3-25,6.9-37.1,11.6c-2.8,1.1-5.6,2.2-8.3,3.5 | ||
| c15-4.5,30.2-7.9,45.7-10.2c15.3-2.4,30.7-4,46.2-5c16.3-1,32.7-1.4,49-1.1c15.9,0.3,31.7,1.2,47.5,2.9c14.3,1.5,28.5,3.6,42.6,6.5 | ||
| c14.2,2.8,28.2,6.7,41.8,11.8c10.3,4,20.6,8.8,29.1,15.9c4.9,4.1,9.4,9.3,10.9,15.7c1.5,6.6-1,13-5.2,18.1 | ||
| c-4.6,5.6-10.8,9.8-17.1,13.4c-6.7,3.7-13.7,6.8-20.9,9.4c-14.1,5-28.6,8.9-43.3,11.6c-4,0.8-8,1.5-12,2.1l-2.9,0.5 | ||
| c-1.1,0.1-2.2,0.3-3.3,0.5c-0.5,0.2-0.3,0-0.4,0.2c-0.1,0.3-0.1,0.7,0,1v2c0,5.3,0,10.5,0,15.8l0.1,24.5c0,9.3,0,18.7,0,28 | ||
| c0,8.7,0,17.4,0,26.1c0,6.4,0,12.9-0.1,19.3c0,1,0,2.1,0,3.1c-0.1,0.8,0.1,1.5,0.7,2.1c2,1.6,5.5,1.3,7.9,1.3c4-0.2,8-0.7,12-1.5 | ||
| c3.7-0.7,7.3-1.7,10.8-3c1.1-0.4,2.1-0.9,3.1-1.6c1.2-1,0.8-2.6,0.9-4c0.3-6.3,1.4-12.5,3.3-18.5c3-9,8.9-18.2,19-19.6 | ||
| c5.8-0.8,12.4,0.4,17,4c5.2,4.1,7.6,10.7,8.8,17c2,10.9,1.2,22.4-0.1,33.3c-0.9,7.4-2.2,14.8-3.1,22.2c-0.8,6.3-1.3,12.7-1.5,19.1 | ||
| c-0.2,9,0.2,18.4,3.3,26.8c2.1,5.8,5.8,11,11.4,13.8c2.7,1.3,5.6,2.2,8.6,2.5c1.7,0.2,3.4,0.3,5.1,0.3c0.5,0,1-0.1,1.4-0.2 | ||
| c2.3-0.7,3.8-3,5-5c1.9-3.5,3.3-7.2,4.3-11c1.5-5.3,2.8-10.6,3.7-16c1.3-6.7,2.3-13.5,3.1-20.2c1-7.8,1.8-15.6,2.5-23.5 | ||
| c0.8-8.6,1.4-17.2,1.9-25.8c0.6-9,1-18.1,1.3-27.2c0.3-9.2,0.6-18.4,0.7-27.5c0.1-9.1,0.2-18.1,0.1-27.2 | ||
| c-0.1-8.6-0.2-17.2-0.5-25.8c-0.2-7.8-0.6-15.7-1.1-23.5c-0.4-6.8-0.9-13.5-1.7-20.3c-0.6-5.4-1.3-10.8-2.2-16.1 | ||
| C752.4,584.7,751.5,581.1,750.4,577.5z" style="fill:#F04E23;"/> | ||
| <path d="M709.1,783.4c1.4-8.4,2.2-16.8,2.6-25.2c0.3-6.1,0-12.1-1-18.1c-1.3-7.5-4.4-15.4-11.5-19.1 | ||
| c-7.5-4-17.7-3.4-24.2,2.2c-4.7,4.1-7.6,10-9.4,15.9c-1.1,3.5-1.9,7.1-2.4,10.8c-0.3,2-0.5,4-0.7,6c-0.1,1-0.1,1.9-0.2,2.9 | ||
| c0,0.4,0,0.9-0.1,1.3c-0.4,1-1.8,1.6-2.7,2c-1.8,0.8-3.6,1.4-5.5,1.9c-4.8,1.3-9.7,2.2-14.7,2.7c-2.2,0.2-4.4,0.3-6.6,0.2 | ||
| c-1.5,0-3-0.3-4.4-0.9c-1-0.6-1.2-1.3-1.2-2.4c0-1.3,0-2.6,0-3.9c0.1-8.3,0.1-16.7,0.1-25c0-11.1,0-22.2,0-33.3 | ||
| c0-10.7,0-21.3-0.1-32l-0.1-21.5c0-0.9,0-1.8,0-2.7c0-0.3-0.2-1,0-1.3c0.3-0.4,2.2-0.4,2.7-0.5c2.4-0.4,4.9-0.8,7.3-1.2 | ||
| c4.8-0.8,9.6-1.7,14.3-2.7c17.8-3.7,35.8-8.6,52.2-16.5c8-3.9,16-8.6,22.1-15.2c5.3-5.8,8.5-13.4,6-21.2c-2.4-7.4-8.6-13-14.8-17.3 | ||
| c-5.3-3.6-11-6.8-16.9-9.3c-15.6-7-32.3-11.5-49-14.9c-17.3-3.5-34.9-6-52.5-7.5c-19.6-1.7-39.3-2.3-59-2 | ||
| c-19.3,0.3-38.7,1.5-57.9,3.8c-18.7,2.1-37.2,5.6-55.4,10.5c-9,2.4-17.8,5.4-26.5,9c-7.3,2.9-14.2,6.7-20.6,11.2 | ||
| c-6.3,4.5-12.6,10.7-14.3,18.6v-0.1c-0.5,2.3-0.9,4.6-1.1,6.9c-0.3,2.1-0.5,4.2-0.8,6.2c-0.4,3.7-0.8,7.4-1.1,11.2 | ||
| c-0.5,5.5-0.9,11-1.3,16.4c-0.5,7.3-0.9,14.7-1.3,22c-0.5,9.3-0.8,18.6-1.1,27.9c-0.4,11.4-0.6,22.8-0.7,34.2 | ||
| c-0.2,13.4-0.3,26.9-0.2,40.3c0.1,12.8,0.4,25.7,0.9,38.5c0.4,11.1,1,22.2,1.7,33.3c0.6,9.3,1.4,18.6,2.3,27.9 | ||
| c0.7,7.5,1.6,15,2.5,22.5c0.7,5.6,1.4,11.3,2.3,16.9c0.6,3.8,1.2,7.5,1.9,11.2c0.3,1.8,0.7,3.6,1,5.5c0.7,3.5,1.7,7,2.8,10.5 | ||
| c1.8,5.6,4.1,11.1,6.9,16.3s6,10.1,9.6,14.7c3.7,4.7,7.7,9.1,12,13.1c4.4,4.2,9.1,8,14.1,11.6c5.1,3.6,10.4,7,15.9,10 | ||
| c5.7,3.1,11.5,5.9,17.5,8.4c6.1,2.6,12.4,4.8,18.8,6.8c6.5,2,13.2,3.8,19.8,5.3s13.7,2.7,20.6,3.7s14,1.7,21.1,2.1 | ||
| s14.2,0.6,21.4,0.5s14.2-0.5,21.3-1.1s14.1-1.5,21-2.7s13.7-2.6,20.5-4.3s13.2-3.7,19.7-6c6.3-2.2,12.5-4.7,18.6-7.6 | ||
| c5.9-2.7,11.6-5.8,17.2-9.2c5.4-3.3,10.6-6.9,15.6-10.8c4.9-3.8,9.5-8,13.7-12.5c4.2-4.4,8-9,11.5-14s6.5-10.2,9.1-15.7 | ||
| c2.6-5.6,4.8-11.4,6.4-17.4c0.5-1.8,1.2-3.5,1.7-5.3c1.5-5.4,2.8-10.8,3.7-16.4c1.8-10.4,3.1-20.9,4.2-31.5 | ||
| c-8.5,0.1-16.8-2.6-21.8-9.9c-2.6-4.1-4.4-8.6-5.3-13.4c-1.2-6.6-1.7-13.2-1.4-19.9C706.4,804.1,707.4,793.7,709.1,783.4z | ||
| M365,589.2L365,589.2C365,589.4,365,589.2,365,589.2z M421,624.8c-11.5-3.6-23.1-7.9-33.4-14.4c-4.5-2.8-9-6.2-12.1-10.7 | ||
| c-1.6-2.1-2.6-4.7-2.8-7.4c0.1-2.7,1-5.3,2.6-7.4c3-4.5,7.6-7.9,12.1-10.8c4.6-2.9,9.5-5.4,14.5-7.6c11.2-4.7,22.7-8.5,34.6-11.3 | ||
| c11.7-2.9,23.6-5.1,35.5-6.8c13.1-1.9,26.4-3.2,39.6-4.1c14.1-0.9,28.2-1.3,42.3-1.1c13.9,0.1,27.9,0.7,41.8,1.9 | ||
| c13,1.1,26,2.6,38.9,4.8c11.5,1.9,22.9,4.4,34.2,7.4c11.8,3.3,23.6,7.2,34.5,12.9c4.5,2.3,8.8,5.1,12.8,8.3c4,3.4,8.7,8.1,8.6,13.8 | ||
| S720,602.6,716,606c-4,3.2-8.3,6-12.9,8.3c-10.9,5.6-22.6,9.6-34.3,12.8c-11.3,3.1-22.8,5.6-34.4,7.5c-12.8,2.1-25.8,3.7-38.8,4.7 | ||
| c-13.9,1.1-27.8,1.8-41.8,1.9c-14.1,0.1-28.2-0.3-42.3-1.2c-13.2-0.8-26.5-2.2-39.6-4.1c-11.9-1.7-23.8-3.9-35.5-6.8 | ||
| C431.2,627.8,426.1,626.4,421,624.8z M608.8,646L608.8,646z" style="fill:#FDB813;"/> | ||
| <path d="M709.6,590.3c0-5.2-4-9.5-7.8-12.5c-6.5-5-14.3-8.2-21.9-10.9c-10.9-3.7-22-6.6-33.3-8.6 | ||
| c-14.1-2.7-28.3-4.5-42.5-5.8c-16.3-1.5-32.8-2.3-49.2-2.4c-16.7-0.2-33.5,0.3-50.1,1.5c-14.9,1-29.7,2.7-44.4,5.1 | ||
| c-12.1,1.9-24.1,4.5-35.9,8c-8.5,2.6-17.1,5.7-24.8,10.3c-4.6,2.8-9.8,6.7-11.5,12.1c-1.5,4.8,0.8,9.3,4.1,12.7 | ||
| c5.1,5.2,12,8.5,18.6,11.3c10,4,20.3,7.1,30.9,9.3c13.4,2.9,26.9,5.1,40.5,6.6c15.8,1.8,31.7,2.8,47.6,3.3c17,0.5,34,0.3,51-0.5 | ||
| c15.4-0.8,30.8-2.1,46-4.3c12.8-1.7,25.5-4.2,38-7.3c9.5-2.5,19-5.5,27.9-9.9c5.5-2.8,11.5-6.3,14.9-11.7 | ||
| C708.9,594.6,709.6,592.4,709.6,590.3z" style="fill:#10294C;"/> | ||
| <path d="M713.5,607.9c4.4-3.2,9.2-7.4,10.8-12.8c1.6-5.6-1.9-10.7-5.7-14.4c-7.8-7.7-18.5-12.5-28.5-16.4 | ||
| c-11.2-4.3-22.7-7.7-34.4-10.3c-12-2.7-24.1-4.8-36.3-6.4c-13.3-1.7-26.7-2.9-40.1-3.6c-14.2-0.7-28.3-0.9-42.5-0.7 | ||
| c-13.8,0.3-27.6,1-41.4,2.3c-12.8,1.2-25.5,2.9-38.1,5.2c-11.4,2-22.6,4.7-33.7,7.9c-11.6,3.5-23.2,7.7-33.6,13.8 | ||
| c-8,4.7-21,13.7-15.9,24.5c2.4,4.9,7,8.7,11.4,11.8c4.4,3,9.1,5.5,13.9,7.7c11.1,5,22.7,8.9,34.6,11.8c11.6,3,23.3,5.2,35.2,7 | ||
| c13,2,26.2,3.4,39.3,4.3c14,1,28.1,1.4,42.2,1.4c14.1-0.1,28.1-0.6,42-1.6c13.1-1,26.1-2.5,39-4.5c11.7-1.8,23.3-4.2,34.8-7.2 | ||
| c11.9-3,23.4-7.1,34.5-12.3C705.2,613.3,709.5,610.8,713.5,607.9z M387.8,590.3c0-5.2,4-9.5,7.9-12.5c6.5-5,14.3-8.2,21.9-10.9 | ||
| c10.9-3.7,22-6.6,33.3-8.6c14.1-2.7,28.3-4.5,42.5-5.8c16.3-1.5,32.7-2.3,49.2-2.4c16.7-0.2,33.5,0.3,50.1,1.5 | ||
| c14.8,1,29.7,2.7,44.4,5.1c12.1,1.9,24.1,4.6,35.8,8c8.5,2.6,17.2,5.7,24.8,10.3c4.6,2.8,9.8,6.7,11.5,12.1 | ||
| c1.5,4.8-0.8,9.3-4.1,12.7c-5.1,5.2-12,8.5-18.6,11.3c-10,4-20.3,7.1-30.8,9.3c-13.4,2.9-26.9,5.1-40.6,6.6 | ||
| c-15.8,1.8-31.7,2.8-47.6,3.3c-17,0.5-34,0.3-51-0.5c-15.4-0.8-30.8-2.1-46-4.3c-12.8-1.7-25.5-4.2-38-7.3 | ||
| c-9.5-2.5-19.1-5.5-27.9-9.9c-5.5-2.8-11.5-6.3-14.9-11.7C388.5,594.6,387.8,592.5,387.8,590.3z" style="fill:#FFFFFF;"/> | ||
| <path d="M693.7,522.9c-2,0-4,0.3-5.9,0.9l0,0c-5.7,2.1-10.3,3.4-16.1-0.1l0,0L618.4,491c-5.5-3.4-7.2-8.3-8.5-14.4 | ||
| c-0.2-1-0.4-2-0.5-3.1l0,0c0-0.1-0.2-0.3-0.2-0.4c-2.9-10.2-12.2-17.2-22.7-17.2c-0.8,0-1.7,0.1-2.5,0.1c-0.3-0.4-0.6-0.7-1-1 | ||
| c-1-0.9-2.1-1.8-3.2-2.5l-1.8-1.2l-1.9-1l-1-0.5c-0.3-0.1-0.7-0.3-1-0.4l-2.1-0.8c-0.7-0.2-1.4-0.4-2.2-0.6l-1.1-0.3 | ||
| c-0.4-0.1-0.7-0.1-1.1-0.2l-2.2-0.3l-2.3-0.1L562,447h-1c-0.7,0-1.5,0-2.3,0.1c-1.5,0.2-2.9,0.3-4.5,0.6c-0.8,0.1-1.6,0.3-2.3,0.5 | ||
| l-1.9,0.5l-3.7,1c-2.1,0.5-4.2,0.9-6.4,1c-1.8,0.1-3.7-0.1-5.5-0.7c-1.7-0.5-3.3-1.2-4.8-2.1s-2.9-1.8-4.1-2.6l-0.5-0.4l0,0 | ||
| c-2.4-2-3.8-0.7-4.3-0.2c-1.1,1-2.8,1.2-4.1,0.4c-1.8-1-4.4,1.3-1.5,3.6c0.4,0.4,0.9,0.8,1.6,1.3c0.9,0.8,2,1.7,3.4,2.7 | ||
| s2.9,2.2,4.8,3.3c2.1,1.3,4.4,2.4,6.7,3.2c2.7,0.9,5.6,1.4,8.5,1.3c2.9,0,5.8-0.4,8.6-1c0.7-0.1,1.4-0.3,2-0.5l1-0.3l0.8-0.2 | ||
| l1.9-0.5c0.6-0.1,1.1-0.1,1.6-0.2c1-0.2,2.2-0.2,3.3-0.3c0.5,0,1.1,0,1.7,0h0.8c0.3,0,0.5,0,0.8,0.1l1.6,0.1l1.5,0.3 | ||
| c0.3,0,0.5,0.1,0.8,0.1l0.7,0.2c0.5,0.1,1,0.3,1.4,0.4l1.3,0.5c0.2,0.1,0.4,0.2,0.7,0.3l0.6,0.3l1,0.5c-3.9,3-6.8,7.1-8.2,11.9l0,0 | ||
| c0,0,0,0,0,0.1c-0.2,0.7-0.4,1.6-0.5,2.3c-1.8,7.8-1.9,12.2-9.8,18.2l0,0l-19,12.4c-5.9,3.9-9.6,3.8-20.2,2.1h-0.1 | ||
| c-1.3-0.3-2.7-0.4-4.1-0.5c-10.5,0-19,8.5-19,19c0,2.1,0.3,4.1,1,6.1c0.1,0.3,0.1,0.5,0.3,0.7l0,0c0.3,0.9,0.8,1.8,1.3,2.6 | ||
| c2.2,5.1,3.4,11.4,2.4,15.7l-6.5,28.2l0,0c-0.7,3.1-4,8.4-7.7,9.1l0,0c-1.2,0.2-2.3,0.5-3.5,0.9c-0.2,0-0.3,0.1-0.5,0.2 | ||
| c-2.6,1.2-8-0.4-10.1-2.6l-70.3-72.8c-2.4-2.5-1.2-4.8-1.2-7l0,0c0-0.9,0.2-1.9,0.2-2.8c0-7.7-6.2-14-13.9-14s-14,6.2-14,13.9 | ||
| c0,7.7,6.2,14,13.9,14l0,0c0.7,0,1.4-0.1,2.1-0.2c0.1,0,0.1,0,0.2,0c2.5-0.5,5.8,0.3,7.2,1.7l68.6,71c2.1,2.2,4.5,9.1,3.5,12.6v0.1 | ||
| c-2.6,7-1.7,14.9,2.5,21.2c11.2,1.7,23.2,3,35.9,3.9c2.5-2.4,4.5-5.4,5.7-8.6c0,0,0,0,0.1-0.1c0.7-2.3,4-5.9,7.6-6.9l0,0l74.2-14.8 | ||
| c4.6-1,9.5,0,13.4,2.6c0.3,0.3,0.7,0.5,1.1,0.7l0,0c4.3,3.4,7.2,5,8.5,10.8l3.2,14.2c4.8-0.6,9.4-1.2,13.9-1.8l-6.3-16.1 | ||
| c-1.4-4.1-1.2-8.5,0.7-12.4c0.2-0.3,0.3-0.6,0.4-0.8c0-0.1,0.1-0.2,0.2-0.3l0,0c0.5-1.2,0.8-2.4,1-3.7v-0.2c0.6-3.9,3-7.3,6.5-9.2 | ||
| l0,0l39.8-23.3c4-2.3,11.8,0.8,10.1,6.6l-15.7,52.1c3.2-0.8,6.2-1.7,9-2.6l13.9-46.1c1.3-4.4,5.7-7.6,9.8-8.9l0,0 | ||
| c9.9-3.4,15.3-14.1,11.9-24.1C708.9,528.3,701.8,523.1,693.7,522.9L693.7,522.9z M590,589.2L590,589.2l-74.2,11.1 | ||
| c-3.8,0.6-10.4-2.9-12.9-5.4s-3.5-8.1-2.5-12.2l0,0l9.5-29.5l0,0c1.3-4,3.4-7.6,6.6-9c0.3-0.1,0.5-0.2,0.8-0.3c0,0,0,0,0.1,0 | ||
| c3.7-1.1,7.2,0.1,12.3,3l61.7,35.6l0,0C595.5,584.7,594.9,588.2,590,589.2z M668.8,552l-38.7,22.8c-4.2,2.3-9,3-13.6,1.9 | ||
| c-1.4-0.5-2.8-0.7-4.3-0.7c-1.1,0-2.2,0.1-3.3,0.4h-0.1c-4.7,0.9-9.7-0.1-13.7-2.8l0,0L536.4,534c-4.3-2.9-7.3-5.9-7.2-9 | ||
| s4.4-7.7,10-10.8l0,0l20.8-11.9l0,0c5.3-3,12.2-2.3,19.5-0.5c3.5,1.1,7.2,1.3,10.8,0.8c0.3,0,0.6-0.1,0.9-0.2 | ||
| c8.8-1.8,14.4-4.7,23.2,0.1l0,0l53,29.2c3.7,2,7.5,6.4,7.5,10.1C674.8,546.1,672.5,549.9,668.8,552z" style="fill:#00ACDC;"/> | ||
| <path d="M379.7,663c0.4-0.4,0.6-0.6,0.9-0.9s0.6-0.5,0.9-0.7c0.6-0.5,1.1-0.8,1.7-1.2c1.2-0.7,2.4-1.4,3.6-2 | ||
| s2.5-1.1,3.7-1.6c1.3-0.5,2.6-0.8,3.9-1.2c5.2-1.2,10.5-1.7,15.8-1.4l2,0.1h1l1,0.1l3.9,0.5c1.3,0.1,2.5,0.5,3.8,0.7l3.8,0.8 | ||
| c2.5,0.7,5,1.4,7.4,2.1l7.2,2.6c3.2,1.1,4.9,4.7,3.7,7.8c-1.1,3.2-4.7,4.9-7.9,3.7l0,0l-0.5-0.2l-0.3-0.2l-6.3-3 | ||
| c-2.1-0.9-4.3-1.7-6.4-2.5l-3.3-1c-1.1-0.3-2.2-0.7-3.3-1l-3.3-0.8l-0.8-0.2l-0.8-0.1l-1.7-0.2c-4.4-0.7-8.9-0.8-13.3-0.2 | ||
| c-1.1,0.2-2.2,0.4-3.2,0.6c-1.1,0.3-2.1,0.6-3.1,1s-2,0.8-3,1.3c-0.4,0.3-0.9,0.5-1.3,0.8l-0.6,0.4c-0.2,0.1-0.4,0.3-0.5,0.3 | ||
| l-1.3,0.5c-1.6,0.6-3.4-0.3-4-1.9C378.6,665.1,378.9,663.8,379.7,663L379.7,663z" style="fill:#F04E23;"/> | ||
| <path d="M585.5,705.1l-0.4-0.6c-0.2-0.2-0.4-0.5-0.5-0.7c-0.3-0.5-0.8-0.9-1.2-1.4s-0.9-0.9-1.3-1.3 | ||
| c-0.4-0.4-0.9-0.9-1.4-1.3c-1.9-1.6-4-3.1-6.2-4.4c-4.5-2.5-9.3-4.4-14.3-5.6c-1.3-0.3-2.5-0.6-3.8-0.9l-3.8-0.6 | ||
| c-0.6-0.1-1.3-0.2-1.9-0.3l-2-0.1l-3.9-0.3c-2.6-0.1-5.3,0-7.9,0l-7.9,0.6h-0.4c-3.4,0.4-6.4-2.1-6.8-5.4s2.1-6.4,5.4-6.8 | ||
| c0.1,0,0.3,0,0.4,0h0.5l8.7,0.1c2.9,0.3,5.8,0.4,8.7,0.8l4.3,0.7l2.2,0.3c0.7,0.2,1.4,0.3,2.2,0.5l4.3,1c1.4,0.4,2.8,0.9,4.2,1.4 | ||
| c5.6,1.9,11,4.6,15.9,8c2.5,1.7,4.7,3.7,6.8,5.8c0.5,0.5,1.1,1.1,1.5,1.7c0.5,0.6,1,1.1,1.5,1.8s0.9,1.2,1.4,1.9 | ||
| c0.2,0.3,0.4,0.6,0.7,1s0.4,0.7,0.7,1.2c0.8,1.6,0.2,3.5-1.4,4.4c-1.4,0.7-3.2,0.3-4.1-1L585.5,705.1z" style="fill:#F04E23;"/> | ||
| <path d="M486.1,822.3c-0.2-1.1-1.3-1.9-2.5-1.7l0,0l0,0c-1.8,0.3-3.6,0.5-5.4,0.5c-0.9,0-1.8-0.1-2.6-0.2l-1.3-0.2 | ||
| l-1.3-0.3c-10.2-2.3-17.5-11.4-17.7-21.8c0-3.3,0.7-6.7,2.2-9.7c1.4-3.1,3.6-5.8,6.2-7.9c2.7-2.2,5.9-3.8,9.3-4.6l1.3-0.3l1.3-0.2 | ||
| c0.9-0.1,1.8-0.2,2.7-0.2c1.8,0,3.7,0.1,5.5,0.5l0,0c0.9,0.2,1.8-0.5,2-1.4c0.1-0.8-0.3-1.5-1-1.8c-2-0.9-4-1.5-6.1-1.8 | ||
| c-0.1,0-0.3,0-0.4,0c0.5-2.8,0.8-5.6,1-8.4c0.3-3.7,0.4-7.4,0.3-11s-0.2-7.4-0.6-11c-0.3-3.7-0.9-7.4-1.7-11 | ||
| c-0.2-0.9-1.2-1.5-2.1-1.3c-0.6,0.2-1.1,0.7-1.3,1.3c-0.8,3.6-1.4,7.3-1.7,11c-0.3,3.7-0.5,7.4-0.6,11s0,7.4,0.3,11 | ||
| c0.2,2.7,0.5,5.5,1,8.2l-0.8,0.1c-2.2,0.2-4.4,0.7-6.5,1.4c-6.4,2.4-11.7,7-14.9,13c-4.4,8.2-4.5,18.1-0.1,26.3 | ||
| c2.1,4.1,5.2,7.5,9,10.1c1.9,1.3,3.9,2.3,6,3.1c2.1,0.7,4.3,1.3,6.5,1.5l1.7,0.2h1.6c1.1,0,2.2-0.1,3.3-0.2 | ||
| c2.2-0.3,4.3-0.9,6.3-1.8C485.9,824.2,486.3,823.3,486.1,822.3z" style="fill:#10294C;"/> | ||
| <path d="M691.3,739.4c0.9,2.4,1.4,4.8,1.7,7.4c0.6,4.9,0.6,9.8,0,14.7c-0.3,2.5-0.9,5-1.7,7.4c-0.4,1-1.5,1.5-2.5,1.1 | ||
| c-0.5-0.2-0.9-0.6-1.1-1.1c-0.9-2.4-1.4-4.8-1.7-7.4c-0.6-4.9-0.6-9.8,0-14.7c0.3-2.5,0.9-5,1.7-7.4c0.4-1,1.5-1.5,2.4-1.1 | ||
| C690.7,738.5,691.1,738.9,691.3,739.4z" style="fill:#F04E23;"/> | ||
| <path d="M465,928.8c2.5,0,5.1,0.2,7.6,0.5s5,0.6,7.5,1s5,0.8,7.5,1.4c2.5,0.5,5,1.2,7.5,2.1l0,0 | ||
| c1.1,0.4,1.7,1.7,1.3,2.8c-0.3,0.7-0.8,1.1-1.5,1.3c-2.6,0.6-5.3,0.9-8,0.9c-5.4,0-10.7-0.8-15.8-2.5c-2.6-0.8-5.1-2-7.4-3.6 | ||
| c-1-0.7-1.2-2-0.6-2.9c0.4-0.6,1-0.9,1.7-0.9L465,928.8z" style="fill:#10294C;"/> | ||
| <path d="M630,776.9c2.6-0.4,5.2-0.6,7.7-0.9s5.1-0.6,7.6-0.9s5-0.7,7.5-1.2s5-1,7.6-1.5h0.1c0.7-0.1,1.4,0.4,1.5,1.1 | ||
| c0.1,0.5-0.1,0.9-0.5,1.2c-2.2,1.8-4.7,3.2-7.3,4.2s-5.3,1.8-8.1,2.2s-5.6,0.6-8.3,0.5c-2.8-0.1-5.6-0.6-8.3-1.5 | ||
| c-0.8-0.3-1.3-1.2-1-2c0.2-0.6,0.7-1,1.3-1.1L630,776.9z" style="fill:#F04E23;"/> | ||
| <path d="M495.3,894.5c-4.2-0.3-8.4,0-12.5,0.7c-4.3,0.7-8.4,2.2-12.2,4.3c-1.8,1.1-3.6,2.2-5.3,3.4 | ||
| c-0.5,0.3-0.7,0.5-1.1,0.7l-1.1,0.6c-0.8,0.3-1.5,0.6-2.3,0.9c-3.2,0.9-6.5,0.9-8.9-0.6c-1.2-0.8-2.3-1.8-3.1-3 | ||
| c-0.4-0.6-0.8-1.3-1.1-2c-0.2-0.3-0.3-0.7-0.5-1.1l-0.3-1.1c-0.3-1.4-0.4-2.8-0.1-4.3c0.3-1.4,0.9-2.7,1.8-3.8s1.9-2,3.2-2.7 | ||
| c1.2-0.6,2.5-0.9,3.9-0.8h0.5l1,0.1c0.7,0,1.4,0,2.1,0c1.7-0.2,3.3-0.7,4.7-1.7c1.6-1.1,2.7-2.7,3.2-4.6c0.5-1.7,0.5-3.5,0-5.1 | ||
| c-0.5-1.8-1.6-3.4-3.1-4.5c-1.4-1-3-1.6-4.6-1.8c-0.7-0.1-1.5-0.1-2.2-0.1l-1,0.1h-0.5c-0.7,0.1-1.4,0-2.1-0.1 | ||
| c-2.9-0.5-5.5-2.2-7.3-4.7c-1.6-2.4-2-5.5-1-8.2l0.4-1.1c0.2-0.3,0.4-0.7,0.5-1l0.3-0.5l0.3-0.4c0.2-0.3,0.4-0.6,0.6-0.9 | ||
| c1-1.1,2.2-1.9,3.6-2.4c1.5-0.6,3.1-0.9,4.7-0.9c1.7,0,3.4,0.2,5.1,0.6l1.2,0.3l1.2,0.4c0.4,0.2,0.7,0.3,1.1,0.5l0.5,0.3 | ||
| c0.1,0,0.1,0.1,0.2,0.1l0.3,0.2c1.7,1.2,3.5,2.4,5.3,3.4c3.8,2,7.9,3.4,12.1,4.2c4.1,0.7,8.3,0.9,12.4,0.6c0.9,0,1.6-0.7,1.6-1.6 | ||
| c0.1-0.9-0.6-1.7-1.6-1.8l0,0l0,0c-7.8-0.5-15.5-2.1-21.7-6.2c-1.6-1-3-2.1-4.6-3.4l-0.3-0.2l-0.4-0.3l-0.8-0.5 | ||
| c-0.5-0.3-1.1-0.6-1.6-0.9l-1.6-0.7l-1.6-0.6c-2.2-0.7-4.5-1-6.8-1.1c-2.4-0.1-4.8,0.2-7.1,1c-2.4,0.8-4.6,2.2-6.5,4 | ||
| c-0.4,0.5-0.9,0.9-1.3,1.5c-0.2,0.3-0.4,0.5-0.6,0.8l-0.5,0.8c-0.3,0.5-0.6,1.1-0.9,1.7l-0.7,1.7c-1.8,4.9-1.3,10.4,1.5,14.8 | ||
| c2.8,4.3,7.2,7.2,12.3,8.2c1.3,0.2,2.6,0.3,3.9,0.3h1h0.5c0.3,0,0.5,0,0.8,0s0.6,0.1,0.9,0.3c0.1,0.1,0.2,0.2,0.2,0.4 | ||
| c0.1,0.3,0.1,0.5,0,0.8c0,0.1,0,0.1-0.1,0.1c0,0,0,0-0.1,0.1c-0.3,0.2-0.6,0.3-0.9,0.3s-0.6,0-0.9,0h-0.5h-1 | ||
| c-2.7-0.1-5.3,0.5-7.8,1.7c-5.7,6.9-6.9,17-6.3,25.3l0.7,1.1c1.5,2.1,3.5,3.8,5.7,5c2.3,1.2,4.8,1.8,7.4,1.8c2.4,0,4.8-0.4,7.1-1.2 | ||
| c1.1-0.4,2.2-0.9,3.2-1.4c0.5-0.3,1-0.6,1.5-0.9s1.1-0.7,1.4-1c1.6-1.2,3-2.3,4.6-3.3c6.2-4,13.8-5.6,21.5-6.1l0,0 | ||
| c1.2-0.1,2-1.1,2-2.2c-0.1-1-0.9-1.9-2-2L495.3,894.5z" style="fill:#10294C;"/> | ||
| <path d="M448.4,879.4c-3.2,0-31.4,10-29.7,35.3c1.8,27.5,32.6,16,26.2,2.3c-1.6-4-2.5-8.1-2.8-12.4 | ||
| C441.5,896.3,442.7,886.3,448.4,879.4z" style="fill:#00ACDC;"/> | ||
| <path d="M427,911.7c0.3,0.3,0.5,0.6,0.7,0.9c0.1,0.3,0.2,0.6,0.2,0.9c0,0.3-0.1,0.7-0.2,1c-0.1,0.4-0.4,0.8-0.7,1.2 | ||
| l-0.1,0.1c-0.4,0.4-1,0.6-1.5,0.3c-0.4-0.2-0.8-0.6-1.1-1c-0.2-0.4-0.4-0.8-0.4-1.2c-0.1-0.4,0-0.9,0.1-1.3c0.2-0.5,0.5-1,0.9-1.3 | ||
| c0.5-0.4,1.2-0.3,1.7,0.1L427,911.7z" style="fill:#FFFFFF;"/> | ||
| <path d="M431,898.8c0,0.8-0.2,1.5-0.4,2.3c-0.2,0.7-0.5,1.4-0.8,2.1c-0.3,0.7-0.6,1.4-1,2c-0.4,0.7-0.8,1.4-1.4,2l0,0 | ||
| c-0.4,0.4-0.9,0.4-1.4,0.1c-0.1-0.1-0.2-0.3-0.3-0.4c-0.5-1.7-0.4-3.5,0.3-5.2c0.3-0.8,0.7-1.6,1.2-2.3s1.2-1.4,2-1.8 | ||
| c0.5-0.3,1.2-0.1,1.5,0.4c0.1,0.2,0.2,0.4,0.2,0.6L431,898.8z" style="fill:#FFFFFF;"/> | ||
| <path d="M692.7,663.8c3.4-1.8,6.8-3.3,10.2-4.8l10.2-4.5c3.4-1.5,6.7-3.1,10-4.7s6.6-3.3,9.9-5.1l0,0 | ||
| c0.6-0.3,1.4-0.1,1.7,0.6c0.2,0.5,0.2,1-0.2,1.4c-2.6,2.9-5.6,5.4-8.9,7.5c-3.2,2.2-6.6,4.1-10.1,5.7c-3.5,1.6-7.1,3.1-10.7,4.2 | ||
| c-3.6,1.2-7.4,2.1-11.2,2.7c-0.9,0.1-1.7-0.4-1.8-1.3C691.7,664.8,692,664.1,692.7,663.8L692.7,663.8z" style="fill:#10294C;"/> | ||
| <path d="M660,660.2c0.9-0.8,1.9-1.4,3-1.8c1-0.4,2.1-0.8,3.2-1c1.1-0.2,2.2-0.3,3.3-0.4c1.2,0,2.4,0.1,3.5,0.4l0,0 | ||
| c0.8,0.2,1.2,1,1,1.8c-0.1,0.2-0.2,0.5-0.4,0.6c-0.9,0.9-1.9,1.6-2.9,2.1s-2.1,0.9-3.2,1.2c-1.1,0.3-2.2,0.5-3.4,0.5 | ||
| c-1.2,0.1-2.4-0.1-3.6-0.5c-0.9-0.3-1.5-1.3-1.2-2.3l0,0C659.5,660.7,659.7,660.5,660,660.2L660,660.2z" style="fill:#10294C;"/> | ||
| <path d="M680.3,680.8c2.2-1.2,4.4-2.2,6.8-3.1c2.3-0.9,4.5-1.6,6.8-2.4l6.8-2.2c2.3-0.7,4.5-1.5,6.9-2.2l0,0 | ||
| c0.7-0.2,1.4,0.2,1.6,0.9c0.1,0.4,0,0.9-0.3,1.3c-1.8,1.8-3.9,3.4-6.2,4.7c-2.2,1.3-4.5,2.3-6.9,3.2c-2.4,0.8-4.8,1.5-7.3,2 | ||
| s-4.9,0.8-7.5,0.8l0,0c-0.9,0-1.6-0.7-1.7-1.6C679.4,681.6,679.7,681.1,680.3,680.8z" style="fill:#10294C;"/> | ||
| <path d="M533.6,816.5c1,1,2.2,2.2,3.4,3.2c0.6,0.5,1.2,1,1.8,1.5c0.3,0.2,0.6,0.5,0.9,0.7l1,0.7 | ||
| c2.6,1.8,5.3,3.3,8.1,4.6c1.5,0.6,2.9,1.3,4.4,1.7c0.7,0.2,1.5,0.5,2.2,0.8s1.5,0.4,2.3,0.7c0.4,0.1,0.8,0.2,1.2,0.3 | ||
| s0.8,0.2,1.2,0.3l2.4,0.6c1.6,0.3,3.2,0.6,4.9,1h0.1c0.7,0.2,1.1,0.9,0.9,1.6l0,0c-0.1,0.5-0.5,0.8-1,0.9c-1.7,0.3-3.5,0.5-5.2,0.6 | ||
| h-2.6c-0.9,0-1.8-0.1-2.6-0.2c-0.9-0.1-1.8-0.2-2.6-0.4c-0.9-0.2-1.8-0.4-2.6-0.6c-1.7-0.4-3.5-1-5.1-1.7c-3.3-1.4-6.4-3.4-9.1-5.8 | ||
| c-2.8-2.4-5-5.4-6.4-8.8c-0.3-0.8,0.1-1.7,0.9-2c0.6-0.2,1.2-0.1,1.7,0.4L533.6,816.5z" style="fill:#10294C;"/> | ||
| <path d="M427.4,802.3c-1,1.4-2.1,2.6-3.5,3.6c-1.3,1-2.8,1.8-4.3,2.4c-3,1.3-6.3,1.9-9.6,1.9l-2.4-0.1 | ||
| c-0.8-0.1-1.6-0.2-2.4-0.4c-1.6-0.3-3.1-0.7-4.6-1.2c-1.5-0.6-2.9-1.2-4.3-1.9c-1.3-0.8-2.6-1.6-3.9-2.5c-0.6-0.4-0.7-1.2-0.3-1.8 | ||
| c0.3-0.4,0.8-0.6,1.3-0.5h0.1c1.5,0.3,2.9,0.6,4.3,1l2.1,0.4c0.7,0.1,1.4,0.3,2.1,0.4c1.4,0.1,2.7,0.5,4.1,0.5c0.7,0,1.3,0.2,2,0.2 | ||
| h2c2.6,0,5.2-0.4,7.8-1l1-0.3c0.3-0.1,0.6-0.2,0.9-0.3c0.6-0.2,1.2-0.5,1.9-0.7s1.2-0.5,1.8-0.8c0.3-0.2,0.6-0.3,0.9-0.5 | ||
| s0.6-0.3,0.8-0.4l0.4-0.2c0.8-0.3,1.7,0,2,0.8C427.7,801.3,427.6,801.9,427.4,802.3L427.4,802.3z" style="fill:#10294C;"/> | ||
| <path d="M552,707c23.5,5.2,25.7,22.1,25.7,61.8c0,23.3-2.2,37.8-7.9,44.7c-2.7,0.1-5.4-0.2-8-0.9 | ||
| c-19.3-4.9-25.7-19.3-25.7-61.8c0-23.4,1.5-37.7,7.1-44.7C546.2,706.1,549.1,706.3,552,707z" style="fill:#FFFFFF;"/> | ||
| <path d="M561.8,812.6c2.6,0.7,5.3,1,8,0.9c-4,4.8-9.8,6-17.8,4c-19.3-4.9-25.7-19.3-25.7-61.8 | ||
| c0-33.5,3.1-48.5,16.9-49.6c-5.6,6.9-7.1,21.3-7.1,44.7C536.1,793.4,542.4,807.8,561.8,812.6z" style="fill:#10294C;"/> | ||
| <path d="M408.7,683.5c23.5,5.2,25.7,22.1,25.7,61.8c0,23.3-2.1,37.8-7.9,44.7c-2.7,0.1-5.4-0.2-8-0.9 | ||
| c-19.4-4.9-25.7-19.3-25.7-61.8c0-23.4,1.5-37.7,7.1-44.7C402.9,682.5,405.8,682.8,408.7,683.5z" style="fill:#FFFFFF;"/> | ||
| <path d="M418.5,789.1c2.6,0.7,5.3,1,8,0.9c-4,4.8-9.8,6-17.8,4c-19.4-4.9-25.7-19.3-25.7-61.8 | ||
| c0-33.5,3.1-48.5,16.9-49.6c-5.6,6.9-7.1,21.3-7.1,44.7C392.9,769.8,399.2,784.2,418.5,789.1z" style="fill:#10294C;"/> | ||
| <path d="M415.1,739.6c0,5.3-0.8,7.1-3.4,6.5s-3.4-2.6-3.4-8.2c0-5.3,0.6-7.1,3.4-6.5 | ||
| C414.8,732.1,415.1,734.4,415.1,739.6z" style="fill:#10294C;"/> | ||
| <path d="M558.3,763.2c0,5.3-0.8,7.1-3.4,6.5s-3.4-2.6-3.4-8.2c0-5.3,0.6-7.1,3.4-6.5 | ||
| C558,755.7,558.3,757.9,558.3,763.2z" style="fill:#10294C;"/> | ||
| <path d="M597.1,485.9c0,3.7-3,6.7-6.7,6.7s-6.7-3-6.7-6.7s3-6.7,6.7-6.7l0,0C594.1,479.2,597.1,482.2,597.1,485.9z" style="fill:#10294C;"/> | ||
| <path d="M702.1,545.8c0,3.7-3,6.7-6.7,6.7s-6.7-3-6.7-6.7s3-6.7,6.7-6.7l0,0C699.1,539.2,702.1,542.2,702.1,545.8z" style="fill:#10294C;"/> | ||
| <path d="M496.1,602.8c0,3.7-3,6.7-6.7,6.7s-6.7-3-6.7-6.7s3-6.7,6.7-6.7l0,0C493.1,596.2,496.1,599.2,496.1,602.8z" style="fill:#10294C;"/> | ||
| <path d="M389.1,502.9c0,3.7-3,6.7-6.7,6.7s-6.7-3-6.7-6.7s3-6.7,6.7-6.7C386.1,496.2,389.1,499.2,389.1,502.9 | ||
| L389.1,502.9z" style="fill:#10294C;"/> | ||
| <path d="M580.6,470.9c0,2.2-1.8,3.9-3.9,3.9c-2.2,0-3.9-1.8-3.9-3.9c0-2.2,1.8-3.9,3.9-3.9l0,0 | ||
| C578.9,466.9,580.6,468.7,580.6,470.9z" style="fill:#10294C;"/> | ||
| <path d="M480.6,614.8c0,2.2-1.8,3.9-3.9,3.9c-2.2,0-3.9-1.8-3.9-3.9c0-2.2,1.8-3.9,3.9-3.9l0,0 | ||
| C478.9,610.9,480.6,612.7,480.6,614.8z" style="fill:#10294C;"/> | ||
| <path d="M618.6,588.8c0,2.2-1.8,3.9-3.9,3.9c-2.2,0-3.9-1.8-3.9-3.9c0-2.2,1.8-3.9,3.9-3.9l0,0 | ||
| C616.9,584.9,618.6,586.7,618.6,588.8z" style="fill:#10294C;"/> | ||
| <path d="M511.6,528.8c0,2.2-1.8,3.9-3.9,3.9c-2.2,0-3.9-1.8-3.9-3.9c0-2.2,1.8-3.9,3.9-3.9l0,0 | ||
| C509.9,524.9,511.6,526.7,511.6,528.8z" style="fill:#10294C;"/> | ||
| </g> | ||
| </svg> |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
+47
| const fs = require('fs') | ||
| const path = require('path') | ||
| const paths = [ | ||
| 'lib/core.js', | ||
| 'lib/hooks.js', | ||
| 'lib/index.js', | ||
| 'lib/service.js', | ||
| 'types', | ||
| 'package-lock.json', | ||
| 'CHANGELOG.md', | ||
| 'LICENSE.md', | ||
| 'README.md', | ||
| ] | ||
| ;(async function () { | ||
| const { execa } = await import('execa') | ||
| const sh = (...args) => execa(...args, { stdio: 'inherit', shell: true }) | ||
| await sh('rm -rf dist && mkdir -p dist') | ||
| for (const p of paths) { | ||
| await sh(`cp -R ${p} dist`) | ||
| } | ||
| const pkg = require('./package.json') | ||
| fs.writeFileSync( | ||
| path.join('dist', 'package.json'), | ||
| JSON.stringify( | ||
| { | ||
| name: pkg.name, | ||
| version: pkg.version, | ||
| description: pkg.description, | ||
| type: 'module', | ||
| exports: { | ||
| '.': './index.js', | ||
| './core': './core.js', | ||
| './service': './service.js', | ||
| './hooks': './hooks.js', | ||
| }, | ||
| ...pkg, | ||
| }, | ||
| null, | ||
| 2, | ||
| ), | ||
| ) | ||
| })() |
| body, html { | ||
| margin:0; padding: 0; | ||
| height: 100%; | ||
| } | ||
| body { | ||
| font-family: Helvetica Neue, Helvetica, Arial; | ||
| font-size: 14px; | ||
| color:#333; | ||
| } | ||
| .small { font-size: 12px; } | ||
| *, *:after, *:before { | ||
| -webkit-box-sizing:border-box; | ||
| -moz-box-sizing:border-box; | ||
| box-sizing:border-box; | ||
| } | ||
| h1 { font-size: 20px; margin: 0;} | ||
| h2 { font-size: 14px; } | ||
| pre { | ||
| font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; | ||
| margin: 0; | ||
| padding: 0; | ||
| -moz-tab-size: 2; | ||
| -o-tab-size: 2; | ||
| tab-size: 2; | ||
| } | ||
| a { color:#0074D9; text-decoration:none; } | ||
| a:hover { text-decoration:underline; } | ||
| .strong { font-weight: bold; } | ||
| .space-top1 { padding: 10px 0 0 0; } | ||
| .pad2y { padding: 20px 0; } | ||
| .pad1y { padding: 10px 0; } | ||
| .pad2x { padding: 0 20px; } | ||
| .pad2 { padding: 20px; } | ||
| .pad1 { padding: 10px; } | ||
| .space-left2 { padding-left:55px; } | ||
| .space-right2 { padding-right:20px; } | ||
| .center { text-align:center; } | ||
| .clearfix { display:block; } | ||
| .clearfix:after { | ||
| content:''; | ||
| display:block; | ||
| height:0; | ||
| clear:both; | ||
| visibility:hidden; | ||
| } | ||
| .fl { float: left; } | ||
| @media only screen and (max-width:640px) { | ||
| .col3 { width:100%; max-width:100%; } | ||
| .hide-mobile { display:none!important; } | ||
| } | ||
| .quiet { | ||
| color: #7f7f7f; | ||
| color: rgba(0,0,0,0.5); | ||
| } | ||
| .quiet a { opacity: 0.7; } | ||
| .fraction { | ||
| font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; | ||
| font-size: 10px; | ||
| color: #555; | ||
| background: #E8E8E8; | ||
| padding: 4px 5px; | ||
| border-radius: 3px; | ||
| vertical-align: middle; | ||
| } | ||
| div.path a:link, div.path a:visited { color: #333; } | ||
| table.coverage { | ||
| border-collapse: collapse; | ||
| margin: 10px 0 0 0; | ||
| padding: 0; | ||
| } | ||
| table.coverage td { | ||
| margin: 0; | ||
| padding: 0; | ||
| vertical-align: top; | ||
| } | ||
| table.coverage td.line-count { | ||
| text-align: right; | ||
| padding: 0 5px 0 20px; | ||
| } | ||
| table.coverage td.line-coverage { | ||
| text-align: right; | ||
| padding-right: 10px; | ||
| min-width:20px; | ||
| } | ||
| table.coverage td span.cline-any { | ||
| display: inline-block; | ||
| padding: 0 5px; | ||
| width: 100%; | ||
| } | ||
| .missing-if-branch { | ||
| display: inline-block; | ||
| margin-right: 5px; | ||
| border-radius: 3px; | ||
| position: relative; | ||
| padding: 0 4px; | ||
| background: #333; | ||
| color: yellow; | ||
| } | ||
| .skip-if-branch { | ||
| display: none; | ||
| margin-right: 10px; | ||
| position: relative; | ||
| padding: 0 4px; | ||
| background: #ccc; | ||
| color: white; | ||
| } | ||
| .missing-if-branch .typ, .skip-if-branch .typ { | ||
| color: inherit !important; | ||
| } | ||
| .coverage-summary { | ||
| border-collapse: collapse; | ||
| width: 100%; | ||
| } | ||
| .coverage-summary tr { border-bottom: 1px solid #bbb; } | ||
| .keyline-all { border: 1px solid #ddd; } | ||
| .coverage-summary td, .coverage-summary th { padding: 10px; } | ||
| .coverage-summary tbody { border: 1px solid #bbb; } | ||
| .coverage-summary td { border-right: 1px solid #bbb; } | ||
| .coverage-summary td:last-child { border-right: none; } | ||
| .coverage-summary th { | ||
| text-align: left; | ||
| font-weight: normal; | ||
| white-space: nowrap; | ||
| } | ||
| .coverage-summary th.file { border-right: none !important; } | ||
| .coverage-summary th.pct { } | ||
| .coverage-summary th.pic, | ||
| .coverage-summary th.abs, | ||
| .coverage-summary td.pct, | ||
| .coverage-summary td.abs { text-align: right; } | ||
| .coverage-summary td.file { white-space: nowrap; } | ||
| .coverage-summary td.pic { min-width: 120px !important; } | ||
| .coverage-summary tfoot td { } | ||
| .coverage-summary .sorter { | ||
| height: 10px; | ||
| width: 7px; | ||
| display: inline-block; | ||
| margin-left: 0.5em; | ||
| background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; | ||
| } | ||
| .coverage-summary .sorted .sorter { | ||
| background-position: 0 -20px; | ||
| } | ||
| .coverage-summary .sorted-desc .sorter { | ||
| background-position: 0 -10px; | ||
| } | ||
| .status-line { height: 10px; } | ||
| /* yellow */ | ||
| .cbranch-no { background: yellow !important; color: #111; } | ||
| /* dark red */ | ||
| .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } | ||
| .low .chart { border:1px solid #C21F39 } | ||
| .highlighted, | ||
| .highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ | ||
| background: #C21F39 !important; | ||
| } | ||
| /* medium red */ | ||
| .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } | ||
| /* light red */ | ||
| .low, .cline-no { background:#FCE1E5 } | ||
| /* light green */ | ||
| .high, .cline-yes { background:rgb(230,245,208) } | ||
| /* medium green */ | ||
| .cstat-yes { background:rgb(161,215,106) } | ||
| /* dark green */ | ||
| .status-line.high, .high .cover-fill { background:rgb(77,146,33) } | ||
| .high .chart { border:1px solid rgb(77,146,33) } | ||
| /* dark yellow (gold) */ | ||
| .status-line.medium, .medium .cover-fill { background: #f9cd0b; } | ||
| .medium .chart { border:1px solid #f9cd0b; } | ||
| /* light yellow */ | ||
| .medium { background: #fff4c2; } | ||
| .cstat-skip { background: #ddd; color: #111; } | ||
| .fstat-skip { background: #ddd; color: #111 !important; } | ||
| .cbranch-skip { background: #ddd !important; color: #111; } | ||
| span.cline-neutral { background: #eaeaea; } | ||
| .coverage-summary td.empty { | ||
| opacity: .5; | ||
| padding-top: 4px; | ||
| padding-bottom: 4px; | ||
| line-height: 1; | ||
| color: #888; | ||
| } | ||
| .cover-fill, .cover-empty { | ||
| display:inline-block; | ||
| height: 12px; | ||
| } | ||
| .chart { | ||
| line-height: 0; | ||
| } | ||
| .cover-empty { | ||
| background: white; | ||
| } | ||
| .cover-full { | ||
| border-right: none !important; | ||
| } | ||
| pre.prettyprint { | ||
| border: none !important; | ||
| padding: 0 !important; | ||
| margin: 0 !important; | ||
| } | ||
| .com { color: #999 !important; } | ||
| .ignore-none { color: #999; font-weight: normal; } | ||
| .wrapper { | ||
| min-height: 100%; | ||
| height: auto !important; | ||
| height: 100%; | ||
| margin: 0 auto -48px; | ||
| } | ||
| .footer, .push { | ||
| height: 48px; | ||
| } |
| /* eslint-disable */ | ||
| var jumpToCode = (function init() { | ||
| // Classes of code we would like to highlight in the file view | ||
| var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; | ||
| // Elements to highlight in the file listing view | ||
| var fileListingElements = ['td.pct.low']; | ||
| // We don't want to select elements that are direct descendants of another match | ||
| var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` | ||
| // Selecter that finds elements on the page to which we can jump | ||
| var selector = | ||
| fileListingElements.join(', ') + | ||
| ', ' + | ||
| notSelector + | ||
| missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` | ||
| // The NodeList of matching elements | ||
| var missingCoverageElements = document.querySelectorAll(selector); | ||
| var currentIndex; | ||
| function toggleClass(index) { | ||
| missingCoverageElements | ||
| .item(currentIndex) | ||
| .classList.remove('highlighted'); | ||
| missingCoverageElements.item(index).classList.add('highlighted'); | ||
| } | ||
| function makeCurrent(index) { | ||
| toggleClass(index); | ||
| currentIndex = index; | ||
| missingCoverageElements.item(index).scrollIntoView({ | ||
| behavior: 'smooth', | ||
| block: 'center', | ||
| inline: 'center' | ||
| }); | ||
| } | ||
| function goToPrevious() { | ||
| var nextIndex = 0; | ||
| if (typeof currentIndex !== 'number' || currentIndex === 0) { | ||
| nextIndex = missingCoverageElements.length - 1; | ||
| } else if (missingCoverageElements.length > 1) { | ||
| nextIndex = currentIndex - 1; | ||
| } | ||
| makeCurrent(nextIndex); | ||
| } | ||
| function goToNext() { | ||
| var nextIndex = 0; | ||
| if ( | ||
| typeof currentIndex === 'number' && | ||
| currentIndex < missingCoverageElements.length - 1 | ||
| ) { | ||
| nextIndex = currentIndex + 1; | ||
| } | ||
| makeCurrent(nextIndex); | ||
| } | ||
| return function jump(event) { | ||
| switch (event.which) { | ||
| case 78: // n | ||
| case 74: // j | ||
| goToNext(); | ||
| break; | ||
| case 66: // b | ||
| case 75: // k | ||
| case 80: // p | ||
| goToPrevious(); | ||
| break; | ||
| } | ||
| }; | ||
| })(); | ||
| window.addEventListener('keydown', jumpToCode); |
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <title>Code coverage report for core.js</title> | ||
| <meta charset="utf-8" /> | ||
| <link rel="stylesheet" href="prettify.css" /> | ||
| <link rel="stylesheet" href="base.css" /> | ||
| <link rel="shortcut icon" type="image/x-icon" href="favicon.png" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
| <style type='text/css'> | ||
| .coverage-summary .sorter { | ||
| background-image: url(sort-arrow-sprite.png); | ||
| } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <div class='wrapper'> | ||
| <div class='pad1'> | ||
| <h1><a href="index.html">All files</a> core.js</h1> | ||
| <div class='clearfix'> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">98.41% </span> | ||
| <span class="quiet">Statements</span> | ||
| <span class='fraction'>372/378</span> | ||
| </div> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">98.18% </span> | ||
| <span class="quiet">Branches</span> | ||
| <span class='fraction'>108/110</span> | ||
| </div> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">100% </span> | ||
| <span class="quiet">Functions</span> | ||
| <span class='fraction'>24/24</span> | ||
| </div> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">98.41% </span> | ||
| <span class="quiet">Lines</span> | ||
| <span class='fraction'>372/378</span> | ||
| </div> | ||
| </div> | ||
| <p class="quiet"> | ||
| Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. | ||
| </p> | ||
| </div> | ||
| <div class='status-line high'></div> | ||
| <pre><table class="coverage"> | ||
| <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> | ||
| <a name='L2'></a><a href='#L2'>2</a> | ||
| <a name='L3'></a><a href='#L3'>3</a> | ||
| <a name='L4'></a><a href='#L4'>4</a> | ||
| <a name='L5'></a><a href='#L5'>5</a> | ||
| <a name='L6'></a><a href='#L6'>6</a> | ||
| <a name='L7'></a><a href='#L7'>7</a> | ||
| <a name='L8'></a><a href='#L8'>8</a> | ||
| <a name='L9'></a><a href='#L9'>9</a> | ||
| <a name='L10'></a><a href='#L10'>10</a> | ||
| <a name='L11'></a><a href='#L11'>11</a> | ||
| <a name='L12'></a><a href='#L12'>12</a> | ||
| <a name='L13'></a><a href='#L13'>13</a> | ||
| <a name='L14'></a><a href='#L14'>14</a> | ||
| <a name='L15'></a><a href='#L15'>15</a> | ||
| <a name='L16'></a><a href='#L16'>16</a> | ||
| <a name='L17'></a><a href='#L17'>17</a> | ||
| <a name='L18'></a><a href='#L18'>18</a> | ||
| <a name='L19'></a><a href='#L19'>19</a> | ||
| <a name='L20'></a><a href='#L20'>20</a> | ||
| <a name='L21'></a><a href='#L21'>21</a> | ||
| <a name='L22'></a><a href='#L22'>22</a> | ||
| <a name='L23'></a><a href='#L23'>23</a> | ||
| <a name='L24'></a><a href='#L24'>24</a> | ||
| <a name='L25'></a><a href='#L25'>25</a> | ||
| <a name='L26'></a><a href='#L26'>26</a> | ||
| <a name='L27'></a><a href='#L27'>27</a> | ||
| <a name='L28'></a><a href='#L28'>28</a> | ||
| <a name='L29'></a><a href='#L29'>29</a> | ||
| <a name='L30'></a><a href='#L30'>30</a> | ||
| <a name='L31'></a><a href='#L31'>31</a> | ||
| <a name='L32'></a><a href='#L32'>32</a> | ||
| <a name='L33'></a><a href='#L33'>33</a> | ||
| <a name='L34'></a><a href='#L34'>34</a> | ||
| <a name='L35'></a><a href='#L35'>35</a> | ||
| <a name='L36'></a><a href='#L36'>36</a> | ||
| <a name='L37'></a><a href='#L37'>37</a> | ||
| <a name='L38'></a><a href='#L38'>38</a> | ||
| <a name='L39'></a><a href='#L39'>39</a> | ||
| <a name='L40'></a><a href='#L40'>40</a> | ||
| <a name='L41'></a><a href='#L41'>41</a> | ||
| <a name='L42'></a><a href='#L42'>42</a> | ||
| <a name='L43'></a><a href='#L43'>43</a> | ||
| <a name='L44'></a><a href='#L44'>44</a> | ||
| <a name='L45'></a><a href='#L45'>45</a> | ||
| <a name='L46'></a><a href='#L46'>46</a> | ||
| <a name='L47'></a><a href='#L47'>47</a> | ||
| <a name='L48'></a><a href='#L48'>48</a> | ||
| <a name='L49'></a><a href='#L49'>49</a> | ||
| <a name='L50'></a><a href='#L50'>50</a> | ||
| <a name='L51'></a><a href='#L51'>51</a> | ||
| <a name='L52'></a><a href='#L52'>52</a> | ||
| <a name='L53'></a><a href='#L53'>53</a> | ||
| <a name='L54'></a><a href='#L54'>54</a> | ||
| <a name='L55'></a><a href='#L55'>55</a> | ||
| <a name='L56'></a><a href='#L56'>56</a> | ||
| <a name='L57'></a><a href='#L57'>57</a> | ||
| <a name='L58'></a><a href='#L58'>58</a> | ||
| <a name='L59'></a><a href='#L59'>59</a> | ||
| <a name='L60'></a><a href='#L60'>60</a> | ||
| <a name='L61'></a><a href='#L61'>61</a> | ||
| <a name='L62'></a><a href='#L62'>62</a> | ||
| <a name='L63'></a><a href='#L63'>63</a> | ||
| <a name='L64'></a><a href='#L64'>64</a> | ||
| <a name='L65'></a><a href='#L65'>65</a> | ||
| <a name='L66'></a><a href='#L66'>66</a> | ||
| <a name='L67'></a><a href='#L67'>67</a> | ||
| <a name='L68'></a><a href='#L68'>68</a> | ||
| <a name='L69'></a><a href='#L69'>69</a> | ||
| <a name='L70'></a><a href='#L70'>70</a> | ||
| <a name='L71'></a><a href='#L71'>71</a> | ||
| <a name='L72'></a><a href='#L72'>72</a> | ||
| <a name='L73'></a><a href='#L73'>73</a> | ||
| <a name='L74'></a><a href='#L74'>74</a> | ||
| <a name='L75'></a><a href='#L75'>75</a> | ||
| <a name='L76'></a><a href='#L76'>76</a> | ||
| <a name='L77'></a><a href='#L77'>77</a> | ||
| <a name='L78'></a><a href='#L78'>78</a> | ||
| <a name='L79'></a><a href='#L79'>79</a> | ||
| <a name='L80'></a><a href='#L80'>80</a> | ||
| <a name='L81'></a><a href='#L81'>81</a> | ||
| <a name='L82'></a><a href='#L82'>82</a> | ||
| <a name='L83'></a><a href='#L83'>83</a> | ||
| <a name='L84'></a><a href='#L84'>84</a> | ||
| <a name='L85'></a><a href='#L85'>85</a> | ||
| <a name='L86'></a><a href='#L86'>86</a> | ||
| <a name='L87'></a><a href='#L87'>87</a> | ||
| <a name='L88'></a><a href='#L88'>88</a> | ||
| <a name='L89'></a><a href='#L89'>89</a> | ||
| <a name='L90'></a><a href='#L90'>90</a> | ||
| <a name='L91'></a><a href='#L91'>91</a> | ||
| <a name='L92'></a><a href='#L92'>92</a> | ||
| <a name='L93'></a><a href='#L93'>93</a> | ||
| <a name='L94'></a><a href='#L94'>94</a> | ||
| <a name='L95'></a><a href='#L95'>95</a> | ||
| <a name='L96'></a><a href='#L96'>96</a> | ||
| <a name='L97'></a><a href='#L97'>97</a> | ||
| <a name='L98'></a><a href='#L98'>98</a> | ||
| <a name='L99'></a><a href='#L99'>99</a> | ||
| <a name='L100'></a><a href='#L100'>100</a> | ||
| <a name='L101'></a><a href='#L101'>101</a> | ||
| <a name='L102'></a><a href='#L102'>102</a> | ||
| <a name='L103'></a><a href='#L103'>103</a> | ||
| <a name='L104'></a><a href='#L104'>104</a> | ||
| <a name='L105'></a><a href='#L105'>105</a> | ||
| <a name='L106'></a><a href='#L106'>106</a> | ||
| <a name='L107'></a><a href='#L107'>107</a> | ||
| <a name='L108'></a><a href='#L108'>108</a> | ||
| <a name='L109'></a><a href='#L109'>109</a> | ||
| <a name='L110'></a><a href='#L110'>110</a> | ||
| <a name='L111'></a><a href='#L111'>111</a> | ||
| <a name='L112'></a><a href='#L112'>112</a> | ||
| <a name='L113'></a><a href='#L113'>113</a> | ||
| <a name='L114'></a><a href='#L114'>114</a> | ||
| <a name='L115'></a><a href='#L115'>115</a> | ||
| <a name='L116'></a><a href='#L116'>116</a> | ||
| <a name='L117'></a><a href='#L117'>117</a> | ||
| <a name='L118'></a><a href='#L118'>118</a> | ||
| <a name='L119'></a><a href='#L119'>119</a> | ||
| <a name='L120'></a><a href='#L120'>120</a> | ||
| <a name='L121'></a><a href='#L121'>121</a> | ||
| <a name='L122'></a><a href='#L122'>122</a> | ||
| <a name='L123'></a><a href='#L123'>123</a> | ||
| <a name='L124'></a><a href='#L124'>124</a> | ||
| <a name='L125'></a><a href='#L125'>125</a> | ||
| <a name='L126'></a><a href='#L126'>126</a> | ||
| <a name='L127'></a><a href='#L127'>127</a> | ||
| <a name='L128'></a><a href='#L128'>128</a> | ||
| <a name='L129'></a><a href='#L129'>129</a> | ||
| <a name='L130'></a><a href='#L130'>130</a> | ||
| <a name='L131'></a><a href='#L131'>131</a> | ||
| <a name='L132'></a><a href='#L132'>132</a> | ||
| <a name='L133'></a><a href='#L133'>133</a> | ||
| <a name='L134'></a><a href='#L134'>134</a> | ||
| <a name='L135'></a><a href='#L135'>135</a> | ||
| <a name='L136'></a><a href='#L136'>136</a> | ||
| <a name='L137'></a><a href='#L137'>137</a> | ||
| <a name='L138'></a><a href='#L138'>138</a> | ||
| <a name='L139'></a><a href='#L139'>139</a> | ||
| <a name='L140'></a><a href='#L140'>140</a> | ||
| <a name='L141'></a><a href='#L141'>141</a> | ||
| <a name='L142'></a><a href='#L142'>142</a> | ||
| <a name='L143'></a><a href='#L143'>143</a> | ||
| <a name='L144'></a><a href='#L144'>144</a> | ||
| <a name='L145'></a><a href='#L145'>145</a> | ||
| <a name='L146'></a><a href='#L146'>146</a> | ||
| <a name='L147'></a><a href='#L147'>147</a> | ||
| <a name='L148'></a><a href='#L148'>148</a> | ||
| <a name='L149'></a><a href='#L149'>149</a> | ||
| <a name='L150'></a><a href='#L150'>150</a> | ||
| <a name='L151'></a><a href='#L151'>151</a> | ||
| <a name='L152'></a><a href='#L152'>152</a> | ||
| <a name='L153'></a><a href='#L153'>153</a> | ||
| <a name='L154'></a><a href='#L154'>154</a> | ||
| <a name='L155'></a><a href='#L155'>155</a> | ||
| <a name='L156'></a><a href='#L156'>156</a> | ||
| <a name='L157'></a><a href='#L157'>157</a> | ||
| <a name='L158'></a><a href='#L158'>158</a> | ||
| <a name='L159'></a><a href='#L159'>159</a> | ||
| <a name='L160'></a><a href='#L160'>160</a> | ||
| <a name='L161'></a><a href='#L161'>161</a> | ||
| <a name='L162'></a><a href='#L162'>162</a> | ||
| <a name='L163'></a><a href='#L163'>163</a> | ||
| <a name='L164'></a><a href='#L164'>164</a> | ||
| <a name='L165'></a><a href='#L165'>165</a> | ||
| <a name='L166'></a><a href='#L166'>166</a> | ||
| <a name='L167'></a><a href='#L167'>167</a> | ||
| <a name='L168'></a><a href='#L168'>168</a> | ||
| <a name='L169'></a><a href='#L169'>169</a> | ||
| <a name='L170'></a><a href='#L170'>170</a> | ||
| <a name='L171'></a><a href='#L171'>171</a> | ||
| <a name='L172'></a><a href='#L172'>172</a> | ||
| <a name='L173'></a><a href='#L173'>173</a> | ||
| <a name='L174'></a><a href='#L174'>174</a> | ||
| <a name='L175'></a><a href='#L175'>175</a> | ||
| <a name='L176'></a><a href='#L176'>176</a> | ||
| <a name='L177'></a><a href='#L177'>177</a> | ||
| <a name='L178'></a><a href='#L178'>178</a> | ||
| <a name='L179'></a><a href='#L179'>179</a> | ||
| <a name='L180'></a><a href='#L180'>180</a> | ||
| <a name='L181'></a><a href='#L181'>181</a> | ||
| <a name='L182'></a><a href='#L182'>182</a> | ||
| <a name='L183'></a><a href='#L183'>183</a> | ||
| <a name='L184'></a><a href='#L184'>184</a> | ||
| <a name='L185'></a><a href='#L185'>185</a> | ||
| <a name='L186'></a><a href='#L186'>186</a> | ||
| <a name='L187'></a><a href='#L187'>187</a> | ||
| <a name='L188'></a><a href='#L188'>188</a> | ||
| <a name='L189'></a><a href='#L189'>189</a> | ||
| <a name='L190'></a><a href='#L190'>190</a> | ||
| <a name='L191'></a><a href='#L191'>191</a> | ||
| <a name='L192'></a><a href='#L192'>192</a> | ||
| <a name='L193'></a><a href='#L193'>193</a> | ||
| <a name='L194'></a><a href='#L194'>194</a> | ||
| <a name='L195'></a><a href='#L195'>195</a> | ||
| <a name='L196'></a><a href='#L196'>196</a> | ||
| <a name='L197'></a><a href='#L197'>197</a> | ||
| <a name='L198'></a><a href='#L198'>198</a> | ||
| <a name='L199'></a><a href='#L199'>199</a> | ||
| <a name='L200'></a><a href='#L200'>200</a> | ||
| <a name='L201'></a><a href='#L201'>201</a> | ||
| <a name='L202'></a><a href='#L202'>202</a> | ||
| <a name='L203'></a><a href='#L203'>203</a> | ||
| <a name='L204'></a><a href='#L204'>204</a> | ||
| <a name='L205'></a><a href='#L205'>205</a> | ||
| <a name='L206'></a><a href='#L206'>206</a> | ||
| <a name='L207'></a><a href='#L207'>207</a> | ||
| <a name='L208'></a><a href='#L208'>208</a> | ||
| <a name='L209'></a><a href='#L209'>209</a> | ||
| <a name='L210'></a><a href='#L210'>210</a> | ||
| <a name='L211'></a><a href='#L211'>211</a> | ||
| <a name='L212'></a><a href='#L212'>212</a> | ||
| <a name='L213'></a><a href='#L213'>213</a> | ||
| <a name='L214'></a><a href='#L214'>214</a> | ||
| <a name='L215'></a><a href='#L215'>215</a> | ||
| <a name='L216'></a><a href='#L216'>216</a> | ||
| <a name='L217'></a><a href='#L217'>217</a> | ||
| <a name='L218'></a><a href='#L218'>218</a> | ||
| <a name='L219'></a><a href='#L219'>219</a> | ||
| <a name='L220'></a><a href='#L220'>220</a> | ||
| <a name='L221'></a><a href='#L221'>221</a> | ||
| <a name='L222'></a><a href='#L222'>222</a> | ||
| <a name='L223'></a><a href='#L223'>223</a> | ||
| <a name='L224'></a><a href='#L224'>224</a> | ||
| <a name='L225'></a><a href='#L225'>225</a> | ||
| <a name='L226'></a><a href='#L226'>226</a> | ||
| <a name='L227'></a><a href='#L227'>227</a> | ||
| <a name='L228'></a><a href='#L228'>228</a> | ||
| <a name='L229'></a><a href='#L229'>229</a> | ||
| <a name='L230'></a><a href='#L230'>230</a> | ||
| <a name='L231'></a><a href='#L231'>231</a> | ||
| <a name='L232'></a><a href='#L232'>232</a> | ||
| <a name='L233'></a><a href='#L233'>233</a> | ||
| <a name='L234'></a><a href='#L234'>234</a> | ||
| <a name='L235'></a><a href='#L235'>235</a> | ||
| <a name='L236'></a><a href='#L236'>236</a> | ||
| <a name='L237'></a><a href='#L237'>237</a> | ||
| <a name='L238'></a><a href='#L238'>238</a> | ||
| <a name='L239'></a><a href='#L239'>239</a> | ||
| <a name='L240'></a><a href='#L240'>240</a> | ||
| <a name='L241'></a><a href='#L241'>241</a> | ||
| <a name='L242'></a><a href='#L242'>242</a> | ||
| <a name='L243'></a><a href='#L243'>243</a> | ||
| <a name='L244'></a><a href='#L244'>244</a> | ||
| <a name='L245'></a><a href='#L245'>245</a> | ||
| <a name='L246'></a><a href='#L246'>246</a> | ||
| <a name='L247'></a><a href='#L247'>247</a> | ||
| <a name='L248'></a><a href='#L248'>248</a> | ||
| <a name='L249'></a><a href='#L249'>249</a> | ||
| <a name='L250'></a><a href='#L250'>250</a> | ||
| <a name='L251'></a><a href='#L251'>251</a> | ||
| <a name='L252'></a><a href='#L252'>252</a> | ||
| <a name='L253'></a><a href='#L253'>253</a> | ||
| <a name='L254'></a><a href='#L254'>254</a> | ||
| <a name='L255'></a><a href='#L255'>255</a> | ||
| <a name='L256'></a><a href='#L256'>256</a> | ||
| <a name='L257'></a><a href='#L257'>257</a> | ||
| <a name='L258'></a><a href='#L258'>258</a> | ||
| <a name='L259'></a><a href='#L259'>259</a> | ||
| <a name='L260'></a><a href='#L260'>260</a> | ||
| <a name='L261'></a><a href='#L261'>261</a> | ||
| <a name='L262'></a><a href='#L262'>262</a> | ||
| <a name='L263'></a><a href='#L263'>263</a> | ||
| <a name='L264'></a><a href='#L264'>264</a> | ||
| <a name='L265'></a><a href='#L265'>265</a> | ||
| <a name='L266'></a><a href='#L266'>266</a> | ||
| <a name='L267'></a><a href='#L267'>267</a> | ||
| <a name='L268'></a><a href='#L268'>268</a> | ||
| <a name='L269'></a><a href='#L269'>269</a> | ||
| <a name='L270'></a><a href='#L270'>270</a> | ||
| <a name='L271'></a><a href='#L271'>271</a> | ||
| <a name='L272'></a><a href='#L272'>272</a> | ||
| <a name='L273'></a><a href='#L273'>273</a> | ||
| <a name='L274'></a><a href='#L274'>274</a> | ||
| <a name='L275'></a><a href='#L275'>275</a> | ||
| <a name='L276'></a><a href='#L276'>276</a> | ||
| <a name='L277'></a><a href='#L277'>277</a> | ||
| <a name='L278'></a><a href='#L278'>278</a> | ||
| <a name='L279'></a><a href='#L279'>279</a> | ||
| <a name='L280'></a><a href='#L280'>280</a> | ||
| <a name='L281'></a><a href='#L281'>281</a> | ||
| <a name='L282'></a><a href='#L282'>282</a> | ||
| <a name='L283'></a><a href='#L283'>283</a> | ||
| <a name='L284'></a><a href='#L284'>284</a> | ||
| <a name='L285'></a><a href='#L285'>285</a> | ||
| <a name='L286'></a><a href='#L286'>286</a> | ||
| <a name='L287'></a><a href='#L287'>287</a> | ||
| <a name='L288'></a><a href='#L288'>288</a> | ||
| <a name='L289'></a><a href='#L289'>289</a> | ||
| <a name='L290'></a><a href='#L290'>290</a> | ||
| <a name='L291'></a><a href='#L291'>291</a> | ||
| <a name='L292'></a><a href='#L292'>292</a> | ||
| <a name='L293'></a><a href='#L293'>293</a> | ||
| <a name='L294'></a><a href='#L294'>294</a> | ||
| <a name='L295'></a><a href='#L295'>295</a> | ||
| <a name='L296'></a><a href='#L296'>296</a> | ||
| <a name='L297'></a><a href='#L297'>297</a> | ||
| <a name='L298'></a><a href='#L298'>298</a> | ||
| <a name='L299'></a><a href='#L299'>299</a> | ||
| <a name='L300'></a><a href='#L300'>300</a> | ||
| <a name='L301'></a><a href='#L301'>301</a> | ||
| <a name='L302'></a><a href='#L302'>302</a> | ||
| <a name='L303'></a><a href='#L303'>303</a> | ||
| <a name='L304'></a><a href='#L304'>304</a> | ||
| <a name='L305'></a><a href='#L305'>305</a> | ||
| <a name='L306'></a><a href='#L306'>306</a> | ||
| <a name='L307'></a><a href='#L307'>307</a> | ||
| <a name='L308'></a><a href='#L308'>308</a> | ||
| <a name='L309'></a><a href='#L309'>309</a> | ||
| <a name='L310'></a><a href='#L310'>310</a> | ||
| <a name='L311'></a><a href='#L311'>311</a> | ||
| <a name='L312'></a><a href='#L312'>312</a> | ||
| <a name='L313'></a><a href='#L313'>313</a> | ||
| <a name='L314'></a><a href='#L314'>314</a> | ||
| <a name='L315'></a><a href='#L315'>315</a> | ||
| <a name='L316'></a><a href='#L316'>316</a> | ||
| <a name='L317'></a><a href='#L317'>317</a> | ||
| <a name='L318'></a><a href='#L318'>318</a> | ||
| <a name='L319'></a><a href='#L319'>319</a> | ||
| <a name='L320'></a><a href='#L320'>320</a> | ||
| <a name='L321'></a><a href='#L321'>321</a> | ||
| <a name='L322'></a><a href='#L322'>322</a> | ||
| <a name='L323'></a><a href='#L323'>323</a> | ||
| <a name='L324'></a><a href='#L324'>324</a> | ||
| <a name='L325'></a><a href='#L325'>325</a> | ||
| <a name='L326'></a><a href='#L326'>326</a> | ||
| <a name='L327'></a><a href='#L327'>327</a> | ||
| <a name='L328'></a><a href='#L328'>328</a> | ||
| <a name='L329'></a><a href='#L329'>329</a> | ||
| <a name='L330'></a><a href='#L330'>330</a> | ||
| <a name='L331'></a><a href='#L331'>331</a> | ||
| <a name='L332'></a><a href='#L332'>332</a> | ||
| <a name='L333'></a><a href='#L333'>333</a> | ||
| <a name='L334'></a><a href='#L334'>334</a> | ||
| <a name='L335'></a><a href='#L335'>335</a> | ||
| <a name='L336'></a><a href='#L336'>336</a> | ||
| <a name='L337'></a><a href='#L337'>337</a> | ||
| <a name='L338'></a><a href='#L338'>338</a> | ||
| <a name='L339'></a><a href='#L339'>339</a> | ||
| <a name='L340'></a><a href='#L340'>340</a> | ||
| <a name='L341'></a><a href='#L341'>341</a> | ||
| <a name='L342'></a><a href='#L342'>342</a> | ||
| <a name='L343'></a><a href='#L343'>343</a> | ||
| <a name='L344'></a><a href='#L344'>344</a> | ||
| <a name='L345'></a><a href='#L345'>345</a> | ||
| <a name='L346'></a><a href='#L346'>346</a> | ||
| <a name='L347'></a><a href='#L347'>347</a> | ||
| <a name='L348'></a><a href='#L348'>348</a> | ||
| <a name='L349'></a><a href='#L349'>349</a> | ||
| <a name='L350'></a><a href='#L350'>350</a> | ||
| <a name='L351'></a><a href='#L351'>351</a> | ||
| <a name='L352'></a><a href='#L352'>352</a> | ||
| <a name='L353'></a><a href='#L353'>353</a> | ||
| <a name='L354'></a><a href='#L354'>354</a> | ||
| <a name='L355'></a><a href='#L355'>355</a> | ||
| <a name='L356'></a><a href='#L356'>356</a> | ||
| <a name='L357'></a><a href='#L357'>357</a> | ||
| <a name='L358'></a><a href='#L358'>358</a> | ||
| <a name='L359'></a><a href='#L359'>359</a> | ||
| <a name='L360'></a><a href='#L360'>360</a> | ||
| <a name='L361'></a><a href='#L361'>361</a> | ||
| <a name='L362'></a><a href='#L362'>362</a> | ||
| <a name='L363'></a><a href='#L363'>363</a> | ||
| <a name='L364'></a><a href='#L364'>364</a> | ||
| <a name='L365'></a><a href='#L365'>365</a> | ||
| <a name='L366'></a><a href='#L366'>366</a> | ||
| <a name='L367'></a><a href='#L367'>367</a> | ||
| <a name='L368'></a><a href='#L368'>368</a> | ||
| <a name='L369'></a><a href='#L369'>369</a> | ||
| <a name='L370'></a><a href='#L370'>370</a> | ||
| <a name='L371'></a><a href='#L371'>371</a> | ||
| <a name='L372'></a><a href='#L372'>372</a> | ||
| <a name='L373'></a><a href='#L373'>373</a> | ||
| <a name='L374'></a><a href='#L374'>374</a> | ||
| <a name='L375'></a><a href='#L375'>375</a> | ||
| <a name='L376'></a><a href='#L376'>376</a> | ||
| <a name='L377'></a><a href='#L377'>377</a> | ||
| <a name='L378'></a><a href='#L378'>378</a> | ||
| <a name='L379'></a><a href='#L379'>379</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">80x</span> | ||
| <span class="cline-any cline-yes">80x</span> | ||
| <span class="cline-any cline-yes">80x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">80x</span> | ||
| <span class="cline-any cline-yes">80x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">28x</span> | ||
| <span class="cline-any cline-yes">28x</span> | ||
| <span class="cline-any cline-yes">28x</span> | ||
| <span class="cline-any cline-yes">27x</span> | ||
| <span class="cline-any cline-yes">27x</span> | ||
| <span class="cline-any cline-yes">46x</span> | ||
| <span class="cline-any cline-yes">46x</span> | ||
| <span class="cline-any cline-yes">27x</span> | ||
| <span class="cline-any cline-yes">27x</span> | ||
| <span class="cline-any cline-yes">27x</span> | ||
| <span class="cline-any cline-yes">27x</span> | ||
| <span class="cline-any cline-yes">27x</span> | ||
| <span class="cline-any cline-yes">27x</span> | ||
| <span class="cline-any cline-yes">27x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">45x</span> | ||
| <span class="cline-any cline-yes">6x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">6x</span> | ||
| <span class="cline-any cline-yes">45x</span> | ||
| <span class="cline-any cline-yes">28x</span> | ||
| <span class="cline-any cline-yes">28x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">28x</span> | ||
| <span class="cline-any cline-yes">27x</span> | ||
| <span class="cline-any cline-yes">43x</span> | ||
| <span class="cline-any cline-yes">23x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">46x</span> | ||
| <span class="cline-any cline-yes">46x</span> | ||
| <span class="cline-any cline-yes">46x</span> | ||
| <span class="cline-any cline-yes">46x</span> | ||
| <span class="cline-any cline-yes">46x</span> | ||
| <span class="cline-any cline-yes">46x</span> | ||
| <span class="cline-any cline-yes">46x</span> | ||
| <span class="cline-any cline-yes">50x</span> | ||
| <span class="cline-any cline-yes">50x</span> | ||
| <span class="cline-any cline-yes">28x</span> | ||
| <span class="cline-any cline-yes">28x</span> | ||
| <span class="cline-any cline-yes">46x</span> | ||
| <span class="cline-any cline-yes">6x</span> | ||
| <span class="cline-any cline-yes">22x</span> | ||
| <span class="cline-any cline-yes">11x</span> | ||
| <span class="cline-any cline-yes">16x</span> | ||
| <span class="cline-any cline-yes">4x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">50x</span> | ||
| <span class="cline-any cline-yes">45x</span> | ||
| <span class="cline-any cline-yes">45x</span> | ||
| <span class="cline-any cline-yes">45x</span> | ||
| <span class="cline-any cline-yes">45x</span> | ||
| <span class="cline-any cline-yes">45x</span> | ||
| <span class="cline-any cline-yes">45x</span> | ||
| <span class="cline-any cline-yes">45x</span> | ||
| <span class="cline-any cline-yes">45x</span> | ||
| <span class="cline-any cline-yes">45x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">11x</span> | ||
| <span class="cline-any cline-yes">11x</span> | ||
| <span class="cline-any cline-yes">11x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">4x</span> | ||
| <span class="cline-any cline-yes">4x</span> | ||
| <span class="cline-any cline-yes">4x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">26x</span> | ||
| <span class="cline-any cline-yes">26x</span> | ||
| <span class="cline-any cline-yes">26x</span> | ||
| <span class="cline-any cline-yes">26x</span> | ||
| <span class="cline-any cline-yes">26x</span> | ||
| <span class="cline-any cline-yes">26x</span> | ||
| <span class="cline-any cline-yes">26x</span> | ||
| <span class="cline-any cline-yes">26x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">56x</span> | ||
| <span class="cline-any cline-yes">56x</span> | ||
| <span class="cline-any cline-yes">56x</span> | ||
| <span class="cline-any cline-yes">56x</span> | ||
| <span class="cline-any cline-yes">23x</span> | ||
| <span class="cline-any cline-yes">23x</span> | ||
| <span class="cline-any cline-yes">20x</span> | ||
| <span class="cline-any cline-yes">20x</span> | ||
| <span class="cline-any cline-yes">20x</span> | ||
| <span class="cline-any cline-yes">20x</span> | ||
| <span class="cline-any cline-yes">23x</span> | ||
| <span class="cline-any cline-yes">36x</span> | ||
| <span class="cline-any cline-yes">56x</span> | ||
| <span class="cline-any cline-yes">56x</span> | ||
| <span class="cline-any cline-yes">56x</span> | ||
| <span class="cline-any cline-yes">56x</span> | ||
| <span class="cline-any cline-yes">56x</span> | ||
| <span class="cline-any cline-yes">32x</span> | ||
| <span class="cline-any cline-yes">31x</span> | ||
| <span class="cline-any cline-yes">31x</span> | ||
| <span class="cline-any cline-yes">32x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">55x</span> | ||
| <span class="cline-any cline-yes">55x</span> | ||
| <span class="cline-any cline-yes">55x</span> | ||
| <span class="cline-any cline-yes">55x</span> | ||
| <span class="cline-any cline-yes">55x</span> | ||
| <span class="cline-any cline-yes">55x</span> | ||
| <span class="cline-any cline-yes">55x</span> | ||
| <span class="cline-any cline-yes">55x</span> | ||
| <span class="cline-any cline-yes">30x</span> | ||
| <span class="cline-any cline-yes">4x</span> | ||
| <span class="cline-any cline-yes">4x</span> | ||
| <span class="cline-any cline-yes">30x</span> | ||
| <span class="cline-any cline-yes">55x</span> | ||
| <span class="cline-any cline-yes">55x</span> | ||
| <span class="cline-any cline-yes">55x</span> | ||
| <span class="cline-any cline-yes">55x</span> | ||
| <span class="cline-any cline-yes">55x</span> | ||
| <span class="cline-any cline-yes">55x</span> | ||
| <span class="cline-any cline-yes">50x</span> | ||
| <span class="cline-any cline-yes">13x</span> | ||
| <span class="cline-any cline-yes">13x</span> | ||
| <span class="cline-any cline-yes">50x</span> | ||
| <span class="cline-any cline-yes">55x</span> | ||
| <span class="cline-any cline-yes">55x</span> | ||
| <span class="cline-any cline-yes">8x</span> | ||
| <span class="cline-any cline-yes">4x</span> | ||
| <span class="cline-any cline-yes">4x</span> | ||
| <span class="cline-any cline-yes">8x</span> | ||
| <span class="cline-any cline-yes">51x</span> | ||
| <span class="cline-any cline-yes">55x</span> | ||
| <span class="cline-any cline-yes">17x</span> | ||
| <span class="cline-any cline-yes">17x</span> | ||
| <span class="cline-any cline-yes">51x</span> | ||
| <span class="cline-any cline-yes">55x</span> | ||
| <span class="cline-any cline-yes">46x</span> | ||
| <span class="cline-any cline-yes">13x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">13x</span> | ||
| <span class="cline-any cline-yes">13x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">13x</span> | ||
| <span class="cline-any cline-yes">46x</span> | ||
| <span class="cline-any cline-yes">51x</span> | ||
| <span class="cline-any cline-yes">51x</span> | ||
| <span class="cline-any cline-yes">51x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">40x</span> | ||
| <span class="cline-any cline-yes">40x</span> | ||
| <span class="cline-any cline-yes">40x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">72x</span> | ||
| <span class="cline-any cline-yes">72x</span> | ||
| <span class="cline-any cline-yes">34x</span> | ||
| <span class="cline-any cline-yes">34x</span> | ||
| <span class="cline-any cline-yes">32x</span> | ||
| <span class="cline-any cline-yes">32x</span> | ||
| <span class="cline-any cline-yes">34x</span> | ||
| <span class="cline-any cline-yes">72x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">21x</span> | ||
| <span class="cline-any cline-yes">21x</span> | ||
| <span class="cline-any cline-yes">21x</span> | ||
| <span class="cline-any cline-yes">21x</span> | ||
| <span class="cline-any cline-yes">21x</span> | ||
| <span class="cline-any cline-yes">11x</span> | ||
| <span class="cline-any cline-yes">11x</span> | ||
| <span class="cline-any cline-yes">10x</span> | ||
| <span class="cline-any cline-yes">19x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">7x</span> | ||
| <span class="cline-any cline-yes">7x</span> | ||
| <span class="cline-any cline-yes">7x</span> | ||
| <span class="cline-any cline-yes">21x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">69x</span> | ||
| <span class="cline-any cline-yes">69x</span> | ||
| <span class="cline-any cline-yes">69x</span> | ||
| <span class="cline-any cline-yes">69x</span> | ||
| <span class="cline-any cline-yes">283x</span> | ||
| <span class="cline-any cline-yes">283x</span> | ||
| <span class="cline-any cline-yes">69x</span> | ||
| <span class="cline-any cline-yes">69x</span> | ||
| <span class="cline-any cline-yes">283x</span> | ||
| <span class="cline-any cline-yes">283x</span> | ||
| <span class="cline-any cline-yes">283x</span> | ||
| <span class="cline-any cline-yes">283x</span> | ||
| <span class="cline-any cline-yes">138x</span> | ||
| <span class="cline-any cline-yes">138x</span> | ||
| <span class="cline-any cline-yes">138x</span> | ||
| <span class="cline-any cline-yes">138x</span> | ||
| <span class="cline-any cline-yes">283x</span> | ||
| <span class="cline-any cline-yes">283x</span> | ||
| <span class="cline-any cline-yes">283x</span> | ||
| <span class="cline-any cline-yes">283x</span> | ||
| <span class="cline-any cline-yes">283x</span> | ||
| <span class="cline-any cline-yes">69x</span> | ||
| <span class="cline-any cline-yes">69x</span> | ||
| <span class="cline-any cline-yes">69x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">44x</span> | ||
| <span class="cline-any cline-yes">44x</span> | ||
| <span class="cline-any cline-yes">44x</span> | ||
| <span class="cline-any cline-yes">7x</span> | ||
| <span class="cline-any cline-yes">6x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">6x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">6x</span> | ||
| <span class="cline-any cline-yes">7x</span> | ||
| <span class="cline-any cline-yes">7x</span> | ||
| <span class="cline-any cline-yes">7x</span> | ||
| <span class="cline-any cline-no"> </span> | ||
| <span class="cline-any cline-no"> </span> | ||
| <span class="cline-any cline-no"> </span> | ||
| <span class="cline-any cline-no"> </span> | ||
| <span class="cline-any cline-no"> </span> | ||
| <span class="cline-any cline-no"> </span> | ||
| <span class="cline-any cline-yes">7x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">7x</span> | ||
| <span class="cline-any cline-yes">44x</span> | ||
| <span class="cline-any cline-yes">44x</span> | ||
| <span class="cline-any cline-yes">44x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">51x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">5x</span> | ||
| <span class="cline-any cline-yes">51x</span> | ||
| <span class="cline-any cline-yes">51x</span> | ||
| <span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">const hookKeys = { | ||
| guard: 'guards', | ||
| reduce: 'reducers', | ||
| effect: 'effects', | ||
| invoke: 'invokes', | ||
| } | ||
| | ||
| const transitionHooks = ['assign', 'reduce', 'action', 'guard'] | ||
| const enterHooks = ['assign', 'reduce', 'action', 'invoke', 'effect'] | ||
| const exitHooks = ['assign', 'reduce', 'action'] | ||
| | ||
| const mappedHooks = { | ||
| assign: ['reduce', assignToReduce], | ||
| action: ['reduce', actionToReduce], | ||
| } | ||
| | ||
| const ACTION = {} | ||
| | ||
| const env = <span class="branch-0 cbranch-no" title="branch not covered" >(process && process.env && process.env.NODE_ENV) || 'development'</span> | ||
| | ||
| function warn(msg) { | ||
| if (env !== 'production') { | ||
| console.warn(msg) | ||
| } | ||
| } | ||
| | ||
| function arg(argument, type, error) { | ||
| if (type === 'string') { | ||
| if (typeof argument !== 'string') { | ||
| throw new Error(error) | ||
| } | ||
| } | ||
| } | ||
| | ||
| /** | ||
| * Parse the machine DSL into a machine object. | ||
| */ | ||
| export function createMachine(create) { | ||
| const machine = { states: {} } | ||
| | ||
| if (create) { | ||
| create({ | ||
| state: (name, ...opts) => { | ||
| machine.states[name] = createState(name, ...opts) | ||
| }, | ||
| enter: createEnter, | ||
| exit: createExit, | ||
| transition: createTransition, | ||
| immediate: createImmediate, | ||
| internal: createInternal, | ||
| }) | ||
| } | ||
| | ||
| validate(machine) | ||
| | ||
| return machine | ||
| } | ||
| | ||
| function validate(machine) { | ||
| for (const [, state] of Object.entries(machine.states)) { | ||
| for (const transition of state.immediates) { | ||
| if (!machine.states[transition.target]) { | ||
| throw new Error(`Invalid transition target '${transition.target}'`) | ||
| } | ||
| } | ||
| for (const transitions of Object.values(state.transitions)) { | ||
| for (const transition of transitions) { | ||
| if (!transition.internal && !machine.states[transition.target]) { | ||
| throw new Error(`Invalid transition target '${transition.target}'`) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| | ||
| /** | ||
| * Create a state node. | ||
| */ | ||
| function createState(name, ...opts) { | ||
| const enter = [] | ||
| const exit = [] | ||
| const transitions = {} | ||
| const immediates = [] | ||
| | ||
| for (const opt of opts) { | ||
| const { type, event } = opt | ||
| if (type === 'transition') { | ||
| if (!transitions[event]) transitions[event] = [] | ||
| transitions[event].push(opt) | ||
| } else if (type === 'immediate') { | ||
| immediates.push(opt) | ||
| } else if (type === 'enter') { | ||
| enter.push(opt) | ||
| } else if (type === 'exit') { | ||
| exit.push(opt) | ||
| } else { | ||
| throw new Error( | ||
| `State '${name}' should be passed one of enter(), exit(), transition(), immediate() or internal()` | ||
| ) | ||
| } | ||
| } | ||
| | ||
| return { | ||
| name, | ||
| enter, | ||
| exit, | ||
| transitions, | ||
| immediates, | ||
| } | ||
| } | ||
| | ||
| function createEnter(opts) { | ||
| return { type: 'enter', ...merge(opts, enterHooks) } | ||
| } | ||
| | ||
| function createExit(opts) { | ||
| return { type: 'exit', ...merge(opts, exitHooks) } | ||
| } | ||
| | ||
| function createTransition(event, target, opts) { | ||
| arg(event, 'string', 'First argument of the transition must be the name of the event') | ||
| arg(target, 'string', 'Second argument of the transition must be the name of the target state') | ||
| return { type: 'transition', event, target, ...merge(opts, transitionHooks) } | ||
| } | ||
| | ||
| function createInternal(event, opts) { | ||
| arg(event, 'string', 'First argument of the internal transition must be the name of the event') | ||
| return { | ||
| type: 'transition', | ||
| event, | ||
| internal: true, | ||
| ...merge(opts, transitionHooks), | ||
| } | ||
| } | ||
| | ||
| function createImmediate(target, opts) { | ||
| arg( | ||
| target, | ||
| 'string', | ||
| 'First argument of the immediate transition must be the name of the target state' | ||
| ) | ||
| return { type: 'immediate', target, ...merge(opts, transitionHooks) } | ||
| } | ||
| | ||
| /** | ||
| * Transition the given machine, with the given state | ||
| * to the next state based on the event. Returns the tuple | ||
| * of the next state and any events to execute. In case no external | ||
| * transition took place, return null as effects, to indicate | ||
| * that the active effects should continue running. | ||
| */ | ||
| export function transition(machine = {}, state = {}, event) { | ||
| event = typeof event === 'string' ? { type: event } : event | ||
| | ||
| // initial transition | ||
| if (!state.name && event && event.type === null) { | ||
| const stateNames = Object.keys(machine.states) | ||
| if (stateNames.length > 0) { | ||
| const initialStateName = stateNames[0] | ||
| const initialTransition = createImmediate(initialStateName) | ||
| return applyTransition(machine, state, event, initialTransition) | ||
| } | ||
| } | ||
| | ||
| const currState = machine.states[state.name] || {} | ||
| const transitions = currState.transitions || {} | ||
| const candidates = transitions[event.type] || [] | ||
| | ||
| for (const candidate of candidates) { | ||
| if (checkGuards(state.context, event, candidate)) { | ||
| return applyTransition(machine, state, event, candidate) | ||
| } | ||
| } | ||
| | ||
| return [state, null] | ||
| } | ||
| | ||
| /** | ||
| * The logic of applying a transition to the machine. Exit states, | ||
| * apply transition hooks, enter states and collect any events. Do this | ||
| * recursively untill all immediate transitions settle. | ||
| */ | ||
| function applyTransition(machine, curr, event, transition) { | ||
| const next = { ...curr } | ||
| const target = transition.internal ? curr.name : transition.target | ||
| const currState = machine.states[curr.name] | ||
| const nextState = machine.states[target] | ||
| const effects = transition.internal ? null : [] | ||
| | ||
| if (currState && !transition.internal) { | ||
| for (const exit of currState.exit) { | ||
| applyReducers(next, event, exit.reducers) | ||
| } | ||
| } | ||
| | ||
| next.name = target | ||
| | ||
| applyReducers(next, event, transition.reducers) | ||
| | ||
| if (!transition.internal) { | ||
| for (const enter of nextState.enter) { | ||
| applyReducers(next, event, enter.reducers) | ||
| } | ||
| } | ||
| | ||
| for (const candidate of nextState.immediates) { | ||
| if (checkGuards(next.context, event, candidate)) { | ||
| return applyTransition(machine, next, event, candidate) | ||
| } | ||
| } | ||
| | ||
| if (Object.keys(nextState.transitions).length === 0 && nextState.immediates.length === 0) { | ||
| next.final = true | ||
| } | ||
| | ||
| if (!transition.internal) { | ||
| for (const enter of nextState.enter) { | ||
| for (const invoke of enter.invokes) { | ||
| effects.push({ run: promiseEffect(invoke), event }) | ||
| } | ||
| | ||
| for (const effect of enter.effects) { | ||
| effects.push({ run: effect, event }) | ||
| } | ||
| } | ||
| } | ||
| | ||
| return [next, effects] | ||
| } | ||
| | ||
| function checkGuards(context, event, transition) { | ||
| return !transition.guards.length || transition.guards.every((g) => g(context, event)) | ||
| } | ||
| | ||
| function applyReducers(next, event, reducers) { | ||
| for (const reduce of reducers) { | ||
| const result = reduce(next.context, event) | ||
| if (result !== ACTION) { | ||
| next.context = result | ||
| } | ||
| } | ||
| } | ||
| | ||
| /** | ||
| * A common operation is to assign event payload | ||
| * to the context, this allows to do in several ways: | ||
| * true - assign the full event payload to context | ||
| * fn - assign the result of the fn(context, data) to context | ||
| * val - assign the constant provided value to context | ||
| */ | ||
| function assignToReduce(assign) { | ||
| return (context, event) => { | ||
| const { type, ...data } = event | ||
| | ||
| if (assign === true) { | ||
| return { ...context, ...data } | ||
| } | ||
| | ||
| if (typeof assign === 'function') { | ||
| return { ...context, ...assign(context, data) } | ||
| } | ||
| | ||
| return { ...context, ...assign } | ||
| } | ||
| } | ||
| | ||
| function actionToReduce(action) { | ||
| return (context, event) => { | ||
| action(context, event) | ||
| return ACTION | ||
| } | ||
| } | ||
| | ||
| /** | ||
| * Allow to pass each hook as a function, or a list of functions | ||
| * transition(..., { reduce: fn }) | ||
| * transition(..., { reduce: [fn1, fn2] }) | ||
| * Convert both of those into arrays, and also remap some of the | ||
| * hooks to different hooks (i.e. assign -> reduce) | ||
| */ | ||
| function merge(opts = {}, allowedHooks) { | ||
| const merged = {} | ||
| | ||
| for (const hook of allowedHooks) { | ||
| add(hook) | ||
| } | ||
| | ||
| function add(hook) { | ||
| let t = opts[hook] || [] | ||
| t = Array.isArray(t) ? t : [t] | ||
| | ||
| if (mappedHooks[hook]) { | ||
| const [newName, transform] = mappedHooks[hook] | ||
| hook = newName | ||
| t = t.map(transform) | ||
| } | ||
| | ||
| const key = hookKeys[hook] | ||
| merged[key] = merged[key] || [] | ||
| merged[key] = merged[key].concat(t) | ||
| } | ||
| | ||
| return merged | ||
| } | ||
| | ||
| /** | ||
| * Convert an async function into an effect | ||
| * that sends 'done' and 'error' events | ||
| */ | ||
| function promiseEffect(fn) { | ||
| return (curr, event, send) => { | ||
| let disposed = false | ||
| Promise.resolve(fn(curr, event)) | ||
| .then((data) => { | ||
| if (!disposed) { | ||
| send({ type: 'done', data }) | ||
| } | ||
| }) | ||
| .catch((error) => { | ||
| if (!disposed) { | ||
| send({ type: 'error', error }) | ||
| } | ||
| }) | ||
| return () => { | ||
| disposed = true | ||
| } | ||
| } | ||
| } | ||
| | ||
| /** | ||
| * createMachine and transition are pure, stateless functions. After | ||
| * transitioning the machine to the next state, the caller must clean | ||
| * up all of the running effects, and then run the newly provided effects. | ||
| */ | ||
| export function runEffects(effects = [], state, send) { | ||
| const runningEffects = [] | ||
| | ||
| for (const effect of effects) { | ||
| const safeSend = (...args) => { | ||
| if (effect.disposed) { | ||
| warn( | ||
| [ | ||
| "Can't send events in an effect after it has been cleaned up.", | ||
| 'This is a no-op, but indicates a memory leak in your application.', | ||
| "To fix, cancel all subscriptions and asynchronous tasks in the effect's cleanup function.", | ||
| ].join(' ') | ||
| ) | ||
| } else { | ||
| return send(...args) | ||
| } | ||
| } | ||
| | ||
| const dispose = effect.run(state.context, effect.event, safeSend) | ||
| if (dispose && dispose.then) <span class="branch-0 cbranch-no" title="branch not covered" >{</span> | ||
| <span class="cstat-no" title="statement not covered" > warn(</span> | ||
| <span class="cstat-no" title="statement not covered" > [</span> | ||
| <span class="cstat-no" title="statement not covered" > 'Effect function must return a cleanup function or nothing.',</span> | ||
| <span class="cstat-no" title="statement not covered" > 'Use invoke instead of effect for async functions, or call the async function inside the synchronous effect function.',</span> | ||
| <span class="cstat-no" title="statement not covered" > ].join(' ')</span> | ||
| <span class="cstat-no" title="statement not covered" > )</span> | ||
| } else if (dispose) { | ||
| effect.dispose = () => { | ||
| effect.disposed = true | ||
| return dispose() | ||
| } | ||
| runningEffects.push(effect) | ||
| } | ||
| } | ||
| | ||
| return runningEffects | ||
| } | ||
| | ||
| export function cleanEffects(runningEffects = []) { | ||
| for (const effect of runningEffects) { | ||
| effect.dispose() | ||
| } | ||
| return [] | ||
| } | ||
| </pre></td></tr></table></pre> | ||
| <div class='push'></div><!-- for sticky footer --> | ||
| </div><!-- /wrapper --> | ||
| <div class='footer quiet pad2 space-top1 center small'> | ||
| Code coverage generated by | ||
| <a href="https://istanbul.js.org/" target="_blank">istanbul</a> | ||
| at Tue Dec 22 2020 15:10:56 GMT+0000 (Greenwich Mean Time) | ||
| </div> | ||
| </div> | ||
| <script src="prettify.js"></script> | ||
| <script> | ||
| window.onload = function () { | ||
| prettyPrint(); | ||
| }; | ||
| </script> | ||
| <script src="sorter.js"></script> | ||
| <script src="block-navigation.js"></script> | ||
| </body> | ||
| </html> | ||
Sorry, the diff of this file is not supported yet
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <title>Code coverage report for hooks.js</title> | ||
| <meta charset="utf-8" /> | ||
| <link rel="stylesheet" href="prettify.css" /> | ||
| <link rel="stylesheet" href="base.css" /> | ||
| <link rel="shortcut icon" type="image/x-icon" href="favicon.png" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
| <style type='text/css'> | ||
| .coverage-summary .sorter { | ||
| background-image: url(sort-arrow-sprite.png); | ||
| } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <div class='wrapper'> | ||
| <div class='pad1'> | ||
| <h1><a href="index.html">All files</a> hooks.js</h1> | ||
| <div class='clearfix'> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">100% </span> | ||
| <span class="quiet">Statements</span> | ||
| <span class='fraction'>67/67</span> | ||
| </div> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">86.67% </span> | ||
| <span class="quiet">Branches</span> | ||
| <span class='fraction'>13/15</span> | ||
| </div> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">100% </span> | ||
| <span class="quiet">Functions</span> | ||
| <span class='fraction'>3/3</span> | ||
| </div> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">100% </span> | ||
| <span class="quiet">Lines</span> | ||
| <span class='fraction'>67/67</span> | ||
| </div> | ||
| </div> | ||
| <p class="quiet"> | ||
| Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. | ||
| </p> | ||
| </div> | ||
| <div class='status-line high'></div> | ||
| <pre><table class="coverage"> | ||
| <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> | ||
| <a name='L2'></a><a href='#L2'>2</a> | ||
| <a name='L3'></a><a href='#L3'>3</a> | ||
| <a name='L4'></a><a href='#L4'>4</a> | ||
| <a name='L5'></a><a href='#L5'>5</a> | ||
| <a name='L6'></a><a href='#L6'>6</a> | ||
| <a name='L7'></a><a href='#L7'>7</a> | ||
| <a name='L8'></a><a href='#L8'>8</a> | ||
| <a name='L9'></a><a href='#L9'>9</a> | ||
| <a name='L10'></a><a href='#L10'>10</a> | ||
| <a name='L11'></a><a href='#L11'>11</a> | ||
| <a name='L12'></a><a href='#L12'>12</a> | ||
| <a name='L13'></a><a href='#L13'>13</a> | ||
| <a name='L14'></a><a href='#L14'>14</a> | ||
| <a name='L15'></a><a href='#L15'>15</a> | ||
| <a name='L16'></a><a href='#L16'>16</a> | ||
| <a name='L17'></a><a href='#L17'>17</a> | ||
| <a name='L18'></a><a href='#L18'>18</a> | ||
| <a name='L19'></a><a href='#L19'>19</a> | ||
| <a name='L20'></a><a href='#L20'>20</a> | ||
| <a name='L21'></a><a href='#L21'>21</a> | ||
| <a name='L22'></a><a href='#L22'>22</a> | ||
| <a name='L23'></a><a href='#L23'>23</a> | ||
| <a name='L24'></a><a href='#L24'>24</a> | ||
| <a name='L25'></a><a href='#L25'>25</a> | ||
| <a name='L26'></a><a href='#L26'>26</a> | ||
| <a name='L27'></a><a href='#L27'>27</a> | ||
| <a name='L28'></a><a href='#L28'>28</a> | ||
| <a name='L29'></a><a href='#L29'>29</a> | ||
| <a name='L30'></a><a href='#L30'>30</a> | ||
| <a name='L31'></a><a href='#L31'>31</a> | ||
| <a name='L32'></a><a href='#L32'>32</a> | ||
| <a name='L33'></a><a href='#L33'>33</a> | ||
| <a name='L34'></a><a href='#L34'>34</a> | ||
| <a name='L35'></a><a href='#L35'>35</a> | ||
| <a name='L36'></a><a href='#L36'>36</a> | ||
| <a name='L37'></a><a href='#L37'>37</a> | ||
| <a name='L38'></a><a href='#L38'>38</a> | ||
| <a name='L39'></a><a href='#L39'>39</a> | ||
| <a name='L40'></a><a href='#L40'>40</a> | ||
| <a name='L41'></a><a href='#L41'>41</a> | ||
| <a name='L42'></a><a href='#L42'>42</a> | ||
| <a name='L43'></a><a href='#L43'>43</a> | ||
| <a name='L44'></a><a href='#L44'>44</a> | ||
| <a name='L45'></a><a href='#L45'>45</a> | ||
| <a name='L46'></a><a href='#L46'>46</a> | ||
| <a name='L47'></a><a href='#L47'>47</a> | ||
| <a name='L48'></a><a href='#L48'>48</a> | ||
| <a name='L49'></a><a href='#L49'>49</a> | ||
| <a name='L50'></a><a href='#L50'>50</a> | ||
| <a name='L51'></a><a href='#L51'>51</a> | ||
| <a name='L52'></a><a href='#L52'>52</a> | ||
| <a name='L53'></a><a href='#L53'>53</a> | ||
| <a name='L54'></a><a href='#L54'>54</a> | ||
| <a name='L55'></a><a href='#L55'>55</a> | ||
| <a name='L56'></a><a href='#L56'>56</a> | ||
| <a name='L57'></a><a href='#L57'>57</a> | ||
| <a name='L58'></a><a href='#L58'>58</a> | ||
| <a name='L59'></a><a href='#L59'>59</a> | ||
| <a name='L60'></a><a href='#L60'>60</a> | ||
| <a name='L61'></a><a href='#L61'>61</a> | ||
| <a name='L62'></a><a href='#L62'>62</a> | ||
| <a name='L63'></a><a href='#L63'>63</a> | ||
| <a name='L64'></a><a href='#L64'>64</a> | ||
| <a name='L65'></a><a href='#L65'>65</a> | ||
| <a name='L66'></a><a href='#L66'>66</a> | ||
| <a name='L67'></a><a href='#L67'>67</a> | ||
| <a name='L68'></a><a href='#L68'>68</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">6x</span> | ||
| <span class="cline-any cline-yes">6x</span> | ||
| <span class="cline-any cline-yes">6x</span> | ||
| <span class="cline-any cline-yes">6x</span> | ||
| <span class="cline-any cline-yes">6x</span> | ||
| <span class="cline-any cline-yes">6x</span> | ||
| <span class="cline-any cline-yes">6x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">6x</span> | ||
| <span class="cline-any cline-yes">6x</span> | ||
| <span class="cline-any cline-yes">6x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">4x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-yes">14x</span> | ||
| <span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import { useReducer, useEffect, useCallback, useMemo, useRef } from 'react' | ||
| import { createMachine, transition, runEffects, cleanEffects } from './core.js' | ||
| | ||
| function initial({ context, machine }) { | ||
| const initialState = { context } | ||
| const initialEvent = { type: null } | ||
| | ||
| const [state, effects] = transition(machine, initialState, initialEvent) | ||
| | ||
| const curr = { | ||
| machine, | ||
| effects: effects<span class="branch-0 cbranch-no" title="branch not covered" > || [],</span> | ||
| state, | ||
| } | ||
| | ||
| return curr | ||
| } | ||
| | ||
| function reduce(curr, action) { | ||
| if (action.type === 'send') { | ||
| const { event, machine } = action | ||
| const [state, effects] = transition(machine, curr.state, event) | ||
| return { ...curr, state, effects: effects || curr.effects } | ||
| } | ||
| } | ||
| | ||
| export function useMachine(create, context = {}, options = {}) { | ||
| const { assign = 'assign', deps } = options | ||
| | ||
| const runningEffects = useRef() | ||
| const firstRender = useRef(true) | ||
| const machine = useMemo(() => createMachine(create), [create]) | ||
| const [curr, dispatch] = useReducer(reduce, { context, machine }, initial) | ||
| const send = useCallback((event) => dispatch({ type: 'send', event, machine }), [ | ||
| machine, | ||
| dispatch, | ||
| ]) | ||
| | ||
| useEffect(() => { | ||
| runningEffects.current = cleanEffects(runningEffects.current) | ||
| | ||
| if (curr.effects.length) { | ||
| runningEffects.current = runEffects(curr.effects, curr.state, send) | ||
| } | ||
| }, [send, curr.effects]) | ||
| | ||
| useEffect(() => { | ||
| return () => { | ||
| runningEffects.current = cleanEffects(runningEffects.current) | ||
| } | ||
| }, []) | ||
| | ||
| const assignEffectDeps = [send].concat(deps || (context<span class="branch-0 cbranch-no" title="branch not covered" > ? Object.values(context) : []))</span> | ||
| | ||
| useEffect(() => { | ||
| if (firstRender.current) { | ||
| firstRender.current = false | ||
| return | ||
| } | ||
| | ||
| if (assign) { | ||
| send({ type: assign, ...context }) | ||
| } | ||
| }, assignEffectDeps) | ||
| | ||
| return [curr.state, send, curr.machine] | ||
| } | ||
| </pre></td></tr></table></pre> | ||
| <div class='push'></div><!-- for sticky footer --> | ||
| </div><!-- /wrapper --> | ||
| <div class='footer quiet pad2 space-top1 center small'> | ||
| Code coverage generated by | ||
| <a href="https://istanbul.js.org/" target="_blank">istanbul</a> | ||
| at Tue Dec 22 2020 15:10:56 GMT+0000 (Greenwich Mean Time) | ||
| </div> | ||
| </div> | ||
| <script src="prettify.js"></script> | ||
| <script> | ||
| window.onload = function () { | ||
| prettyPrint(); | ||
| }; | ||
| </script> | ||
| <script src="sorter.js"></script> | ||
| <script src="block-navigation.js"></script> | ||
| </body> | ||
| </html> | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <title>Code coverage report for All files</title> | ||
| <meta charset="utf-8" /> | ||
| <link rel="stylesheet" href="prettify.css" /> | ||
| <link rel="stylesheet" href="base.css" /> | ||
| <link rel="shortcut icon" type="image/x-icon" href="favicon.png" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
| <style type='text/css'> | ||
| .coverage-summary .sorter { | ||
| background-image: url(sort-arrow-sprite.png); | ||
| } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <div class='wrapper'> | ||
| <div class='pad1'> | ||
| <h1>All files</h1> | ||
| <div class='clearfix'> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">98.82% </span> | ||
| <span class="quiet">Statements</span> | ||
| <span class='fraction'>502/508</span> | ||
| </div> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">97.12% </span> | ||
| <span class="quiet">Branches</span> | ||
| <span class='fraction'>135/139</span> | ||
| </div> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">100% </span> | ||
| <span class="quiet">Functions</span> | ||
| <span class='fraction'>32/32</span> | ||
| </div> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">98.82% </span> | ||
| <span class="quiet">Lines</span> | ||
| <span class='fraction'>502/508</span> | ||
| </div> | ||
| </div> | ||
| <p class="quiet"> | ||
| Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. | ||
| </p> | ||
| </div> | ||
| <div class='status-line high'></div> | ||
| <div class="pad1"> | ||
| <table class="coverage-summary"> | ||
| <thead> | ||
| <tr> | ||
| <th data-col="file" data-fmt="html" data-html="true" class="file">File</th> | ||
| <th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th> | ||
| <th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th> | ||
| <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th> | ||
| <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th> | ||
| <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th> | ||
| <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th> | ||
| <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th> | ||
| <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th> | ||
| <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th> | ||
| </tr> | ||
| </thead> | ||
| <tbody><tr> | ||
| <td class="file high" data-value="core.js"><a href="core.js.html">core.js</a></td> | ||
| <td data-value="98.41" class="pic high"> | ||
| <div class="chart"><div class="cover-fill" style="width: 98%"></div><div class="cover-empty" style="width: 2%"></div></div> | ||
| </td> | ||
| <td data-value="98.41" class="pct high">98.41%</td> | ||
| <td data-value="378" class="abs high">372/378</td> | ||
| <td data-value="98.18" class="pct high">98.18%</td> | ||
| <td data-value="110" class="abs high">108/110</td> | ||
| <td data-value="100" class="pct high">100%</td> | ||
| <td data-value="24" class="abs high">24/24</td> | ||
| <td data-value="98.41" class="pct high">98.41%</td> | ||
| <td data-value="378" class="abs high">372/378</td> | ||
| </tr> | ||
| <tr> | ||
| <td class="file high" data-value="hooks.js"><a href="hooks.js.html">hooks.js</a></td> | ||
| <td data-value="100" class="pic high"> | ||
| <div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div> | ||
| </td> | ||
| <td data-value="100" class="pct high">100%</td> | ||
| <td data-value="67" class="abs high">67/67</td> | ||
| <td data-value="86.67" class="pct high">86.67%</td> | ||
| <td data-value="15" class="abs high">13/15</td> | ||
| <td data-value="100" class="pct high">100%</td> | ||
| <td data-value="3" class="abs high">3/3</td> | ||
| <td data-value="100" class="pct high">100%</td> | ||
| <td data-value="67" class="abs high">67/67</td> | ||
| </tr> | ||
| <tr> | ||
| <td class="file high" data-value="index.js"><a href="index.js.html">index.js</a></td> | ||
| <td data-value="100" class="pic high"> | ||
| <div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div> | ||
| </td> | ||
| <td data-value="100" class="pct high">100%</td> | ||
| <td data-value="2" class="abs high">2/2</td> | ||
| <td data-value="100" class="pct high">100%</td> | ||
| <td data-value="0" class="abs high">0/0</td> | ||
| <td data-value="100" class="pct high">100%</td> | ||
| <td data-value="0" class="abs high">0/0</td> | ||
| <td data-value="100" class="pct high">100%</td> | ||
| <td data-value="2" class="abs high">2/2</td> | ||
| </tr> | ||
| <tr> | ||
| <td class="file high" data-value="service.js"><a href="service.js.html">service.js</a></td> | ||
| <td data-value="100" class="pic high"> | ||
| <div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div> | ||
| </td> | ||
| <td data-value="100" class="pct high">100%</td> | ||
| <td data-value="61" class="abs high">61/61</td> | ||
| <td data-value="100" class="pct high">100%</td> | ||
| <td data-value="14" class="abs high">14/14</td> | ||
| <td data-value="100" class="pct high">100%</td> | ||
| <td data-value="5" class="abs high">5/5</td> | ||
| <td data-value="100" class="pct high">100%</td> | ||
| <td data-value="61" class="abs high">61/61</td> | ||
| </tr> | ||
| </tbody> | ||
| </table> | ||
| </div> | ||
| <div class='push'></div><!-- for sticky footer --> | ||
| </div><!-- /wrapper --> | ||
| <div class='footer quiet pad2 space-top1 center small'> | ||
| Code coverage generated by | ||
| <a href="https://istanbul.js.org/" target="_blank">istanbul</a> | ||
| at Tue Dec 22 2020 15:10:56 GMT+0000 (Greenwich Mean Time) | ||
| </div> | ||
| </div> | ||
| <script src="prettify.js"></script> | ||
| <script> | ||
| window.onload = function () { | ||
| prettyPrint(); | ||
| }; | ||
| </script> | ||
| <script src="sorter.js"></script> | ||
| <script src="block-navigation.js"></script> | ||
| </body> | ||
| </html> | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <title>Code coverage report for index.js</title> | ||
| <meta charset="utf-8" /> | ||
| <link rel="stylesheet" href="prettify.css" /> | ||
| <link rel="stylesheet" href="base.css" /> | ||
| <link rel="shortcut icon" type="image/x-icon" href="favicon.png" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
| <style type='text/css'> | ||
| .coverage-summary .sorter { | ||
| background-image: url(sort-arrow-sprite.png); | ||
| } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <div class='wrapper'> | ||
| <div class='pad1'> | ||
| <h1><a href="index.html">All files</a> index.js</h1> | ||
| <div class='clearfix'> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">100% </span> | ||
| <span class="quiet">Statements</span> | ||
| <span class='fraction'>2/2</span> | ||
| </div> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">100% </span> | ||
| <span class="quiet">Branches</span> | ||
| <span class='fraction'>0/0</span> | ||
| </div> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">100% </span> | ||
| <span class="quiet">Functions</span> | ||
| <span class='fraction'>0/0</span> | ||
| </div> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">100% </span> | ||
| <span class="quiet">Lines</span> | ||
| <span class='fraction'>2/2</span> | ||
| </div> | ||
| </div> | ||
| <p class="quiet"> | ||
| Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. | ||
| </p> | ||
| </div> | ||
| <div class='status-line high'></div> | ||
| <pre><table class="coverage"> | ||
| <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> | ||
| <a name='L2'></a><a href='#L2'>2</a> | ||
| <a name='L3'></a><a href='#L3'>3</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">export { createMachine, transition, runEffects, cleanEffects } from './core.js' | ||
| export { useMachine } from './hooks.js' | ||
| </pre></td></tr></table></pre> | ||
| <div class='push'></div><!-- for sticky footer --> | ||
| </div><!-- /wrapper --> | ||
| <div class='footer quiet pad2 space-top1 center small'> | ||
| Code coverage generated by | ||
| <a href="https://istanbul.js.org/" target="_blank">istanbul</a> | ||
| at Tue Dec 22 2020 15:10:56 GMT+0000 (Greenwich Mean Time) | ||
| </div> | ||
| </div> | ||
| <script src="prettify.js"></script> | ||
| <script> | ||
| window.onload = function () { | ||
| prettyPrint(); | ||
| }; | ||
| </script> | ||
| <script src="sorter.js"></script> | ||
| <script src="block-navigation.js"></script> | ||
| </body> | ||
| </html> | ||
| .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} |
| /* eslint-disable */ | ||
| window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V<U;++V){var ae=Z[V];if(ae.ignoreCase){ac=true}else{if(/[a-z]/i.test(ae.source.replace(/\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi,""))){S=true;ac=false;break}}}var Y={b:8,t:9,n:10,v:11,f:12,r:13};function ab(ah){var ag=ah.charCodeAt(0);if(ag!==92){return ag}var af=ah.charAt(1);ag=Y[af];if(ag){return ag}else{if("0"<=af&&af<="7"){return parseInt(ah.substring(1),8)}else{if(af==="u"||af==="x"){return parseInt(ah.substring(2),16)}else{return ah.charCodeAt(1)}}}}function T(af){if(af<32){return(af<16?"\\x0":"\\x")+af.toString(16)}var ag=String.fromCharCode(af);if(ag==="\\"||ag==="-"||ag==="["||ag==="]"){ag="\\"+ag}return ag}function X(am){var aq=am.substring(1,am.length-1).match(new RegExp("\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]","g"));var ak=[];var af=[];var ao=aq[0]==="^";for(var ar=ao?1:0,aj=aq.length;ar<aj;++ar){var ah=aq[ar];if(/\\[bdsw]/i.test(ah)){ak.push(ah)}else{var ag=ab(ah);var al;if(ar+2<aj&&"-"===aq[ar+1]){al=ab(aq[ar+2]);ar+=2}else{al=ag}af.push([ag,al]);if(!(al<65||ag>122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;ar<af.length;++ar){var at=af[ar];if(at[0]<=ap[1]+1){ap[1]=Math.max(ap[1],at[1])}else{ai.push(ap=at)}}var an=["["];if(ao){an.push("^")}an.push.apply(an,ak);for(var ar=0;ar<ai.length;++ar){var at=ai[ar];an.push(T(at[0]));if(at[1]>at[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak<ah;++ak){var ag=aj[ak];if(ag==="("){++am}else{if("\\"===ag.charAt(0)){var af=+ag.substring(1);if(af&&af<=am){an[af]=-1}}}}for(var ak=1;ak<an.length;++ak){if(-1===an[ak]){an[ak]=++ad}}for(var ak=0,am=0;ak<ah;++ak){var ag=aj[ak];if(ag==="("){++am;if(an[am]===undefined){aj[ak]="(?:"}}else{if("\\"===ag.charAt(0)){var af=+ag.substring(1);if(af&&af<=am){aj[ak]="\\"+an[am]}}}}for(var ak=0,am=0;ak<ah;++ak){if("^"===aj[ak]&&"^"!==aj[ak+1]){aj[ak]=""}}if(al.ignoreCase&&S){for(var ak=0;ak<ah;++ak){var ag=aj[ak];var ai=ag.charAt(0);if(ag.length>=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V<U;++V){var ae=Z[V];if(ae.global||ae.multiline){throw new Error(""+ae)}aa.push("(?:"+W(ae)+")")}return new RegExp(aa.join("|"),ac?"gi":"g")}function a(V){var U=/(?:^|\s)nocode(?:\s|$)/;var X=[];var T=0;var Z=[];var W=0;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=document.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Y=S&&"pre"===S.substring(0,3);function aa(ab){switch(ab.nodeType){case 1:if(U.test(ab.className)){return}for(var ae=ab.firstChild;ae;ae=ae.nextSibling){aa(ae)}var ad=ab.nodeName;if("BR"===ad||"LI"===ad){X[W]="\n";Z[W<<1]=T++;Z[(W++<<1)|1]=ab}break;case 3:case 4:var ac=ab.nodeValue;if(ac.length){if(!Y){ac=ac.replace(/[ \t\r\n]+/g," ")}else{ac=ac.replace(/\r\n?/g,"\n")}X[W]=ac;Z[W<<1]=T;T+=ac.length;Z[(W++<<1)|1]=ab}break}}aa(V);return{sourceCode:X.join("").replace(/\n$/,""),spans:Z}}function B(S,U,W,T){if(!U){return}var V={sourceCode:U,basePos:S};W(V);T.push.apply(T,V.decorations)}var v=/\S/;function o(S){var V=undefined;for(var U=S.firstChild;U;U=U.nextSibling){var T=U.nodeType;V=(T===1)?(V?S:U):(T===3)?(v.test(U.nodeValue)?S:V):V}return V===S?undefined:V}function g(U,T){var S={};var V;(function(){var ad=U.concat(T);var ah=[];var ag={};for(var ab=0,Z=ad.length;ab<Z;++ab){var Y=ad[ab];var ac=Y[3];if(ac){for(var ae=ac.length;--ae>=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae<aq;++ae){var ag=an[ae];var ap=aj[ag];var ai=void 0;var am;if(typeof ap==="string"){am=false}else{var aa=S[ag.charAt(0)];if(aa){ai=ag.match(aa[1]);ap=aa[0]}else{for(var ao=0;ao<X;++ao){aa=T[ao];ai=ag.match(aa[1]);if(ai){ap=aa[0];break}}if(!ai){ap=F}}am=ap.length>=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y<W.length;++Y){ae(W[Y])}if(ag===(ag|0)){W[0].setAttribute("value",ag)}var aa=ac.createElement("OL");aa.className="linenums";var X=Math.max(0,((ag-1))|0)||0;for(var Y=0,T=W.length;Y<T;++Y){af=W[Y];af.className="L"+((Y+X)%10);if(!af.firstChild){af.appendChild(ac.createTextNode("\xA0"))}aa.appendChild(af)}V.appendChild(aa)}function D(ac){var aj=/\bMSIE\b/.test(navigator.userAgent);var am=/\n/g;var al=ac.sourceCode;var an=al.length;var V=0;var aa=ac.spans;var T=aa.length;var ah=0;var X=ac.decorations;var Y=X.length;var Z=0;X[Y]=an;var ar,aq;for(aq=ar=0;aq<Y;){if(X[aq]!==X[aq+2]){X[ar++]=X[aq++];X[ar++]=X[aq++]}else{aq+=2}}Y=ar;for(aq=ar=0;aq<Y;){var at=X[aq];var ab=X[aq+1];var W=aq+2;while(W+2<=Y&&X[W+1]===ab){W+=2}X[ar++]=at;X[ar++]=ab;aq=W}Y=X.length=ar;var ae=null;while(ah<T){var af=aa[ah];var S=aa[ah+2]||an;var ag=X[Z];var ap=X[Z+2]||an;var W=Math.min(S,ap);var ak=aa[ah+1];var U;if(ak.nodeType!==1&&(U=al.substring(V,W))){if(aj){U=U.replace(am,"\r")}ak.nodeValue=U;var ai=ak.ownerDocument;var ao=ai.createElement("SPAN");ao.className=X[Z+1];var ad=ak.parentNode;ad.replaceChild(ao,ak);ao.appendChild(ak);if(V<S){aa[ah+1]=ak=ai.createTextNode(al.substring(W,S));ad.insertBefore(ak,ao.nextSibling)}}V=W;if(V>=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*</.test(S)?"default-markup":"default-code"}return t[T]}c(K,["default-code"]);c(g([],[[F,/^[^<?]+/],[E,/^<!\w[^>]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa<ac.length;++aa){for(var Z=0,V=ac[aa].length;Z<V;++Z){T.push(ac[aa][Z])}}ac=null;var W=Date;if(!W.now){W={now:function(){return +(new Date)}}}var X=0;var S;var ab=/\blang(?:uage)?-([\w.]+)(?!\S)/;var ae=/\bprettyprint\b/;function U(){var ag=(window.PR_SHOULD_USE_CONTINUATION?W.now()+250:Infinity);for(;X<T.length&&W.now()<ag;X++){var aj=T[X];var ai=aj.className;if(ai.indexOf("prettyprint")>=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X<T.length){setTimeout(U,250)}else{if(ad){ad()}}}U()}window.prettyPrintOne=y;window.prettyPrint=b;window.PR={createSimpleLexer:g,registerLangHandler:c,sourceDecorator:i,PR_ATTRIB_NAME:P,PR_ATTRIB_VALUE:n,PR_COMMENT:j,PR_DECLARATION:E,PR_KEYWORD:z,PR_LITERAL:G,PR_NOCODE:N,PR_PLAIN:F,PR_PUNCTUATION:L,PR_SOURCE:J,PR_STRING:C,PR_TAG:m,PR_TYPE:O}})();PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_DECLARATION,/^<!\w[^>]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^<script\b[^>]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:<!--|-->)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); |
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <title>Code coverage report for service.js</title> | ||
| <meta charset="utf-8" /> | ||
| <link rel="stylesheet" href="prettify.css" /> | ||
| <link rel="stylesheet" href="base.css" /> | ||
| <link rel="shortcut icon" type="image/x-icon" href="favicon.png" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
| <style type='text/css'> | ||
| .coverage-summary .sorter { | ||
| background-image: url(sort-arrow-sprite.png); | ||
| } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <div class='wrapper'> | ||
| <div class='pad1'> | ||
| <h1><a href="index.html">All files</a> service.js</h1> | ||
| <div class='clearfix'> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">100% </span> | ||
| <span class="quiet">Statements</span> | ||
| <span class='fraction'>61/61</span> | ||
| </div> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">100% </span> | ||
| <span class="quiet">Branches</span> | ||
| <span class='fraction'>14/14</span> | ||
| </div> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">100% </span> | ||
| <span class="quiet">Functions</span> | ||
| <span class='fraction'>5/5</span> | ||
| </div> | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">100% </span> | ||
| <span class="quiet">Lines</span> | ||
| <span class='fraction'>61/61</span> | ||
| </div> | ||
| </div> | ||
| <p class="quiet"> | ||
| Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block. | ||
| </p> | ||
| </div> | ||
| <div class='status-line high'></div> | ||
| <pre><table class="coverage"> | ||
| <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a> | ||
| <a name='L2'></a><a href='#L2'>2</a> | ||
| <a name='L3'></a><a href='#L3'>3</a> | ||
| <a name='L4'></a><a href='#L4'>4</a> | ||
| <a name='L5'></a><a href='#L5'>5</a> | ||
| <a name='L6'></a><a href='#L6'>6</a> | ||
| <a name='L7'></a><a href='#L7'>7</a> | ||
| <a name='L8'></a><a href='#L8'>8</a> | ||
| <a name='L9'></a><a href='#L9'>9</a> | ||
| <a name='L10'></a><a href='#L10'>10</a> | ||
| <a name='L11'></a><a href='#L11'>11</a> | ||
| <a name='L12'></a><a href='#L12'>12</a> | ||
| <a name='L13'></a><a href='#L13'>13</a> | ||
| <a name='L14'></a><a href='#L14'>14</a> | ||
| <a name='L15'></a><a href='#L15'>15</a> | ||
| <a name='L16'></a><a href='#L16'>16</a> | ||
| <a name='L17'></a><a href='#L17'>17</a> | ||
| <a name='L18'></a><a href='#L18'>18</a> | ||
| <a name='L19'></a><a href='#L19'>19</a> | ||
| <a name='L20'></a><a href='#L20'>20</a> | ||
| <a name='L21'></a><a href='#L21'>21</a> | ||
| <a name='L22'></a><a href='#L22'>22</a> | ||
| <a name='L23'></a><a href='#L23'>23</a> | ||
| <a name='L24'></a><a href='#L24'>24</a> | ||
| <a name='L25'></a><a href='#L25'>25</a> | ||
| <a name='L26'></a><a href='#L26'>26</a> | ||
| <a name='L27'></a><a href='#L27'>27</a> | ||
| <a name='L28'></a><a href='#L28'>28</a> | ||
| <a name='L29'></a><a href='#L29'>29</a> | ||
| <a name='L30'></a><a href='#L30'>30</a> | ||
| <a name='L31'></a><a href='#L31'>31</a> | ||
| <a name='L32'></a><a href='#L32'>32</a> | ||
| <a name='L33'></a><a href='#L33'>33</a> | ||
| <a name='L34'></a><a href='#L34'>34</a> | ||
| <a name='L35'></a><a href='#L35'>35</a> | ||
| <a name='L36'></a><a href='#L36'>36</a> | ||
| <a name='L37'></a><a href='#L37'>37</a> | ||
| <a name='L38'></a><a href='#L38'>38</a> | ||
| <a name='L39'></a><a href='#L39'>39</a> | ||
| <a name='L40'></a><a href='#L40'>40</a> | ||
| <a name='L41'></a><a href='#L41'>41</a> | ||
| <a name='L42'></a><a href='#L42'>42</a> | ||
| <a name='L43'></a><a href='#L43'>43</a> | ||
| <a name='L44'></a><a href='#L44'>44</a> | ||
| <a name='L45'></a><a href='#L45'>45</a> | ||
| <a name='L46'></a><a href='#L46'>46</a> | ||
| <a name='L47'></a><a href='#L47'>47</a> | ||
| <a name='L48'></a><a href='#L48'>48</a> | ||
| <a name='L49'></a><a href='#L49'>49</a> | ||
| <a name='L50'></a><a href='#L50'>50</a> | ||
| <a name='L51'></a><a href='#L51'>51</a> | ||
| <a name='L52'></a><a href='#L52'>52</a> | ||
| <a name='L53'></a><a href='#L53'>53</a> | ||
| <a name='L54'></a><a href='#L54'>54</a> | ||
| <a name='L55'></a><a href='#L55'>55</a> | ||
| <a name='L56'></a><a href='#L56'>56</a> | ||
| <a name='L57'></a><a href='#L57'>57</a> | ||
| <a name='L58'></a><a href='#L58'>58</a> | ||
| <a name='L59'></a><a href='#L59'>59</a> | ||
| <a name='L60'></a><a href='#L60'>60</a> | ||
| <a name='L61'></a><a href='#L61'>61</a> | ||
| <a name='L62'></a><a href='#L62'>62</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">43x</span> | ||
| <span class="cline-any cline-yes">43x</span> | ||
| <span class="cline-any cline-yes">43x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">2x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">28x</span> | ||
| <span class="cline-any cline-yes">27x</span> | ||
| <span class="cline-any cline-yes">27x</span> | ||
| <span class="cline-any cline-yes">27x</span> | ||
| <span class="cline-any cline-yes">27x</span> | ||
| <span class="cline-any cline-yes">28x</span> | ||
| <span class="cline-any cline-yes">23x</span> | ||
| <span class="cline-any cline-yes">23x</span> | ||
| <span class="cline-any cline-yes">23x</span> | ||
| <span class="cline-any cline-yes">27x</span> | ||
| <span class="cline-any cline-yes">28x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">3x</span> | ||
| <span class="cline-any cline-yes">27x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-yes">25x</span> | ||
| <span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import { createMachine, transition, runEffects as run, cleanEffects as clean } from './core.js' | ||
| | ||
| export function createService(machineDescription, context = {}) { | ||
| const machine = createMachine(machineDescription) | ||
| | ||
| // initial transition | ||
| const [state, effects] = transition(machine, { name: null, context }, { type: null }) | ||
| | ||
| let cbs = [] | ||
| let running = true | ||
| | ||
| const service = { | ||
| machine, | ||
| state, | ||
| prev: null, | ||
| pendingEffects: effects || [], | ||
| runningEffects: [], | ||
| send, | ||
| subscribe, | ||
| stop, | ||
| } | ||
| | ||
| function runEffects() { | ||
| service.runningEffects = clean(service.runningEffects) | ||
| service.runningEffects = run(service.pendingEffects, service.state, service.send) | ||
| } | ||
| | ||
| function stop() { | ||
| running = false | ||
| cbs = [] | ||
| service.pendingEffects = [] | ||
| service.runningEffects = clean(service.runningEffects) | ||
| } | ||
| | ||
| function subscribe(fn) { | ||
| cbs.push(fn) | ||
| return () => { | ||
| cbs = cbs.filter((f) => f !== fn) | ||
| } | ||
| } | ||
| | ||
| function send(event) { | ||
| if (!running) return | ||
| | ||
| service.prev = service.state | ||
| const [state, effects] = transition(service.machine, service.state, event) | ||
| service.state = state | ||
| if (effects) { | ||
| service.pendingEffects = effects | ||
| runEffects() | ||
| } | ||
| | ||
| for (const cb of cbs) { | ||
| cb(state) | ||
| } | ||
| } | ||
| | ||
| runEffects() | ||
| | ||
| return service | ||
| } | ||
| </pre></td></tr></table></pre> | ||
| <div class='push'></div><!-- for sticky footer --> | ||
| </div><!-- /wrapper --> | ||
| <div class='footer quiet pad2 space-top1 center small'> | ||
| Code coverage generated by | ||
| <a href="https://istanbul.js.org/" target="_blank">istanbul</a> | ||
| at Tue Dec 22 2020 15:10:56 GMT+0000 (Greenwich Mean Time) | ||
| </div> | ||
| </div> | ||
| <script src="prettify.js"></script> | ||
| <script> | ||
| window.onload = function () { | ||
| prettyPrint(); | ||
| }; | ||
| </script> | ||
| <script src="sorter.js"></script> | ||
| <script src="block-navigation.js"></script> | ||
| </body> | ||
| </html> | ||
Sorry, the diff of this file is not supported yet
| /* eslint-disable */ | ||
| var addSorting = (function() { | ||
| 'use strict'; | ||
| var cols, | ||
| currentSort = { | ||
| index: 0, | ||
| desc: false | ||
| }; | ||
| // returns the summary table element | ||
| function getTable() { | ||
| return document.querySelector('.coverage-summary'); | ||
| } | ||
| // returns the thead element of the summary table | ||
| function getTableHeader() { | ||
| return getTable().querySelector('thead tr'); | ||
| } | ||
| // returns the tbody element of the summary table | ||
| function getTableBody() { | ||
| return getTable().querySelector('tbody'); | ||
| } | ||
| // returns the th element for nth column | ||
| function getNthColumn(n) { | ||
| return getTableHeader().querySelectorAll('th')[n]; | ||
| } | ||
| // loads all columns | ||
| function loadColumns() { | ||
| var colNodes = getTableHeader().querySelectorAll('th'), | ||
| colNode, | ||
| cols = [], | ||
| col, | ||
| i; | ||
| for (i = 0; i < colNodes.length; i += 1) { | ||
| colNode = colNodes[i]; | ||
| col = { | ||
| key: colNode.getAttribute('data-col'), | ||
| sortable: !colNode.getAttribute('data-nosort'), | ||
| type: colNode.getAttribute('data-type') || 'string' | ||
| }; | ||
| cols.push(col); | ||
| if (col.sortable) { | ||
| col.defaultDescSort = col.type === 'number'; | ||
| colNode.innerHTML = | ||
| colNode.innerHTML + '<span class="sorter"></span>'; | ||
| } | ||
| } | ||
| return cols; | ||
| } | ||
| // attaches a data attribute to every tr element with an object | ||
| // of data values keyed by column name | ||
| function loadRowData(tableRow) { | ||
| var tableCols = tableRow.querySelectorAll('td'), | ||
| colNode, | ||
| col, | ||
| data = {}, | ||
| i, | ||
| val; | ||
| for (i = 0; i < tableCols.length; i += 1) { | ||
| colNode = tableCols[i]; | ||
| col = cols[i]; | ||
| val = colNode.getAttribute('data-value'); | ||
| if (col.type === 'number') { | ||
| val = Number(val); | ||
| } | ||
| data[col.key] = val; | ||
| } | ||
| return data; | ||
| } | ||
| // loads all row data | ||
| function loadData() { | ||
| var rows = getTableBody().querySelectorAll('tr'), | ||
| i; | ||
| for (i = 0; i < rows.length; i += 1) { | ||
| rows[i].data = loadRowData(rows[i]); | ||
| } | ||
| } | ||
| // sorts the table using the data for the ith column | ||
| function sortByIndex(index, desc) { | ||
| var key = cols[index].key, | ||
| sorter = function(a, b) { | ||
| a = a.data[key]; | ||
| b = b.data[key]; | ||
| return a < b ? -1 : a > b ? 1 : 0; | ||
| }, | ||
| finalSorter = sorter, | ||
| tableBody = document.querySelector('.coverage-summary tbody'), | ||
| rowNodes = tableBody.querySelectorAll('tr'), | ||
| rows = [], | ||
| i; | ||
| if (desc) { | ||
| finalSorter = function(a, b) { | ||
| return -1 * sorter(a, b); | ||
| }; | ||
| } | ||
| for (i = 0; i < rowNodes.length; i += 1) { | ||
| rows.push(rowNodes[i]); | ||
| tableBody.removeChild(rowNodes[i]); | ||
| } | ||
| rows.sort(finalSorter); | ||
| for (i = 0; i < rows.length; i += 1) { | ||
| tableBody.appendChild(rows[i]); | ||
| } | ||
| } | ||
| // removes sort indicators for current column being sorted | ||
| function removeSortIndicators() { | ||
| var col = getNthColumn(currentSort.index), | ||
| cls = col.className; | ||
| cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); | ||
| col.className = cls; | ||
| } | ||
| // adds sort indicators for current column being sorted | ||
| function addSortIndicators() { | ||
| getNthColumn(currentSort.index).className += currentSort.desc | ||
| ? ' sorted-desc' | ||
| : ' sorted'; | ||
| } | ||
| // adds event listeners for all sorter widgets | ||
| function enableUI() { | ||
| var i, | ||
| el, | ||
| ithSorter = function ithSorter(i) { | ||
| var col = cols[i]; | ||
| return function() { | ||
| var desc = col.defaultDescSort; | ||
| if (currentSort.index === i) { | ||
| desc = !currentSort.desc; | ||
| } | ||
| sortByIndex(i, desc); | ||
| removeSortIndicators(); | ||
| currentSort.index = i; | ||
| currentSort.desc = desc; | ||
| addSortIndicators(); | ||
| }; | ||
| }; | ||
| for (i = 0; i < cols.length; i += 1) { | ||
| if (cols[i].sortable) { | ||
| // add the click event handler on the th so users | ||
| // dont have to click on those tiny arrows | ||
| el = getNthColumn(i).querySelector('.sorter').parentElement; | ||
| if (el.addEventListener) { | ||
| el.addEventListener('click', ithSorter(i)); | ||
| } else { | ||
| el.attachEvent('onclick', ithSorter(i)); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| // adds sorting functionality to the UI | ||
| return function() { | ||
| if (!getTable()) { | ||
| return; | ||
| } | ||
| cols = loadColumns(); | ||
| loadData(); | ||
| addSortIndicators(); | ||
| enableUI(); | ||
| }; | ||
| })(); | ||
| window.addEventListener('load', addSorting); |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
| # Changelog | ||
| ## 0.4.5 | ||
| - Upgrade all dependencies to address security alerts. | ||
| ## 0.4.4 | ||
| - Release failed, doing it again. | ||
| ## 0.4.3 | ||
| - Upgrade all dependencies to address security alerts. | ||
| ## 0.4.2 | ||
| - Fix the release, publish the right directory. | ||
| ## 0.4.1 | ||
| - Fix the release, publish the right directory. | ||
| ## 0.4.0 | ||
| - Upgrade all dependencies. | ||
| ## 0.3.0 | ||
| API refinements on the way to 1.0 | ||
| #### Decomplecting machine context and state data | ||
| Often in machine logic, when reducing machine state or applying side effects, it is useful to have access to some props passed to the React component. For example, if you want to save an item as an effect of transitioning from `edit` to `saving` state, you might want to have acccess to this `item` to reference it's `id`. | ||
| Previously, to do this, your could pass this context as the second argument to the hook and it would get merged into machine's state. This however, meant there was no distinction between initial state, state managed by the reducers and the "static" props/context passed to the machine. | ||
| In this version, we separate context from the state data. The second argument to the `useMachine` hook is now always immutable context. React components get props, the machine reducers and effects get context. | ||
| Before: | ||
| ```js | ||
| // component was receiving context (mixed in with state) and event | ||
| const guard = (ctx, event) => {} | ||
| const reduce = (ctx, event) => {} | ||
| const effect = (ctx, event) => {} | ||
| ``` | ||
| After: | ||
| ```js | ||
| // component hooks now get context, state data and events | ||
| const guard = (ctx, data, event) => {} | ||
| const reduce = (ctx, data, event) => {} | ||
| const effect = (ctx, data, event) => {} | ||
| ``` | ||
| Introducing this separation optimised component re-rendering. For example, if component props change, and so the machine context changes, but the current machine state does not care about these changes, no extra component re-renders will get queued. Whereas before, because the context was stored as state, every change to the prop would cause double rendering of the entire subtree. | ||
| As a final note, in React, it is common to close over props inside the component function body, which works well when using `useReducer`, `useState`, or `useEffect` hooks. The `useMachine` hook is meant to help you extract some of the component business logic away from components into standalone reducer and effect functions as part of modelling the machine. Introducing `context` as the first class citizen, allows to create the component hook functions as pure functions that don't have to be kept inside component function body. | ||
| #### Reactive context | ||
| As part of separating the context from machine state data, we've made the machine context reactive. If new props are passed into the component, and `useMachine` hook is called with an udpated context, an event of tpye `assign` gets sent into the machine. This means that every immediate transition will get reevaluated with the new, updated context. And that optionally, a transition reacting to the `assign` event can be declared, for example, to transfer something from context into state, or similar. | ||
| Furthermore, this transition is done inside the function body (as opposed to an effect), ensuring best performance and avoiding incorrect/partial intermediate states when it comes to child components. The machine state will transition in response to the updated context before React will render the component children. | ||
| #### Updated useMachine return value | ||
| Before: | ||
| ```js | ||
| const [state, send] = useMachine(machine, context) | ||
| const { name, context } = state | ||
| ``` | ||
| After: | ||
| ```js | ||
| const { state, context, send, machine: m } = useMachine(machine, context) | ||
| const { name, data } = state | ||
| ``` | ||
| #### Removal of actions | ||
| Previously, `actions` were supposed to be used for effects that trigger immediately when machine is transitioning, where `effects` were queued up and were triggered in `useEffect`. This was a flawed / non Reacty idea. Mixing state transition and effects in one does not work well in React and is not compatible with the upcoming Concurrent Mode. | ||
| For example, say you wanted to trigger `props.onClose()` as an `action` in response to a user click event, and `onClose()` was causing a `setState` in a parent component. This would not work, because React does not allow updating state of other components in the middle of updating state of the current component. Instead, this should now be done as an `effect`. | ||
| Effects can now be added as part of `enter`, `transition` (any kind) or `exit`. They get queued up as part of transitioning and get executed all at once once in a `useEffect()`. | ||
| If multiple state updates get batched by React, all effects now get correctly collected and executed. | ||
| In Concurrent Mode, if the component is simultaneously transitioning into 2 divergent states, it's not longer a problem as the state transitions are independend (thanks to the use of `useReducer`) and only once React performs the render commits and executes effects, the right set of effects will get executed. | ||
| #### Replacing `deps` with `areEqual` | ||
| Now that the handling of `context` changed as described above, the `deps` option has been removed in favor of `areEqual`, which is now used to compare previous context with new context. | ||
| ## 0.2.0 | ||
| First release 🎉. Thanks to @tempname11 for the npm package name. |
+450
| let nextEffectId | ||
| const hookKeys = { | ||
| guard: 'guards', | ||
| reduce: 'reducers', | ||
| effect: 'effects', | ||
| } | ||
| const transitionHooks = ['assign', 'reduce', 'invoke', 'effect', 'guard'] | ||
| const enterHooks = ['assign', 'reduce', 'invoke', 'effect'] | ||
| const exitHooks = ['assign', 'reduce', 'effect'] | ||
| const mappedHooks = { | ||
| assign: ['reduce', assignToReduce], | ||
| invoke: ['effect', invokeToEffect], | ||
| } | ||
| const env = (process && process.env && process.env.NODE_ENV) || 'development' | ||
| function warn(msg) { | ||
| if (env !== 'production') { | ||
| console.warn(msg) | ||
| } | ||
| } | ||
| function arg(argument, type, error) { | ||
| if (type === 'string') { | ||
| if (typeof argument !== 'string') { | ||
| throw new Error(error) | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Parse the machine DSL into a machine object. | ||
| */ | ||
| export function createMachine(create) { | ||
| const machine = { initial: defaultInitial, nodes: {} } | ||
| if (create) { | ||
| // restart the auto incrementing id | ||
| nextEffectId = 1 | ||
| create({ | ||
| state: (name, ...opts) => { | ||
| machine.nodes[name] = createStateNode(name, ...opts) | ||
| }, | ||
| initial: (...opts) => { | ||
| machine.initial = createInitial(...opts) | ||
| }, | ||
| enter: createEnter, | ||
| exit: createExit, | ||
| transition: createTransition, | ||
| immediate: createImmediate, | ||
| internal: createInternal, | ||
| }) | ||
| } | ||
| validate(machine) | ||
| return machine | ||
| } | ||
| function validate(machine) { | ||
| for (const [, node] of Object.entries(machine.nodes)) { | ||
| for (const transition of node.immediates) { | ||
| if (!machine.nodes[transition.target]) { | ||
| throw new Error(`Invalid transition target '${transition.target}'`) | ||
| } | ||
| } | ||
| for (const transitions of Object.values(node.transitions)) { | ||
| for (const transition of transitions) { | ||
| if (!transition.internal && !machine.nodes[transition.target]) { | ||
| throw new Error(`Invalid transition target '${transition.target}'`) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Create a state node. | ||
| */ | ||
| function createStateNode(name, ...opts) { | ||
| const enter = [] | ||
| const exit = [] | ||
| const transitions = {} | ||
| const immediates = [] | ||
| for (const opt of opts) { | ||
| const { type, event } = opt | ||
| if (type === 'transition') { | ||
| if (!transitions[event]) transitions[event] = [] | ||
| transitions[event].push(opt) | ||
| } else if (type === 'immediate') { | ||
| immediates.push(opt) | ||
| } else if (type === 'enter') { | ||
| enter.push(opt) | ||
| } else if (type === 'exit') { | ||
| exit.push(opt) | ||
| } else { | ||
| throw new Error( | ||
| `State '${name}' should be passed one of enter(), exit(), transition(), immediate() or internal()`, | ||
| ) | ||
| } | ||
| } | ||
| return { | ||
| name, | ||
| enter, | ||
| exit, | ||
| transitions, | ||
| immediates, | ||
| } | ||
| } | ||
| function defaultInitial(opts) { | ||
| return { data: {} } | ||
| } | ||
| function createInitial(name, initialData) { | ||
| return (context) => { | ||
| const initial = {} | ||
| if (typeof name === 'string') { | ||
| initial.name = name | ||
| } else { | ||
| initialData = name | ||
| } | ||
| if (typeof initialData === 'function') { | ||
| initial.data = initialData(context) | ||
| } else { | ||
| initial.data = initialData | ||
| } | ||
| return initial | ||
| } | ||
| } | ||
| function createEnter(opts) { | ||
| return { type: 'enter', ...merge(opts, enterHooks) } | ||
| } | ||
| function createExit(opts) { | ||
| return { type: 'exit', ...merge(opts, exitHooks) } | ||
| } | ||
| function createTransition(event, target, opts) { | ||
| arg(event, 'string', 'First argument of the transition must be the name of the event') | ||
| arg(target, 'string', 'Second argument of the transition must be the name of the target state') | ||
| return { type: 'transition', event, target, ...merge(opts, transitionHooks) } | ||
| } | ||
| function createInternal(event, opts) { | ||
| arg(event, 'string', 'First argument of the internal transition must be the name of the event') | ||
| return { | ||
| type: 'transition', | ||
| event, | ||
| internal: true, | ||
| ...merge(opts, transitionHooks), | ||
| } | ||
| } | ||
| function createImmediate(target, opts) { | ||
| arg( | ||
| target, | ||
| 'string', | ||
| 'First argument of the immediate transition must be the name of the target state', | ||
| ) | ||
| return { type: 'immediate', target, ...merge(opts, transitionHooks) } | ||
| } | ||
| /** | ||
| * Transition the given machine from the provided state | ||
| * to the next state based on the event. Returns the tuple | ||
| * of the next state and any events to execute. In case effects | ||
| * did not change, return null for effects, to indicate that the | ||
| * active effects should continue running. | ||
| */ | ||
| export function transition(machine = {}, context = {}, state = {}, event, { assign } = {}) { | ||
| event = typeof event === 'string' ? { type: event } : event | ||
| // initial transition | ||
| if (!state.name && event && event.type === null) { | ||
| let { name, data } = machine.initial(context) | ||
| const curr = { ...state, data } | ||
| if (!name) { | ||
| const nodeNames = Object.keys(machine.nodes) | ||
| if (nodeNames.length > 0) { | ||
| name = nodeNames[0] | ||
| } | ||
| } | ||
| if (name) { | ||
| const initialTransition = createImmediate(name) | ||
| return applyTransition(machine, context, curr, event, initialTransition) | ||
| } | ||
| return [curr, []] | ||
| } | ||
| const currNode = machine.nodes[state.name] || {} | ||
| const transitions = currNode.transitions || {} | ||
| const candidates = transitions[event.type] || [] | ||
| for (const candidate of candidates) { | ||
| if (checkGuards(context, state, event, candidate)) { | ||
| return applyTransition(machine, context, state, event, candidate) | ||
| } | ||
| } | ||
| // did not find any explicit assign transition, construct a dynamic transition | ||
| // so that we re-trigger all of the immediate transitions every time the context | ||
| // changes | ||
| if (event.type === assign) { | ||
| return applyTransition(machine, context, state, event, createInternal(assign)) | ||
| } | ||
| return [state, []] | ||
| } | ||
| /** | ||
| * The logic of applying a transition to the machine. Exit active state nodes, | ||
| * apply transition hooks, enter target state nodes and collect any effects. Do this | ||
| * recursively until all immediate transitions settle. | ||
| */ | ||
| function applyTransition(machine, context, curr, event, transition, effects = []) { | ||
| const next = { ...curr } | ||
| const target = transition.internal ? curr.name : transition.target | ||
| const currNode = machine.nodes[curr.name] | ||
| const nextNode = machine.nodes[target] | ||
| if (currNode && !transition.internal) { | ||
| effects.push({ op: 'exit', name: currNode.name }) | ||
| for (const exit of currNode.exit) { | ||
| applyReducers(exit, context, next, event) | ||
| queueEffects(exit, effects, next, event, target) | ||
| } | ||
| } | ||
| next.name = target | ||
| applyReducers(transition, context, next, event) | ||
| queueEffects(transition, effects, next, event, target) | ||
| if (!transition.internal) { | ||
| for (const enter of nextNode.enter) { | ||
| applyReducers(enter, context, next, event) | ||
| queueEffects(enter, effects, next, event, target) | ||
| } | ||
| } | ||
| for (const candidate of nextNode.immediates) { | ||
| if (checkGuards(context, next, event, candidate)) { | ||
| return applyTransition(machine, context, next, event, candidate, effects) | ||
| } | ||
| } | ||
| if (Object.keys(nextNode.transitions).length === 0 && nextNode.immediates.length === 0) { | ||
| next.final = true | ||
| } | ||
| return [next, effects] | ||
| } | ||
| function checkGuards(context, state, event, transition) { | ||
| return !transition.guards.length || transition.guards.every((g) => g(context, state.data, event)) | ||
| } | ||
| function applyReducers({ reducers }, context, next, event) { | ||
| for (const reduce of reducers) { | ||
| next.data = reduce(context, next.data, event) | ||
| } | ||
| } | ||
| function queueEffects({ effects }, effectQueue, state, event, target) { | ||
| for (const effect of effects) { | ||
| effectQueue.push({ ...effect, op: 'effect', data: state.data, event, target }) | ||
| } | ||
| } | ||
| /** | ||
| * A common operation is to assign event payload | ||
| * to the context, this allows to do in several ways: | ||
| * true - assign the full event payload to context | ||
| * fn - assign the result of the fn(context, data) to context | ||
| * val - assign the constant provided value to context | ||
| */ | ||
| function assignToReduce(assign) { | ||
| return (context, data, event) => { | ||
| const { type, ...payload } = event | ||
| if (assign === true) { | ||
| return { ...data, ...payload } | ||
| } | ||
| if (typeof assign === 'function') { | ||
| return { ...data, ...assign(context, data, payload) } | ||
| } | ||
| return { ...data, ...assign } | ||
| } | ||
| } | ||
| /** | ||
| * Allow to pass each hook as a function, or a list of functions | ||
| * transition(..., { reduce: fn }) | ||
| * transition(..., { reduce: [fn1, fn2] }) | ||
| * Convert both of those into arrays, and also remap some of the | ||
| * hooks to different hooks (i.e. assign -> reduce) | ||
| */ | ||
| function merge(opts = {}, allowedHooks) { | ||
| const merged = {} | ||
| for (const hook of allowedHooks) { | ||
| add(hook) | ||
| } | ||
| function add(hook) { | ||
| let t = opts[hook] || [] | ||
| t = Array.isArray(t) ? t : [t] | ||
| if (mappedHooks[hook]) { | ||
| const [newName, transform] = mappedHooks[hook] | ||
| hook = newName | ||
| t = t.map(transform) | ||
| } | ||
| if (hook === 'effect') { | ||
| t = t.map((run) => ({ id: nextEffectId++, run })) | ||
| } | ||
| const key = hookKeys[hook] | ||
| merged[key] = merged[key] || [] | ||
| merged[key] = merged[key].concat(t) | ||
| } | ||
| return merged | ||
| } | ||
| /** | ||
| * Convert an async function into an effect | ||
| * that sends 'done' and 'error' events | ||
| */ | ||
| function invokeToEffect(fn) { | ||
| return (context, data, event, send) => { | ||
| let disposed = false | ||
| Promise.resolve(fn(context, data, event)) | ||
| .then((result) => { | ||
| if (!disposed) { | ||
| send({ type: 'done', result }) | ||
| } | ||
| }) | ||
| .catch((error) => { | ||
| if (!disposed) { | ||
| send({ type: 'error', error }) | ||
| } | ||
| }) | ||
| return () => { | ||
| disposed = true | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * createMachine and transition are pure, stateless functions. After | ||
| * transitioning the machine to the next state node, the caller must apply | ||
| * the new set of effects atop of the currently running ones. This stops | ||
| * any effects as necessary and starts any new ones. | ||
| */ | ||
| export function applyEffects(runningEffects = [], effectQueue, context, send) { | ||
| let nextRunningEffects = runningEffects | ||
| for (const effect of effectQueue) { | ||
| if (effect.op === 'exit') { | ||
| for (let i = runningEffects.length - 1; i >= 0; i--) { | ||
| const eff = runningEffects[i] | ||
| if (eff.disposed) { | ||
| continue | ||
| } | ||
| if (effect.name === eff.target) { | ||
| eff.dispose() | ||
| } | ||
| } | ||
| continue | ||
| } | ||
| for (let i = runningEffects.length - 1; i >= 0; i--) { | ||
| const eff = runningEffects[i] | ||
| if (eff.disposed) { | ||
| continue | ||
| } | ||
| if (effect.id === eff.id) { | ||
| eff.dispose() | ||
| } | ||
| } | ||
| const safeSend = (...args) => { | ||
| if (effect.disposed) { | ||
| warn( | ||
| [ | ||
| "Can't send events in an effect after it has been cleaned up.", | ||
| 'This is a no-op, but indicates a memory leak in your application.', | ||
| "To fix, cancel all subscriptions and asynchronous tasks in the effect's cleanup function.", | ||
| ].join(' '), | ||
| ) | ||
| } else { | ||
| return send(...args) | ||
| } | ||
| } | ||
| effect.executed = true | ||
| const dispose = effect.run(context, effect.data, effect.event, safeSend) | ||
| if (dispose && dispose.then) { | ||
| warn( | ||
| [ | ||
| 'Effect function must return a cleanup function or nothing.', | ||
| 'Use invoke instead of effect for async functions, or call the async function inside the synchronous effect function.', | ||
| ].join(' '), | ||
| ) | ||
| } | ||
| effect.dispose = () => { | ||
| effect.disposed = true | ||
| if (dispose) { | ||
| return dispose() | ||
| } | ||
| } | ||
| nextRunningEffects.push(effect) | ||
| } | ||
| nextRunningEffects = nextRunningEffects.filter((eff) => !eff.disposed) | ||
| return nextRunningEffects | ||
| } | ||
| export function cleanEffects(runningEffects = []) { | ||
| for (const effect of runningEffects) { | ||
| effect.dispose() | ||
| } | ||
| return [] | ||
| } |
+141
| import { useReducer, useEffect, useCallback, useMemo, useRef } from 'react' | ||
| import { createMachine, transition, applyEffects, cleanEffects } from './core.js' | ||
| function defaultAreEqual(prev, next) { | ||
| if (prev === next) { | ||
| return true | ||
| } | ||
| for (const i in prev) { | ||
| if (prev[i] !== next[i]) return false | ||
| } | ||
| for (const i in next) { | ||
| if (!(i in prev)) return false | ||
| } | ||
| return true | ||
| } | ||
| function initial({ machine, context }) { | ||
| const initialState = { name: null } | ||
| const initialEvent = { type: null } | ||
| let [state, effects] = transition(machine, context, initialState, initialEvent) | ||
| // minor optimisation to avoid a re-render if no effects have been queued | ||
| if (!effects.some((eff) => eff.op === 'effect')) { | ||
| effects = [] | ||
| } | ||
| const curr = { | ||
| state, | ||
| effects, | ||
| context, | ||
| } | ||
| return curr | ||
| } | ||
| function reduce(curr, action) { | ||
| if (action.type === 'send') { | ||
| const { machine, context, runningEffects, event, options } = action | ||
| const { assign, areEqual } = options | ||
| const [state, effects] = transition(machine, context, curr.state, event, { assign }) | ||
| if ( | ||
| state === curr.state || | ||
| (state.name === curr.state.name && state.data === curr.state.data && !effects.length) | ||
| ) { | ||
| if (areEqual(curr.context, context)) { | ||
| return curr | ||
| } else { | ||
| return { ...curr, context } | ||
| } | ||
| } | ||
| let nextEffects = [] | ||
| if (curr.effects) { | ||
| nextEffects = nextEffects.concat(curr.effects) | ||
| } | ||
| if (effects) { | ||
| nextEffects = nextEffects.concat(effects) | ||
| } | ||
| // minor optimisation to avoid a re-render if no effects are running | ||
| if (!runningEffects.current || !runningEffects.current.length) { | ||
| if (!nextEffects.some((eff) => eff.op === 'effect')) { | ||
| nextEffects = [] | ||
| } | ||
| } | ||
| return { ...curr, state, effects: nextEffects, context } | ||
| } | ||
| if (action.type === 'flushEffects') { | ||
| if (curr.effects.length) { | ||
| return { ...curr, effects: [] } | ||
| } else { | ||
| return curr | ||
| } | ||
| } | ||
| return curr | ||
| } | ||
| export function useMachine(create, context = {}, options = {}) { | ||
| const { assign = 'assign', areEqual = defaultAreEqual } = options | ||
| const contextRef = useRef(context) | ||
| const runningEffects = useRef() | ||
| const machine = useMemo(() => createMachine(create), [create]) | ||
| const [curr, dispatch] = useReducer(reduce, { machine, context }, initial) | ||
| const send = useCallback( | ||
| (event) => | ||
| dispatch({ | ||
| type: 'send', | ||
| context: contextRef.current, | ||
| machine, | ||
| runningEffects, | ||
| event, | ||
| options: { assign, areEqual }, | ||
| }), | ||
| [dispatch, machine, contextRef, assign, areEqual], | ||
| ) | ||
| // if context changed, we will transition | ||
| // the machine if necessary | ||
| if (assign && !areEqual(curr.context, context)) { | ||
| dispatch({ | ||
| type: 'send', | ||
| machine, | ||
| context, | ||
| runningEffects, | ||
| event: { type: assign }, | ||
| options: { assign, areEqual }, | ||
| }) | ||
| } | ||
| useEffect(() => { | ||
| contextRef.current = context | ||
| }) | ||
| useEffect(() => { | ||
| if (!curr.effects.length) return | ||
| runningEffects.current = applyEffects( | ||
| runningEffects.current, | ||
| curr.effects, | ||
| contextRef.current, | ||
| send, | ||
| ) | ||
| dispatch({ type: 'flushEffects' }) | ||
| }, [contextRef, dispatch, send, curr.effects]) | ||
| useEffect(() => { | ||
| return () => { | ||
| runningEffects.current = cleanEffects(runningEffects.current) | ||
| } | ||
| }, []) | ||
| return { state: curr.state, send, context, machine } | ||
| } |
| export { createMachine, transition, applyEffects, cleanEffects } from './core.js' | ||
| export { useMachine } from './hooks.js' |
| The MIT License (MIT) | ||
| Copyright (c) 2023 Karolis Narkevicius | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
| this software and associated documentation files (the "Software"), to deal in | ||
| the Software without restriction, including without limitation the rights to | ||
| use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | ||
| the Software, and to permit persons to whom the Software is furnished to do so, | ||
| subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | ||
| FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | ||
| COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | ||
| IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||
| CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
Sorry, the diff of this file is too big to display
| { | ||
| "name": "react-machine", | ||
| "version": "0.4.5", | ||
| "description": "A lightweight state machine for React applications", | ||
| "type": "module", | ||
| "exports": { | ||
| ".": "./index.js", | ||
| "./core": "./core.js", | ||
| "./service": "./service.js", | ||
| "./hooks": "./hooks.js" | ||
| }, | ||
| "types": "types", | ||
| "sideEffects": false, | ||
| "files": [ | ||
| "*" | ||
| ], | ||
| "scripts": { | ||
| "test": "healthier && prettier --check '**/*.{js,css,yml}' && ava && npm run package-check", | ||
| "format": "prettier --write '**/*.{js,css,yml}'", | ||
| "package-check": "npm run build && cd dist && package-check", | ||
| "coverage": "c8 --reporter=html ava", | ||
| "build": "node ./build.js", | ||
| "watch": "nodemon --ignore dist ./build.js", | ||
| "version": "npm run build", | ||
| "release": "np --contents dist", | ||
| "release-beta": "np --tag=beta --contents=dist", | ||
| "types": "tsc" | ||
| }, | ||
| "license": "MIT", | ||
| "author": "Karolis Narkevicius <hello@kn8.lt>", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/humaans/react-machine.git" | ||
| }, | ||
| "engines": { | ||
| "node": "*" | ||
| }, | ||
| "keywords": [ | ||
| "react", | ||
| "state", | ||
| "effects", | ||
| "react hook", | ||
| "state machine", | ||
| "finite state machine", | ||
| "finite automata" | ||
| ], | ||
| "peerDependencies": { | ||
| "react": "^17.0.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@babel/cli": "^7.23.0", | ||
| "@babel/core": "^7.23.2", | ||
| "@babel/plugin-transform-modules-commonjs": "^7.23.0", | ||
| "@babel/plugin-transform-react-jsx": "^7.22.15", | ||
| "@babel/register": "^7.22.15", | ||
| "@skypack/package-check": "^0.2.2", | ||
| "ava": "^5.3.1", | ||
| "c8": "^8.0.1", | ||
| "esm": "^3.2.25", | ||
| "execa": "^8.0.1", | ||
| "healthier": "^7.0.0", | ||
| "jsdom": "^22.1.0", | ||
| "prettier": "^3.0.3", | ||
| "react": "^18.2.0", | ||
| "react-dom": "^18.2.0", | ||
| "typescript": "^5.2.2" | ||
| }, | ||
| "ava": { | ||
| "files": [ | ||
| "test/test-*.js" | ||
| ], | ||
| "require": [ | ||
| "@babel/register" | ||
| ] | ||
| }, | ||
| "np": { | ||
| "releaseDraft": false | ||
| } | ||
| } |
+339
| <p align="center"> | ||
| <img width="360" src="https://user-images.githubusercontent.com/324440/102810325-6ce7ad80-43bb-11eb-9a72-9eead02fc71f.png" alt="react machine logo, a man with an open head with a state chart inside" title="react-machine"> | ||
| </p> | ||
| <h4 align="center">Finite state machine hook for React</h4> | ||
| <br /> | ||
| When `useState` or `useReducer` is not enough, `useMachine` hook can be used to express more complex component state and business logic. Machines are especially useful for handling asynchronous effects in your components, for example, saving a form. | ||
| Features include: | ||
| - a single `useMachine` hook for declaratively describing state machines | ||
| - define `states` and `transitions` between states | ||
| - apply `reducers` and queue `effects` when transitioning | ||
| - `immediate` transitions with `guards` | ||
| - `internal` transitions for updating state data or queuing effects | ||
| - first class support for promises with the `invoke` effects | ||
| - internally implemented using `useReducer` and `useEffect` hooks | ||
| - hierarchical and parallel states _(coming in V2 in 2021)_ | ||
| - semantics guided by the [SCXML](https://www.w3.org/TR/scxml/) spec _(coming in V2 in 2021)_ | ||
| ### Example | ||
| ```js | ||
| import React from 'react' | ||
| import { useMachine } from 'react-machine' | ||
| const isSuccess = ctx => ctx.item.status === 'success' | ||
| const isError = ctx => ctx.item.status === 'error' | ||
| const increment = (ctx, data) => ({ ...data, count: data.count + 1 }) | ||
| const retry = ctx => ctx.retry() | ||
| const machine = ({ state, transition, immediate, internal, enter }) => { | ||
| state( | ||
| 'loading', | ||
| immediate('counter', { guard: isSuccess }), | ||
| immediate('error', { guard: isError }) | ||
| ) | ||
| state('error', | ||
| transition('retry', 'loading', { effect: retry }) | ||
| ) | ||
| state( | ||
| 'counter', | ||
| enter({ assign: { count: 0 } }), | ||
| internal('increment', { reduce: increment }) | ||
| ) | ||
| } | ||
| export function Component({ item, retry }) { | ||
| const { state, send } = useMachine(machine, { item, retry }) | ||
| if (state.name === 'loading') return <div>Loading</div> | ||
| if (state.name === 'error') return <button onClick={() => send('retry')}>Retry</button> | ||
| return <button onClick={() => send('increment')}>{state.data.count}</button> | ||
| } | ||
| ``` | ||
| Also see: | ||
| * [Example: Form](examples/example-form.md) | ||
| ## Motivation | ||
| `react-machine` is very much inspired by [XState](https://xstate.js.org/) and [Robot](https://thisrobot.life/) - thank you for really great libraries 🙏 | ||
| ### Comparison to [Robot](https://thisrobot.life/) | ||
| Robot is a great Finite State Machine library with integrations into React, Preact, Svelte and more. It has a neat succint API that inspired the API of `react-machine` and boasts a lightweight implementation. Here are some differences: | ||
| - Robot requires every helper function to be imported individually. `react-machine` uses a similar DSL, but passes helpers as arguments to the machine creation function or takes them as options. This means you only ever need to import `useMachine`, which is more akin to how you'd use `useState` or `useReducer` for managing state. | ||
| - Robot has some support for nesting machines, but `react-machine` (_in the upcoming V2_) will support arbitrarily nested and parallel states, adhering more closely to the [SCXML](https://www.w3.org/TR/scxml/) spec. | ||
| - Robot does not support internal transitions, making it difficult to update machine context in the middle of an async function invocation. | ||
| - Robot does not clean up promise invocations in between transitions. | ||
| - Robot does not have `enter` and `exit` hooks, and does not allow custom effect implementations. | ||
| - Since `react-machine` is built with React in mind, it has a special ability to react to context (or prop) changes to drive machine transitions. | ||
| ### Comparison to [XState](https://xstate.js.org/): | ||
| XState is the most powerful modern state chart / state machine implementation for JavaScript. It's rich in features and supports React and Vue out of the box. Here are some differences: | ||
| - `react-machine` strives to create a smaller surface area, less features, less options, less packages. This could be seen as a good or a bad thing depending on your perspective and your requirements. For example, you will not find actors, machine inter messaging, delayed events or history states in `react-machine`. | ||
| - related to the point above, full compatibility / serialisation to SCXML is a non goal for `react-machine`, SCXML (and it's interpration algorithm in particular) is only used to guide the implementation of `react-machine`. | ||
| - `react-machine` uses a more functional machine declaration DSL that is closer to that found in Robot, whereas XState declares machines using a deeply nested object notation, this might well be a personal preference, give both a try, and also XState might gain new optional DSL adapters in the future. | ||
| - XState provides visualisation of it's state charts, a feature that could be added to `react-machine` in the future. | ||
| - Since `react-machine` is built with React in mind, it has a special ability to react to context (or prop) changes to drive machine transitions. | ||
| ### Conclusion | ||
| In summary, `react-machine` is an experiment in creating a lightweight and flexible solution for component level state management in React. The core machine logic is pure and stateless, which allows for delegating the state storage to `useReducer` and effect execution to `useEffect`, done so with concurrent mode compatibility in mind. | ||
| ## API | ||
| Machines are created using the API passed into machine description function, here's an exhaustive example showing all possible types of transitions and hooks: | ||
| ```js | ||
| const { state, send, context, machine } = useMachine(({ state, transition, immediate, internal, enter, exit }) => { | ||
| state(stateName, | ||
| enter({ reduce, assign, invoke, effect }), | ||
| transition(event, target, { guard, reduce, assign, effect }), | ||
| immediate(target, { guard, reduce, assign, effect }), | ||
| internal(event, { guard, reduce, assign, effect }), | ||
| exit({ reduce, assign, effect }), | ||
| ) | ||
| }, context, options) | ||
| ``` | ||
| ### Concepts | ||
| When you invoke the `useMachine` hook it returns machine state, send, context and the constructed machine itself. Typically you'll use state, send and you could use context in case you're passing it down to other components. | ||
| **State** - state consists of `name` and `data` (state data). Name is the name of the current state node the machine is in. State data is any data that has been computed using reducers (and assigners) as part of transitioning between states. | ||
| **Send** - this is the main way you will transition machine from one state to another. Send can take the event name, or an event object with key `type` and arbitrary other keys as payload. | ||
| **Context** - context is passed as the second argument to `useMachine` and is not to be confused with machine state or machine state data. Context is a little bit like React component props. It's some immutable data that gets passed into every machine hook (such as guard, reduce or effect). Context provides the best way for your machine to utilise component props in machine's business logic. If context changes (or more specifically, any of value of the context object changes), an event of type `assign` and no payload will be sent into the machine, which will evaluate any guarded immediated transitions and any transitions listening to `assign` event. This is very useful if you want your machine to change states or update it's state data based on changing context. It is also efficient, since context changes that are not relevant to the current machine state will not trigger any re-renders. | ||
| #### Hook | ||
| * [useMachine](#usemachinedescription-context-options) | ||
| #### State machine description | ||
| * [state](#statename-transitions) | ||
| * [transition](#transitionevent-target-options) | ||
| * [immediate](#immediatetarget-options) | ||
| * [internal](#internalevent-options) | ||
| * [enter](#enteroptions) | ||
| * [exit](#exitoptions) | ||
| * [initial](#initialoptions) | ||
| #### Transition hooks | ||
| * [guard](#guard) | ||
| * [reduce](#reduce) | ||
| * [assign](#assign) | ||
| * [invoke](#invoke) | ||
| * [effect](#effect) | ||
| ### `useMachine(description, context, options)` | ||
| Create and initialise the machine. | ||
| - `description` - the machine description function invoked with `state`, `transition`, `immediate`, `internal`, `enter`, `exit` and `initial` as arguments. | ||
| - `context` - the context to be assigned to the machine's state. Since it's common to pass props and other computed data via context, by default, whenever any of the values of the context change, the hook will send an event of type `assign` with the context object spread onto the event object, this event can be renamed or disabled in options. | ||
| - `options` - hook options | ||
| Available options: | ||
| - `assign` (default: `"assign"`) - the name of the event to be sent when context changes. Set this to `false` to disable sending the event altogether. | ||
| - `areEqual` (default: compare object values) - by default all context values are checked for changes on each render. Use this option to customize how equality is computed when comparing the previous context with the new context. | ||
| Returns `{ state, send, context, machine }`: | ||
| - `state` - current state of shape `{ name, data, final }` | ||
| - `send` - send an event, e.g. `send('save')` or `send({ type: 'save', item: 'x' })` | ||
| - `context` - the same value that was passed in as the context argument to the hook | ||
| - `machine` - a stateless machine description that is used internally to compute transitions | ||
| ```js | ||
| const myMachine = useCallback(({ state, transition, initial }) => { | ||
| initial({ x: 0 }) | ||
| state('a', transition('next', 'b')) | ||
| state('b', transition('next', 'c')) | ||
| state('c') | ||
| }, []) | ||
| const {state, send, context, machine } = useMachine(myMachine, { close: props.close }) | ||
| const { name, data, final } = state | ||
| ``` | ||
| ### `state(name, ...transitions)` | ||
| Declare a state. | ||
| - `name` - name of the state | ||
| - `transitions` - any number of available: `transition()`, `immediate()`, `internal()`, `enter()`, `exit()` | ||
| ```js | ||
| state('loading') | ||
| state('loading', transition('go', 'ready')) | ||
| state('loading', immediate('ready', { guard: ctx => ctx.loaded })) | ||
| ``` | ||
| ### `transition(event, target, options)` | ||
| Declare a transition between states. | ||
| - `event` - the name of the event that will trigger this transition | ||
| - `target` - the name of the target state | ||
| - `options` - in the shape of `{ reduce, assign, invoke, effect, guard }` | ||
| ```js | ||
| transition('save', 'saving') | ||
| transition('reset', 'edit', { reduce: (ctx, data) => ({ ...data, value: null }) }) | ||
| transition('close', 'closing', { effect: ctx => ctx.onClose() }) | ||
| ``` | ||
| ### `immediate(target, options)` | ||
| A special type of transition that is executed immediately upon entering (or re-entering a state with an internal transition). If no `guard` option is used, the transition will always immediately be applied and move the machine to a new state. If the `guard` option is used, the transition will only be applied if the `guard` condition passes. Note that, when immediate transitions take place, all of the intermediate transition hooks and intermediate state enter/exit hooks are triggered, however the effects (including `invoke`) are only executed for the final state, not any of the intermediate states. | ||
| - `target` - the name of the target state | ||
| - `options` - in the shape of `{ reduce, assign, invoke, effect, guard }` | ||
| ```js | ||
| immediate('ready') | ||
| immediate('ready', { guard: { guard: ctx => ctx.loaded } }) | ||
| ``` | ||
| ### `internal(event, options)` | ||
| A special type of transition that does not leave the state and does not trigger any enter/exit hooks. Useful for performing effects or updating context without leaving the state. Note: this transition does re-evaluate all immediate transitions of the state. | ||
| - `event` - the name of the event that will trigger this transition | ||
| - `options` - in the shape of `{ reduce, assign, invoke, effect, guard }` | ||
| ```js | ||
| internal('assign', { assign: true }) | ||
| internal('reset', { assign: { count: 0 } }) | ||
| ``` | ||
| ### `enter(options)` | ||
| Hooks to run when entering a state. | ||
| - `options` - in the shape of `{ reduce, assign, invoke, effect }` | ||
| ```js | ||
| enter({ effect: ctx => ctx.start() }) | ||
| enter({ invoke: (ctx, data) => ctx.fetch('/item/' + data.id) }) | ||
| enter({ assign: { count: 0 } }) | ||
| ``` | ||
| ### `exit(options)` | ||
| Hooks to run when leaving the state. | ||
| - `options` - in the shape of `{ reduce, assign, effect }` | ||
| ```js | ||
| exit({ effect: ctx => ctx.stop() }) | ||
| exit({ assign: { error: null } }) | ||
| ``` | ||
| ### `initial(...)` | ||
| A hook for setting initial state and/or initial state data. If initial state name is omitted, the first state node will be used as the initial state. The initial data can be a static object (or any kind of data structure) or a function that takes context and returns the initial data. | ||
| ```js | ||
| initial('loading') | ||
| initial('loading', { item: null }) | ||
| initial('loading', (ctx) => ({ item: ctx.item })) | ||
| initial('loading', (ctx) => { item: ctx.item }) | ||
| initial({ item: null }) | ||
| initial((ctx) => ({ item: ctx.item })) | ||
| initial((ctx) => { item: ctx.item }) | ||
| ``` | ||
| ### `guard` | ||
| If the guard condition fails, the transition is skipped when matching against the event and selection proceeds to the next transition. Commonly used with `immediate` transitions, but works with any type of transition. | ||
| ```js | ||
| { guard: (context, data, event) => context.status === 'success' } | ||
| ``` | ||
| ### `reduce` | ||
| Updated context based on current context and the incoming event. | ||
| ```js | ||
| { reduce: (context, data, event) => nextData } | ||
| { reduce: (context, data, { type, ...payload }) => nextData } | ||
| { reduce: [reduce1, reduce2] } | ||
| ``` | ||
| ### `assign` | ||
| Return a partial context update object, that will be immutably assigned to the current context. A commonly useful shortcut for assigning event paylods to the context. | ||
| ```js | ||
| { assign: (context, data, { type, ...payload }) => ({ ...data , ...payload }) } | ||
| { assign: true } // same as above | ||
| { assign: { some: 'value' } } | ||
| { assign: [assign1, assign2] } | ||
| ``` | ||
| ### `invoke` | ||
| A way to invoke async functions as part of entering a state. If the promise is fulfilled, an event of shape `{ type: 'done', result }` is sent, and if the promise rejects, an event of `{ type: 'error', error }` is sent. Note, if the machine exits the state while the promise is pending, the results will be ignored and no event will get sent. Note, internally, `invoke` is turned into an `effect`. | ||
| ```js | ||
| state('save', | ||
| enter({ invoke: async (context, data, event) => context.save() }), | ||
| transition('done', 'show', { assign: (ctx, data, event) => ({ item: event.result }) }), | ||
| transition('error', 'edit', { assign: (ctx, data, event) => ({ error: event.error }) }), | ||
| ) | ||
| ``` | ||
| ### `effect` | ||
| A way of handling side effects, async effects, subscriptions or activities. Once the state is entered, the effect gets started (in `useEffect` and only after finalising all of the immediate transitions) and can send any number of events. Note that `context` will be valid when initially running the effect, but will get stale afterwards. Also note that `send` will be ignored after the effect is cleaned up, and similarly `send` can not be used in the cleanup function of the effect. | ||
| ```js | ||
| const addPing = (ctx, data, event) => ({ pings: data.pings.concat(event.ping) }) | ||
| const listenToPings = (context, data, event, send) => { | ||
| const cancel = context.ponger.subscribe((ping) => { | ||
| send({ type: 'ping', ping }) | ||
| }) | ||
| return () => cancel() | ||
| } | ||
| state('save', | ||
| enter({ assign: { pings: [] }, effect: listenToPings }), | ||
| internal('ping', { assign: addPing }) | ||
| ) | ||
| ``` | ||
| ### Roadmap | ||
| #### V1 | ||
| - [x] decomplect machine context from state data (see [Changelog](CHANGELOG.md)) | ||
| - [x] remove the concept of actions in favor of effects (see [Changelog](CHANGELOG.md)) | ||
| - [ ] add an option to enable debug logging | ||
| - [ ] write proper TypeScript type definitions | ||
| #### V2 | ||
| - [ ] add hierarchical and parallel states | ||
| - [ ] introduce initial and final states | ||
| - [ ] change from state.name string to state.value object | ||
| - [ ] introduce matches() api | ||
| #### V3 | ||
| - [ ] add compatibility with XState visualiser, serialize into compatible JSON |
| import { createMachine, transition, applyEffects, cleanEffects } from './core.js' | ||
| export function createService(machineDescription, context = {}) { | ||
| const machine = createMachine(machineDescription) | ||
| // initial transition | ||
| const initialState = { name: null } | ||
| const initialEvent = { type: null } | ||
| const [state, effects] = transition(machine, context, initialState, initialEvent) | ||
| let cbs = [] | ||
| let running = true | ||
| const service = { | ||
| state, | ||
| send, | ||
| context, | ||
| subscribe, | ||
| machine, | ||
| stop, | ||
| runningEffects: [], | ||
| } | ||
| function runEffects(effects) { | ||
| if (!effects) return | ||
| let deferred = true | ||
| const sendQueue = [] | ||
| const queueSend = (...args) => { | ||
| if (deferred) { | ||
| sendQueue.push(args) | ||
| } else { | ||
| service.send(...args) | ||
| } | ||
| } | ||
| service.runningEffects = applyEffects(service.runningEffects, effects, context, queueSend) | ||
| deferred = false | ||
| for (const s of sendQueue) { | ||
| service.send(...s) | ||
| } | ||
| } | ||
| function stop() { | ||
| running = false | ||
| cbs = [] | ||
| service.runningEffects = cleanEffects(service.runningEffects) | ||
| } | ||
| function subscribe(fn) { | ||
| cbs.push(fn) | ||
| return () => { | ||
| cbs = cbs.filter((f) => f !== fn) | ||
| } | ||
| } | ||
| function send(event) { | ||
| if (!running) return | ||
| const [state, effects] = transition(service.machine, context, service.state, event) | ||
| service.state = state | ||
| if (effects) { | ||
| runEffects(effects) | ||
| } | ||
| for (const cb of cbs) { | ||
| cb(state) | ||
| } | ||
| } | ||
| runEffects(effects) | ||
| return service | ||
| } |
| export type EventType = string; | ||
| export type MetaObject = Record<string, any>; | ||
| export interface EventObject { | ||
| type: string; | ||
| } | ||
| export type Event<TEvent extends EventObject> = TEvent['type'] | TEvent; | ||
| export interface StateObject<TContext = any> { | ||
| name: string | ||
| context: TContext | ||
| final?: true | ||
| } | ||
| export interface StateSchema<TContext = any> { | ||
| meta?: any; | ||
| context?: Partial<TContext>; | ||
| states?: { | ||
| [key: string]: StateSchema<TContext>; | ||
| }; | ||
| } | ||
| export function useMachine<TContext, TStateSchema extends StateSchema, TEvent extends EventObject>( | ||
| description: MachineDescription<TContext, TStateSchema, TEvent>, | ||
| context?: TContext, | ||
| options?: MachineOptions | ||
| ): [state: StateObject<TContext>, send: SendFunction<TEvent>] | ||
| export interface MachineOptions { | ||
| assign: string | boolean, | ||
| deps: string[] | ||
| } | ||
| export type MachineDescription<C, S, E> = ({ state, transition, immediate, internal, enter, exit }: { | ||
| state: StateFunction | ||
| transition: TransitionFunction<C, E> | ||
| immediate: ImmediateFunction<C, E> | ||
| internal: TransitionFunction<C, E> | ||
| enter: EnterFunction<C, E> | ||
| exit: ExitFunction<C, E> | ||
| }) => any | ||
| export type StateFunction = ( | ||
| name: string, | ||
| ...opts: (Transition | Immediate | Internal | Enter | Exit)[] | ||
| ) => MachineState | ||
| /** | ||
| * A `transition` function is used to move from one state to another. | ||
| * | ||
| * @param event - This will give the name of the event that triggers this transition. | ||
| * @param target - The name of the destination state. | ||
| * @param opts - Transition hooks, one of reduce, assign, guard or action. | ||
| */ | ||
| export type TransitionFunction<C, E> = ( | ||
| event: string, | ||
| target: string, | ||
| opts: TransitionOptions<C, E> | ||
| ) => Transition | ||
| /** | ||
| * An `immediate` transition is triggered immediately upon entering the state. | ||
| * | ||
| * @param target - The name of the destination state. | ||
| * @param opts - Transition hooks, one of reduce, assign, guard or action. | ||
| */ | ||
| export type ImmediateFunction<C, E> = ( | ||
| target: string, | ||
| opts: TransitionOptions<C, E> | ||
| ) => Immediate | ||
| /** | ||
| * An `internal` transition will re-enter the same state, but without re-runing enter/exit hooks. | ||
| * | ||
| * @param event - This will give the name of the event that triggers this transition. | ||
| * @param opts - Transition hooks, one of reduce, assign, guard or action. | ||
| */ | ||
| export type InternalFunction<C, E> = ( | ||
| target: string, | ||
| opts: TransitionOptions<C, E> | ||
| ) => Internal | ||
| export type EnterFunction<C, E> = ( | ||
| opts: EnterOptions<C, E> | ||
| ) => Enter | ||
| export type ExitFunction<C, E> = ( | ||
| opts: ExitOptions<C, E> | ||
| ) => Exit | ||
| export interface MachineState { | ||
| name: string | ||
| transitions: Map<string, Transition[]> | ||
| immediates?: Map<string, Immediate[]> | ||
| enter: any[] | ||
| exit: any[] | ||
| final?: true | ||
| } | ||
| export interface Transition { | ||
| type: 'transition' | ||
| event: string | ||
| target: string | ||
| guards: any[] | ||
| reducers: any[] | ||
| } | ||
| export interface Immediate { | ||
| type: 'transition' | ||
| target: string | ||
| guards: any[] | ||
| reducers: any[] | ||
| } | ||
| export interface Internal { | ||
| type: 'transition' | ||
| internal: true | ||
| event: string | ||
| guards: any[] | ||
| reducers: any[] | ||
| } | ||
| export interface Enter { | ||
| type: 'enter' | ||
| reducers: any[] | ||
| effects: any[] | ||
| } | ||
| export interface Exit { | ||
| type: 'enter' | ||
| reducers: any[] | ||
| effects: any[] | ||
| } | ||
| export interface TransitionOptions<C, E> { | ||
| guard?: GuardFunction<C, E> | GuardFunction<C, E>[] | ||
| reduce?: ReduceFunction<C, E> | ReduceFunction<C, E>[] | ||
| assign?: Assign<C, E> | Assign<C, E>[] | ||
| action?: ActionFunction<C, E> | ActionFunction<C, E>[] | ||
| } | ||
| export interface EnterOptions<C, E> { | ||
| effect?: EffectFunction<C, E> | EffectFunction<C, E>[] | ||
| invoke?: InvokeFunction<C, E> | InvokeFunction<C, E>[] | ||
| reduce?: ReduceFunction<C, E> | ReduceFunction<C, E>[] | ||
| assign?: Assign<C, E> | Assign<C, E>[] | ||
| action?: ActionFunction<C, E> | ActionFunction<C, E>[] | ||
| } | ||
| export interface ExitOptions<C, E> { | ||
| reduce?: ReduceFunction<C, E> | ReduceFunction<C, E>[] | ||
| assign?: Assign<C, E> | Assign<C, E>[] | ||
| action?: ActionFunction<C, E> | ActionFunction<C, E>[] | ||
| } | ||
| export type ReduceFunction<C, E> = (context: C, event: E) => C | ||
| export type ActionFunction<C, E> = (context: C, event: E) => unknown | ||
| export type GuardFunction<C, E> = (context: C, event: E) => boolean | ||
| export type Assign<C, E> = true | Partial<C> | ((context: C, event: E) => Partial<C>) | ||
| export type InvokeFunction<C, E> = (context: C, event: E) => Promise<any> | ||
| export type EffectFunction<C, E> = (context: C, event: E) => CleanupFunction | void | ||
| export type CleanupFunction = () => void | ||
| export type SendFunction<TEvent extends EventObject> = (event: TEvent) => void |
| import { MachineDescription, useMachine } from 'react-machine' | ||
| interface LightStateSchema { | ||
| states: { | ||
| green: {}; | ||
| yellow: {}; | ||
| red: { | ||
| states: { | ||
| walk: {}; | ||
| wait: {}; | ||
| stop: {}; | ||
| }; | ||
| }; | ||
| }; | ||
| } | ||
| interface LightContext { | ||
| elapsed: number; | ||
| a: number | ||
| } | ||
| type LightEvent = | ||
| | { type: 'TIMER' } | ||
| | { type: 'POWER_OUTAGE' } | ||
| | { type: 'PED_COUNTDOWN', duration: number } | ||
| const machine: MachineDescription<LightContext, LightStateSchema, LightEvent> = ({ state, transition, enter }) => { | ||
| state('green', | ||
| transition('TIMER', 'yellow', { | ||
| reduce: (ctx, { type, ...data }) => { | ||
| if (type === 'PED_COUNTDOWN') { | ||
| return { ...ctx, a: data.duration } | ||
| } | ||
| return ctx | ||
| }, | ||
| guard: (ctx) => true | ||
| }), | ||
| enter({ effect: (ctx, event) => {}}) | ||
| ) | ||
| } | ||
| export function TypoComponent () { | ||
| const [state, send] = useMachine(machine) | ||
| } |
| { | ||
| "compilerOptions": { | ||
| "module": "commonjs", | ||
| "lib": ["es6"], | ||
| "target": "es6", | ||
| "noImplicitAny": true, | ||
| "noImplicitThis": true, | ||
| "strictNullChecks": true, | ||
| "strictFunctionTypes": true, | ||
| "noEmit": true, | ||
| "baseUrl": ".", | ||
| "paths": { "react-machine": ["."] } | ||
| } | ||
| } |
| ## Example: Form | ||
| ```js | ||
| import React from 'react' | ||
| import { useMutation } from 'some-api-client' | ||
| import { useMachine } from 'react-machine' | ||
| // guards | ||
| const isLoadingSuccess = (ctx) => ctx.item.status === 'success' | ||
| const isLoadingError = (ctx) => ctx.item.status === 'error' | ||
| // reducers | ||
| const assign = (key) => (ctx, data, event) => ({ [key]: event[key] }) | ||
| const clear = (key) => () => ({ [key]: null }) | ||
| // effects | ||
| const showLoadingError = (ctx) => ctx.showToast('Failed to load the item') | ||
| const save = (ctx, data, { values }) => ctx.save(ctx.item.id, values) | ||
| const remove = (ctx, data, { values }) => ctx.remove(ctx.item.id) | ||
| const close = (ctx) => ctx.onClose() | ||
| const machine = ({ state, transition, immediate, internal, enter }) => { | ||
| state( | ||
| 'loading', | ||
| immediate('edit', { guard: isLoadingSuccess }), | ||
| immediate('closing', { guard: isLoadingError, effect: showLoadingError }) | ||
| ) | ||
| state( | ||
| 'edit', | ||
| transition('save', 'saving', { assign: clear('error') }), | ||
| transition('remove', 'remove', { assign: clear('error') }), | ||
| transition('close', 'closing') | ||
| ) | ||
| state( | ||
| 'saving', | ||
| enter({ invoke: save }), | ||
| transition('done', 'closing'), | ||
| transition('error', 'edit', { assign: assign('error') }), | ||
| transition('close', 'closing') | ||
| ) | ||
| state( | ||
| 'remove', | ||
| transition('confirm', 'removing'), | ||
| transition('close', 'edit', { assign: clear('error') }) | ||
| ) | ||
| state( | ||
| 'removing', | ||
| enter({ invoke: remove }), | ||
| transition('done', 'closing'), | ||
| transition('error', 'remove', { reduce: assign('error') }) | ||
| ) | ||
| state('closing', enter({ effect: close })) | ||
| } | ||
| export function EditForm({ item, showToast, onClose }) { | ||
| const { save, remove } = useMutation('api/items') | ||
| const { state, send } = useMachine(machine, { item, showToast, save, remove, onClose }) | ||
| const shared = { state, send } | ||
| if (name === 'loading') return <Loading {...shared} /> | ||
| if (name === 'edit' || name === 'saving') return <Edit {...shared} /> | ||
| if (name === 'remove' || name === 'removing') return <Remove {...shared} /> | ||
| if (name === 'closing') return null | ||
| } | ||
| function Loading() {} | ||
| function Edit() {} | ||
| function Remove() {} | ||
| ``` |
+450
| let nextEffectId | ||
| const hookKeys = { | ||
| guard: 'guards', | ||
| reduce: 'reducers', | ||
| effect: 'effects', | ||
| } | ||
| const transitionHooks = ['assign', 'reduce', 'invoke', 'effect', 'guard'] | ||
| const enterHooks = ['assign', 'reduce', 'invoke', 'effect'] | ||
| const exitHooks = ['assign', 'reduce', 'effect'] | ||
| const mappedHooks = { | ||
| assign: ['reduce', assignToReduce], | ||
| invoke: ['effect', invokeToEffect], | ||
| } | ||
| const env = (process && process.env && process.env.NODE_ENV) || 'development' | ||
| function warn(msg) { | ||
| if (env !== 'production') { | ||
| console.warn(msg) | ||
| } | ||
| } | ||
| function arg(argument, type, error) { | ||
| if (type === 'string') { | ||
| if (typeof argument !== 'string') { | ||
| throw new Error(error) | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Parse the machine DSL into a machine object. | ||
| */ | ||
| export function createMachine(create) { | ||
| const machine = { initial: defaultInitial, nodes: {} } | ||
| if (create) { | ||
| // restart the auto incrementing id | ||
| nextEffectId = 1 | ||
| create({ | ||
| state: (name, ...opts) => { | ||
| machine.nodes[name] = createStateNode(name, ...opts) | ||
| }, | ||
| initial: (...opts) => { | ||
| machine.initial = createInitial(...opts) | ||
| }, | ||
| enter: createEnter, | ||
| exit: createExit, | ||
| transition: createTransition, | ||
| immediate: createImmediate, | ||
| internal: createInternal, | ||
| }) | ||
| } | ||
| validate(machine) | ||
| return machine | ||
| } | ||
| function validate(machine) { | ||
| for (const [, node] of Object.entries(machine.nodes)) { | ||
| for (const transition of node.immediates) { | ||
| if (!machine.nodes[transition.target]) { | ||
| throw new Error(`Invalid transition target '${transition.target}'`) | ||
| } | ||
| } | ||
| for (const transitions of Object.values(node.transitions)) { | ||
| for (const transition of transitions) { | ||
| if (!transition.internal && !machine.nodes[transition.target]) { | ||
| throw new Error(`Invalid transition target '${transition.target}'`) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Create a state node. | ||
| */ | ||
| function createStateNode(name, ...opts) { | ||
| const enter = [] | ||
| const exit = [] | ||
| const transitions = {} | ||
| const immediates = [] | ||
| for (const opt of opts) { | ||
| const { type, event } = opt | ||
| if (type === 'transition') { | ||
| if (!transitions[event]) transitions[event] = [] | ||
| transitions[event].push(opt) | ||
| } else if (type === 'immediate') { | ||
| immediates.push(opt) | ||
| } else if (type === 'enter') { | ||
| enter.push(opt) | ||
| } else if (type === 'exit') { | ||
| exit.push(opt) | ||
| } else { | ||
| throw new Error( | ||
| `State '${name}' should be passed one of enter(), exit(), transition(), immediate() or internal()`, | ||
| ) | ||
| } | ||
| } | ||
| return { | ||
| name, | ||
| enter, | ||
| exit, | ||
| transitions, | ||
| immediates, | ||
| } | ||
| } | ||
| function defaultInitial(opts) { | ||
| return { data: {} } | ||
| } | ||
| function createInitial(name, initialData) { | ||
| return (context) => { | ||
| const initial = {} | ||
| if (typeof name === 'string') { | ||
| initial.name = name | ||
| } else { | ||
| initialData = name | ||
| } | ||
| if (typeof initialData === 'function') { | ||
| initial.data = initialData(context) | ||
| } else { | ||
| initial.data = initialData | ||
| } | ||
| return initial | ||
| } | ||
| } | ||
| function createEnter(opts) { | ||
| return { type: 'enter', ...merge(opts, enterHooks) } | ||
| } | ||
| function createExit(opts) { | ||
| return { type: 'exit', ...merge(opts, exitHooks) } | ||
| } | ||
| function createTransition(event, target, opts) { | ||
| arg(event, 'string', 'First argument of the transition must be the name of the event') | ||
| arg(target, 'string', 'Second argument of the transition must be the name of the target state') | ||
| return { type: 'transition', event, target, ...merge(opts, transitionHooks) } | ||
| } | ||
| function createInternal(event, opts) { | ||
| arg(event, 'string', 'First argument of the internal transition must be the name of the event') | ||
| return { | ||
| type: 'transition', | ||
| event, | ||
| internal: true, | ||
| ...merge(opts, transitionHooks), | ||
| } | ||
| } | ||
| function createImmediate(target, opts) { | ||
| arg( | ||
| target, | ||
| 'string', | ||
| 'First argument of the immediate transition must be the name of the target state', | ||
| ) | ||
| return { type: 'immediate', target, ...merge(opts, transitionHooks) } | ||
| } | ||
| /** | ||
| * Transition the given machine from the provided state | ||
| * to the next state based on the event. Returns the tuple | ||
| * of the next state and any events to execute. In case effects | ||
| * did not change, return null for effects, to indicate that the | ||
| * active effects should continue running. | ||
| */ | ||
| export function transition(machine = {}, context = {}, state = {}, event, { assign } = {}) { | ||
| event = typeof event === 'string' ? { type: event } : event | ||
| // initial transition | ||
| if (!state.name && event && event.type === null) { | ||
| let { name, data } = machine.initial(context) | ||
| const curr = { ...state, data } | ||
| if (!name) { | ||
| const nodeNames = Object.keys(machine.nodes) | ||
| if (nodeNames.length > 0) { | ||
| name = nodeNames[0] | ||
| } | ||
| } | ||
| if (name) { | ||
| const initialTransition = createImmediate(name) | ||
| return applyTransition(machine, context, curr, event, initialTransition) | ||
| } | ||
| return [curr, []] | ||
| } | ||
| const currNode = machine.nodes[state.name] || {} | ||
| const transitions = currNode.transitions || {} | ||
| const candidates = transitions[event.type] || [] | ||
| for (const candidate of candidates) { | ||
| if (checkGuards(context, state, event, candidate)) { | ||
| return applyTransition(machine, context, state, event, candidate) | ||
| } | ||
| } | ||
| // did not find any explicit assign transition, construct a dynamic transition | ||
| // so that we re-trigger all of the immediate transitions every time the context | ||
| // changes | ||
| if (event.type === assign) { | ||
| return applyTransition(machine, context, state, event, createInternal(assign)) | ||
| } | ||
| return [state, []] | ||
| } | ||
| /** | ||
| * The logic of applying a transition to the machine. Exit active state nodes, | ||
| * apply transition hooks, enter target state nodes and collect any effects. Do this | ||
| * recursively until all immediate transitions settle. | ||
| */ | ||
| function applyTransition(machine, context, curr, event, transition, effects = []) { | ||
| const next = { ...curr } | ||
| const target = transition.internal ? curr.name : transition.target | ||
| const currNode = machine.nodes[curr.name] | ||
| const nextNode = machine.nodes[target] | ||
| if (currNode && !transition.internal) { | ||
| effects.push({ op: 'exit', name: currNode.name }) | ||
| for (const exit of currNode.exit) { | ||
| applyReducers(exit, context, next, event) | ||
| queueEffects(exit, effects, next, event, target) | ||
| } | ||
| } | ||
| next.name = target | ||
| applyReducers(transition, context, next, event) | ||
| queueEffects(transition, effects, next, event, target) | ||
| if (!transition.internal) { | ||
| for (const enter of nextNode.enter) { | ||
| applyReducers(enter, context, next, event) | ||
| queueEffects(enter, effects, next, event, target) | ||
| } | ||
| } | ||
| for (const candidate of nextNode.immediates) { | ||
| if (checkGuards(context, next, event, candidate)) { | ||
| return applyTransition(machine, context, next, event, candidate, effects) | ||
| } | ||
| } | ||
| if (Object.keys(nextNode.transitions).length === 0 && nextNode.immediates.length === 0) { | ||
| next.final = true | ||
| } | ||
| return [next, effects] | ||
| } | ||
| function checkGuards(context, state, event, transition) { | ||
| return !transition.guards.length || transition.guards.every((g) => g(context, state.data, event)) | ||
| } | ||
| function applyReducers({ reducers }, context, next, event) { | ||
| for (const reduce of reducers) { | ||
| next.data = reduce(context, next.data, event) | ||
| } | ||
| } | ||
| function queueEffects({ effects }, effectQueue, state, event, target) { | ||
| for (const effect of effects) { | ||
| effectQueue.push({ ...effect, op: 'effect', data: state.data, event, target }) | ||
| } | ||
| } | ||
| /** | ||
| * A common operation is to assign event payload | ||
| * to the context, this allows to do in several ways: | ||
| * true - assign the full event payload to context | ||
| * fn - assign the result of the fn(context, data) to context | ||
| * val - assign the constant provided value to context | ||
| */ | ||
| function assignToReduce(assign) { | ||
| return (context, data, event) => { | ||
| const { type, ...payload } = event | ||
| if (assign === true) { | ||
| return { ...data, ...payload } | ||
| } | ||
| if (typeof assign === 'function') { | ||
| return { ...data, ...assign(context, data, payload) } | ||
| } | ||
| return { ...data, ...assign } | ||
| } | ||
| } | ||
| /** | ||
| * Allow to pass each hook as a function, or a list of functions | ||
| * transition(..., { reduce: fn }) | ||
| * transition(..., { reduce: [fn1, fn2] }) | ||
| * Convert both of those into arrays, and also remap some of the | ||
| * hooks to different hooks (i.e. assign -> reduce) | ||
| */ | ||
| function merge(opts = {}, allowedHooks) { | ||
| const merged = {} | ||
| for (const hook of allowedHooks) { | ||
| add(hook) | ||
| } | ||
| function add(hook) { | ||
| let t = opts[hook] || [] | ||
| t = Array.isArray(t) ? t : [t] | ||
| if (mappedHooks[hook]) { | ||
| const [newName, transform] = mappedHooks[hook] | ||
| hook = newName | ||
| t = t.map(transform) | ||
| } | ||
| if (hook === 'effect') { | ||
| t = t.map((run) => ({ id: nextEffectId++, run })) | ||
| } | ||
| const key = hookKeys[hook] | ||
| merged[key] = merged[key] || [] | ||
| merged[key] = merged[key].concat(t) | ||
| } | ||
| return merged | ||
| } | ||
| /** | ||
| * Convert an async function into an effect | ||
| * that sends 'done' and 'error' events | ||
| */ | ||
| function invokeToEffect(fn) { | ||
| return (context, data, event, send) => { | ||
| let disposed = false | ||
| Promise.resolve(fn(context, data, event)) | ||
| .then((result) => { | ||
| if (!disposed) { | ||
| send({ type: 'done', result }) | ||
| } | ||
| }) | ||
| .catch((error) => { | ||
| if (!disposed) { | ||
| send({ type: 'error', error }) | ||
| } | ||
| }) | ||
| return () => { | ||
| disposed = true | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * createMachine and transition are pure, stateless functions. After | ||
| * transitioning the machine to the next state node, the caller must apply | ||
| * the new set of effects atop of the currently running ones. This stops | ||
| * any effects as necessary and starts any new ones. | ||
| */ | ||
| export function applyEffects(runningEffects = [], effectQueue, context, send) { | ||
| let nextRunningEffects = runningEffects | ||
| for (const effect of effectQueue) { | ||
| if (effect.op === 'exit') { | ||
| for (let i = runningEffects.length - 1; i >= 0; i--) { | ||
| const eff = runningEffects[i] | ||
| if (eff.disposed) { | ||
| continue | ||
| } | ||
| if (effect.name === eff.target) { | ||
| eff.dispose() | ||
| } | ||
| } | ||
| continue | ||
| } | ||
| for (let i = runningEffects.length - 1; i >= 0; i--) { | ||
| const eff = runningEffects[i] | ||
| if (eff.disposed) { | ||
| continue | ||
| } | ||
| if (effect.id === eff.id) { | ||
| eff.dispose() | ||
| } | ||
| } | ||
| const safeSend = (...args) => { | ||
| if (effect.disposed) { | ||
| warn( | ||
| [ | ||
| "Can't send events in an effect after it has been cleaned up.", | ||
| 'This is a no-op, but indicates a memory leak in your application.', | ||
| "To fix, cancel all subscriptions and asynchronous tasks in the effect's cleanup function.", | ||
| ].join(' '), | ||
| ) | ||
| } else { | ||
| return send(...args) | ||
| } | ||
| } | ||
| effect.executed = true | ||
| const dispose = effect.run(context, effect.data, effect.event, safeSend) | ||
| if (dispose && dispose.then) { | ||
| warn( | ||
| [ | ||
| 'Effect function must return a cleanup function or nothing.', | ||
| 'Use invoke instead of effect for async functions, or call the async function inside the synchronous effect function.', | ||
| ].join(' '), | ||
| ) | ||
| } | ||
| effect.dispose = () => { | ||
| effect.disposed = true | ||
| if (dispose) { | ||
| return dispose() | ||
| } | ||
| } | ||
| nextRunningEffects.push(effect) | ||
| } | ||
| nextRunningEffects = nextRunningEffects.filter((eff) => !eff.disposed) | ||
| return nextRunningEffects | ||
| } | ||
| export function cleanEffects(runningEffects = []) { | ||
| for (const effect of runningEffects) { | ||
| effect.dispose() | ||
| } | ||
| return [] | ||
| } |
+141
| import { useReducer, useEffect, useCallback, useMemo, useRef } from 'react' | ||
| import { createMachine, transition, applyEffects, cleanEffects } from './core.js' | ||
| function defaultAreEqual(prev, next) { | ||
| if (prev === next) { | ||
| return true | ||
| } | ||
| for (const i in prev) { | ||
| if (prev[i] !== next[i]) return false | ||
| } | ||
| for (const i in next) { | ||
| if (!(i in prev)) return false | ||
| } | ||
| return true | ||
| } | ||
| function initial({ machine, context }) { | ||
| const initialState = { name: null } | ||
| const initialEvent = { type: null } | ||
| let [state, effects] = transition(machine, context, initialState, initialEvent) | ||
| // minor optimisation to avoid a re-render if no effects have been queued | ||
| if (!effects.some((eff) => eff.op === 'effect')) { | ||
| effects = [] | ||
| } | ||
| const curr = { | ||
| state, | ||
| effects, | ||
| context, | ||
| } | ||
| return curr | ||
| } | ||
| function reduce(curr, action) { | ||
| if (action.type === 'send') { | ||
| const { machine, context, runningEffects, event, options } = action | ||
| const { assign, areEqual } = options | ||
| const [state, effects] = transition(machine, context, curr.state, event, { assign }) | ||
| if ( | ||
| state === curr.state || | ||
| (state.name === curr.state.name && state.data === curr.state.data && !effects.length) | ||
| ) { | ||
| if (areEqual(curr.context, context)) { | ||
| return curr | ||
| } else { | ||
| return { ...curr, context } | ||
| } | ||
| } | ||
| let nextEffects = [] | ||
| if (curr.effects) { | ||
| nextEffects = nextEffects.concat(curr.effects) | ||
| } | ||
| if (effects) { | ||
| nextEffects = nextEffects.concat(effects) | ||
| } | ||
| // minor optimisation to avoid a re-render if no effects are running | ||
| if (!runningEffects.current || !runningEffects.current.length) { | ||
| if (!nextEffects.some((eff) => eff.op === 'effect')) { | ||
| nextEffects = [] | ||
| } | ||
| } | ||
| return { ...curr, state, effects: nextEffects, context } | ||
| } | ||
| if (action.type === 'flushEffects') { | ||
| if (curr.effects.length) { | ||
| return { ...curr, effects: [] } | ||
| } else { | ||
| return curr | ||
| } | ||
| } | ||
| return curr | ||
| } | ||
| export function useMachine(create, context = {}, options = {}) { | ||
| const { assign = 'assign', areEqual = defaultAreEqual } = options | ||
| const contextRef = useRef(context) | ||
| const runningEffects = useRef() | ||
| const machine = useMemo(() => createMachine(create), [create]) | ||
| const [curr, dispatch] = useReducer(reduce, { machine, context }, initial) | ||
| const send = useCallback( | ||
| (event) => | ||
| dispatch({ | ||
| type: 'send', | ||
| context: contextRef.current, | ||
| machine, | ||
| runningEffects, | ||
| event, | ||
| options: { assign, areEqual }, | ||
| }), | ||
| [dispatch, machine, contextRef, assign, areEqual], | ||
| ) | ||
| // if context changed, we will transition | ||
| // the machine if necessary | ||
| if (assign && !areEqual(curr.context, context)) { | ||
| dispatch({ | ||
| type: 'send', | ||
| machine, | ||
| context, | ||
| runningEffects, | ||
| event: { type: assign }, | ||
| options: { assign, areEqual }, | ||
| }) | ||
| } | ||
| useEffect(() => { | ||
| contextRef.current = context | ||
| }) | ||
| useEffect(() => { | ||
| if (!curr.effects.length) return | ||
| runningEffects.current = applyEffects( | ||
| runningEffects.current, | ||
| curr.effects, | ||
| contextRef.current, | ||
| send, | ||
| ) | ||
| dispatch({ type: 'flushEffects' }) | ||
| }, [contextRef, dispatch, send, curr.effects]) | ||
| useEffect(() => { | ||
| return () => { | ||
| runningEffects.current = cleanEffects(runningEffects.current) | ||
| } | ||
| }, []) | ||
| return { state: curr.state, send, context, machine } | ||
| } |
| export { createMachine, transition, applyEffects, cleanEffects } from './core.js' | ||
| export { useMachine } from './hooks.js' |
| import { createMachine, transition, applyEffects, cleanEffects } from './core.js' | ||
| export function createService(machineDescription, context = {}) { | ||
| const machine = createMachine(machineDescription) | ||
| // initial transition | ||
| const initialState = { name: null } | ||
| const initialEvent = { type: null } | ||
| const [state, effects] = transition(machine, context, initialState, initialEvent) | ||
| let cbs = [] | ||
| let running = true | ||
| const service = { | ||
| state, | ||
| send, | ||
| context, | ||
| subscribe, | ||
| machine, | ||
| stop, | ||
| runningEffects: [], | ||
| } | ||
| function runEffects(effects) { | ||
| if (!effects) return | ||
| let deferred = true | ||
| const sendQueue = [] | ||
| const queueSend = (...args) => { | ||
| if (deferred) { | ||
| sendQueue.push(args) | ||
| } else { | ||
| service.send(...args) | ||
| } | ||
| } | ||
| service.runningEffects = applyEffects(service.runningEffects, effects, context, queueSend) | ||
| deferred = false | ||
| for (const s of sendQueue) { | ||
| service.send(...s) | ||
| } | ||
| } | ||
| function stop() { | ||
| running = false | ||
| cbs = [] | ||
| service.runningEffects = cleanEffects(service.runningEffects) | ||
| } | ||
| function subscribe(fn) { | ||
| cbs.push(fn) | ||
| return () => { | ||
| cbs = cbs.filter((f) => f !== fn) | ||
| } | ||
| } | ||
| function send(event) { | ||
| if (!running) return | ||
| const [state, effects] = transition(service.machine, context, service.state, event) | ||
| service.state = state | ||
| if (effects) { | ||
| runEffects(effects) | ||
| } | ||
| for (const cb of cbs) { | ||
| cb(state) | ||
| } | ||
| } | ||
| runEffects(effects) | ||
| return service | ||
| } |
+19
| .PHONY: build watch release release-beta test coverage | ||
| build: | ||
| @npm run build | ||
| watch: | ||
| @npm run watch | ||
| release: | ||
| @npm run release | ||
| release-beta: | ||
| @npm run release:beta | ||
| test: | ||
| @npm run test | ||
| coverage: | ||
| @npm run coverage |
| /* eslint-disable no-sequences */ | ||
| import test from 'ava' | ||
| import { createService as createMachine } from '../lib/service' | ||
| test('empty machine', (t) => { | ||
| const machine1 = createMachine() | ||
| t.deepEqual(machine1.state, { name: null, data: {} }) | ||
| const machine2 = createMachine(() => {}) | ||
| t.deepEqual(machine2.state, { name: null, data: {} }) | ||
| }) | ||
| test('initial transition', (t) => { | ||
| const machine1 = createMachine(({ initial, state, enter }) => { | ||
| initial('bar') | ||
| state('foo', enter({ assign: { foo: true } })) | ||
| state('bar', enter({ assign: { bar: true } })) | ||
| }) | ||
| t.deepEqual(machine1.state, { | ||
| name: 'bar', | ||
| data: { bar: true }, | ||
| final: true, | ||
| }) | ||
| const machine2 = createMachine(({ initial, state, enter }) => { | ||
| initial('bar', { baz: true }) | ||
| state('foo', enter({ assign: { foo: true } })) | ||
| state('bar', enter({ assign: { bar: true } })) | ||
| }) | ||
| t.deepEqual(machine2.state, { | ||
| name: 'bar', | ||
| data: { bar: true, baz: true }, | ||
| final: true, | ||
| }) | ||
| const machine3 = createMachine( | ||
| ({ initial, state, enter }) => { | ||
| initial('bar', (ctx) => ({ baz: ctx.baz })) | ||
| state('foo', enter({ assign: { foo: true } })) | ||
| state('bar', enter({ assign: { bar: true } })) | ||
| }, | ||
| { baz: 123 }, | ||
| ) | ||
| t.deepEqual(machine3.state, { | ||
| name: 'bar', | ||
| data: { bar: true, baz: 123 }, | ||
| final: true, | ||
| }) | ||
| const machine4 = createMachine( | ||
| ({ initial, state, enter }) => { | ||
| initial((ctx) => ({ baz: ctx.baz })) | ||
| state('foo', enter({ assign: { foo: true } })) | ||
| state('bar', enter({ assign: { bar: true } })) | ||
| }, | ||
| { baz: 123 }, | ||
| ) | ||
| t.deepEqual(machine4.state, { | ||
| name: 'foo', | ||
| data: { foo: true, baz: 123 }, | ||
| final: true, | ||
| }) | ||
| }) | ||
| test('transition guard', (t) => { | ||
| const machine = createMachine(({ state, transition }) => { | ||
| state('a', transition('go', 'b', { guard: (ctx, data, event) => event.ok })) | ||
| state('b') | ||
| }) | ||
| t.deepEqual(machine.state, { name: 'a', data: {} }) | ||
| machine.send('go') | ||
| t.deepEqual(machine.state, { name: 'a', data: {} }) | ||
| machine.send({ type: 'go', ok: true }) | ||
| t.deepEqual(machine.state, { name: 'b', data: {}, final: true }) | ||
| }) | ||
| test('transition reduce', (t) => { | ||
| const machine = createMachine( | ||
| ({ initial, state, transition }) => { | ||
| initial({ a: 1, b: 2 }) | ||
| state( | ||
| 'a', | ||
| transition('go', 'b', { | ||
| reduce: (ctx, data, event) => ({ a: data.a + 5, c: ctx.c, d: event.value }), | ||
| }), | ||
| ) | ||
| state('b') | ||
| }, | ||
| { c: 0 }, | ||
| ) | ||
| t.deepEqual(machine.state, { name: 'a', data: { a: 1, b: 2 } }) | ||
| machine.send({ type: 'go', value: 3 }) | ||
| t.deepEqual(machine.state, { name: 'b', data: { a: 6, c: 0, d: 3 }, final: true }) | ||
| }) | ||
| test('transition assign', (t) => { | ||
| const x = true | ||
| const y = { z: 'foo' } | ||
| const z = (ctx, data, payload) => { | ||
| for (const key of Object.keys(payload)) { | ||
| payload[key] = 'prefix' + payload[key] | ||
| } | ||
| return payload | ||
| } | ||
| const machine = createMachine(({ state, transition, immediate }) => { | ||
| state('a', transition('go', 'b', { assign: [x, y, z] })) | ||
| state('b') | ||
| }) | ||
| t.is(machine.state.name, 'a') | ||
| t.deepEqual(machine.state.data, {}) | ||
| machine.send({ type: 'go', x: 1, y: 2 }) | ||
| t.is(machine.state.name, 'b') | ||
| t.deepEqual(machine.state.data, { x: 'prefix1', y: 'prefix2', z: 'foo' }) | ||
| }) | ||
| test('transition effect', (t) => { | ||
| let effectCalledWith = null | ||
| const machine = createMachine(({ initial, state, transition }) => { | ||
| initial({ a: 1, b: 2 }) | ||
| state( | ||
| 'a', | ||
| transition('go', 'b', { | ||
| effect: (ctx, data, event) => { | ||
| effectCalledWith = event | ||
| return { unused: 123 } | ||
| }, | ||
| }), | ||
| ) | ||
| state('b') | ||
| }) | ||
| t.deepEqual(machine.state, { name: 'a', data: { a: 1, b: 2 } }) | ||
| machine.send({ type: 'go', value: 3 }) | ||
| t.deepEqual(machine.state, { name: 'b', data: { a: 1, b: 2 }, final: true }) | ||
| t.deepEqual(effectCalledWith, { type: 'go', value: 3 }) | ||
| }) | ||
| test('enter guard is ignored', (t) => { | ||
| const machine = createMachine(({ state, transition, enter }) => { | ||
| state('a', transition('go', 'b')) | ||
| state('b', enter({ guard: (ctx, event) => event.ok })) | ||
| }) | ||
| t.deepEqual(machine.state, { name: 'a', data: {} }) | ||
| machine.send({ type: 'go' }) | ||
| t.deepEqual(machine.state, { name: 'b', data: {}, final: true }) | ||
| }) | ||
| test('enter reduce', (t) => { | ||
| const machine = createMachine(({ initial, state, transition, enter }) => { | ||
| initial({ a: 1, b: 2 }) | ||
| state('a', transition('go', 'b')) | ||
| state('b', enter({ reduce: (ctx, data, event) => ({ a: data.a, c: event.value }) })) | ||
| }) | ||
| t.deepEqual(machine.state, { name: 'a', data: { a: 1, b: 2 } }) | ||
| machine.send({ type: 'go', value: 3 }) | ||
| t.deepEqual(machine.state, { name: 'b', data: { a: 1, c: 3 }, final: true }) | ||
| }) | ||
| test('enter assign', (t) => { | ||
| const x = true | ||
| const y = { z: 'foo' } | ||
| const z = (ctx, data, p) => { | ||
| for (const key of Object.keys(p)) { | ||
| p[key] = 'prefix' + p[key] | ||
| } | ||
| return p | ||
| } | ||
| const machine = createMachine(({ state, transition, enter }) => { | ||
| state('a', transition('go', 'b')) | ||
| state('b', enter({ assign: [x, y, z] })) | ||
| }) | ||
| t.is(machine.state.name, 'a') | ||
| t.deepEqual(machine.state.data, {}) | ||
| machine.send({ type: 'go', x: 1, y: 2 }) | ||
| t.is(machine.state.name, 'b') | ||
| t.deepEqual(machine.state.data, { x: 'prefix1', y: 'prefix2', z: 'foo' }) | ||
| }) | ||
| test('enter invoke', async (t) => { | ||
| const machine = createMachine(({ state, transition, immediate, enter }) => { | ||
| state('a', transition('go', 'b')) | ||
| state( | ||
| 'b', | ||
| enter({ invoke: save }), | ||
| transition('done', 'c', { assign: [true, { error: null }] }), | ||
| transition('error', 'a', { assign: true }), | ||
| ) | ||
| state('c') | ||
| }) | ||
| async function save(context, data) { | ||
| if (data.error) return { id: 1, name: 'hello' } | ||
| throw new Error('Fails the first time') | ||
| } | ||
| t.deepEqual(machine.state, { name: 'a', data: {} }) | ||
| machine.send('go') | ||
| t.deepEqual(machine.state, { name: 'b', data: {} }) | ||
| let max = 50 | ||
| while (!machine.state.data.error) { | ||
| await new Promise((resolve) => setTimeout(resolve), 4) | ||
| if (max-- < 0) throw new Error('Failed to settle') | ||
| } | ||
| t.is(machine.state.name, 'a') | ||
| t.is(machine.state.data.error.message, 'Fails the first time') | ||
| machine.send('go') | ||
| max = 50 | ||
| while (machine.state.data.error) { | ||
| await new Promise((resolve) => setTimeout(resolve), 4) | ||
| if (max-- < 0) throw new Error('Failed to settle') | ||
| } | ||
| t.is(machine.state.name, 'c') | ||
| t.deepEqual(machine.state, { | ||
| name: 'c', | ||
| data: { | ||
| result: { id: 1, name: 'hello' }, | ||
| error: null, | ||
| }, | ||
| final: true, | ||
| }) | ||
| }) | ||
| test('enter effect', async (t) => { | ||
| const machine = createMachine(({ state, transition, immediate, enter }) => { | ||
| state('a', transition('go', 'b')) | ||
| state( | ||
| 'b', | ||
| enter({ effect: save }), | ||
| transition('done', 'c', { assign: [true, { error: null }] }), | ||
| transition('error', 'a', { assign: true }), | ||
| ) | ||
| state('c') | ||
| }) | ||
| function save(context, data, event, send) { | ||
| Promise.resolve('defer').then(() => { | ||
| if (data.error) return send({ type: 'done', result: { id: 1, name: 'hello' } }) | ||
| send({ type: 'error', error: new Error('Fails the first time') }) | ||
| }) | ||
| } | ||
| t.deepEqual(machine.state, { name: 'a', data: {} }) | ||
| machine.send('go') | ||
| t.deepEqual(machine.state, { name: 'b', data: {} }) | ||
| while (!machine.state.data.error) { | ||
| await new Promise((resolve) => setTimeout(resolve), 4) | ||
| } | ||
| t.is(machine.state.name, 'a') | ||
| t.is(machine.state.data.error.message, 'Fails the first time') | ||
| machine.send('go') | ||
| while (machine.state.data.error) { | ||
| await new Promise((resolve) => setTimeout(resolve), 4) | ||
| } | ||
| t.is(machine.state.name, 'c') | ||
| t.deepEqual(machine.state, { | ||
| name: 'c', | ||
| data: { | ||
| result: { id: 1, name: 'hello' }, | ||
| error: null, | ||
| }, | ||
| final: true, | ||
| }) | ||
| }) | ||
| test('exit guard is ignored', (t) => { | ||
| const machine = createMachine(({ state, transition, exit }) => { | ||
| state('a', transition('go', 'b'), exit({ guard: (ctx, event) => event.ok })) | ||
| state('b') | ||
| }) | ||
| t.deepEqual(machine.state, { name: 'a', data: {} }) | ||
| machine.send({ type: 'go' }) | ||
| t.deepEqual(machine.state, { name: 'b', data: {}, final: true }) | ||
| }) | ||
| test('exit reduce', (t) => { | ||
| const machine = createMachine(({ initial, state, transition, exit }) => { | ||
| initial({ a: 1, b: 2 }) | ||
| state( | ||
| 'a', | ||
| transition('go', 'b'), | ||
| exit({ reduce: (ctx, data, event) => ({ a: data.a, c: event.value }) }), | ||
| ) | ||
| state('b') | ||
| }) | ||
| t.deepEqual(machine.state, { name: 'a', data: { a: 1, b: 2 } }) | ||
| machine.send({ type: 'go', value: 3 }) | ||
| t.deepEqual(machine.state, { name: 'b', data: { a: 1, c: 3 }, final: true }) | ||
| }) | ||
| test('exit assign', (t) => { | ||
| const x = true | ||
| const y = { z: 'foo' } | ||
| const z = (ctx, data, p) => { | ||
| for (const key of Object.keys(p)) { | ||
| p[key] = 'prefix' + p[key] | ||
| } | ||
| return p | ||
| } | ||
| const machine = createMachine(({ state, transition, exit }) => { | ||
| state('a', transition('go', 'b'), exit({ assign: [x, y, z] })) | ||
| state('b') | ||
| }) | ||
| t.is(machine.state.name, 'a') | ||
| t.deepEqual(machine.state.data, {}) | ||
| machine.send({ type: 'go', x: 1, y: 2 }) | ||
| t.is(machine.state.name, 'b') | ||
| t.deepEqual(machine.state.data, { x: 'prefix1', y: 'prefix2', z: 'foo' }) | ||
| }) | ||
| test('simple machine', (t) => { | ||
| const machine = createMachine(({ state, transition }) => { | ||
| state('initial', transition('go', 'final')) | ||
| state('final') | ||
| }) | ||
| // machine structure | ||
| t.deepEqual(machine.machine.nodes, { | ||
| initial: { | ||
| name: 'initial', | ||
| enter: [], | ||
| exit: [], | ||
| transitions: { | ||
| go: [ | ||
| { | ||
| type: 'transition', | ||
| event: 'go', | ||
| target: 'final', | ||
| guards: [], | ||
| reducers: [], | ||
| effects: [], | ||
| }, | ||
| ], | ||
| }, | ||
| immediates: [], | ||
| }, | ||
| final: { | ||
| name: 'final', | ||
| enter: [], | ||
| exit: [], | ||
| transitions: {}, | ||
| immediates: [], | ||
| }, | ||
| }) | ||
| t.deepEqual(machine.runningEffects, []) | ||
| t.is(typeof machine.subscribe, 'function') | ||
| t.is(typeof machine.send, 'function') | ||
| t.is(typeof machine.stop, 'function') | ||
| t.deepEqual(Object.keys(machine), [ | ||
| 'state', | ||
| 'send', | ||
| 'context', | ||
| 'subscribe', | ||
| 'machine', | ||
| 'stop', | ||
| 'runningEffects', | ||
| ]) | ||
| t.deepEqual(machine.state, { | ||
| name: 'initial', | ||
| data: {}, | ||
| }) | ||
| t.is(machine.state.name, 'initial') | ||
| t.is(machine.state.final, undefined) | ||
| machine.send('go') | ||
| t.is(machine.state.name, 'final') | ||
| t.is(machine.state.final, true) | ||
| }) | ||
| test('complex machine', (t) => { | ||
| const set = (key, val) => (ctx, data) => ((data[key] = val), data) | ||
| const assign = (ctx, data, { type, ...payload }) => ({ ...data, ...payload }) | ||
| const machine = createMachine(({ state, transition, internal, immediate, enter, exit }) => { | ||
| state('initial', transition('goToSecond', 'second', { reduce: assign })) | ||
| state('second', immediate('third')) | ||
| state( | ||
| 'third', | ||
| immediate('final', { guard: () => false }), | ||
| transition('goToFourth', 'fourth', { reduce: assign }), | ||
| ) | ||
| state( | ||
| 'fourth', | ||
| enter({ effect: ping }), | ||
| internal('assign', { reduce: assign }), | ||
| transition('goToFinal', 'final', { reduce: assign }), | ||
| exit({ assign: { fourthExited: 'pong' } }), | ||
| ) | ||
| state('final', enter({ reduce: set('y', 2) })) | ||
| }) | ||
| function ping(context, data, event, send) { | ||
| t.deepEqual(data, { x: 2 }) | ||
| t.deepEqual(event, { type: 'goToFourth', x: 2 }) | ||
| send({ type: 'assign', fourthEntered: 'ping' }) | ||
| return () => { | ||
| // check that we have stale context | ||
| t.deepEqual(data, { x: 2 }) | ||
| const stubbed = stub(console, 'warn') | ||
| send('ping') | ||
| stubbed.restore() | ||
| t.is(stubbed.calls.length, 1) | ||
| t.deepEqual(stubbed.calls[0], [ | ||
| [ | ||
| "Can't send events in an effect after it has been cleaned up.", | ||
| 'This is a no-op, but indicates a memory leak in your application.', | ||
| "To fix, cancel all subscriptions and asynchronous tasks in the effect's cleanup function.", | ||
| ].join(' '), | ||
| ]) | ||
| } | ||
| } | ||
| t.is(machine.state.name, 'initial') | ||
| t.deepEqual(machine.state.data, {}) | ||
| machine.send({ type: 'goToSecond', x: 1 }) | ||
| t.is(machine.state.name, 'third') | ||
| t.deepEqual(machine.state.data, { x: 1 }) | ||
| machine.send({ type: 'goToFourth', x: 2 }) | ||
| t.is(machine.state.name, 'fourth') | ||
| t.deepEqual(machine.state.data, { x: 2, fourthEntered: 'ping' }) | ||
| machine.send({ type: 'goToFinal', x: 3 }) | ||
| t.is(machine.state.name, 'final') | ||
| t.deepEqual(machine.state.data, { x: 3, y: 2, fourthEntered: 'ping', fourthExited: 'pong' }) | ||
| t.is(machine.state.final, true) | ||
| }) | ||
| test('throw an error if state is passed an incorrect argument', (t) => { | ||
| const err = t.throws(() => | ||
| createMachine(({ state, enter }) => { | ||
| state('foo', { assign: { b: 2 } }) | ||
| }), | ||
| ) | ||
| t.is( | ||
| err.message, | ||
| "State 'foo' should be passed one of enter(), exit(), transition(), immediate() or internal()", | ||
| ) | ||
| }) | ||
| test('throw an error if transition specifies an invalid target', (t) => { | ||
| const err1 = t.throws(() => | ||
| createMachine(({ state, transition }) => { | ||
| state('foo', transition('next', 'bar')) | ||
| }), | ||
| ) | ||
| t.is(err1.message, "Invalid transition target 'bar'") | ||
| const err2 = t.throws(() => | ||
| createMachine(({ state, immediate }) => { | ||
| state('foo', immediate('bar')) | ||
| }), | ||
| ) | ||
| t.is(err2.message, "Invalid transition target 'bar'") | ||
| }) | ||
| test('throw an error if transition arguments are incorrect', (t) => { | ||
| const err1 = t.throws(() => | ||
| createMachine(({ state, transition }) => { | ||
| state('foo', transition(1)) | ||
| }), | ||
| ) | ||
| t.is(err1.message, 'First argument of the transition must be the name of the event') | ||
| const err2 = t.throws(() => | ||
| createMachine(({ state, transition }) => { | ||
| state('foo', transition('1', 2)) | ||
| }), | ||
| ) | ||
| t.is(err2.message, 'Second argument of the transition must be the name of the target state') | ||
| }) | ||
| test('service subscriptions', (t) => { | ||
| let active = null | ||
| const machine = createMachine(({ state, enter, internal }) => { | ||
| state( | ||
| 'one', | ||
| enter({ | ||
| effect: () => { | ||
| active = true | ||
| return () => { | ||
| active = false | ||
| } | ||
| }, | ||
| }), | ||
| internal('assign', { assign: true }), | ||
| ) | ||
| }) | ||
| const subs = [] | ||
| const dispose = machine.subscribe((curr) => { | ||
| subs.push({ sub: 'sub1', data: curr.data }) | ||
| }) | ||
| machine.subscribe((curr) => { | ||
| subs.push({ sub: 'sub2', data: curr.data }) | ||
| }) | ||
| t.is(active, true) | ||
| t.deepEqual(machine.state, { name: 'one', data: {} }) | ||
| machine.send({ type: 'assign', a: 1 }) | ||
| t.deepEqual(machine.state, { name: 'one', data: { a: 1 } }) | ||
| t.deepEqual(subs, [ | ||
| { sub: 'sub1', data: { a: 1 } }, | ||
| { sub: 'sub2', data: { a: 1 } }, | ||
| ]) | ||
| dispose() | ||
| machine.send({ type: 'assign', a: 2 }) | ||
| t.deepEqual(machine.state, { name: 'one', data: { a: 2 } }) | ||
| t.deepEqual(subs, [ | ||
| { sub: 'sub1', data: { a: 1 } }, | ||
| { sub: 'sub2', data: { a: 1 } }, | ||
| { sub: 'sub2', data: { a: 2 } }, | ||
| ]) | ||
| machine.stop() | ||
| machine.send({ type: 'assign', a: 3 }) | ||
| t.deepEqual(machine.state, { name: 'one', data: { a: 2 } }) | ||
| }) | ||
| function stub(obj, fn) { | ||
| const original = obj[fn] | ||
| const calls = [] | ||
| obj[fn] = (...args) => { | ||
| calls.push(args) | ||
| } | ||
| return { | ||
| restore: () => { | ||
| obj[fn] = original | ||
| }, | ||
| calls, | ||
| } | ||
| } |
| import test from 'ava' | ||
| import React from 'react' | ||
| import { createRoot } from 'react-dom/client' | ||
| import { act } from 'react-dom/test-utils' | ||
| import { JSDOM } from 'jsdom' | ||
| import { useMachine } from '../lib/index' | ||
| global.IS_REACT_ACT_ENVIRONMENT = true | ||
| function dom() { | ||
| const dom = new JSDOM('<!doctype html><div id="root"></div>') | ||
| global.window = dom.window | ||
| const domNode = dom.window.document.getElementById('root') | ||
| const root = createRoot(domNode) | ||
| function render(el) { | ||
| act(() => { | ||
| root.render(el) | ||
| }) | ||
| } | ||
| function click(el) { | ||
| act(() => { | ||
| el.dispatchEvent( | ||
| new dom.window.MouseEvent('click', { | ||
| view: dom.window, | ||
| bubbles: true, | ||
| cancelable: true, | ||
| }), | ||
| ) | ||
| }) | ||
| } | ||
| function $(sel) { | ||
| return dom.window.document.querySelector(sel) | ||
| } | ||
| return { root, render, click, $ } | ||
| } | ||
| test.serial('usage', async (t) => { | ||
| const { render, click, $ } = dom() | ||
| const machine = ({ initial, state, transition, immediate }) => { | ||
| initial({ a: 1 }) | ||
| state('initial', immediate('counter')) | ||
| state( | ||
| 'counter', | ||
| transition('increment', 'counter', { reduce: (ctx, data) => ({ ...data, a: data.a + 1 }) }), | ||
| immediate('final', { guard: (ctx, data) => data.a >= 4 }), | ||
| ) | ||
| state('final') | ||
| } | ||
| function App() { | ||
| const { state, send } = useMachine(machine, {}) | ||
| return ( | ||
| <> | ||
| <div id='state'>State: {state.name}</div> | ||
| <div id='count'>Count: {state.data.a}</div> | ||
| <button id='increment' onClick={() => send('increment')} /> | ||
| </> | ||
| ) | ||
| } | ||
| render(<App />) | ||
| t.is($('#state').innerHTML, 'State: counter') | ||
| t.is($('#count').innerHTML, 'Count: 1') | ||
| click($('#increment')) | ||
| click($('#increment')) | ||
| t.is($('#state').innerHTML, 'State: counter') | ||
| t.is($('#count').innerHTML, 'Count: 3') | ||
| click($('#increment')) | ||
| click($('#increment')) | ||
| t.is($('#state').innerHTML, 'State: final') | ||
| t.is($('#count').innerHTML, 'Count: 4') | ||
| }) | ||
| test.serial('changing props automatically send assign event', (t) => { | ||
| const { render, click, $ } = dom() | ||
| const machine = ({ initial, state, transition, immediate, internal }) => { | ||
| initial({ thang: 'x' }) | ||
| state('initial', immediate('counter')) | ||
| state( | ||
| 'counter', | ||
| internal('assign', { assign: (ctx, data) => ({ ...data, thang: ctx.thing + ctx.thing }) }), | ||
| internal('changeThang', { assign: true }), | ||
| ) | ||
| } | ||
| function App({ thing }) { | ||
| const { state, context, send } = useMachine(machine, { thing }) | ||
| return ( | ||
| <> | ||
| <div id='state'>Name: {state.name}</div> | ||
| <div id='props-thing'>Props thing: {thing}</div> | ||
| <div id='context-thing'>Context thing: {context.thing}</div> | ||
| <div id='derived-thang'>Derived thang: {state.data.thang}</div> | ||
| <button id='changeThing' onClick={() => send({ type: 'changeThang', thang: 'c', x: 1 })} /> | ||
| </> | ||
| ) | ||
| } | ||
| render(<App thing='a' />) | ||
| t.is($('#state').innerHTML, 'Name: counter') | ||
| t.is($('#props-thing').innerHTML, 'Props thing: a') | ||
| t.is($('#context-thing').innerHTML, 'Context thing: a') | ||
| t.is($('#derived-thang').innerHTML, 'Derived thang: x') | ||
| render(<App thing='b' />) | ||
| t.is($('#state').innerHTML, 'Name: counter') | ||
| t.is($('#props-thing').innerHTML, 'Props thing: b') | ||
| t.is($('#context-thing').innerHTML, 'Context thing: b') | ||
| t.is($('#derived-thang').innerHTML, 'Derived thang: bb') | ||
| click($('#changeThing')) | ||
| t.is($('#state').innerHTML, 'Name: counter') | ||
| t.is($('#props-thing').innerHTML, 'Props thing: b') | ||
| t.is($('#context-thing').innerHTML, 'Context thing: b') | ||
| t.is($('#derived-thang').innerHTML, 'Derived thang: c') | ||
| }) | ||
| test.serial('effects', (t) => { | ||
| const { render, $ } = dom() | ||
| const eff = [] | ||
| const machine = ({ state, enter }) => { | ||
| state( | ||
| 'counter', | ||
| enter({ | ||
| effect: (context, data, event, send) => { | ||
| eff.push('started') | ||
| return () => { | ||
| eff.push('finished') | ||
| } | ||
| }, | ||
| }), | ||
| ) | ||
| } | ||
| function App({ thing }) { | ||
| const { state } = useMachine(machine, { thing }) | ||
| return ( | ||
| <> | ||
| <div id='state'>State: {state.name}</div> | ||
| <Child /> | ||
| </> | ||
| ) | ||
| } | ||
| function Child() { | ||
| return <div /> | ||
| } | ||
| render(<App thing='a' />) | ||
| t.is($('#state').innerHTML, 'State: counter') | ||
| t.deepEqual(eff, ['started']) | ||
| render(<App thing='a' />) | ||
| t.is($('#state').innerHTML, 'State: counter') | ||
| t.deepEqual(eff, ['started']) | ||
| render(null) | ||
| t.is($('#root').innerHTML, '') | ||
| t.deepEqual(eff, ['started', 'finished']) | ||
| }) | ||
| test.serial('internal transition effects', (t) => { | ||
| const { render, click, $ } = dom() | ||
| const eff = [] | ||
| function enterEffect(context, data, event, send) { | ||
| eff.push('enter effect started') | ||
| return () => { | ||
| eff.push('enter effect finished') | ||
| } | ||
| } | ||
| function internalEffect(context, data, event, send) { | ||
| eff.push('internal effect started') | ||
| return () => { | ||
| eff.push('internal effect finished') | ||
| } | ||
| } | ||
| const machine = ({ state, enter, internal }) => { | ||
| state( | ||
| 'counter', | ||
| enter({ effect: enterEffect }), | ||
| internal('increment', { effect: internalEffect }), | ||
| ) | ||
| } | ||
| function App() { | ||
| const { state, send } = useMachine(machine) | ||
| return ( | ||
| <> | ||
| <div id='state'>State: {state.name}</div> | ||
| <button id='increment' onClick={() => send('increment')} /> | ||
| </> | ||
| ) | ||
| } | ||
| render(<App />) | ||
| t.deepEqual(eff, ['enter effect started']) | ||
| click($('#increment')) | ||
| render(<App />) | ||
| t.deepEqual(eff, ['enter effect started', 'internal effect started']) | ||
| click($('#increment')) | ||
| render(<App />) | ||
| t.deepEqual(eff, [ | ||
| 'enter effect started', | ||
| 'internal effect started', | ||
| 'internal effect finished', | ||
| 'internal effect started', | ||
| ]) | ||
| }) | ||
| test.serial('all types of effects with cleanup', (t) => { | ||
| const { render, click, $ } = dom() | ||
| let eff = [] | ||
| const effect = (label) => (context, data, event, send) => { | ||
| eff.push(`${label} started`) | ||
| return () => { | ||
| eff.push(`${label} stopped`) | ||
| } | ||
| } | ||
| const machine = ({ state, enter, exit, transition, internal, immediate }) => { | ||
| state( | ||
| 'a', | ||
| enter({ effect: effect('a.enter') }), | ||
| transition('next', 'b', { effect: effect('a.transition') }), | ||
| exit({ effect: effect('a.exit') }), | ||
| ) | ||
| state( | ||
| 'b', | ||
| enter({ effect: effect('b.enter') }), | ||
| internal('retry', { effect: effect('b.internal') }), | ||
| transition('next', 'c', { effect: effect('b.transition') }), | ||
| exit({ effect: effect('b.exit') }), | ||
| ) | ||
| state( | ||
| 'c', | ||
| enter({ effect: effect('c.enter') }), | ||
| immediate('d', { effect: effect('c.immediate') }), | ||
| exit({ effect: effect('c.exit') }), | ||
| ) | ||
| state('d') | ||
| } | ||
| function App() { | ||
| const { state, send } = useMachine(machine) | ||
| return ( | ||
| <> | ||
| <div id='state'>State: {state.name}</div> | ||
| <button id='next' onClick={() => send('next')} /> | ||
| <button id='retry' onClick={() => send('retry')} /> | ||
| </> | ||
| ) | ||
| } | ||
| const rerender = () => { | ||
| render(<App />) | ||
| } | ||
| eff = [] | ||
| rerender() | ||
| t.deepEqual(eff, ['a.enter started']) | ||
| eff = [] | ||
| click($('#next')) | ||
| rerender() | ||
| t.deepEqual(eff, ['a.enter stopped', 'a.exit started', 'a.transition started', 'b.enter started']) | ||
| eff = [] | ||
| click($('#retry')) | ||
| rerender() | ||
| t.deepEqual(eff, ['b.internal started']) | ||
| eff = [] | ||
| click($('#retry')) | ||
| rerender() | ||
| t.deepEqual(eff, ['b.internal stopped', 'b.internal started']) | ||
| eff = [] | ||
| click($('#next')) | ||
| rerender() | ||
| t.deepEqual(eff, [ | ||
| 'b.internal stopped', | ||
| 'b.enter stopped', | ||
| 'a.transition stopped', | ||
| 'a.exit stopped', | ||
| 'b.exit started', | ||
| 'b.transition started', | ||
| 'c.enter started', | ||
| 'c.enter stopped', | ||
| 'b.transition stopped', | ||
| 'b.exit stopped', | ||
| 'c.exit started', | ||
| 'c.immediate started', | ||
| ]) | ||
| t.is($('#state').innerHTML, 'State: d') | ||
| }) | ||
| test.serial('all types of effects without cleanup', (t) => { | ||
| const { render, click, $ } = dom() | ||
| let eff = [] | ||
| const effect = (label) => (context, data, event, send) => { | ||
| eff.push(`${label} started`) | ||
| } | ||
| const machine = ({ state, enter, exit, transition, internal, immediate }) => { | ||
| state( | ||
| 'a', | ||
| enter({ effect: effect('a.enter') }), | ||
| transition('next', 'b', { effect: effect('a.transition') }), | ||
| exit({ effect: effect('a.exit') }), | ||
| ) | ||
| state( | ||
| 'b', | ||
| enter({ effect: effect('b.enter') }), | ||
| internal('retry', { effect: effect('b.internal') }), | ||
| transition('next', 'c', { effect: effect('b.transition') }), | ||
| exit({ effect: effect('b.exit') }), | ||
| ) | ||
| state( | ||
| 'c', | ||
| enter({ effect: effect('c.enter') }), | ||
| immediate('d', { effect: effect('c.immediate') }), | ||
| exit({ effect: effect('c.exit') }), | ||
| ) | ||
| state('d') | ||
| } | ||
| function App() { | ||
| const { state, send } = useMachine(machine) | ||
| return ( | ||
| <> | ||
| <div id='state'>State: {state.name}</div> | ||
| <button id='next' onClick={() => send('next')} /> | ||
| <button id='retry' onClick={() => send('retry')} /> | ||
| </> | ||
| ) | ||
| } | ||
| const rerender = () => { | ||
| render(<App />) | ||
| } | ||
| eff = [] | ||
| rerender() | ||
| t.deepEqual(eff, ['a.enter started']) | ||
| eff = [] | ||
| click($('#next')) | ||
| rerender() | ||
| t.deepEqual(eff, ['a.exit started', 'a.transition started', 'b.enter started']) | ||
| eff = [] | ||
| click($('#retry')) | ||
| rerender() | ||
| t.deepEqual(eff, ['b.internal started']) | ||
| eff = [] | ||
| click($('#retry')) | ||
| rerender() | ||
| t.deepEqual(eff, ['b.internal started']) | ||
| eff = [] | ||
| click($('#next')) | ||
| rerender() | ||
| t.deepEqual(eff, [ | ||
| 'b.exit started', | ||
| 'b.transition started', | ||
| 'c.enter started', | ||
| 'c.exit started', | ||
| 'c.immediate started', | ||
| ]) | ||
| t.is($('#state').innerHTML, 'State: d') | ||
| }) | ||
| test.serial('effect sending an event', (t) => { | ||
| const { render, click, $ } = dom() | ||
| let eff = [] | ||
| let state | ||
| const effect = (label) => (context, data, event, send) => { | ||
| // both effects see the original state data | ||
| t.deepEqual(data, { a: 1 }) | ||
| eff.push(`${label} started`) | ||
| // further state updates are queued, not executed immediately | ||
| send({ type: 'assign', b: 2 }) | ||
| return () => { | ||
| eff.push(`${label} stopped`) | ||
| } | ||
| } | ||
| const machine = ({ state, enter, exit, transition, internal, immediate }) => { | ||
| state( | ||
| 'a', | ||
| enter({ effect: effect('enter1'), assign: { a: 1 } }), | ||
| enter({ effect: effect('enter2') }), | ||
| internal('assign', { assign: true }), | ||
| transition('next', 'b'), | ||
| ) | ||
| state('b') | ||
| } | ||
| function App() { | ||
| const { state: _state, send } = useMachine(machine) | ||
| state = _state | ||
| return ( | ||
| <> | ||
| <div id='state'>State: {state.name}</div> | ||
| <button id='next' onClick={() => send('next')} /> | ||
| </> | ||
| ) | ||
| } | ||
| const rerender = () => { | ||
| render(<App />) | ||
| } | ||
| eff = [] | ||
| rerender() | ||
| t.deepEqual(eff, ['enter1 started', 'enter2 started']) | ||
| t.deepEqual(state, { name: 'a', data: { a: 1, b: 2 } }) | ||
| eff = [] | ||
| click($('#next')) | ||
| rerender() | ||
| t.deepEqual(eff, ['enter2 stopped', 'enter1 stopped']) | ||
| t.deepEqual(state, { name: 'b', data: { a: 1, b: 2 }, final: true }) | ||
| }) | ||
| test.serial('sending consecutive events', (t) => { | ||
| const { render, click, $ } = dom() | ||
| let eff = [] | ||
| let state | ||
| const effect = (label, val, expectedData) => (context, data, event, send) => { | ||
| // all effects see the original state data | ||
| t.deepEqual(data, expectedData) | ||
| eff.push(`${label} started`) | ||
| // further state updates are queued, not executed immediately | ||
| send({ type: 'assign', e: val }) | ||
| return () => { | ||
| eff.push(`${label} stopped`) | ||
| } | ||
| } | ||
| const machine = ({ state, enter, exit, transition, internal, immediate }) => { | ||
| state( | ||
| 'a', | ||
| enter({ effect: effect('a.enter', 11, { a: 1 }), assign: { a: 1 } }), | ||
| internal('assign', { assign: true }), | ||
| transition('next', 'b', { | ||
| effect: effect('a.transition', 22, { a: 1, e: 11, b: 2 }), | ||
| assign: { b: 2 }, | ||
| }), | ||
| ) | ||
| state( | ||
| 'b', | ||
| enter({ effect: effect('b.enter', 33, { a: 1, e: 11, b: 2, c: 3 }), assign: { c: 3 } }), | ||
| transition('next', 'c'), | ||
| ) | ||
| state('c') | ||
| } | ||
| function App() { | ||
| const { state: _state, send } = useMachine(machine) | ||
| state = _state | ||
| const doubleSend = () => { | ||
| send('next') | ||
| send('next') | ||
| } | ||
| return ( | ||
| <> | ||
| <div id='state'>State: {state.name}</div> | ||
| <button id='next' onClick={doubleSend} /> | ||
| </> | ||
| ) | ||
| } | ||
| const rerender = () => { | ||
| render(<App />) | ||
| } | ||
| eff = [] | ||
| rerender() | ||
| t.deepEqual(eff, ['a.enter started']) | ||
| t.deepEqual(state, { name: 'a', data: { a: 1, e: 11 } }) | ||
| eff = [] | ||
| click($('#next')) | ||
| rerender() | ||
| t.deepEqual(eff, [ | ||
| 'a.enter stopped', | ||
| 'a.transition started', | ||
| 'b.enter started', | ||
| 'b.enter stopped', | ||
| 'a.transition stopped', | ||
| ]) | ||
| t.deepEqual(state, { name: 'c', data: { a: 1, b: 2, c: 3, e: 11 }, final: true }) | ||
| }) | ||
| test.serial('external self transition', (t) => { | ||
| const { render, click, $ } = dom() | ||
| let eff = [] | ||
| let state | ||
| const effect = (label, val) => (context, data, event, send) => { | ||
| eff.push(`${label} started`) | ||
| return () => { | ||
| eff.push(`${label} stopped`) | ||
| } | ||
| } | ||
| const machine = ({ state, enter, exit, transition, internal, immediate }) => { | ||
| state( | ||
| 'a', | ||
| enter({ effect: effect('a.enter', 11), assign: { a: 1 } }), | ||
| transition('assign', 'a', { assign: true, effect: effect('a.transition') }), | ||
| transition('next', 'b'), | ||
| ) | ||
| state('b') | ||
| } | ||
| function App() { | ||
| const { state: _state, send } = useMachine(machine) | ||
| state = _state | ||
| return ( | ||
| <> | ||
| <div id='state'>State: {state.name}</div> | ||
| <button id='next' onClick={() => send('next')} /> | ||
| <button id='assign' onClick={() => send('assign')} /> | ||
| </> | ||
| ) | ||
| } | ||
| const rerender = () => { | ||
| render(<App />) | ||
| } | ||
| eff = [] | ||
| rerender() | ||
| t.deepEqual(eff, ['a.enter started']) | ||
| t.deepEqual(state, { name: 'a', data: { a: 1 } }) | ||
| eff = [] | ||
| click($('#assign')) | ||
| rerender() | ||
| t.deepEqual(eff, ['a.enter stopped', 'a.transition started', 'a.enter started']) | ||
| eff = [] | ||
| click($('#next')) | ||
| rerender() | ||
| t.deepEqual(eff, ['a.enter stopped', 'a.transition stopped']) | ||
| t.deepEqual(state, { name: 'b', data: { a: 1 }, final: true }) | ||
| }) | ||
| test.serial('context changes are handled efficiently', (t) => { | ||
| const { render, $ } = dom() | ||
| const fooIsLarge = (ctx, data) => ctx.foo >= 3 | ||
| const barIsLarge = (ctx, data) => ctx.bar >= 3 | ||
| const multiplyBar = (ctx, data) => ({ ...data, derivedBar: ctx.bar * 2 }) | ||
| const machine = ({ state, enter, exit, transition, internal, immediate }) => { | ||
| state( | ||
| 'a', | ||
| transition('next', 'b'), | ||
| internal('assign', { guard: barIsLarge, reduce: multiplyBar }), | ||
| immediate('b', { guard: fooIsLarge }), | ||
| ) | ||
| state('b') | ||
| } | ||
| let state | ||
| const renderCounts = { | ||
| app: 0, | ||
| componentWithMachine: 0, | ||
| child: 0, | ||
| } | ||
| function App({ foo, bar }) { | ||
| renderCounts.app++ | ||
| return <ComponentWithMachine foo={foo} bar={bar} /> | ||
| } | ||
| function ComponentWithMachine({ foo, bar }) { | ||
| renderCounts.componentWithMachine++ | ||
| const { state: _state } = useMachine(machine, { foo, bar }) | ||
| state = _state | ||
| return <Child foo={foo} bar={bar} derivedBar={state.data.derivedBar} /> | ||
| } | ||
| function Child({ foo, bar, derivedBar }) { | ||
| renderCounts.child++ | ||
| return ( | ||
| <> | ||
| <div id='foo'>{foo}</div> | ||
| <div id='bar'>{bar}</div> | ||
| <div id='derivedBar'>{derivedBar}</div> | ||
| </> | ||
| ) | ||
| } | ||
| const rerender = ({ foo, bar }) => { | ||
| render(<App foo={foo} bar={bar} />) | ||
| } | ||
| rerender({ foo: 1, bar: 2 }) | ||
| t.deepEqual(state, { name: 'a', data: {} }) | ||
| t.deepEqual(renderCounts, { | ||
| app: 1, | ||
| componentWithMachine: 1, | ||
| child: 1, | ||
| }) | ||
| t.is($('#foo').innerHTML, '1') | ||
| t.is($('#bar').innerHTML, '2') | ||
| t.is($('#derivedBar').innerHTML, '') | ||
| rerender({ foo: 2, bar: 2 }) | ||
| t.deepEqual(state, { name: 'a', data: {} }) | ||
| t.deepEqual(renderCounts, { | ||
| app: 2, | ||
| componentWithMachine: 3, | ||
| child: 2, | ||
| }) | ||
| t.is($('#foo').innerHTML, '2') | ||
| t.is($('#bar').innerHTML, '2') | ||
| t.is($('#derivedBar').innerHTML, '') | ||
| rerender({ foo: 2, bar: 3 }) | ||
| t.deepEqual(state, { name: 'a', data: { derivedBar: 6 } }) | ||
| t.deepEqual(renderCounts, { | ||
| app: 3, | ||
| componentWithMachine: 5, | ||
| child: 3, | ||
| }) | ||
| t.is($('#foo').innerHTML, '2') | ||
| t.is($('#bar').innerHTML, '3') | ||
| t.is($('#derivedBar').innerHTML, '6') | ||
| rerender({ foo: 3, bar: 3 }) | ||
| t.deepEqual(state, { name: 'b', data: { derivedBar: 6 }, final: true }) | ||
| t.deepEqual(renderCounts, { | ||
| app: 4, | ||
| componentWithMachine: 7, | ||
| child: 4, | ||
| }) | ||
| t.is($('#foo').innerHTML, '3') | ||
| t.is($('#bar').innerHTML, '3') | ||
| t.is($('#derivedBar').innerHTML, '6') | ||
| // rerender with the same props, everything re-renders only once | ||
| rerender({ foo: 3, bar: 3 }) | ||
| t.deepEqual(state, { name: 'b', data: { derivedBar: 6 }, final: true }) | ||
| t.deepEqual(renderCounts, { | ||
| app: 5, | ||
| componentWithMachine: 8, | ||
| child: 5, | ||
| }) | ||
| }) | ||
| // | ||
| // Test | ||
| // count renders of parent, machine component, child | ||
| // when context changes | ||
| // when context changes + guard is triggered |
+4
-0
| # Changelog | ||
| ## 0.4.5 | ||
| - Upgrade all dependencies to address security alerts. | ||
| ## 0.4.4 | ||
@@ -4,0 +8,0 @@ |
+14
-18
| { | ||
| "name": "react-machine", | ||
| "version": "0.4.4", | ||
| "version": "0.4.5", | ||
| "description": "A lightweight state machine for React applications", | ||
| "type": "module", | ||
| "exports": { | ||
| ".": "./index.js", | ||
| "./core": "./core.js", | ||
| "./service": "./service.js", | ||
| "./hooks": "./hooks.js" | ||
| }, | ||
| "types": "types", | ||
@@ -35,2 +28,5 @@ "sideEffects": false, | ||
| }, | ||
| "engines": { | ||
| "node": "*" | ||
| }, | ||
| "keywords": [ | ||
@@ -49,7 +45,7 @@ "react", | ||
| "devDependencies": { | ||
| "@babel/cli": "^7.22.9", | ||
| "@babel/core": "^7.22.9", | ||
| "@babel/plugin-transform-modules-commonjs": "^7.22.5", | ||
| "@babel/plugin-transform-react-jsx": "^7.22.5", | ||
| "@babel/register": "^7.22.5", | ||
| "@babel/cli": "^7.23.0", | ||
| "@babel/core": "^7.23.2", | ||
| "@babel/plugin-transform-modules-commonjs": "^7.23.0", | ||
| "@babel/plugin-transform-react-jsx": "^7.22.15", | ||
| "@babel/register": "^7.22.15", | ||
| "@skypack/package-check": "^0.2.2", | ||
@@ -59,9 +55,9 @@ "ava": "^5.3.1", | ||
| "esm": "^3.2.25", | ||
| "execa": "^7.2.0", | ||
| "healthier": "^6.3.0", | ||
| "execa": "^8.0.1", | ||
| "healthier": "^7.0.0", | ||
| "jsdom": "^22.1.0", | ||
| "prettier": "^3.0.0", | ||
| "prettier": "^3.0.3", | ||
| "react": "^18.2.0", | ||
| "react-dom": "^18.2.0", | ||
| "typescript": "^5.1.6" | ||
| "typescript": "^5.2.2" | ||
| }, | ||
@@ -79,2 +75,2 @@ "ava": { | ||
| } | ||
| } | ||
| } |
-450
| let nextEffectId | ||
| const hookKeys = { | ||
| guard: 'guards', | ||
| reduce: 'reducers', | ||
| effect: 'effects', | ||
| } | ||
| const transitionHooks = ['assign', 'reduce', 'invoke', 'effect', 'guard'] | ||
| const enterHooks = ['assign', 'reduce', 'invoke', 'effect'] | ||
| const exitHooks = ['assign', 'reduce', 'effect'] | ||
| const mappedHooks = { | ||
| assign: ['reduce', assignToReduce], | ||
| invoke: ['effect', invokeToEffect], | ||
| } | ||
| const env = (process && process.env && process.env.NODE_ENV) || 'development' | ||
| function warn(msg) { | ||
| if (env !== 'production') { | ||
| console.warn(msg) | ||
| } | ||
| } | ||
| function arg(argument, type, error) { | ||
| if (type === 'string') { | ||
| if (typeof argument !== 'string') { | ||
| throw new Error(error) | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Parse the machine DSL into a machine object. | ||
| */ | ||
| export function createMachine(create) { | ||
| const machine = { initial: defaultInitial, nodes: {} } | ||
| if (create) { | ||
| // restart the auto incrementing id | ||
| nextEffectId = 1 | ||
| create({ | ||
| state: (name, ...opts) => { | ||
| machine.nodes[name] = createStateNode(name, ...opts) | ||
| }, | ||
| initial: (...opts) => { | ||
| machine.initial = createInitial(...opts) | ||
| }, | ||
| enter: createEnter, | ||
| exit: createExit, | ||
| transition: createTransition, | ||
| immediate: createImmediate, | ||
| internal: createInternal, | ||
| }) | ||
| } | ||
| validate(machine) | ||
| return machine | ||
| } | ||
| function validate(machine) { | ||
| for (const [, node] of Object.entries(machine.nodes)) { | ||
| for (const transition of node.immediates) { | ||
| if (!machine.nodes[transition.target]) { | ||
| throw new Error(`Invalid transition target '${transition.target}'`) | ||
| } | ||
| } | ||
| for (const transitions of Object.values(node.transitions)) { | ||
| for (const transition of transitions) { | ||
| if (!transition.internal && !machine.nodes[transition.target]) { | ||
| throw new Error(`Invalid transition target '${transition.target}'`) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Create a state node. | ||
| */ | ||
| function createStateNode(name, ...opts) { | ||
| const enter = [] | ||
| const exit = [] | ||
| const transitions = {} | ||
| const immediates = [] | ||
| for (const opt of opts) { | ||
| const { type, event } = opt | ||
| if (type === 'transition') { | ||
| if (!transitions[event]) transitions[event] = [] | ||
| transitions[event].push(opt) | ||
| } else if (type === 'immediate') { | ||
| immediates.push(opt) | ||
| } else if (type === 'enter') { | ||
| enter.push(opt) | ||
| } else if (type === 'exit') { | ||
| exit.push(opt) | ||
| } else { | ||
| throw new Error( | ||
| `State '${name}' should be passed one of enter(), exit(), transition(), immediate() or internal()`, | ||
| ) | ||
| } | ||
| } | ||
| return { | ||
| name, | ||
| enter, | ||
| exit, | ||
| transitions, | ||
| immediates, | ||
| } | ||
| } | ||
| function defaultInitial(opts) { | ||
| return { data: {} } | ||
| } | ||
| function createInitial(name, initialData) { | ||
| return (context) => { | ||
| const initial = {} | ||
| if (typeof name === 'string') { | ||
| initial.name = name | ||
| } else { | ||
| initialData = name | ||
| } | ||
| if (typeof initialData === 'function') { | ||
| initial.data = initialData(context) | ||
| } else { | ||
| initial.data = initialData | ||
| } | ||
| return initial | ||
| } | ||
| } | ||
| function createEnter(opts) { | ||
| return { type: 'enter', ...merge(opts, enterHooks) } | ||
| } | ||
| function createExit(opts) { | ||
| return { type: 'exit', ...merge(opts, exitHooks) } | ||
| } | ||
| function createTransition(event, target, opts) { | ||
| arg(event, 'string', 'First argument of the transition must be the name of the event') | ||
| arg(target, 'string', 'Second argument of the transition must be the name of the target state') | ||
| return { type: 'transition', event, target, ...merge(opts, transitionHooks) } | ||
| } | ||
| function createInternal(event, opts) { | ||
| arg(event, 'string', 'First argument of the internal transition must be the name of the event') | ||
| return { | ||
| type: 'transition', | ||
| event, | ||
| internal: true, | ||
| ...merge(opts, transitionHooks), | ||
| } | ||
| } | ||
| function createImmediate(target, opts) { | ||
| arg( | ||
| target, | ||
| 'string', | ||
| 'First argument of the immediate transition must be the name of the target state', | ||
| ) | ||
| return { type: 'immediate', target, ...merge(opts, transitionHooks) } | ||
| } | ||
| /** | ||
| * Transition the given machine from the provided state | ||
| * to the next state based on the event. Returns the tuple | ||
| * of the next state and any events to execute. In case effects | ||
| * did not change, return null for effects, to indicate that the | ||
| * active effects should continue running. | ||
| */ | ||
| export function transition(machine = {}, context = {}, state = {}, event, { assign } = {}) { | ||
| event = typeof event === 'string' ? { type: event } : event | ||
| // initial transition | ||
| if (!state.name && event && event.type === null) { | ||
| let { name, data } = machine.initial(context) | ||
| const curr = { ...state, data } | ||
| if (!name) { | ||
| const nodeNames = Object.keys(machine.nodes) | ||
| if (nodeNames.length > 0) { | ||
| name = nodeNames[0] | ||
| } | ||
| } | ||
| if (name) { | ||
| const initialTransition = createImmediate(name) | ||
| return applyTransition(machine, context, curr, event, initialTransition) | ||
| } | ||
| return [curr, []] | ||
| } | ||
| const currNode = machine.nodes[state.name] || {} | ||
| const transitions = currNode.transitions || {} | ||
| const candidates = transitions[event.type] || [] | ||
| for (const candidate of candidates) { | ||
| if (checkGuards(context, state, event, candidate)) { | ||
| return applyTransition(machine, context, state, event, candidate) | ||
| } | ||
| } | ||
| // did not find any explicit assign transition, construct a dynamic transition | ||
| // so that we re-trigger all of the immediate transitions every time the context | ||
| // changes | ||
| if (event.type === assign) { | ||
| return applyTransition(machine, context, state, event, createInternal(assign)) | ||
| } | ||
| return [state, []] | ||
| } | ||
| /** | ||
| * The logic of applying a transition to the machine. Exit active state nodes, | ||
| * apply transition hooks, enter target state nodes and collect any effects. Do this | ||
| * recursively until all immediate transitions settle. | ||
| */ | ||
| function applyTransition(machine, context, curr, event, transition, effects = []) { | ||
| const next = { ...curr } | ||
| const target = transition.internal ? curr.name : transition.target | ||
| const currNode = machine.nodes[curr.name] | ||
| const nextNode = machine.nodes[target] | ||
| if (currNode && !transition.internal) { | ||
| effects.push({ op: 'exit', name: currNode.name }) | ||
| for (const exit of currNode.exit) { | ||
| applyReducers(exit, context, next, event) | ||
| queueEffects(exit, effects, next, event, target) | ||
| } | ||
| } | ||
| next.name = target | ||
| applyReducers(transition, context, next, event) | ||
| queueEffects(transition, effects, next, event, target) | ||
| if (!transition.internal) { | ||
| for (const enter of nextNode.enter) { | ||
| applyReducers(enter, context, next, event) | ||
| queueEffects(enter, effects, next, event, target) | ||
| } | ||
| } | ||
| for (const candidate of nextNode.immediates) { | ||
| if (checkGuards(context, next, event, candidate)) { | ||
| return applyTransition(machine, context, next, event, candidate, effects) | ||
| } | ||
| } | ||
| if (Object.keys(nextNode.transitions).length === 0 && nextNode.immediates.length === 0) { | ||
| next.final = true | ||
| } | ||
| return [next, effects] | ||
| } | ||
| function checkGuards(context, state, event, transition) { | ||
| return !transition.guards.length || transition.guards.every((g) => g(context, state.data, event)) | ||
| } | ||
| function applyReducers({ reducers }, context, next, event) { | ||
| for (const reduce of reducers) { | ||
| next.data = reduce(context, next.data, event) | ||
| } | ||
| } | ||
| function queueEffects({ effects }, effectQueue, state, event, target) { | ||
| for (const effect of effects) { | ||
| effectQueue.push({ ...effect, op: 'effect', data: state.data, event, target }) | ||
| } | ||
| } | ||
| /** | ||
| * A common operation is to assign event payload | ||
| * to the context, this allows to do in several ways: | ||
| * true - assign the full event payload to context | ||
| * fn - assign the result of the fn(context, data) to context | ||
| * val - assign the constant provided value to context | ||
| */ | ||
| function assignToReduce(assign) { | ||
| return (context, data, event) => { | ||
| const { type, ...payload } = event | ||
| if (assign === true) { | ||
| return { ...data, ...payload } | ||
| } | ||
| if (typeof assign === 'function') { | ||
| return { ...data, ...assign(context, data, payload) } | ||
| } | ||
| return { ...data, ...assign } | ||
| } | ||
| } | ||
| /** | ||
| * Allow to pass each hook as a function, or a list of functions | ||
| * transition(..., { reduce: fn }) | ||
| * transition(..., { reduce: [fn1, fn2] }) | ||
| * Convert both of those into arrays, and also remap some of the | ||
| * hooks to different hooks (i.e. assign -> reduce) | ||
| */ | ||
| function merge(opts = {}, allowedHooks) { | ||
| const merged = {} | ||
| for (const hook of allowedHooks) { | ||
| add(hook) | ||
| } | ||
| function add(hook) { | ||
| let t = opts[hook] || [] | ||
| t = Array.isArray(t) ? t : [t] | ||
| if (mappedHooks[hook]) { | ||
| const [newName, transform] = mappedHooks[hook] | ||
| hook = newName | ||
| t = t.map(transform) | ||
| } | ||
| if (hook === 'effect') { | ||
| t = t.map((run) => ({ id: nextEffectId++, run })) | ||
| } | ||
| const key = hookKeys[hook] | ||
| merged[key] = merged[key] || [] | ||
| merged[key] = merged[key].concat(t) | ||
| } | ||
| return merged | ||
| } | ||
| /** | ||
| * Convert an async function into an effect | ||
| * that sends 'done' and 'error' events | ||
| */ | ||
| function invokeToEffect(fn) { | ||
| return (context, data, event, send) => { | ||
| let disposed = false | ||
| Promise.resolve(fn(context, data, event)) | ||
| .then((result) => { | ||
| if (!disposed) { | ||
| send({ type: 'done', result }) | ||
| } | ||
| }) | ||
| .catch((error) => { | ||
| if (!disposed) { | ||
| send({ type: 'error', error }) | ||
| } | ||
| }) | ||
| return () => { | ||
| disposed = true | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * createMachine and transition are pure, stateless functions. After | ||
| * transitioning the machine to the next state node, the caller must apply | ||
| * the new set of effects atop of the currently running ones. This stops | ||
| * any effects as necessary and starts any new ones. | ||
| */ | ||
| export function applyEffects(runningEffects = [], effectQueue, context, send) { | ||
| let nextRunningEffects = runningEffects | ||
| for (const effect of effectQueue) { | ||
| if (effect.op === 'exit') { | ||
| for (let i = runningEffects.length - 1; i >= 0; i--) { | ||
| const eff = runningEffects[i] | ||
| if (eff.disposed) { | ||
| continue | ||
| } | ||
| if (effect.name === eff.target) { | ||
| eff.dispose() | ||
| } | ||
| } | ||
| continue | ||
| } | ||
| for (let i = runningEffects.length - 1; i >= 0; i--) { | ||
| const eff = runningEffects[i] | ||
| if (eff.disposed) { | ||
| continue | ||
| } | ||
| if (effect.id === eff.id) { | ||
| eff.dispose() | ||
| } | ||
| } | ||
| const safeSend = (...args) => { | ||
| if (effect.disposed) { | ||
| warn( | ||
| [ | ||
| "Can't send events in an effect after it has been cleaned up.", | ||
| 'This is a no-op, but indicates a memory leak in your application.', | ||
| "To fix, cancel all subscriptions and asynchronous tasks in the effect's cleanup function.", | ||
| ].join(' '), | ||
| ) | ||
| } else { | ||
| return send(...args) | ||
| } | ||
| } | ||
| effect.executed = true | ||
| const dispose = effect.run(context, effect.data, effect.event, safeSend) | ||
| if (dispose && dispose.then) { | ||
| warn( | ||
| [ | ||
| 'Effect function must return a cleanup function or nothing.', | ||
| 'Use invoke instead of effect for async functions, or call the async function inside the synchronous effect function.', | ||
| ].join(' '), | ||
| ) | ||
| } | ||
| effect.dispose = () => { | ||
| effect.disposed = true | ||
| if (dispose) { | ||
| return dispose() | ||
| } | ||
| } | ||
| nextRunningEffects.push(effect) | ||
| } | ||
| nextRunningEffects = nextRunningEffects.filter((eff) => !eff.disposed) | ||
| return nextRunningEffects | ||
| } | ||
| export function cleanEffects(runningEffects = []) { | ||
| for (const effect of runningEffects) { | ||
| effect.dispose() | ||
| } | ||
| return [] | ||
| } |
-141
| import { useReducer, useEffect, useCallback, useMemo, useRef } from 'react' | ||
| import { createMachine, transition, applyEffects, cleanEffects } from './core.js' | ||
| function defaultAreEqual(prev, next) { | ||
| if (prev === next) { | ||
| return true | ||
| } | ||
| for (const i in prev) { | ||
| if (prev[i] !== next[i]) return false | ||
| } | ||
| for (const i in next) { | ||
| if (!(i in prev)) return false | ||
| } | ||
| return true | ||
| } | ||
| function initial({ machine, context }) { | ||
| const initialState = { name: null } | ||
| const initialEvent = { type: null } | ||
| let [state, effects] = transition(machine, context, initialState, initialEvent) | ||
| // minor optimisation to avoid a re-render if no effects have been queued | ||
| if (!effects.some((eff) => eff.op === 'effect')) { | ||
| effects = [] | ||
| } | ||
| const curr = { | ||
| state, | ||
| effects, | ||
| context, | ||
| } | ||
| return curr | ||
| } | ||
| function reduce(curr, action) { | ||
| if (action.type === 'send') { | ||
| const { machine, context, runningEffects, event, options } = action | ||
| const { assign, areEqual } = options | ||
| const [state, effects] = transition(machine, context, curr.state, event, { assign }) | ||
| if ( | ||
| state === curr.state || | ||
| (state.name === curr.state.name && state.data === curr.state.data && !effects.length) | ||
| ) { | ||
| if (areEqual(curr.context, context)) { | ||
| return curr | ||
| } else { | ||
| return { ...curr, context } | ||
| } | ||
| } | ||
| let nextEffects = [] | ||
| if (curr.effects) { | ||
| nextEffects = nextEffects.concat(curr.effects) | ||
| } | ||
| if (effects) { | ||
| nextEffects = nextEffects.concat(effects) | ||
| } | ||
| // minor optimisation to avoid a re-render if no effects are running | ||
| if (!runningEffects.current || !runningEffects.current.length) { | ||
| if (!nextEffects.some((eff) => eff.op === 'effect')) { | ||
| nextEffects = [] | ||
| } | ||
| } | ||
| return { ...curr, state, effects: nextEffects, context } | ||
| } | ||
| if (action.type === 'flushEffects') { | ||
| if (curr.effects.length) { | ||
| return { ...curr, effects: [] } | ||
| } else { | ||
| return curr | ||
| } | ||
| } | ||
| return curr | ||
| } | ||
| export function useMachine(create, context = {}, options = {}) { | ||
| const { assign = 'assign', areEqual = defaultAreEqual } = options | ||
| const contextRef = useRef(context) | ||
| const runningEffects = useRef() | ||
| const machine = useMemo(() => createMachine(create), [create]) | ||
| const [curr, dispatch] = useReducer(reduce, { machine, context }, initial) | ||
| const send = useCallback( | ||
| (event) => | ||
| dispatch({ | ||
| type: 'send', | ||
| context: contextRef.current, | ||
| machine, | ||
| runningEffects, | ||
| event, | ||
| options: { assign, areEqual }, | ||
| }), | ||
| [dispatch, machine, contextRef, assign, areEqual], | ||
| ) | ||
| // if context changed, we will transition | ||
| // the machine if necessary | ||
| if (assign && !areEqual(curr.context, context)) { | ||
| dispatch({ | ||
| type: 'send', | ||
| machine, | ||
| context, | ||
| runningEffects, | ||
| event: { type: assign }, | ||
| options: { assign, areEqual }, | ||
| }) | ||
| } | ||
| useEffect(() => { | ||
| contextRef.current = context | ||
| }) | ||
| useEffect(() => { | ||
| if (!curr.effects.length) return | ||
| runningEffects.current = applyEffects( | ||
| runningEffects.current, | ||
| curr.effects, | ||
| contextRef.current, | ||
| send, | ||
| ) | ||
| dispatch({ type: 'flushEffects' }) | ||
| }, [contextRef, dispatch, send, curr.effects]) | ||
| useEffect(() => { | ||
| return () => { | ||
| runningEffects.current = cleanEffects(runningEffects.current) | ||
| } | ||
| }, []) | ||
| return { state: curr.state, send, context, machine } | ||
| } |
-2
| export { createMachine, transition, applyEffects, cleanEffects } from './core.js' | ||
| export { useMachine } from './hooks.js' |
-76
| import { createMachine, transition, applyEffects, cleanEffects } from './core.js' | ||
| export function createService(machineDescription, context = {}) { | ||
| const machine = createMachine(machineDescription) | ||
| // initial transition | ||
| const initialState = { name: null } | ||
| const initialEvent = { type: null } | ||
| const [state, effects] = transition(machine, context, initialState, initialEvent) | ||
| let cbs = [] | ||
| let running = true | ||
| const service = { | ||
| state, | ||
| send, | ||
| context, | ||
| subscribe, | ||
| machine, | ||
| stop, | ||
| runningEffects: [], | ||
| } | ||
| function runEffects(effects) { | ||
| if (!effects) return | ||
| let deferred = true | ||
| const sendQueue = [] | ||
| const queueSend = (...args) => { | ||
| if (deferred) { | ||
| sendQueue.push(args) | ||
| } else { | ||
| service.send(...args) | ||
| } | ||
| } | ||
| service.runningEffects = applyEffects(service.runningEffects, effects, context, queueSend) | ||
| deferred = false | ||
| for (const s of sendQueue) { | ||
| service.send(...s) | ||
| } | ||
| } | ||
| function stop() { | ||
| running = false | ||
| cbs = [] | ||
| service.runningEffects = cleanEffects(service.runningEffects) | ||
| } | ||
| function subscribe(fn) { | ||
| cbs.push(fn) | ||
| return () => { | ||
| cbs = cbs.filter((f) => f !== fn) | ||
| } | ||
| } | ||
| function send(event) { | ||
| if (!running) return | ||
| const [state, effects] = transition(service.machine, context, service.state, event) | ||
| service.state = state | ||
| if (effects) { | ||
| runEffects(effects) | ||
| } | ||
| for (const cb of cbs) { | ||
| cb(state) | ||
| } | ||
| } | ||
| runEffects(effects) | ||
| return service | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
11289096
24017.36%62
463.64%10739
1329.96%3
50%6
200%1
Infinity%No
NaN