@testspectra/cli 1.1.7 → 1.1.8-rc.10

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 (256) hide show
  1. package/README.md +47 -0
  2. package/demo-app/assets/index-BhlW-eXG.css +1 -0
  3. package/demo-app/assets/index-DOtRypCa.js +154 -0
  4. package/demo-app/index.html +2 -2
  5. package/dist/.tsbuildinfo +1 -1
  6. package/dist/commands/__tests__/run-e2e.test.js +254 -13
  7. package/dist/commands/__tests__/run.test.js +92 -16
  8. package/dist/commands/add.js +2 -1
  9. package/dist/commands/demo.js +17 -7
  10. package/dist/commands/init.d.ts +1 -1
  11. package/dist/commands/init.js +8 -2
  12. package/dist/commands/lint.d.ts +5 -0
  13. package/dist/commands/lint.js +55 -0
  14. package/dist/commands/refactor.d.ts +2 -1
  15. package/dist/commands/refactor.js +129 -35
  16. package/dist/commands/run.d.ts +2 -3
  17. package/dist/commands/run.js +93 -28
  18. package/dist/commands/skills.d.ts +9 -0
  19. package/dist/commands/skills.js +28 -0
  20. package/dist/commands/sync-types.js +1 -0
  21. package/dist/commands/test.d.ts +17 -0
  22. package/dist/commands/test.js +104 -0
  23. package/dist/commands/watch.js +9 -3
  24. package/dist/config/loader.js +1 -1
  25. package/dist/config/schema.d.ts +5 -114
  26. package/dist/config/schema.js +1 -17
  27. package/dist/generator/__tests__/tsconfig-isolation.test.js +6 -2
  28. package/dist/generator/__tests__/type-generator.test.js +39 -1
  29. package/dist/generator/tsconfig-generator.js +44 -16
  30. package/dist/generator/type-generator.d.ts +29 -3
  31. package/dist/generator/type-generator.js +115 -22
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.js +65 -29
  34. package/dist/linter/__tests__/anonymous-export.test.js +184 -0
  35. package/dist/linter/__tests__/component-test-imports.test.d.ts +1 -0
  36. package/dist/linter/__tests__/component-test-imports.test.js +115 -0
  37. package/dist/linter/__tests__/shared-lib-boundary.test.d.ts +1 -0
  38. package/dist/linter/__tests__/shared-lib-boundary.test.js +270 -0
  39. package/dist/linter/__tests__/spec-schema.test.d.ts +1 -0
  40. package/dist/linter/__tests__/spec-schema.test.js +369 -0
  41. package/dist/linter/__tests__/step-parity.test.d.ts +1 -0
  42. package/dist/linter/__tests__/step-parity.test.js +372 -0
  43. package/dist/linter/discovery.d.ts +30 -0
  44. package/dist/linter/discovery.js +249 -0
  45. package/dist/linter/extractor.d.ts +67 -0
  46. package/dist/linter/extractor.js +245 -0
  47. package/dist/linter/index.d.ts +7 -0
  48. package/dist/linter/index.js +7 -0
  49. package/dist/linter/parser.d.ts +24 -0
  50. package/dist/linter/parser.js +219 -0
  51. package/dist/linter/schemas/spec.schema.d.ts +83 -0
  52. package/dist/linter/schemas/spec.schema.js +150 -0
  53. package/dist/linter/schemas/suite.schema.d.ts +59 -0
  54. package/dist/linter/schemas/suite.schema.js +83 -0
  55. package/dist/linter/spec-linter.d.ts +23 -0
  56. package/dist/linter/spec-linter.js +630 -0
  57. package/dist/linter/types.d.ts +28 -0
  58. package/dist/linter/types.js +1 -0
  59. package/dist/refactor/__tests__/refactor-engine.test.js +698 -0
  60. package/dist/refactor/refactor-engine.d.ts +25 -0
  61. package/dist/refactor/refactor-engine.js +374 -42
  62. package/dist/reporter/semantic-formatter.js +22 -2
  63. package/dist/reporter/types.d.ts +1 -1
  64. package/dist/runner/bridge.d.ts +1 -1
  65. package/dist/runner/bridge.js +85 -24
  66. package/dist/runner/reporter.d.ts +2 -0
  67. package/dist/runner/reporter.js +11 -3
  68. package/dist/utils/__tests__/api-server.test.d.ts +1 -0
  69. package/dist/utils/__tests__/api-server.test.js +62 -0
  70. package/dist/utils/__tests__/demo-server.test.d.ts +1 -0
  71. package/dist/utils/__tests__/demo-server.test.js +35 -0
  72. package/dist/utils/api-server.d.ts +19 -0
  73. package/dist/utils/api-server.js +179 -0
  74. package/dist/utils/demo-server.d.ts +5 -0
  75. package/dist/utils/demo-server.js +8 -1
  76. package/package.json +13 -4
  77. package/templates/default/CLAUDE.md +5 -0
  78. package/templates/default/package.json +3 -3
  79. package/templates/default/page-objects/AuthPage/mobile.ts +5 -0
  80. package/templates/default/page-objects/AuthPage/web.ts +10 -0
  81. package/templates/default/page-objects/MatchersPage/mobile.ts +26 -2
  82. package/templates/default/page-objects/MatchersPage/web.ts +8 -2
  83. package/templates/default/page-objects/NavigationPage/mobile.ts +1 -1
  84. package/templates/default/page-objects/NavigationPage/web.ts +1 -1
  85. package/templates/default/page-objects/PlaygroundPage/mobile.ts +12 -6
  86. package/templates/default/page-objects/PlaygroundPage/web.ts +2 -2
  87. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +45 -3
  88. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +7 -0
  89. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +8 -4
  90. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/android.test.ts +3 -0
  91. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/common.test.ts +3 -0
  92. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +13 -2
  93. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +7 -0
  94. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +6 -4
  95. package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/common.test.ts +30 -0
  96. package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/spec.md +22 -0
  97. package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/common.test.ts +27 -0
  98. package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/spec.md +20 -0
  99. package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/spec.md +24 -0
  100. package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/web.test.ts +26 -0
  101. package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/spec.md +37 -0
  102. package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/web.test.ts +21 -0
  103. package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/spec.md +29 -0
  104. package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/web.test.ts +16 -0
  105. package/templates/default/specs/ApiInterception/suite.md +1 -0
  106. package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/common.test.ts +30 -0
  107. package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/spec.md +23 -0
  108. package/templates/default/specs/ApiSeeding/hooks/after/common.hook.ts +16 -0
  109. package/templates/default/specs/ApiSeeding/hooks/before/common.hook.ts +28 -0
  110. package/templates/default/specs/ApiSeeding/suite.md +9 -0
  111. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/mobile.test.ts +7 -0
  112. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/spec.md +19 -0
  113. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/web.test.ts +9 -0
  114. package/templates/default/specs/Authentication/suite.md +9 -0
  115. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +1 -1
  116. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +5 -0
  117. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +1 -1
  118. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/mobile.test.ts +17 -0
  119. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/spec.md +28 -0
  120. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/web.test.ts +26 -0
  121. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/common.test.ts +26 -0
  122. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/spec.md +7 -0
  123. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +1 -1
  124. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +5 -0
  125. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +1 -1
  126. package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/common.test.ts +14 -0
  127. package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/spec.md +21 -0
  128. package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/mobile.test.ts +28 -0
  129. package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/spec.md +29 -0
  130. package/templates/default/specs/DeepLink/suite.md +9 -0
  131. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +1 -1
  132. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +5 -0
  133. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +1 -1
  134. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +1 -1
  135. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +7 -0
  136. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +1 -1
  137. package/templates/default/specs/ElementMatchers/suite.md +2 -1
  138. package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -0
  139. package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -0
  140. package/templates/default/specs/EnvironmentConfig/suite.md +9 -0
  141. package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/mobile.test.ts +26 -0
  142. package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/spec.md +22 -0
  143. package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/mobile.test.ts +13 -0
  144. package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/spec.md +20 -0
  145. package/templates/default/specs/PermissionRationale/suite.md +9 -0
  146. package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +1 -1
  147. package/templates/default/specs/Playground/TC-0001-form-controls/spec.md +6 -0
  148. package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +1 -1
  149. package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +1 -1
  150. package/templates/default/specs/Playground/TC-0002-toggle-elements/spec.md +6 -0
  151. package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +1 -1
  152. package/templates/default/spectra.config.ts +1 -17
  153. package/templates/default/support/actions/fillCredentials/mobile.action.ts +3 -3
  154. package/templates/default/support/actions/fillCredentials/web.action.ts +1 -1
  155. package/templates/default/support/actions/seedSession/mobile.action.ts +9 -0
  156. package/templates/default/support/actions/seedSession/web.action.ts +13 -0
  157. package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +1 -1
  158. package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +1 -1
  159. package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +1 -1
  160. package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +1 -1
  161. package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db +0 -0
  162. package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-shm +0 -0
  163. package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-wal +0 -0
  164. package/templates/nx/.nx/workspace-data/d/daemon.log +5803 -168
  165. package/templates/nx/.nx/workspace-data/d/server-process.json +3 -0
  166. package/templates/nx/.nx/workspace-data/file-map.json +464 -0
  167. package/templates/nx/.nx/workspace-data/lockfile.hash +1 -0
  168. package/templates/nx/.nx/workspace-data/nx_files.nxt +0 -0
  169. package/templates/nx/.nx/workspace-data/parsed-lock-file.json +13485 -0
  170. package/templates/nx/.nx/workspace-data/project-graph.json +14388 -0
  171. package/templates/nx/package.json +3 -3
  172. package/templates/nx/packages/matchers/e2e/project.json +2 -2
  173. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +25 -1
  174. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +2 -2
  175. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +22 -1
  176. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +2 -2
  177. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +1 -1
  178. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +1 -1
  179. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +1 -1
  180. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +1 -1
  181. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +1 -1
  182. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +1 -1
  183. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +1 -1
  184. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +1 -1
  185. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +1 -1
  186. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +1 -1
  187. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -0
  188. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -0
  189. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/suite.md +9 -0
  190. package/templates/nx/packages/playground/e2e/project.json +2 -2
  191. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/mobile.test.ts +1 -1
  192. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +1 -1
  193. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +1 -1
  194. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +1 -1
  195. package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +1 -1
  196. package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +83 -29
  197. package/templates/nx/shared/testing/page-objects/NavigationPage/mobile.ts +1 -1
  198. package/templates/nx/shared/testing/page-objects/NavigationPage/web.ts +1 -1
  199. package/templates/nx/shared/testing/page-objects/PlaygroundPage/mobile.ts +1 -1
  200. package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +2 -2
  201. package/templates/nx/shared/testing/project.json +1 -2
  202. package/templates/nx/shared/testing/support/actions/fillCredentials/mobile.action.ts +1 -1
  203. package/templates/nx/shared/testing/support/actions/fillCredentials/web.action.ts +1 -1
  204. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/mobile.step.ts +1 -1
  205. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +1 -1
  206. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +1 -1
  207. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +1 -1
  208. package/templates/nx/spectra.config.ts +1 -17
  209. package/templates/react-component/.cursorrules +17 -0
  210. package/templates/react-component/.vscode/extensions.json +5 -0
  211. package/templates/react-component/CLAUDE.md +14 -0
  212. package/templates/react-component/README.md +69 -0
  213. package/templates/react-component/fixtures/component-mock.json +12 -0
  214. package/templates/react-component/global-hooks/after/web.hook.ts +3 -0
  215. package/templates/react-component/global-hooks/before/web.hook.ts +3 -0
  216. package/templates/react-component/package.json +27 -0
  217. package/templates/react-component/page-objects/BadgeComponent/web.ts +23 -0
  218. package/templates/react-component/page-objects/ButtonComponent/web.ts +23 -0
  219. package/templates/react-component/page-objects/InputComponent/web.ts +13 -0
  220. package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/spec.md +18 -0
  221. package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/web.test.tsx +11 -0
  222. package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/spec.md +18 -0
  223. package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/web.test.tsx +10 -0
  224. package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/spec.md +18 -0
  225. package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/web.test.tsx +16 -0
  226. package/templates/react-component/specs/BadgeComponent/suite.md +12 -0
  227. package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/spec.md +19 -0
  228. package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/web.test.tsx +28 -0
  229. package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/spec.md +19 -0
  230. package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/web.test.tsx +22 -0
  231. package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/spec.md +18 -0
  232. package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/web.test.tsx +8 -0
  233. package/templates/react-component/specs/ButtonComponent/suite.md +12 -0
  234. package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/spec.md +20 -0
  235. package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/web.test.tsx +28 -0
  236. package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/spec.md +18 -0
  237. package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/web.test.tsx +9 -0
  238. package/templates/react-component/specs/InputComponent/suite.md +12 -0
  239. package/templates/react-component/spectra.config.ts +27 -0
  240. package/templates/react-component/src/components/Badge/Badge.tsx +60 -0
  241. package/templates/react-component/src/components/Button/Button.tsx +57 -0
  242. package/templates/react-component/src/components/Input/Input.tsx +41 -0
  243. package/templates/react-component/src/test-utils.tsx +23 -0
  244. package/templates/react-component/support/actions/fillInputField/web.action.ts +9 -0
  245. package/templates/react-component/support/steps/verifyButtonState/web.step.ts +12 -0
  246. package/templates/react-component/tsconfig.json +8 -0
  247. package/bin/.testspectra-runner.hash +0 -1
  248. package/bin/testspectra-runner +0 -0
  249. package/demo-app/assets/index-DPfv5_EF.js +0 -116
  250. package/demo-app/assets/index-gIwu2EMV.css +0 -1
  251. package/dist/commands/__tests__/cloud-run.test.js +0 -93
  252. package/dist/commands/cloud-run.d.ts +0 -13
  253. package/dist/commands/cloud-run.js +0 -120
  254. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -17
  255. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -17
  256. /package/dist/{commands/__tests__/cloud-run.test.d.ts → linter/__tests__/anonymous-export.test.d.ts} +0 -0
