Skip to content

Commit e299eb5

Browse files
authored
Make all test items debuggable (#1181)
* enable debugging for all test items * adding tests
1 parent 237bc53 commit e299eb5

13 files changed

+213
-142
lines changed

src/DebugConfigurationProvider.ts

+43-17
Original file line numberDiff line numberDiff line change
@@ -13,33 +13,35 @@ import {
1313
} from './helpers';
1414
import { platform } from 'os';
1515
import { PluginResourceSettings } from './Settings';
16+
import { DebugInfo } from './types';
1617

1718
export const DEBUG_CONFIG_PLATFORMS = ['windows', 'linux', 'osx'];
1819
const testNamePatternRegex = /\$\{jest.testNamePattern\}/g;
1920
const testFileRegex = /\$\{jest.testFile\}/g;
2021
const testFilePatternRegex = /\$\{jest.testFilePattern\}/g;
22+
const replaceTestPathPatternRegex = /--(testPathPattern(s?)|runTestsByPath)/g;
2123

2224
export type DebugConfigOptions = Partial<
2325
Pick<PluginResourceSettings, 'jestCommandLine' | 'rootPath' | 'nodeEnv'>
2426
>;
2527
type PartialDebugConfig = Partial<vscode.DebugConfiguration>;
2628
export class DebugConfigurationProvider implements vscode.DebugConfigurationProvider {
27-
private fileNameToRun = '';
28-
private testToRun = '';
29+
private debugInfo: DebugInfo | undefined;
2930
private fromWorkspaceFolder: vscode.WorkspaceFolder | undefined;
31+
private useJest30: boolean | undefined;
3032

3133
/**
3234
* Prepares injecting the name of the test, which has to be debugged, into the `DebugConfiguration`,
3335
* This function has to be called before `vscode.debug.startDebugging`.
3436
*/
3537
public prepareTestRun(
36-
fileNameToRun: string,
37-
testToRun: string,
38-
workspaceFolder: vscode.WorkspaceFolder
38+
debugInfo: DebugInfo,
39+
workspaceFolder: vscode.WorkspaceFolder,
40+
useJest30?: boolean
3941
): void {
40-
this.fileNameToRun = fileNameToRun;
41-
this.testToRun = testToRun;
42+
this.debugInfo = { ...debugInfo };
4243
this.fromWorkspaceFolder = workspaceFolder;
44+
this.useJest30 = useJest30;
4345
}
4446

4547
getDebugConfigNames(workspaceFolder?: vscode.WorkspaceFolder): {
@@ -83,45 +85,69 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
8385

8486
const args = debugConfiguration.args || [];
8587

86-
if (this.fileNameToRun) {
87-
if (this.testToRun) {
88+
if (this.debugInfo) {
89+
if (this.debugInfo.testName) {
8890
args.push('--testNamePattern');
89-
args.push(this.testToRun);
91+
args.push(escapeRegExp(this.debugInfo.testName));
92+
}
93+
if (this.debugInfo.useTestPathPattern) {
94+
args.push(this.getTestPathPatternOption());
95+
args.push(escapeRegExp(this.debugInfo.testPath));
96+
} else {
97+
args.push('--runTestsByPath');
98+
args.push(toFilePath(this.debugInfo.testPath));
9099
}
91-
args.push('--runTestsByPath');
92-
args.push(toFilePath(this.fileNameToRun));
93100

94-
this.fileNameToRun = '';
95-
this.testToRun = '';
101+
this.debugInfo = undefined;
96102
}
97103

98104
debugConfiguration.args = args;
99105
return debugConfiguration;
100106
}
101107

108+
private getTestPathPatternOption(): string {
109+
return this.useJest30 ? '--testPathPatterns' : '--testPathPattern';
110+
}
102111
/**
103112
* resolve v2 debug config
104113
* @param debugConfiguration v2 debug config
105114
* @returns
106115
*/
107116
resolveDebugConfig2(debugConfiguration: vscode.DebugConfiguration): vscode.DebugConfiguration {
108117
if (
118+
!this.debugInfo ||
109119
!debugConfiguration.args ||
110120
!Array.isArray(debugConfiguration.args) ||
111121
debugConfiguration.args.length <= 0
112122
) {
113123
return debugConfiguration;
114124
}
125+
126+
const debugInfo = this.debugInfo;
115127
const args = debugConfiguration.args.map((arg) => {
116128
if (typeof arg !== 'string') {
117129
return arg;
118130
}
131+
if (debugInfo.useTestPathPattern) {
132+
// if the debugInfo indicated this is a testPathPattern (such as running all tests within a folder)
133+
// , we need to replace the --runTestsByPath argument with the correct --testPathPattern(s) argument
134+
if (replaceTestPathPatternRegex.test(arg)) {
135+
return arg.replace(replaceTestPathPatternRegex, this.getTestPathPatternOption());
136+
}
137+
if (testFileRegex.test(arg)) {
138+
return arg.replace(testFileRegex, escapeRegExp(debugInfo.testPath));
139+
}
140+
}
119141
return arg
120-
.replace(testFileRegex, toFilePath(this.fileNameToRun))
121-
.replace(testFilePatternRegex, escapeRegExp(this.fileNameToRun))
122-
.replace(testNamePatternRegex, escapeQuotes(this.testToRun));
142+
.replace(testFileRegex, toFilePath(debugInfo.testPath))
143+
.replace(testFilePatternRegex, escapeRegExp(debugInfo.testPath))
144+
.replace(
145+
testNamePatternRegex,
146+
debugInfo.testName ? escapeQuotes(escapeRegExp(debugInfo.testName)) : '.*'
147+
);
123148
});
124149
debugConfiguration.args = args;
150+
this.debugInfo = undefined;
125151

126152
return debugConfiguration;
127153
}

src/JestExt/core.ts

+7-9
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import {
88
SortedTestResults,
99
TestResultProviderOptions,
1010
} from '../TestResults';
11-
import { escapeRegExp, emptyTestStats, getValidJestCommand } from '../helpers';
11+
import { emptyTestStats, getValidJestCommand } from '../helpers';
1212
import { CoverageMapProvider, CoverageCodeLensProvider } from '../Coverage';
1313
import { updateDiagnostics, updateCurrentDiagnostics, resetDiagnostics } from '../diagnostics';
1414
import { DebugConfigurationProvider } from '../DebugConfigurationProvider';
15-
import { TestExplorerRunRequest, TestNamePattern, TestStats } from '../types';
15+
import { TestExplorerRunRequest, TestStats } from '../types';
1616
import { CoverageOverlay } from '../Coverage/CoverageOverlay';
1717
import { resultsWithoutAnsiEscapeSequence } from '../TestResults/TestResult';
1818
import { CoverageMapData } from 'istanbul-lib-coverage';
@@ -25,6 +25,7 @@ import {
2525
JestRunEvent,
2626
JestTestDataAvailableEvent,
2727
} from './types';
28+
import { DebugInfo } from '../types';
2829
import { extensionName, SupportedLanguageIds } from '../appGlobals';
2930
import { createJestExtContext, getExtensionResourceSettings, prefixWorkspace } from './helper';
3031
import { PluginResourceSettings } from '../Settings';
@@ -566,10 +567,7 @@ export class JestExt {
566567
}
567568

568569
//** commands */
569-
public debugTests = async (
570-
document: vscode.TextDocument | string,
571-
testNamePattern?: TestNamePattern
572-
): Promise<void> => {
570+
public debugTests = async (debugInfo: DebugInfo): Promise<void> => {
573571
const getDebugConfig = (
574572
folder?: vscode.WorkspaceFolder
575573
): vscode.DebugConfiguration | undefined => {
@@ -592,9 +590,9 @@ export class JestExt {
592590
};
593591

594592
this.debugConfigurationProvider.prepareTestRun(
595-
typeof document === 'string' ? document : document.fileName,
596-
testNamePattern ? escapeRegExp(testNamePattern) : '.*',
597-
this.extContext.workspace
593+
debugInfo,
594+
this.extContext.workspace,
595+
this.extContext.settings.useJest30
598596
);
599597

600598
let debugConfig =

src/JestExt/types.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { ProcessSession } from './process-session';
77
import { JestProcessInfo } from '../JestProcessManagement';
88
import { JestOutputTerminal } from './output-terminal';
99
import { TestIdentifier } from '../TestResults';
10-
import { TestNamePattern } from '../types';
10+
import { DebugInfo } from '../types';
1111

1212
export enum WatchMode {
1313
None = 'none',
@@ -61,7 +61,5 @@ export interface JestExtProcessContextRaw extends JestExtContext {
6161
export type JestExtProcessContext = Readonly<JestExtProcessContextRaw>;
6262

6363
export type DebugTestIdentifier = string | TestIdentifier;
64-
export type DebugFunction = (
65-
document: vscode.TextDocument | string,
66-
testNamePattern?: TestNamePattern
67-
) => Promise<void>;
64+
65+
export type DebugFunction = (debugInfo: DebugInfo) => Promise<void>;

src/extension-manager.ts

-7
Original file line numberDiff line numberDiff line change
@@ -391,13 +391,6 @@ export class ExtensionManager {
391391
name: 'run-all-tests',
392392
callback: (extension, editor) => extension.runAllTests(editor),
393393
}),
394-
this.registerCommand({
395-
type: 'active-text-editor',
396-
name: 'debug-tests',
397-
callback: (extension, editor, ...identifiers) => {
398-
extension.debugTests(editor.document, ...identifiers);
399-
},
400-
}),
401394
this.registerCommand({
402395
type: 'select-workspace',
403396
name: 'save-run-mode',

src/test-provider/test-item-data.ts

+14-13
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { ItBlock, TestAssertionStatus } from 'jest-editor-support';
88
import { ContainerNode, DataNode, NodeType, ROOT_NODE_NAME } from '../TestResults/match-node';
99
import { Logging } from '../logging';
1010
import { TestSuitChangeEvent } from '../TestResults/test-result-events';
11-
import { Debuggable, ItemCommand, ScheduleTestOptions, TestItemData } from './types';
11+
import { ItemCommand, ScheduleTestOptions, TestItemData } from './types';
1212
import { JestTestProviderContext } from './test-provider-context';
1313
import { JestTestRun } from './jest-test-run';
1414
import { JestProcessInfo, ProcessStatus } from '../JestProcessManagement';
@@ -17,7 +17,7 @@ import { tiContextManager } from './test-item-context-manager';
1717
import { runModeDescription } from '../JestExt/run-mode';
1818
import { isVirtualWorkspaceFolder } from '../virtual-workspace-folder';
1919
import { outputManager } from '../output-manager';
20-
import { TestNamePattern } from '../types';
20+
import { DebugInfo, TestNamePattern } from '../types';
2121

2222
interface JestRunnable {
2323
getJestRunRequest: (options?: ScheduleTestOptions) => JestExtRequestType;
@@ -109,6 +109,10 @@ abstract class TestItemDataBase implements TestItemData, JestRunnable, WithUri {
109109
viewSnapshot(): Promise<void> {
110110
return Promise.reject(`viewSnapshot is not supported for ${this.item.id}`);
111111
}
112+
113+
getDebugInfo(): DebugInfo {
114+
return { testPath: this.uri.fsPath, useTestPathPattern: true };
115+
}
112116
abstract getJestRunRequest(options?: ScheduleTestOptions): JestExtRequestType;
113117
}
114118

@@ -141,9 +145,7 @@ export class WorkspaceRoot extends TestItemDataBase {
141145
isVirtualWorkspaceFolder(workspaceFolder)
142146
? workspaceFolder.effectiveUri
143147
: workspaceFolder.uri,
144-
this,
145-
undefined,
146-
['run']
148+
this
147149
);
148150
const desc = runModeDescription(this.context.ext.settings.runMode.config);
149151
item.description = `(${desc.deferred?.label ?? desc.type.label})`;
@@ -496,7 +498,7 @@ export class FolderData extends TestItemDataBase {
496498
}
497499
private createTestItem(name: string, parent: vscode.TestItem) {
498500
const uri = FolderData.makeUri(parent, name);
499-
const item = this.context.createTestItem(uri.fsPath, name, uri, this, parent, ['run']);
501+
const item = this.context.createTestItem(uri.fsPath, name, uri, this, parent);
500502

501503
item.canResolveChildren = false;
502504
return item;
@@ -723,18 +725,17 @@ export class TestDocumentRoot extends TestResultData {
723725
};
724726
}
725727

726-
getDebugInfo(): ReturnType<Debuggable['getDebugInfo']> {
727-
return { fileName: this.uri.fsPath };
728-
}
729-
730728
public onTestMatched(): void {
731729
this.forEachChild((child) => child.onTestMatched());
732730
}
733731
public gatherSnapshotItems(snapshotItems: SnapshotItemCollection): void {
734732
this.forEachChild((child) => child.gatherSnapshotItems(snapshotItems));
735733
}
734+
getDebugInfo(): DebugInfo {
735+
return { testPath: this.uri.fsPath };
736+
}
736737
}
737-
export class TestData extends TestResultData implements Debuggable {
738+
export class TestData extends TestResultData {
738739
constructor(
739740
readonly context: JestTestProviderContext,
740741
fileUri: vscode.Uri,
@@ -777,8 +778,8 @@ export class TestData extends TestResultData implements Debuggable {
777778
};
778779
}
779780

780-
getDebugInfo(): ReturnType<Debuggable['getDebugInfo']> {
781-
return { fileName: this.uri.fsPath, testNamePattern: this.getTestNamePattern() };
781+
getDebugInfo(): DebugInfo {
782+
return { testPath: this.uri.fsPath, testName: this.getTestNamePattern() };
782783
}
783784
private updateItemRange(): void {
784785
if (this.node.attrs.range) {

src/test-provider/test-provider.ts

+7-17
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
import * as vscode from 'vscode';
22
import { JestTestProviderContext } from './test-provider-context';
33
import { WorkspaceRoot } from './test-item-data';
4-
import { Debuggable, ItemCommand, JestExtExplorerContext, TestItemData, TestTagId } from './types';
4+
import { ItemCommand, JestExtExplorerContext, TestItemData, TestTagId } from './types';
55
import { extensionId, extensionName } from '../appGlobals';
66
import { Logging } from '../logging';
77
import { toErrorString } from '../helpers';
88
import { tiContextManager } from './test-item-context-manager';
99
import { JestTestRun } from './jest-test-run';
1010
import { JestTestCoverageProvider } from './test-coverage';
1111

12-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
13-
const isDebuggable = (arg: any): arg is Debuggable => arg && typeof arg.getDebugInfo === 'function';
14-
1512
export class JestTestProvider {
1613
private readonly controller: vscode.TestController;
1714
private context: JestTestProviderContext;
@@ -132,20 +129,13 @@ export class JestTestProvider {
132129
*/
133130
debugTest = async (tData: TestItemData, run: JestTestRun): Promise<void> => {
134131
let error;
135-
if (isDebuggable(tData)) {
136-
try {
137-
const debugInfo = tData.getDebugInfo();
138-
if (debugInfo.testNamePattern) {
139-
await this.context.ext.debugTests(debugInfo.fileName, debugInfo.testNamePattern);
140-
} else {
141-
await this.context.ext.debugTests(debugInfo.fileName);
142-
}
143-
return;
144-
} catch (e) {
145-
error = `item ${tData.item.id} failed to debug: ${JSON.stringify(e)}`;
146-
}
132+
try {
133+
const debugInfo = tData.getDebugInfo();
134+
await this.context.ext.debugTests(debugInfo);
135+
return;
136+
} catch (e) {
137+
error = `item ${tData.item.id} failed to debug: ${JSON.stringify(e)}`;
147138
}
148-
error = error ?? `item ${tData.item.id} is not debuggable`;
149139
run.errored(tData.item, new vscode.TestMessage(error));
150140
run.write(error, 'error');
151141
return Promise.resolve();

src/test-provider/types.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { TestResultProvider } from '../TestResults';
44
import { WorkspaceRoot, FolderData, TestData, TestDocumentRoot } from './test-item-data';
55
import { JestTestProviderContext } from './test-provider-context';
66
import { JestTestRun } from './jest-test-run';
7-
import { TestNamePattern } from '../types';
7+
import { DebugInfo } from '../types';
88

99
export type TestItemDataType = WorkspaceRoot | FolderData | TestDocumentRoot | TestData;
1010

@@ -19,17 +19,15 @@ export interface ScheduleTestOptions {
1919
itemCommand?: ItemCommand;
2020
profile?: vscode.TestRunProfile;
2121
}
22+
2223
export interface TestItemData {
2324
readonly item: vscode.TestItem;
2425
readonly uri: vscode.Uri;
2526
context: JestTestProviderContext;
2627
discoverTest?: (run: JestTestRun) => void;
2728
scheduleTest: (run: JestTestRun, options?: ScheduleTestOptions) => void;
2829
runItemCommand: (command: ItemCommand) => void;
29-
}
30-
31-
export interface Debuggable {
32-
getDebugInfo: () => { fileName: string; testNamePattern?: TestNamePattern };
30+
getDebugInfo: () => DebugInfo;
3331
}
3432

3533
export enum TestTagId {

src/types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,8 @@ export interface StringPattern {
2323
}
2424

2525
export type TestNamePattern = StringPattern | string;
26+
export interface DebugInfo {
27+
testPath: string;
28+
useTestPathPattern?: boolean;
29+
testName?: TestNamePattern;
30+
}

0 commit comments

Comments
 (0)