Skip to content

Commit

Permalink
use exact regex for TestNamePattern if the item is a test block. (#1091)
Browse files Browse the repository at this point in the history
  • Loading branch information
connectdotz authored Nov 3, 2023
1 parent 66c5d56 commit 5557e93
Show file tree
Hide file tree
Showing 11 changed files with 149 additions and 260 deletions.
57 changes: 5 additions & 52 deletions src/JestExt/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -562,10 +546,8 @@ export class JestExt {
//** commands */
public debugTests = async (
document: vscode.TextDocument | string,
...ids: DebugTestIdentifier[]
testNamePattern?: TestNamePattern
): Promise<void> => {
const idString = (type: IdStringType, id: DebugTestIdentifier): string =>
typeof id === 'string' ? id : testIdString(type, id);
const getDebugConfig = (
folder?: vscode.WorkspaceFolder
): vscode.DebugConfiguration | undefined => {
Expand All @@ -586,39 +568,10 @@ export class JestExt {
}
}
};
const selectTest = async (
testIdentifiers: DebugTestIdentifier[]
): Promise<DebugTestIdentifier | undefined> => {
const items: RunTestPickItem[] = testIdentifiers.map((id) => ({
label: idString('display-reverse', id),
id,
}));
const selected = await vscode.window.showQuickPick<RunTestPickItem>(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
);

Expand Down
3 changes: 2 additions & 1 deletion src/JestExt/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -56,5 +57,5 @@ export type JestExtProcessContext = Readonly<JestExtProcessContextRaw>;
export type DebugTestIdentifier = string | TestIdentifier;
export type DebugFunction = (
document: vscode.TextDocument | string,
...ids: DebugTestIdentifier[]
testNamePattern?: TestNamePattern
) => Promise<void>;
5 changes: 3 additions & 2 deletions src/JestProcessManagement/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -71,7 +72,7 @@ export type JestProcessRequestSimple =
| {
type: Extract<JestTestProcessType, 'by-file-test'>;
testFileName: string;
testNamePattern: string;
testNamePattern: TestNamePattern;
updateSnapshot?: boolean;
}
| {
Expand All @@ -82,7 +83,7 @@ export type JestProcessRequestSimple =
| {
type: Extract<JestTestProcessType, 'by-file-test-pattern'>;
testFileNamePattern: string;
testNamePattern: string;
testNamePattern: TestNamePattern;
updateSnapshot?: boolean;
}
| {
Expand Down
2 changes: 1 addition & 1 deletion src/TestResults/snapshot-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class SnapshotProvider {
public async previewSnapshot(testPath: string, testFullName: string): Promise<void> {
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');
Expand Down
20 changes: 16 additions & 4 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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;
}

/**
Expand Down
51 changes: 22 additions & 29 deletions src/test-provider/test-item-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
}
Expand All @@ -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': {
Expand All @@ -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
);
Expand Down Expand Up @@ -505,13 +503,10 @@ const isAssertDataNode = (arg: ItemNodeType): arg is DataNode<TestAssertionStatu
// eslint-disable-next-line @typescript-eslint/no-explicit-any
isDataNode(arg) && (arg.data as any).fullName;

const isEmpty = (node?: ItemNodeType): boolean => {
const isContainerEmpty = (node?: ContainerNode<TestAssertionStatus>): boolean => {
if (!node) {
return true;
}
if (isDataNode(node)) {
return false;
}
if (
(node.childData && node.childData.length > 0) ||
(node.childContainers && node.childContainers.length > 0)
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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<Debuggable['getDebugInfo']> {
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) {
Expand Down
3 changes: 2 additions & 1 deletion src/test-provider/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -24,7 +25,7 @@ export interface TestItemData {
}

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

export enum TestTagId {
Expand Down
8 changes: 8 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Loading

0 comments on commit 5557e93

Please sign in to comment.