ado-sync 0.1.24 → 0.1.27

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 (46) hide show
  1. package/README.md +240 -678
  2. package/dist/azure/client.d.ts +3 -0
  3. package/dist/azure/client.js +6 -0
  4. package/dist/azure/client.js.map +1 -1
  5. package/dist/azure/test-cases.d.ts +8 -3
  6. package/dist/azure/test-cases.js +406 -25
  7. package/dist/azure/test-cases.js.map +1 -1
  8. package/dist/cli.js +51 -4
  9. package/dist/cli.js.map +1 -1
  10. package/dist/config.js +111 -5
  11. package/dist/config.js.map +1 -1
  12. package/dist/parsers/csharp.d.ts +30 -0
  13. package/dist/parsers/csharp.js +257 -0
  14. package/dist/parsers/csharp.js.map +1 -0
  15. package/dist/parsers/gherkin.d.ts +4 -1
  16. package/dist/parsers/gherkin.js +19 -4
  17. package/dist/parsers/gherkin.js.map +1 -1
  18. package/dist/parsers/java.d.ts +40 -0
  19. package/dist/parsers/java.js +329 -0
  20. package/dist/parsers/java.js.map +1 -0
  21. package/dist/parsers/javascript.d.ts +33 -0
  22. package/dist/parsers/javascript.js +261 -0
  23. package/dist/parsers/javascript.js.map +1 -0
  24. package/dist/parsers/markdown.d.ts +4 -1
  25. package/dist/parsers/markdown.js +5 -3
  26. package/dist/parsers/markdown.js.map +1 -1
  27. package/dist/parsers/python.d.ts +34 -0
  28. package/dist/parsers/python.js +305 -0
  29. package/dist/parsers/python.js.map +1 -0
  30. package/dist/parsers/shared.d.ts +18 -0
  31. package/dist/parsers/shared.js +40 -0
  32. package/dist/parsers/shared.js.map +1 -1
  33. package/dist/sync/engine.js +114 -5
  34. package/dist/sync/engine.js.map +1 -1
  35. package/dist/sync/publish-results.d.ts +49 -0
  36. package/dist/sync/publish-results.js +476 -0
  37. package/dist/sync/publish-results.js.map +1 -0
  38. package/dist/sync/writeback.d.ts +57 -1
  39. package/dist/sync/writeback.js +243 -0
  40. package/dist/sync/writeback.js.map +1 -1
  41. package/dist/types.d.ts +159 -2
  42. package/docs/advanced.md +350 -0
  43. package/docs/configuration.md +293 -0
  44. package/docs/publish-test-results.md +317 -0
  45. package/docs/spec-formats.md +595 -0
  46. package/package.json +1 -1