@@ -0,0 +1,369 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
5
+ import { SpecLinter } from '../spec-linter.js';
6
+ import { parseMarkdownFrontmatter } from '../parser.js';
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ describe('Spec Schema Linter (Stage 1 & Stage 2)', () => {
10
+ const tempDir = path.join(__dirname, '__temp_specs__');
11
+ beforeEach(() => {
12
+ fs.mkdirSync(tempDir, { recursive: true });
13
+ });
14
+ afterEach(() => {
15
+ if (fs.existsSync(tempDir)) {
16
+ fs.rmSync(tempDir, { recursive: true, force: true });
17
+ }
18
+ });
19
+ describe('Spec File Validation (spec.md)', () => {
20
+ it('passes validation for a well-formed spec.md with frontmatter, H1 title, and Test Steps', () => {
21
+ const specPath = path.join(tempDir, 'spec.md');
22
+ fs.writeFileSync(specPath, `---
23
+ id: TC-0001-valid
24
+ title: Verify user authentication
25
+ priority: High
26
+ caseType: Positive
27
+ status: ready-for-automation
28
+ coverage:
29
+ web: automated
30
+ mobile: automated
31
+ tags:
32
+ - auth
33
+ ---
34
+
35
+ # Verify user authentication
36
+
37
+ ## Test Steps
38
+ 1. Open login page and submit credentials
39
+ `);
40
+ const result = SpecLinter.validateSpecFile(specPath);
41
+ expect(result.diagnostics).toHaveLength(0);
42
+ expect(result.id).toBe('TC-0001-valid');
43
+ });
44
+ it('reports testspectra/missing-frontmatter when required frontmatter attributes are missing', () => {
45
+ const specPath = path.join(tempDir, 'spec.md');
46
+ fs.writeFileSync(specPath, `---
47
+ id: TC-0002-missing-attrs
48
+ title: Missing priority and caseType
49
+ status: draft
50
+ ---
51
+
52
+ # Missing priority and caseType
53
+
54
+ ## Test Steps
55
+ 1. Step one
56
+ `);
57
+ const result = SpecLinter.validateSpecFile(specPath);
58
+ const codes = result.diagnostics.map((d) => d.code);
59
+ expect(codes).toContain('testspectra/missing-frontmatter');
60
+ const missingPriority = result.diagnostics.find((d) => d.code === 'testspectra/missing-frontmatter' &&
61
+ d.message.includes("Missing required frontmatter attribute 'priority'"));
62
+ expect(missingPriority).toBeDefined();
63
+ expect(missingPriority?.severity).toBe('error');
64
+ const missingCaseType = result.diagnostics.find((d) => d.code === 'testspectra/missing-frontmatter' &&
65
+ d.message.includes("Missing required frontmatter attribute 'caseType'"));
66
+ expect(missingCaseType).toBeDefined();
67
+ expect(missingCaseType?.severity).toBe('error');
68
+ });
69
+ it('differentiates missing status (testspectra/missing-frontmatter) from invalid status value (testspectra/invalid-frontmatter-enum)', () => {
70
+ // 1. Missing status -> testspectra/missing-frontmatter
71
+ const missingSpecPath = path.join(tempDir, 'missing-status.spec.md');
72
+ fs.writeFileSync(missingSpecPath, `---
73
+ id: TC-0002-missing-status
74
+ title: Missing Status Spec
75
+ priority: High
76
+ caseType: Positive
77
+ ---
78
+
79
+ # Missing Status Spec
80
+
81
+ ## Test Steps
82
+ 1. Step one
83
+ `);
84
+ const missingResult = SpecLinter.validateSpecFile(missingSpecPath);
85
+ const missingStatusDiag = missingResult.diagnostics.find((d) => d.code === 'testspectra/missing-frontmatter' &&
86
+ d.message === "Missing required frontmatter attribute 'status'");
87
+ expect(missingStatusDiag).toBeDefined();
88
+ expect(missingStatusDiag?.severity).toBe('error');
89
+ expect(missingResult.diagnostics.some((d) => d.code === 'testspectra/invalid-frontmatter-enum')).toBe(false);
90
+ // 2. Invalid status -> testspectra/invalid-frontmatter-enum
91
+ const invalidSpecPath = path.join(tempDir, 'invalid-status.spec.md');
92
+ fs.writeFileSync(invalidSpecPath, `---
93
+ id: TC-0002-invalid-status
94
+ title: Invalid Status Spec
95
+ priority: High
96
+ caseType: Positive
97
+ status: not-a-valid-status
98
+ ---
99
+
100
+ # Invalid Status Spec
101
+
102
+ ## Test Steps
103
+ 1. Step one
104
+ `);
105
+ const invalidResult = SpecLinter.validateSpecFile(invalidSpecPath);
106
+ const invalidStatusDiag = invalidResult.diagnostics.find((d) => d.code === 'testspectra/invalid-frontmatter-enum' &&
107
+ d.message === 'Invalid status value. Expected one of: draft, ready-for-automation, automated, deprecated');
108
+ expect(invalidStatusDiag).toBeDefined();
109
+ expect(invalidStatusDiag?.severity).toBe('error');
110
+ expect(invalidResult.diagnostics.some((d) => d.message.includes("Missing required frontmatter attribute 'status'"))).toBe(false);
111
+ });
112
+ it('reports testspectra/invalid-frontmatter-enum when enum attributes have invalid values', () => {
113
+ const specPath = path.join(tempDir, 'spec.md');
114
+ fs.writeFileSync(specPath, `---
115
+ id: TC-0003-invalid-enum
116
+ title: Invalid priority value
117
+ priority: SuperHigh
118
+ caseType: MaybePositive
119
+ status: unknown-status
120
+ ---
121
+
122
+ # Invalid priority value
123
+
124
+ ## Test Steps
125
+ 1. Step one
126
+ `);
127
+ const result = SpecLinter.validateSpecFile(specPath);
128
+ const enumErrors = result.diagnostics.filter((d) => d.code === 'testspectra/invalid-frontmatter-enum');
129
+ expect(enumErrors.length).toBeGreaterThanOrEqual(1);
130
+ const priorityErr = enumErrors.find((d) => d.message.toLowerCase().includes('priority'));
131
+ expect(priorityErr).toBeDefined();
132
+ expect(priorityErr?.line).toBe(4); // Priority is on line 4
133
+ });
134
+ it('reports testspectra/missing-frontmatter when required H1 title section is missing in spec.md body', () => {
135
+ const specPath = path.join(tempDir, 'spec.md');
136
+ fs.writeFileSync(specPath, `---
137
+ id: TC-0004-no-h1
138
+ title: No Heading Section
139
+ priority: High
140
+ caseType: Positive
141
+ status: draft
142
+ ---
143
+
144
+ ## Test Steps
145
+ 1. Step without H1
146
+ `);
147
+ const result = SpecLinter.validateSpecFile(specPath);
148
+ const missingHeading = result.diagnostics.find((d) => d.code === 'testspectra/missing-frontmatter' && d.message.includes('expected top-level test title'));
149
+ expect(missingHeading).toBeDefined();
150
+ expect(missingHeading?.severity).toBe('error');
151
+ });
152
+ it('reports testspectra/missing-frontmatter when required ## Test Steps section is missing in spec.md body', () => {
153
+ const specPath = path.join(tempDir, 'spec.md');
154
+ fs.writeFileSync(specPath, `---
155
+ id: TC-0005-no-steps
156
+ title: No Steps Heading
157
+ priority: High
158
+ caseType: Positive
159
+ status: draft
160
+ ---
161
+
162
+ # No Steps Heading
163
+
164
+ Some narrative without Test Steps section.
165
+ `);
166
+ const result = SpecLinter.validateSpecFile(specPath);
167
+ const missingSteps = result.diagnostics.find((d) => d.code === 'testspectra/missing-frontmatter' && d.message.includes('expected "## Test Steps"'));
168
+ expect(missingSteps).toBeDefined();
169
+ expect(missingSteps?.severity).toBe('error');
170
+ });
171
+ it('warns when multiple H1 headings are declared in spec.md body', () => {
172
+ const specPath = path.join(tempDir, 'spec.md');
173
+ fs.writeFileSync(specPath, `---
174
+ id: TC-0006-multiple-h1
175
+ title: Multiple Headings
176
+ priority: High
177
+ caseType: Positive
178
+ status: draft
179
+ ---
180
+
181
+ # First Title
182
+
183
+ # Second Title
184
+
185
+ ## Test Steps
186
+ 1. Step
187
+ `);
188
+ const result = SpecLinter.validateSpecFile(specPath);
189
+ const multipleH1 = result.diagnostics.find((d) => d.severity === 'warning' && d.message.includes('Multiple top-level H1 headings'));
190
+ expect(multipleH1).toBeDefined();
191
+ });
192
+ it('reports testspectra/parse-error on malformed YAML syntax', () => {
193
+ const specPath = path.join(tempDir, 'spec.md');
194
+ fs.writeFileSync(specPath, `---
195
+ id: [broken yaml
196
+ title: Test
197
+ ---
198
+ `);
199
+ const result = SpecLinter.validateSpecFile(specPath);
200
+ expect(result.diagnostics.some((d) => d.code === 'testspectra/parse-error')).toBe(true);
201
+ });
202
+ });
203
+ describe('Suite File Validation (suite.md)', () => {
204
+ it('passes validation for a well-formed suite.md with H1 suite heading', () => {
205
+ const suitePath = path.join(tempDir, 'suite.md');
206
+ fs.writeFileSync(suitePath, `---
207
+ id: suite-auth
208
+ name: Authentication
209
+ description: User authentication flows
210
+ executionOrder: 1.0
211
+ parallel: true
212
+ status: active
213
+ ---
214
+
215
+ # Suite: Authentication
216
+ `);
217
+ const result = SpecLinter.validateSuiteFile(suitePath);
218
+ expect(result.diagnostics).toHaveLength(0);
219
+ expect(result.id).toBe('suite-auth');
220
+ });
221
+ it('reports testspectra/missing-frontmatter when suite id or title/name is missing', () => {
222
+ const suitePath = path.join(tempDir, 'suite.md');
223
+ fs.writeFileSync(suitePath, `---
224
+ description: No id and no name
225
+ ---
226
+
227
+ # Suite: Incomplete
228
+ `);
229
+ const result = SpecLinter.validateSuiteFile(suitePath);
230
+ expect(result.diagnostics.some((d) => d.code === 'testspectra/missing-frontmatter')).toBe(true);
231
+ });
232
+ it('reports testspectra/invalid-frontmatter-enum when suite status has invalid enum value', () => {
233
+ const suitePath = path.join(tempDir, 'suite.md');
234
+ fs.writeFileSync(suitePath, `---
235
+ id: suite-invalid-status
236
+ name: Invalid Status Suite
237
+ status: unknown-status
238
+ ---
239
+
240
+ # Suite: Invalid Status Suite
241
+ `);
242
+ const result = SpecLinter.validateSuiteFile(suitePath);
243
+ const enumErr = result.diagnostics.find((d) => d.code === 'testspectra/invalid-frontmatter-enum');
244
+ expect(enumErr).toBeDefined();
245
+ expect(enumErr?.severity).toBe('error');
246
+ });
247
+ it('supports in-memory content override for suite.md without writing to disk', () => {
248
+ const suitePath = path.join(tempDir, 'non-existent-suite.md');
249
+ const inMemoryContent = `---
250
+ id: suite-in-memory
251
+ title: In Memory Suite
252
+ status: active
253
+ ---
254
+
255
+ # Suite: In Memory Suite
256
+ `;
257
+ const result = SpecLinter.validateSuiteFile(suitePath, inMemoryContent);
258
+ expect(result.diagnostics).toHaveLength(0);
259
+ expect(result.id).toBe('suite-in-memory');
260
+ });
261
+ it('reports testspectra/missing-frontmatter when suite H1 title heading is missing', () => {
262
+ const suitePath = path.join(tempDir, 'suite.md');
263
+ fs.writeFileSync(suitePath, `---
264
+ id: suite-no-heading
265
+ name: No Heading
266
+ ---
267
+
268
+ Body without H1 suite heading.
269
+ `);
270
+ const result = SpecLinter.validateSuiteFile(suitePath);
271
+ const headingErr = result.diagnostics.find((d) => d.code === 'testspectra/missing-frontmatter' && d.message.includes('expected top-level suite title'));
272
+ expect(headingErr).toBeDefined();
273
+ });
274
+ });
275
+ describe('Workspace Validation & Duplicate ID Detection (testspectra/duplicate-spec-id)', () => {
276
+ it('detects duplicate IDs across different spec files', () => {
277
+ const tc1Dir = path.join(tempDir, 'TC-1');
278
+ const tc2Dir = path.join(tempDir, 'TC-2');
279
+ fs.mkdirSync(tc1Dir, { recursive: true });
280
+ fs.mkdirSync(tc2Dir, { recursive: true });
281
+ const specContent = `---
282
+ id: TC-DUPLICATE-001
283
+ title: Duplicate test case
284
+ priority: High
285
+ caseType: Positive
286
+ status: automated
287
+ ---
288
+
289
+ # Duplicate test case
290
+
291
+ ## Test Steps
292
+ 1. Duplicate step
293
+ `;
294
+ fs.writeFileSync(path.join(tc1Dir, 'spec.md'), specContent);
295
+ fs.writeFileSync(path.join(tc2Dir, 'spec.md'), specContent);
296
+ const result = SpecLinter.validateWorkspace(tempDir);
297
+ expect(result.valid).toBe(false);
298
+ const dupErrors = result.diagnostics.filter((d) => d.code === 'testspectra/duplicate-spec-id');
299
+ expect(dupErrors).toHaveLength(1);
300
+ expect(dupErrors[0].message).toContain('TC-DUPLICATE-001');
301
+ });
302
+ it('successfully validates workspace with valid specs', () => {
303
+ const validSuiteDir = path.join(tempDir, 'ValidSuite');
304
+ const validTcDir = path.join(validSuiteDir, 'TC-001');
305
+ fs.mkdirSync(validTcDir, { recursive: true });
306
+ fs.writeFileSync(path.join(validSuiteDir, 'suite.md'), `---
307
+ id: suite-valid
308
+ name: Valid Suite
309
+ status: active
310
+ ---
311
+
312
+ # Suite: Valid Suite
313
+ `);
314
+ fs.writeFileSync(path.join(validTcDir, 'spec.md'), `---
315
+ id: TC-VALID-001
316
+ title: Valid Test Case
317
+ priority: High
318
+ caseType: Positive
319
+ status: automated
320
+ ---
321
+
322
+ # Valid Test Case
323
+
324
+ ## Test Steps
325
+ 1. Open page
326
+ `);
327
+ fs.writeFileSync(path.join(validTcDir, 'web.test.ts'), `it('Valid Test Case', async () => {});\n`);
328
+ const result = SpecLinter.validateWorkspace(tempDir);
329
+ expect(result.valid).toBe(true);
330
+ expect(result.stats.errorCount).toBe(0);
331
+ expect(result.stats.totalSpecs).toBe(1);
332
+ expect(result.stats.totalSuites).toBe(1);
333
+ });
334
+ });
335
+ describe('parseMarkdownFrontmatter Delimiter Detection Cases', () => {
336
+ it('Case 1: detects missing opening delimiter when closing delimiter exists', () => {
337
+ const content = `id: suite-api-interception\nname: ApiInterception\n---\n\n# Suite: ApiInterception\n`;
338
+ const result = parseMarkdownFrontmatter(content, 'specs/api/suite.md');
339
+ expect(result.hasFrontmatter).toBe(false);
340
+ expect(result.diagnostics).toHaveLength(1);
341
+ expect(result.diagnostics[0].code).toBe('testspectra/missing-frontmatter');
342
+ expect(result.diagnostics[0].message).toBe('Missing opening YAML frontmatter delimiter (---) at line 1');
343
+ });
344
+ it('Case 2: detects missing closing delimiter when opening delimiter exists', () => {
345
+ const content = `---\nid: suite-api-interception\nname: ApiInterception\n\n# Suite: ApiInterception\n`;
346
+ const result = parseMarkdownFrontmatter(content, 'specs/api/suite.md');
347
+ expect(result.hasFrontmatter).toBe(false);
348
+ expect(result.diagnostics).toHaveLength(1);
349
+ expect(result.diagnostics[0].code).toBe('testspectra/missing-frontmatter');
350
+ expect(result.diagnostics[0].message).toBe('Missing closing YAML frontmatter delimiter (---). Expected closing "---"');
351
+ });
352
+ it('Case 3: detects missing delimiters when frontmatter properties exist', () => {
353
+ const content = `id: suite-api-interception\nname: ApiInterception\nexecutionOrder: 1.4\nparallel: true\n\n# Suite: ApiInterception\n`;
354
+ const result = parseMarkdownFrontmatter(content, 'specs/api/suite.md');
355
+ expect(result.hasFrontmatter).toBe(false);
356
+ expect(result.diagnostics).toHaveLength(1);
357
+ expect(result.diagnostics[0].code).toBe('testspectra/missing-frontmatter');
358
+ expect(result.diagnostics[0].message).toBe('Missing YAML frontmatter delimiters (---) at line 1');
359
+ });
360
+ it('Case 4: detects missing delimiter when file is empty or only has markdown heading', () => {
361
+ const content = `# Suite: ApiInterception\n`;
362
+ const result = parseMarkdownFrontmatter(content, 'specs/api/suite.md');
363
+ expect(result.hasFrontmatter).toBe(false);
364
+ expect(result.diagnostics).toHaveLength(1);
365
+ expect(result.diagnostics[0].code).toBe('testspectra/missing-frontmatter');
366
+ expect(result.diagnostics[0].message).toBe('Missing YAML frontmatter delimiter (---) at line 1');
367
+ });
368
+ });
369
+ });
@@ -0,0 +1 @@
1
+ export {};