Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make all test items debuggable #1181

Merged
merged 3 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading