doc-detective 3.6.2 → 3.7.0-preview.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/package.json +14 -5
  2. package/samples/http.spec.yaml +38 -36
  3. package/samples/kitten-search.spec.json +24 -14
  4. package/scripts/bump-sync-version-core.js +1 -1
  5. package/src/cli/App.mjs +39 -0
  6. package/src/cli/ResultsSummary.mjs +162 -0
  7. package/src/cli/TestRunner.mjs +41 -0
  8. package/src/cli/builder/DebugRunner.mjs +963 -0
  9. package/src/cli/builder/FieldEditor.mjs +433 -0
  10. package/src/cli/builder/SpecSelector.mjs +250 -0
  11. package/src/cli/builder/StepEditor.mjs +644 -0
  12. package/src/cli/builder/TestBuilder.mjs +1159 -0
  13. package/src/cli/builder/TestEditor.mjs +486 -0
  14. package/src/cli/builder/builderRunner.js +86 -0
  15. package/src/cli/builder/components.mjs +442 -0
  16. package/src/cli/builder/index.js +11 -0
  17. package/src/cli/builder/schemaUtils.mjs +559 -0
  18. package/src/cli/builder/sourceFileUtils.js +892 -0
  19. package/src/cli/runner.js +105 -0
  20. package/src/index.js +319 -4
  21. package/src/utils.js +19 -18
  22. package/test/builder/DebugRunner.test.mjs +441 -0
  23. package/test/builder/SpecSelector.test.mjs +496 -0
  24. package/test/builder/StepEditor.test.mjs +379 -0
  25. package/test/builder/TestBuilder.test.mjs +528 -0
  26. package/test/builder/TestEditor.test.mjs +672 -0
  27. package/test/builder/editor.test.mjs +452 -0
  28. package/test/builder/fixtures.mjs +266 -0
  29. package/test/builder/inlineSourceUpdates.test.mjs +977 -0
  30. package/test/builder/schemaUtils.test.mjs +576 -0
  31. package/test/builder/sourceFileUtils.test.mjs +823 -0
  32. package/test/resolvedTests.test.js +50 -42
  33. package/test/server/index.js +26 -24
  34. package/samples/tests.spec.json +0 -70
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doc-detective",
3
- "version": "3.6.2",
3
+ "version": "3.7.0-preview.0",
4
4
  "description": "Treat doc content as testable assertions to validate doc accuracy and product UX.",
