@nutin/cli 1.3.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (284) hide show
  1. package/CHANGELOG.md +122 -8
  2. package/README.md +21 -37
  3. package/bin/add-feature.mjs +9 -0
  4. package/bin/cli.mjs +8 -0
  5. package/bin/create-app.mjs +5 -4
  6. package/bin/update-app.mjs +9 -0
  7. package/lib/index.mjs +81 -30
  8. package/lib/src/add-feature/feature-adder.mjs +83 -0
  9. package/lib/src/add-feature/feature-context-builder.mjs +25 -0
  10. package/lib/src/add-feature/nutin-config-updater.mjs +29 -0
  11. package/lib/src/add-feature/package-json-updater.mjs +49 -0
  12. package/lib/src/common/feature-registry.mjs +11 -0
  13. package/lib/src/common/file-generator.mjs +159 -0
  14. package/lib/src/common/meta-bootstrap-prompt.mjs +33 -0
  15. package/lib/src/common/package-data.mjs +10 -0
  16. package/lib/src/common/package-json-helper.mjs +103 -0
  17. package/lib/src/common/project-meta.mjs +54 -0
  18. package/lib/src/create-app/context-builder.mjs +21 -0
  19. package/lib/src/{git-manager.mjs → create-app/git-manager.mjs} +2 -2
  20. package/lib/src/create-app/json-manager.mjs +73 -0
  21. package/lib/src/create-app/new-app-prompt.mjs +51 -0
  22. package/lib/src/{project-generator.mjs → create-app/project-generator.mjs} +15 -13
  23. package/lib/src/create-app/success-message.mjs +14 -0
  24. package/lib/src/update-app/conflict-report-writer.mjs +31 -0
  25. package/lib/src/update-app/fetch-old-templates.mjs +61 -0
  26. package/lib/src/update-app/project-updater.mjs +136 -0
  27. package/lib/src/update-app/template-differ.mjs +124 -0
  28. package/lib/src/update-app/update-context-builder.mjs +25 -0
  29. package/lib/src/update-app/update-summary-printer.mjs +22 -0
  30. package/lib/src/update-app/version-compare.mjs +12 -0
  31. package/lib/src/utils/print.mjs +36 -0
  32. package/lib/src/utils/promise-exec-alias.mjs +4 -0
  33. package/package.json +17 -12
  34. package/templates/base/.gitignore.hbs +6 -1
  35. package/templates/base/AGENTS.md.hbs +169 -0
  36. package/templates/base/GETTING_STARTED.md.hbs +227 -0
  37. package/templates/base/README.md.hbs +8 -17
  38. package/templates/base/config/languages.json.hbs +6 -0
  39. package/templates/base/config/seo.json.hbs +12 -0
  40. package/templates/base/nutin.config.js.hbs +51 -0
  41. package/templates/base/src/app/components/index.ts.hbs +1 -2
  42. package/templates/base/src/app/globals.d.ts.hbs +3 -15
  43. package/templates/base/src/{libs/guards → app}/guards.ts.hbs +1 -5
  44. package/templates/base/src/app/main.ts.hbs +7 -26
  45. package/templates/base/src/app/services/index.ts.hbs +1 -1
  46. package/templates/base/src/app/views/home/home.view.html.hbs +7 -0
  47. package/templates/base/src/app/views/home/home.view.ts.hbs +1 -56
  48. package/templates/base/src/app/views/index.ts.hbs +1 -0
  49. package/templates/base/src/app/views/not-found/not-found.view.html.hbs +8 -0
  50. package/templates/base/src/app/views/not-found/not-found.view.ts.hbs +4 -37
  51. package/templates/base/src/core/base-classes/base-component/base-component.test.js.hbs +158 -0
  52. package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +81 -31
  53. package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.test.js.hbs +124 -0
  54. package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +38 -23
  55. package/templates/base/src/core/base-classes/base-component/helpers/children.helper.test.js.hbs +74 -0
  56. package/templates/base/src/core/base-classes/base-component/helpers/children.helper.ts.hbs +1 -1
  57. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.test.js.hbs +145 -0
  58. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +41 -1
  59. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.test.js.hbs +88 -0
  60. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +13 -14
  61. package/templates/base/src/core/base-classes/base-component/helpers/i18n.helper.test.js.hbs +62 -0
  62. package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.test.js.hbs +82 -0
  63. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.test.js.hbs +142 -0
  64. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.ts.hbs +67 -22
  65. package/templates/base/src/core/base-classes/base-component/helpers/token.helper.test.js.hbs +183 -0
  66. package/templates/base/src/core/base-classes/component/component.test.js.hbs +85 -0
  67. package/templates/base/src/core/base-classes/component/component.ts.hbs +27 -40
  68. package/templates/base/src/core/base-classes/component/helpers/config.helper.test.js.hbs +58 -0
  69. package/templates/base/src/core/base-classes/component/helpers/config.helper.ts.hbs +17 -0
  70. package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.test.js.hbs +75 -0
  71. package/templates/base/src/core/base-classes/service/service.test.js.hbs +150 -0
  72. package/templates/base/src/core/base-classes/service/service.ts.hbs +26 -20
  73. package/templates/base/src/core/base-classes/view/view.test.js.hbs +74 -0
  74. package/templates/base/src/core/base-classes/view/view.ts.hbs +17 -41
  75. package/templates/base/src/core/config.ts.hbs +7 -0
  76. package/templates/base/src/core/index.ts.hbs +0 -3
  77. package/templates/base/src/core/internals.d.ts.hbs +27 -0
  78. package/templates/base/src/core/nutin-config.d.ts.hbs +4 -0
  79. package/templates/base/src/core/services/event-bus/event-bus.test.js.hbs +67 -3
  80. package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +9 -4
  81. package/templates/base/src/core/services/event-bus/index.ts.hbs +3 -0
  82. package/templates/base/src/core/services/event-bus/lifecycle.facade.test.js.hbs +123 -0
  83. package/templates/base/src/core/services/event-bus/lifecycle.facade.ts.hbs +61 -0
  84. package/templates/base/src/core/services/event-bus/navigation.facade.test.js.hbs +46 -0
  85. package/templates/base/src/core/services/event-bus/navigation.facade.ts.hbs +25 -0
  86. package/templates/base/src/core/services/globals/globals.test.js.hbs +149 -0
  87. package/templates/base/src/core/services/globals/globals.ts.hbs +95 -0
  88. package/templates/base/src/core/services/http-client/helpers/http-builder.helper.test.js.hbs +25 -0
  89. package/templates/base/src/core/services/http-client/helpers/http-manager.helper.test.js.hbs +20 -0
  90. package/templates/base/src/core/services/http-client/http-client.test.js.hbs +43 -0
  91. package/templates/base/src/core/services/i18n/i18n.test.js.hbs +270 -0
  92. package/templates/{features/i18n → base}/src/core/services/i18n/i18n.ts.hbs +60 -9
  93. package/templates/base/src/core/services/index.ts.hbs +5 -6
  94. package/templates/base/src/core/services/pipe-registry/pipe-registry.test.js.hbs +60 -1
  95. package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +1 -1
  96. package/templates/base/src/core/services/pipe-registry/pipes.test.js.hbs +128 -0
  97. package/templates/base/src/{libs/pipes → core/services/pipe-registry}/pipes.ts.hbs +8 -5
  98. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.test.js.hbs +149 -6
  99. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +42 -30
  100. package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.test.js.hbs +27 -0
  101. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.test.js.hbs +83 -17
  102. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +13 -70
  103. package/templates/base/src/core/services/router/router.test.js.hbs +226 -2
  104. package/templates/base/src/core/services/router/router.ts.hbs +40 -9
  105. package/templates/base/src/index.html.hbs +2 -6
  106. package/templates/base/src/styles/_styles.scss.hbs +102 -43
  107. package/templates/base/src/styles/base/_variables.scss.hbs +1 -85
  108. package/templates/base/src/styles/main.scss.hbs +1 -18
  109. package/templates/base/src/styles/tailwind.css.hbs +2 -0
  110. package/templates/base/tools/builder/builder.config.js.hbs +40 -0
  111. package/templates/base/tools/builder/builder.js.hbs +24 -26
  112. package/templates/base/tools/builder/core/app/binary-extensions.test.js.hbs +16 -0
  113. package/templates/base/tools/builder/core/app/compile-ts.js.hbs +16 -0
  114. package/templates/base/tools/builder/core/{copy-static.js.hbs → app/copy-static.js.hbs} +27 -34
  115. package/templates/base/tools/builder/core/app/validate-routes.js.hbs +77 -0
  116. package/templates/base/tools/builder/core/finalize-build.js.hbs +16 -7
  117. package/templates/base/tools/builder/core/{add-tags.js.hbs → html-index/add-tags.js.hbs} +2 -2
  118. package/templates/base/tools/builder/core/html-index/add-tags.test.js.hbs +56 -0
  119. package/templates/base/tools/builder/core/{validate-html.js.hbs → html-index/validate-html.js.hbs} +5 -11
  120. package/templates/base/tools/builder/core/html-templates/merge-templates.js.hbs +152 -0
  121. package/templates/base/tools/builder/core/html-templates/minify-html.js.hbs +15 -0
  122. package/templates/base/tools/builder/core/html-templates/process-html-templates.js.hbs +8 -0
  123. package/templates/{features/i18n/tools/builder/core → base/tools/builder/core/i18n}/build-i18n.js.hbs +6 -17
  124. package/templates/base/tools/builder/core/prod-bundle/compress-files.js.hbs +37 -0
  125. package/templates/base/tools/builder/core/prod-bundle/esbuild.js.hbs +30 -0
  126. package/templates/base/tools/builder/core/{hash-files.js.hbs → prod-bundle/hash-files.js.hbs} +14 -11
  127. package/templates/base/tools/builder/core/seo/generate-robots-txt.js.hbs +51 -0
  128. package/templates/base/tools/builder/core/seo/generate-seo-files.js.hbs +16 -0
  129. package/templates/base/tools/builder/core/seo/generate-seo-html.js.hbs +128 -0
  130. package/templates/base/tools/builder/core/seo/generate-sitemap-xml.js.hbs +43 -0
  131. package/templates/base/tools/builder/core/seo/html-substitutions.js.hbs +99 -0
  132. package/templates/base/tools/builder/core/seo/i18n-resolution.js.hbs +66 -0
  133. package/templates/base/tools/builder/core/seo/route-output.js.hbs +64 -0
  134. package/templates/base/tools/builder/core/seo/ssr/ssr-bundle.js.hbs +54 -0
  135. package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.js.hbs +132 -0
  136. package/templates/base/tools/builder/core/seo/ssr/ssr-render.js.hbs +95 -0
  137. package/templates/base/tools/builder/core/styles/sass.js.hbs +40 -0
  138. package/templates/base/tools/builder/core/styles/tailwind.js.hbs +84 -0
  139. package/templates/base/tools/dev/dev-serve.js.hbs +26 -18
  140. package/templates/base/tools/dev/serve.js.hbs +11 -1
  141. package/templates/base/tools/dev/watcher.js.hbs +14 -6
  142. package/templates/base/tools/generator/generator.js.hbs +64 -47
  143. package/templates/base/tools/generator/handle-file.js.hbs +17 -44
  144. package/templates/base/tools/generator/handle-file.test.js.hbs +72 -0
  145. package/templates/base/tools/generator/templates/{component.template.js.hbs → component/component.template.js.hbs} +3 -2
  146. package/templates/base/tools/generator/templates/component/component.template.test.js.hbs +23 -0
  147. package/templates/base/tools/generator/templates/html/html.template.js.hbs +2 -0
  148. package/templates/base/tools/generator/templates/html/html.template.test.js.hbs +7 -0
  149. package/templates/base/tools/generator/templates/index.js.hbs +7 -12
  150. package/templates/base/tools/generator/templates/locale/locale.template.js.hbs +4 -0
  151. package/templates/base/tools/generator/templates/locale/locale.template.test.js.hbs +7 -0
  152. package/templates/base/tools/generator/templates/scss/scss.template.js.hbs +1 -0
  153. package/templates/base/tools/generator/templates/scss/scss.template.test.js.hbs +7 -0
  154. package/templates/base/tools/generator/templates/service/service.template.js.hbs +17 -0
  155. package/templates/base/tools/generator/templates/service/service.template.test.js.hbs +22 -0
  156. package/templates/base/tools/generator/templates/test/test.template.js.hbs +10 -0
  157. package/templates/base/tools/generator/templates/test/test.template.test.js.hbs +27 -0
  158. package/templates/base/tools/generator/templates/{view.template.js.hbs → view/view.template.js.hbs} +3 -2
  159. package/templates/base/tools/generator/templates/view/view.template.test.js.hbs +24 -0
  160. package/templates/base/tools/testin-nutin/core/coverage/collect-coverage.js.hbs +39 -0
  161. package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.js.hbs +115 -0
  162. package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.test.js.hbs +74 -0
  163. package/templates/base/tools/testin-nutin/core/coverage/write-summary-report.js.hbs +54 -0
  164. package/templates/base/tools/testin-nutin/core/coverage/write-uncovered-report.js.hbs +44 -0
  165. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/assertion-lib.js.hbs +62 -24
  166. package/templates/base/tools/testin-nutin/core/globals/clock.js.hbs +206 -0
  167. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/jsdom-setup.js.hbs +19 -6
  168. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/register-test-globals.js.hbs +13 -4
  169. package/templates/base/tools/testin-nutin/core/globals/silence-console.js.hbs +23 -0
  170. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/spyon.js.hbs +2 -0
  171. package/templates/base/tools/testin-nutin/core/index.js.hbs +9 -0
  172. package/templates/base/tools/testin-nutin/core/printer.js.hbs +85 -0
  173. package/templates/base/tools/testin-nutin/core/queue/queue.test.js.hbs +46 -0
  174. package/templates/base/tools/testin-nutin/core/queue/test-discovery.test.js.hbs +46 -0
  175. package/templates/base/tools/testin-nutin/core/queue/test-queue.js.hbs +154 -0
  176. package/templates/{features/testinNutin → base/tools}/testin-nutin/core/tests/assertions.test.js.hbs +27 -0
  177. package/templates/base/tools/testin-nutin/core/tests/clock.test.js.hbs +118 -0
  178. package/templates/base/tools/testin-nutin/core/tests/globals-each-all.test.js.hbs +28 -0
  179. package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/create-mock-method.js.hbs +2 -1
  180. package/templates/base/tools/testin-nutin/mocks/create-mock-method.test.js.hbs +64 -0
  181. package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-event-bus.js.hbs +3 -2
  182. package/templates/base/tools/testin-nutin/mocks/mock-event-bus.test.js.hbs +66 -0
  183. package/templates/base/tools/testin-nutin/mocks/mock-http-client.test.js.hbs +26 -0
  184. package/templates/base/tools/testin-nutin/mocks/mock-i18n.test.js.hbs +61 -0
  185. package/templates/base/tools/testin-nutin/mocks/mock-router.test.js.hbs +29 -0
  186. package/templates/base/tools/testin-nutin/runner.js.hbs +100 -0
  187. package/templates/base/tools/testin-nutin/watch-tests.js.hbs +37 -0
  188. package/templates/base/tools/utils/error-exit.js.hbs +8 -0
  189. package/templates/base/tools/utils/format.js.hbs +10 -10
  190. package/templates/base/tools/utils/format.test.js.hbs +45 -0
  191. package/templates/base/tools/utils/get-files-recursive.js.hbs +6 -1
  192. package/templates/base/tools/utils/get-files-recursive.test.js.hbs +49 -0
  193. package/templates/base/tools/utils/get-rel-to-core.test.js.hbs +18 -0
  194. package/templates/base/tools/utils/index.js.hbs +1 -3
  195. package/templates/base/tools/utils/languages.js.hbs +15 -0
  196. package/templates/base/tools/utils/languages.test.js.hbs +16 -0
  197. package/templates/base/tools/utils/print.js.hbs +19 -13
  198. package/templates/base/tools/utils/print.test.js.hbs +60 -0
  199. package/templates/base/tools/utils/prompt-boolean.js.hbs +7 -0
  200. package/templates/base/tools/utils/prompt-boolean.test.js.hbs +54 -0
  201. package/templates/base/tools/utils/run-command.js.hbs +4 -2
  202. package/templates/base/tools/utils/run-command.test.js.hbs +19 -0
  203. package/templates/base/tools/utils/run-script.js.hbs +14 -3
  204. package/templates/base/tools/utils/run-script.test.js.hbs +40 -0
  205. package/templates/features/{deployHelper → docker}/.dockerignore.hbs +4 -4
  206. package/templates/features/docker/tools/docker/Dockerfile.template.hbs +51 -0
  207. package/templates/features/{deployHelper/tools/deployment/nginx.conf.hbs → docker/tools/docker/nginx.conf.template} +4 -2
  208. package/templates/features/docker/tools/docker/scripts/docker-build.js.hbs +20 -0
  209. package/templates/features/docker/tools/docker/scripts/docker-ports.js.hbs +48 -0
  210. package/templates/features/docker/tools/docker/scripts/docker-ports.test.js.hbs +52 -0
  211. package/templates/features/docker/tools/docker/scripts/docker-run.js.hbs +28 -0
  212. package/templates/features/docker/tools/docker/scripts/render-docker-template.js.hbs +23 -0
  213. package/templates/features/docker/tools/docker/scripts/render-docker-template.test.js.hbs +60 -0
  214. package/templates/features/docker/tools/docker/scripts/validate-docker.js.hbs +30 -0
  215. package/lib/src/context-builder.mjs +0 -35
  216. package/lib/src/file-generator.mjs +0 -109
  217. package/lib/src/json-generator.mjs +0 -31
  218. package/lib/src/package-manager.mjs +0 -139
  219. package/lib/src/print.mjs +0 -31
  220. package/lib/src/prompts.mjs +0 -123
  221. package/lib/src/utils.mjs +0 -18
  222. package/lib/src/version.mjs +0 -1
  223. package/templates/base/builder.config.js.hbs +0 -35
  224. package/templates/base/src/app/components/common/anchor/anchor.component.ts.hbs +0 -32
  225. package/templates/base/src/app/components/common/button/button.component.ts.hbs +0 -40
  226. package/templates/base/src/app/dom.test.js.hbs +0 -13
  227. package/templates/base/src/app/services/example/example.service.ts.hbs +0 -18
  228. package/templates/base/src/app/views/home/home.view.test.js.hbs +0 -26
  229. package/templates/base/src/core/services/store/store.test.js.hbs +0 -8
  230. package/templates/base/src/core/services/store/store.ts.hbs +0 -39
  231. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.test.js.hbs +0 -0
  232. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +0 -85
  233. package/templates/base/src/core/utils/button-manager/button-manager.utils.test.js.hbs +0 -249
  234. package/templates/base/src/core/utils/button-manager/button-manager.utils.ts.hbs +0 -108
  235. package/templates/base/src/core/utils/helpers/attributes.helper.ts.hbs +0 -50
  236. package/templates/base/src/core/utils/index.ts.hbs +0 -2
  237. package/templates/base/src/libs/index.ts.hbs +0 -4
  238. package/templates/base/src/libs/popover/popover.ts.hbs +0 -145
  239. package/templates/base/src/libs/snackbar/snackbar.ts.hbs +0 -72
  240. package/templates/base/src/styles/base/_functions.scss.hbs +0 -9
  241. package/templates/base/src/styles/core/_libs.scss.hbs +0 -132
  242. package/templates/base/tools/builder/core/esbuild.js.hbs +0 -25
  243. package/templates/base/tools/builder/core/minify-html.js.hbs +0 -60
  244. package/templates/base/tools/builder/core/sass.js.hbs +0 -22
  245. package/templates/base/tools/builder/core/tsc.js.hbs +0 -12
  246. package/templates/base/tools/generator/templates/service.template.js.hbs +0 -17
  247. package/templates/base/tools/utils/export-flags.js.hbs +0 -6
  248. package/templates/features/deployHelper/tools/builder/core/compress-files.js.hbs +0 -56
  249. package/templates/features/deployHelper/tools/deployment/Dockerfile.hbs +0 -30
  250. package/templates/features/i18n/src/app/views/home/locales/en.json.hbs +0 -9
  251. package/templates/features/i18n/src/app/views/not-found/locales/en.json +0 -8
  252. package/templates/features/i18n/src/app/views/not-found/locales/fr.json +0 -8
  253. package/templates/features/i18n/src/core/config.ts.hbs +0 -3
  254. package/templates/features/i18n/src/core/services/i18n/i18n.test.js.hbs +0 -125
  255. package/templates/features/i18n/tools/generator/templates/json.template.js.hbs +0 -8
  256. package/templates/features/i18n/tools/utils/languages.js.hbs +0 -7
  257. package/templates/features/stylinNutin/src/styles/_custom-classes.scss.hbs +0 -9
  258. package/templates/features/stylinNutin/src/styles/base/_mixins.scss.hbs +0 -372
  259. package/templates/features/stylinNutin/src/styles/components/_index.scss.hbs +0 -3
  260. package/templates/features/stylinNutin/src/styles/core/_utilities.scss.hbs +0 -240
  261. package/templates/features/stylinNutin/src/styles/views/_index.scss.hbs +0 -0
  262. package/templates/features/stylinNutin/tools/generator/templates/scss.template.js.hbs +0 -4
  263. package/templates/features/template/src/app/views/home/home.view.html.hbs +0 -56
  264. package/templates/features/template/src/app/views/not-found/not-found.view.html.hbs +0 -19
  265. package/templates/features/template/tools/builder/core/merge-templates.js.hbs +0 -49
  266. package/templates/features/template/tools/generator/templates/html.template.js.hbs +0 -2
  267. package/templates/features/testinNutin/testin-nutin/core/index.js.hbs +0 -5
  268. package/templates/features/testinNutin/testin-nutin/core/src/utils/print.js.hbs +0 -64
  269. package/templates/features/testinNutin/testin-nutin/core/src/utils/test-queue.js.hbs +0 -64
  270. package/templates/features/testinNutin/testin-nutin/core/tests/globals-each-all.test.js.hbs +0 -28
  271. package/templates/features/testinNutin/testin-nutin/mocks/mock-store.js.hbs +0 -38
  272. package/templates/features/testinNutin/testin-nutin/runner.js.hbs +0 -36
  273. package/templates/features/testinNutin/testin-nutin/watch-tests.js.hbs +0 -21
  274. package/templates/features/testinNutin/testin-nutin.config.js.hbs +0 -14
  275. /package/lib/src/{template-compiler.mjs → common/template-compiler.mjs} +0 -0
  276. /package/templates/{features/i18n → base}/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +0 -0
  277. /package/templates/{features/i18n → base}/src/core/services/i18n/languages.ts.hbs +0 -0
  278. /package/templates/base/tools/builder/{variables → core/app}/binary-extensions.js.hbs +0 -0
  279. /package/templates/base/tools/builder/core/{paths.js.hbs → app/paths.js.hbs} +0 -0
  280. /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/queue.js.hbs +0 -0
  281. /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/test-discovery.js.hbs +0 -0
  282. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-http-client.js.hbs +0 -0
  283. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-i18n.js.hbs +0 -0
  284. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-router.js.hbs +0 -0
