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

fix TestNamePattern issue #1091

Merged
merged 1 commit into from
Nov 3, 2023
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
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
Loading