@nutin/cli 2.1.0 → 3.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 (241) hide show
  1. package/CHANGELOG.md +322 -0
  2. package/README.md +14 -6
  3. package/bin/add-feature.mjs +1 -1
  4. package/bin/cli.mjs +1 -1
  5. package/bin/create-app.mjs +1 -1
  6. package/bin/update-app.mjs +1 -1
  7. package/lib/index.mjs +6 -2
  8. package/lib/src/add-feature/base-template-patcher.mjs +139 -0
  9. package/lib/src/add-feature/feature-adder.mjs +20 -9
  10. package/lib/src/add-feature/feature-context-builder.mjs +1 -0
  11. package/lib/src/add-feature/nutin-config-updater.mjs +34 -8
  12. package/lib/src/add-feature/package-json-updater.mjs +7 -9
  13. package/lib/src/common/feature-registry.mjs +4 -0
  14. package/lib/src/common/file-generator.mjs +25 -3
  15. package/lib/src/common/git.mjs +31 -0
  16. package/lib/src/common/meta-bootstrap-prompt.mjs +28 -2
  17. package/lib/src/common/package-json-helper.mjs +14 -4
  18. package/lib/src/common/project-meta.mjs +2 -2
  19. package/lib/src/common/template-compiler.mjs +2 -0
  20. package/lib/src/common/transpile-ts.mjs +94 -0
  21. package/lib/src/create-app/context-builder.mjs +1 -0
  22. package/lib/src/create-app/git-manager.mjs +12 -2
  23. package/lib/src/create-app/json-manager.mjs +21 -18
  24. package/lib/src/create-app/new-app-prompt.mjs +4 -2
  25. package/lib/src/create-app/success-message.mjs +1 -1
  26. package/lib/src/update-app/conflict-report-writer.mjs +33 -21
  27. package/lib/src/update-app/json-drift.mjs +54 -0
  28. package/lib/src/update-app/project-updater.mjs +123 -14
  29. package/lib/src/update-app/template-differ.mjs +5 -5
  30. package/lib/src/update-app/three-way-merge.mjs +42 -0
  31. package/lib/src/update-app/update-context-builder.mjs +1 -0
  32. package/lib/src/update-app/update-summary-printer.mjs +10 -2
  33. package/package.json +12 -11
  34. package/templates/base/.gitignore.hbs +1 -0
  35. package/templates/base/AGENTS.md.hbs +45 -43
  36. package/templates/base/GETTING_STARTED.md.hbs +32 -23
  37. package/templates/base/README.md.hbs +7 -4
  38. package/templates/base/nutin.config.js.hbs +1 -1
  39. package/templates/base/src/app/components/.gitkeep +0 -0
  40. package/templates/base/src/app/globals.d.ts.hbs +6 -3
  41. package/templates/base/src/app/guards.ts.hbs +1 -3
  42. package/templates/base/src/app/main.ts.hbs +2 -9
  43. package/templates/base/src/app/routes.ts.hbs +9 -3
  44. package/templates/base/src/app/services/.gitkeep +0 -0
  45. package/templates/base/src/app/views/home/home.view.html.hbs +1 -1
  46. package/templates/base/src/app/views/home/home.view.ts.hbs +2 -2
  47. package/templates/base/src/app/views/not-found/not-found.view.ts.hbs +2 -2
  48. package/templates/base/src/core/base-classes/base-component/base-component.test.js.hbs +461 -1
  49. package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +39 -48
  50. package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.test.js.hbs +36 -1
  51. package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +47 -77
  52. package/templates/base/src/core/base-classes/base-component/helpers/children.helper.test.js.hbs +307 -21
  53. package/templates/base/src/core/base-classes/base-component/helpers/children.helper.ts.hbs +144 -26
  54. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.test.js.hbs +105 -4
  55. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +136 -62
  56. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.test.js.hbs +85 -4
  57. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +101 -51
  58. package/templates/base/src/core/base-classes/base-component/helpers/html.helper.test.js.hbs +404 -0
  59. package/templates/base/src/core/base-classes/base-component/helpers/html.helper.ts.hbs +242 -0
  60. package/templates/base/src/core/base-classes/base-component/helpers/i18n.helper.test.js.hbs +70 -1
  61. package/templates/base/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +54 -15
  62. package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.test.js.hbs +12 -1
  63. package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.ts.hbs +39 -36
  64. package/templates/base/src/core/base-classes/base-component/helpers/preservation.helper.test.js.hbs +179 -0
  65. package/templates/base/src/core/base-classes/base-component/helpers/preservation.helper.ts.hbs +93 -0
  66. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.test.js.hbs +135 -17
  67. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.ts.hbs +199 -81
  68. package/templates/base/src/core/base-classes/base-component/helpers/token.helper.test.js.hbs +40 -23
  69. package/templates/base/src/core/base-classes/base-component/helpers/token.helper.ts.hbs +67 -65
  70. package/templates/base/src/core/base-classes/component/component.test.js.hbs +17 -17
  71. package/templates/base/src/core/base-classes/component/component.ts.hbs +8 -41
  72. package/templates/base/src/core/base-classes/component/helpers/config.helper.test.js.hbs +1 -1
  73. package/templates/base/src/core/base-classes/component/helpers/config.helper.ts.hbs +37 -39
  74. package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.test.js.hbs +14 -1
  75. package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.ts.hbs +33 -31
  76. package/templates/base/src/core/base-classes/service/service.test.js.hbs +32 -0
  77. package/templates/base/src/core/base-classes/service/service.ts.hbs +21 -13
  78. package/templates/base/src/core/base-classes/view/view.test.js.hbs +4 -0
  79. package/templates/base/src/core/base-classes/view/view.ts.hbs +11 -8
  80. package/templates/base/src/core/index.ts.hbs +4 -2
  81. package/templates/base/src/core/internals.d.ts.hbs +199 -20
  82. package/templates/base/src/core/services/event-bus/event-bus.test.js.hbs +18 -0
  83. package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +26 -25
  84. package/templates/base/src/core/services/event-bus/lifecycle.facade.ts.hbs +3 -3
  85. package/templates/base/src/core/services/event-bus/navigation.facade.ts.hbs +3 -3
  86. package/templates/base/src/core/services/globals/globals.ts.hbs +7 -32
  87. package/templates/base/src/core/services/http-client/helpers/http-builder.helper.test.js.hbs +133 -50
  88. package/templates/base/src/core/services/http-client/helpers/http-builder.helper.ts.hbs +147 -39
  89. package/templates/base/src/core/services/http-client/helpers/http-manager.helper.test.js.hbs +75 -19
  90. package/templates/base/src/core/services/http-client/helpers/http-manager.helper.ts.hbs +73 -47
  91. package/templates/base/src/core/services/http-client/http-client.test.js.hbs +339 -232
  92. package/templates/base/src/core/services/http-client/http-client.ts.hbs +37 -25
  93. package/templates/base/src/core/services/i18n/i18n.test.js.hbs +57 -16
  94. package/templates/base/src/core/services/i18n/i18n.ts.hbs +42 -42
  95. package/templates/base/src/core/services/i18n/languages.ts.hbs +0 -2
  96. package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +4 -6
  97. package/templates/base/src/core/services/pipe-registry/pipes.test.js.hbs +10 -0
  98. package/templates/base/src/core/services/pipe-registry/pipes.ts.hbs +4 -2
  99. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.test.js.hbs +217 -4
  100. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +210 -65
  101. package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.test.js.hbs +26 -27
  102. package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.ts.hbs +38 -60
  103. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.test.js.hbs +4 -4
  104. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +30 -30
  105. package/templates/base/src/core/services/router/router.test.js.hbs +321 -0
  106. package/templates/base/src/core/services/router/router.ts.hbs +122 -54
  107. package/templates/base/src/index.html.hbs +1 -0
  108. package/templates/base/src/styles/_styles.scss.hbs +1 -113
  109. package/templates/base/src/styles/main.scss.hbs +1 -1
  110. package/templates/base/tools/builder/builder.config.js.hbs +6 -0
  111. package/templates/base/tools/builder/builder.js.hbs +26 -8
  112. package/templates/base/tools/builder/core/app/compile-ts.js.hbs +81 -6
  113. package/templates/base/tools/builder/core/app/copy-static.js.hbs +12 -1
  114. package/templates/base/tools/builder/core/app/handler-refs.js.hbs +114 -0
  115. package/templates/base/tools/builder/core/app/handler-refs.test.js.hbs +70 -0
  116. package/templates/base/tools/builder/core/app/paths.js.hbs +17 -0
  117. package/templates/base/tools/builder/core/app/route-keys.js.hbs +47 -0
  118. package/templates/base/tools/builder/core/app/route-keys.test.js.hbs +97 -0
  119. package/templates/base/tools/builder/core/app/ts-errors.js.hbs +58 -0
  120. package/templates/base/tools/builder/core/app/ts-errors.test.js.hbs +84 -0
  121. package/templates/base/tools/builder/core/app/validate-routes.js.hbs +21 -55
  122. package/templates/base/tools/builder/core/finalize-build.js.hbs +6 -0
  123. package/templates/base/tools/builder/core/html-index/add-tags.js.hbs +1 -4
  124. package/templates/base/tools/builder/core/html-index/add-tags.test.js.hbs +3 -3
  125. package/templates/base/tools/builder/core/html-index/validate-html.js.hbs +10 -2
  126. package/templates/base/tools/builder/core/html-templates/merge-templates.js.hbs +111 -23
  127. package/templates/base/tools/builder/core/html-templates/minify-templates.js.hbs +8 -0
  128. package/templates/base/tools/builder/core/html-templates/process-html-templates.js.hbs +5 -3
  129. package/templates/base/tools/builder/core/html-templates/template-literal.js.hbs +193 -0
  130. package/templates/base/tools/builder/core/html-templates/template-literal.test.js.hbs +69 -0
  131. package/templates/base/tools/builder/core/i18n/build-i18n.js.hbs +14 -6
  132. package/templates/base/tools/builder/core/prod-bundle/bundle-options.js.hbs +82 -0
  133. package/templates/base/tools/builder/core/prod-bundle/bundle-options.test.js.hbs +65 -0
  134. package/templates/base/tools/builder/core/prod-bundle/compress-files.js.hbs +6 -6
  135. package/templates/base/tools/builder/core/prod-bundle/esbuild.js.hbs +25 -8
  136. package/templates/base/tools/builder/core/prod-bundle/hash-files.js.hbs +4 -4
  137. package/templates/base/tools/builder/core/seo/generate-robots-txt.js.hbs +31 -13
  138. package/templates/base/tools/builder/core/seo/generate-robots-txt.test.js.hbs +48 -0
  139. package/templates/base/tools/builder/core/seo/generate-seo-files.js.hbs +3 -3
  140. package/templates/base/tools/builder/core/seo/generate-seo-html.js.hbs +61 -15
  141. package/templates/base/tools/builder/core/seo/generate-sitemap-xml.js.hbs +22 -7
  142. package/templates/base/tools/builder/core/seo/generate-sitemap-xml.test.js.hbs +30 -0
  143. package/templates/base/tools/builder/core/seo/html-substitutions.js.hbs +48 -18
  144. package/templates/base/tools/builder/core/seo/html-substitutions.test.js.hbs +45 -0
  145. package/templates/base/tools/builder/core/seo/route-output.js.hbs +26 -8
  146. package/templates/base/tools/builder/core/seo/route-output.test.js.hbs +31 -0
  147. package/templates/base/tools/builder/core/seo/seo-routes.js.hbs +29 -0
  148. package/templates/base/tools/builder/core/seo/seo-routes.test.js.hbs +21 -0
  149. package/templates/base/tools/builder/core/seo/ssr/ssr-bundle.js.hbs +6 -7
  150. package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.js.hbs +34 -5
  151. package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.test.js.hbs +48 -0
  152. package/templates/base/tools/builder/core/seo/ssr/ssr-render.js.hbs +26 -9
  153. package/templates/base/tools/builder/core/styles/sass.js.hbs +7 -5
  154. package/templates/base/tools/builder/core/styles/tailwind.js.hbs +19 -59
  155. package/templates/base/tools/dev/dev-serve.js.hbs +71 -7
  156. package/templates/base/tools/dev/serve.js.hbs +152 -22
  157. package/templates/base/tools/dev/watcher.js.hbs +75 -28
  158. package/templates/base/tools/generator/generator.js.hbs +23 -18
  159. package/templates/base/tools/generator/handle-file.js.hbs +4 -24
  160. package/templates/base/tools/generator/handle-file.test.js.hbs +4 -23
  161. package/templates/base/tools/generator/templates/component/component.template.js.hbs +3 -3
  162. package/templates/base/tools/generator/templates/component/component.template.test.js.hbs +3 -3
  163. package/templates/base/tools/generator/templates/service/service.template.js.hbs +1 -1
  164. package/templates/base/tools/generator/templates/service/service.template.test.js.hbs +1 -1
  165. package/templates/base/tools/generator/templates/view/view.template.js.hbs +2 -2
  166. package/templates/base/tools/generator/templates/view/view.template.test.js.hbs +2 -2
  167. package/templates/base/tools/testin-nutin/core/coverage/write-uncovered-report.js.hbs +1 -1
  168. package/templates/base/tools/testin-nutin/core/globals/assertion-lib.js.hbs +2 -2
  169. package/templates/base/tools/testin-nutin/core/{tests/assertions.test.js.hbs → globals/assertion-lib.test.js.hbs} +6 -0
  170. package/templates/base/tools/testin-nutin/core/globals/register-test-globals.js.hbs +6 -7
  171. package/templates/base/tools/testin-nutin/core/{tests/globals-each-all.test.js.hbs → globals/register-test-globals.test.js.hbs} +12 -0
  172. package/templates/base/tools/testin-nutin/core/printer.js.hbs +10 -4
  173. package/templates/base/tools/testin-nutin/core/queue/test-queue.js.hbs +8 -8
  174. package/templates/base/tools/testin-nutin/mocks/mock-event-bus.js.hbs +27 -25
  175. package/templates/base/tools/testin-nutin/mocks/mock-event-bus.test.js.hbs +30 -22
  176. package/templates/base/tools/testin-nutin/mocks/mock-http-client.js.hbs +7 -10
  177. package/templates/base/tools/testin-nutin/mocks/mock-http-client.test.js.hbs +12 -0
  178. package/templates/base/tools/testin-nutin/mocks/mock-i18n.js.hbs +47 -22
  179. package/templates/base/tools/testin-nutin/mocks/mock-i18n.test.js.hbs +47 -26
  180. package/templates/base/tools/testin-nutin/mocks/mock-router.js.hbs +19 -11
  181. package/templates/base/tools/testin-nutin/mocks/mock-router.test.js.hbs +24 -7
  182. package/templates/base/tools/testin-nutin/runner.js.hbs +12 -3
  183. package/templates/base/tools/testin-nutin/watch-tests.js.hbs +45 -20
  184. package/templates/base/tools/utils/build-lock.js.hbs +97 -0
  185. package/templates/base/tools/utils/ensure-deps.js.hbs +72 -0
  186. package/templates/base/tools/utils/ensure-deps.test.js.hbs +17 -0
  187. package/templates/base/tools/utils/format.js.hbs +1 -1
  188. package/templates/base/tools/utils/index.js.hbs +3 -1
  189. package/templates/base/tools/utils/print.js.hbs +2 -2
  190. package/templates/base/tools/utils/resolve-port.js.hbs +30 -0
  191. package/templates/base/tools/utils/resolve-port.test.js.hbs +47 -0
  192. package/templates/base/tools/utils/run-command.js.hbs +30 -1
  193. package/templates/base/tools/utils/run-command.test.js.hbs +12 -1
  194. package/templates/base/tools/utils/run-script.js.hbs +2 -2
  195. package/templates/features/docker/tools/docker/Dockerfile.template.hbs +5 -2
  196. package/templates/features/docker/tools/docker/nginx.conf.template +19 -2
  197. package/templates/features/docker/tools/docker/scripts/docker-ports.js.hbs +1 -1
  198. package/templates/features/docker/tools/docker/scripts/docker-ports.test.js.hbs +2 -2
  199. package/templates/features/docker/tools/docker/scripts/locale-redirects.js.hbs +28 -0
  200. package/templates/features/docker/tools/docker/scripts/locale-redirects.test.js.hbs +34 -0
  201. package/templates/features/docker/tools/docker/scripts/markdown-sources-copy.js.hbs +25 -0
  202. package/templates/features/docker/tools/docker/scripts/markdown-sources-copy.test.js.hbs +34 -0
  203. package/templates/features/docker/tools/docker/scripts/render-docker-template.js.hbs +3 -1
  204. package/templates/features/docker/tools/docker/scripts/render-docker-template.test.js.hbs +12 -0
  205. package/templates/features/docker/tools/docker/scripts/validate-docker.js.hbs +29 -0
  206. package/templates/features/markdown/markdown-content/getting-started.md.hbs +28 -0
  207. package/templates/features/markdown/markdown-content/writing-pages.md.hbs +19 -0
  208. package/templates/features/markdown/src/app/markdown/components/markdown-content/markdown-content.component.scss.hbs +75 -0
  209. package/templates/features/markdown/src/app/markdown/components/markdown-content/markdown-content.component.test.js.hbs +40 -0
  210. package/templates/features/markdown/src/app/markdown/components/markdown-content/markdown-content.component.ts.hbs +65 -0
  211. package/templates/features/markdown/src/app/markdown/components/markdown-landing/markdown-landing.component.test.js.hbs +67 -0
  212. package/templates/features/markdown/src/app/markdown/components/markdown-landing/markdown-landing.component.ts.hbs +87 -0
  213. package/templates/features/markdown/src/app/markdown/components/markdown-nav/markdown-nav.component.scss.hbs +42 -0
  214. package/templates/features/markdown/src/app/markdown/components/markdown-nav/markdown-nav.component.test.js.hbs +59 -0
  215. package/templates/features/markdown/src/app/markdown/components/markdown-nav/markdown-nav.component.ts.hbs +72 -0
  216. package/templates/features/markdown/src/app/markdown/locales/en.json.hbs +6 -0
  217. package/templates/features/markdown/src/app/markdown/markdown-guards.test.js.hbs +63 -0
  218. package/templates/features/markdown/src/app/markdown/markdown-guards.ts.hbs +31 -0
  219. package/templates/features/markdown/src/app/markdown/markdown-i18n.test.js.hbs +78 -0
  220. package/templates/features/markdown/src/app/markdown/markdown-i18n.ts.hbs +31 -0
  221. package/templates/features/markdown/src/app/markdown/markdown-manifest.test.js.hbs +98 -0
  222. package/templates/features/markdown/src/app/markdown/markdown-manifest.ts.hbs +157 -0
  223. package/templates/features/markdown/src/app/markdown/markdown-manifests.service.test.js.hbs +87 -0
  224. package/templates/features/markdown/src/app/markdown/markdown-manifests.service.ts.hbs +50 -0
  225. package/templates/features/markdown/src/app/markdown/markdown-routes.test.js.hbs +74 -0
  226. package/templates/features/markdown/src/app/markdown/markdown-routes.ts.hbs +26 -0
  227. package/templates/features/markdown/src/app/markdown/markdown-sources.test.js.hbs +22 -0
  228. package/templates/features/markdown/src/app/markdown/markdown-sources.ts.hbs +42 -0
  229. package/templates/features/markdown/src/app/markdown/view/markdown.view.html.hbs +4 -0
  230. package/templates/features/markdown/src/app/markdown/view/markdown.view.scss.hbs +19 -0
  231. package/templates/features/markdown/src/app/markdown/view/markdown.view.test.js.hbs +125 -0
  232. package/templates/features/markdown/src/app/markdown/view/markdown.view.ts.hbs +146 -0
  233. package/templates/features/markdown/tools/builder/core/markdown/generate-markdown-pages.js.hbs +71 -0
  234. package/templates/features/markdown/tools/builder/core/markdown/markdown-compiler.js.hbs +582 -0
  235. package/templates/features/markdown/tools/builder/core/markdown/markdown-compiler.test.js.hbs +390 -0
  236. package/templates/features/markdown/tools/builder/core/markdown/markdown-seo-routes.js.hbs +155 -0
  237. package/templates/features/markdown/tools/builder/core/markdown/markdown-seo-routes.test.js.hbs +112 -0
  238. package/templates/base/src/app/components/index.ts.hbs +0 -1
  239. package/templates/base/src/app/services/index.ts.hbs +0 -1
  240. package/templates/base/src/app/views/index.ts.hbs +0 -3
  241. /package/templates/base/tools/testin-nutin/core/{tests → globals}/clock.test.js.hbs +0 -0
