Skip to content

Commit

Permalink
Make all test items debuggable (#1181)
Browse files Browse the repository at this point in the history
* enable debugging for all test items

* adding tests
  • Loading branch information
connectdotz authored Oct 9, 2024
1 parent 237bc53 commit e299eb5
Show file tree
Hide file tree
Showing 13 changed files with 213 additions and 142 deletions.
60 changes: 43 additions & 17 deletions src/DebugConfigurationProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,35 @@ import {
} from './helpers';
import { platform } from 'os';
import { PluginResourceSettings } from './Settings';
import { DebugInfo } from './types';

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

export type DebugConfigOptions = Partial<
Pick<PluginResourceSettings, 'jestCommandLine' | 'rootPath' | 'nodeEnv'>
>;
type PartialDebugConfig = Partial<vscode.DebugConfiguration>;
export class DebugConfigurationProvider implements vscode.DebugConfigurationProvider {
private fileNameToRun = '';
private testToRun = '';
private debugInfo: DebugInfo | undefined;
private fromWorkspaceFolder: vscode.WorkspaceFolder | undefined;
private useJest30: boolean | undefined;

/**
* Prepares injecting the name of the test, which has to be debugged, into the `DebugConfiguration`,
* This function has to be called before `vscode.debug.startDebugging`.
*/
public prepareTestRun(
fileNameToRun: string,
testToRun: string,
workspaceFolder: vscode.WorkspaceFolder
debugInfo: DebugInfo,
workspaceFolder: vscode.WorkspaceFolder,
useJest30?: boolean
): void {
this.fileNameToRun = fileNameToRun;
this.testToRun = testToRun;
this.debugInfo = { ...debugInfo };
this.fromWorkspaceFolder = workspaceFolder;
this.useJest30 = useJest30;
}

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

const args = debugConfiguration.args || [];

if (this.fileNameToRun) {
if (this.testToRun) {
if (this.debugInfo) {
if (this.debugInfo.testName) {
args.push('--testNamePattern');
args.push(this.testToRun);
args.push(escapeRegExp(this.debugInfo.testName));
}
if (this.debugInfo.useTestPathPattern) {
args.push(this.getTestPathPatternOption());
args.push(escapeRegExp(this.debugInfo.testPath));
} else {
args.push('--runTestsByPath');
args.push(toFilePath(this.debugInfo.testPath));
}
args.push('--runTestsByPath');
args.push(toFilePath(this.fileNameToRun));

this.fileNameToRun = '';
this.testToRun = '';
this.debugInfo = undefined;
}

debugConfiguration.args = args;
return debugConfiguration;
}

private getTestPathPatternOption(): string {
return this.useJest30 ? '--testPathPatterns' : '--testPathPattern';
}
/**
* resolve v2 debug config
* @param debugConfiguration v2 debug config
* @returns
*/
resolveDebugConfig2(debugConfiguration: vscode.DebugConfiguration): vscode.DebugConfiguration {
if (
!this.debugInfo ||
!debugConfiguration.args ||
!Array.isArray(debugConfiguration.args) ||
debugConfiguration.args.length <= 0
) {
return debugConfiguration;
}

const debugInfo = this.debugInfo;
const args = debugConfiguration.args.map((arg) => {
if (typeof arg !== 'string') {
return arg;
}
if (debugInfo.useTestPathPattern) {
// if the debugInfo indicated this is a testPathPattern (such as running all tests within a folder)
// , we need to replace the --runTestsByPath argument with the correct --testPathPattern(s) argument
if (replaceTestPathPatternRegex.test(arg)) {
return arg.replace(replaceTestPathPatternRegex, this.getTestPathPatternOption());
}
if (testFileRegex.test(arg)) {
return arg.replace(testFileRegex, escapeRegExp(debugInfo.testPath));
}
}
return arg
.replace(testFileRegex, toFilePath(this.fileNameToRun))
.replace(testFilePatternRegex, escapeRegExp(this.fileNameToRun))
.replace(testNamePatternRegex, escapeQuotes(this.testToRun));
.replace(testFileRegex, toFilePath(debugInfo.testPath))
.replace(testFilePatternRegex, escapeRegExp(debugInfo.testPath))
.replace(
testNamePatternRegex,
debugInfo.testName ? escapeQuotes(escapeRegExp(debugInfo.testName)) : '.*'
);
});
debugConfiguration.args = args;
this.debugInfo = undefined;

return debugConfiguration;
}
Expand Down
16 changes: 7 additions & 9 deletions src/JestExt/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import {
SortedTestResults,
TestResultProviderOptions,
} from '../TestResults';
import { escapeRegExp, emptyTestStats, getValidJestCommand } from '../helpers';
import { emptyTestStats, getValidJestCommand } from '../helpers';
import { CoverageMapProvider, CoverageCodeLensProvider } from '../Coverage';
import { updateDiagnostics, updateCurrentDiagnostics, resetDiagnostics } from '../diagnostics';
import { DebugConfigurationProvider } from '../DebugConfigurationProvider';
import { TestExplorerRunRequest, TestNamePattern, TestStats } from '../types';
import { TestExplorerRunRequest, TestStats } from '../types';
import { CoverageOverlay } from '../Coverage/CoverageOverlay';
import { resultsWithoutAnsiEscapeSequence } from '../TestResults/TestResult';
import { CoverageMapData } from 'istanbul-lib-coverage';
Expand All @@ -25,6 +25,7 @@ import {
JestRunEvent,
JestTestDataAvailableEvent,
} from './types';
import { DebugInfo } from '../types';
import { extensionName, SupportedLanguageIds } from '../appGlobals';
import { createJestExtContext, getExtensionResourceSettings, prefixWorkspace } from './helper';
import { PluginResourceSettings } from '../Settings';
Expand Down Expand Up @@ -566,10 +567,7 @@ export class JestExt {
}

//** commands */
public debugTests = async (
document: vscode.TextDocument | string,
testNamePattern?: TestNamePattern
): Promise<void> => {
public debugTests = async (debugInfo: DebugInfo): Promise<void> => {
const getDebugConfig = (
folder?: vscode.WorkspaceFolder
): vscode.DebugConfiguration | undefined => {
Expand All @@ -592,9 +590,9 @@ export class JestExt {
};

this.debugConfigurationProvider.prepareTestRun(
typeof document === 'string' ? document : document.fileName,
testNamePattern ? escapeRegExp(testNamePattern) : '.*',
this.extContext.workspace
debugInfo,
this.extContext.workspace,
this.extContext.settings.useJest30
);

let debugConfig =
Expand Down
8 changes: 3 additions & 5 deletions src/JestExt/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ProcessSession } from './process-session';
import { JestProcessInfo } from '../JestProcessManagement';
import { JestOutputTerminal } from './output-terminal';
import { TestIdentifier } from '../TestResults';
import { TestNamePattern } from '../types';
import { DebugInfo } from '../types';

export enum WatchMode {
None = 'none',
Expand Down Expand Up @@ -61,7 +61,5 @@ export interface JestExtProcessContextRaw extends JestExtContext {
export type JestExtProcessContext = Readonly<JestExtProcessContextRaw>;

export type DebugTestIdentifier = string | TestIdentifier;
export type DebugFunction = (
document: vscode.TextDocument | string,
testNamePattern?: TestNamePattern
) => Promise<void>;

export type DebugFunction = (debugInfo: DebugInfo) => Promise<void>;
7 changes: 0 additions & 7 deletions src/extension-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,13 +391,6 @@ export class ExtensionManager {
name: 'run-all-tests',
callback: (extension, editor) => extension.runAllTests(editor),
}),
this.registerCommand({
type: 'active-text-editor',
name: 'debug-tests',
callback: (extension, editor, ...identifiers) => {
extension.debugTests(editor.document, ...identifiers);
},
}),
this.registerCommand({
type: 'select-workspace',
name: 'save-run-mode',
Expand Down
27 changes: 14 additions & 13 deletions src/test-provider/test-item-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ItBlock, TestAssertionStatus } from 'jest-editor-support';
import { ContainerNode, DataNode, NodeType, ROOT_NODE_NAME } from '../TestResults/match-node';
import { Logging } from '../logging';
import { TestSuitChangeEvent } from '../TestResults/test-result-events';
import { Debuggable, ItemCommand, ScheduleTestOptions, TestItemData } from './types';
import { ItemCommand, ScheduleTestOptions, TestItemData } from './types';
import { JestTestProviderContext } from './test-provider-context';
import { JestTestRun } from './jest-test-run';
import { JestProcessInfo, ProcessStatus } from '../JestProcessManagement';
Expand All @@ -17,7 +17,7 @@ import { tiContextManager } from './test-item-context-manager';
import { runModeDescription } from '../JestExt/run-mode';
import { isVirtualWorkspaceFolder } from '../virtual-workspace-folder';
import { outputManager } from '../output-manager';
import { TestNamePattern } from '../types';
import { DebugInfo, TestNamePattern } from '../types';

interface JestRunnable {
getJestRunRequest: (options?: ScheduleTestOptions) => JestExtRequestType;
Expand Down Expand Up @@ -109,6 +109,10 @@ abstract class TestItemDataBase implements TestItemData, JestRunnable, WithUri {
viewSnapshot(): Promise<void> {
return Promise.reject(`viewSnapshot is not supported for ${this.item.id}`);
}

getDebugInfo(): DebugInfo {
return { testPath: this.uri.fsPath, useTestPathPattern: true };
}
abstract getJestRunRequest(options?: ScheduleTestOptions): JestExtRequestType;
}

Expand Down Expand Up @@ -141,9 +145,7 @@ export class WorkspaceRoot extends TestItemDataBase {
isVirtualWorkspaceFolder(workspaceFolder)
? workspaceFolder.effectiveUri
: workspaceFolder.uri,
this,
undefined,
['run']
this
);
const desc = runModeDescription(this.context.ext.settings.runMode.config);
item.description = `(${desc.deferred?.label ?? desc.type.label})`;
Expand Down Expand Up @@ -496,7 +498,7 @@ export class FolderData extends TestItemDataBase {
}
private createTestItem(name: string, parent: vscode.TestItem) {
const uri = FolderData.makeUri(parent, name);
const item = this.context.createTestItem(uri.fsPath, name, uri, this, parent, ['run']);
const item = this.context.createTestItem(uri.fsPath, name, uri, this, parent);

item.canResolveChildren = false;
return item;
Expand Down Expand Up @@ -723,18 +725,17 @@ export class TestDocumentRoot extends TestResultData {
};
}

getDebugInfo(): ReturnType<Debuggable['getDebugInfo']> {
return { fileName: this.uri.fsPath };
}

public onTestMatched(): void {
this.forEachChild((child) => child.onTestMatched());
}
public gatherSnapshotItems(snapshotItems: SnapshotItemCollection): void {
this.forEachChild((child) => child.gatherSnapshotItems(snapshotItems));
}
getDebugInfo(): DebugInfo {
return { testPath: this.uri.fsPath };
}
}
export class TestData extends TestResultData implements Debuggable {
export class TestData extends TestResultData {
constructor(
readonly context: JestTestProviderContext,
fileUri: vscode.Uri,
Expand Down Expand Up @@ -777,8 +778,8 @@ export class TestData extends TestResultData implements Debuggable {
};
}

getDebugInfo(): ReturnType<Debuggable['getDebugInfo']> {
return { fileName: this.uri.fsPath, testNamePattern: this.getTestNamePattern() };
getDebugInfo(): DebugInfo {
return { testPath: this.uri.fsPath, testName: this.getTestNamePattern() };
}
private updateItemRange(): void {
if (this.node.attrs.range) {
Expand Down
24 changes: 7 additions & 17 deletions src/test-provider/test-provider.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import * as vscode from 'vscode';
import { JestTestProviderContext } from './test-provider-context';
import { WorkspaceRoot } from './test-item-data';
import { Debuggable, ItemCommand, JestExtExplorerContext, TestItemData, TestTagId } from './types';
import { ItemCommand, JestExtExplorerContext, TestItemData, TestTagId } from './types';
import { extensionId, extensionName } from '../appGlobals';
import { Logging } from '../logging';
import { toErrorString } from '../helpers';
import { tiContextManager } from './test-item-context-manager';
import { JestTestRun } from './jest-test-run';
import { JestTestCoverageProvider } from './test-coverage';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const isDebuggable = (arg: any): arg is Debuggable => arg && typeof arg.getDebugInfo === 'function';

export class JestTestProvider {
private readonly controller: vscode.TestController;
private context: JestTestProviderContext;
Expand Down Expand Up @@ -132,20 +129,13 @@ export class JestTestProvider {
*/
debugTest = async (tData: TestItemData, run: JestTestRun): Promise<void> => {
let error;
if (isDebuggable(tData)) {
try {
const debugInfo = tData.getDebugInfo();
if (debugInfo.testNamePattern) {
await this.context.ext.debugTests(debugInfo.fileName, debugInfo.testNamePattern);
} else {
await this.context.ext.debugTests(debugInfo.fileName);
}
return;
} catch (e) {
error = `item ${tData.item.id} failed to debug: ${JSON.stringify(e)}`;
}
try {
const debugInfo = tData.getDebugInfo();
await this.context.ext.debugTests(debugInfo);
return;
} catch (e) {
error = `item ${tData.item.id} failed to debug: ${JSON.stringify(e)}`;
}
error = error ?? `item ${tData.item.id} is not debuggable`;
run.errored(tData.item, new vscode.TestMessage(error));
run.write(error, 'error');
return Promise.resolve();
Expand Down
8 changes: 3 additions & 5 deletions src/test-provider/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { TestResultProvider } from '../TestResults';
import { WorkspaceRoot, FolderData, TestData, TestDocumentRoot } from './test-item-data';
import { JestTestProviderContext } from './test-provider-context';
import { JestTestRun } from './jest-test-run';
import { TestNamePattern } from '../types';
import { DebugInfo } from '../types';

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

Expand All @@ -19,17 +19,15 @@ export interface ScheduleTestOptions {
itemCommand?: ItemCommand;
profile?: vscode.TestRunProfile;
}

export interface TestItemData {
readonly item: vscode.TestItem;
readonly uri: vscode.Uri;
context: JestTestProviderContext;
discoverTest?: (run: JestTestRun) => void;
scheduleTest: (run: JestTestRun, options?: ScheduleTestOptions) => void;
runItemCommand: (command: ItemCommand) => void;
}

export interface Debuggable {
getDebugInfo: () => { fileName: string; testNamePattern?: TestNamePattern };
getDebugInfo: () => DebugInfo;
}

export enum TestTagId {
Expand Down
5 changes: 5 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,8 @@ export interface StringPattern {
}

export type TestNamePattern = StringPattern | string;
export interface DebugInfo {
testPath: string;
useTestPathPattern?: boolean;
testName?: TestNamePattern;
}
Loading

0 comments on commit e299eb5

Please sign in to comment.