@@ -0,0 +1,329 @@
1
+ "use strict";
2
+ /**
3
+ * Java test parser for azure-test-sync.
4
+ *
5
+ * Supports JUnit 4, JUnit 5 (Jupiter), and TestNG frameworks:
6
+ *
7
+ * Framework Test marker Tag / group attr
8
+ * ───────── ─────────── ────────────────
9
+ * JUnit 4 @Test @Category({Smoke.class, Regression.class})
10
+ * JUnit 5 @Test @Tag("smoke")
11
+ * TestNG @Test @Test(groups = {"smoke"}) or @Test(groups = "smoke")
12
+ *
13
+ * Source mapping:
14
+ * Javadoc /** ... * / first non-numbered line → TC Title
15
+ * Numbered lines "N. text" → TC Steps (action)
16
+ * "N. Check: text" → TC Steps (expected result column)
17
+ * @Tag("name") → TC Tags (JUnit 5)
18
+ * @Category({Smoke.class}) → TC Tags (JUnit 4)
19
+ * @Test(groups = {"smoke"}) → TC Tags (TestNG)
20
+ * @Test(description = "...") → TC Title fallback (TestNG)
21
+ * @Tag("tc:N") in the annotation block → Azure TC ID (JUnit 5)
22
+ * // @tc:N comment above @Test → Azure TC ID (JUnit 4 / TestNG / fallback)
23
+ * package.ClassName.methodName → automatedTestName
24
+ *
25
+ * ID writeback:
26
+ * JUnit 5 → @Tag("tc:12345") inserted/updated in the annotation block above @Test.
27
+ * No extra dependency — @Tag is part of junit-jupiter-api.
28
+ * JUnit 4 → // @tc:12345 comment inserted/updated immediately above @Test.
29
+ * TestNG → // @tc:12345 comment inserted/updated immediately above @Test.
30
+ * Note: @Test(tc="...") is NOT valid TestNG syntax; @Test has no such attribute.
31
+ *
32
+ * Path-based auto-tagging: directory segments starting with '@' become tags.
33
+ */
34
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
35
+ if (k2 === undefined) k2 = k;
36
+ var desc = Object.getOwnPropertyDescriptor(m, k);
37
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
38
+ desc = { enumerable: true, get: function() { return m[k]; } };
39
+ }
40
+ Object.defineProperty(o, k2, desc);
41
+ }) : (function(o, m, k, k2) {
42
+ if (k2 === undefined) k2 = k;
43
+ o[k2] = m[k];
44
+ }));
45
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
46
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
47
+ }) : function(o, v) {
48
+ o["default"] = v;
49
+ });
50
+ var __importStar = (this && this.__importStar) || (function () {
51
+ var ownKeys = function(o) {
52
+ ownKeys = Object.getOwnPropertyNames || function (o) {
53
+ var ar = [];
54
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
55
+ return ar;
56
+ };
57
+ return ownKeys(o);
58
+ };
59
+ return function (mod) {
60
+ if (mod && mod.__esModule) return mod;
61
+ var result = {};
62
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
63
+ __setModuleDefault(result, mod);
64
+ return result;
65
+ };
66
+ })();
67
+ Object.defineProperty(exports, "__esModule", { value: true });
68
+ exports.detectJavaFramework = detectJavaFramework;
69
+ exports.parseJavaFile = parseJavaFile;
70
+ const fs = __importStar(require("fs"));
71
+ const shared_1 = require("./shared");
72
+ /**
73
+ * Detect the test framework from import statements.
74
+ * Returns 'junit5' when org.junit.jupiter imports are present.
75
+ */
76
+ function detectJavaFramework(lines) {
77
+ for (const line of lines) {
78
+ const trimmed = line.trim();
79
+ if (trimmed.startsWith('import org.junit.jupiter.'))
80
+ return 'junit5';
81
+ if (trimmed.startsWith('import org.testng.'))
82
+ return 'testng';
83
+ if (trimmed.startsWith('import org.junit.Test') || trimmed.startsWith('import org.junit.runner.'))
84
+ return 'junit4';
85
+ }
86
+ return 'unknown';
87
+ }
88
+ // ─── Package / class extraction ───────────────────────────────────────────────
89
+ function extractPackage(lines) {
90
+ for (const line of lines) {
91
+ const m = line.match(/^\s*package\s+([\w.]+)\s*;/);
92
+ if (m)
93
+ return m[1];
94
+ }
95
+ return '';
96
+ }
97
+ function extractClassName(lines) {
98
+ for (const line of lines) {
99
+ const m = line.match(/^\s*(?:(?:public|protected|private|abstract|final)\s+)*class\s+(\w+)/);
100
+ if (m)
101
+ return m[1];
102
+ }
103
+ return '';
104
+ }
105
+ // ─── @Test annotation detection ───────────────────────────────────────────────
106
+ /**
107
+ * Returns true when the trimmed line is a @Test annotation.
108
+ * Matches @Test and @Test(...) but not @TestFactory / @TestTemplate.
109
+ */
110
+ function isTestAnnotation(trimmedLine) {
111
+ return /^@Test(?:$|\s*\()/.test(trimmedLine);
112
+ }
113
+ // ─── Javadoc extraction ───────────────────────────────────────────────────────
114
+ /**
115
+ * Walk backward from testLineIdx to find a Javadoc comment (/** ... * /).
116
+ * Skips blank lines, // comments, and @annotation lines.
117
+ */
118
+ function extractJavadocBefore(lines, testLineIdx) {
119
+ let i = testLineIdx - 1;
120
+ while (i >= 0) {
121
+ const t = lines[i].trim();
122
+ if (t === '' || t.startsWith('//') || (t.startsWith('@') && !t.endsWith('*/'))) {
123
+ i--;
124
+ continue;
125
+ }
126
+ break;
127
+ }
128
+ if (i < 0 || !lines[i].trim().endsWith('*/'))
129
+ return [];
130
+ const raw = [];
131
+ raw.unshift(lines[i]);
132
+ i--;
133
+ while (i >= 0) {
134
+ raw.unshift(lines[i]);
135
+ if (lines[i].trim().startsWith('/**'))
136
+ break;
137
+ i--;
138
+ }
139
+ return raw
140
+ .map((l) => l
141
+ .replace(/^\s*\/\*\*\s?/, '')
142
+ .replace(/\s*\*\/\s*$/, '')
143
+ .replace(/^\s*\*\s?/, '')
144
+ .trim())
145
+ .filter((l) => l !== '');
146
+ }
147
+ // ─── TC ID extraction — @Tag (JUnit 5) + comment fallback ─────────────────────
148
+ /**
149
+ * Scan above the @Test line (up to 25 lines) for a TC ID stored as either:
150
+ * @Tag("{tagPrefix}:N") — JUnit 5 style
151
+ * // @{tagPrefix}:N — comment style (JUnit 4 / TestNG)
152
+ *
153
+ * Stops at a closing brace or class declaration.
154
+ */
155
+ function extractTcIdAbove(lines, testLineIdx, tagPrefix) {
156
+ const tagRe = new RegExp(`^@Tag\\(\\s*"${tagPrefix}:(\\d+)"\\s*\\)$`);
157
+ const cmmtRe = new RegExp(`//\\s*@${tagPrefix}:(\\d+)`);
158
+ for (let i = testLineIdx - 1; i >= 0 && i >= testLineIdx - 25; i--) {
159
+ const trimmed = lines[i].trim();
160
+ const tagMatch = trimmed.match(tagRe);
161
+ if (tagMatch)
162
+ return parseInt(tagMatch[1], 10);
163
+ const cmtMatch = trimmed.match(cmmtRe);
164
+ if (cmtMatch)
165
+ return parseInt(cmtMatch[1], 10);
166
+ if (trimmed === '}' || /^\s*(?:(?:public|protected|private|abstract|final)\s+)*class\s+/.test(trimmed))
167
+ break;
168
+ }
169
+ return undefined;
170
+ }
171
+ function parseTestAnnotationAttrs(testLine) {
172
+ const groups = [];
173
+ let description = '';
174
+ const multiGroupMatch = testLine.match(/\bgroups\s*=\s*\{([^}]*)\}/);
175
+ if (multiGroupMatch) {
176
+ const literals = multiGroupMatch[1].match(/"([^"]+)"/g);
177
+ if (literals)
178
+ groups.push(...literals.map((g) => g.replace(/"/g, '')));
179
+ }
180
+ else {
181
+ const singleGroupMatch = testLine.match(/\bgroups\s*=\s*"([^"]+)"/);
182
+ if (singleGroupMatch)
183
+ groups.push(singleGroupMatch[1]);
184
+ }
185
+ const descMatch = testLine.match(/\bdescription\s*=\s*"([^"]+)"/);
186
+ if (descMatch)
187
+ description = descMatch[1];
188
+ return { groups, description };
189
+ }
190
+ // ─── Forward scan: annotations + method signature ─────────────────────────────
191
+ const JAVA_KEYWORDS = new Set([
192
+ 'if', 'for', 'while', 'switch', 'catch', 'super', 'this', 'new', 'return', 'throw',
193
+ 'instanceof', 'assert', 'synchronized',
194
+ ]);
195
+ function extractMethodInfo(lines, testLineIdx) {
196
+ const tagValues = [];
197
+ let methodName = '';
198
+ let endLine = testLineIdx;
199
+ // TestNG groups on the @Test line itself — push into tagValues so they surface as TC tags
200
+ const { groups } = parseTestAnnotationAttrs(lines[testLineIdx] ?? '');
201
+ tagValues.push(...groups);
202
+ for (let i = testLineIdx + 1; i < lines.length && i < testLineIdx + 30; i++) {
203
+ const trimmed = lines[i].trim();
204
+ endLine = i;
205
+ // @Tag("value") — JUnit 5 (includes potential tc:N ID tags)
206
+ const tagMatch = trimmed.match(/^@Tag\(\s*"([^"]+)"\s*\)/);
207
+ if (tagMatch) {
208
+ tagValues.push(tagMatch[1]);
209
+ continue;
210
+ }
211
+ // @Category({Smoke.class, Reg.class}) — JUnit 4
212
+ const multiCatMatch = trimmed.match(/^@Category\(\s*\{([^}]*)\}\s*\)/);
213
+ if (multiCatMatch) {
214
+ const classRefs = multiCatMatch[1].match(/(\w+)\.class/g);
215
+ if (classRefs)
216
+ tagValues.push(...classRefs.map((c) => c.replace('.class', '')));
217
+ continue;
218
+ }
219
+ // @Category(Smoke.class) — single
220
+ const singleCatMatch = trimmed.match(/^@Category\(\s*(\w+)\.class\s*\)/);
221
+ if (singleCatMatch) {
222
+ tagValues.push(singleCatMatch[1]);
223
+ continue;
224
+ }
225
+ // Other @Annotation — skip (e.g. @DisplayName, @Timeout, @AzureTestCase)
226
+ if (trimmed.startsWith('@'))
227
+ continue;
228
+ // Blank / comment interior
229
+ if (trimmed === '' || trimmed.startsWith('//') || trimmed.startsWith('*'))
230
+ continue;
231
+ // Method signature: first word before ( that is not a keyword
232
+ if (trimmed.includes('(')) {
233
+ const m = trimmed.match(/(\w+)\s*\(/);
234
+ if (m && !JAVA_KEYWORDS.has(m[1])) {
235
+ methodName = m[1];
236
+ break;
237
+ }
238
+ }
239
+ if (trimmed === '{')
240
+ break;
241
+ }
242
+ return { methodName, tagValues, endLine };
243
+ }
244
+ // ─── Javadoc → title + steps ──────────────────────────────────────────────────
245
+ const NUMBERED_STEP_RE = /^\d+\.\s+(.+)$/;
246
+ const CHECK_RE = /^[Cc]heck:\s+(.+)$/;
247
+ const META_RE = /^(?:test\s+case|user\s+story)[\s:]/i;
248
+ function parseSummary(summaryLines, methodName, descriptionFallback) {
249
+ let title = '';
250
+ const steps = [];
251
+ for (const line of summaryLines) {
252
+ if (!line || META_RE.test(line))
253
+ continue;
254
+ const numMatch = NUMBERED_STEP_RE.exec(line);
255
+ if (numMatch) {
256
+ const content = numMatch[1].trim();
257
+ const checkMatch = CHECK_RE.exec(content);
258
+ if (checkMatch) {
259
+ steps.push({ keyword: 'Then', text: checkMatch[1].trim() });
260
+ }
261
+ else {
262
+ steps.push({ keyword: 'Step', text: content });
263
+ }
264
+ continue;
265
+ }
266
+ if (!title)
267
+ title = line;
268
+ }
269
+ if (!title) {
270
+ title = descriptionFallback || methodName.replace(/([A-Z])/g, ' $1').trim();
271
+ }
272
+ return { title, steps };
273
+ }
274
+ // ─── Public parser ────────────────────────────────────────────────────────────
275
+ function parseJavaFile(filePath, tagPrefix, linkConfigs) {
276
+ const source = fs.readFileSync(filePath, 'utf8');
277
+ const lines = source.split('\n');
278
+ const pkg = extractPackage(lines);
279
+ const className = extractClassName(lines);
280
+ const pathTags = (0, shared_1.extractPathTags)(filePath);
281
+ // ID tag regex: matches e.g. "tc:12345" inside a @Tag value
282
+ const idTagValueRe = new RegExp(`^${tagPrefix}:(\\d+)$`);
283
+ const results = [];
284
+ for (let i = 0; i < lines.length; i++) {
285
+ const trimmed = lines[i].trim();
286
+ if (!isTestAnnotation(trimmed))
287
+ continue;
288
+ const testAnnotationLineIdx = i;
289
+ const summaryLines = extractJavadocBefore(lines, testAnnotationLineIdx);
290
+ const { description: testNgDescription, groups: testNgGroups } = parseTestAnnotationAttrs(lines[testAnnotationLineIdx] ?? '');
291
+ const { methodName, tagValues, endLine } = extractMethodInfo(lines, testAnnotationLineIdx);
292
+ if (!methodName) {
293
+ i = endLine;
294
+ continue;
295
+ }
296
+ // Separate tc-ID tags from regular @Tag values (JUnit 5)
297
+ let azureId;
298
+ const regularTagValues = [];
299
+ for (const tv of tagValues) {
300
+ const m = tv.match(idTagValueRe);
301
+ if (m && azureId === undefined) {
302
+ azureId = parseInt(m[1], 10);
303
+ }
304
+ else {
305
+ regularTagValues.push(tv);
306
+ }
307
+ }
308
+ // Fallback: look for ID above @Test (@Tag or comment)
309
+ if (azureId === undefined) {
310
+ azureId = extractTcIdAbove(lines, testAnnotationLineIdx, tagPrefix);
311
+ }
312
+ const allTags = [...new Set([...pathTags, ...regularTagValues, ...testNgGroups])];
313
+ const { title, steps } = parseSummary(summaryLines, methodName, testNgDescription);
314
+ const fqmn = [pkg, className, methodName].filter(Boolean).join('.');
315
+ results.push({
316
+ filePath,
317
+ title,
318
+ steps,
319
+ tags: allTags,
320
+ azureId: azureId !== undefined && !isNaN(azureId) ? azureId : undefined,
321
+ line: testAnnotationLineIdx + 1, // 1-based; writeback targets this @Test line
322
+ linkRefs: (0, shared_1.extractLinkRefs)(allTags, linkConfigs),
323
+ automatedTestName: fqmn || undefined,
324
+ });
325
+ i = endLine;
326
+ }
327
+ return results;
328
+ }
329
+ //# sourceMappingURL=java.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"java.js","sourceRoot":"","sources":["../../src/parsers/java.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * JavaScript / TypeScript test parser for azure-test-sync.
3
+ *
4
+ * Supports Jest, Jasmine, and WebdriverIO (which uses Jest or Jasmine as its
5
+ * underlying test runner). All three frameworks share the same
6
+ * describe() / it() / test() API for test organisation, so a single parser
7
+ * handles all of them.
8
+ *
9
+ * Cucumber (.feature files) is already handled by the existing 'gherkin' type.
10
+ *
11
+ * Detected test functions:
12
+ * it(title, fn) test(title, fn)
13
+ * it.only / test.only it.skip / test.skip
14
+ * xit / xtest (Jasmine skip — still synced to Azure)
15
+ * it.concurrent / test.concurrent
16
+ *
17
+ * Source mapping:
18
+ * JSDoc /** ... * / first non-numbered line → TC Title
19
+ * Numbered lines "N. text" → TC Steps (action)
20
+ * "N. Check: text" → TC Steps (expected result column)
21
+ * // @tags: smoke, regression → TC Tags (comma-separated list)
22
+ * // @smoke → TC Tag (single-word shorthand)
23
+ * // @{tagPrefix}:N comment above it()/test() → Azure TC ID (written back after push)
24
+ * {fileBasename} > {describe} > {it title} → automatedTestName (Jest report format)
25
+ *
26
+ * ID writeback:
27
+ * Inserts / updates // @tc:12345 immediately above the it() / test() line.
28
+ * No extra dependency required.
29
+ *
30
+ * Path-based auto-tagging: directory segments starting with '@' become tags.
31
+ */
32
+ import { LinkConfig, ParsedTest } from '../types';
33
+ export declare function parseJavaScriptFile(filePath: string, tagPrefix: string, linkConfigs?: LinkConfig[]): ParsedTest[];
@@ -0,0 +1,261 @@
1
+ "use strict";
2
+ /**
3
+ * JavaScript / TypeScript test parser for azure-test-sync.
4
+ *
5
+ * Supports Jest, Jasmine, and WebdriverIO (which uses Jest or Jasmine as its
6
+ * underlying test runner). All three frameworks share the same
7
+ * describe() / it() / test() API for test organisation, so a single parser
8
+ * handles all of them.
9
+ *
10
+ * Cucumber (.feature files) is already handled by the existing 'gherkin' type.
11
+ *
12
+ * Detected test functions:
13
+ * it(title, fn) test(title, fn)
14
+ * it.only / test.only it.skip / test.skip
15
+ * xit / xtest (Jasmine skip — still synced to Azure)
16
+ * it.concurrent / test.concurrent
17
+ *
18
+ * Source mapping:
19
+ * JSDoc /** ... * / first non-numbered line → TC Title
20
+ * Numbered lines "N. text" → TC Steps (action)
21
+ * "N. Check: text" → TC Steps (expected result column)
22
+ * // @tags: smoke, regression → TC Tags (comma-separated list)
23
+ * // @smoke → TC Tag (single-word shorthand)
24
+ * // @{tagPrefix}:N comment above it()/test() → Azure TC ID (written back after push)
25
+ * {fileBasename} > {describe} > {it title} → automatedTestName (Jest report format)
26
+ *
27
+ * ID writeback:
28
+ * Inserts / updates // @tc:12345 immediately above the it() / test() line.
29
+ * No extra dependency required.
30
+ *
31
+ * Path-based auto-tagging: directory segments starting with '@' become tags.
32
+ */
33
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
34
+ if (k2 === undefined) k2 = k;
35
+ var desc = Object.getOwnPropertyDescriptor(m, k);
36
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
37
+ desc = { enumerable: true, get: function() { return m[k]; } };
38
+ }
39
+ Object.defineProperty(o, k2, desc);
40
+ }) : (function(o, m, k, k2) {
41
+ if (k2 === undefined) k2 = k;
42
+ o[k2] = m[k];
43
+ }));
44
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
45
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
46
+ }) : function(o, v) {
47
+ o["default"] = v;
48
+ });
49
+ var __importStar = (this && this.__importStar) || (function () {
50
+ var ownKeys = function(o) {
51
+ ownKeys = Object.getOwnPropertyNames || function (o) {
52
+ var ar = [];
53
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
54
+ return ar;
55
+ };
56
+ return ownKeys(o);
57
+ };
58
+ return function (mod) {
59
+ if (mod && mod.__esModule) return mod;
60
+ var result = {};
61
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
62
+ __setModuleDefault(result, mod);
63
+ return result;
64
+ };
65
+ })();
66
+ Object.defineProperty(exports, "__esModule", { value: true });
67
+ exports.parseJavaScriptFile = parseJavaScriptFile;
68
+ const fs = __importStar(require("fs"));
69
+ const path = __importStar(require("path"));
70
+ const shared_1 = require("./shared");
71
+ // ─── Test function detection ──────────────────────────────────────────────────
72
+ const TEST_FN_PREFIX = '(?:it|test|xit|xtest|it\\.only|test\\.only|it\\.skip|test\\.skip|it\\.concurrent|test\\.concurrent)';
73
+ const TEST_CALL_RE = new RegExp(`^${TEST_FN_PREFIX}\\s*\\(`);
74
+ const TEST_TITLE_RE = new RegExp(`^${TEST_FN_PREFIX}\\s*\\(\\s*(['"\`])((?:\\\\.|[^\\\\])*?)\\1`);
75
+ const DESCRIBE_TITLE_RE = /^describe(?:\.(?:only|skip|concurrent))?\s*\(\s*(['"`])((?:\\.|[^\\])*?)\1/;
76
+ function isTestLine(trimmedLine) {
77
+ return TEST_CALL_RE.test(trimmedLine);
78
+ }
79
+ /** Extract the literal string passed as the first argument of it() / test(). */
80
+ function extractTestCallTitle(trimmedLine) {
81
+ const m = trimmedLine.match(TEST_TITLE_RE);
82
+ if (!m)
83
+ return '';
84
+ return m[2]
85
+ .replace(/\\'/g, "'")
86
+ .replace(/\\"/g, '"')
87
+ .replace(/\\`/g, '`')
88
+ .replace(/\\\\/g, '\\');
89
+ }
90
+ // ─── Indentation helpers ──────────────────────────────────────────────────────
91
+ function getIndentLength(line) {
92
+ return (line.match(/^(\s*)/) ?? ['', ''])[1].length;
93
+ }
94
+ // ─── Enclosing describe blocks ────────────────────────────────────────────────
95
+ /**
96
+ * Walk backward from the it()/test() line to collect enclosing describe() titles,
97
+ * outermost first. Uses indentation to identify nesting levels.
98
+ */
99
+ function findEnclosingDescribes(lines, itLineIdx) {
100
+ const describes = [];
101
+ let targetIndent = getIndentLength(lines[itLineIdx]);
102
+ for (let i = itLineIdx - 1; i >= 0; i--) {
103
+ const line = lines[i];
104
+ const trimmed = line.trim();
105
+ if (!trimmed)
106
+ continue;
107
+ const lineIndent = getIndentLength(line);
108
+ if (lineIndent < targetIndent) {
109
+ const m = trimmed.match(DESCRIBE_TITLE_RE);
110
+ if (m) {
111
+ describes.unshift(m[2].replace(/\\'/g, "'").replace(/\\"/g, '"').replace(/\\`/g, '`'));
112
+ targetIndent = lineIndent;
113
+ }
114
+ }
115
+ }
116
+ return describes;
117
+ }
118
+ // ─── JSDoc extraction ─────────────────────────────────────────────────────────
119
+ /**
120
+ * Walk backward from the it()/test() line to find a JSDoc comment (/** ... * /).
121
+ * Skips blank lines and // single-line comment lines on the way.
122
+ */
123
+ function extractJsdocBefore(lines, itLineIdx) {
124
+ let i = itLineIdx - 1;
125
+ while (i >= 0) {
126
+ const t = lines[i].trim();
127
+ if (t === '' || t.startsWith('//')) {
128
+ i--;
129
+ continue;
130
+ }
131
+ break;
132
+ }
133
+ if (i < 0 || !lines[i].trim().endsWith('*/'))
134
+ return [];
135
+ const raw = [];
136
+ raw.unshift(lines[i]);
137
+ i--;
138
+ while (i >= 0) {
139
+ raw.unshift(lines[i]);
140
+ if (lines[i].trim().startsWith('/**'))
141
+ break;
142
+ i--;
143
+ }
144
+ return raw
145
+ .map((l) => l
146
+ .replace(/^\s*\/\*\*\s?/, '')
147
+ .replace(/\s*\*\/\s*$/, '')
148
+ .replace(/^\s*\*\s?/, '')
149
+ .trim())
150
+ .filter((l) => l !== '');
151
+ }
152
+ /**
153
+ * Scan the comment block immediately above the it()/test() line for:
154
+ * // @tc:12345 → Azure TC ID
155
+ * // @tags: a, b, c → tag list (comma-separated)
156
+ * // @smoke → single-word tag shorthand
157
+ *
158
+ * Stops at blank lines (the comment block must be adjacent to the test).
159
+ */
160
+ function extractCommentMetadataAbove(lines, itLineIdx, tagPrefix) {
161
+ const tags = [];
162
+ let azureId;
163
+ const idRe = new RegExp(`//\\s*@${tagPrefix}:(\\d+)`);
164
+ const tagsListRe = /\/\/\s*@tags?\s*:\s*(.+)/i;
165
+ const singleTagRe = /\/\/\s*@(\w+)\s*$/;
166
+ for (let i = itLineIdx - 1; i >= 0 && i >= itLineIdx - 25; i--) {
167
+ const trimmed = lines[i].trim();
168
+ // Stop at blank lines — metadata must be adjacent
169
+ if (trimmed === '')
170
+ break;
171
+ // Stop at lines that are clearly not comments
172
+ if (!trimmed.startsWith('//') && !trimmed.startsWith('*') && !trimmed.startsWith('/*'))
173
+ break;
174
+ // ID comment
175
+ const idMatch = trimmed.match(idRe);
176
+ if (idMatch && azureId === undefined) {
177
+ azureId = parseInt(idMatch[1], 10);
178
+ continue;
179
+ }
180
+ // Tags list: // @tags: smoke, regression
181
+ const tagsMatch = trimmed.match(tagsListRe);
182
+ if (tagsMatch) {
183
+ tags.push(...tagsMatch[1].split(',').map((t) => t.trim()).filter(Boolean));
184
+ continue;
185
+ }
186
+ // Single tag shorthand: // @smoke
187
+ const singleTag = trimmed.match(singleTagRe);
188
+ if (singleTag && singleTag[1] !== tagPrefix) {
189
+ tags.push(singleTag[1]);
190
+ continue;
191
+ }
192
+ }
193
+ return { azureId, tags };
194
+ }
195
+ // ─── JSDoc → title + steps ────────────────────────────────────────────────────
196
+ const NUMBERED_STEP_RE = /^\d+\.\s+(.+)$/;
197
+ const CHECK_RE = /^[Cc]heck:\s+(.+)$/;
198
+ const META_RE = /^(?:test\s+case|user\s+story)[\s:]/i;
199
+ function parseSummary(jsdocLines, fallbackTitle) {
200
+ let title = '';
201
+ const steps = [];
202
+ for (const line of jsdocLines) {
203
+ if (!line || META_RE.test(line))
204
+ continue;
205
+ const numMatch = NUMBERED_STEP_RE.exec(line);
206
+ if (numMatch) {
207
+ const content = numMatch[1].trim();
208
+ const checkMatch = CHECK_RE.exec(content);
209
+ if (checkMatch) {
210
+ steps.push({ keyword: 'Then', text: checkMatch[1].trim() });
211
+ }
212
+ else {
213
+ steps.push({ keyword: 'Step', text: content });
214
+ }
215
+ continue;
216
+ }
217
+ if (!title)
218
+ title = line;
219
+ }
220
+ return { title: title || fallbackTitle, steps };
221
+ }
222
+ // ─── Public parser ────────────────────────────────────────────────────────────
223
+ function parseJavaScriptFile(filePath, tagPrefix, linkConfigs) {
224
+ const source = fs.readFileSync(filePath, 'utf8');
225
+ const lines = source.split('\n');
226
+ // Strip .spec.ts / .test.js / .js / .ts suffixes for a clean base name
227
+ const fileBaseName = path.basename(filePath)
228
+ .replace(/\.(spec|test)\.(js|ts|mjs|cjs)$/, '')
229
+ .replace(/\.(js|ts|mjs|cjs)$/, '');
230
+ const pathTags = (0, shared_1.extractPathTags)(filePath);
231
+ const results = [];
232
+ for (let i = 0; i < lines.length; i++) {
233
+ const trimmed = lines[i].trim();
234
+ if (!isTestLine(trimmed))
235
+ continue;
236
+ const itLineIdx = i;
237
+ const callTitle = extractTestCallTitle(trimmed);
238
+ // Skip dynamic titles (e.g. template expressions) that we can't resolve
239
+ if (!callTitle)
240
+ continue;
241
+ const jsdocLines = extractJsdocBefore(lines, itLineIdx);
242
+ const { azureId, tags: cTags } = extractCommentMetadataAbove(lines, itLineIdx, tagPrefix);
243
+ const describes = findEnclosingDescribes(lines, itLineIdx);
244
+ const allTags = [...new Set([...pathTags, ...cTags])];
245
+ const { title, steps } = parseSummary(jsdocLines, callTitle);
246
+ // automatedTestName mirrors Jest's built-in test-result path format
247
+ const automatedTestName = [fileBaseName, ...describes, callTitle].join(' > ');
248
+ results.push({
249
+ filePath,
250
+ title,
251
+ steps,
252
+ tags: allTags,
253
+ azureId: azureId !== undefined && !isNaN(azureId) ? azureId : undefined,
254
+ line: itLineIdx + 1, // 1-based; writeback targets this it()/test() line
255
+ linkRefs: (0, shared_1.extractLinkRefs)(allTags, linkConfigs),
256
+ automatedTestName,
257
+ });
258
+ }
259
+ return results;
260
+ }
261
+ //# sourceMappingURL=javascript.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"javascript.js","sourceRoot":"","sources":["../../src/parsers/javascript.ts"],"names":[],"mappings":"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"}
@@ -29,4 +29,7 @@
29
29
  * Path-based auto-tagging: same as Gherkin (directory segments starting with @)
30
30
  */
31
31
  import { LinkConfig, ParsedTest } from '../types';
32
- export declare function parseMarkdownFile(filePath: string, tagPrefix: string, linkConfigs?: LinkConfig[]): ParsedTest[];
32
+ export declare function parseMarkdownFile(filePath: string, tagPrefix: string, linkConfigs?: LinkConfig[], attachmentsConfig?: {
33
+ enabled: boolean;
34
+ tagPrefixes?: string[];
35
+ }): ParsedTest[];