From 7a0185ac939ebb6c115b16582ec49444476dc4f9 Mon Sep 17 00:00:00 2001 From: connectdotz Date: Tue, 10 Sep 2024 22:12:23 -0400 Subject: [PATCH 1/2] enable debugging for all test items --- src/DebugConfigurationProvider.ts | 59 +++++++++++++++------- src/JestExt/core.ts | 16 +++--- src/JestExt/types.ts | 8 ++- src/extension-manager.ts | 7 --- src/test-provider/test-item-data.ts | 27 +++++----- src/test-provider/test-provider.ts | 24 +++------ src/test-provider/types.ts | 8 ++- src/types.ts | 5 ++ tests/DebugConfigurationProvider.test.ts | 32 ++++++------ tests/JestExt/core.test.ts | 42 ++++++--------- tests/extension-manager.test.ts | 1 - tests/test-provider/test-item-data.test.ts | 33 ++++++++---- tests/test-provider/test-provider.test.ts | 25 ++++----- 13 files changed, 147 insertions(+), 140 deletions(-) diff --git a/src/DebugConfigurationProvider.ts b/src/DebugConfigurationProvider.ts index c0d7ffc2d..5f9a072ca 100755 --- a/src/DebugConfigurationProvider.ts +++ b/src/DebugConfigurationProvider.ts @@ -12,6 +12,7 @@ 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; @@ -23,22 +24,22 @@ export type DebugConfigOptions = Partial< >; type PartialDebugConfig = Partial; 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): { @@ -82,22 +83,29 @@ 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 @@ -105,22 +113,39 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv */ 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 (arg.includes('--runTestsByPath')) { + return arg.replace('--runTestsByPath', 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, this.testToRun); + .replace(testFileRegex, toFilePath(debugInfo.testPath)) + .replace(testFilePatternRegex, escapeRegExp(debugInfo.testPath)) + .replace( + testNamePatternRegex, + debugInfo.testName ? escapeRegExp(debugInfo.testName) : '.*' + ); }); debugConfiguration.args = args; + this.debugInfo = undefined; return debugConfiguration; } diff --git a/src/JestExt/core.ts b/src/JestExt/core.ts index 0e04601d6..1ddf6070a 100644 --- a/src/JestExt/core.ts +++ b/src/JestExt/core.ts @@ -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'; @@ -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'; @@ -566,10 +567,7 @@ export class JestExt { } //** commands */ - public debugTests = async ( - document: vscode.TextDocument | string, - testNamePattern?: TestNamePattern - ): Promise => { + public debugTests = async (debugInfo: DebugInfo): Promise => { const getDebugConfig = ( folder?: vscode.WorkspaceFolder ): vscode.DebugConfiguration | undefined => { @@ -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 = diff --git a/src/JestExt/types.ts b/src/JestExt/types.ts index 5fa520fa1..334351284 100644 --- a/src/JestExt/types.ts +++ b/src/JestExt/types.ts @@ -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', @@ -61,7 +61,5 @@ export interface JestExtProcessContextRaw extends JestExtContext { export type JestExtProcessContext = Readonly; export type DebugTestIdentifier = string | TestIdentifier; -export type DebugFunction = ( - document: vscode.TextDocument | string, - testNamePattern?: TestNamePattern -) => Promise; + +export type DebugFunction = (debugInfo: DebugInfo) => Promise; diff --git a/src/extension-manager.ts b/src/extension-manager.ts index fefd15d7c..f55cb3bea 100644 --- a/src/extension-manager.ts +++ b/src/extension-manager.ts @@ -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', diff --git a/src/test-provider/test-item-data.ts b/src/test-provider/test-item-data.ts index 67c8a7e82..a0943935c 100644 --- a/src/test-provider/test-item-data.ts +++ b/src/test-provider/test-item-data.ts @@ -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'; @@ -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; @@ -109,6 +109,10 @@ abstract class TestItemDataBase implements TestItemData, JestRunnable, WithUri { viewSnapshot(): Promise { 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; } @@ -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})`; @@ -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; @@ -724,18 +726,17 @@ export class TestDocumentRoot extends TestResultData { }; } - getDebugInfo(): ReturnType { - 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, @@ -778,8 +779,8 @@ export class TestData extends TestResultData implements Debuggable { }; } - getDebugInfo(): ReturnType { - 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) { diff --git a/src/test-provider/test-provider.ts b/src/test-provider/test-provider.ts index 01139670f..d4af16ae5 100644 --- a/src/test-provider/test-provider.ts +++ b/src/test-provider/test-provider.ts @@ -1,7 +1,7 @@ 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'; @@ -9,9 +9,6 @@ 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; @@ -132,20 +129,13 @@ export class JestTestProvider { */ debugTest = async (tData: TestItemData, run: JestTestRun): Promise => { 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(); diff --git a/src/test-provider/types.ts b/src/test-provider/types.ts index 7a019f493..e3f9aee4f 100644 --- a/src/test-provider/types.ts +++ b/src/test-provider/types.ts @@ -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; @@ -19,6 +19,7 @@ export interface ScheduleTestOptions { itemCommand?: ItemCommand; profile?: vscode.TestRunProfile; } + export interface TestItemData { readonly item: vscode.TestItem; readonly uri: vscode.Uri; @@ -26,10 +27,7 @@ export interface TestItemData { 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 { diff --git a/src/types.ts b/src/types.ts index 362c633fc..b120935f5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -23,3 +23,8 @@ export interface StringPattern { } export type TestNamePattern = StringPattern | string; +export interface DebugInfo { + testPath: string; + useTestPathPattern?: boolean; + testName?: TestNamePattern; +} diff --git a/tests/DebugConfigurationProvider.test.ts b/tests/DebugConfigurationProvider.test.ts index f28082a2d..2932b086b 100755 --- a/tests/DebugConfigurationProvider.test.ts +++ b/tests/DebugConfigurationProvider.test.ts @@ -16,8 +16,12 @@ import * as fs from 'fs'; import { makeWorkspaceFolder } from './test-helper'; describe('DebugConfigurationProvider', () => { - const fileName = '/a/file'; + const testPath = '/a/file'; const testName = 'a test'; + beforeEach(() => { + (toFilePath as unknown as jest.Mock<{}>).mockImplementation((s) => s); + (escapeRegExp as unknown as jest.Mock<{}>).mockImplementation((s) => s); + }); it('should by default return a DebugConfiguration for Jest', () => { const folder: any = { name: 'folder', uri: { fsPath: null } }; @@ -72,18 +76,16 @@ describe('DebugConfigurationProvider', () => { it.each` debugConfigArgs | expectedArgs - ${[]} | ${['--testNamePattern', testName, '--runTestsByPath', fileName]} - ${['--runInBand']} | ${['--runInBand', '--testNamePattern', testName, '--runTestsByPath', fileName]} + ${[]} | ${['--testNamePattern', testName, '--runTestsByPath', testPath]} + ${['--runInBand']} | ${['--runInBand', '--testNamePattern', testName, '--runTestsByPath', testPath]} `( 'should append the specified tests arguments for non-v2 config', ({ debugConfigArgs, expectedArgs }) => { - (toFilePath as unknown as jest.Mock<{}>).mockImplementation((s) => s); - let configuration: any = { name: 'vscode-jest-tests', args: debugConfigArgs }; const sut = new DebugConfigurationProvider(); const ws = makeWorkspaceFolder('whatever'); - sut.prepareTestRun(fileName, testName, ws); + sut.prepareTestRun({ testPath, testName }, ws); configuration = sut.resolveDebugConfiguration(undefined, configuration); @@ -96,8 +98,6 @@ describe('DebugConfigurationProvider', () => { } ); it('skip non-jest config', () => { - (toFilePath as unknown as jest.Mock<{}>).mockImplementation((s) => s); - const configuration: any = { name: 'non-jest', args: [] }; const sut = new DebugConfigurationProvider(); @@ -110,20 +110,20 @@ describe('DebugConfigurationProvider', () => { it.each` args | expected ${[]} | ${[]} - ${['${jest.testFile}']} | ${[fileName]} + ${['${jest.testFile}']} | ${[testPath]} ${['${jest.testFilePattern}']} | ${[fileNamePattern]} ${['${jest.testNamePattern}']} | ${[testName]} - ${['--testNamePattern', '${jest.testNamePattern}', '--runTestsByPath', '${jest.testFile}']} | ${['--testNamePattern', testName, '--runTestsByPath', fileName]} + ${['--testNamePattern', '${jest.testNamePattern}', '--runTestsByPath', '${jest.testFile}']} | ${['--testNamePattern', testName, '--runTestsByPath', testPath]} ${['${jest.testNamePattern}', true]} | ${[testName, true]} `('will only translate known variables: $args', ({ args, expected }) => { - (toFilePath as unknown as jest.Mock<{}>).mockReturnValueOnce(fileName); + (toFilePath as unknown as jest.Mock<{}>).mockReturnValueOnce(testPath); (escapeRegExp as unknown as jest.Mock<{}>).mockReturnValueOnce(fileNamePattern); let configuration: any = { name: 'vscode-jest-tests.v2', args }; const sut = new DebugConfigurationProvider(); const ws = makeWorkspaceFolder('whatever'); - sut.prepareTestRun(fileName, testName, ws); + sut.prepareTestRun({ testPath, testName }, ws); configuration = sut.resolveDebugConfiguration(undefined, configuration); @@ -131,8 +131,8 @@ describe('DebugConfigurationProvider', () => { expect(configuration.args).toEqual(expected); }); it('will translate multiple variables in a single arg', () => { - (toFilePath as unknown as jest.Mock<{}>).mockReturnValueOnce(fileName); - (escapeRegExp as unknown as jest.Mock<{}>).mockReturnValueOnce(fileNamePattern); + (toFilePath as unknown as jest.Mock<{}>).mockImplementation((s) => s); + (escapeRegExp as unknown as jest.Mock<{}>).mockImplementation((s) => s); let configuration: any = { name: 'vscode-jest-tests.v2', @@ -141,13 +141,13 @@ describe('DebugConfigurationProvider', () => { const sut = new DebugConfigurationProvider(); const ws = makeWorkspaceFolder('whatever'); - sut.prepareTestRun(fileName, testName, ws); + sut.prepareTestRun({ testPath, testName }, ws); configuration = sut.resolveDebugConfiguration(undefined, configuration); expect(configuration).toBeDefined(); expect(configuration.args).toEqual([ - `--testNamePattern "${testName}" --runTestsByPath "${fileName}"`, + `--testNamePattern "${testName}" --runTestsByPath "${testPath}"`, ]); }); }); diff --git a/tests/JestExt/core.test.ts b/tests/JestExt/core.test.ts index a4bf3c698..43f808cca 100644 --- a/tests/JestExt/core.test.ts +++ b/tests/JestExt/core.test.ts @@ -172,8 +172,8 @@ describe('JestExt', () => { const debugConfiguration2 = { type: 'with-setting-override' }; describe('debugTests()', () => { - const fileName = 'fileName'; - const document: any = { fileName }; + const testPath = 'fileName'; + const document: any = { fileName: testPath }; let sut: JestExt; let startDebugging; const mockShowQuickPick = jest.fn(); @@ -218,13 +218,14 @@ describe('JestExt', () => { ${['a', 'vscode-jest-tests', 'b']} | ${false} | ${false} | ${false} `('$configNames', async ({ configNames, useDefaultConfig, debugMode, v2 }) => { expect.hasAssertions(); - const testNamePattern = 'testNamePattern'; + const testName = 'testName'; mockConfigurations = configNames ? configNames.map((name) => ({ name })) : undefined; // mockProjectWorkspace.debug = debugMode; sut = newJestExt({ settings: { debugMode } }); - await sut.debugTests(document, testNamePattern); + const debugInfo = { testPath: document.fileName, testName }; + await sut.debugTests(debugInfo); expect(startDebugging).toHaveBeenCalledTimes(1); if (useDefaultConfig) { @@ -241,9 +242,9 @@ describe('JestExt', () => { } expect(sut.debugConfigurationProvider.prepareTestRun).toHaveBeenCalledWith( - fileName, - testNamePattern, - workspaceFolder + debugInfo, + workspaceFolder, + undefined ); }); describe('can fallback to workspace config if no folder config found', () => { @@ -276,7 +277,7 @@ describe('JestExt', () => { }; }); sut = newJestExt({ settings: { jestCommandLine: undefined } }); - sut.debugTests(document, 'testNamePattern'); + sut.debugTests({ testPath: document.fileName, testName: 'testName' }); expect(vscode.debug.startDebugging).toHaveBeenCalledWith( workspaceFolder, expectedConfig @@ -295,7 +296,7 @@ describe('JestExt', () => { sut = newJestExt({ settings }); const mockConfig: any = { get: jest.fn() }; vscode.workspace.getConfiguration = jest.fn(() => mockConfig); - sut.debugTests(document, 'whatever'); + sut.debugTests({ testPath: document.fileName, testName: 'whatever' }); expect(sut.debugConfigurationProvider.createDebugConfig).toHaveBeenCalledWith( workspaceFolder, createDebugConfigOptions ?? settings @@ -309,8 +310,8 @@ describe('JestExt', () => { }); describe('should run the supplied test', () => { it.each([[document], ['fileName']])('support document parameter: %s', async (doc) => { - const testNamePattern = 'testNamePattern'; - await sut.debugTests(doc, testNamePattern); + const debugInfo = { testPath: doc.fileName, testName: 'testName' }; + await sut.debugTests(debugInfo); expect(vscode.debug.startDebugging).toHaveBeenCalledWith( workspaceFolder, debugConfiguration2 @@ -319,24 +320,13 @@ describe('JestExt', () => { expect(configuration).toBeDefined(); expect(configuration.type).toBe(debugConfiguration2.type); expect(sut.debugConfigurationProvider.prepareTestRun).toHaveBeenCalledWith( - fileName, - testNamePattern, - workspaceFolder + debugInfo, + workspaceFolder, + undefined ); - expect(mockHelpers.escapeRegExp).toHaveBeenCalledWith(testNamePattern); + expect(mockHelpers.escapeRegExp).not.toHaveBeenCalled(); }); }); - it('if pass zero testNamePattern, all tests will be run', async () => { - await sut.debugTests(document); - expect(mockShowQuickPick).not.toHaveBeenCalled(); - expect(mockHelpers.testIdString).not.toHaveBeenCalled(); - expect(sut.debugConfigurationProvider.prepareTestRun).toHaveBeenCalledWith( - document.fileName, - '.*', - workspaceFolder - ); - expect(vscode.debug.startDebugging).toHaveBeenCalled(); - }); }); describe('onDidCloseTextDocument()', () => { diff --git a/tests/extension-manager.test.ts b/tests/extension-manager.test.ts index 5f18b7480..420242205 100644 --- a/tests/extension-manager.test.ts +++ b/tests/extension-manager.test.ts @@ -1147,7 +1147,6 @@ describe('ExtensionManager', () => { ${'editor.workspace.toggle-coverage'} | ${'toggleCoverage'} ${'editor.workspace.run-all-tests'} | ${'runAllTests'} ${'editor.run-all-tests'} | ${'runAllTests'} - ${'editor.debug-tests'} | ${'debugTests'} `('editor-based commands "$name"', async ({ name, extFunc }) => { extensionManager.register(); const expectedName = `${extensionName}.${name}`; diff --git a/tests/test-provider/test-item-data.test.ts b/tests/test-provider/test-item-data.test.ts index d3ef2df92..9e6fc320d 100644 --- a/tests/test-provider/test-item-data.test.ts +++ b/tests/test-provider/test-item-data.test.ts @@ -1264,33 +1264,46 @@ describe('test-item-data', () => { expect(itemData.item.tags.find((t) => t.id === 'run')).toBeTruthy(); }); }); - it('only TestData and TestDocument supports debug tags', () => { - [doc, testItem].forEach((itemData) => + it('all test items support debug tags', () => { + [wsRoot, folder, doc, testItem].forEach((itemData) => expect(itemData.item.tags.find((t) => t.id === 'debug')).toBeTruthy() ); - [wsRoot, folder].forEach((itemData) => - expect(itemData.item.tags.find((t) => t.id === 'debug')).toBeUndefined() - ); }); }); describe('getDebugInfo', () => { - let doc, test; + let doc, test, parentItem; beforeEach(() => { const uri: any = { fsPath: 'whatever' }; - const parentItem: any = controllerMock.createTestItem('ws-1', 'ws-1', uri); + parentItem = controllerMock.createTestItem('ws-1', 'ws-1', uri); doc = new TestDocumentRoot(context, uri, parentItem); const node: any = { fullName: 'a test', attrs: {}, data: {} }; test = new TestData(context, uri, node, doc.item); }); it('TestData returns file and test info', () => { const debugInfo = test.getDebugInfo(); - expect(debugInfo.fileName).toEqual(test.item.uri.fsPath); - expect(debugInfo.testNamePattern).toEqual({ value: 'a test', exactMatch: true }); + expect(debugInfo.testPath).toEqual(test.item.uri.fsPath); + expect(debugInfo.testName).toEqual({ value: 'a test', exactMatch: true }); + expect(debugInfo.useTestPathPattern).toBeFalsy(); }); it('TestDocumentRoot returns only file info', () => { const debugInfo = doc.getDebugInfo(); - expect(debugInfo.fileName).toEqual(doc.item.uri.fsPath); + expect(debugInfo.testPath).toEqual(doc.item.uri.fsPath); expect(debugInfo.testNamePattern).toBeUndefined(); + expect(debugInfo.useTestPathPattern).toBeFalsy(); + }); + it('FolderData returns folder path info', () => { + const folder = new FolderData(context, 'folder', parentItem); + const debugInfo = folder.getDebugInfo(); + expect(debugInfo.testPath).toEqual(folder.item.uri.fsPath); + expect(debugInfo.testName).toBeUndefined(); + expect(debugInfo.useTestPathPattern).toBeTruthy(); + }); + it('workspaceRoot returns workspace path info', () => { + const root = new WorkspaceRoot(context); + const debugInfo = root.getDebugInfo(); + expect(debugInfo.testPath).toEqual(root.item.uri.fsPath); + expect(debugInfo.testName).toBeUndefined(); + expect(debugInfo.useTestPathPattern).toBeTruthy(); }); }); describe('WorkspaceRoot', () => { diff --git a/tests/test-provider/test-provider.test.ts b/tests/test-provider/test-provider.test.ts index f8cd28f0c..4b8b15b18 100644 --- a/tests/test-provider/test-provider.test.ts +++ b/tests/test-provider/test-provider.test.ts @@ -286,15 +286,15 @@ describe('JestTestProvider', () => { debugDone = () => resolve(); }); it.each` - case | debugInfo | testNamePattern | debugTests | hasError - ${1} | ${undefined} | ${undefined} | ${() => Promise.resolve()} | ${true} - ${2} | ${{ fileName: 'file' }} | ${'a test'} | ${() => Promise.resolve()} | ${false} - ${3} | ${{ fileName: 'file' }} | ${'a test'} | ${() => Promise.reject('error')} | ${true} - ${4} | ${{ fileName: 'file' }} | ${'a test'} | ${throwError} | ${true} - ${5} | ${{ fileName: 'file' }} | ${undefined} | ${() => Promise.resolve()} | ${false} + case | debugInfo | testName | debugTests | hasError + ${1} | ${undefined} | ${undefined} | ${() => Promise.resolve()} | ${true} + ${2} | ${{ testPath: 'file' }} | ${'a test'} | ${() => Promise.resolve()} | ${false} + ${3} | ${{ testPath: 'file' }} | ${'a test'} | ${() => Promise.reject('error')} | ${true} + ${4} | ${{ testPath: 'file' }} | ${'a test'} | ${throwError} | ${true} + ${5} | ${{ testPath: 'file' }} | ${undefined} | ${() => Promise.resolve()} | ${false} `( 'invoke debug test async case $case => error? $hasError', - async ({ debugInfo, testNamePattern, debugTests, hasError }) => { + async ({ debugInfo, testName, debugTests, hasError }) => { expect.hasAssertions(); extExplorerContextMock.debugTests = jest.fn().mockImplementation(() => { if (debugTests) { @@ -308,9 +308,7 @@ describe('JestTestProvider', () => { if (debugInfo) { d.getDebugInfo = jest .fn() - .mockImplementation(() => - testNamePattern ? { ...debugInfo, testNamePattern } : debugInfo - ); + .mockImplementation(() => (testName ? { ...debugInfo, testName } : debugInfo)); } else { d.getDebugInfo = undefined; } @@ -330,13 +328,12 @@ describe('JestTestProvider', () => { expect(jestRun.errored).toHaveBeenCalledWith(itemDataList[0].item, expect.anything()); expect(vscode.TestMessage).toHaveBeenCalledTimes(1); } else { - if (testNamePattern) { + if (testName) { expect(extExplorerContextMock.debugTests).toHaveBeenCalledWith( - 'file', - testNamePattern + expect.objectContaining({ testPath: 'file', testName }) ); } else { - expect(extExplorerContextMock.debugTests).toHaveBeenCalledWith('file'); + expect(extExplorerContextMock.debugTests).toHaveBeenCalledWith({ testPath: 'file' }); } } } From 1f3e9634f33a79bde6d03559c491e2d40989751b Mon Sep 17 00:00:00 2001 From: connectdotz Date: Wed, 11 Sep 2024 16:06:07 -0400 Subject: [PATCH 2/2] adding tests --- src/DebugConfigurationProvider.ts | 5 +- tests/DebugConfigurationProvider.test.ts | 71 ++++++++++++++++++++++-- 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/src/DebugConfigurationProvider.ts b/src/DebugConfigurationProvider.ts index 7aa502e6d..96beef8f9 100755 --- a/src/DebugConfigurationProvider.ts +++ b/src/DebugConfigurationProvider.ts @@ -19,6 +19,7 @@ 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 @@ -130,8 +131,8 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv 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 (arg.includes('--runTestsByPath')) { - return arg.replace('--runTestsByPath', this.getTestPathPatternOption()); + if (replaceTestPathPatternRegex.test(arg)) { + return arg.replace(replaceTestPathPatternRegex, this.getTestPathPatternOption()); } if (testFileRegex.test(arg)) { return arg.replace(testFileRegex, escapeRegExp(debugInfo.testPath)); diff --git a/tests/DebugConfigurationProvider.test.ts b/tests/DebugConfigurationProvider.test.ts index cac70ef58..696911eb9 100755 --- a/tests/DebugConfigurationProvider.test.ts +++ b/tests/DebugConfigurationProvider.test.ts @@ -22,6 +22,7 @@ describe('DebugConfigurationProvider', () => { beforeEach(() => { (toFilePath as unknown as jest.Mock<{}>).mockImplementation((s) => s); (escapeRegExp as unknown as jest.Mock<{}>).mockImplementation((s) => s); + (escapeQuotes as unknown as jest.Mock<{}>).mockImplementation((s) => s); }); it('should by default return a DebugConfiguration for Jest', () => { @@ -77,6 +78,7 @@ describe('DebugConfigurationProvider', () => { it.each` debugConfigArgs | expectedArgs + ${undefined} | ${['--testNamePattern', testName, '--runTestsByPath', testPath]} ${[]} | ${['--testNamePattern', testName, '--runTestsByPath', testPath]} ${['--runInBand']} | ${['--runInBand', '--testNamePattern', testName, '--runTestsByPath', testPath]} `( @@ -133,10 +135,6 @@ describe('DebugConfigurationProvider', () => { expect(configuration.args).toEqual(expected); }); it('will translate multiple variables in a single arg', () => { - (toFilePath as unknown as jest.Mock<{}>).mockImplementation((s) => s); - (escapeRegExp as unknown as jest.Mock<{}>).mockImplementation((s) => s); - (escapeQuotes as unknown as jest.Mock<{}>).mockImplementation((s) => s); - let configuration: any = { name: 'vscode-jest-tests.v2', args: ['--testNamePattern "${jest.testNamePattern}" --runTestsByPath "${jest.testFile}"'], @@ -421,4 +419,69 @@ describe('DebugConfigurationProvider', () => { }); }); }); + describe('debug with useTestPathPattern = true', () => { + let debugInfo; + const testPath = '/a/__tests__'; + const testName = 'a test'; + const createDebugConfig = (type: 'v1' | 'v2' | 'v2-custom') => { + switch (type) { + case 'v1': + return { name: 'vscode-jest-tests', args: [] }; + case 'v2': + return { + name: 'vscode-jest-tests.v2', + args: [ + '--runInBand', + '--watchAll=false', + '--testNamePattern', + '${jest.testNamePattern}', + '--runTestsByPath', + '${jest.testFile}', + ], + }; + case 'v2-custom': + return { + name: 'vscode-jest-tests.v2', + args: [ + '--runInBand', + '--watchAll=false', + '--testNamePattern', + '${jest.testNamePattern}', + '--testPathPattern', + '${jest.testFile}', + ], + }; + } + }; + beforeEach(() => { + debugInfo = { testPath, testName, useTestPathPattern: true }; + }); + + describe('should update debug config args with testPathPattern(s) accordingly', () => { + it.each` + configType | useJest30 + ${'v1'} | ${true} + ${'v1'} | ${false} + ${'v1'} | ${undefined} + ${'v2'} | ${true} + ${'v2'} | ${false} + ${'v2'} | ${undefined} + ${'v2-custom'} | ${true} + ${'v2-custom'} | ${false} + ${'v2-custom'} | ${undefined} + `('for $configType, useJest30=$useJest30', ({ configType, useJest30 }) => { + let debugConfig: any = createDebugConfig(configType); + + const sut = new DebugConfigurationProvider(); + const ws = makeWorkspaceFolder('whatever'); + sut.prepareTestRun(debugInfo, ws, useJest30); + + debugConfig = sut.resolveDebugConfiguration(undefined, debugConfig); + + const testPathPatternOption = useJest30 ? '--testPathPatterns' : '--testPathPattern'; + expect(debugConfig.args).toEqual(expect.arrayContaining([testPathPatternOption, testPath])); + expect(debugConfig.args).not.toContain('--runTestsByPath'); + }); + }); + }); });