@testspectra/cli 1.1.7 → 1.1.8-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.
- package/bin/.testspectra-runner.hash +1 -1
- package/bin/testspectra-runner +0 -0
- package/demo-app/assets/index-BhlW-eXG.css +1 -0
- package/demo-app/assets/index-DOtRypCa.js +154 -0
- package/demo-app/index.html +2 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/commands/__tests__/run-e2e.test.js +254 -13
- package/dist/commands/__tests__/run.test.js +92 -16
- package/dist/commands/demo.js +17 -7
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +6 -1
- package/dist/commands/lint.d.ts +5 -0
- package/dist/commands/lint.js +55 -0
- package/dist/commands/refactor.d.ts +2 -1
- package/dist/commands/refactor.js +129 -35
- package/dist/commands/run.d.ts +2 -3
- package/dist/commands/run.js +93 -28
- package/dist/commands/skills.d.ts +9 -0
- package/dist/commands/skills.js +28 -0
- package/dist/commands/sync-types.js +1 -0
- package/dist/commands/test.d.ts +17 -0
- package/dist/commands/test.js +76 -0
- package/dist/commands/watch.js +9 -3
- package/dist/config/loader.js +1 -1
- package/dist/config/schema.d.ts +5 -114
- package/dist/config/schema.js +1 -17
- package/dist/generator/__tests__/tsconfig-isolation.test.js +6 -2
- package/dist/generator/__tests__/type-generator.test.js +1 -1
- package/dist/generator/tsconfig-generator.js +44 -16
- package/dist/generator/type-generator.d.ts +24 -1
- package/dist/generator/type-generator.js +104 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.js +65 -29
- package/dist/linter/__tests__/anonymous-export.test.js +184 -0
- package/dist/linter/__tests__/component-test-imports.test.d.ts +1 -0
- package/dist/linter/__tests__/component-test-imports.test.js +115 -0
- package/dist/linter/__tests__/shared-lib-boundary.test.d.ts +1 -0
- package/dist/linter/__tests__/shared-lib-boundary.test.js +270 -0
- package/dist/linter/__tests__/spec-schema.test.d.ts +1 -0
- package/dist/linter/__tests__/spec-schema.test.js +369 -0
- package/dist/linter/__tests__/step-parity.test.d.ts +1 -0
- package/dist/linter/__tests__/step-parity.test.js +372 -0
- package/dist/linter/discovery.d.ts +30 -0
- package/dist/linter/discovery.js +249 -0
- package/dist/linter/extractor.d.ts +67 -0
- package/dist/linter/extractor.js +245 -0
- package/dist/linter/index.d.ts +7 -0
- package/dist/linter/index.js +7 -0
- package/dist/linter/parser.d.ts +24 -0
- package/dist/linter/parser.js +219 -0
- package/dist/linter/schemas/spec.schema.d.ts +83 -0
- package/dist/linter/schemas/spec.schema.js +150 -0
- package/dist/linter/schemas/suite.schema.d.ts +59 -0
- package/dist/linter/schemas/suite.schema.js +83 -0
- package/dist/linter/spec-linter.d.ts +23 -0
- package/dist/linter/spec-linter.js +630 -0
- package/dist/linter/types.d.ts +28 -0
- package/dist/linter/types.js +1 -0
- package/dist/refactor/__tests__/refactor-engine.test.js +698 -0
- package/dist/refactor/refactor-engine.d.ts +25 -0
- package/dist/refactor/refactor-engine.js +374 -42
- package/dist/reporter/semantic-formatter.js +17 -2
- package/dist/reporter/types.d.ts +1 -1
- package/dist/runner/bridge.d.ts +1 -1
- package/dist/runner/bridge.js +34 -21
- package/dist/runner/reporter.d.ts +2 -0
- package/dist/runner/reporter.js +11 -3
- package/dist/utils/__tests__/api-server.test.d.ts +1 -0
- package/dist/utils/__tests__/api-server.test.js +62 -0
- package/dist/utils/__tests__/demo-server.test.d.ts +1 -0
- package/dist/utils/__tests__/demo-server.test.js +35 -0
- package/dist/utils/api-server.d.ts +19 -0
- package/dist/utils/api-server.js +179 -0
- package/dist/utils/demo-server.d.ts +5 -0
- package/dist/utils/demo-server.js +8 -1
- package/package.json +29 -25
- package/templates/default/CLAUDE.md +5 -0
- package/templates/default/package.json +3 -3
- package/templates/default/page-objects/AuthPage/mobile.ts +5 -0
- package/templates/default/page-objects/AuthPage/web.ts +10 -0
- package/templates/default/page-objects/MatchersPage/mobile.ts +26 -2
- package/templates/default/page-objects/MatchersPage/web.ts +8 -2
- package/templates/default/page-objects/NavigationPage/mobile.ts +1 -1
- package/templates/default/page-objects/NavigationPage/web.ts +1 -1
- package/templates/default/page-objects/PlaygroundPage/mobile.ts +12 -6
- package/templates/default/page-objects/PlaygroundPage/web.ts +2 -2
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +45 -3
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +7 -0
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +8 -4
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/android.test.ts +3 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/common.test.ts +3 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +13 -2
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +7 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +6 -4
- package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/common.test.ts +30 -0
- package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/spec.md +22 -0
- package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/common.test.ts +27 -0
- package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/spec.md +20 -0
- package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/spec.md +24 -0
- package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/web.test.ts +26 -0
- package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/spec.md +37 -0
- package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/web.test.ts +21 -0
- package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/spec.md +29 -0
- package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/web.test.ts +16 -0
- package/templates/default/specs/ApiInterception/suite.md +1 -0
- package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/common.test.ts +30 -0
- package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/spec.md +23 -0
- package/templates/default/specs/ApiSeeding/hooks/after/common.hook.ts +16 -0
- package/templates/default/specs/ApiSeeding/hooks/before/common.hook.ts +28 -0
- package/templates/default/specs/ApiSeeding/suite.md +9 -0
- package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/mobile.test.ts +7 -0
- package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/spec.md +19 -0
- package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/web.test.ts +9 -0
- package/templates/default/specs/Authentication/suite.md +9 -0
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +1 -1
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +5 -0
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +1 -1
- package/templates/default/specs/BrowserContext/TC-0022-history-navigation/mobile.test.ts +17 -0
- package/templates/default/specs/BrowserContext/TC-0022-history-navigation/spec.md +28 -0
- package/templates/default/specs/BrowserContext/TC-0022-history-navigation/web.test.ts +26 -0
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/common.test.ts +26 -0
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/spec.md +7 -0
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +1 -1
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +5 -0
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +1 -1
- package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/common.test.ts +14 -0
- package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/spec.md +21 -0
- package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/mobile.test.ts +28 -0
- package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/spec.md +29 -0
- package/templates/default/specs/DeepLink/suite.md +9 -0
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +1 -1
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +5 -0
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +1 -1
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +1 -1
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +7 -0
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +1 -1
- package/templates/default/specs/ElementMatchers/suite.md +2 -1
- package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -0
- package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -0
- package/templates/default/specs/EnvironmentConfig/suite.md +9 -0
- package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/mobile.test.ts +26 -0
- package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/spec.md +22 -0
- package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/mobile.test.ts +13 -0
- package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/spec.md +20 -0
- package/templates/default/specs/PermissionRationale/suite.md +9 -0
- package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +1 -1
- package/templates/default/specs/Playground/TC-0001-form-controls/spec.md +6 -0
- package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +1 -1
- package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +1 -1
- package/templates/default/specs/Playground/TC-0002-toggle-elements/spec.md +6 -0
- package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +1 -1
- package/templates/default/spectra.config.ts +1 -17
- package/templates/default/support/actions/fillCredentials/mobile.action.ts +3 -3
- package/templates/default/support/actions/fillCredentials/web.action.ts +1 -1
- package/templates/default/support/actions/seedSession/mobile.action.ts +9 -0
- package/templates/default/support/actions/seedSession/web.action.ts +13 -0
- package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +1 -1
- package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +1 -1
- package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +1 -1
- package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +1 -1
- package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db +0 -0
- package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-shm +0 -0
- package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-wal +0 -0
- package/templates/nx/.nx/workspace-data/d/daemon.log +5388 -168
- package/templates/nx/.nx/workspace-data/d/server-process.json +3 -0
- package/templates/nx/.nx/workspace-data/file-map.json +464 -0
- package/templates/nx/.nx/workspace-data/lockfile.hash +1 -0
- package/templates/nx/.nx/workspace-data/nx_files.nxt +0 -0
- package/templates/nx/.nx/workspace-data/parsed-lock-file.json +13485 -0
- package/templates/nx/.nx/workspace-data/project-graph.json +14389 -0
- package/templates/nx/package.json +3 -3
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +25 -1
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +2 -2
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +22 -1
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +2 -2
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +1 -1
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +1 -1
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +1 -1
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +1 -1
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +1 -1
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +1 -1
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +1 -1
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +1 -1
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +1 -1
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +1 -1
- package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -0
- package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -0
- package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/suite.md +9 -0
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/mobile.test.ts +1 -1
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +1 -1
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +1 -1
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +1 -1
- package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +1 -1
- package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +83 -29
- package/templates/nx/shared/testing/page-objects/NavigationPage/mobile.ts +1 -1
- package/templates/nx/shared/testing/page-objects/NavigationPage/web.ts +1 -1
- package/templates/nx/shared/testing/page-objects/PlaygroundPage/mobile.ts +1 -1
- package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +2 -2
- package/templates/nx/shared/testing/support/actions/fillCredentials/mobile.action.ts +1 -1
- package/templates/nx/shared/testing/support/actions/fillCredentials/web.action.ts +1 -1
- package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/mobile.step.ts +1 -1
- package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +1 -1
- package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +1 -1
- package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +1 -1
- package/templates/nx/spectra.config.ts +1 -17
- package/templates/react-component/.cursorrules +17 -0
- package/templates/react-component/.vscode/extensions.json +5 -0
- package/templates/react-component/CLAUDE.md +14 -0
- package/templates/react-component/README.md +69 -0
- package/templates/react-component/fixtures/component-mock.json +12 -0
- package/templates/react-component/global-hooks/after/web.hook.ts +3 -0
- package/templates/react-component/global-hooks/before/web.hook.ts +3 -0
- package/templates/react-component/package.json +27 -0
- package/templates/react-component/page-objects/BadgeComponent/web.ts +23 -0
- package/templates/react-component/page-objects/ButtonComponent/web.ts +23 -0
- package/templates/react-component/page-objects/InputComponent/web.ts +13 -0
- package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/spec.md +18 -0
- package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/web.test.tsx +11 -0
- package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/spec.md +18 -0
- package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/web.test.tsx +10 -0
- package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/spec.md +18 -0
- package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/web.test.tsx +16 -0
- package/templates/react-component/specs/BadgeComponent/suite.md +12 -0
- package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/spec.md +19 -0
- package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/web.test.tsx +28 -0
- package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/spec.md +19 -0
- package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/web.test.tsx +22 -0
- package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/spec.md +18 -0
- package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/web.test.tsx +8 -0
- package/templates/react-component/specs/ButtonComponent/suite.md +12 -0
- package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/spec.md +20 -0
- package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/web.test.tsx +28 -0
- package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/spec.md +18 -0
- package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/web.test.tsx +9 -0
- package/templates/react-component/specs/InputComponent/suite.md +12 -0
- package/templates/react-component/spectra.config.ts +27 -0
- package/templates/react-component/src/components/Badge/Badge.tsx +60 -0
- package/templates/react-component/src/components/Button/Button.tsx +57 -0
- package/templates/react-component/src/components/Input/Input.tsx +41 -0
- package/templates/react-component/src/test-utils.tsx +23 -0
- package/templates/react-component/support/actions/fillInputField/web.action.ts +9 -0
- package/templates/react-component/support/steps/verifyButtonState/web.step.ts +12 -0
- package/templates/react-component/tsconfig.json +8 -0
- package/demo-app/assets/index-DPfv5_EF.js +0 -116
- package/demo-app/assets/index-gIwu2EMV.css +0 -1
- package/dist/commands/__tests__/cloud-run.test.js +0 -93
- package/dist/commands/cloud-run.d.ts +0 -13
- package/dist/commands/cloud-run.js +0 -120
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -17
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -17
- /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 {};
|