5
5
  "bin": {
6
6
  "doc-detective": "src/index.js"
@@ -8,7 +8,7 @@
8
8
  "main": "./src/index.js",
9
9
  "scripts": {
10
10
  "mocha": "mocha",
11
- "test": "mocha test/*.test.js",
11
+ "test": "mocha --exit test/*.test.js test/builder/*.test.mjs",
12
12
  "prerunTests": "node ./src/checkDependencies.js",
13
13
  "runTests": "node ./src/index.js runTests",
14
14
  "start": "node ./src/index.js",
@@ -34,14 +34,23 @@
34
34
  "dependencies": {
35
35
  "@ffmpeg-installer/ffmpeg": "^1.1.0",
36
36
  "axios": "^1.13.2",
37
- "doc-detective-common": "^3.6.0",
38
- "doc-detective-core": "^3.6.2",
37
+ "doc-detective-common": "^3.7.0-preview.0",
38
+ "doc-detective-core": "^3.7.0-preview.0",
39
+ "doc-detective-resolver": "^3.7.0-preview.0",
40
+ "ink": "^6.5.1",
41
+ "ink-select-input": "^6.2.0",
42
+ "ink-spinner": "^5.0.0",
43
+ "js-yaml": "^4.1.1",
44
+ "react": "^19.2.1",
45
+ "yaml": "^2.8.2",
39
46
  "yargs": "^17.7.2"
40
47
  },
41
48
  "devDependencies": {
42
49
  "body-parser": "^2.2.1",
43
50
  "chai": "^6.2.1",
44
51
  "express": "^5.2.1",
45
- "mocha": "^11.7.5"
52
+ "ink-testing-library": "^4.0.0",
53
+ "mocha": "^11.7.5",
54
+ "sinon": "^19.0.2"
46
55
  }
47
56
  }
@@ -1,37 +1,39 @@
1
1
  tests:
2
- - steps:
3
- - loadVariables: env
4
- - httpRequest:
5
- url: http://localhost:8092/api/users
6
- method: post
7
- request:
8
- body:
9
- name: $USER
10
- job: $JOB
11
- response:
12
- body:
13
- name: John Doe
14
- job: Software Engineer
15
- - httpRequest:
16
- url: http://localhost:8092/api/users
17
- method: post
18
- request:
19
- body:
20
- data:
21
- - first_name: George
22
- last_name: Bluth
23
- id: 1
24
- response:
25
- body:
26
- data:
27
- - first_name: George
28
- last_name: Bluth
29
- variables:
30
- ID: $$response.body.data[0].id
31
- - httpRequest:
32
- url: http://localhost:8092/api/$ID
33
- method: get
34
- timeout: 1000
35
- savePath: response.json
36
- maxVariation: 0
37
- overwrite: aboveVariation
2
+ - steps:
3
+ - loadVariables: env
4
+ - httpRequest:
5
+ url: http://localhost:8092/api/users
6
+ method: post
7
+ request:
8
+ body:
9
+ name: $USER
10
+ job: $JOB
11
+ response:
12
+ body:
13
+ name: John Doe
14
+ job: Software Engineer
15
+ - httpRequest:
16
+ url: http://localhost:8092/api/users
17
+ method: post
18
+ request:
19
+ body:
20
+ data:
21
+ - first_name: George
22
+ last_name: Bluth
23
+ id: 1
24
+ response:
25
+ body:
26
+ data:
27
+ - first_name: George
28
+ last_name: Bluth
29
+ variables:
30
+ ID: $$response.body.data[0].id
31
+ - httpRequest:
32
+ url: http://localhost:8092/api/$ID
33
+ method: get
34
+ timeout: 1000
35
+ savePath: response.json
36
+ maxVariation: 0
37
+ overwrite: aboveVariation
38
+ testId: foobar
39
+ specId: edf6cf93-04f4-4931-ae7d-037bd75def9a
@@ -3,26 +3,36 @@
3
3
  {
4
4
  "steps": [
5
5
  {
6
- "action": "goTo",
7
- "url": "https://www.duckduckgo.com"
6
+ "goTo": {
7
+ "url": "https://www.duckduckgo.com"
8
+ }
8
9
  },
9
10
  {
10
- "action": "find",
11
- "selector": "#searchbox_input",
12
- "click": true,
13
- "typeKeys": {
14
- "keys": ["American Shorthair kittens", "$ENTER$"]
15
- }
11
+ "find": {
12
+ "selector": "#searchbox_input",
13
+ "click": true,
14
+ "type": {
15
+ "keys": ["American Shorthair kittens", "$ENTER$"]
16
+ }
17
+ },
18
+ "variables": {}
16
19
  },
17
20
  {
18
- "action": "find",
19
- "selector": "[data-testid='web-vertical']"
21
+ "find": {
22
+ "selector": "[data-testid='web-vertical']"
23
+ },
24
+ "variables": {}
20
25
  },
21
26
  {
22
- "action": "saveScreenshot",
23
- "path": "search-results.png"
27
+ "screenshot": {
28
+ "path": "search-results.png",
29
+ "maxVariation": 0
30
+ }
24
31
  }
25
- ]
32
+ ],
33
+ "detectSteps": true,
34
+ "testId": "efd34931-ba4f-47be-85bb-d8cb61123b8d"
26
35
  }
27
- ]
36
+ ],
37
+ "specId": "02db96fc-7912-4eb6-ad79-9c96c2f1f8db"
28
38
  }