@@ -0,0 +1,115 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { errorExit } from '../../../utils/index.js';
4
+
5
+ // V8 ranges within a script are always disjoint or strictly nested, never partially
6
+ // overlapping. The smallest range containing an offset holds the true execution count
7
+ // for that offset - e.g. "this function ran 5x but this one branch inside it ran 0x".
8
+ function countAt(offset, ranges) {
9
+ let best = null;
10
+ for (const range of ranges) {
11
+ if (offset < range.startOffset || offset >= range.endOffset) continue;
12
+ if (!best || (range.endOffset - range.startOffset) < (best.endOffset - best.startOffset)) {
13
+ best = range;
14
+ }
15
+ }
16
+ return best ? best.count : 0;
17
+ }
18
+
19
+ function buildLineStarts(sourceText) {
20
+ const starts = [0];
21
+ for (let i = 0; i < sourceText.length; i++) {
22
+ if (sourceText[i] === '\n') starts.push(i + 1);
23
+ }
24
+ return starts;
25
+ }
26
+
27
+ function offsetToLine(offset, lineStarts) {
28
+ let lo = 0, hi = lineStarts.length - 1;
29
+ while (lo < hi) {
30
+ const mid = (lo + hi + 1) >> 1;
31
+ if (lineStarts[mid] <= offset) lo = mid; else hi = mid - 1;
32
+ }
33
+ return lo + 1; // 1-indexed
34
+ }
35
+
36
+ // V8 sometimes reports an empty functionName (e.g. accessors, unnamed callbacks).
37
+ // Fall back to reading the source at the function's own start offset instead of a
38
+ // meaningless placeholder - covers `constructor(`, `get foo(`, method names, etc.
39
+ // Only treat a leading identifier as a real name when it's immediately followed by
40
+ // `(` (a param list) - otherwise it's just an arrow function's bare parameter (e.g.
41
+ // `c => ...`), which isn't a name at all, so fall through to a source snippet instead.
42
+ function deriveLabel(sourceText, startOffset) {
43
+ const snippet = sourceText.slice(startOffset, startOffset + 60);
44
+ const named = snippet.match(/^\s*(?:get |set )?([A-Za-z_$][\w$]*)\s*\(/);
45
+ if (named) return named[1];
46
+ return snippet.trim().split('\n')[0].slice(0, 30) || '<anonymous>';
47
+ }
48
+
49
+ export function computeFileCoverage({ functions }, sourceText) {
50
+ const allRanges = functions.flatMap(fn => fn.ranges);
51
+ const lineStarts = buildLineStarts(sourceText);
52
+
53
+ // Line coverage: sample the count at each line's first non-whitespace character.
54
+ // Not statement/branch-accurate, but simple and good enough for a lightweight report.
55
+ let offset = 0;
56
+ let totalLines = 0;
57
+ let coveredLines = 0;
58
+ const uncoveredLines = [];
59
+ for (const line of sourceText.split('\n')) {
60
+ const firstChar = line.search(/\S/);
61
+ if (firstChar !== -1) {
62
+ totalLines++;
63
+ if (countAt(offset + firstChar, allRanges) > 0) coveredLines++;
64
+ else uncoveredLines.push(offsetToLine(offset + firstChar, lineStarts));
65
+ }
66
+ offset += line.length + 1;
67
+ }
68
+
69
+ // Function coverage: every functions[] entry except the whole-script pseudo-function,
70
+ // identified by its first range spanning the entire file.
71
+ const realFunctions = functions.filter(fn => {
72
+ const [topRange] = fn.ranges;
73
+ return !(topRange.startOffset === 0 && topRange.endOffset === sourceText.length);
74
+ });
75
+ const coveredFunctions = realFunctions.filter(fn => fn.ranges[0].count > 0).length;
76
+ const uncoveredFunctions = realFunctions
77
+ .filter(fn => fn.ranges[0].count === 0)
78
+ .map(fn => ({
79
+ name: fn.functionName || deriveLabel(sourceText, fn.ranges[0].startOffset),
80
+ line: offsetToLine(fn.ranges[0].startOffset, lineStarts),
81
+ }));
82
+
83
+ // Branch coverage: every range beyond a function's own top-level range (index 0)
84
+ // is a nested conditional block V8 already tracked while collecting detailed
85
+ // coverage - if/else arms, ternary branches, switch cases, logical short-circuits,
86
+ // loop bodies. No AST parsing needed, just reusing data already being collected.
87
+ const allBranchRanges = functions.flatMap(fn => fn.ranges.slice(1));
88
+ const coveredBranches = allBranchRanges.filter(r => r.count > 0).length;
89
+ const uncoveredBranches = allBranchRanges
90
+ .filter(r => r.count === 0)
91
+ .map(r => offsetToLine(r.startOffset, lineStarts))
92
+ .sort((a, b) => a - b);
93
+
94
+ return {
95
+ totalLines, coveredLines, uncoveredLines,
96
+ linesPct: totalLines ? (coveredLines / totalLines) * 100 : 100,
97
+ totalBranches: allBranchRanges.length, coveredBranches, uncoveredBranches,
98
+ branchesPct: allBranchRanges.length ? (coveredBranches / allBranchRanges.length) * 100 : 100,
99
+ totalFunctions: realFunctions.length, coveredFunctions, uncoveredFunctions,
100
+ functionsPct: realFunctions.length ? (coveredFunctions / realFunctions.length) * 100 : 100,
101
+ };
102
+ }
103
+
104
+ export function buildCoverageReport(scopedResults) {
105
+ return scopedResults.map(entry => {
106
+ let sourceText;
107
+ try {
108
+ sourceText = fs.readFileSync(entry.filePath, 'utf-8');
109
+ } catch (err) {
110
+ errorExit(err, 'coverage/compute-coverage');
111
+ }
112
+ const coverage = computeFileCoverage(entry, sourceText);
113
+ return { file: path.relative(process.cwd(), entry.filePath), ...coverage };
114
+ });
115
+ }
@@ -0,0 +1,74 @@
1
+ import { computeFileCoverage } from './compute-coverage.js';
2
+
3
+ describe('computeFileCoverage', () => {
4
+ it('reports 100% when every function and line ran', () => {
5
+ const sourceText = 'function covered() {\n return 1;\n}\n';
6
+ const scriptResult = {
7
+ functions: [
8
+ { functionName: '', ranges: [{ startOffset: 0, endOffset: sourceText.length, count: 1 }] },
9
+ { functionName: 'covered', ranges: [{ startOffset: 0, endOffset: sourceText.length - 1, count: 3 }] },
10
+ ],
11
+ };
12
+
13
+ const { linesPct, functionsPct, branchesPct } = computeFileCoverage(scriptResult, sourceText);
14
+ expect(linesPct).toEqual(100);
15
+ expect(functionsPct).toEqual(100);
16
+ expect(branchesPct).toEqual(100); // no nested ranges anywhere -> defaults to 100
17
+ });
18
+
19
+ it('reports 0% for a function that never ran', () => {
20
+ const sourceText = 'function uncovered() {\n return 1;\n}\n';
21
+ const scriptResult = {
22
+ functions: [
23
+ { functionName: '', ranges: [{ startOffset: 0, endOffset: sourceText.length, count: 1 }] },
24
+ { functionName: 'uncovered', ranges: [{ startOffset: 0, endOffset: sourceText.length - 1, count: 0 }] },
25
+ ],
26
+ };
27
+
28
+ const { linesPct, functionsPct } = computeFileCoverage(scriptResult, sourceText);
29
+ expect(linesPct).toEqual(0);
30
+ expect(functionsPct).toEqual(0);
31
+ });
32
+
33
+ it('lets a nested 0-count branch override its enclosing covered range', () => {
34
+ const sourceText = 'function branchy(flag) {\n if (flag) {\n return 1;\n }\n return 2;\n}\n';
35
+ const branchStart = sourceText.indexOf('return 1');
36
+ const branchEnd = sourceText.indexOf(';', branchStart) + 1;
37
+
38
+ const scriptResult = {
39
+ functions: [
40
+ { functionName: '', ranges: [{ startOffset: 0, endOffset: sourceText.length, count: 1 }] },
41
+ {
42
+ functionName: 'branchy',
43
+ ranges: [
44
+ { startOffset: 0, endOffset: sourceText.length - 1, count: 5 },
45
+ { startOffset: branchStart, endOffset: branchEnd, count: 0 },
46
+ ],
47
+ },
48
+ ],
49
+ };
50
+
51
+ const { linesPct, totalBranches, coveredBranches, branchesPct } = computeFileCoverage(scriptResult, sourceText);
52
+ // every line covered except the never-taken "return 1;" branch
53
+ expect(linesPct).toEqual(5 / 6 * 100);
54
+ expect(totalBranches).toEqual(1);
55
+ expect(coveredBranches).toEqual(0);
56
+ expect(branchesPct).toEqual(0);
57
+ });
58
+
59
+ it('derives a name from the source when V8 reports an empty functionName', () => {
60
+ const sourceText = 'class Foo {\n constructor(x) {\n this.x = x;\n }\n}\n';
61
+ const constructorStart = sourceText.indexOf('constructor');
62
+ const constructorEnd = sourceText.indexOf('}', constructorStart) + 1;
63
+
64
+ const scriptResult = {
65
+ functions: [
66
+ { functionName: '', ranges: [{ startOffset: 0, endOffset: sourceText.length, count: 1 }] },
67
+ { functionName: '', ranges: [{ startOffset: constructorStart, endOffset: constructorEnd, count: 0 }] },
68
+ ],
69
+ };
70
+
71
+ const { uncoveredFunctions } = computeFileCoverage(scriptResult, sourceText);
72
+ expect(uncoveredFunctions).toEqual([{ name: 'constructor', line: 2 }]);
73
+ });
74
+ });
@@ -0,0 +1,54 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { print, toTsPath } from '../printer.js';
4
+ import { errorExit } from '../../../utils/index.js';
5
+ import config from '#root/nutin.config.js';
6
+
7
+ const REPORT_PATH = path.join('coverage', 'summary.md');
8
+
9
+ export function writeSummaryReport(report, globals, testResults) {
10
+ if (!report.length) return;
11
+
12
+ try {
13
+ fs.mkdirSync(path.dirname(REPORT_PATH), { recursive: true });
14
+ fs.writeFileSync(REPORT_PATH, buildMarkdown(report, globals, testResults));
15
+ } catch (err) {
16
+ errorExit(err, 'coverage/write-summary-report');
17
+ }
18
+ print.info(`Wrote ${REPORT_PATH}\n`);
19
+ }
20
+
21
+ function buildMarkdown(report, globals, testResults) {
22
+ const rows = report
23
+ .slice()
24
+ .sort((a, b) => a.file.localeCompare(b.file))
25
+ .map(r => `| ${toTsPath(r.file)} | ${r.linesPct.toFixed(1)} | ${r.functionsPct.toFixed(1)} | ${r.branchesPct.toFixed(1)} |`)
26
+ .join('\n');
27
+
28
+ const threshold = config.testinNutin.coverage?.threshold;
29
+ const thresholdLine = typeof threshold === 'number' ? `\n- Threshold: ${threshold}%` : '';
30
+
31
+ const { todo, passed, failed, elapsedTime } = testResults;
32
+ const total = passed + failed;
33
+ const todoLine = todo > 0 ? `\n- ⚠️ To do: ${todo}` : '';
34
+
35
+ return `# Coverage Summary
36
+
37
+ ## Test Summary
38
+
39
+ - ✓ ${passed} passed
40
+ - ✗ ${failed} failed
41
+ - 📖 ${total} total
42
+ - ⌚️ Time: ${elapsedTime.toFixed(2)}ms${todoLine}
43
+
44
+ | File | Lines % | Functions % | Branches % |
45
+ |---|---|---|---|
46
+ ${rows}
47
+
48
+ ## Global
49
+
50
+ - Lines: ${globals.globalLinesPct.toFixed(1)}%
51
+ - Functions: ${globals.globalFunctionsPct.toFixed(1)}%
52
+ - Branches: ${globals.globalBranchesPct.toFixed(1)}%${thresholdLine}
53
+ `;
54
+ }
@@ -0,0 +1,44 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { print } from '../printer.js';
4
+ import { errorExit } from '../../../utils/index.js';
5
+ import config from '#root/nutin.config.js';
6
+
7
+ const REPORT_PATH = path.join('coverage', 'uncovered.md');
8
+
9
+ export async function maybeWriteUncoveredReport(report) {
10
+ const uncovered = report.filter(r => r.uncoveredLines.length || r.uncoveredBranches.length || r.uncoveredFunctions.length);
11
+ if (!uncovered.length) {
12
+ print.info('🎉 No uncovered code found.\n');
13
+ return;
14
+ }
15
+
16
+ if (!config.testinNutin.coverage.reportUncovered) return;
17
+
18
+ try {
19
+ fs.mkdirSync(path.dirname(REPORT_PATH), { recursive: true });
20
+ fs.writeFileSync(REPORT_PATH, buildMarkdown(uncovered));
21
+ } catch (err) {
22
+ errorExit(err, 'coverage/write-uncovered-report');
23
+ }
24
+ print.info(`Wrote ${REPORT_PATH}\n`);
25
+ }
26
+
27
+ function buildMarkdown(uncovered) {
28
+ const sections = uncovered
29
+ .sort((a, b) => a.file.localeCompare(b.file))
30
+ .map(r => {
31
+ const branches = r.uncoveredBranches.length
32
+ ? `Uncovered branches (lines): ${r.uncoveredBranches.join(', ')}\n`
33
+ : '';
34
+ const fns = r.uncoveredFunctions.length
35
+ ? `Uncovered functions:\n${r.uncoveredFunctions.map(f => `- \`${f.name}\` (line ${f.line})`).join('\n')}\n`
36
+ : '';
37
+ const lines = r.uncoveredLines.length
38
+ ? `Uncovered lines: ${r.uncoveredLines.join(', ')}\n`
39
+ : '';
40
+ return `## ${r.file}\n\n${branches}${fns}${lines}`;
41
+ });
42
+
43
+ return `# Uncovered Code\n\nIMPORTANT: Line numbers refer to **compiled** .js code (\`dist/src/\`).\n\n${sections.join('\n')}`;
44
+ }
@@ -1,8 +1,17 @@
1
+ class AssertionError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = 'AssertionError';
5
+ }
6
+ }
7
+
8
+ global.AssertionError = AssertionError;
9
+
1
10
  global.expect = (actual) => {
2
11
  const expectObj = {
3
12
  toBe(expected) {
4
13
  if (actual !== expected) {
5
- throw new Error(`Expected ${actual} to be strictly equal to ${expected}`);
14
+ throw new AssertionError(`Expected ${actual} to be strictly equal to ${expected}`);
6
15
  }
7
16
  },
8
17
  toEqual(expected) {
@@ -22,42 +31,42 @@ global.expect = (actual) => {
22
31
  };
23
32
 
24
33
  if (!deepEqual(actual, expected)) {
25
- throw new Error(`Expected ${JSON.stringify(actual)} to deeply equal ${JSON.stringify(expected)}`);
34
+ throw new AssertionError(`Expected ${JSON.stringify(actual)} to deeply equal ${JSON.stringify(expected)}`);
26
35
  }
27
36
  },
28
37
  toContain(substring) {
29
38
  if (!actual.includes(substring)) {
30
- throw new Error(`Expected '${actual}' to contain '${substring}'`);
39
+ throw new AssertionError(`Expected '${actual}' to contain '${substring}'`);
31
40
  }
32
41
  },
33
42
  toBeTruthy() {
34
43
  if (!actual) {
35
- throw new Error(`Expected ${actual} to be truthy`);
44
+ throw new AssertionError(`Expected ${actual} to be truthy`);
36
45
  }
37
46
  },
38
47
  toBeFalsy() {
39
48
  if (actual) {
40
- throw new Error(`Expected ${actual} to be falsy`);
49
+ throw new AssertionError(`Expected ${actual} to be falsy`);
41
50
  }
42
51
  },
43
52
  toBeDefined() {
44
53
  if (actual === null || actual === undefined) {
45
- throw new Error(`Expected value to be defined, but got ${actual}`);
54
+ throw new AssertionError(`Expected value to be defined, but got ${actual}`);
46
55
  }
47
56
  },
48
57
  toBeUndefined() {
49
58
  if (actual !== null && actual !== undefined) {
50
- throw new Error(`Expected value to be undefined, but got ${actual}`);
59
+ throw new AssertionError(`Expected value to be undefined, but got ${actual}`);
51
60
  }
52
61
  },
53
62
  toBeInstanceOf(constructor) {
54
63
  if (!(actual instanceof constructor)) {
55
- throw new Error(`Expected ${actual} to be instance of ${constructor.name}`);
64
+ throw new AssertionError(`Expected ${actual} to be instance of ${constructor.name}`);
56
65
  }
57
66
  },
58
67
  toHaveBeenCalled() {
59
68
  if (!actual?.calls || actual.calls.length === 0) {
60
- throw new Error(`Expected function to have been called, but it was not`);
69
+ throw new AssertionError(`Expected function to have been called, but it was not`);
61
70
  }
62
71
  },
63
72
  toHaveBeenCalledWith(...expectedArgs) {
@@ -85,7 +94,7 @@ global.expect = (actual) => {
85
94
  );
86
95
 
87
96
  if (!matched) {
88
- throw new Error(
97
+ throw new AssertionError(
89
98
  `Expected function to have been called with ${JSON.stringify(expectedArgs)}, but it was not.\n` +
90
99
  `Actual calls:\n${JSON.stringify(actual?.calls, null, 2)}`
91
100
  );
@@ -93,27 +102,40 @@ global.expect = (actual) => {
93
102
  },
94
103
  toBeLessThan(expected) {
95
104
  if (actual > expected) {
96
- throw new Error(`Expected ${actual} to be less than ${expected}`)
105
+ throw new AssertionError(`Expected ${actual} to be less than ${expected}`)
97
106
  }
98
107
  },
99
108
  toBeGreaterThan(expected) {
100
109
  if (actual < expected) {
101
- throw new Error(`Expected ${actual} to be greater than ${expected}`)
110
+ throw new AssertionError(`Expected ${actual} to be greater than ${expected}`)
102
111
  }
103
112
  },
104
113
  toThrow(expectedMessage) {
105
- let threw = false;
106
- try {
107
- actual();
108
- } catch (err) {
109
- threw = true;
114
+ const checkMessage = (err) => {
110
115
  if (expectedMessage && !err.message.includes(expectedMessage)) {
111
- throw new Error(`Expected error message to include "${expectedMessage}", but got "${err.message}"`);
116
+ throw new AssertionError(`Expected error message to include "${expectedMessage}", but got "${err.message}"`);
112
117
  }
118
+ };
119
+
120
+ let result;
121
+ try {
122
+ result = actual();
123
+ } catch (err) {
124
+ checkMessage(err);
125
+ return;
113
126
  }
114
- if (!threw) {
115
- throw new Error(`Expected function to throw, but it did not`);
127
+
128
+ // actual() returned a promise (async function) — it can only reject
129
+ // asynchronously, so return a promise the caller must await instead of
130
+ // throwing synchronously.
131
+ if (result && typeof result.then === 'function') {
132
+ return result.then(
133
+ () => { throw new AssertionError(`Expected function to throw, but it did not`); },
134
+ (err) => { checkMessage(err); }
135
+ );
116
136
  }
137
+
138
+ throw new AssertionError(`Expected function to throw, but it did not`);
117
139
  },
118
140
  };
119
141
 
@@ -124,12 +146,28 @@ global.expect = (actual) => {
124
146
  if (typeof fn !== 'function') continue;
125
147
 
126
148
  expectObj.not[name] = (...args) => {
149
+ const settle = (err) => {
150
+ if (err) {
151
+ if (err instanceof AssertionError) return;
152
+ throw err;
153
+ }
154
+ throw new AssertionError(`Expected NOT: ${name}(${JSON.stringify(args)})`);
155
+ };
156
+
157
+ let result;
127
158
  try {
128
- fn(...args);
129
- } catch {
130
- return;
159
+ result = fn(...args);
160
+ } catch (err) {
161
+ return settle(err);
131
162
  }
132
- throw new Error(`Expected NOT: ${name}(${JSON.stringify(args)})`);
163
+
164
+ // Only toThrow(), given an async function, can return a promise here —
165
+ // every other matcher (and toThrow given a sync function) is synchronous.
166
+ if (result && typeof result.then === 'function') {
167
+ return result.then(() => settle(), (err) => settle(err));
168
+ }
169
+
170
+ return settle();
133
171
  };
134
172
  }
135
173
  return expectObj;
@@ -0,0 +1,206 @@
1
+ let _installed = false;
2
+ let _now = 0; // virtual clock, ms since epoch
3
+ let _idSeq = 1; // fake timer id counter — starts at 1, never falsy
4
+ const _timers = new Map(); // id -> { id, type: 'timeout'|'interval', callback, args, delay, due }
5
+ let _originals = null; // real fns captured at install time
6
+ const MAX_LOOP_ITERATIONS = 100000;
7
+
8
+ function _schedule(type, cb, delay, args) {
9
+ if (typeof cb !== 'function') {
10
+ throw new Error(`${type === 'timeout' ? 'setTimeout' : 'setInterval'} requires a function callback`);
11
+ }
12
+ const id = _idSeq++;
13
+ const safeDelay = Math.max(0, Number(delay) || 0);
14
+ _timers.set(id, { id, type, callback: cb, args, delay: safeDelay, due: _now + safeDelay });
15
+ return id;
16
+ }
17
+
18
+ const fakeSetTimeout = (cb, delay = 0, ...args) => _schedule('timeout', cb, delay, args);
19
+ const fakeSetInterval = (cb, delay = 0, ...args) => _schedule('interval', cb, delay, args);
20
+ const fakeClearTimeout = (id) => { _timers.delete(id); };
21
+ const fakeClearInterval = fakeClearTimeout;
22
+
23
+ function _makeFakeDate(RealDate) {
24
+ return class FakeDate extends RealDate {
25
+ constructor(...args) {
26
+ if (args.length === 0) {
27
+ super(_now);
28
+ } else {
29
+ super(...args);
30
+ }
31
+ }
32
+ static now() {
33
+ return _now;
34
+ }
35
+ };
36
+ }
37
+
38
+ // Sugar over the fake timers rather than relying on jsdom-setup.js's
39
+ // `window.requestAnimationFrame ??= (cb) => setTimeout(cb, 0)` shim, which is
40
+ // dead code with `pretendToBeVisual: true` jsdom config
41
+ const fakeRequestAnimationFrame = (cb) => fakeSetTimeout(() => cb(_now), 16);
42
+ const fakeCancelAnimationFrame = (id) => fakeClearTimeout(id);
43
+
44
+ function _assertFake(name) {
45
+ if (!_installed) {
46
+ throw new Error(`${name}() requires useFakeTimers() to be called first`);
47
+ }
48
+ }
49
+
50
+ function _earliestDue(predicate) {
51
+ let best = null;
52
+ for (const timer of _timers.values()) {
53
+ if (predicate(timer) && (!best || timer.due < best.due || (timer.due === best.due && timer.id < best.id))) {
54
+ best = timer;
55
+ }
56
+ }
57
+ return best;
58
+ }
59
+
60
+ function _fire(timer) {
61
+ if (timer.type === 'timeout') {
62
+ _timers.delete(timer.id);
63
+ timer.callback(...timer.args);
64
+ } else {
65
+ timer.due += timer.delay;
66
+ timer.callback(...timer.args);
67
+ }
68
+ }
69
+
70
+ global.useFakeTimers = function useFakeTimers() {
71
+ if (_installed) return;
72
+
73
+ _originals = {
74
+ setTimeout: global.setTimeout,
75
+ clearTimeout: global.clearTimeout,
76
+ setInterval: global.setInterval,
77
+ clearInterval: global.clearInterval,
78
+ Date: global.Date,
79
+ requestAnimationFrame: global.requestAnimationFrame,
80
+ cancelAnimationFrame: global.cancelAnimationFrame,
81
+ windowSetTimeout: typeof window !== 'undefined' ? window.setTimeout : undefined,
82
+ windowClearTimeout: typeof window !== 'undefined' ? window.clearTimeout : undefined,
83
+ windowSetInterval: typeof window !== 'undefined' ? window.setInterval : undefined,
84
+ windowClearInterval: typeof window !== 'undefined' ? window.clearInterval : undefined,
85
+ windowDate: typeof window !== 'undefined' ? window.Date : undefined,
86
+ windowRequestAnimationFrame: typeof window !== 'undefined' ? window.requestAnimationFrame : undefined,
87
+ windowCancelAnimationFrame: typeof window !== 'undefined' ? window.cancelAnimationFrame : undefined,
88
+ };
89
+
90
+ _now = _originals.Date.now();
91
+ _idSeq = 1;
92
+ _timers.clear();
93
+
94
+ const FakeDate = _makeFakeDate(_originals.Date);
95
+
96
+ global.setTimeout = fakeSetTimeout;
97
+ global.clearTimeout = fakeClearTimeout;
98
+ global.setInterval = fakeSetInterval;
99
+ global.clearInterval = fakeClearInterval;
100
+ global.Date = FakeDate;
101
+ // Compiled app code calls the bare `requestAnimationFrame` identifier, which
102
+ // resolves via normal scope lookup to `global.requestAnimationFrame` — not
103
+ // `window.requestAnimationFrame` — so both must be faked, not just window's.
104
+ global.requestAnimationFrame = fakeRequestAnimationFrame;
105
+ global.cancelAnimationFrame = fakeCancelAnimationFrame;
106
+
107
+ if (typeof window !== 'undefined') {
108
+ window.setTimeout = fakeSetTimeout;
109
+ window.clearTimeout = fakeClearTimeout;
110
+ window.setInterval = fakeSetInterval;
111
+ window.clearInterval = fakeClearInterval;
112
+ window.Date = FakeDate;
113
+ window.requestAnimationFrame = fakeRequestAnimationFrame;
114
+ window.cancelAnimationFrame = fakeCancelAnimationFrame;
115
+ }
116
+
117
+ _installed = true;
118
+ };
119
+
120
+ global.useRealTimers = function useRealTimers() {
121
+ if (!_installed) return;
122
+
123
+ global.setTimeout = _originals.setTimeout;
124
+ global.clearTimeout = _originals.clearTimeout;
125
+ global.setInterval = _originals.setInterval;
126
+ global.clearInterval = _originals.clearInterval;
127
+ global.Date = _originals.Date;
128
+ global.requestAnimationFrame = _originals.requestAnimationFrame;
129
+ global.cancelAnimationFrame = _originals.cancelAnimationFrame;
130
+
131
+ if (typeof window !== 'undefined') {
132
+ window.setTimeout = _originals.windowSetTimeout;
133
+ window.clearTimeout = _originals.windowClearTimeout;
134
+ window.setInterval = _originals.windowSetInterval;
135
+ window.clearInterval = _originals.windowClearInterval;
136
+ window.Date = _originals.windowDate;
137
+ window.requestAnimationFrame = _originals.windowRequestAnimationFrame;
138
+ window.cancelAnimationFrame = _originals.windowCancelAnimationFrame;
139
+ }
140
+
141
+ _timers.clear();
142
+ _installed = false;
143
+ _originals = null;
144
+ };
145
+
146
+ global.advanceTimersByTime = function advanceTimersByTime(ms) {
147
+ _assertFake('advanceTimersByTime');
148
+ const target = _now + Math.max(0, Number(ms) || 0);
149
+ let iterations = 0;
150
+
151
+ for (;;) {
152
+ const next = _earliestDue(timer => timer.due <= target);
153
+ if (!next) break;
154
+ if (++iterations > MAX_LOOP_ITERATIONS) {
155
+ throw new Error(`advanceTimersByTime: exceeded ${MAX_LOOP_ITERATIONS} timer firings — likely an interval rescheduling itself with delay 0`);
156
+ }
157
+ _now = next.due;
158
+ _fire(next);
159
+ }
160
+
161
+ _now = target;
162
+ };
163
+
164
+ global.tick = global.advanceTimersByTime;
165
+
166
+ global.runAllTimers = function runAllTimers() {
167
+ _assertFake('runAllTimers');
168
+ let iterations = 0;
169
+
170
+ while (_timers.size > 0) {
171
+ if (++iterations > MAX_LOOP_ITERATIONS) {
172
+ throw new Error(`runAllTimers: aborting after ${MAX_LOOP_ITERATIONS} timers — likely an infinite loop (e.g. an uncleared setInterval)`);
173
+ }
174
+ const next = _earliestDue(() => true);
175
+ _now = Math.max(_now, next.due);
176
+ _fire(next);
177
+ }
178
+ };
179
+
180
+ global.runOnlyPendingTimers = function runOnlyPendingTimers() {
181
+ _assertFake('runOnlyPendingTimers');
182
+ const pendingIds = new Set(_timers.keys());
183
+
184
+ for (;;) {
185
+ const next = _earliestDue(timer => pendingIds.has(timer.id));
186
+ if (!next) break;
187
+ pendingIds.delete(next.id);
188
+ _now = Math.max(_now, next.due);
189
+ _fire(next);
190
+ }
191
+ };
192
+
193
+ global.clearAllTimers = function clearAllTimers() {
194
+ _assertFake('clearAllTimers');
195
+ _timers.clear();
196
+ };
197
+
198
+ global.getTimerCount = function getTimerCount() {
199
+ _assertFake('getTimerCount');
200
+ return _timers.size;
201
+ };
202
+
203
+ global.setSystemTime = function setSystemTime(msOrDate) {
204
+ _assertFake('setSystemTime');
205
+ _now = msOrDate instanceof Date ? msOrDate.getTime() : Number(msOrDate);
206
+ };