@testspectra/cli 1.0.70 → 1.1.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 (277) hide show
  1. package/LICENSE.md +48 -0
  2. package/README.md +36 -270
  3. package/bin/.testspectra-runner.hash +1 -0
  4. package/bin/spectra.js +1 -1
  5. package/bin/testspectra-runner +0 -0
  6. package/demo-app/assets/index-DPfv5_EF.js +116 -0
  7. package/demo-app/assets/index-gIwu2EMV.css +1 -0
  8. package/demo-app/index.html +2 -2
  9. package/dist/.tsbuildinfo +1 -0
  10. package/dist/commands/__tests__/cloud-run.test.d.ts +1 -0
  11. package/dist/commands/__tests__/cloud-run.test.js +93 -0
  12. package/dist/commands/__tests__/doctor.test.d.ts +1 -0
  13. package/dist/commands/__tests__/doctor.test.js +39 -0
  14. package/dist/commands/__tests__/init.test.d.ts +1 -0
  15. package/dist/commands/__tests__/init.test.js +148 -0
  16. package/dist/commands/__tests__/license.test.d.ts +1 -0
  17. package/dist/commands/__tests__/license.test.js +13 -0
  18. package/dist/commands/__tests__/refactor.test.d.ts +1 -0
  19. package/dist/commands/__tests__/refactor.test.js +64 -0
  20. package/dist/commands/__tests__/run-e2e.test.d.ts +1 -0
  21. package/dist/commands/__tests__/run-e2e.test.js +181 -0
  22. package/dist/commands/__tests__/run.test.d.ts +1 -0
  23. package/dist/commands/__tests__/run.test.js +197 -0
  24. package/dist/commands/__tests__/watch.test.d.ts +1 -0
  25. package/dist/commands/__tests__/watch.test.js +155 -0
  26. package/dist/commands/add.js +86 -85
  27. package/dist/commands/cloud-run.d.ts +11 -0
  28. package/dist/commands/cloud-run.js +118 -0
  29. package/dist/commands/demo.js +7 -7
  30. package/dist/commands/devices.d.ts +14 -2
  31. package/dist/commands/devices.js +44 -34
  32. package/dist/commands/doctor.d.ts +3 -1
  33. package/dist/commands/doctor.js +43 -264
  34. package/dist/commands/init.d.ts +1 -1
  35. package/dist/commands/init.js +206 -196
  36. package/dist/commands/install.d.ts +4 -0
  37. package/dist/commands/install.js +59 -0
  38. package/dist/commands/license.d.ts +4 -0
  39. package/dist/commands/license.js +30 -0
  40. package/dist/commands/refactor.d.ts +8 -0
  41. package/dist/commands/refactor.js +78 -0
  42. package/dist/commands/run.d.ts +22 -1
  43. package/dist/commands/run.js +252 -109
  44. package/dist/commands/sync-types.d.ts +4 -0
  45. package/dist/commands/sync-types.js +14 -0
  46. package/dist/commands/update.js +39 -43
  47. package/dist/commands/watch.d.ts +6 -0
  48. package/dist/commands/watch.js +47 -43
  49. package/dist/config/loader.d.ts +1 -1
  50. package/dist/config/loader.js +40 -25
  51. package/dist/config/schema.d.ts +117 -49
  52. package/dist/config/schema.js +41 -34
  53. package/dist/generator/__tests__/tsconfig-isolation.test.d.ts +1 -0
  54. package/dist/generator/__tests__/tsconfig-isolation.test.js +278 -0
  55. package/dist/generator/__tests__/type-generator.test.d.ts +1 -0
  56. package/dist/generator/__tests__/type-generator.test.js +386 -0
  57. package/dist/generator/index.d.ts +2 -0
  58. package/dist/generator/index.js +2 -0
  59. package/dist/generator/tsconfig-generator.d.ts +27 -0
  60. package/dist/generator/tsconfig-generator.js +391 -0
  61. package/dist/generator/type-generator.d.ts +36 -0
  62. package/dist/generator/type-generator.js +644 -0
  63. package/dist/index.d.ts +9 -5
  64. package/dist/index.js +124 -51
  65. package/dist/lifecycle/__tests__/lifecycle-engine.test.d.ts +1 -0
  66. package/dist/lifecycle/__tests__/lifecycle-engine.test.js +290 -0
  67. package/dist/lifecycle/hook-discovery.d.ts +24 -0
  68. package/dist/lifecycle/hook-discovery.js +60 -0
  69. package/dist/lifecycle/hook-dispatcher.d.ts +34 -0
  70. package/dist/lifecycle/hook-dispatcher.js +190 -0
  71. package/dist/lifecycle/index.d.ts +3 -0
  72. package/dist/lifecycle/index.js +3 -0
  73. package/dist/lifecycle/parallel-grouping.d.ts +27 -0
  74. package/dist/lifecycle/parallel-grouping.js +151 -0
  75. package/dist/mobile/__tests__/android-device-e2e.test.d.ts +1 -0
  76. package/dist/mobile/__tests__/android-device-e2e.test.js +53 -0
  77. package/dist/mobile/__tests__/device-pool.test.d.ts +1 -0
  78. package/dist/mobile/__tests__/device-pool.test.js +97 -0
  79. package/dist/mobile/device-pool.d.ts +23 -0
  80. package/dist/mobile/device-pool.js +1 -0
  81. package/dist/mobile/index.d.ts +1 -0
  82. package/dist/mobile/index.js +1 -0
  83. package/dist/refactor/__tests__/refactor-engine.test.d.ts +1 -0
  84. package/dist/refactor/__tests__/refactor-engine.test.js +175 -0
  85. package/dist/refactor/index.d.ts +1 -0
  86. package/dist/refactor/index.js +1 -0
  87. package/dist/refactor/refactor-engine.d.ts +25 -0
  88. package/dist/refactor/refactor-engine.js +255 -0
  89. package/dist/reporter/__tests__/semantic-formatter.test.d.ts +1 -0
  90. package/dist/reporter/__tests__/semantic-formatter.test.js +73 -0
  91. package/dist/reporter/semantic-formatter.d.ts +6 -0
  92. package/dist/reporter/semantic-formatter.js +191 -0
  93. package/dist/reporter/summary-view.d.ts +12 -0
  94. package/dist/reporter/summary-view.js +19 -0
  95. package/dist/reporter/terminal-stream.d.ts +32 -0
  96. package/dist/reporter/terminal-stream.js +82 -0
  97. package/dist/reporter/types.d.ts +2 -0
  98. package/dist/reporter/types.js +1 -0
  99. package/dist/runner/bridge.d.ts +10 -5
  100. package/dist/runner/bridge.js +69 -44
  101. package/dist/runner/reporter.d.ts +30 -2
  102. package/dist/runner/reporter.js +419 -13
  103. package/dist/server/demo.js +43 -44
  104. package/dist/types/generator.d.ts +1 -17
  105. package/dist/types/generator.js +1 -443
  106. package/dist/utils/demo-server.js +38 -35
  107. package/package.json +20 -12
  108. package/templates/default/CLAUDE.md +7 -5
  109. package/templates/default/fixtures/apiData.json +24 -0
  110. package/templates/default/package.json +13 -4
  111. package/templates/default/page-objects/MatchersPage/mobile.ts +41 -16
  112. package/templates/default/page-objects/MatchersPage/web.ts +14 -0
  113. package/templates/default/page-objects/NavigationPage/mobile.ts +10 -1
  114. package/templates/default/page-objects/PlaygroundPage/mobile.ts +27 -17
  115. package/templates/default/page-objects/PlaygroundPage/web.ts +2 -0
  116. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +11 -0
  117. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +12 -0
  118. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -0
  119. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +16 -0
  120. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +12 -0
  121. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -0
  122. package/templates/default/specs/ApiInterception/suite.md +9 -0
  123. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +7 -1
  124. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
  125. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +15 -1
  126. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +14 -4
  127. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +9 -1
  128. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +8 -3
  129. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +9 -1
  130. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
  131. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +27 -1
  132. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
  133. package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +4 -1
  134. package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +3 -3
  135. package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +4 -1
  136. package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +2 -2
  137. package/templates/default/spectra.config.ts +21 -14
  138. package/templates/default/support/actions/fillCredentials/mobile.action.ts +2 -2
  139. package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +2 -0
  140. package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +2 -2
  141. package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +3 -1
  142. package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +3 -3
  143. package/templates/default/tsconfig.json +3 -1
  144. package/templates/nx/.nx/workspace-data/d/daemon.log +168 -0
  145. package/templates/nx/CLAUDE.md +2 -2
  146. package/templates/nx/package.json +7 -5
  147. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +3 -0
  148. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +12 -0
  149. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -0
  150. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +3 -0
  151. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/spec.md +12 -0
  152. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -0
  153. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/suite.md +9 -0
  154. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
  155. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +14 -4
  156. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +8 -3
  157. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
  158. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
  159. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +3 -3
  160. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +2 -2
  161. package/templates/nx/pnpm-lock.yaml +6663 -0
  162. package/templates/nx/pnpm-workspace.yaml +2 -1
  163. package/templates/nx/shared/testing/fixtures/apiData.json +24 -0
  164. package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +12 -0
  165. package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +14 -0
  166. package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +2 -0
  167. package/templates/nx/shared/testing/project.json +19 -0
  168. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +2 -2
  169. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +1 -1
  170. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +3 -3
  171. package/templates/nx/spectra.config.ts +19 -12
  172. package/templates/nx/tsconfig.json +3 -1
  173. package/demo-app/assets/index-DAUlrHTr.css +0 -1
  174. package/demo-app/assets/index-DN2Kb0A8.js +0 -100
  175. package/dist/index.cjs +0 -101
  176. package/dist/plugin.cjs +0 -575
  177. package/dist/plugin.d.ts +0 -16
  178. package/dist/plugin.js +0 -184
  179. package/dist/step/index.d.ts +0 -6
  180. package/dist/step/index.js +0 -6
  181. package/dist/step/matchers.d.ts +0 -14
  182. package/dist/step/matchers.js +0 -114
  183. package/dist/step/proto.d.ts +0 -46
  184. package/dist/step/proto.js +0 -114
  185. package/dist/step/runner/collection.d.ts +0 -13
  186. package/dist/step/runner/collection.js +0 -51
  187. package/dist/step/runner/single.d.ts +0 -21
  188. package/dist/step/runner/single.js +0 -138
  189. package/dist/step/spectra.d.ts +0 -34
  190. package/dist/step/spectra.js +0 -156
  191. package/dist/step/types.d.ts +0 -32
  192. package/dist/step/types.js +0 -5
  193. package/templates/default/tsconfig.spectra.android.json +0 -46
  194. package/templates/default/tsconfig.spectra.ios.json +0 -46
  195. package/templates/default/tsconfig.spectra.json +0 -22
  196. package/templates/default/tsconfig.spectra.web.json +0 -53
  197. package/templates/nx/fixtures/playgroundData.json +0 -7
  198. package/templates/nx/global-hooks/after/mobile.hook.ts +0 -3
  199. package/templates/nx/global-hooks/after/web.hook.ts +0 -3
  200. package/templates/nx/global-hooks/before/mobile.hook.ts +0 -3
  201. package/templates/nx/global-hooks/before/web.hook.ts +0 -3
  202. package/templates/nx/page-objects/MatchersPage/mobile.ts +0 -21
  203. package/templates/nx/page-objects/MatchersPage/web.ts +0 -23
  204. package/templates/nx/page-objects/NavigationPage/mobile.ts +0 -5
  205. package/templates/nx/page-objects/NavigationPage/web.ts +0 -8
  206. package/templates/nx/page-objects/PlaygroundPage/mobile.ts +0 -26
  207. package/templates/nx/page-objects/PlaygroundPage/web.ts +0 -29
  208. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +0 -3
  209. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +0 -12
  210. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +0 -5
  211. package/templates/nx/specs/BrowserContext/suite.md +0 -9
  212. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -3
  213. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/spec.md +0 -12
  214. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -7
  215. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +0 -3
  216. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +0 -12
  217. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +0 -6
  218. package/templates/nx/specs/ContentMatchers/suite.md +0 -9
  219. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +0 -3
  220. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +0 -12
  221. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +0 -5
  222. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +0 -3
  223. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +0 -12
  224. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +0 -8
  225. package/templates/nx/specs/ElementMatchers/hooks/after/mobile.hook.ts +0 -1
  226. package/templates/nx/specs/ElementMatchers/hooks/after/web.hook.ts +0 -1
  227. package/templates/nx/specs/ElementMatchers/hooks/afterEach/mobile.hook.ts +0 -1
  228. package/templates/nx/specs/ElementMatchers/hooks/afterEach/web.hook.ts +0 -1
  229. package/templates/nx/specs/ElementMatchers/hooks/before/mobile.hook.ts +0 -1
  230. package/templates/nx/specs/ElementMatchers/hooks/before/web.hook.ts +0 -1
  231. package/templates/nx/specs/ElementMatchers/hooks/beforeEach/mobile.hook.ts +0 -1
  232. package/templates/nx/specs/ElementMatchers/hooks/beforeEach/web.hook.ts +0 -1
  233. package/templates/nx/specs/ElementMatchers/suite.md +0 -9
  234. package/templates/nx/specs/Playground/TC-0001-form-controls/mobile.test.ts +0 -3
  235. package/templates/nx/specs/Playground/TC-0001-form-controls/spec.md +0 -12
  236. package/templates/nx/specs/Playground/TC-0001-form-controls/web.test.ts +0 -6
  237. package/templates/nx/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +0 -3
  238. package/templates/nx/specs/Playground/TC-0002-toggle-elements/spec.md +0 -12
  239. package/templates/nx/specs/Playground/TC-0002-toggle-elements/web.test.ts +0 -6
  240. package/templates/nx/specs/Playground/hooks/after/mobile.hook.ts +0 -1
  241. package/templates/nx/specs/Playground/hooks/after/web.hook.ts +0 -1
  242. package/templates/nx/specs/Playground/hooks/afterEach/mobile.hook.ts +0 -1
  243. package/templates/nx/specs/Playground/hooks/afterEach/web.hook.ts +0 -1
  244. package/templates/nx/specs/Playground/hooks/before/mobile.hook.ts +0 -1
  245. package/templates/nx/specs/Playground/hooks/before/web.hook.ts +0 -1
  246. package/templates/nx/specs/Playground/hooks/beforeEach/mobile.hook.ts +0 -1
  247. package/templates/nx/specs/Playground/hooks/beforeEach/web.hook.ts +0 -1
  248. package/templates/nx/specs/Playground/suite.md +0 -9
  249. package/templates/nx/support/actions/fillCredentials/mobile.action.ts +0 -4
  250. package/templates/nx/support/actions/fillCredentials/web.action.ts +0 -4
  251. package/templates/nx/support/steps/togglePlaygroundStates/mobile.step.ts +0 -3
  252. package/templates/nx/support/steps/togglePlaygroundStates/step.md +0 -7
  253. package/templates/nx/support/steps/togglePlaygroundStates/web.step.ts +0 -5
  254. package/templates/nx/support/steps/verifyPlaygroundState/mobile.step.ts +0 -4
  255. package/templates/nx/support/steps/verifyPlaygroundState/step.md +0 -7
  256. package/templates/nx/support/steps/verifyPlaygroundState/web.step.ts +0 -6
  257. package/templates/nx/tsconfig.spectra.android.json +0 -39
  258. package/templates/nx/tsconfig.spectra.ios.json +0 -39
  259. package/templates/nx/tsconfig.spectra.json +0 -25
  260. package/templates/nx/tsconfig.spectra.shared.json +0 -25
  261. package/templates/nx/tsconfig.spectra.web.json +0 -45
  262. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
  263. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
  264. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
  265. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
  266. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
  267. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
  268. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
  269. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
  270. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
  271. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
  272. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
  273. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
  274. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
  275. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
  276. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
  277. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