@@ -70,11 +70,11 @@ function main() {
70
70
  console.log(`Version mismatch detected. Setting version to: ${newVersion}`);
71
71
  } else {
72
72
  // Project version is already equal or greater than core version, just bump patch
73
+ newVersion = `${projMajor}.${projMinor}.${projPatch + 1}`;
73
74
  console.log(
74
75
  "Project version is current or ahead. Bumping patch version to:",
75
76
  newVersion
76
77
  );
77
- newVersion = `${projMajor}.${projMinor}.${projPatch + 1}`;
78
78
  }
79
79
 
80
80
  // Validate the new version before setting it
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ const { createElement: h } = React;
3
+ import { Box, Text } from 'ink';
4
+ import Spinner from 'ink-spinner';
5
+ import TestRunner from './TestRunner.mjs';
6
+ import ResultsSummary from './ResultsSummary.mjs';
7
+
8
+ const App = ({ config, state }) => {
9
+ return h(Box, { flexDirection: 'column', paddingY: 1 },
10
+ h(Box, { marginBottom: 1 },
11
+ h(Text, { bold: true, color: 'cyan' }, 'Doc Detective')
12
+ ),
13
+
14
+ state.phase === 'initializing' && h(Box, null,
15
+ h(Text, { color: 'gray' },
16
+ h(Spinner, { type: 'dots' }), ' Initializing...'
17
+ )
18
+ ),
19
+
20
+ state.phase === 'running' && h(TestRunner, {
21
+ config,
22
+ progress: state.progress,
23
+ currentSpec: state.currentSpec,
24
+ currentTest: state.currentTest,
25
+ }),
26
+
27
+ state.phase === 'completed' && state.results && h(ResultsSummary, {
28
+ results: state.results,
29
+ config,
30
+ }),
31
+
32
+ state.phase === 'error' && h(Box, { flexDirection: 'column' },
33
+ h(Text, { color: 'red', bold: true }, '✖ Error'),
34
+ h(Text, { color: 'red' }, state.error?.message || String(state.error))
35
+ )
36
+ );
37
+ };
38
+
39
+ export default App;
@@ -0,0 +1,162 @@
1
+ import React from 'react';
2
+ const { createElement: h } = React;
3
+ import { Box, Text } from 'ink';
4
+
5
+ const ResultsSummary = ({ results, config }) => {
6
+ if (!results || !results.summary) {
7
+ return h(Box, null,
8
+ h(Text, { color: 'yellow' }, 'No results available.')
9
+ );
10
+ }
11
+
12
+ const { specs, tests, contexts, steps } = results.summary;
13
+
14
+ // Calculate totals
15
+ const totalSpecs = specs ? specs.pass + specs.fail + specs.warning + specs.skipped : 0;
16
+ const totalTests = tests ? tests.pass + tests.fail + tests.warning + tests.skipped : 0;
17
+ const totalContexts = contexts ? contexts.pass + contexts.fail + contexts.warning + contexts.skipped : 0;
18
+ const totalSteps = steps ? steps.pass + steps.fail + steps.warning + steps.skipped : 0;
19
+
20
+ // Check for failures
21
+ const hasFailures =
22
+ (specs && specs.fail > 0) ||
23
+ (tests && tests.fail > 0) ||
24
+ (contexts && contexts.fail > 0) ||
25
+ (steps && steps.fail > 0);
26
+
27
+ // Check if all skipped
28
+ const allSpecsSkipped =
29
+ specs && specs.pass === 0 && specs.fail === 0 && specs.skipped > 0;
30
+
31
+ const failedItems = hasFailures && results.specs ? getFailedItems(results) : [];
32
+
33
+ return h(Box, { flexDirection: 'column' },
34
+ // Header
35
+ h(Box, { marginBottom: 1 },
36
+ h(Text, { bold: true, underline: true }, 'Test Results Summary')
37
+ ),
38
+
39
+ // Specs summary
40
+ specs && h(Box, { flexDirection: 'column', marginBottom: 1 },
41
+ h(Text, { bold: true }, 'Specs'),
42
+ h(Box, { marginLeft: 2 },
43
+ h(Text, null, `Total: ${totalSpecs} `),
44
+ specs.pass > 0 && h(Text, { color: 'green' }, `✓ ${specs.pass} passed `),
45
+ specs.fail > 0 && h(Text, { color: 'red' }, `✖ ${specs.fail} failed `),
46
+ specs.warning > 0 && h(Text, { color: 'yellow' }, `⚠ ${specs.warning} warnings `),
47
+ specs.skipped > 0 && h(Text, { color: 'gray' }, `⊘ ${specs.skipped} skipped`)
48
+ )
49
+ ),
50
+
51
+ // Tests summary
52
+ tests && h(Box, { flexDirection: 'column', marginBottom: 1 },
53
+ h(Text, { bold: true }, 'Tests'),
54
+ h(Box, { marginLeft: 2 },
55
+ h(Text, null, `Total: ${totalTests} `),
56
+ tests.pass > 0 && h(Text, { color: 'green' }, `✓ ${tests.pass} passed `),
57
+ tests.fail > 0 && h(Text, { color: 'red' }, `✖ ${tests.fail} failed `),
58
+ tests.warning > 0 && h(Text, { color: 'yellow' }, `⚠ ${tests.warning} warnings `),
59
+ tests.skipped > 0 && h(Text, { color: 'gray' }, `⊘ ${tests.skipped} skipped`)
60
+ )
61
+ ),
62
+
63
+ // Contexts summary
64
+ contexts && h(Box, { flexDirection: 'column', marginBottom: 1 },
65
+ h(Text, { bold: true }, 'Contexts'),
66
+ h(Box, { marginLeft: 2 },
67
+ h(Text, null, `Total: ${totalContexts} `),
68
+ contexts.pass > 0 && h(Text, { color: 'green' }, `✓ ${contexts.pass} passed `),
69
+ contexts.fail > 0 && h(Text, { color: 'red' }, `✖ ${contexts.fail} failed `),
70
+ contexts.warning > 0 && h(Text, { color: 'yellow' }, `⚠ ${contexts.warning} warnings `),
71
+ contexts.skipped > 0 && h(Text, { color: 'gray' }, `⊘ ${contexts.skipped} skipped`)
72
+ )
73
+ ),
74
+
75
+ // Steps summary
76
+ steps && h(Box, { flexDirection: 'column', marginBottom: 1 },
77
+ h(Text, { bold: true }, 'Steps'),
78
+ h(Box, { marginLeft: 2 },
79
+ h(Text, null, `Total: ${totalSteps} `),
80
+ steps.pass > 0 && h(Text, { color: 'green' }, `✓ ${steps.pass} passed `),
81
+ steps.fail > 0 && h(Text, { color: 'red' }, `✖ ${steps.fail} failed `),
82
+ steps.warning > 0 && h(Text, { color: 'yellow' }, `⚠ ${steps.warning} warnings `),
83
+ steps.skipped > 0 && h(Text, { color: 'gray' }, `⊘ ${steps.skipped} skipped`)
84
+ )
85
+ ),
86
+
87
+ // Overall status
88
+ h(Box, { marginTop: 1 },
89
+ allSpecsSkipped
90
+ ? h(Text, { color: 'yellow' }, '⚠ All items were skipped')
91
+ : hasFailures
92
+ ? h(Text, { color: 'red', bold: true }, '✖ Tests failed')
93
+ : h(Text, { color: 'green', bold: true }, '✓ All tests passed!')
94
+ ),
95
+
96
+ // Failed items detail
97
+ hasFailures && results.specs && h(Box, { flexDirection: 'column', marginTop: 1 },
98
+ h(Text, { bold: true, color: 'red' }, 'Failed Items:'),
99
+ ...failedItems.map((item, index) =>
100
+ h(Box, { key: index, marginLeft: 2 },
101
+ h(Text, { color: 'red' }, `• ${item}`)
102
+ )
103
+ )
104
+ )
105
+ );
106
+ };
107
+
108
+ // Helper function to extract failed items
109
+ function getFailedItems(results) {
110
+ const failures = [];
111
+
112
+ if (!results.specs) return failures;
113
+
114
+ results.specs.forEach((spec, specIndex) => {
115
+ if (spec.result === 'FAIL') {
116
+ failures.push(`Spec: ${spec.specId || `Spec ${specIndex + 1}`}`);
117
+ }
118
+
119
+ if (spec.tests && spec.tests.length > 0) {
120
+ spec.tests.forEach((test, testIndex) => {
121
+ if (test.result === 'FAIL') {
122
+ failures.push(
123
+ `Test: ${test.testId || `Test ${testIndex + 1}`} (from ${
124
+ spec.specId || `Spec ${specIndex + 1}`
125
+ })`
126
+ );
127
+ }
128
+
129
+ if (test.contexts && test.contexts.length > 0) {
130
+ test.contexts.forEach((context, contextIndex) => {
131
+ if (
132
+ context.result === 'FAIL' ||
133
+ (context.result && context.result.status === 'FAIL')
134
+ ) {
135
+ failures.push(
136
+ `Context: ${context.platform || 'unknown'}/${
137
+ context.browser ? context.browser.name : 'unknown'
138
+ } (from ${test.testId || `Test ${testIndex + 1}`})`
139
+ );
140
+ }
141
+
142
+ if (context.steps && context.steps.length > 0) {
143
+ context.steps.forEach((step, stepIndex) => {
144
+ if (step.result === 'FAIL') {
145
+ failures.push(
146
+ `Step: ${step.stepId || `Step ${stepIndex + 1}`} - ${
147
+ step.resultDescription || 'Unknown error'
148
+ }`
149
+ );
150
+ }
151
+ });
152
+ }
153
+ });
154
+ }
155
+ });
156
+ }
157
+ });
158
+
159
+ return failures;
160
+ }
161
+
162
+ export default ResultsSummary;
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ const { createElement: h } = React;
3
+ import { Box, Text } from 'ink';
4
+ import Spinner from 'ink-spinner';
5
+
6
+ const TestRunner = ({ config, progress, currentSpec, currentTest }) => {
7
+ return h(Box, { flexDirection: 'column' },
8
+ h(Box, { marginBottom: 1 },
9
+ h(Text, { color: 'yellow' },
10
+ h(Spinner, { type: 'dots' }), ' Running tests...'
11
+ )
12
+ ),
13
+
14
+ // Progress bars
15
+ progress.specs.total > 0 && h(Box, { marginBottom: 0 },
16
+ h(Text, { color: 'gray' },
17
+ `Specs: ${progress.specs.current}/${progress.specs.total}`
18
+ )
19
+ ),
20
+
21
+ progress.tests.total > 0 && h(Box, { marginBottom: 0 },
22
+ h(Text, { color: 'gray' },
23
+ `Tests: ${progress.tests.current}/${progress.tests.total}`
24
+ )
25
+ ),
26
+
27
+ progress.steps.total > 0 && h(Box, { marginBottom: 1 },
28
+ h(Text, { color: 'gray' },
29
+ `Steps: ${progress.steps.current}/${progress.steps.total}`
30
+ )
31
+ ),
32
+
33
+ // Current execution context
34
+ currentSpec && h(Box, { flexDirection: 'column', marginTop: 1 },
35
+ h(Text, { color: 'cyan' }, `Current: ${currentSpec}`),
36
+ currentTest && h(Text, { color: 'gray', dimColor: true }, `→ ${currentTest}`)
37
+ )
38
+ );
39
+ };
40
+
41
+ export default TestRunner;