@@ -1,22 +1,19 @@
1
1
  import fs from 'fs';
2
2
  import os from 'os';
3
3
  import path from 'path';
4
- import { generateFile, appendToIndex } from './handle-file.js';
4
+ import { generateFile } from './handle-file.js';
5
5
 
6
6
  describe('handle-file', () => {
7
7
  let tmpDir;
8
- let originalCwd;
9
8
  let exitSpy;
10
9
 
11
10
  beforeEach(() => {
12
11
  tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nutin-handle-file-'));
13
- originalCwd = process.cwd();
14
12
  exitSpy = spyOn(process, 'exit').andCallFake(() => {});
15
13
  });
16
14
 
17
15
  afterEach(() => {
18
16
  exitSpy.restore();
19
- process.chdir(originalCwd);
20
17
  fs.rmSync(tmpDir, { recursive: true, force: true });
21
18
  });
22
19
 
@@ -26,7 +23,7 @@ describe('handle-file', () => {
26
23
 
27
24
  generateFile({ name, targetPath, templateFn: (n) => `content for ${n.kebab}`, suffix: 'component' });
28
25
 
29
- const filePath = path.join(targetPath, 'widget.component.ts');
26
+ const filePath = path.join(targetPath, 'widget.component.{{#if (eq lang "js")}}js{{else}}ts{{/if}}');
30
27
  expect(fs.existsSync(filePath)).toBeTruthy();
31
28
  expect(fs.readFileSync(filePath, 'utf8')).toBe('content for widget');
32
29
  expect(exitSpy.callCount).toBe(0);
@@ -36,9 +33,9 @@ describe('handle-file', () => {
36
33
  const targetPath = path.join(tmpDir, 'services', 'widget');
37
34
  const name = { kebab: 'widget', pascal: 'Widget', camel: 'widget', capitalized: 'Widget' };
38
35
 
39
- generateFile({ name, targetPath, templateFn: () => 'x', suffix: 'service', extension: 'js' });
36
+ generateFile({ name, targetPath, templateFn: () => 'x', suffix: 'service', extension: 'scss' });
40
37
 
41
- expect(fs.existsSync(path.join(targetPath, 'widget.service.js'))).toBeTruthy();
38
+ expect(fs.existsSync(path.join(targetPath, 'widget.service.scss'))).toBeTruthy();
42
39
  });
43
40
 
44
41
  it('generateFile calls process.exit(1) instead of overwriting an existing file', () => {
@@ -53,20 +50,4 @@ describe('handle-file', () => {
53
50
  expect(exitSpy.callCount).toBe(1);
54
51
  expect(exitSpy.lastCall).toEqual([1]);
55
52
  });
56
-
57
- it('appendToIndex appends an export line to the barrel three segments up from targetPath', () => {
58
- process.chdir(tmpDir);
59
- const basePath = path.join('src', 'app', 'components');
60
- fs.mkdirSync(basePath, { recursive: true });
61
- fs.writeFileSync(path.join(basePath, 'index.ts'), "export * from './existing.js';\n");
62
-
63
- const targetPath = path.join('src', 'app', 'components', 'widget');
64
- const name = { kebab: 'widget', pascal: 'Widget', camel: 'widget', capitalized: 'Widget' };
65
-
66
- // appendToIndex prints a "index.ts updated" log via console.info
67
- silenceConsole('info', () => appendToIndex({ name, targetPath, suffix: 'component' }));
68
-
69
- const indexContent = fs.readFileSync(path.join(basePath, 'index.ts'), 'utf8');
70
- expect(indexContent).toBe("export * from './existing.js';\nexport * from './widget/widget.component.js';\n");
71
- });
72
53
  });
@@ -4,12 +4,12 @@ export const componentTemplate = (name, targetPath) => {
4
4
  const relToCore = getRelToCore(targetPath);
5
5
  const htmlTemplate = '__TEMPLATE_PLACEHOLDER__';
6
6
 
7
- return `import { Component } from '${relToCore}';
7
+ return `import { Component, html } from '${relToCore}';
8
8
 
9
- const templateFn = () => \`${htmlTemplate}\`;
9
+ const templateFn = () => html\`${htmlTemplate}\`;
10
10
 
11
11
  export class ${name.pascal}Component extends Component {
12
- constructor(mountTarget: HTMLElement) {
12
+ constructor({{#if (eq lang "js")}}mountTarget{{else}}mountTarget: HTMLElement{{/if}}) {
13
13
  super({templateFn, mountTarget});
14
14
  }
15
15
  }
@@ -8,12 +8,12 @@ describe('componentTemplate', () => {
8
8
  // The generator always leaves the html body as a placeholder — the builder
9
9
  // swaps in the .html sibling's content at build time.
10
10
  expect(componentTemplate(name, targetPath)).toBe(
11
- `import { Component } from '../../../core/index.js';
11
+ `import { Component, html } from '../../../core/index.js';
12
12
 
13
- const templateFn = () => \`__TEMPLATE_PLACEHOLDER__\`;
13
+ const templateFn = () => html\`__TEMPLATE_PLACEHOLDER__\`;
14
14
 
15
15
  export class WidgetComponent extends Component {
16
- constructor(mountTarget: HTMLElement) {
16
+ constructor({{#if (eq lang "js")}}mountTarget{{else}}mountTarget: HTMLElement{{/if}}) {
17
17
  super({templateFn, mountTarget});
18
18
  }
19
19
  }
@@ -5,7 +5,7 @@ export const serviceTemplate = (name, targetPath) => {
5
5
 
6
6
  return `import { Service } from '${relToCore}';
7
7
 
8
- export class ${name.pascal}Service extends Service<${name.pascal}Service> {
8
+ export class ${name.pascal}Service extends Service{{#unless (eq lang "js")}}<${name.pascal}Service>{{/unless}} {
9
9
  constructor() {
10
10
  super();
11
11
  }
@@ -8,7 +8,7 @@ describe('serviceTemplate', () => {
8
8
  expect(serviceTemplate(name, targetPath)).toBe(
9
9
  `import { Service } from '../../../core/index.js';
10
10
 
11
- export class WidgetService extends Service<WidgetService> {
11
+ export class WidgetService extends Service{{#unless (eq lang "js")}}<WidgetService>{{/unless}} {
12
12
  constructor() {
13
13
  super();
14
14
  }
@@ -4,9 +4,9 @@ export const viewTemplate = (name, targetPath) => {
4
4
  const relToCore = getRelToCore(targetPath);
5
5
  const htmlTemplate = '__TEMPLATE_PLACEHOLDER__';
6
6
 
7
- return `import { View } from '${relToCore}';
7
+ return `import { View, html } from '${relToCore}';
8
8
 
9
- const template = \`${htmlTemplate}\`;
9
+ const template = html\`${htmlTemplate}\`;
10
10
 
11
11
  export class ${name.pascal}View extends View {
12
12
  constructor() {
@@ -8,9 +8,9 @@ describe('viewTemplate', () => {
8
8
  // The generator always leaves the html body as a placeholder — the builder
9
9
  // swaps in the .html sibling's content at build time.
10
10
  expect(viewTemplate(name, targetPath)).toBe(
11
- `import { View } from '../../../core/index.js';
11
+ `import { View, html } from '../../../core/index.js';
12
12
 
13
- const template = \`__TEMPLATE_PLACEHOLDER__\`;
13
+ const template = html\`__TEMPLATE_PLACEHOLDER__\`;
14
14
 
15
15
  export class WidgetView extends View {
16
16
  constructor() {
@@ -13,7 +13,7 @@ export async function maybeWriteUncoveredReport(report) {
13
13
  return;
14
14
  }
15
15
 
16
- if (!config.testinNutin.coverage.reportUncovered) return;
16
+ if (!config.testinNutin.coverage?.reportUncovered) return;
17
17
 
18
18
  try {
19
19
  fs.mkdirSync(path.dirname(REPORT_PATH), { recursive: true });
@@ -101,12 +101,12 @@ global.expect = (actual) => {
101
101
  }
102
102
  },
103
103
  toBeLessThan(expected) {
104
- if (actual > expected) {
104
+ if (!(actual < expected)) {
105
105
  throw new AssertionError(`Expected ${actual} to be less than ${expected}`)
106
106
  }
107
107
  },
108
108
  toBeGreaterThan(expected) {
109
- if (actual < expected) {
109
+ if (!(actual > expected)) {
110
110
  throw new AssertionError(`Expected ${actual} to be greater than ${expected}`)
111
111
  }
112
112
  },
@@ -94,4 +94,10 @@ describe('assertions', () => {
94
94
  }
95
95
  expect(caught).toBeInstanceOf(TypeError);
96
96
  });
97
+ it('toBeLessThan / toBeGreaterThan are strict comparisons', () => {
98
+ expect(1).toBeLessThan(2);
99
+ expect(2).toBeGreaterThan(1);
100
+ expect(2).not.toBeLessThan(2);
101
+ expect(2).not.toBeGreaterThan(2);
102
+ });
97
103
  });
@@ -23,15 +23,14 @@ export function registerTestGlobals() {
23
23
  fn();
24
24
  };
25
25
 
26
+ // The suite's hooks object is shared by reference (read when the queue runs), so a hook
27
+ // registered after some it() calls in the same describe() still applies to them.
26
28
  global.it = (name, fn) => {
27
- addTest({
28
- suiteName: currentSuite,
29
- testName: name,
29
+ addTest({
30
+ suiteName: currentSuite,
31
+ testName: name,
30
32
  testFn: fn,
31
- beforeEach: currentSuiteHooks.beforeEach,
32
- beforeAll: currentSuiteHooks.beforeAll,
33
- afterEach: currentSuiteHooks.afterEach,
34
- afterAll: currentSuiteHooks.afterAll
33
+ hooks: currentSuiteHooks,
35
34
  });
36
35
  };
37
36
  global.it.todo = (s) => {
@@ -26,3 +26,15 @@ describe('globals beforeEach/All and afterEach/All', () => {
26
26
  expect(afterEachCount).toEqual(1);
27
27
  });
28
28
  })
29
+
30
+ describe('Hooks registered after it()', () => {
31
+ let beforeEachCount = 0;
32
+
33
+ it('still get the beforeEach declared further down', () => {
34
+ expect(beforeEachCount).toBe(1);
35
+ });
36
+
37
+ beforeEach(() => {
38
+ beforeEachCount++;
39
+ });
40
+ });
@@ -39,8 +39,9 @@ export function printStart() {
39
39
 
40
40
  export function printCoverageReport(report) {
41
41
  if (!report.length) {
42
- print.warn('No app source files were covered.');
43
- return { globalLinesPct: 100, globalFunctionsPct: 100, globalBranchesPct: 100 };
42
+ // 0, not 100: an empty report must not pass a coverage threshold by default.
43
+ print.warn('No source files were covered.');
44
+ return { globalLinesPct: 0, globalFunctionsPct: 0, globalBranchesPct: 0 };
44
45
  }
45
46
 
46
47
  print.boldBlue('\nCoverage Summary');
@@ -77,9 +78,14 @@ export function printCoverageReport(report) {
77
78
 
78
79
  export function toTsPath(input) {
79
80
  return input
80
- .split('/')
81
+ .split(/[\\/]/)
81
82
  .slice(1)
83
+ {{#if (eq lang "js")}}
84
+ .join('/');
85
+ {{else}}
82
86
  .join('/')
83
- .replace(/\.js$/, '.ts');
87
+ // Test files stay .test.js in TypeScript projects too.
88
+ .replace(/(?<!\.test)\.js$/, '.ts');
89
+ {{/if}}
84
90
  }
85
91
 
@@ -20,7 +20,6 @@ export function addTest(test) {
20
20
 
21
21
  export async function runQueuedTests() {
22
22
  printStart();
23
- const startTime = performance.now();
24
23
  const testAmount = testQueue.length;
25
24
  const results = [];
26
25
  let previousSuiteKey = null;
@@ -70,10 +69,10 @@ export async function runQueuedTests() {
70
69
 
71
70
  previousSuiteKey = suiteKey(test);
72
71
  previousSuite = test.suiteName;
73
- previousSuiteAfterAll = test.afterAll;
74
- if (test.beforeAll) {
72
+ previousSuiteAfterAll = test.hooks?.afterAll;
73
+ if (test.hooks?.beforeAll) {
75
74
  try {
76
- await test.beforeAll();
75
+ await test.hooks.beforeAll();
77
76
  } catch (err) {
78
77
  failed++;
79
78
  results.push({
@@ -99,7 +98,7 @@ export async function runQueuedTests() {
99
98
 
100
99
  let testError = null;
101
100
  try {
102
- if (test.beforeEach) await test.beforeEach();
101
+ if (test.hooks?.beforeEach) await test.hooks.beforeEach();
103
102
  await test.testFn();
104
103
  } catch (err) {
105
104
  testError = err;
@@ -107,7 +106,7 @@ export async function runQueuedTests() {
107
106
 
108
107
  let afterEachError = null;
109
108
  try {
110
- if (test.afterEach) await test.afterEach();
109
+ if (test.hooks?.afterEach) await test.hooks.afterEach();
111
110
  } catch (err) {
112
111
  afterEachError = err;
113
112
  }
@@ -145,8 +144,9 @@ export async function runQueuedTests() {
145
144
  teardownJsdom();
146
145
  }
147
146
 
148
- const endTime = performance.now();
149
- elapsedTime = (endTime - startTime);
147
+ // Since the process started (performance's time origin): Node startup, discovery and
148
+ // the test files' imports count too, not only running the queued tests.
149
+ elapsedTime = performance.now();
150
150
  printResults(results);
151
151
  printSummary(todo, passed, failed, elapsedTime);
152
152
 
@@ -1,45 +1,47 @@
1
1
  import { createMockMethod } from './create-mock-method.js';
2
2
 
3
+ // Mirrors EventBus's public API (subscribe/once/emit/off): every method is a trackable
4
+ // mock whose default implementation really dispatches, so subscribed callbacks run.
3
5
  export class MockEventBus {
4
6
  constructor() {
5
7
  this.handlers = {};
6
8
 
7
- this.subscribe = createMockMethod();
8
- this.emit = (...args) => {
9
- this.emit.calls.push(args);
10
- const [event, data] = args;
11
- const callbacks = this.handlers[event];
12
- if (callbacks) {
13
- callbacks.forEach(cb => cb(data));
14
- }
15
- };
16
- this.emit.calls = [];
17
- this.emit.mockReset = () => { this.emit.calls = []; };
18
-
19
- this.off = (event, callback) => {
20
- this.off.calls.push([event, callback]);
21
- if (!this.handlers[event]) return;
22
- this.handlers[event] = this.handlers[event].filter(cb => cb !== callback);
23
- };
24
- this.off.calls = [];
25
- this.off.mockReset = () => { this.off.calls = []; };
26
-
27
- this.on = createMockMethod((event, callback) => {
28
- if (!this.handlers[event]) this.handlers[event] = [];
29
- this.handlers[event].push(callback);
9
+ this.subscribe = createMockMethod((event, callback) => this._addHandler(event, callback, false));
10
+ this.once = createMockMethod((event, callback) => this._addHandler(event, callback, true));
11
+
12
+ this.emit = createMockMethod((event, data) => {
13
+ const entries = this.handlers[event];
14
+ if (!entries) return;
15
+ entries.slice().forEach((entry) => {
16
+ if (entry.once) this._removeHandler(event, entry.callback);
17
+ entry.callback(data);
18
+ });
19
+ });
20
+
21
+ this.off = createMockMethod((event, callback) => {
22
+ if (callback) this._removeHandler(event, callback);
23
+ else delete this.handlers[event];
30
24
  });
31
25
 
32
26
  this.onDestroy = createMockMethod();
33
27
  this.reset = this.reset.bind(this);
34
28
  }
35
29
 
30
+ _addHandler(event, callback, once) {
31
+ (this.handlers[event] ??= []).push({ callback, once });
32
+ }
33
+
34
+ _removeHandler(event, callback) {
35
+ if (!this.handlers[event]) return;
36
+ this.handlers[event] = this.handlers[event].filter((entry) => entry.callback !== callback);
37
+ }
38
+
36
39
  reset() {
37
40
  this.subscribe.mockReset();
41
+ this.once.mockReset();
38
42
  this.emit.mockReset();
39
43
  this.off.mockReset();
40
- this.on.mockReset();
41
44
  this.onDestroy.mockReset();
42
45
  this.handlers = {};
43
46
  }
44
47
  }
45
-
@@ -1,10 +1,10 @@
1
1
  import { MockEventBus } from './mock-event-bus.js';
2
2
 
3
3
  describe('MockEventBus', () => {
4
- it('on() + emit() dispatches the event data to the registered handler', () => {
4
+ it('subscribe() + emit() dispatches the event data to the subscribed callback', () => {
5
5
  const bus = new MockEventBus();
6
6
  let received;
7
- bus.on('greet', (data) => { received = data; });
7
+ bus.subscribe('greet', (data) => { received = data; });
8
8
 
9
9
  bus.emit('greet', 'hello');
10
10
 
@@ -12,53 +12,61 @@ describe('MockEventBus', () => {
12
12
  expect(bus.emit.calls).toEqual([['greet', 'hello']]);
13
13
  });
14
14
 
15
- it('on() is a trackable mock', () => {
15
+ it('subscribe() is a trackable mock', () => {
16
16
  const bus = new MockEventBus();
17
17
  const handler = () => {};
18
- bus.on('greet', handler);
18
+ bus.subscribe('greet', handler);
19
19
 
20
- expect(bus.on).toHaveBeenCalled();
21
- expect(bus.on).toHaveBeenCalledWith('greet', handler);
20
+ expect(bus.subscribe).toHaveBeenCalledWith('greet', handler);
22
21
  });
23
22
 
24
- it('off() removes a previously registered handler', () => {
23
+ it('once() callbacks run for the first emit only', () => {
25
24
  const bus = new MockEventBus();
26
25
  let callCount = 0;
27
- const handler = () => { callCount++; };
28
- bus.on('greet', handler);
29
- bus.off('greet', handler);
26
+ bus.once('greet', () => { callCount++; });
30
27
 
31
- bus.emit('greet', 'hello');
28
+ bus.emit('greet');
29
+ bus.emit('greet');
32
30
 
33
- expect(callCount).toBe(0);
31
+ expect(callCount).toBe(1);
34
32
  });
35
33
 
36
- it('subscribe() is a bare mock and does not actually register a handler', () => {
34
+ it('off(event, callback) removes that callback only', () => {
37
35
  const bus = new MockEventBus();
38
- let called = false;
39
- const handler = () => { called = true; };
36
+ let removed = 0;
37
+ let kept = 0;
38
+ const handler = () => { removed++; };
40
39
  bus.subscribe('greet', handler);
40
+ bus.subscribe('greet', () => { kept++; });
41
+ bus.off('greet', handler);
41
42
 
42
43
  bus.emit('greet', 'hello');
43
44
 
44
- expect(called).toBeFalsy();
45
- expect(bus.subscribe.calls.length).toBe(1);
46
- expect(bus.subscribe.calls[0][0]).toBe('greet');
47
- expect(bus.subscribe.calls[0][1]).toBe(handler);
45
+ expect(removed).toBe(0);
46
+ expect(kept).toBe(1);
47
+ });
48
+
49
+ it('off(event) removes every callback for that event', () => {
50
+ const bus = new MockEventBus();
51
+ let callCount = 0;
52
+ bus.subscribe('greet', () => { callCount++; });
53
+ bus.off('greet');
54
+
55
+ bus.emit('greet');
56
+
57
+ expect(callCount).toBe(0);
48
58
  });
49
59
 
50
60
  it('reset() clears handlers and call logs', () => {
51
61
  const bus = new MockEventBus();
52
62
  let callCount = 0;
53
- bus.on('greet', () => { callCount++; });
63
+ bus.subscribe('greet', () => { callCount++; });
54
64
  bus.emit('greet', 'x');
55
- bus.subscribe('greet', () => {});
56
65
 
57
66
  bus.reset();
58
67
 
59
68
  expect(bus.emit.calls).toEqual([]);
60
69
  expect(bus.subscribe.calls).toEqual([]);
61
- expect(bus.on.calls).toEqual([]);
62
70
 
63
71
  bus.emit('greet', 'y');
64
72
  expect(callCount).toBe(1); // only the pre-reset emit reached the handler
@@ -1,22 +1,19 @@
1
1
  import { createMockMethod } from './create-mock-method.js';
2
2
 
3
+ const METHODS = ['get', 'post', 'put', 'patch', 'delete', 'addRequestInterceptor', 'addResponseInterceptor', 'onDestroy'];
4
+
5
+ // Mirrors HttpClient's public API; every method is a bare mock (configure responses
6
+ // with e.g. `client.get.mockReturnValue(Promise.resolve(data))`).
3
7
  export class MockHttpClient {
4
8
  constructor() {
5
- this._defineHttpMethods();
6
- this.onDestroy = createMockMethod();
7
- this.reset = this.reset.bind(this);
8
- }
9
-
10
- _defineHttpMethods() {
11
- const methods = ['get', 'post', 'put', 'patch', 'delete'];
12
- for (const method of methods) {
9
+ for (const method of METHODS) {
13
10
  this[method] = createMockMethod();
14
11
  }
12
+ this.reset = this.reset.bind(this);
15
13
  }
16
14
 
17
15
  reset() {
18
- const methods = ['get', 'post', 'put', 'patch', 'delete', 'onDestroy'];
19
- for (const method of methods) {
16
+ for (const method of METHODS) {
20
17
  this[method].mockReset();
21
18
  }
22
19
  }
@@ -11,16 +11,28 @@ describe('MockHttpClient', () => {
11
11
  expect(client.put.calls).toEqual([]);
12
12
  });
13
13
 
14
+ it('exposes the interceptor registration methods as mocks', () => {
15
+ const client = new MockHttpClient();
16
+ const interceptor = () => {};
17
+ client.addRequestInterceptor(interceptor);
18
+ client.addResponseInterceptor(interceptor);
19
+
20
+ expect(client.addRequestInterceptor).toHaveBeenCalledWith(interceptor);
21
+ expect(client.addResponseInterceptor).toHaveBeenCalledWith(interceptor);
22
+ });
23
+
14
24
  it('reset() clears calls and configured return values on every method', () => {
15
25
  const client = new MockHttpClient();
16
26
  client.get.mockReturnValue('cached');
17
27
  client.get();
18
28
  client.delete('/widgets/1');
29
+ client.addRequestInterceptor(() => {});
19
30
 
20
31
  client.reset();
21
32
 
22
33
  expect(client.get.calls).toEqual([]);
23
34
  expect(client.delete.calls).toEqual([]);
35
+ expect(client.addRequestInterceptor.calls).toEqual([]);
24
36
  expect(client.get()).toBe(undefined);
25
37
  });
26
38
  });
@@ -1,5 +1,7 @@
1
1
  import { createMockMethod } from './create-mock-method.js';
2
2
 
3
+ // Mirrors I18n's public API with in-memory translations (seed them with
4
+ // setTranslations()/setDefaultTranslations()); no fetch, no localStorage.
3
5
  export class MockI18n {
4
6
  constructor(defaultLang = 'en', supportedLangs = ['en']) {
5
7
  this._DEFAULT_LANGUAGE = defaultLang;
@@ -7,39 +9,41 @@ export class MockI18n {
7
9
  this._translations = {};
8
10
  this._defaultTranslations = {};
9
11
  this._currentLanguage = defaultLang;
12
+ this._languageListeners = [];
10
13
 
11
14
  this.loadTranslations = createMockMethod(async (lang) => {
12
15
  this._currentLanguage = lang;
13
16
  });
14
17
 
15
- this.translate = createMockMethod((key, params) => {
16
- const value = this._translations[key] || this._defaultTranslations[key] || key;
17
- if (!params) return value;
18
- return Object.entries(params).reduce(
19
- (acc, [k, v]) => acc.replace(`{${k}}`, v),
20
- value
21
- );
18
+ this.setCurrentLanguage = createMockMethod(async (lang) => {
19
+ await this.loadTranslations(lang);
20
+ this._languageListeners.forEach((callback) => callback({ lang }));
22
21
  });
23
22
 
24
- this.initTranslations = createMockMethod(async () => {
25
- const lang = this._LANGUAGES.includes('browser') ? 'browser' : this._DEFAULT_LANGUAGE;
26
- await this.loadTranslations(lang);
23
+ // Returns the unsubscribe function, like I18n.onLanguageChange.
24
+ this.onLanguageChange = createMockMethod((callback) => {
25
+ this._languageListeners.push(callback);
26
+ return () => {
27
+ this._languageListeners = this._languageListeners.filter((listener) => listener !== callback);
28
+ };
27
29
  });
28
30
 
29
- this.resetTranslations = createMockMethod(() => {
30
- this._translations = {};
31
- this._defaultTranslations = {};
32
- this._currentLanguage = this._DEFAULT_LANGUAGE;
31
+ // Same lookup as I18n.translate: nested dot keys, default-language fallback,
32
+ // then the given textContent, then the key itself.
33
+ this.translate = createMockMethod((key, textContent) => {
34
+ return this._lookup(key) || textContent || key;
33
35
  });
34
36
 
35
- this.getBrowserLanguage = createMockMethod(() => 'en');
37
+ this.getTranslationObject = createMockMethod((key) => this._lookup(key) || null);
36
38
 
37
- this.loadDefaultTranslations = createMockMethod(async () => {
38
- this._defaultTranslations = { 'fallback.key': 'Fallback Value' };
39
+ this.initTranslations = createMockMethod(async () => {
40
+ await this.loadTranslations(this._DEFAULT_LANGUAGE);
39
41
  });
40
42
 
41
- this.getNestedValue = createMockMethod((obj, keys) => {
42
- return keys.reduce((acc, key) => acc?.[key], obj);
43
+ this.resetTranslations = createMockMethod(() => {
44
+ this._translations = {};
45
+ this._defaultTranslations = {};
46
+ this._currentLanguage = this._DEFAULT_LANGUAGE;
43
47
  });
44
48
 
45
49
  this.onDestroy = createMockMethod(() => {
@@ -51,6 +55,26 @@ export class MockI18n {
51
55
  return this._currentLanguage;
52
56
  }
53
57
 
58
+ get defaultLanguage() {
59
+ return this._DEFAULT_LANGUAGE;
60
+ }
61
+
62
+ get languages() {
63
+ return this._LANGUAGES;
64
+ }
65
+
66
+ get localStorageKey() {
67
+ return 'nutin-fav-lang';
68
+ }
69
+
70
+ _lookup(key) {
71
+ const keys = key.split('.');
72
+ const read = (obj) => keys.reduce((acc, k) => acc?.[k], obj);
73
+ let value = read(this._translations);
74
+ if (!value && this._currentLanguage !== this._DEFAULT_LANGUAGE) value = read(this._defaultTranslations);
75
+ return value;
76
+ }
77
+
54
78
  setTranslations(translations) {
55
79
  this._translations = translations;
56
80
  }
@@ -61,16 +85,17 @@ export class MockI18n {
61
85
 
62
86
  reset() {
63
87
  this.loadTranslations.mockReset();
88
+ this.setCurrentLanguage.mockReset();
89
+ this.onLanguageChange.mockReset();
64
90
  this.translate.mockReset();
91
+ this.getTranslationObject.mockReset();
65
92
  this.initTranslations.mockReset();
66
93
  this.resetTranslations.mockReset();
67
- this.getBrowserLanguage.mockReset();
68
- this.loadDefaultTranslations.mockReset();
69
- this.getNestedValue.mockReset();
70
94
  this.onDestroy.mockReset();
71
95
 
72
96
  this._translations = {};
73
97
  this._defaultTranslations = {};
74
98
  this._currentLanguage = this._DEFAULT_LANGUAGE;
99
+ this._languageListeners = [];
75
100
  }
76
101
  }