From 3afe373019cf5552284d5969fa7ac2663c18c3d7 Mon Sep 17 00:00:00 2001 From: connectdotz Date: Fri, 3 Nov 2023 18:04:15 -0400 Subject: [PATCH] use exact regex for TestNamePattern if the item is a test block. --- src/JestExt/core.ts | 57 +------- src/JestExt/types.ts | 3 +- src/JestProcessManagement/types.ts | 5 +- src/TestResults/snapshot-provider.ts | 2 +- src/helpers.ts | 20 ++- src/test-provider/test-item-data.ts | 51 ++++---- src/test-provider/types.ts | 3 +- src/types.ts | 8 ++ tests/JestExt/core.test.ts | 143 ++------------------- tests/helpers.test.ts | 14 +- tests/test-provider/test-item-data.test.ts | 103 ++++++++++----- 11 files changed, 149 insertions(+), 260 deletions(-) diff --git a/src/JestExt/core.ts b/src/JestExt/core.ts index 7a2719a2..6cfa2e0b 100644 --- a/src/JestExt/core.ts +++ b/src/JestExt/core.ts @@ -8,29 +8,17 @@ import { SortedTestResults, TestResultProviderOptions, } from '../TestResults'; -import { - testIdString, - IdStringType, - escapeRegExp, - emptyTestStats, - getValidJestCommand, -} from '../helpers'; +import { escapeRegExp, emptyTestStats, getValidJestCommand } from '../helpers'; import { CoverageMapProvider, CoverageCodeLensProvider } from '../Coverage'; import { updateDiagnostics, updateCurrentDiagnostics, resetDiagnostics } from '../diagnostics'; import { DebugConfigurationProvider } from '../DebugConfigurationProvider'; -import { TestExplorerRunRequest, TestStats } from '../types'; +import { TestExplorerRunRequest, TestNamePattern, TestStats } from '../types'; import { CoverageOverlay } from '../Coverage/CoverageOverlay'; import { resultsWithoutAnsiEscapeSequence } from '../TestResults/TestResult'; import { CoverageMapData } from 'istanbul-lib-coverage'; import { Logging } from '../logging'; import { createProcessSession, ProcessSession } from './process-session'; -import { - JestExtContext, - JestSessionEvents, - JestExtSessionContext, - JestRunEvent, - DebugTestIdentifier, -} from './types'; +import { JestExtContext, JestSessionEvents, JestExtSessionContext, JestRunEvent } from './types'; import { extensionName, SupportedLanguageIds } from '../appGlobals'; import { createJestExtContext, getExtensionResourceSettings, prefixWorkspace } from './helper'; import { PluginResourceSettings } from '../Settings'; @@ -45,10 +33,6 @@ import { QuickFixActionType } from '../quick-fix'; import { executableTerminalLinkProvider } from '../terminal-link-provider'; import { outputManager } from '../output-manager'; -interface RunTestPickItem extends vscode.QuickPickItem { - id: DebugTestIdentifier; -} - interface JestCommandSettings { rootPath: string; jestCommandLine: string; @@ -562,10 +546,8 @@ export class JestExt { //** commands */ public debugTests = async ( document: vscode.TextDocument | string, - ...ids: DebugTestIdentifier[] + testNamePattern?: TestNamePattern ): Promise => { - const idString = (type: IdStringType, id: DebugTestIdentifier): string => - typeof id === 'string' ? id : testIdString(type, id); const getDebugConfig = ( folder?: vscode.WorkspaceFolder ): vscode.DebugConfiguration | undefined => { @@ -586,39 +568,10 @@ export class JestExt { } } }; - const selectTest = async ( - testIdentifiers: DebugTestIdentifier[] - ): Promise => { - const items: RunTestPickItem[] = testIdentifiers.map((id) => ({ - label: idString('display-reverse', id), - id, - })); - const selected = await vscode.window.showQuickPick(items, { - placeHolder: 'Select a test to debug', - }); - - return selected?.id; - }; - let testId: DebugTestIdentifier | undefined; - switch (ids.length) { - case 0: - //no testId, will run all tests in the file - break; - case 1: - testId = ids[0]; - break; - default: - testId = await selectTest(ids); - // if nothing is selected, abort - if (!testId) { - return; - } - break; - } this.debugConfigurationProvider.prepareTestRun( typeof document === 'string' ? document : document.fileName, - testId ? escapeRegExp(idString('full-name', testId)) : '.*', + testNamePattern ? escapeRegExp(testNamePattern) : '.*', this.extContext.workspace ); diff --git a/src/JestExt/types.ts b/src/JestExt/types.ts index ae2f8599..cc72b232 100644 --- a/src/JestExt/types.ts +++ b/src/JestExt/types.ts @@ -7,6 +7,7 @@ import { ProcessSession } from './process-session'; import { JestProcessInfo } from '../JestProcessManagement'; import { JestOutputTerminal } from './output-terminal'; import { TestIdentifier } from '../TestResults'; +import { TestNamePattern } from '../types'; export enum WatchMode { None = 'none', @@ -56,5 +57,5 @@ export type JestExtProcessContext = Readonly; export type DebugTestIdentifier = string | TestIdentifier; export type DebugFunction = ( document: vscode.TextDocument | string, - ...ids: DebugTestIdentifier[] + testNamePattern?: TestNamePattern ) => Promise; diff --git a/src/JestProcessManagement/types.ts b/src/JestProcessManagement/types.ts index de803c5e..2cc404cb 100644 --- a/src/JestProcessManagement/types.ts +++ b/src/JestProcessManagement/types.ts @@ -3,6 +3,7 @@ import { RunnerEvent } from 'jest-editor-support'; import { JestTestProcessType } from '../Settings'; import { JestProcess } from './JestProcess'; import { JestTestRun } from '../test-provider/jest-test-run'; +import { TestNamePattern } from '../types'; export interface JestProcessListener { onEvent: (process: JestProcess, event: RunnerEvent, ...args: unknown[]) => unknown; @@ -71,7 +72,7 @@ export type JestProcessRequestSimple = | { type: Extract; testFileName: string; - testNamePattern: string; + testNamePattern: TestNamePattern; updateSnapshot?: boolean; } | { @@ -82,7 +83,7 @@ export type JestProcessRequestSimple = | { type: Extract; testFileNamePattern: string; - testNamePattern: string; + testNamePattern: TestNamePattern; updateSnapshot?: boolean; } | { diff --git a/src/TestResults/snapshot-provider.ts b/src/TestResults/snapshot-provider.ts index 348c39ee..dffab9e4 100644 --- a/src/TestResults/snapshot-provider.ts +++ b/src/TestResults/snapshot-provider.ts @@ -50,7 +50,7 @@ export class SnapshotProvider { public async previewSnapshot(testPath: string, testFullName: string): Promise { const content = await this.snapshotSupport.getSnapshotContent( testPath, - new RegExp(`^${escapeRegExp(testFullName)} [0-9]+$`) + new RegExp(`^${escapeRegExp({ value: testFullName, exactMatch: false })} [0-9]+$`) ); const noSnapshotFound = (): void => { vscode.window.showErrorMessage('no snapshot is found, please run test to generate first'); diff --git a/src/helpers.ts b/src/helpers.ts index 2a2de025..6efac5c1 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -5,7 +5,7 @@ import { join, resolve, normalize, isAbsolute } from 'path'; import { ExtensionContext } from 'vscode'; import { TestIdentifier } from './TestResults'; -import { TestStats } from './types'; +import { StringPattern, TestStats } from './types'; import { LoginShell } from 'jest-editor-support'; import { WorkspaceManager } from './workspace-manager'; @@ -125,10 +125,22 @@ export const getDefaultJestCommand = (rootPath = ''): string | undefined => { }; /** - * Taken From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions + * Escapes special characters in a string to be used as a regular expression pattern. + * @param str - The string to escape. + * @returns The escaped string. + * + * Note: the conversion algorithm is taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions */ -export function escapeRegExp(str: string): string { - return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +export function escapeRegExp(str: string | StringPattern): string { + const sp: StringPattern = typeof str === 'string' ? { value: str } : str; + if (sp.isRegExp) { + return sp.value; + } + const escaped = sp.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string + if (sp.exactMatch) { + return escaped + '$'; + } + return escaped; } /** diff --git a/src/test-provider/test-item-data.ts b/src/test-provider/test-item-data.ts index 5a65b1e3..ec8d58ad 100644 --- a/src/test-provider/test-item-data.ts +++ b/src/test-provider/test-item-data.ts @@ -13,12 +13,12 @@ import { JestTestProviderContext } from './test-provider-context'; import { JestTestRun } from './jest-test-run'; import { JestProcessInfo, JestProcessRequest } from '../JestProcessManagement'; import { GENERIC_ERROR, LONG_RUNNING_TESTS, getExitErrorDef } from '../errors'; -import { JestExtOutput } from '../JestExt/output-terminal'; import { tiContextManager } from './test-item-context-manager'; import { toAbsoluteRootPath } from '../helpers'; import { runModeDescription } from '../JestExt/run-mode'; import { isVirtualWorkspaceFolder } from '../virtual-workspace-folder'; import { outputManager } from '../output-manager'; +import { TestNamePattern } from '../types'; interface JestRunnable { getJestRunRequest: () => JestExtRequestType; @@ -370,6 +370,7 @@ export class WorkspaceRoot extends TestItemDataBase { /** return a valid run from event. if createIfMissing is true, then create a new one if none exist in the event **/ private getJestRun(event: TypedRunEvent, createIfMissing: true): JestTestRun; private getJestRun(event: TypedRunEvent, createIfMissing?: false): JestTestRun | undefined; + // istanbul ignore next private getJestRun(event: TypedRunEvent, createIfMissing = false): JestTestRun | undefined { if (event.process.userData?.run) { return event.process.userData.run; @@ -393,9 +394,6 @@ export class WorkspaceRoot extends TestItemDataBase { 'new-line', ]); } - private writer(run?: JestTestRun): JestExtOutput { - return run ?? this.context.output; - } private onRunEvent = (event: JestRunEvent) => { if (event.process.request.type === 'not-test') { return; @@ -412,7 +410,7 @@ export class WorkspaceRoot extends TestItemDataBase { const text = event.raw ?? event.text; if (text && text.length > 0) { const opt = event.isError ? 'error' : event.newLine ? 'new-line' : undefined; - this.writer(run).write(text, opt); + run.write(text, opt); } break; } @@ -424,11 +422,11 @@ export class WorkspaceRoot extends TestItemDataBase { } case 'end': { if (event.error && !event.process.userData?.execError) { - this.writer(run).write(event.error, 'error'); + run.write(event.error, 'error'); event.process.userData = { ...(event.process.userData ?? {}), execError: true }; } this.runLog('finished'); - run?.end({ pid: event.process.id, delay: 30000, reason: 'process end' }); + run.end({ pid: event.process.id, delay: 30000, reason: 'process end' }); break; } case 'exit': { @@ -448,7 +446,7 @@ export class WorkspaceRoot extends TestItemDataBase { break; } case 'long-run': { - this.writer(run).write( + run.write( `Long Running Tests Warning: Tests exceeds ${event.threshold}ms threshold. Please reference Troubleshooting if this is not expected`, LONG_RUNNING_TESTS ); @@ -505,13 +503,10 @@ const isAssertDataNode = (arg: ItemNodeType): arg is DataNode { +const isContainerEmpty = (node?: ContainerNode): boolean => { if (!node) { return true; } - if (isDataNode(node)) { - return false; - } if ( (node.childData && node.childData.length > 0) || (node.childContainers && node.childContainers.length > 0) @@ -570,21 +565,12 @@ abstract class TestResultData extends TestItemDataBase { return parts.join('#'); } - isSameId(id1: string, id2: string): boolean { - if (id1 === id2) { - return true; - } - // truncate the last "extra-id" added for duplicate test names before comparing - const truncateExtra = (id: string): string => id.replace(/(.*)(#[0-9]+$)/, '$1'); - return truncateExtra(id1) === truncateExtra(id2); - } - + /** + * Synchronizes the child nodes of the test item with the given ItemNodeType, recursively. + * @param node - The ItemNodeType to synchronize the child nodes with. + * @returns void + */ syncChildNodes(node: ItemNodeType): void { - const testId = this.makeTestId(this.uri, node); - if (!this.isSameId(testId, this.item.id)) { - this.item.error = 'invalid node'; - return; - } this.item.error = undefined; if (!isDataNode(node)) { @@ -684,7 +670,7 @@ export class TestDocumentRoot extends TestResultData { // test file has syntax error or failed to run for whatever reason. // In this case we should mark the suite itself as TestExplorer won't be able to // aggregate from the children list - if (isEmpty(suiteResult?.assertionContainer)) { + if (isContainerEmpty(suiteResult?.assertionContainer)) { this.updateItemState(run, suiteResult); } this.forEachChild((child) => child.updateResultState(run)); @@ -736,17 +722,24 @@ export class TestData extends TestResultData implements Debuggable { return item; } + private getTestNamePattern(): TestNamePattern { + if (isDataNode(this.node)) { + return { value: this.node.fullName, exactMatch: true }; + } + return { value: this.node.fullName, exactMatch: false }; + } + getJestRunRequest(itemCommand?: ItemCommand): JestExtRequestType { return { type: 'by-file-test-pattern', updateSnapshot: itemCommand === ItemCommand.updateSnapshot, testFileNamePattern: this.uri.fsPath, - testNamePattern: this.node.fullName, + testNamePattern: this.getTestNamePattern(), }; } getDebugInfo(): ReturnType { - return { fileName: this.uri.fsPath, testNamePattern: this.node.fullName }; + return { fileName: this.uri.fsPath, testNamePattern: this.getTestNamePattern() }; } private updateItemRange(): void { if (this.node.attrs.range) { diff --git a/src/test-provider/types.ts b/src/test-provider/types.ts index c23dbe95..6856291b 100644 --- a/src/test-provider/types.ts +++ b/src/test-provider/types.ts @@ -4,6 +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'; export type TestItemDataType = WorkspaceRoot | FolderData | TestDocumentRoot | TestData; @@ -24,7 +25,7 @@ export interface TestItemData { } export interface Debuggable { - getDebugInfo: () => { fileName: string; testNamePattern?: string }; + getDebugInfo: () => { fileName: string; testNamePattern?: TestNamePattern }; } export enum TestTagId { diff --git a/src/types.ts b/src/types.ts index 70a6504f..362c633f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -15,3 +15,11 @@ export interface TestExplorerRunRequest { request: vscode.TestRunRequest; token: vscode.CancellationToken; } + +export interface StringPattern { + value: string; + exactMatch?: boolean; + isRegExp?: boolean; +} + +export type TestNamePattern = StringPattern | string; diff --git a/tests/JestExt/core.test.ts b/tests/JestExt/core.test.ts index ea771d72..4c066627 100644 --- a/tests/JestExt/core.test.ts +++ b/tests/JestExt/core.test.ts @@ -41,7 +41,7 @@ import { createProcessSession } from '../../src/JestExt/process-session'; import { updateCurrentDiagnostics, updateDiagnostics } from '../../src/diagnostics'; import { CoverageMapProvider } from '../../src/Coverage'; import * as helper from '../../src/helpers'; -import { TestIdentifier, resultsWithLowerCaseWindowsDriveLetters } from '../../src/TestResults'; +import { resultsWithLowerCaseWindowsDriveLetters } from '../../src/TestResults'; import * as messaging from '../../src/messaging'; import { PluginResourceSettings } from '../../src/Settings'; import * as extHelper from '../../src/JestExt/helper'; @@ -164,10 +164,6 @@ describe('JestExt', () => { }); describe('debugTests()', () => { - const makeIdentifier = (title: string, ancestors?: string[]): TestIdentifier => ({ - title, - ancestorTitles: ancestors || [], - }); const fileName = 'fileName'; const document: any = { fileName }; let sut: JestExt; @@ -314,140 +310,19 @@ describe('JestExt', () => { testNamePattern, workspaceFolder ); + expect(mockHelpers.escapeRegExp).toHaveBeenCalledWith(testNamePattern); }); }); - it('can handle testIdentifier argument', async () => { - const tId = makeIdentifier('test-1', ['d-1', 'd-1-1']); - const fullName = 'd-1 d-1-1 test-1'; - mockHelpers.testIdString.mockReturnValue(fullName); - await sut.debugTests(document, tId); - expect(vscode.debug.startDebugging).toHaveBeenCalledWith( - workspaceFolder, - debugConfiguration2 - ); - const configuration = startDebugging.mock.calls[startDebugging.mock.calls.length - 1][1]; - expect(configuration).toBeDefined(); - // test identifier is cleaned up before debug - expect(mockHelpers.testIdString).toHaveBeenCalledWith('full-name', tId); + 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( - fileName, - fullName, + document.fileName, + '.*', workspaceFolder ); - }); - it.each` - desc | testIds | testIdStringCount | startDebug - ${'no id'} | ${undefined} | ${0} | ${true} - ${'empty id'} | ${[]} | ${0} | ${true} - ${'1 string id '} | ${['test-1']} | ${0} | ${true} - ${'1 testIdentifier id '} | ${[makeIdentifier('test-1', ['d-1'])]} | ${1} | ${true} - `('no selection needed: $desc', async ({ testIds, testIdStringCount, startDebug }) => { - if (testIds) { - await sut.debugTests(document, ...testIds); - } else { - await sut.debugTests(document); - } - expect(mockShowQuickPick).not.toHaveBeenCalled(); - expect(mockHelpers.testIdString).toHaveBeenCalledTimes(testIdStringCount); - if (testIdStringCount >= 1) { - expect(mockHelpers.testIdString).toHaveBeenLastCalledWith('full-name', testIds[0]); - expect(mockHelpers.escapeRegExp).toHaveBeenCalled(); - } - if (startDebug) { - expect(vscode.debug.startDebugging).toHaveBeenCalledWith( - workspaceFolder, - debugConfiguration2 - ); - const configuration = startDebugging.mock.calls[startDebugging.mock.calls.length - 1][1]; - expect(configuration).toBeDefined(); - if (testIds?.length === 1) { - expect(sut.debugConfigurationProvider.prepareTestRun).toHaveBeenCalledWith( - document.fileName, - testIds[0], - workspaceFolder - ); - } else { - expect(sut.debugConfigurationProvider.prepareTestRun).toHaveBeenCalledWith( - document.fileName, - '.*', - workspaceFolder - ); - } - expect(sut.debugConfigurationProvider.prepareTestRun).toHaveBeenCalled(); - } else { - expect(sut.debugConfigurationProvider.prepareTestRun).not.toHaveBeenCalled(); - expect(vscode.debug.startDebugging).not.toHaveBeenCalled(); - } - }); - describe('parameterized test', () => { - describe.each` - desc | tId1 | tId2 | tId3 | selectIdx - ${'testIdentifiers'} | ${makeIdentifier('test-1', ['d-1'])} | ${makeIdentifier('test-2', ['d-1'])} | ${makeIdentifier('test-3', ['d-1'])} | ${0} - ${'string ids'} | ${'d-1 test-1'} | ${'d-1 test-2'} | ${'d-1 test-3'} | ${2} - ${'mixed ids'} | ${'d-1 test-1'} | ${makeIdentifier('test-2', ['d-1'])} | ${'d-1 test-3'} | ${1} - `('with $desc', ({ tId1, tId2, tId3, selectIdx }) => { - let identifierIdCount = 0; - beforeEach(() => { - mockShowQuickPick.mockImplementation((items) => Promise.resolve(items[selectIdx])); - identifierIdCount = [tId1, tId2, tId3].filter((id) => typeof id !== 'string').length; - }); - it('can run selected test', async () => { - // user choose the 2nd test: tId2 - await sut.debugTests(document, tId1, tId2, tId3); - // user has made selection to choose from 3 candidates - expect(mockShowQuickPick).toHaveBeenCalledTimes(1); - const [items] = mockShowQuickPick.mock.calls[0]; - expect(items).toHaveLength(3); - const hasIds = () => { - // id string is called 4 times: 3 to construct the quickPickItems, the last one is for jest test fullName - expect(mockHelpers.testIdString).toHaveBeenCalledTimes(identifierIdCount + 1); - const calls = mockHelpers.testIdString.mock.calls; - expect( - calls.slice(0, identifierIdCount).every((c) => c[0] === 'display-reverse') - ).toBeTruthy(); - expect(calls[calls.length - 1][0]).toEqual('full-name'); - }; - const hasNoId = () => { - expect(mockHelpers.testIdString).toHaveBeenCalledTimes(0); - }; - if (identifierIdCount) { - hasIds(); - } else { - hasNoId(); - } - const selected = [tId1, tId2, tId3][selectIdx]; - expect(mockHelpers.escapeRegExp).toHaveBeenCalledWith(selected); - // verify the actual test to be run is the one we selected: tId2 - expect(vscode.debug.startDebugging).toHaveBeenCalledWith( - workspaceFolder, - debugConfiguration2 - ); - const configuration = startDebugging.mock.calls[startDebugging.mock.calls.length - 1][1]; - expect(configuration).toBeDefined(); - expect(sut.debugConfigurationProvider.prepareTestRun).toHaveBeenCalledWith( - fileName, - selected, - workspaceFolder - ); - }); - it('if user did not choose any test, no debug will be run', async () => { - selectIdx = -1; - await sut.debugTests(document, tId1, tId2, tId3); - expect(mockShowQuickPick).toHaveBeenCalledTimes(1); - expect(vscode.debug.startDebugging).not.toHaveBeenCalled(); - }); - it('if pass zero testId, 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(); - }); - }); + expect(vscode.debug.startDebugging).toHaveBeenCalled(); }); }); diff --git a/tests/helpers.test.ts b/tests/helpers.test.ts index e4327897..6e3af5c3 100644 --- a/tests/helpers.test.ts +++ b/tests/helpers.test.ts @@ -148,11 +148,15 @@ describe('ModuleHelpers', () => { describe('escapeRegExp', () => { it.each` - str | expected - ${'no special char'} | ${'no special char'} - ${'with (a)'} | ${'with \\(a\\)'} - ${'with {} and $sign'} | ${'with \\{\\} and \\$sign'} - ${'with []'} | ${'with \\[\\]'} + str | expected + ${'no special char'} | ${'no special char'} + ${'with (a)'} | ${'with \\(a\\)'} + ${'with {} and $sign'} | ${'with \\{\\} and \\$sign'} + ${'with []'} | ${'with \\[\\]'} + ${{ value: 'with []', exactMatch: true }} | ${'with \\[\\]$'} + ${{ value: 'with []', exactMatch: false }} | ${'with \\[\\]'} + ${{ value: 'with []', isRegExp: true }} | ${'with []'} + ${{ value: 'with []', isRegExp: false }} | ${'with \\[\\]'} `('escapeRegExp: $str', ({ str, expected }) => { expect(escapeRegExp(str)).toEqual(expected); }); diff --git a/tests/test-provider/test-item-data.test.ts b/tests/test-provider/test-item-data.test.ts index 257acf62..7d3e2855 100644 --- a/tests/test-provider/test-item-data.test.ts +++ b/tests/test-provider/test-item-data.test.ts @@ -732,20 +732,38 @@ describe('test-item-data', () => { expect.anything() ); }); - it('TestData runs the specific test pattern', () => { - const uri: any = { fsPath: '/ws-1/a.test.ts' }; - const node: any = { fullName: 'a test', attrs: {}, data: {} }; - const parent: any = controllerMock.createTestItem('ws-1', 'ws-1', uri); - const tData = new TestData(context, uri, node, parent); - const jestRun = createTestRun(); - tData.scheduleTest(jestRun); - expect(context.ext.session.scheduleProcess).toHaveBeenCalledWith( - expect.objectContaining({ - type: 'by-file-test-pattern', - testFileNamePattern: uri.fsPath, - testNamePattern: 'a test', - }), - expect.anything() + describe('testNamePattern differ between describe and test', () => { + it.each` + isDescribeBlock | exactMatch + ${true} | ${false} + ${false} | ${true} + `( + 'isDescribeBlock=$isDescribeBlock, exactMatch=$exactMatch', + ({ isDescribeBlock, exactMatch }) => { + const uri: any = { fsPath: '/ws-1/a.test.ts' }; + + const node: any = isDescribeBlock + ? { + fullName: 'a test', + attrs: {}, + childContainers: [], + childData: [], + } + : { fullName: 'a test', attrs: {}, data: {} }; + + const parent: any = controllerMock.createTestItem('ws-1', 'ws-1', uri); + const tData = new TestData(context, uri, node, parent); + const jestRun = createTestRun(); + tData.scheduleTest(jestRun); + expect(context.ext.session.scheduleProcess).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'by-file-test-pattern', + testFileNamePattern: uri.fsPath, + testNamePattern: { value: 'a test', exactMatch }, + }), + expect.anything() + ); + } ); }); }); @@ -767,23 +785,37 @@ describe('test-item-data', () => { expect(process.userData.run).toBe(jestRun); expect(process.userData.testItem).toBe(folderData.item); }); - it('if test name is not resolved, it will execute the resolved parent test block', () => { - const { doc } = createAllTestItems(); - const descNode: any = { - fullName: 'a $describe', - attrs: { nonLiteralName: true }, - data: {}, - }; - const testNode: any = { fullName: 'a test', attrs: { isGroup: 'yes' }, data: {} }; - const descItem = new TestData(context, doc.uri, descNode, doc.item); - const testItem = new TestData(context, doc.uri, testNode, descItem.item); - const jestRun = createTestRun(); + describe('if test name is not resolved', () => { + it('if there is a parent block => will execute it instead', () => { + const { doc } = createAllTestItems(); + const descNode: any = { + fullName: 'a $describe', + attrs: { nonLiteralName: true }, + data: {}, + }; + const testNode: any = { fullName: 'a test', attrs: { isGroup: 'yes' }, data: {} }; + const descItem = new TestData(context, doc.uri, descNode, doc.item); + const testItem = new TestData(context, doc.uri, testNode, descItem.item); + const jestRun = createTestRun(); - testItem.scheduleTest(jestRun); - // const process: any = context.ext.session.scheduleProcess.mock.results[0].value; - expect(process.userData.run).toBe(jestRun); - expect(process.userData.testItem.id).toBe(doc.item.id); - // try + testItem.scheduleTest(jestRun); + expect(process.userData.run).toBe(jestRun); + expect(process.userData.testItem.id).toBe(doc.item.id); + }); + it('if failed to get parent block, will attempt to run the test anyway', () => { + const { doc } = createAllTestItems(); + + const testNode: any = { fullName: 'a $test', attrs: { nonLiteralName: true }, data: {} }; + const testItem = new TestData(context, doc.uri, testNode, doc.item); + const jestRun = createTestRun(); + + // simulate no parent block + context.getData = jest.fn().mockReturnValueOnce(undefined); + + testItem.scheduleTest(jestRun); + expect(process.userData.run).toBe(jestRun); + expect(process.userData.testItem.id).toBe(testItem.item.id); + }); }); describe('can update snapshot based on runProfile', () => { let wsRoot, folder, doc, testItem; @@ -1161,7 +1193,7 @@ describe('test-item-data', () => { it('TestData returns file and test info', () => { const debugInfo = test.getDebugInfo(); expect(debugInfo.fileName).toEqual(test.item.uri.fsPath); - expect(debugInfo.testNamePattern).toEqual('a test'); + expect(debugInfo.testNamePattern).toEqual({ value: 'a test', exactMatch: true }); }); it('TestDocumentRoot returns only file info', () => { const debugInfo = doc.getDebugInfo(); @@ -1293,6 +1325,15 @@ describe('test-item-data', () => { // end will be called again expect(process.userData.run.end).toHaveBeenCalledTimes(2); }); + it('if process has no testItem, will not do anything', () => { + env.scheduleItem(itemType); + mockedJestTestRun.mockClear(); + + process.userData.run = createTestRun(); + process.userData.testItem = undefined; + env.onRunEvent({ type: 'start', process }); + expect(process.userData.run.started).not.toHaveBeenCalled(); + }); }); }); describe('extension-managed runs', () => {