@testspectra/cli 1.0.70 → 1.1.0-rc.1

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 (427) 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-BhlW-eXG.css +1 -0
  7. package/demo-app/assets/index-DOtRypCa.js +154 -0
  8. package/demo-app/index.html +2 -2
  9. package/dist/.tsbuildinfo +1 -0
  10. package/dist/commands/__tests__/doctor.test.d.ts +1 -0
  11. package/dist/commands/__tests__/doctor.test.js +39 -0
  12. package/dist/commands/__tests__/init.test.d.ts +1 -0
  13. package/dist/commands/__tests__/init.test.js +148 -0
  14. package/dist/commands/__tests__/license.test.d.ts +1 -0
  15. package/dist/commands/__tests__/license.test.js +13 -0
  16. package/dist/commands/__tests__/refactor.test.d.ts +1 -0
  17. package/dist/commands/__tests__/refactor.test.js +64 -0
  18. package/dist/commands/__tests__/run-e2e.test.d.ts +1 -0
  19. package/dist/commands/__tests__/run-e2e.test.js +422 -0
  20. package/dist/commands/__tests__/run.test.d.ts +1 -0
  21. package/dist/commands/__tests__/run.test.js +273 -0
  22. package/dist/commands/__tests__/watch.test.d.ts +1 -0
  23. package/dist/commands/__tests__/watch.test.js +155 -0
  24. package/dist/commands/add.js +86 -85
  25. package/dist/commands/demo.js +21 -11
  26. package/dist/commands/devices.d.ts +14 -2
  27. package/dist/commands/devices.js +44 -34
  28. package/dist/commands/doctor.d.ts +3 -1
  29. package/dist/commands/doctor.js +43 -264
  30. package/dist/commands/init.d.ts +1 -1
  31. package/dist/commands/init.js +211 -196
  32. package/dist/commands/install.d.ts +4 -0
  33. package/dist/commands/install.js +59 -0
  34. package/dist/commands/license.d.ts +4 -0
  35. package/dist/commands/license.js +30 -0
  36. package/dist/commands/lint.d.ts +5 -0
  37. package/dist/commands/lint.js +55 -0
  38. package/dist/commands/refactor.d.ts +9 -0
  39. package/dist/commands/refactor.js +172 -0
  40. package/dist/commands/run.d.ts +23 -2
  41. package/dist/commands/run.js +322 -111
  42. package/dist/commands/skills.d.ts +9 -0
  43. package/dist/commands/skills.js +28 -0
  44. package/dist/commands/sync-types.d.ts +4 -0
  45. package/dist/commands/sync-types.js +15 -0
  46. package/dist/commands/test.d.ts +17 -0
  47. package/dist/commands/test.js +76 -0
  48. package/dist/commands/update.js +39 -43
  49. package/dist/commands/watch.d.ts +6 -0
  50. package/dist/commands/watch.js +55 -45
  51. package/dist/config/loader.d.ts +1 -1
  52. package/dist/config/loader.js +40 -25
  53. package/dist/config/schema.d.ts +104 -145
  54. package/dist/config/schema.js +36 -45
  55. package/dist/generator/__tests__/tsconfig-isolation.test.d.ts +1 -0
  56. package/dist/generator/__tests__/tsconfig-isolation.test.js +282 -0
  57. package/dist/generator/__tests__/type-generator.test.d.ts +1 -0
  58. package/dist/generator/__tests__/type-generator.test.js +386 -0
  59. package/dist/generator/index.d.ts +2 -0
  60. package/dist/generator/index.js +2 -0
  61. package/dist/generator/tsconfig-generator.d.ts +27 -0
  62. package/dist/generator/tsconfig-generator.js +419 -0
  63. package/dist/generator/type-generator.d.ts +59 -0
  64. package/dist/generator/type-generator.js +740 -0
  65. package/dist/index.d.ts +9 -5
  66. package/dist/index.js +165 -51
  67. package/dist/lifecycle/__tests__/lifecycle-engine.test.d.ts +1 -0
  68. package/dist/lifecycle/__tests__/lifecycle-engine.test.js +290 -0
  69. package/dist/lifecycle/hook-discovery.d.ts +24 -0
  70. package/dist/lifecycle/hook-discovery.js +60 -0
  71. package/dist/lifecycle/hook-dispatcher.d.ts +34 -0
  72. package/dist/lifecycle/hook-dispatcher.js +190 -0
  73. package/dist/lifecycle/index.d.ts +3 -0
  74. package/dist/lifecycle/index.js +3 -0
  75. package/dist/lifecycle/parallel-grouping.d.ts +27 -0
  76. package/dist/lifecycle/parallel-grouping.js +151 -0
  77. package/dist/linter/__tests__/anonymous-export.test.d.ts +1 -0
  78. package/dist/linter/__tests__/anonymous-export.test.js +184 -0
  79. package/dist/linter/__tests__/component-test-imports.test.d.ts +1 -0
  80. package/dist/linter/__tests__/component-test-imports.test.js +115 -0
  81. package/dist/linter/__tests__/shared-lib-boundary.test.d.ts +1 -0
  82. package/dist/linter/__tests__/shared-lib-boundary.test.js +270 -0
  83. package/dist/linter/__tests__/spec-schema.test.d.ts +1 -0
  84. package/dist/linter/__tests__/spec-schema.test.js +369 -0
  85. package/dist/linter/__tests__/step-parity.test.d.ts +1 -0
  86. package/dist/linter/__tests__/step-parity.test.js +372 -0
  87. package/dist/linter/discovery.d.ts +30 -0
  88. package/dist/linter/discovery.js +249 -0
  89. package/dist/linter/extractor.d.ts +67 -0
  90. package/dist/linter/extractor.js +245 -0
  91. package/dist/linter/index.d.ts +7 -0
  92. package/dist/linter/index.js +7 -0
  93. package/dist/linter/parser.d.ts +24 -0
  94. package/dist/linter/parser.js +219 -0
  95. package/dist/linter/schemas/spec.schema.d.ts +83 -0
  96. package/dist/linter/schemas/spec.schema.js +150 -0
  97. package/dist/linter/schemas/suite.schema.d.ts +59 -0
  98. package/dist/linter/schemas/suite.schema.js +83 -0
  99. package/dist/linter/spec-linter.d.ts +23 -0
  100. package/dist/linter/spec-linter.js +630 -0
  101. package/dist/linter/types.d.ts +28 -0
  102. package/dist/linter/types.js +1 -0
  103. package/dist/mobile/__tests__/android-device-e2e.test.d.ts +1 -0
  104. package/dist/mobile/__tests__/android-device-e2e.test.js +53 -0
  105. package/dist/mobile/__tests__/device-pool.test.d.ts +1 -0
  106. package/dist/mobile/__tests__/device-pool.test.js +97 -0
  107. package/dist/mobile/device-pool.d.ts +23 -0
  108. package/dist/mobile/device-pool.js +1 -0
  109. package/dist/mobile/index.d.ts +1 -0
  110. package/dist/mobile/index.js +1 -0
  111. package/dist/refactor/__tests__/refactor-engine.test.d.ts +1 -0
  112. package/dist/refactor/__tests__/refactor-engine.test.js +873 -0
  113. package/dist/refactor/index.d.ts +1 -0
  114. package/dist/refactor/index.js +1 -0
  115. package/dist/refactor/refactor-engine.d.ts +50 -0
  116. package/dist/refactor/refactor-engine.js +587 -0
  117. package/dist/reporter/__tests__/semantic-formatter.test.d.ts +1 -0
  118. package/dist/reporter/__tests__/semantic-formatter.test.js +73 -0
  119. package/dist/reporter/semantic-formatter.d.ts +6 -0
  120. package/dist/reporter/semantic-formatter.js +206 -0
  121. package/dist/reporter/summary-view.d.ts +12 -0
  122. package/dist/reporter/summary-view.js +19 -0
  123. package/dist/reporter/terminal-stream.d.ts +32 -0
  124. package/dist/reporter/terminal-stream.js +82 -0
  125. package/dist/reporter/types.d.ts +2 -0
  126. package/dist/reporter/types.js +1 -0
  127. package/dist/runner/bridge.d.ts +10 -5
  128. package/dist/runner/bridge.js +101 -45
  129. package/dist/runner/reporter.d.ts +32 -2
  130. package/dist/runner/reporter.js +427 -13
  131. package/dist/server/demo.js +43 -44
  132. package/dist/types/generator.d.ts +1 -17
  133. package/dist/types/generator.js +1 -443
  134. package/dist/utils/__tests__/api-server.test.d.ts +1 -0
  135. package/dist/utils/__tests__/api-server.test.js +62 -0
  136. package/dist/utils/__tests__/demo-server.test.d.ts +1 -0
  137. package/dist/utils/__tests__/demo-server.test.js +35 -0
  138. package/dist/utils/api-server.d.ts +19 -0
  139. package/dist/utils/api-server.js +179 -0
  140. package/dist/utils/demo-server.d.ts +5 -0
  141. package/dist/utils/demo-server.js +46 -36
  142. package/package.json +25 -13
  143. package/templates/default/CLAUDE.md +12 -5
  144. package/templates/default/fixtures/apiData.json +24 -0
  145. package/templates/default/package.json +12 -3
  146. package/templates/default/page-objects/AuthPage/mobile.ts +5 -0
  147. package/templates/default/page-objects/AuthPage/web.ts +10 -0
  148. package/templates/default/page-objects/MatchersPage/mobile.ts +66 -17
  149. package/templates/default/page-objects/MatchersPage/web.ts +22 -2
  150. package/templates/default/page-objects/NavigationPage/mobile.ts +11 -2
  151. package/templates/default/page-objects/NavigationPage/web.ts +1 -1
  152. package/templates/default/page-objects/PlaygroundPage/mobile.ts +34 -18
  153. package/templates/default/page-objects/PlaygroundPage/web.ts +4 -2
  154. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +53 -0
  155. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +19 -0
  156. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +31 -0
  157. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/android.test.ts +3 -0
  158. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/common.test.ts +3 -0
  159. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +27 -0
  160. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +19 -0
  161. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +26 -0
  162. package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/common.test.ts +30 -0
  163. package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/spec.md +22 -0
  164. package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/common.test.ts +27 -0
  165. package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/spec.md +20 -0
  166. package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/spec.md +24 -0
  167. package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/web.test.ts +26 -0
  168. package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/spec.md +37 -0
  169. package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/web.test.ts +21 -0
  170. package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/spec.md +29 -0
  171. package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/web.test.ts +16 -0
  172. package/templates/default/specs/ApiInterception/suite.md +10 -0
  173. package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/common.test.ts +30 -0
  174. package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/spec.md +23 -0
  175. package/templates/default/specs/ApiSeeding/hooks/after/common.hook.ts +16 -0
  176. package/templates/default/specs/ApiSeeding/hooks/before/common.hook.ts +28 -0
  177. package/templates/default/specs/ApiSeeding/suite.md +9 -0
  178. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/mobile.test.ts +7 -0
  179. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/spec.md +19 -0
  180. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/web.test.ts +9 -0
  181. package/templates/default/specs/Authentication/suite.md +9 -0
  182. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +7 -1
  183. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +5 -0
  184. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
  185. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/mobile.test.ts +17 -0
  186. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/spec.md +28 -0
  187. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/web.test.ts +26 -0
  188. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/common.test.ts +26 -0
  189. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/spec.md +7 -0
  190. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +9 -1
  191. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +5 -0
  192. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +9 -4
  193. package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/common.test.ts +14 -0
  194. package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/spec.md +21 -0
  195. package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/mobile.test.ts +28 -0
  196. package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/spec.md +29 -0
  197. package/templates/default/specs/DeepLink/suite.md +9 -0
  198. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +9 -1
  199. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +5 -0
  200. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
  201. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +27 -1
  202. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +7 -0
  203. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
  204. package/templates/default/specs/ElementMatchers/suite.md +2 -1
  205. package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -0
  206. package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -0
  207. package/templates/default/specs/EnvironmentConfig/suite.md +9 -0
  208. package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/mobile.test.ts +26 -0
  209. package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/spec.md +22 -0
  210. package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/mobile.test.ts +13 -0
  211. package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/spec.md +20 -0
  212. package/templates/default/specs/PermissionRationale/suite.md +9 -0
  213. package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +4 -1
  214. package/templates/default/specs/Playground/TC-0001-form-controls/spec.md +6 -0
  215. package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +4 -4
  216. package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +4 -1
  217. package/templates/default/specs/Playground/TC-0002-toggle-elements/spec.md +6 -0
  218. package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +3 -3
  219. package/templates/default/spectra.config.ts +22 -31
  220. package/templates/default/support/actions/fillCredentials/mobile.action.ts +3 -3
  221. package/templates/default/support/actions/fillCredentials/web.action.ts +1 -1
  222. package/templates/default/support/actions/seedSession/mobile.action.ts +9 -0
  223. package/templates/default/support/actions/seedSession/web.action.ts +13 -0
  224. package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +3 -1
  225. package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +3 -3
  226. package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +4 -2
  227. package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +4 -4
  228. package/templates/default/tsconfig.json +3 -1
  229. package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db +0 -0
  230. package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-shm +0 -0
  231. package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-wal +0 -0
  232. package/templates/nx/.nx/workspace-data/d/daemon.log +5366 -0
  233. package/templates/nx/.nx/workspace-data/d/server-process.json +3 -0
  234. package/templates/nx/.nx/workspace-data/file-map.json +464 -0
  235. package/templates/nx/.nx/workspace-data/lockfile.hash +1 -0
  236. package/templates/nx/.nx/workspace-data/nx_files.nxt +0 -0
  237. package/templates/nx/.nx/workspace-data/parsed-lock-file.json +13485 -0
  238. package/templates/nx/.nx/workspace-data/project-graph.json +14389 -0
  239. package/templates/nx/CLAUDE.md +2 -2
  240. package/templates/nx/package.json +6 -4
  241. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +27 -0
  242. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +12 -0
  243. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -0
  244. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +24 -0
  245. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/spec.md +12 -0
  246. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -0
  247. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/suite.md +9 -0
  248. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +1 -1
  249. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
  250. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +1 -1
  251. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +15 -5
  252. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +1 -1
  253. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +9 -4
  254. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +1 -1
  255. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
  256. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +1 -1
  257. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
  258. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -0
  259. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -0
  260. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/suite.md +9 -0
  261. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/mobile.test.ts +1 -1
  262. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +4 -4
  263. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +1 -1
  264. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +3 -3
  265. package/templates/nx/pnpm-lock.yaml +6663 -0
  266. package/templates/nx/pnpm-workspace.yaml +2 -1
  267. package/templates/nx/shared/testing/fixtures/apiData.json +24 -0
  268. package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +13 -1
  269. package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +85 -17
  270. package/templates/nx/shared/testing/page-objects/NavigationPage/mobile.ts +1 -1
  271. package/templates/nx/shared/testing/page-objects/NavigationPage/web.ts +1 -1
  272. package/templates/nx/shared/testing/page-objects/PlaygroundPage/mobile.ts +1 -1
  273. package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +4 -2
  274. package/templates/nx/shared/testing/project.json +19 -0
  275. package/templates/nx/shared/testing/support/actions/fillCredentials/mobile.action.ts +1 -1
  276. package/templates/nx/shared/testing/support/actions/fillCredentials/web.action.ts +1 -1
  277. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/mobile.step.ts +1 -1
  278. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +3 -3
  279. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +2 -2
  280. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +4 -4
  281. package/templates/nx/spectra.config.ts +20 -29
  282. package/templates/nx/tsconfig.json +3 -1
  283. package/templates/react-component/.cursorrules +17 -0
  284. package/templates/react-component/.vscode/extensions.json +5 -0
  285. package/templates/react-component/CLAUDE.md +14 -0
  286. package/templates/react-component/README.md +69 -0
  287. package/templates/react-component/fixtures/component-mock.json +12 -0
  288. package/templates/react-component/global-hooks/after/web.hook.ts +3 -0
  289. package/templates/react-component/global-hooks/before/web.hook.ts +3 -0
  290. package/templates/react-component/package.json +27 -0
  291. package/templates/react-component/page-objects/BadgeComponent/web.ts +23 -0
  292. package/templates/react-component/page-objects/ButtonComponent/web.ts +23 -0
  293. package/templates/react-component/page-objects/InputComponent/web.ts +13 -0
  294. package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/spec.md +18 -0
  295. package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/web.test.tsx +11 -0
  296. package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/spec.md +18 -0
  297. package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/web.test.tsx +10 -0
  298. package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/spec.md +18 -0
  299. package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/web.test.tsx +16 -0
  300. package/templates/react-component/specs/BadgeComponent/suite.md +12 -0
  301. package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/spec.md +19 -0
  302. package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/web.test.tsx +28 -0
  303. package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/spec.md +19 -0
  304. package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/web.test.tsx +22 -0
  305. package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/spec.md +18 -0
  306. package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/web.test.tsx +8 -0
  307. package/templates/react-component/specs/ButtonComponent/suite.md +12 -0
  308. package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/spec.md +20 -0
  309. package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/web.test.tsx +28 -0
  310. package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/spec.md +18 -0
  311. package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/web.test.tsx +9 -0
  312. package/templates/react-component/specs/InputComponent/suite.md +12 -0
  313. package/templates/react-component/spectra.config.ts +27 -0
  314. package/templates/react-component/src/components/Badge/Badge.tsx +60 -0
  315. package/templates/react-component/src/components/Button/Button.tsx +57 -0
  316. package/templates/react-component/src/components/Input/Input.tsx +41 -0
  317. package/templates/react-component/src/test-utils.tsx +23 -0
  318. package/templates/react-component/support/actions/fillInputField/web.action.ts +9 -0
  319. package/templates/react-component/support/steps/verifyButtonState/web.step.ts +12 -0
  320. package/templates/react-component/tsconfig.json +8 -0
  321. package/demo-app/assets/index-DAUlrHTr.css +0 -1
  322. package/demo-app/assets/index-DN2Kb0A8.js +0 -100
  323. package/dist/index.cjs +0 -101
  324. package/dist/plugin.cjs +0 -575
  325. package/dist/plugin.d.ts +0 -16
  326. package/dist/plugin.js +0 -184
  327. package/dist/step/index.d.ts +0 -6
  328. package/dist/step/index.js +0 -6
  329. package/dist/step/matchers.d.ts +0 -14
  330. package/dist/step/matchers.js +0 -114
  331. package/dist/step/proto.d.ts +0 -46
  332. package/dist/step/proto.js +0 -114
  333. package/dist/step/runner/collection.d.ts +0 -13
  334. package/dist/step/runner/collection.js +0 -51
  335. package/dist/step/runner/single.d.ts +0 -21
  336. package/dist/step/runner/single.js +0 -138
  337. package/dist/step/spectra.d.ts +0 -34
  338. package/dist/step/spectra.js +0 -156
  339. package/dist/step/types.d.ts +0 -32
  340. package/dist/step/types.js +0 -5
  341. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -3
  342. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -7
  343. package/templates/default/tsconfig.spectra.android.json +0 -46
  344. package/templates/default/tsconfig.spectra.ios.json +0 -46
  345. package/templates/default/tsconfig.spectra.json +0 -22
  346. package/templates/default/tsconfig.spectra.web.json +0 -53
  347. package/templates/nx/fixtures/playgroundData.json +0 -7
  348. package/templates/nx/global-hooks/after/mobile.hook.ts +0 -3
  349. package/templates/nx/global-hooks/after/web.hook.ts +0 -3
  350. package/templates/nx/global-hooks/before/mobile.hook.ts +0 -3
  351. package/templates/nx/global-hooks/before/web.hook.ts +0 -3
  352. package/templates/nx/page-objects/MatchersPage/mobile.ts +0 -21
  353. package/templates/nx/page-objects/MatchersPage/web.ts +0 -23
  354. package/templates/nx/page-objects/NavigationPage/mobile.ts +0 -5
  355. package/templates/nx/page-objects/NavigationPage/web.ts +0 -8
  356. package/templates/nx/page-objects/PlaygroundPage/mobile.ts +0 -26
  357. package/templates/nx/page-objects/PlaygroundPage/web.ts +0 -29
  358. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +0 -3
  359. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +0 -12
  360. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +0 -5
  361. package/templates/nx/specs/BrowserContext/suite.md +0 -9
  362. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -3
  363. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/spec.md +0 -12
  364. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -7
  365. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +0 -3
  366. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +0 -12
  367. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +0 -6
  368. package/templates/nx/specs/ContentMatchers/suite.md +0 -9
  369. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +0 -3
  370. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +0 -12
  371. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +0 -5
  372. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +0 -3
  373. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +0 -12
  374. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +0 -8
  375. package/templates/nx/specs/ElementMatchers/hooks/after/mobile.hook.ts +0 -1
  376. package/templates/nx/specs/ElementMatchers/hooks/after/web.hook.ts +0 -1
  377. package/templates/nx/specs/ElementMatchers/hooks/afterEach/mobile.hook.ts +0 -1
  378. package/templates/nx/specs/ElementMatchers/hooks/afterEach/web.hook.ts +0 -1
  379. package/templates/nx/specs/ElementMatchers/hooks/before/mobile.hook.ts +0 -1
  380. package/templates/nx/specs/ElementMatchers/hooks/before/web.hook.ts +0 -1
  381. package/templates/nx/specs/ElementMatchers/hooks/beforeEach/mobile.hook.ts +0 -1
  382. package/templates/nx/specs/ElementMatchers/hooks/beforeEach/web.hook.ts +0 -1
  383. package/templates/nx/specs/ElementMatchers/suite.md +0 -9
  384. package/templates/nx/specs/Playground/TC-0001-form-controls/mobile.test.ts +0 -3
  385. package/templates/nx/specs/Playground/TC-0001-form-controls/spec.md +0 -12
  386. package/templates/nx/specs/Playground/TC-0001-form-controls/web.test.ts +0 -6
  387. package/templates/nx/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +0 -3
  388. package/templates/nx/specs/Playground/TC-0002-toggle-elements/spec.md +0 -12
  389. package/templates/nx/specs/Playground/TC-0002-toggle-elements/web.test.ts +0 -6
  390. package/templates/nx/specs/Playground/hooks/after/mobile.hook.ts +0 -1
  391. package/templates/nx/specs/Playground/hooks/after/web.hook.ts +0 -1
  392. package/templates/nx/specs/Playground/hooks/afterEach/mobile.hook.ts +0 -1
  393. package/templates/nx/specs/Playground/hooks/afterEach/web.hook.ts +0 -1
  394. package/templates/nx/specs/Playground/hooks/before/mobile.hook.ts +0 -1
  395. package/templates/nx/specs/Playground/hooks/before/web.hook.ts +0 -1
  396. package/templates/nx/specs/Playground/hooks/beforeEach/mobile.hook.ts +0 -1
  397. package/templates/nx/specs/Playground/hooks/beforeEach/web.hook.ts +0 -1
  398. package/templates/nx/specs/Playground/suite.md +0 -9
  399. package/templates/nx/support/actions/fillCredentials/mobile.action.ts +0 -4
  400. package/templates/nx/support/actions/fillCredentials/web.action.ts +0 -4
  401. package/templates/nx/support/steps/togglePlaygroundStates/mobile.step.ts +0 -3
  402. package/templates/nx/support/steps/togglePlaygroundStates/step.md +0 -7
  403. package/templates/nx/support/steps/togglePlaygroundStates/web.step.ts +0 -5
  404. package/templates/nx/support/steps/verifyPlaygroundState/mobile.step.ts +0 -4
  405. package/templates/nx/support/steps/verifyPlaygroundState/step.md +0 -7
  406. package/templates/nx/support/steps/verifyPlaygroundState/web.step.ts +0 -6
  407. package/templates/nx/tsconfig.spectra.android.json +0 -39
  408. package/templates/nx/tsconfig.spectra.ios.json +0 -39
  409. package/templates/nx/tsconfig.spectra.json +0 -25
  410. package/templates/nx/tsconfig.spectra.shared.json +0 -25
  411. package/templates/nx/tsconfig.spectra.web.json +0 -45
  412. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
  413. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
  414. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
  415. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
  416. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
  417. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
  418. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
  419. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
  420. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
  421. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
  422. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
  423. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
  424. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
  425. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
  426. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
  427. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
