Skip to content

Commit c5caf87

Browse files
authored
Merge pull request #1 from jimasp/go-to-step-def
Go to step def
2 parents a567533 + 3e2e262 commit c5caf87

File tree

10 files changed

+194
-57
lines changed

10 files changed

+194
-57
lines changed

CHANGELOG.md

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,3 @@
11
# Changelog
22

3-
## [Pre-release]
4-
5-
## [0.0.1] - 2021-03-05
6-
7-
### Contributors
8-
9-
N/A
10-
11-
### Tested with
12-
13-
- behave 1.2.6
14-
- Python 3.9.7
15-
- Linux/Windows
16-
- Visual Studio Code 1.64.x
17-
18-
### Added/Changed/Removed
19-
20-
N/A
3+
See [github releases](https://github.com/jimasp/behave-vsc/releases)

README.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
# Behave VSC
22

3-
## Pre-release v0.0.1
3+
## Pre-release v0.0.2
44
- A simple test runner (and debugger) for running python Behave tests in vscode
55
- Built with the new Visual Studio Code Test API
66
- See [Known Issues](#known-issues) and [Troubleshooting](#troubleshooting) below if you have any problems
77

88
## Tested with
9-
- behave 1.2.6
10-
- Python 3.9.7
11-
- Linux/Windows
9+
- behave 1.2.6
10+
- Python 3.9.7
11+
- Linux/Windows
1212

1313
---
1414
## Project Requirements
@@ -38,7 +38,8 @@ paths=behave-tests/features
3838
---
3939
## Features
4040

41-
- Run/debug behave tests from the test workbench, or from inside a Feature file.
41+
- Run or Debug behave tests from the test workbench, or from inside a Feature file.
42+
- Go to step definition from feature file. (Not shown in below gif, just right-click inside feature file on a line containing a step and click "Go to step").
4243
- Run customisation via [Extension settings](#extension-settings).
4344
- In run mode, std/err output can be found in the Behave VSC output window, including an equivalent behave command to run the test manually. (In debug mode, errors are shown in the console.)
4445

@@ -82,16 +83,16 @@ It will be faster if you select a subset/group of tests to run.
8283
---
8384
## Troubleshooting
8485
- Does your setup match the [Requirements](#requirements) section above?
85-
- Check if the problem is in [Known Issues](#known-issues) above
86+
- Check if the problem is in [Known Issues](#known-issues) above.
87+
- Have you tried manually running the outputted behave command from the Behave VSC output window?
8688
- Do you have runParallel turned on? Try turning it off.
8789
- Do you have the latest version of the extension?
8890
- Do you have the correct [Extension Settings](#extension-settings) for your project? (Also if you have removed extension settings from your
8991
workspace `.vscode/settings.json`, then do you have any of the extension settings in your user settings?)
90-
- Have you tried manually running the outputted behave command from the behave-vsc output window?
91-
- Try disabling other extensions
92+
- Try disabling other extensions.
9293
- Have you got the latest version of the extension?
93-
- Does your project environment match the environment tested for this release? Older releases are kept in the vsix folder in
94-
[github](https://github.com/jimasp/behave-vsc/vsix). The details of each release are in the (TODO:release doc)
94+
- Does your project environment match the environment tested for this release? Older releases are available in
95+
[github](https://github.com/jimasp/behave-vsc/releases).
9596
- See [Contributing](#contributing) below for extension debugging instructions. (Does the issue occur with the example project workspaces, or just
9697
in your own project?)
9798

package-lock.json

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,21 @@
2121
"description": "Optional csv list of fast skip tags, example: '@skip, @skip-me-too'.\nThis will stop behave being called for those tags. Use if you have a lot of tests to skip."
2222
}
2323
}
24+
},
25+
"commands": [{
26+
"command": "behave-vsc.gotoStep",
27+
"title": "Go to step"
28+
}],
29+
"menus": {
30+
"editor/context": [{
31+
"when": "editorTextFocus && resourceExtname =~ /\\.feature$/",
32+
"command": "behave-vsc.gotoStep",
33+
"group": "navigation"
34+
}]
2435
}
2536
},
26-
"description": "Run/debug behave tests from Sidebar or Feature file",
27-
"version": "0.0.1",
37+
"description": "Debug/Run Behave tests from Sidebar or Feature file",
38+
"version": "0.0.2",
2839
"icon": "images/behave-vsc.png",
2940
"galleryBanner": {
3041
"color": "#2B2B2B",

src/extension.ts

Lines changed: 116 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { getContentFromFilesystem, Scenario, testData, TestFile } from './testTr
77
import { runBehaveAll } from './runOrDebug';
88
import { getGroupedFeatureSubPath } from './helpers';
99
import { getFeatureNameFromFile } from './featureParser';
10+
import { parseStepsFile, StepDetail, Steps } from './stepsParser';
1011

1112

1213
function logRunHandlerDiagOutput() {
@@ -24,6 +25,7 @@ function logActivate(context:vscode.ExtensionContext) {
2425
config.logger.logInfo(`activated (version ${version})`);
2526
}
2627

28+
const steps:Steps = new Map<string, StepDetail>();
2729

2830
export interface QueueItem { test: vscode.TestItem; scenario: Scenario }
2931

@@ -34,6 +36,7 @@ export async function activate(context: vscode.ExtensionContext) {
3436
const ctrl = vscode.tests.createTestController(`${config.extensionName}.TestController`, 'Feature Tests');
3537
context.subscriptions.push(ctrl);
3638
context.subscriptions.push(startWatchingWorkspace(ctrl));
39+
context.subscriptions.push(vscode.commands.registerCommand("behave-vsc.gotoStep", gotoStepHandler));
3740

3841
const runHandler = async (debug: boolean, request: vscode.TestRunRequest, cancellation: vscode.CancellationToken) => {
3942

@@ -180,7 +183,7 @@ export async function activate(context: vscode.ExtensionContext) {
180183
};
181184

182185
const refreshHandler = async() => {
183-
await findInitialFiles(ctrl, true);
186+
await findInitialFeatureFiles(ctrl, true);
184187
};
185188
// @ts-ignore: Property 'refreshHander' does not exist on type 'TestController'
186189
ctrl.refreshHandler = refreshHandler;
@@ -235,7 +238,7 @@ export async function activate(context: vscode.ExtensionContext) {
235238
// vscode.workspace.onDidChangeTextDocument((e: vscode.TextDocumentChangeEvent) => updateNodeForDocument(e.document))
236239
// );
237240

238-
return { runHandler: runHandler, config: config, ctrl: ctrl, findInitialFiles: findInitialFiles}; // support extensiontest.ts
241+
return { runHandler: runHandler, config: config, ctrl: ctrl, findInitialFiles: findInitialFeatureFiles}; // support extensiontest.ts
239242

240243
} // end activate()
241244

@@ -288,7 +291,7 @@ function gatherTestItems(collection: vscode.TestItemCollection) {
288291

289292

290293

291-
async function findInitialFiles(controller: vscode.TestController, reparse?:boolean) {
294+
async function findInitialFeatureFiles(controller: vscode.TestController, reparse?:boolean) {
292295
controller.items.forEach(item => controller.items.delete(item.id));
293296
const featureFiles1 = await vscode.workspace.findFiles("**/features/*.feature");
294297
const featureFiles2 = await vscode.workspace.findFiles("**/features/**/*.feature");
@@ -302,36 +305,142 @@ async function findInitialFiles(controller: vscode.TestController, reparse?:bool
302305
}
303306
}
304307

308+
async function findInitialStepsFiles() {
309+
steps.clear();
310+
const stepFiles1 = await vscode.workspace.findFiles("**/features/steps/*.py");
311+
const stepFiles2 = await vscode.workspace.findFiles("**/features/steps/**/*.py");
312+
const stepFiles = stepFiles1.concat(stepFiles2);
313+
314+
for (const stepFile of stepFiles) {
315+
updateSteps(stepFile);
316+
}
317+
}
318+
305319
function startWatchingWorkspace(controller: vscode.TestController) {
306320

307321
// not just .feature files, also support folder changes, could change to just .feature files when refreshhandler() works
308322
const watcher = vscode.workspace.createFileSystemWatcher('**/features/**');
309323

310324
watcher.onDidCreate(uri => {
325+
326+
if(uri.path.indexOf("/steps/") !== -1) {
327+
updateSteps(uri);
328+
return;
329+
}
330+
311331
if(uri.path.toLowerCase().endsWith(".feature"))
312332
getOrCreateFile(controller, uri);
313333
else if(uri.path.toLowerCase().endsWith("/"))
314-
findInitialFiles(controller);
334+
findInitialFeatureFiles(controller);
335+
315336
});
337+
316338
watcher.onDidChange(uri => {
339+
340+
if(uri.path.indexOf("/steps/") !== -1) {
341+
updateSteps(uri);
342+
return;
343+
}
344+
317345
if(uri.path.toLowerCase().endsWith(".feature")) {
318346
const created = getOrCreateFile(controller, uri);
319347
if (created) {
320348
created.data.updateFromDisk(controller, created.file);
321349
}
322350
}
323351
else if(uri.path.toLowerCase().endsWith("/"))
324-
findInitialFiles(controller);
352+
findInitialFeatureFiles(controller);
353+
325354
});
355+
356+
326357
watcher.onDidDelete(uri => {
358+
359+
if(uri.path.indexOf("/steps/") !== -1) {
360+
updateSteps(uri);
361+
return;
362+
}
363+
327364
if(uri.path.toLowerCase().endsWith(".feature"))
328365
getOrCreateFile(controller, uri);
329366
else if(uri.path.toLowerCase().endsWith("/"))
330-
findInitialFiles(controller);
367+
findInitialFeatureFiles(controller);
368+
331369
});
332370

333-
findInitialFiles(controller, true);
371+
findInitialFeatureFiles(controller, true);
372+
findInitialStepsFiles();
334373

335374
return watcher;
336375

337376
}
377+
378+
379+
function updateSteps(uri:vscode.Uri) {
380+
if(uri.path.toLowerCase().endsWith(".py")) {
381+
const content = getContentFromFilesystem(uri);
382+
parseStepsFile(uri, content, steps);
383+
}
384+
else if(uri.path.toLowerCase().endsWith("/"))
385+
findInitialStepsFiles();
386+
}
387+
388+
389+
function gotoStepHandler(uri: vscode.Uri) {
390+
391+
function getStepMatch(stepText:string): StepDetail|null {
392+
393+
let stepMatch:StepDetail|null = null;
394+
395+
for(const[key, value] of steps) {
396+
const rx = new RegExp(key);
397+
const match = rx.exec(stepText);
398+
if(match && match.length !== 0) {
399+
stepMatch = value;
400+
break;
401+
}
402+
}
403+
404+
return stepMatch;
405+
}
406+
407+
try {
408+
const activeEditor = vscode.window.activeTextEditor;
409+
if (!activeEditor) {
410+
return;
411+
}
412+
413+
const line = activeEditor.document.lineAt(activeEditor.selection.active.line).text.trim();
414+
const stepRe = /^(\s*)(Given|When|Then|And)(.+)(\s*)$/i;
415+
const matches = stepRe.exec(line);
416+
if(!matches || !matches[3])
417+
return;
418+
419+
const stepText = matches[3].trim();
420+
const stepMatch = getStepMatch(stepText);
421+
422+
if(!stepMatch) {
423+
vscode.window.showInformationMessage("Step not found (is it implemented?)")
424+
return;
425+
}
426+
427+
vscode.workspace.openTextDocument(stepMatch.uri).then(doc => {
428+
vscode.window.showTextDocument(doc, {preview:false}).then(editor => {
429+
if(!editor) {
430+
config.logger.logError("Could not open editor for file:" + uri.fsPath)
431+
return;
432+
}
433+
editor.selection = new vscode.Selection(stepMatch.range.start, stepMatch.range.end);
434+
editor.revealRange(stepMatch.range);
435+
});
436+
});
437+
438+
}
439+
catch(e:unknown) {
440+
config.logger.logError(e instanceof Error ? (e.stack ? e.stack : e.message) : e as string);
441+
}
442+
443+
444+
}
445+
446+

src/featureParser.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import { getContentFromFilesystem } from './testTree';
55

66
const featureReStr = "^(\\s*|\\s*#\\s*)Feature:(\\s*)(.+)(\\s*)$";
77
const featureReLine = new RegExp(featureReStr);
8-
const featureReFile = new RegExp(featureReStr, "m");
9-
const scenarioReLine = /^(\s*)(Scenario|Scenario Outline):(\s*)(.+)(\s*)$/;
10-
const scenarioOutlineRe = /^(\s*)Scenario Outline:(\s*)(.+)(\s*)$/;
8+
const featureReFile = new RegExp(featureReStr, "im");
9+
const scenarioReLine = /^(\s*)(Scenario|Scenario Outline):(\s*)(.+)(\s*)$/i;
10+
const scenarioOutlineRe = /^(\s*)Scenario Outline:(\s*)(.+)(\s*)$/i;
1111

1212

1313
export const getFeatureNameFromFile = (uri:vscode.Uri): string => {

src/runOrDebug.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,9 @@ export async function runBehaveAll(run:vscode.TestRun, queue:QueueItem[], cancel
2323
try {
2424
await runAll(run, queue, shared_args, cancellation);
2525
}
26-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
27-
catch(e:any) {
28-
config.logger.logError(e.stack ? e.stack : e as string);
29-
}
26+
catch(e:unknown) {
27+
config.logger.logError(e instanceof Error ? (e.stack ? e.stack : e.message) : e as string);
28+
}
3029
}
3130

3231

@@ -58,10 +57,9 @@ export async function runOrDebugBehaveScenario(run:vscode.TestRun, queueItem:Que
5857
await runScenario(run, queueItem, args, cancellation);
5958
}
6059
}
61-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
62-
catch(e:any) {
63-
config.logger.logError(e.stack ? e.stack : e as string);
64-
}
60+
catch(e:unknown) {
61+
config.logger.logError(e instanceof Error ? (e.stack ? e.stack : e.message) : e as string);
62+
}
6563

6664

6765
function formatScenarioName(string:string, isOutline:boolean) {

0 commit comments

Comments
 (0)