Skip to content

Commit

Permalink
added autoRevealOutput setting to control when to reveal the output t…
Browse files Browse the repository at this point in the history
…erminal (#976)

* added revealOutput

* fix lint issues

* rename setting to autoRevealOutput and add an "off" option

* update readme and adding test
  • Loading branch information
connectdotz committed Jan 9, 2023
1 parent 77b2448 commit 249deb8
Show file tree
Hide file tree
Showing 12 changed files with 219 additions and 49 deletions.
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This extension supports full [jest](https://jestjs.io/) features in vscode envir
2. [install](#installation) **"Jest"** extension in vscode.
3. reload or restart vscode

If the extension can find the [jest command](#how-to-set-up-the-jest-command), by default it will automatically run and monitor all tests in watch mode upon launch, and you should see tests, status, errors, coverage (if enabled) in TestExplorer and editors like this:
If the extension can find the jest command, by default it will automatically run and monitor all tests in watch mode upon launch, and you should see tests, status, errors, coverage (if enabled) in TestExplorer and editors like this:

![image](images/v5-output-terminals.png)

Expand Down Expand Up @@ -62,6 +62,7 @@ Content
- [Debug Config](#debug-config)
- [Debug Config v2](#debug-config-v2)
- [monitorLongRun](#monitorlongrun)
- [autoRevealOutput](#autorevealoutput)
- [Commands](#commands)
- [Menu](#menu)
- [Troubleshooting](#troubleshooting)
Expand Down Expand Up @@ -185,7 +186,7 @@ Please note, a working jest environment is a prerequisite for this extension. If
StatusBar shows 2 kinds of information:
`Jest` shows the mode and state of the "active" workspace folder.
`Jest-WS` shows the total test suite stats for the whole workspace.
Clicking on each of these button will reveal the OUTPUT channel with more details.
Clicking on each of these button will reveal the corresponding output window with more details.

<details>
<summary>Illustration</summary>
Expand All @@ -202,6 +203,10 @@ shows the active workspace has onSave for test file only, and that the workspace
<img src='images/status-bar-save-all.png' width="600" />

shows the autoRun will be triggered by either test or source file changes.

<img src='images/status-bar-error.png' width="600" />

shows active workspace has an execution error.
</details>

### How to use the Test Explorer?
Expand All @@ -220,6 +225,11 @@ Users with `vscode` v1.59 and `vscode-jest` v4.1 and up will start to see tests
- Click on the coverage button (see image above) to toggle on or off.
- The next test run (auto or manual) will start reporting test coverage.

<a id='how-to-reveal-output'>**How to reveal test output for the workspace?**</a>
- In TestExplorer, click on the root of the test tree, i.e. the one with the workspace name and the current autoRun mode. You will see a list of buttons to its right.
- Click on the terminal button (see image above) to reveal the test output terminal.


You can further customize the explorer with [jest.testExplorer](#testexplorer) in [settings](#settings).

### How to see more debug info (self-diagnosis)?
Expand Down Expand Up @@ -255,6 +265,7 @@ Users can use the following settings to tailor the extension for their environme
|**Misc**|
|debugMode|Enable debug mode to diagnose plugin issues. (see developer console)|false|`"jest.debugMode": true`|
|disabledWorkspaceFolders 💼|Disabled workspace folders names in multiroot environment|[]|`"jest.disabledWorkspaceFolders": ["package-a", "package-b"]`|
|[autoRevealOutput](#autoRevealOutput)|Determine when to show test output|"on-run"|`"jest.autoRevealOutput": "on-exec-error"`|

#### Details
##### jestCommandLine
Expand Down Expand Up @@ -491,6 +502,15 @@ monitorLongRun = number | 'off'
- specify "off" to disable long-run process monitoring

Default is `"jest.monitorLongRun":60000` (1 minute)

##### autoRevealOutput
```ts
autoRevealOutput = "on-run" | "on-exec-error" | "off"
```
- `on-run`: reveal test run output when test run started.
- `on-exec-error`: reveal test run output only when execution error (note, not test error) occurred.
- `off`: no auto reveal test output. Note this could mask critical error, check status bar status for detail.

## Commands

This extension contributes the following commands and can be accessed via [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette):
Expand Down
16 changes: 16 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,22 @@
],
"default": 60000,
"scope": "resource"
},
"jest.autoRevealOutput": {
"description": "Review jest output terminal",
"type": "string",
"default": "on-run",
"enum": [
"on-run",
"on-exec-error",
"off"
],
"enumDescriptions": [
"auto show output when test run starts",
"auto show test output when execution error occurred",
"disable auto show test output"
],
"scope": "resource"
}
}
},
Expand Down
18 changes: 16 additions & 2 deletions src/JestExt/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,9 @@ export class JestExt {
coverageCodeLensProvider: CoverageCodeLensProvider
) {
this.vscodeContext = vscodeContext;
this.output = new JestOutputTerminal(workspaceFolder.name);
const pluginSettings = getExtensionResourceSettings(workspaceFolder.uri);
this.output = new JestOutputTerminal(workspaceFolder.name);
this.updateOutputSetting(pluginSettings);

this.extContext = createJestExtContext(workspaceFolder, pluginSettings, this.output);
this.logging = this.extContext.loggingFactory.create('JestExt');
Expand Down Expand Up @@ -192,9 +193,13 @@ export class JestExt {
return;
}
switch (event.type) {
case 'start':
case 'start': {
if (this.extContext.settings.autoRevealOutput === 'on-run') {
this.output.reveal();
}
this.updateStatusBar({ state: 'running' });
break;
}
case 'end':
this.updateStatusBar({ state: 'done' });
break;
Expand Down Expand Up @@ -362,10 +367,19 @@ export class JestExt {
this.updateTestFileEditor(editor);
}

private updateOutputSetting(settings: PluginResourceSettings): void {
this.output.revealOnError = settings.autoRevealOutput !== 'off';
this.output.close();
}
public async triggerUpdateSettings(newSettings?: PluginResourceSettings): Promise<void> {
const updatedSettings =
newSettings ?? getExtensionResourceSettings(this.extContext.workspace.uri);

// output
if (this.extContext.settings.autoRevealOutput !== updatedSettings.autoRevealOutput) {
this.updateOutputSetting(updatedSettings);
}

// debug
this.testResultProvider.verbose = updatedSettings.debugMode ?? false;

Expand Down
2 changes: 2 additions & 0 deletions src/JestExt/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
MonitorLongRun,
TestExplorerConfigLegacy,
JestExtAutoRunSetting,
AutoRevealOutputType,
} from '../Settings';
import { workspaceLogging } from '../logging';
import { JestExtContext, RunnerWorkspaceOptions } from './types';
Expand Down Expand Up @@ -118,6 +119,7 @@ export const getExtensionResourceSettings = (uri: vscode.Uri): PluginResourceSet
shell: new RunShell(config.get<string | LoginShell>('shell')),
monitorLongRun: config.get<MonitorLongRun>('monitorLongRun') ?? undefined,
autoRun: new AutoRun(config.get<JestExtAutoRunSetting | null>('autoRun')),
autoRevealOutput: config.get<AutoRevealOutputType>('autoRevealOutput') ?? 'on-run',
};
};

Expand Down
77 changes: 65 additions & 12 deletions src/JestExt/output-terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,84 @@ export interface JestExtOutput {
write: (msg: string, opt?: OutputOptions) => string;
}

/**
* simple class to manage the pending data before the terminal is
* visible.
* The default is to keep max 100 output batch (each push counts 1), when exceeding
* it will remove the first 10 batches. We could make this configurable
* if needed.
*/
export class PendingOutput {
private pendingOutput: string[];
constructor(private maxLength = 100) {
this.pendingOutput = [];
}
push(output: string): void {
if (this.pendingOutput.length >= this.maxLength) {
// truncate the first few blocks to make room for the new output
const cutoff = Math.max(Math.floor(this.maxLength / 10), 1);
this.pendingOutput = this.pendingOutput.slice(cutoff);
}
this.pendingOutput.push(output);
}
clear(): void {
this.pendingOutput = [];
}
toString(): string {
return this.pendingOutput.join('');
}
}

/** termerinal per workspace */
export class ExtOutputTerminal implements JestExtOutput {
private pendingMessages: string[];
private pendingMessages: PendingOutput;
private ptyIsOpen: boolean;
private writeEmitter = new vscode.EventEmitter<string>();
private _terminal?: vscode.Terminal;
private canReveal: boolean;
public revealOnError: boolean;

private pty: vscode.Pseudoterminal = {
onDidWrite: this.writeEmitter.event,
open: () => {
this.writeEmitter.fire(`${this.name}\r\n`);
if (this.pendingMessages.length > 0) {
this.writeEmitter.fire(this.pendingMessages.join(''));
this.pendingMessages = [];
const pending = this.pendingMessages.toString();
if (pending) {
this.writeEmitter.fire(pending);
this.pendingMessages.clear();
}
this.ptyIsOpen = true;
},
close: () => {
this._terminal?.dispose();
this.ptyIsOpen = false;
this.canReveal = false;
this._terminal = undefined;
},
};
private _terminal?: vscode.Terminal;
constructor(private name: string) {
constructor(private name: string, visibile?: boolean) {
this.ptyIsOpen = false;
this.pendingMessages = [];
this.pendingMessages = new PendingOutput();
this.canReveal = visibile ?? false;
this.revealOnError = true;
}

/**
* indicate the terminal can be revealed if needed.
* This allows the terminal to be created in a "background" mode, i.e. it will not force the terminal to be shown if the panels are hidden or
* if there are other active terminal
* @returns
*/
reveal(): void {
if (this.canReveal) {
return;
}
this.canReveal = true;
this.createTerminalIfNeeded();
}

/** delay creating terminal until we are actually running the tests */
private createTerminalIfNeeded() {
if (this._terminal) {
if (!this.canReveal || this._terminal) {
return;
}
vscode.window.terminals.forEach((t) => {
Expand Down Expand Up @@ -68,22 +117,26 @@ export class ExtOutputTerminal implements JestExtOutput {
const text = toAnsi(msg, opt);
this.appendRaw(text);

if (isErrorOutputType(opt)) {
if (isErrorOutputType(opt) && this.revealOnError) {
this.show();
}
return text;
}
show(): void {
this.reveal();
this._terminal?.show(true);
}
close(): void {
this._terminal?.dispose();
}
dispose(): void {
this.writeEmitter.dispose();
this._terminal?.dispose();
}
}
export class JestOutputTerminal extends ExtOutputTerminal {
constructor(workspaceName: string) {
super(`Jest (${workspaceName})`);
constructor(workspaceName: string, visible?: boolean) {
super(`Jest (${workspaceName})`, visible);
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/Settings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface TestExplorerConfig {

export type NodeEnv = ProjectWorkspace['nodeEnv'];
export type MonitorLongRun = 'off' | number;
export type AutoRevealOutputType = 'on-run' | 'on-exec-error' | 'off';
export interface PluginResourceSettings {
jestCommandLine?: string;
rootPath: string;
Expand All @@ -48,6 +49,7 @@ export interface PluginResourceSettings {
nodeEnv?: NodeEnv;
shell: RunShell;
monitorLongRun?: MonitorLongRun;
autoRevealOutput: AutoRevealOutputType;
}

export interface PluginWindowSettings {
Expand Down
3 changes: 0 additions & 3 deletions src/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ class VSCodeJestReporter implements Reporter {
// report exec errors that could have prevented Result file being generated
if (results.runExecError) {
process.stderr.write(`onRunComplete: execError: ${results.runExecError.message}\r\n`);
} else if (results.numTotalTests === 0 && results.numTotalTestSuites > 0) {
// for jest < 29.1.2, we won't see onRunComplete with runExecError, so try this as best effort. see https://github.com/facebook/jest/pull/13203
process.stderr.write('onRunComplete: execError: no test is run\r\n');
} else {
process.stderr.write('onRunComplete\r\n');
}
Expand Down
2 changes: 1 addition & 1 deletion src/setup-wizard/start-wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const startWizard = (
): Promise<WizardStatus> => {
const { workspace, taskId, verbose } = options;

const terminal = new ExtOutputTerminal('vscode-jest Setup Tool');
const terminal = new ExtOutputTerminal('vscode-jest Setup Tool', true);

const message = (msg: string, opt?: OutputOptions): string => {
const str = terminal.write(`${msg}${opt ? '' : '\r\n'}`, opt);
Expand Down
45 changes: 45 additions & 0 deletions tests/JestExt/core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,11 @@ import { WorkspaceManager } from '../../src/workspace-manager';
/* eslint jest/expect-expect: ["error", { "assertFunctionNames": ["expect", "expectItTakesNoAction"] }] */
const mockHelpers = helper as jest.Mocked<any>;
const mockOutputTerminal = {
revealOnError: true,
write: jest.fn(),
show: jest.fn(),
close: jest.fn(),
reveal: jest.fn(),
dispose: jest.fn(),
};

Expand Down Expand Up @@ -893,6 +896,19 @@ describe('JestExt', () => {
}
});
});
it('will update statusBar', async () => {
expect.hasAssertions();

const sut = newJestExt();
sut.coverageOverlay.enabled = true;
await sut.startSession();
expect(sbUpdateMock).toHaveBeenCalledWith(
expect.objectContaining({
state: 'initial',
mode: expect.arrayContaining(['coverage']),
})
);
});
});
describe('stopSession', () => {
it('will fire event', async () => {
Expand Down Expand Up @@ -1379,4 +1395,33 @@ describe('JestExt', () => {
});
});
});
describe('autoRevealOutput', () => {
it.each`
autoRevealOutput | shouldReveal | revealOnError
${'on-run'} | ${true} | ${true}
${'on-exec-error'} | ${false} | ${true}
${'off'} | ${false} | ${false}
`(
'autoRevealOutput = $autoRevealOutput, shouldReveal = $shouldReveal',
({ autoRevealOutput, shouldReveal, revealOnError }) => {
const sut = newJestExt({ settings: { autoRevealOutput } });
const onRunEvent = (sut.events.onRunEvent.event as jest.Mocked<any>).mock.calls[0][0];
const process = { id: 'a process id', request: { type: 'watch' } };
onRunEvent({ type: 'start', process });
if (shouldReveal) {
expect(mockOutputTerminal.reveal).toHaveBeenCalled();
} else {
expect(mockOutputTerminal.reveal).not.toHaveBeenCalled();
}
expect(mockOutputTerminal.revealOnError).toEqual(revealOnError);
}
);
it('when setting changed, output setting will change accordingly', () => {
const sut = newJestExt({ settings: { autoRevealOutput: 'on-exec-error' } });
expect(mockOutputTerminal.revealOnError).toEqual(true);
sut.triggerUpdateSettings({ autoRevealOutput: 'off' } as any);
expect(mockOutputTerminal.revealOnError).toEqual(false);
expect(mockOutputTerminal.close).toHaveBeenCalled();
});
});
});
1 change: 1 addition & 0 deletions tests/JestExt/helper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ describe('getExtensionResourceSettings()', () => {
testExplorer: {},
monitorLongRun: 60000,
shell: mockShell,
autoRevealOutput: 'on-run',
});
});

Expand Down
Loading

0 comments on commit 249deb8

Please sign in to comment.