Skip to content

Commit

Permalink
improve default command creation and messaging (jest-community#953)
Browse files Browse the repository at this point in the history
* fix test block not displaying sometimes

* enhance default jestCommandLine detection and validation

* adding test for changes

* refactor validation logic to use package.json instead of jest config

not all projects have custom jest config. So use package.json as the root instead.

* adding more messaging during auto config

* removing code no longer used
  • Loading branch information
connectdotz authored Nov 26, 2022
1 parent 85b026d commit c899e81
Show file tree
Hide file tree
Showing 12 changed files with 514 additions and 287 deletions.
172 changes: 156 additions & 16 deletions src/JestExt/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ import {
resultsWithLowerCaseWindowsDriveLetters,
SortedTestResults,
} from '../TestResults';
import { testIdString, IdStringType, escapeRegExp, emptyTestStats } from '../helpers';
import {
testIdString,
IdStringType,
escapeRegExp,
emptyTestStats,
getDefaultJestCommand,
} from '../helpers';
import { CoverageMapProvider, CoverageCodeLensProvider } from '../Coverage';
import { updateDiagnostics, updateCurrentDiagnostics, resetDiagnostics } from '../diagnostics';
import { DebugConfigurationProvider } from '../DebugConfigurationProvider';
Expand Down Expand Up @@ -35,13 +41,25 @@ import { JestProcessInfo } from '../JestProcessManagement';
import { addFolderToDisabledWorkspaceFolders } from '../extensionManager';
import { MessageAction } from '../messaging';
import { getExitErrorDef } from '../errors';
import { WorkspaceManager } from '../workspace-manager';
import { ansiEsc, JestOutputTerminal } from './output-terminal';

interface RunTestPickItem extends vscode.QuickPickItem {
id: DebugTestIdentifier;
}

type MessageActionType = 'help' | 'wizard' | 'disable-folder' | 'help-long-run';

type MessageActionType =
| 'help'
| 'wizard'
| 'disable-folder'
| 'help-long-run'
| 'setup-cmdline'
| 'setup-monorepo';

interface JestCommandSettings {
rootPath: string;
jestCommandLine: string;
}
/** extract lines starts and end with [] */
export class JestExt {
coverageMapProvider: CoverageMapProvider;
Expand All @@ -67,24 +85,29 @@ export class JestExt {
private testProvider?: JestTestProvider;
public events: JestSessionEvents;

private workspaceManager: WorkspaceManager;
private output: JestOutputTerminal;

constructor(
vscodeContext: vscode.ExtensionContext,
workspaceFolder: vscode.WorkspaceFolder,
debugConfigurationProvider: DebugConfigurationProvider,
coverageCodeLensProvider: CoverageCodeLensProvider
) {
this.vscodeContext = vscodeContext;
this.output = new JestOutputTerminal(workspaceFolder.name);
const pluginSettings = getExtensionResourceSettings(workspaceFolder.uri);

this.extContext = createJestExtContext(workspaceFolder, pluginSettings);
this.extContext = createJestExtContext(workspaceFolder, pluginSettings, this.output);
this.logging = this.extContext.loggingFactory.create('JestExt');
this.workspaceManager = new WorkspaceManager();

this.failDiagnostics = vscode.languages.createDiagnosticCollection(
`Jest (${workspaceFolder.name})`
);
this.coverageCodeLensProvider = coverageCodeLensProvider;

this.coverageMapProvider = new CoverageMapProvider();
this.vscodeContext = vscodeContext;
this.coverageOverlay = new CoverageOverlay(
vscodeContext,
this.coverageMapProvider,
Expand Down Expand Up @@ -133,7 +156,7 @@ export class JestExt {
private setupWizardAction(taskId?: WizardTaskId): messaging.MessageAction {
const command = `${extensionName}.setup-extension`;
return {
title: 'Run Setup Tool',
title: 'Fix',
action: (): unknown =>
vscode.commands.executeCommand(command, {
workspace: this.extContext.workspace,
Expand Down Expand Up @@ -167,9 +190,6 @@ export class JestExt {
if (event.process.request.type === 'not-test') {
return;
}
// console.log(
// `[core.onRunEvent] "${this.extContext.workspace.name}" event:${event.type} process:${event.process.id}`
// );
switch (event.type) {
case 'start':
this.updateStatusBar({ state: 'running' });
Expand All @@ -182,7 +202,7 @@ export class JestExt {
this.updateStatusBar({ state: 'stopped' });
messaging.systemErrorMessage(
prefixWorkspace(this.extContext, event.error),
...this.buildMessageActions(['help', 'wizard', 'disable-folder'])
...this.buildMessageActions(['wizard', 'disable-folder', 'help'])
);
} else {
this.updateStatusBar({ state: 'done' });
Expand All @@ -208,6 +228,12 @@ export class JestExt {
case 'wizard':
actions.push(this.setupWizardAction());
break;
case 'setup-cmdline':
actions.push(this.setupWizardAction('cmdLine'));
break;
case 'setup-monorepo':
actions.push(this.setupWizardAction('monorepo'));
break;
case 'disable-folder':
if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 1) {
actions.push(this.setupIgnoreAction());
Expand Down Expand Up @@ -240,6 +266,11 @@ export class JestExt {
*/
public async startSession(newSession = false): Promise<void> {
try {
const readyState = await this.validateJestCommandLine();
if (readyState !== 'pass') {
return;
}

this.dirtyFiles.clear();
this.resetStatusBar();

Expand All @@ -263,7 +294,7 @@ export class JestExt {
this.extContext.output.write('Failed to start jest session', 'error');
messaging.systemErrorMessage(
`${msg}...`,
...this.buildMessageActions(['help', 'wizard', 'disable-folder'])
...this.buildMessageActions(['wizard', 'disable-folder', 'help'])
);
}
}
Expand Down Expand Up @@ -346,7 +377,7 @@ export class JestExt {
updatedSettings.coverageColors
);

this.extContext = createJestExtContext(this.extContext.workspace, updatedSettings);
this.extContext = createJestExtContext(this.extContext.workspace, updatedSettings, this.output);

await this.startSession(true);
if (vscode.window.activeTextEditor) {
Expand Down Expand Up @@ -376,6 +407,115 @@ export class JestExt {
return true;
}

/**
* validate if there is a valid jest commandline. If the simple default command line is not valid,
* it will take on a more
*
* @returns
*/
public async validateJestCommandLine(): Promise<'pass' | 'fail' | 'restart'> {
const updateSettings = async (update: JestCommandSettings): Promise<'restart'> => {
this.extContext.settings.jestCommandLine = update.jestCommandLine;
this.extContext.settings.rootPath = update.rootPath;
await this.triggerUpdateSettings(this.extContext.settings);
return 'restart';
};

const outputSettings = (settings: JestCommandSettings) => {
this.extContext.output.write(
'found:\r\n' +
` ${ansiEsc('bold', 'rootPath')}: ${settings.rootPath}\r\n` +
` ${ansiEsc('bold', 'jestCommandLine')}: ${settings.jestCommandLine}\r\n`,
'new-line'
);
};

const t0 = Date.now();
if (this.extContext.settings.jestCommandLine) {
return Promise.resolve('pass');
}

this.extContext.output.write('auto config:', 'info');

let jestCommandLine = getDefaultJestCommand(this.extContext.settings.rootPath);
if (jestCommandLine) {
const settings = { rootPath: this.extContext.settings.rootPath, jestCommandLine };
outputSettings(settings);
return updateSettings(settings);
}

// see if we can get a valid command by examing the file system
const uris = await this.workspaceManager.getFoldersFromFilesystem(this.extContext.workspace);

const perf = Date.now() - t0;
/* istanbul ignore next */
if (perf > 2000) {
this.extContext.output.write(
`auto config took ${perf} msec. Might be more efficient to update settings directly`,
'warn'
);
}

const found: JestCommandSettings[] = [];
if (uris.length > 0) {
this.extContext.output.write(
'examining the following package roots:\r\n' +
` ${uris.map((uri) => uri.fsPath).join('\r\n ')}`,
'new-line'
);
for (const uri of uris) {
const rootPath = uri.fsPath;
if (rootPath === this.extContext.settings.rootPath) {
continue;
}
jestCommandLine = getDefaultJestCommand(rootPath);
if (jestCommandLine) {
const settings = { jestCommandLine, rootPath };
outputSettings(settings);
found.push(settings);

if (found.length > 1) {
this.extContext.output.write('Multiple candidates found, abort', 'warn');
break;
}
}
}
}

let msg = 'Not able to auto detect a valid jest command';
let actionType: MessageActionType = 'setup-cmdline';

switch (found.length) {
case 1:
return updateSettings(found[0]);
case 0: {
if (uris.length > 0) {
this.extContext.output.write(
'not able to find test script or jest/CRA binary in any of the package roots',
'warn'
);
} else {
this.extContext.output.write('no package.json file found', 'warn');
}
break;
}
default: {
msg = `${msg}: multiple candidates found`;
if (vscode.workspace.workspaceFolders?.length === 1) {
msg += ' Perhaps this is a multi-root monorepo?';
actionType = 'setup-monorepo';
}
break;
}
}

messaging.systemErrorMessage(
prefixWorkspace(this.extContext, msg),
...this.buildMessageActions([actionType, 'disable-folder', 'help'])
);
this.extContext.output.write(`Abort jest session: ${msg}`, 'error');
return 'fail';
}
public activate(): void {
if (
vscode.window.activeTextEditor?.document.uri &&
Expand Down Expand Up @@ -618,17 +758,17 @@ export class JestExt {
this.updateTestFileList();
}

toggleCoverageOverlay(): void {
toggleCoverageOverlay(): Promise<void> {
this.coverageOverlay.toggleVisibility();

// restart jest since coverage condition has changed
this.triggerUpdateSettings(this.extContext.settings);
return this.triggerUpdateSettings(this.extContext.settings);
}
toggleAutoRun(): void {
toggleAutoRun(): Promise<void> {
this.extContext.settings.autoRun.toggle();

// restart jest since coverage condition has changed
this.triggerUpdateSettings(this.extContext.settings);
return this.triggerUpdateSettings(this.extContext.settings);
}
runItemCommand(testItem: vscode.TestItem, itemCommand: ItemCommand): void {
this.testProvider?.runItemCommand(testItem, itemCommand);
Expand Down
41 changes: 15 additions & 26 deletions src/JestExt/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,18 @@ import {
TestExplorerConfigLegacy,
JestExtAutoRunSetting,
} from '../Settings';
import { pathToJest, pathToConfig, toFilePath } from '../helpers';
import { workspaceLogging } from '../logging';
import { JestExtContext, RunnerWorkspaceOptions } from './types';
import { CoverageColors } from '../Coverage';
import { userInfo } from 'os';
import { JestOutputTerminal } from './output-terminal';
import { AutoRun } from './auto-run';
import { RunShell } from './run-shell';
import { toFilePath } from '../helpers';

export const isWatchRequest = (request: JestProcessRequest): boolean =>
request.type === 'watch-tests' || request.type === 'watch-all-tests';

/**
* This method retrieve a jest command line, if available, otherwise fall back to the legacy
* settings for pathToJest and pathToConfig.
*
* @param settings
*/
//TODO remove pathToJest and pathToConfig once we fully deprecated them
const getJestCommandSettings = (settings: PluginResourceSettings): [string, string] => {
if (settings.jestCommandLine) {
return [settings.jestCommandLine, ''];
}
return [pathToJest(settings), pathToConfig(settings)];
};

const getUserIdString = (): string => {
try {
const user = userInfo();
Expand All @@ -60,16 +46,20 @@ export const outputFileSuffix = (ws: string, extra?: string): string => {
};
export const createJestExtContext = (
workspaceFolder: vscode.WorkspaceFolder,
settings: PluginResourceSettings
settings: PluginResourceSettings,
output: JestOutputTerminal
): JestExtContext => {
const createRunnerWorkspace = (options?: RunnerWorkspaceOptions) => {
const ws = workspaceFolder.name;
const currentJestVersion = 20;
const [jestCommandLine, pathToConfig] = getJestCommandSettings(settings);

if (!settings.jestCommandLine) {
throw new Error(`[${workspaceFolder.name}] missing jestCommandLine`);
}
return new ProjectWorkspace(
toFilePath(settings.rootPath),
jestCommandLine,
pathToConfig,
settings.jestCommandLine,
'',
currentJestVersion,
outputFileSuffix(ws, options?.outputFileSuffix),
options?.collectCoverage ?? settings.showCoverageOnLoad,
Expand All @@ -78,7 +68,6 @@ export const createJestExtContext = (
settings.shell.toSetting()
);
};
const output = new JestOutputTerminal(workspaceFolder.name);
return {
workspace: workspaceFolder,
settings,
Expand Down Expand Up @@ -111,6 +100,9 @@ const getTestExplorer = (config: vscode.WorkspaceConfiguration): TestExplorerCon

return setting;
};
export const absoluteRootPath = (rootPath: string, workspaceRoot: string): string => {
return path.isAbsolute(rootPath) ? rootPath : path.join(workspaceRoot, rootPath);
};
export const getExtensionResourceSettings = (uri: vscode.Uri): PluginResourceSettings => {
const config = vscode.workspace.getConfiguration('jest', uri);

Expand All @@ -122,13 +114,10 @@ export const getExtensionResourceSettings = (uri: vscode.Uri): PluginResourceSet
pathToConfig: config.get<string>('pathToConfig'),
jestCommandLine: config.get<string>('jestCommandLine'),
pathToJest: config.get<string>('pathToJest'),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
rootPath: path.join(uri.fsPath, config.get<string>('rootPath')!),
rootPath: absoluteRootPath(config.get<string>('rootPath') ?? '', uri.fsPath),
runAllTestsFirst,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
showCoverageOnLoad: config.get<boolean>('showCoverageOnLoad')!,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
coverageFormatter: config.get<string>('coverageFormatter')!,
showCoverageOnLoad: config.get<boolean>('showCoverageOnLoad') ?? false,
coverageFormatter: config.get<string>('coverageFormatter') ?? 'DefaultFormatter',
debugMode: config.get<boolean>('debugMode'),
coverageColors: config.get<CoverageColors>('coverageColors'),
testExplorer: getTestExplorer(config),
Expand Down
4 changes: 0 additions & 4 deletions src/Settings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,3 @@ export interface PluginWindowSettings {
export function isDefaultPathToJest(str?: string | null): boolean {
return str === null || str === '';
}

export function hasUserSetPathToJest(str?: string | null): boolean {
return !isDefaultPathToJest(str);
}
Loading

0 comments on commit c899e81

Please sign in to comment.