@@ -0,0 +1,67 @@
1
+ export interface DeclaredSpecStep {
2
+ name: string;
3
+ line: number;
4
+ raw: string;
5
+ }
6
+ export interface ExtractedSymbolCall {
7
+ name: string;
8
+ line: number;
9
+ column: number;
10
+ }
11
+ export interface ExtractedCodeSymbols {
12
+ stepCalls: ExtractedSymbolCall[];
13
+ itBlocks: Array<{
14
+ title: string;
15
+ line: number;
16
+ column: number;
17
+ }>;
18
+ testBlocks: Array<{
19
+ title: string;
20
+ line: number;
21
+ column: number;
22
+ }>;
23
+ describeBlocks: Array<{
24
+ title: string;
25
+ line: number;
26
+ column: number;
27
+ }>;
28
+ importStatements: Array<{
29
+ line: number;
30
+ column: number;
31
+ specifier: string;
32
+ }>;
33
+ }
34
+ /**
35
+ * Extracts @step:<name> tokens from the "## Test Steps" section of spec.md.
36
+ */
37
+ export declare function extractDeclaredStepsFromSpec(body: string, bodyStartLine: number): DeclaredSpecStep[];
38
+ /**
39
+ * Parses a TypeScript test file into an AST and extracts Step.* calls,
40
+ * it() test blocks, describe() suite blocks, and import statements.
41
+ */
42
+ export declare function extractSymbolsFromTestCode(code: string, filePath: string): ExtractedCodeSymbols;
43
+ /**
44
+ * Determines whether an it("...") test title aligns with the spec.md title.
45
+ * In TestSpectra, the it() argument represents the complete test case workflow
46
+ * and directly corresponds to the title declared in spec.md.
47
+ */
48
+ export declare function areTitlesAligned(specTitle: string, testTitle: string): boolean;
49
+ export interface ModuleExportResult {
50
+ hasAnonymousAsyncDefaultExport: boolean;
51
+ hasNamedExports: boolean;
52
+ namedExportLines: Array<{
53
+ line: number;
54
+ column: number;
55
+ name: string;
56
+ }>;
57
+ testBlocks: Array<{
58
+ line: number;
59
+ column: number;
60
+ name: string;
61
+ }>;
62
+ }
63
+ /**
64
+ * Parses a shared module (.step.ts, .action.ts, .hook.ts) into AST
65
+ * and checks for anonymous async default export compliance.
66
+ */
67
+ export declare function extractModuleExports(code: string, filePath: string): ModuleExportResult;
@@ -0,0 +1,245 @@
1
+ import ts from 'typescript';
2
+ /**
3
+ * Extracts @step:<name> tokens from the "## Test Steps" section of spec.md.
4
+ */
5
+ export function extractDeclaredStepsFromSpec(body, bodyStartLine) {
6
+ const steps = [];
7
+ const lines = body.split('\n');
8
+ let inStepsSection = false;
9
+ for (let i = 0; i < lines.length; i++) {
10
+ const lineNum = bodyStartLine + i;
11
+ const rawLine = lines[i];
12
+ const trimmed = rawLine.trim();
13
+ if (trimmed.startsWith('## ')) {
14
+ const heading = trimmed
15
+ .replace(/^##\s+/, '')
16
+ .trim()
17
+ .toLowerCase();
18
+ if (heading === 'test steps') {
19
+ inStepsSection = true;
20
+ continue;
21
+ }
22
+ else if (inStepsSection) {
23
+ break;
24
+ }
25
+ }
26
+ if (inStepsSection) {
27
+ const stepRegex = /@step:([a-zA-Z0-9_-]+)/g;
28
+ let match;
29
+ while ((match = stepRegex.exec(rawLine)) !== null) {
30
+ steps.push({
31
+ name: match[1],
32
+ line: lineNum,
33
+ raw: match[0],
34
+ });
35
+ }
36
+ }
37
+ }
38
+ return steps;
39
+ }
40
+ /**
41
+ * Parses a TypeScript test file into an AST and extracts Step.* calls,
42
+ * it() test blocks, describe() suite blocks, and import statements.
43
+ */
44
+ export function extractSymbolsFromTestCode(code, filePath) {
45
+ const sourceFile = ts.createSourceFile(filePath, code, ts.ScriptTarget.Latest, true);
46
+ const stepCalls = [];
47
+ const itBlocks = [];
48
+ const testBlocks = [];
49
+ const describeBlocks = [];
50
+ const importStatements = [];
51
+ function visit(node) {
52
+ // 1. Detect manual import statements (TS-LINT-006)
53
+ if (ts.isImportDeclaration(node)) {
54
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
55
+ const specifier = ts.isStringLiteral(node.moduleSpecifier) ? node.moduleSpecifier.text : '';
56
+ importStatements.push({
57
+ line: line + 1,
58
+ column: character + 1,
59
+ specifier,
60
+ });
61
+ }
62
+ else if (ts.isImportEqualsDeclaration(node)) {
63
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
64
+ importStatements.push({
65
+ line: line + 1,
66
+ column: character + 1,
67
+ specifier: '',
68
+ });
69
+ }
70
+ // 2. Detect it(...) / test(...) [forbidden] and describe(...) / suite(...) call expressions (TS-LINT-007, TS-LINT-008, TS-LINT-005)
71
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
72
+ const fnName = node.expression.text;
73
+ if (fnName === 'it') {
74
+ let title = '';
75
+ if (node.arguments.length > 0) {
76
+ const firstArg = node.arguments[0];
77
+ if (ts.isStringLiteral(firstArg) || ts.isNoSubstitutionTemplateLiteral(firstArg)) {
78
+ title = firstArg.text;
79
+ }
80
+ }
81
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
82
+ itBlocks.push({
83
+ title,
84
+ line: line + 1,
85
+ column: character + 1,
86
+ });
87
+ }
88
+ else if (fnName === 'test') {
89
+ let title = '';
90
+ if (node.arguments.length > 0) {
91
+ const firstArg = node.arguments[0];
92
+ if (ts.isStringLiteral(firstArg) || ts.isNoSubstitutionTemplateLiteral(firstArg)) {
93
+ title = firstArg.text;
94
+ }
95
+ }
96
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
97
+ testBlocks.push({
98
+ title,
99
+ line: line + 1,
100
+ column: character + 1,
101
+ });
102
+ }
103
+ else if (fnName === 'describe' || fnName === 'suite') {
104
+ let title = '';
105
+ if (node.arguments.length > 0) {
106
+ const firstArg = node.arguments[0];
107
+ if (ts.isStringLiteral(firstArg) || ts.isNoSubstitutionTemplateLiteral(firstArg)) {
108
+ title = firstArg.text;
109
+ }
110
+ }
111
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
112
+ describeBlocks.push({
113
+ title,
114
+ line: line + 1,
115
+ column: character + 1,
116
+ });
117
+ }
118
+ }
119
+ // 3. Detect Step.<name>() calls
120
+ if (ts.isCallExpression(node)) {
121
+ if (ts.isPropertyAccessExpression(node.expression)) {
122
+ const propAccess = node.expression;
123
+ if (ts.isIdentifier(propAccess.expression) && propAccess.expression.text === 'Step') {
124
+ const stepName = propAccess.name.text;
125
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
126
+ stepCalls.push({
127
+ name: stepName,
128
+ line: line + 1,
129
+ column: character + 1,
130
+ });
131
+ }
132
+ }
133
+ }
134
+ ts.forEachChild(node, visit);
135
+ }
136
+ visit(sourceFile);
137
+ return {
138
+ stepCalls,
139
+ itBlocks,
140
+ testBlocks,
141
+ describeBlocks,
142
+ importStatements,
143
+ };
144
+ }
145
+ /**
146
+ * Determines whether an it("...") test title aligns with the spec.md title.
147
+ * In TestSpectra, the it() argument represents the complete test case workflow
148
+ * and directly corresponds to the title declared in spec.md.
149
+ */
150
+ export function areTitlesAligned(specTitle, testTitle) {
151
+ if (!specTitle || !testTitle)
152
+ return true;
153
+ return specTitle.trim().toLowerCase() === testTitle.trim().toLowerCase();
154
+ }
155
+ function hasModifier(node, kind) {
156
+ return ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((m) => m.kind === kind) === true;
157
+ }
158
+ /**
159
+ * Parses a shared module (.step.ts, .action.ts, .hook.ts) into AST
160
+ * and checks for anonymous async default export compliance.
161
+ */
162
+ export function extractModuleExports(code, filePath) {
163
+ const sourceFile = ts.createSourceFile(filePath, code, ts.ScriptTarget.Latest, true);
164
+ let hasAnonymousAsyncDefaultExport = false;
165
+ const namedExportLines = [];
166
+ const testBlocks = [];
167
+ function visit(node) {
168
+ // Check for export default async function () {} (FunctionDeclaration with DefaultKeyword)
169
+ if (ts.isFunctionDeclaration(node)) {
170
+ const hasExport = hasModifier(node, ts.SyntaxKind.ExportKeyword);
171
+ const hasDefault = hasModifier(node, ts.SyntaxKind.DefaultKeyword);
172
+ const hasAsync = hasModifier(node, ts.SyntaxKind.AsyncKeyword);
173
+ if (hasExport && hasDefault && hasAsync) {
174
+ if (!node.name) {
175
+ hasAnonymousAsyncDefaultExport = true;
176
+ }
177
+ else {
178
+ // Named function used with export default: export default async function foo() {}
179
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
180
+ const name = node.name.text;
181
+ namedExportLines.push({ line: line + 1, column: character + 1, name });
182
+ }
183
+ }
184
+ else if (hasExport && !hasDefault) {
185
+ // export function foo() {} — named export (forbidden)
186
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
187
+ const name = node.name?.text || 'default';
188
+ namedExportLines.push({ line: line + 1, column: character + 1, name });
189
+ }
190
+ }
191
+ // Check for export default async () => {} or export default async function () {} (ExportAssignment)
192
+ if (ts.isExportAssignment(node)) {
193
+ const expr = node.expression;
194
+ if (ts.isArrowFunction(expr)) {
195
+ const hasAsync = hasModifier(expr, ts.SyntaxKind.AsyncKeyword);
196
+ if (hasAsync) {
197
+ hasAnonymousAsyncDefaultExport = true;
198
+ }
199
+ }
200
+ else if (ts.isFunctionExpression(expr)) {
201
+ const hasAsync = hasModifier(expr, ts.SyntaxKind.AsyncKeyword);
202
+ if (hasAsync && !expr.name) {
203
+ hasAnonymousAsyncDefaultExport = true;
204
+ }
205
+ else if (hasAsync && expr.name) {
206
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(expr.getStart());
207
+ namedExportLines.push({ line: line + 1, column: character + 1, name: expr.name.text });
208
+ }
209
+ }
210
+ }
211
+ // Check for export class (named exports)
212
+ if (ts.isClassDeclaration(node)) {
213
+ const hasExport = hasModifier(node, ts.SyntaxKind.ExportKeyword);
214
+ if (hasExport) {
215
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
216
+ const name = node.name?.text || 'default';
217
+ namedExportLines.push({ line: line + 1, column: character + 1, name });
218
+ }
219
+ }
220
+ // Check for export { ... } or export * from '...' (named exports)
221
+ if (ts.isExportDeclaration(node)) {
222
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
223
+ const exportName = node.exportClause && ts.isNamedExports(node.exportClause)
224
+ ? node.exportClause.elements.map((e) => e.name.text).join(', ')
225
+ : '*';
226
+ namedExportLines.push({ line: line + 1, column: character + 1, name: exportName });
227
+ }
228
+ // Check for forbidden test blocks
229
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
230
+ const fnName = node.expression.text;
231
+ if (fnName === 'it' || fnName === 'test' || fnName === 'describe') {
232
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
233
+ testBlocks.push({ line: line + 1, column: character + 1, name: fnName });
234
+ }
235
+ }
236
+ ts.forEachChild(node, visit);
237
+ }
238
+ visit(sourceFile);
239
+ return {
240
+ hasAnonymousAsyncDefaultExport,
241
+ hasNamedExports: namedExportLines.length > 0,
242
+ namedExportLines,
243
+ testBlocks,
244
+ };
245
+ }
@@ -0,0 +1,7 @@
1
+ export * from './types.js';
2
+ export * from './parser.js';
3
+ export * from './schemas/spec.schema.js';
4
+ export * from './schemas/suite.schema.js';
5
+ export * from './discovery.js';
6
+ export * from './extractor.js';
7
+ export * from './spec-linter.js';
@@ -0,0 +1,7 @@
1
+ export * from './types.js';
2
+ export * from './parser.js';
3
+ export * from './schemas/spec.schema.js';
4
+ export * from './schemas/suite.schema.js';
5
+ export * from './discovery.js';
6
+ export * from './extractor.js';
7
+ export * from './spec-linter.js';
@@ -0,0 +1,24 @@
1
+ import type { LintDiagnostic } from './types.js';
2
+ export interface ParsedFrontmatterResult<T = Record<string, unknown>> {
3
+ data: T | null;
4
+ raw: string;
5
+ hasFrontmatter: boolean;
6
+ startLine: number;
7
+ endLine: number;
8
+ bodyStartLine: number;
9
+ diagnostics: LintDiagnostic[];
10
+ keyLineMap: Map<string, number>;
11
+ body: string;
12
+ }
13
+ /**
14
+ * Scans lines within a range to locate YAML frontmatter properties before any Markdown heading
15
+ * or code fence. Returns the 0-indexed line range [first, last] or null if no properties found.
16
+ */
17
+ export declare function findPropertyRange(lines: string[], startIdx?: number, maxIdx?: number): {
18
+ first: number;
19
+ last: number;
20
+ } | null;
21
+ /**
22
+ * Extracts and parses YAML frontmatter from a Markdown document with line mapping.
23
+ */
24
+ export declare function parseMarkdownFrontmatter<T = Record<string, unknown>>(content: string, filePath: string): ParsedFrontmatterResult<T>;
@@ -0,0 +1,219 @@
1
+ import YAML from 'yaml';
2
+ /**
3
+ * Scans lines within a range to locate YAML frontmatter properties before any Markdown heading
4
+ * or code fence. Returns the 0-indexed line range [first, last] or null if no properties found.
5
+ */
6
+ export function findPropertyRange(lines, startIdx = 0, maxIdx = lines.length) {
7
+ let first = -1;
8
+ let last = -1;
9
+ for (let i = startIdx; i < maxIdx; i++) {
10
+ const raw = lines[i];
11
+ const trimmed = raw.trim();
12
+ if (!trimmed) {
13
+ if (first !== -1) {
14
+ let hasMorePropsAhead = false;
15
+ for (let j = i + 1; j < maxIdx; j++) {
16
+ const nextTrimmed = lines[j].trim();
17
+ if (!nextTrimmed)
18
+ continue;
19
+ if (nextTrimmed.startsWith('#') || nextTrimmed === '---' || nextTrimmed.startsWith('```')) {
20
+ break;
21
+ }
22
+ if (/^[a-zA-Z0-9_-]+\s*:/.test(nextTrimmed)) {
23
+ hasMorePropsAhead = true;
24
+ }
25
+ break;
26
+ }
27
+ if (!hasMorePropsAhead) {
28
+ break;
29
+ }
30
+ }
31
+ continue;
32
+ }
33
+ if (trimmed.startsWith('#') || trimmed === '---' || trimmed.startsWith('```')) {
34
+ break;
35
+ }
36
+ if (/^[a-zA-Z0-9_-]+\s*:/.test(trimmed) || (first !== -1 && (trimmed.startsWith('- ') || /^\s+/.test(raw)))) {
37
+ if (first === -1) {
38
+ first = i;
39
+ }
40
+ last = i;
41
+ }
42
+ else {
43
+ break;
44
+ }
45
+ }
46
+ return first !== -1 ? { first, last } : null;
47
+ }
48
+ /**
49
+ * Extracts and parses YAML frontmatter from a Markdown document with line mapping.
50
+ */
51
+ export function parseMarkdownFrontmatter(content, filePath) {
52
+ const diagnostics = [];
53
+ const keyLineMap = new Map();
54
+ // Normalize line endings
55
+ const normalized = content.replace(/\r\n/g, '\n');
56
+ const lines = normalized.split('\n');
57
+ if (lines.length === 0 || lines[0].trim() !== '---') {
58
+ // Check if there is a closing delimiter somewhere in the file
59
+ let closingIndex = -1;
60
+ for (let i = 1; i < lines.length; i++) {
61
+ if (lines[i].trim() === '---') {
62
+ closingIndex = i;
63
+ break;
64
+ }
65
+ }
66
+ if (closingIndex !== -1) {
67
+ // Case 1: Opener delimiter is missing, but closing delimiter exists
68
+ return {
69
+ data: null,
70
+ raw: '',
71
+ hasFrontmatter: false,
72
+ startLine: 1,
73
+ endLine: closingIndex + 1,
74
+ bodyStartLine: closingIndex + 2,
75
+ diagnostics: [
76
+ {
77
+ code: 'testspectra/missing-frontmatter',
78
+ severity: 'error',
79
+ message: 'Missing opening YAML frontmatter delimiter (---) at line 1',
80
+ filePath,
81
+ line: 1,
82
+ },
83
+ ],
84
+ keyLineMap,
85
+ body: lines.slice(closingIndex + 1).join('\n'),
86
+ };
87
+ }
88
+ // Neither opener nor closing delimiter exists
89
+ const propRange = findPropertyRange(lines, 0);
90
+ if (propRange) {
91
+ // Case 3: No delimiters at all, but frontmatter properties exist
92
+ return {
93
+ data: null,
94
+ raw: '',
95
+ hasFrontmatter: false,
96
+ startLine: 1,
97
+ endLine: propRange.last + 1,
98
+ bodyStartLine: propRange.last + 2,
99
+ diagnostics: [
100
+ {
101
+ code: 'testspectra/missing-frontmatter',
102
+ severity: 'error',
103
+ message: 'Missing YAML frontmatter delimiters (---) at line 1',
104
+ filePath,
105
+ line: 1,
106
+ },
107
+ ],
108
+ keyLineMap,
109
+ body: normalized,
110
+ };
111
+ }
112
+ // Case 4: Delimiters missing and properties missing
113
+ return {
114
+ data: null,
115
+ raw: '',
116
+ hasFrontmatter: false,
117
+ startLine: 1,
118
+ endLine: 1,
119
+ bodyStartLine: 1,
120
+ diagnostics: [
121
+ {
122
+ code: 'testspectra/missing-frontmatter',
123
+ severity: 'error',
124
+ message: 'Missing YAML frontmatter delimiter (---) at line 1',
125
+ filePath,
126
+ line: 1,
127
+ },
128
+ ],
129
+ keyLineMap,
130
+ body: normalized,
131
+ };
132
+ }
133
+ let closingIndex = -1;
134
+ for (let i = 1; i < lines.length; i++) {
135
+ if (lines[i].trim() === '---') {
136
+ closingIndex = i;
137
+ break;
138
+ }
139
+ }
140
+ if (closingIndex === -1) {
141
+ // Case 2: Opening delimiter exists at line 1, but closing delimiter is missing
142
+ return {
143
+ data: null,
144
+ raw: '',
145
+ hasFrontmatter: false,
146
+ startLine: 1,
147
+ endLine: lines.length,
148
+ bodyStartLine: lines.length,
149
+ diagnostics: [
150
+ {
151
+ code: 'testspectra/missing-frontmatter',
152
+ severity: 'error',
153
+ message: 'Missing closing YAML frontmatter delimiter (---). Expected closing "---"',
154
+ filePath,
155
+ line: 1,
156
+ },
157
+ ],
158
+ keyLineMap,
159
+ body: '',
160
+ };
161
+ }
162
+ const frontmatterLines = lines.slice(1, closingIndex);
163
+ const rawFrontmatter = frontmatterLines.join('\n');
164
+ const body = lines.slice(closingIndex + 1).join('\n');
165
+ const lineCounter = new YAML.LineCounter();
166
+ const doc = YAML.parseDocument(rawFrontmatter, { lineCounter, keepSourceTokens: true });
167
+ if (doc.errors && doc.errors.length > 0) {
168
+ for (const err of doc.errors) {
169
+ const pos = err.linePos ? err.linePos[0] : { line: 1, col: 1 };
170
+ diagnostics.push({
171
+ code: 'testspectra/parse-error',
172
+ severity: 'error',
173
+ message: `Malformed YAML syntax in frontmatter: ${err.message}`,
174
+ filePath,
175
+ line: 1 + pos.line, // 1 for the leading '---'
176
+ column: pos.col,
177
+ });
178
+ }
179
+ return {
180
+ data: null,
181
+ raw: rawFrontmatter,
182
+ hasFrontmatter: true,
183
+ startLine: 1,
184
+ endLine: closingIndex + 1,
185
+ bodyStartLine: closingIndex + 2,
186
+ diagnostics,
187
+ keyLineMap,
188
+ body,
189
+ };
190
+ }
191
+ // Populate keyLineMap for exact line numbers
192
+ if (YAML.isMap(doc.contents)) {
193
+ for (const item of doc.contents.items) {
194
+ if (item && item.key) {
195
+ const keyName = String(item.key);
196
+ if (item.key.range) {
197
+ const pos = lineCounter.linePos(item.key.range[0]);
198
+ // 1 (for opening delimiter line) + linePos.line
199
+ keyLineMap.set(keyName, 1 + pos.line);
200
+ }
201
+ else {
202
+ keyLineMap.set(keyName, 1);
203
+ }
204
+ }
205
+ }
206
+ }
207
+ const data = doc.toJSON();
208
+ return {
209
+ data,
210
+ raw: rawFrontmatter,
211
+ hasFrontmatter: true,
212
+ startLine: 1,
213
+ endLine: closingIndex + 1,
214
+ bodyStartLine: closingIndex + 2,
215
+ diagnostics,
216
+ keyLineMap,
217
+ body,
218
+ };
219
+ }
@@ -0,0 +1,83 @@
1
+ import { z } from 'zod';
2
+ import type { LintDiagnostic } from '../types.js';
3
+ export declare const PRIORITY_VALUES: readonly ["Critical", "High", "Medium", "Low"];
4
+ export declare const CASE_TYPE_VALUES: readonly ["Positive", "Negative", "Edge"];
5
+ export declare const STATUS_VALUES: readonly ["draft", "ready-for-automation", "automated", "deprecated"];
6
+ export declare const COVERAGE_STATUS_VALUES: readonly ["automated", "manual", "unsupported"];
7
+ export declare const PriorityEnum: z.ZodEnum<["Critical", "High", "Medium", "Low"]>;
8
+ export declare const CaseTypeEnum: z.ZodEnum<["Positive", "Negative", "Edge"]>;
9
+ export declare const StatusEnum: z.ZodEnum<["draft", "ready-for-automation", "automated", "deprecated"]>;
10
+ export declare const CoverageSchema: z.ZodObject<{
11
+ web: z.ZodOptional<z.ZodEnum<["automated", "manual", "unsupported"]>>;
12
+ mobile: z.ZodOptional<z.ZodEnum<["automated", "manual", "unsupported"]>>;
13
+ }, "strip", z.ZodTypeAny, {
14
+ mobile?: "automated" | "manual" | "unsupported" | undefined;
15
+ web?: "automated" | "manual" | "unsupported" | undefined;
16
+ }, {
17
+ mobile?: "automated" | "manual" | "unsupported" | undefined;
18
+ web?: "automated" | "manual" | "unsupported" | undefined;
19
+ }>;
20
+ export declare const SpecFrontmatterSchema: z.ZodObject<{
21
+ id: z.ZodString;
22
+ title: z.ZodString;
23
+ priority: z.ZodEnum<["Critical", "High", "Medium", "Low"]>;
24
+ caseType: z.ZodEnum<["Positive", "Negative", "Edge"]>;
25
+ status: z.ZodEnum<["draft", "ready-for-automation", "automated", "deprecated"]>;
26
+ coverage: z.ZodOptional<z.ZodObject<{
27
+ web: z.ZodOptional<z.ZodEnum<["automated", "manual", "unsupported"]>>;
28
+ mobile: z.ZodOptional<z.ZodEnum<["automated", "manual", "unsupported"]>>;
29
+ }, "strip", z.ZodTypeAny, {
30
+ mobile?: "automated" | "manual" | "unsupported" | undefined;
31
+ web?: "automated" | "manual" | "unsupported" | undefined;
32
+ }, {
33
+ mobile?: "automated" | "manual" | "unsupported" | undefined;
34
+ web?: "automated" | "manual" | "unsupported" | undefined;
35
+ }>>;
36
+ tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
37
+ }, "strip", z.ZodTypeAny, {
38
+ status: "draft" | "ready-for-automation" | "automated" | "deprecated";
39
+ title: string;
40
+ id: string;
41
+ priority: "Critical" | "High" | "Medium" | "Low";
42
+ caseType: "Positive" | "Negative" | "Edge";
43
+ coverage?: {
44
+ mobile?: "automated" | "manual" | "unsupported" | undefined;
45
+ web?: "automated" | "manual" | "unsupported" | undefined;
46
+ } | undefined;
47
+ tags?: string[] | undefined;
48
+ }, {
49
+ status: "draft" | "ready-for-automation" | "automated" | "deprecated";
50
+ title: string;
51
+ id: string;
52
+ priority: "Critical" | "High" | "Medium" | "Low";
53
+ caseType: "Positive" | "Negative" | "Edge";
54
+ coverage?: {
55
+ mobile?: "automated" | "manual" | "unsupported" | undefined;
56
+ web?: "automated" | "manual" | "unsupported" | undefined;
57
+ } | undefined;
58
+ tags?: string[] | undefined;
59
+ }>;
60
+ export type SpecFrontmatter = z.infer<typeof SpecFrontmatterSchema>;
61
+ export interface SectionDefinition {
62
+ name: string;
63
+ level: number;
64
+ required: boolean;
65
+ headerPattern: RegExp;
66
+ description: string;
67
+ }
68
+ export declare const REQUIRED_SPEC_SECTIONS: SectionDefinition[];
69
+ export declare const STANDARD_SPEC_SECTIONS: SectionDefinition[];
70
+ export interface ParsedMarkdownHeader {
71
+ level: number;
72
+ text: string;
73
+ line: number;
74
+ raw: string;
75
+ }
76
+ /**
77
+ * Extracts markdown headers from document body.
78
+ */
79
+ export declare function extractMarkdownHeaders(body: string, startLine: number): ParsedMarkdownHeader[];
80
+ /**
81
+ * Validates markdown body sections against spec rules.
82
+ */
83
+ export declare function validateSpecSections(body: string, filePath: string, bodyStartLine: number): LintDiagnostic[];