@@ -0,0 +1,386 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
5
+ import ts from 'typescript';
6
+ import { TypeGenerator, PLATFORM_HIERARCHY } from '../type-generator.js';
7
+ import { startWatcher } from '../../commands/watch.js';
8
+ import { syncTypesCommand } from '../../commands/sync-types.js';
9
+ describe('TypeGenerator Engine (docs/v2/cli/ambient-types-generator.md)', () => {
10
+ let tmpDir;
11
+ beforeEach(() => {
12
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'testspectra-gen-test-'));
13
+ });
14
+ afterEach(() => {
15
+ try {
16
+ fs.rmSync(tmpDir, { recursive: true, force: true });
17
+ }
18
+ catch { }
19
+ });
20
+ it('generates all expected .d.ts files in .testspectra/types/', () => {
21
+ // Setup minimal workspace
22
+ fs.mkdirSync(path.join(tmpDir, 'page-objects', 'LoginPage'), { recursive: true });
23
+ fs.writeFileSync(path.join(tmpDir, 'page-objects', 'LoginPage', 'web.ts'), 'export default class LoginPage {}');
24
+ fs.mkdirSync(path.join(tmpDir, 'support', 'steps', 'loginAsAdmin'), { recursive: true });
25
+ fs.writeFileSync(path.join(tmpDir, 'support', 'steps', 'loginAsAdmin', 'web.step.ts'), 'export default async function () {}');
26
+ fs.mkdirSync(path.join(tmpDir, 'support', 'actions', 'safeClick'), { recursive: true });
27
+ fs.writeFileSync(path.join(tmpDir, 'support', 'actions', 'safeClick', 'web.action.ts'), 'export default async function () {}');
28
+ fs.mkdirSync(path.join(tmpDir, 'fixtures'), { recursive: true });
29
+ fs.writeFileSync(path.join(tmpDir, 'fixtures', 'users.json'), JSON.stringify({ admin: { id: 1 } }));
30
+ TypeGenerator.generateAll(tmpDir);
31
+ const typesDir = path.join(tmpDir, '.testspectra', 'types');
32
+ expect(fs.existsSync(path.join(typesDir, 'web.d.ts'))).toBe(true);
33
+ expect(fs.existsSync(path.join(typesDir, 'mobile.d.ts'))).toBe(true);
34
+ expect(fs.existsSync(path.join(typesDir, 'android.d.ts'))).toBe(true);
35
+ expect(fs.existsSync(path.join(typesDir, 'ios.d.ts'))).toBe(true);
36
+ expect(fs.existsSync(path.join(typesDir, 'common.d.ts'))).toBe(true);
37
+ expect(fs.existsSync(path.join(typesDir, 'fixtures.d.ts'))).toBe(true);
38
+ expect(fs.existsSync(path.join(typesDir, 'index.d.ts'))).toBe(true);
39
+ const webDts = fs.readFileSync(path.join(typesDir, 'web.d.ts'), 'utf-8');
40
+ expect(webDts).toContain('declare const LoginPage:');
41
+ expect(webDts).toContain('"loginAsAdmin":');
42
+ expect(webDts).toContain('"safeClick":');
43
+ expect(webDts).toContain('declare const Spectra: TestSpectraActions;');
44
+ expect(webDts).toContain('declare const Step: TestSpectraSteps;');
45
+ const fixturesDts = fs.readFileSync(path.join(typesDir, 'fixtures.d.ts'), 'utf-8');
46
+ expect(fixturesDts).toContain('readonly users: typeof import(');
47
+ });
48
+ it('validates zero-import TypeScript test compilation with zero diagnostic errors', () => {
49
+ // 1. Setup workspace with real TypeScript exports
50
+ const poDir = path.join(tmpDir, 'page-objects', 'LoginPage');
51
+ fs.mkdirSync(poDir, { recursive: true });
52
+ fs.writeFileSync(path.join(poDir, 'web.ts'), 'export default class LoginPage {\n static username = "#user";\n static async login() {}\n}');
53
+ const stepDir = path.join(tmpDir, 'support', 'steps', 'loginAsAdmin');
54
+ fs.mkdirSync(stepDir, { recursive: true });
55
+ fs.writeFileSync(path.join(stepDir, 'web.step.ts'), 'export default async function loginAsAdmin(role?: string) {}');
56
+ const fixDir = path.join(tmpDir, 'fixtures');
57
+ fs.mkdirSync(fixDir, { recursive: true });
58
+ fs.writeFileSync(path.join(fixDir, 'users.json'), JSON.stringify({ admin: { id: 101, email: 'admin@test.local' } }));
59
+ // Generate declarations
60
+ TypeGenerator.generateAll(tmpDir);
61
+ const webDtsPath = path.join(tmpDir, '.testspectra', 'types', 'web.d.ts');
62
+ const fixturesDtsPath = path.join(tmpDir, '.testspectra', 'types', 'fixtures.d.ts');
63
+ // 2. Write a test file that uses ZERO imports
64
+ const testFilePath = path.join(tmpDir, 'specs', 'auth.test.ts');
65
+ fs.mkdirSync(path.dirname(testFilePath), { recursive: true });
66
+ fs.writeFileSync(testFilePath, `// Zero manual imports!
67
+ async function verifyZeroImport() {
68
+ const user = Fixture.users.admin.email;
69
+ const selector = LoginPage.username;
70
+ await LoginPage.login();
71
+ await Step.loginAsAdmin("superadmin");
72
+ }
73
+ `);
74
+ // 3. Compile with TypeScript compiler API
75
+ const program = ts.createProgram([testFilePath, webDtsPath, fixturesDtsPath], {
76
+ noEmit: true,
77
+ target: ts.ScriptTarget.ES2022,
78
+ module: ts.ModuleKind.NodeNext,
79
+ moduleResolution: ts.ModuleResolutionKind.NodeNext,
80
+ skipLibCheck: true,
81
+ strict: true,
82
+ });
83
+ const diagnostics = ts.getPreEmitDiagnostics(program);
84
+ const relevantErrors = diagnostics.filter((d) => d.file?.fileName === testFilePath);
85
+ expect(relevantErrors.length).toBe(0);
86
+ });
87
+ it('enforces common.d.ts contains only universal common symbols and fixtures', () => {
88
+ // Create web-specific Page Object and common-specific Page Object
89
+ fs.mkdirSync(path.join(tmpDir, 'page-objects', 'WebOnlyPage'), { recursive: true });
90
+ fs.writeFileSync(path.join(tmpDir, 'page-objects', 'WebOnlyPage', 'web.ts'), 'export default class WebOnlyPage {}');
91
+ fs.mkdirSync(path.join(tmpDir, 'page-objects', 'CommonPage'), { recursive: true });
92
+ fs.writeFileSync(path.join(tmpDir, 'page-objects', 'CommonPage', 'common.ts'), 'export default class CommonPage {}');
93
+ // Create web-specific step and common step
94
+ fs.mkdirSync(path.join(tmpDir, 'support', 'steps', 'webStep'), { recursive: true });
95
+ fs.writeFileSync(path.join(tmpDir, 'support', 'steps', 'webStep', 'web.step.ts'), 'export default async function () {}');
96
+ fs.mkdirSync(path.join(tmpDir, 'support', 'steps', 'commonStep'), { recursive: true });
97
+ fs.writeFileSync(path.join(tmpDir, 'support', 'steps', 'commonStep', 'common.step.ts'), 'export default async function () {}');
98
+ TypeGenerator.generateAll(tmpDir);
99
+ const commonDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'common.d.ts'), 'utf-8');
100
+ // Must contain common
101
+ expect(commonDts).toContain('CommonPage');
102
+ expect(commonDts).toContain('commonStep');
103
+ // Must NOT contain web-only symbols
104
+ expect(commonDts).not.toContain('WebOnlyPage');
105
+ expect(commonDts).not.toContain('webStep');
106
+ });
107
+ it('strictly enforces platform inheritance across Android, iOS, Mobile, Web, and Common', () => {
108
+ expect(PLATFORM_HIERARCHY.web).toEqual(['web', 'common']);
109
+ expect(PLATFORM_HIERARCHY.android).toEqual(['android', 'mobile', 'common']);
110
+ expect(PLATFORM_HIERARCHY.ios).toEqual(['ios', 'mobile', 'common']);
111
+ expect(PLATFORM_HIERARCHY.mobile).toEqual(['mobile', 'common']);
112
+ expect(PLATFORM_HIERARCHY.common).toEqual(['common']);
113
+ // Setup platform specific Page Objects
114
+ const poDir = path.join(tmpDir, 'page-objects');
115
+ fs.mkdirSync(path.join(poDir, 'PlatformMatrix'), { recursive: true });
116
+ fs.writeFileSync(path.join(poDir, 'PlatformMatrix', 'android.ts'), 'export default class AndroidPO {}');
117
+ fs.writeFileSync(path.join(poDir, 'PlatformMatrix', 'ios.ts'), 'export default class IosPO {}');
118
+ fs.writeFileSync(path.join(poDir, 'PlatformMatrix', 'web.ts'), 'export default class WebPO {}');
119
+ fs.mkdirSync(path.join(poDir, 'MobileOnly'), { recursive: true });
120
+ fs.writeFileSync(path.join(poDir, 'MobileOnly', 'mobile.ts'), 'export default class MobileOnlyPO {}');
121
+ TypeGenerator.generateAll(tmpDir);
122
+ const typesDir = path.join(tmpDir, '.testspectra', 'types');
123
+ const androidDts = fs.readFileSync(path.join(typesDir, 'android.d.ts'), 'utf-8');
124
+ const iosDts = fs.readFileSync(path.join(typesDir, 'ios.d.ts'), 'utf-8');
125
+ const mobileDts = fs.readFileSync(path.join(typesDir, 'mobile.d.ts'), 'utf-8');
126
+ const webDts = fs.readFileSync(path.join(typesDir, 'web.d.ts'), 'utf-8');
127
+ // Android should resolve android.js and mobile.js, but NOT web.js or ios.js
128
+ expect(androidDts).toContain('PlatformMatrix/android.js');
129
+ expect(androidDts).toContain('MobileOnly/mobile.js');
130
+ expect(androidDts).not.toContain('PlatformMatrix/ios.js');
131
+ expect(androidDts).not.toContain('PlatformMatrix/web.js');
132
+ // iOS should resolve ios.js and mobile.js, but NOT web.js or android.js
133
+ expect(iosDts).toContain('PlatformMatrix/ios.js');
134
+ expect(iosDts).toContain('MobileOnly/mobile.js');
135
+ expect(iosDts).not.toContain('PlatformMatrix/android.js');
136
+ expect(iosDts).not.toContain('PlatformMatrix/web.js');
137
+ // Mobile should resolve mobile.js, but NOT android.js, ios.js, or web.js
138
+ expect(mobileDts).toContain('MobileOnly/mobile.js');
139
+ expect(mobileDts).not.toContain('PlatformMatrix/android.js');
140
+ expect(mobileDts).not.toContain('PlatformMatrix/ios.js');
141
+ expect(mobileDts).not.toContain('PlatformMatrix/web.js');
142
+ // Web should resolve web.js, but NOT mobile.js, android.js, or ios.js
143
+ expect(webDts).toContain('PlatformMatrix/web.js');
144
+ expect(webDts).not.toContain('MobileOnly/mobile.js');
145
+ expect(webDts).not.toContain('PlatformMatrix/android.js');
146
+ expect(webDts).not.toContain('PlatformMatrix/ios.js');
147
+ });
148
+ it('enforces strict ambient scope: excludes raw $ and $$ from global ambient namespace', () => {
149
+ TypeGenerator.generateAll(tmpDir);
150
+ const webDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'web.d.ts'), 'utf-8');
151
+ const mobileDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'mobile.d.ts'), 'utf-8');
152
+ // Raw $ and $$ must NOT be declared as global variables to enforce Spectra.get() / Spectra.getAll()
153
+ expect(webDts).not.toMatch(/declare\s+const\s+\$:/);
154
+ expect(webDts).not.toMatch(/declare\s+const\s+\$\$: /);
155
+ expect(mobileDts).not.toMatch(/declare\s+const\s+\$:/);
156
+ expect(mobileDts).not.toMatch(/declare\s+const\s+\$\$: /);
157
+ });
158
+ it('purges declarations when full directory is deleted', () => {
159
+ const stepDir = path.join(tmpDir, 'support', 'steps', 'loginAsAdmin');
160
+ fs.mkdirSync(stepDir, { recursive: true });
161
+ fs.writeFileSync(path.join(stepDir, 'web.step.ts'), 'export default async function () {}');
162
+ TypeGenerator.generateAll(tmpDir);
163
+ let webDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'web.d.ts'), 'utf-8');
164
+ expect(webDts).toContain('loginAsAdmin');
165
+ // Delete entire step folder
166
+ fs.rmSync(stepDir, { recursive: true, force: true });
167
+ TypeGenerator.generateAll(tmpDir);
168
+ webDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'web.d.ts'), 'utf-8');
169
+ expect(webDts).not.toContain('loginAsAdmin');
170
+ });
171
+ it('handles partial platform file deletion: purges from web while preserving in mobile/android/ios', () => {
172
+ const stepDir = path.join(tmpDir, 'support', 'steps', 'loginAsAdmin');
173
+ fs.mkdirSync(stepDir, { recursive: true });
174
+ fs.writeFileSync(path.join(stepDir, 'web.step.ts'), 'export default async function () {}');
175
+ fs.writeFileSync(path.join(stepDir, 'mobile.step.ts'), 'export default async function () {}');
176
+ TypeGenerator.generateAll(tmpDir);
177
+ let webDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'web.d.ts'), 'utf-8');
178
+ let mobileDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'mobile.d.ts'), 'utf-8');
179
+ let androidDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'android.d.ts'), 'utf-8');
180
+ let iosDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'ios.d.ts'), 'utf-8');
181
+ expect(webDts).toContain('loginAsAdmin');
182
+ expect(mobileDts).toContain('loginAsAdmin');
183
+ expect(androidDts).toContain('loginAsAdmin');
184
+ expect(iosDts).toContain('loginAsAdmin');
185
+ // Delete only web.step.ts
186
+ fs.unlinkSync(path.join(stepDir, 'web.step.ts'));
187
+ TypeGenerator.generateAll(tmpDir);
188
+ webDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'web.d.ts'), 'utf-8');
189
+ mobileDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'mobile.d.ts'), 'utf-8');
190
+ androidDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'android.d.ts'), 'utf-8');
191
+ iosDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'ios.d.ts'), 'utf-8');
192
+ // Purged from web
193
+ expect(webDts).not.toContain('loginAsAdmin');
194
+ // Preserved in mobile, android, ios
195
+ expect(mobileDts).toContain('loginAsAdmin');
196
+ expect(androidDts).toContain('loginAsAdmin');
197
+ expect(iosDts).toContain('loginAsAdmin');
198
+ });
199
+ it('falls back to common.ts when web.ts is deleted inside page object folder', () => {
200
+ const poDir = path.join(tmpDir, 'page-objects', 'LoginPage');
201
+ fs.mkdirSync(poDir, { recursive: true });
202
+ fs.writeFileSync(path.join(poDir, 'web.ts'), 'export default class LoginPageWeb {}');
203
+ fs.writeFileSync(path.join(poDir, 'common.ts'), 'export default class LoginPageCommon {}');
204
+ TypeGenerator.generateAll(tmpDir);
205
+ let webDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'web.d.ts'), 'utf-8');
206
+ expect(webDts).toContain('LoginPage/web.js');
207
+ // Delete web.ts, leaving common.ts
208
+ fs.unlinkSync(path.join(poDir, 'web.ts'));
209
+ TypeGenerator.generateAll(tmpDir);
210
+ webDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'web.d.ts'), 'utf-8');
211
+ // Still declared, gracefully falling back to common.js
212
+ expect(webDts).toContain('declare const LoginPage:');
213
+ expect(webDts).toContain('LoginPage/common.js');
214
+ });
215
+ it('manages fixture file lifecycle for json, yaml, and csv files', () => {
216
+ const fixturesDir = path.join(tmpDir, 'fixtures');
217
+ fs.mkdirSync(fixturesDir, { recursive: true });
218
+ fs.writeFileSync(path.join(fixturesDir, 'users.json'), JSON.stringify({ user1: 'test' }));
219
+ fs.writeFileSync(path.join(fixturesDir, 'config.yaml'), 'env: production\n');
220
+ fs.writeFileSync(path.join(fixturesDir, 'dataset.csv'), 'id,name\n1,Alpha\n');
221
+ TypeGenerator.generateAll(tmpDir);
222
+ let fixturesDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'fixtures.d.ts'), 'utf-8');
223
+ expect(fixturesDts).toContain('readonly users: typeof import(');
224
+ expect(fixturesDts).toContain('readonly config: string;');
225
+ expect(fixturesDts).toContain('readonly dataset: string;');
226
+ // Rename fixtures/users.json to fixtures/accounts.json
227
+ fs.renameSync(path.join(fixturesDir, 'users.json'), path.join(fixturesDir, 'accounts.json'));
228
+ TypeGenerator.generateAll(tmpDir);
229
+ fixturesDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'fixtures.d.ts'), 'utf-8');
230
+ expect(fixturesDts).not.toContain('readonly users:');
231
+ expect(fixturesDts).toContain('readonly accounts:');
232
+ // Delete accounts.json and dataset.csv
233
+ fs.unlinkSync(path.join(fixturesDir, 'accounts.json'));
234
+ fs.unlinkSync(path.join(fixturesDir, 'dataset.csv'));
235
+ TypeGenerator.generateAll(tmpDir);
236
+ fixturesDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'fixtures.d.ts'), 'utf-8');
237
+ expect(fixturesDts).not.toContain('readonly accounts:');
238
+ expect(fixturesDts).not.toContain('readonly dataset:');
239
+ expect(fixturesDts).toContain('readonly config: string;');
240
+ });
241
+ it('executes spectra sync-types CLI command directly', async () => {
242
+ fs.mkdirSync(path.join(tmpDir, 'page-objects', 'CartPage'), { recursive: true });
243
+ fs.writeFileSync(path.join(tmpDir, 'page-objects', 'CartPage', 'web.ts'), 'export default class CartPage {}');
244
+ await syncTypesCommand({ cwd: tmpDir });
245
+ const webDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'web.d.ts'), 'utf-8');
246
+ expect(webDts).toContain('declare const CartPage:');
247
+ });
248
+ it('updates declarations via watcher in debounced manner within <= 100ms', async () => {
249
+ const fixturesDir = path.join(tmpDir, 'fixtures');
250
+ fs.mkdirSync(fixturesDir, { recursive: true });
251
+ const handle = startWatcher(tmpDir, 30);
252
+ await new Promise((resolve) => setTimeout(resolve, 50));
253
+ try {
254
+ // Add a fixture
255
+ fs.writeFileSync(path.join(fixturesDir, 'products.json'), JSON.stringify({ item: 1 }));
256
+ const start = Date.now();
257
+ let found = false;
258
+ while (Date.now() - start < 2000) {
259
+ try {
260
+ const fixturesDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'fixtures.d.ts'), 'utf-8');
261
+ if (fixturesDts.includes('readonly products:')) {
262
+ found = true;
263
+ break;
264
+ }
265
+ }
266
+ catch { }
267
+ await new Promise((resolve) => setTimeout(resolve, 20));
268
+ }
269
+ expect(found).toBe(true);
270
+ const fixturesDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'fixtures.d.ts'), 'utf-8');
271
+ expect(fixturesDts).toContain('readonly products:');
272
+ }
273
+ finally {
274
+ handle.close();
275
+ }
276
+ });
277
+ it('discovers custom shared library directory configured in tsconfig.spectra.shared.json', () => {
278
+ const customSharedDir = path.join(tmpDir, 'libs', 'shared-e2e');
279
+ fs.mkdirSync(path.join(customSharedDir, 'page-objects', 'SharedNav'), { recursive: true });
280
+ fs.writeFileSync(path.join(customSharedDir, 'page-objects', 'SharedNav', 'common.ts'), 'export default class SharedNav {}');
281
+ // Write tsconfig.spectra.shared.json pointing to custom libs/shared-e2e
282
+ fs.writeFileSync(path.join(tmpDir, 'tsconfig.spectra.shared.json'), JSON.stringify({
283
+ include: ['.testspectra/types/shared/common.d.ts', 'libs/shared-e2e/**/*.ts'],
284
+ }), 'utf-8');
285
+ const scopes = TypeGenerator.getEntityScopes(tmpDir);
286
+ const sharedScope = scopes.find((s) => s.isShared);
287
+ expect(sharedScope).toBeDefined();
288
+ expect(sharedScope?.dir).toBe(path.resolve(customSharedDir));
289
+ TypeGenerator.generateAll(tmpDir);
290
+ const sharedCommonDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'shared', 'common.d.ts'), 'utf-8');
291
+ expect(sharedCommonDts).toContain('declare const SharedNav:');
292
+ });
293
+ it('nx monorepo: does NOT generate types from libs/ or packages/ in root scope', () => {
294
+ // Simulate an Nx monorepo: nx.json at root, page-objects only inside libs/shared-e2e
295
+ fs.writeFileSync(path.join(tmpDir, 'nx.json'), JSON.stringify({ version: 2 }), 'utf-8');
296
+ const libsSharedDir = path.join(tmpDir, 'libs', 'shared-e2e');
297
+ fs.mkdirSync(path.join(libsSharedDir, 'page-objects', 'LibPage'), { recursive: true });
298
+ fs.writeFileSync(path.join(libsSharedDir, 'page-objects', 'LibPage', 'common.ts'), 'export default class LibPage {}');
299
+ const pkgDir = path.join(tmpDir, 'packages', 'shared');
300
+ fs.mkdirSync(path.join(pkgDir, 'page-objects', 'PkgPage'), { recursive: true });
301
+ fs.writeFileSync(path.join(pkgDir, 'page-objects', 'PkgPage', 'common.ts'), 'export default class PkgPage {}');
302
+ // Also add a root-level page-object (should appear in root scope)
303
+ fs.mkdirSync(path.join(tmpDir, 'page-objects', 'RootPage'), { recursive: true });
304
+ fs.writeFileSync(path.join(tmpDir, 'page-objects', 'RootPage', 'common.ts'), 'export default class RootPage {}');
305
+ TypeGenerator.generateAll(tmpDir);
306
+ const rootCommonDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'common.d.ts'), 'utf-8');
307
+ // Root scope must contain the root-level page object
308
+ expect(rootCommonDts).toContain('RootPage');
309
+ // Root scope must NOT bleed in types from libs/ or packages/ subdirs
310
+ expect(rootCommonDts).not.toContain('LibPage');
311
+ expect(rootCommonDts).not.toContain('PkgPage');
312
+ });
313
+ it('nx monorepo: does NOT generate steps/actions from libs/ or packages/ in root scope', () => {
314
+ fs.writeFileSync(path.join(tmpDir, 'nx.json'), JSON.stringify({ version: 2 }), 'utf-8');
315
+ // Step inside libs/ — should stay in shared scope, not root scope
316
+ const libsStepDir = path.join(tmpDir, 'libs', 'shared-e2e', 'support', 'steps', 'sharedLogin');
317
+ fs.mkdirSync(libsStepDir, { recursive: true });
318
+ fs.writeFileSync(path.join(libsStepDir, 'common.step.ts'), 'export default async function sharedLogin() {}');
319
+ // Step inside packages/ — same expectation
320
+ const pkgStepDir = path.join(tmpDir, 'packages', 'testing', 'support', 'steps', 'pkgStep');
321
+ fs.mkdirSync(pkgStepDir, { recursive: true });
322
+ fs.writeFileSync(path.join(pkgStepDir, 'common.step.ts'), 'export default async function pkgStep() {}');
323
+ // Root-level step (should appear in root scope)
324
+ const rootStepDir = path.join(tmpDir, 'support', 'steps', 'rootLogin');
325
+ fs.mkdirSync(rootStepDir, { recursive: true });
326
+ fs.writeFileSync(path.join(rootStepDir, 'common.step.ts'), 'export default async function rootLogin() {}');
327
+ TypeGenerator.generateAll(tmpDir);
328
+ const rootCommonDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'common.d.ts'), 'utf-8');
329
+ // Root step is present
330
+ expect(rootCommonDts).toContain('rootLogin');
331
+ // Shared/pkg steps must NOT bleed into root scope
332
+ expect(rootCommonDts).not.toContain('sharedLogin');
333
+ expect(rootCommonDts).not.toContain('pkgStep');
334
+ });
335
+ it('does not generate features/ scope for templates/, src/, or build/ subdirectories', () => {
336
+ // Simulate a parent workspace that contains a templates/default/specs/ structure
337
+ // (e.g., when generateAll is run from cli/ which contains templates/default/)
338
+ const templatesDefault = path.join(tmpDir, 'templates', 'default');
339
+ fs.mkdirSync(path.join(templatesDefault, 'specs', 'Suite1', 'TC-001'), { recursive: true });
340
+ fs.writeFileSync(path.join(templatesDefault, 'specs', 'Suite1', 'TC-001', 'web.test.ts'), 'it("t", () => {});');
341
+ // Same for src/ and build/
342
+ const srcSpecs = path.join(tmpDir, 'src', 'app', 'specs');
343
+ fs.mkdirSync(srcSpecs, { recursive: true });
344
+ fs.writeFileSync(path.join(srcSpecs, 'foo.ts'), '');
345
+ const buildTests = path.join(tmpDir, 'build', 'tests');
346
+ fs.mkdirSync(buildTests, { recursive: true });
347
+ TypeGenerator.generateAll(tmpDir);
348
+ // features/default, features/app, features/build must NOT be generated
349
+ expect(fs.existsSync(path.join(tmpDir, '.testspectra', 'types', 'features', 'default'))).toBe(false);
350
+ expect(fs.existsSync(path.join(tmpDir, '.testspectra', 'types', 'features', 'app'))).toBe(false);
351
+ expect(fs.existsSync(path.join(tmpDir, '.testspectra', 'types', 'features', 'build'))).toBe(false);
352
+ });
353
+ it('discovers arbitrary custom shared library directory (e.g. core/testing) dynamically without candidate list', () => {
354
+ // Custom user folder structure: core/testing (shared) + apps/portal/e2e (feature)
355
+ const customShared = path.join(tmpDir, 'core', 'testing');
356
+ fs.mkdirSync(path.join(customShared, 'page-objects', 'CoreNav'), { recursive: true });
357
+ fs.writeFileSync(path.join(customShared, 'page-objects', 'CoreNav', 'common.ts'), 'export default class CoreNav {}');
358
+ fs.mkdirSync(path.join(customShared, 'support', 'steps', 'coreAuth'), { recursive: true });
359
+ fs.writeFileSync(path.join(customShared, 'support', 'steps', 'coreAuth', 'common.step.ts'), 'export default async function coreAuth() {}');
360
+ fs.mkdirSync(path.join(customShared, 'fixtures'), { recursive: true });
361
+ fs.writeFileSync(path.join(customShared, 'fixtures', 'appConfig.json'), JSON.stringify({ env: 'staging' }));
362
+ const portalFeature = path.join(tmpDir, 'apps', 'portal', 'e2e');
363
+ fs.mkdirSync(path.join(portalFeature, 'specs', 'Auth'), { recursive: true });
364
+ fs.writeFileSync(path.join(portalFeature, 'specs', 'Auth', 'portal.web.test.ts'), 'it("test", () => {});');
365
+ const scopes = TypeGenerator.getEntityScopes(tmpDir);
366
+ const sharedScope = scopes.find((s) => s.isShared);
367
+ const featureScope = scopes.find((s) => !s.isShared && s.name === 'portal');
368
+ expect(sharedScope).toBeDefined();
369
+ expect(sharedScope?.dir).toBe(path.resolve(customShared));
370
+ expect(featureScope).toBeDefined();
371
+ TypeGenerator.generateAll(tmpDir);
372
+ // Shared declarations generated dynamically in .testspectra/types/shared/
373
+ const sharedCommonDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'shared', 'common.d.ts'), 'utf-8');
374
+ expect(sharedCommonDts).toContain('declare const CoreNav:');
375
+ expect(sharedCommonDts).toContain('coreAuth');
376
+ // Feature declarations generated dynamically in .testspectra/types/features/portal/
377
+ const featureCommonDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'features', 'portal', 'common.d.ts'), 'utf-8');
378
+ expect(featureCommonDts).toContain('declare const CoreNav:');
379
+ expect(featureCommonDts).toContain('coreAuth');
380
+ // Pure monorepo with zero root entities: root platform .d.ts files must NOT be generated
381
+ expect(fs.existsSync(path.join(tmpDir, '.testspectra', 'types', 'android.d.ts'))).toBe(false);
382
+ expect(fs.existsSync(path.join(tmpDir, '.testspectra', 'types', 'ios.d.ts'))).toBe(false);
383
+ expect(fs.existsSync(path.join(tmpDir, '.testspectra', 'types', 'web.d.ts'))).toBe(false);
384
+ expect(fs.existsSync(path.join(tmpDir, '.testspectra', 'types', 'common.d.ts'))).toBe(false);
385
+ });
386
+ });
@@ -0,0 +1,2 @@
1
+ export * from './type-generator.js';
2
+ export * from './tsconfig-generator.js';
@@ -0,0 +1,2 @@
1
+ export * from './type-generator.js';
2
+ export * from './tsconfig-generator.js';
@@ -0,0 +1,27 @@
1
+ import { type EntityScope } from './type-generator.js';
2
+ export interface PlatformTsConfigOptions {
3
+ cwd: string;
4
+ force?: boolean;
5
+ }
6
+ export declare class TsConfigGenerator {
7
+ static getRootTsConfig(): object;
8
+ static getMasterSolutionConfig(references?: {
9
+ path: string;
10
+ }[]): object;
11
+ static getStemExcludes(prefix: string, stems: string[]): string[];
12
+ static getWebConfig(): object;
13
+ static getMobileConfig(): object;
14
+ static getAndroidConfig(): object;
15
+ static getIosConfig(): object;
16
+ static getCommonConfig(): object;
17
+ static getScopedConfig(options: {
18
+ cwd: string;
19
+ subConfigDir: string;
20
+ scopeDir: string;
21
+ relTypesDir: string;
22
+ platform: 'web' | 'mobile' | 'android' | 'ios' | 'common';
23
+ }): object;
24
+ static getEntityScopes(cwd: string): EntityScope[];
25
+ static writeConfigs(cwd: string, providedScopes?: EntityScope[]): Record<string, string>;
26
+ static scaffoldConfigs(cwd: string, force?: boolean): Record<string, string>;
27
+ }