From df87aa1b0a3a1bc90cbaa53ac409a9c447c4a570 Mon Sep 17 00:00:00 2001 From: ConnectDotz Date: Thu, 22 Feb 2024 15:41:03 -0500 Subject: [PATCH] Enhance output config support (#1119) * fix auto-focus issue; adding more diagnosis messages; clean up dead files * prepare for release 6.2.2 * make conflict warning message non-modal and more consistent with the rest of the doc * updated release note --- README.md | 47 +++--- package.json | 6 +- release-notes/release-note-v6.md | 55 ++++++- src/JestExt/core.ts | 29 +++- src/appGlobals.ts | 5 + src/extension-manager.ts | 4 +- src/messaging.ts | 67 --------- src/output-manager.ts | 108 ++++++++------ src/quick-fix.ts | 10 +- tests/JestExt/core.test.ts | 46 +++++- tests/JestExt/process-listeners.test.ts | 21 ++- tests/extension-manager.test.ts | 3 + tests/messaging.test.ts | 85 ----------- tests/output-manager.test.ts | 184 +++++++++++++++++++----- 14 files changed, 392 insertions(+), 278 deletions(-) delete mode 100644 src/messaging.ts delete mode 100644 tests/messaging.test.ts diff --git a/README.md b/README.md index 972e31db..eec01400 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ You can see the full [features](#features) and learn more details in the [How-To Happy testing! ## Releases -- **Current** ([v6.2.1](https://github.com/jest-community/vscode-jest/releases/tag/v6.2.1)): [release note](release-notes/release-note-v6.md#v621) +- **Current** ([v6.2.2](https://github.com/jest-community/vscode-jest/releases/tag/v6.2.2)): [release note](release-notes/release-note-v6.md#v622) - **Previous** ([v5.2.3](https://github.com/jest-community/vscode-jest/releases/tag/v5.2.3)): [release note](release-notes/release-note-v5.x.md#v523) @@ -348,7 +348,7 @@ for example: #### outputConfig -The `outputConfig` controls the Jest output experience by specifying when and where to create, display, and clear the output content. It supports 2 output panels: `TEST RESULTS` and `TERMINAL`. The `TEST RESULTS` panel displays test results in the order they were run, while the `TERMINAL` panel organizes outputs by workspace folder. `TERMINAL` panel also contains the non-test run outputs, such as [quick-fix link](#quick-fix-chooser), extension auto-config info and tips. +The `outputConfig` controls the Jest output experience by specifying when and where to create, display, and clear the output content. It supports 2 output panels: `TEST RESULTS` and `TERMINAL`. The `TEST RESULTS` panel displays test results in the order they were run, while the `TERMINAL` panel organizes outputs by workspace folder. `TERMINAL` panel also contains the non-test run outputs, such as [quick-fix link](#quick-fix-chooser), extension auto-config info, and tips. **Type Definitions** ```ts @@ -387,8 +387,8 @@ This setting can be one of the predefined types or a custom object. 4. "none": Do not clear any panel. (default) (_**Note**: As of the current version, the testing framework does not support the clearing of the "TEST RESULTS" panel without side effects. The closest available command also clears all test item statuses, which may not be desirable. We are aware of this limitation and will raise the issue with the VS Code team._) -**Handling Conflicts with "TEST RESULTS" panel** +**Handling Conflicts with "TEST RESULTS" panel** _The Problem_ @@ -404,14 +404,25 @@ _Further Customization_ However, if you prefer "TEST RESULTS" and "TERMINAL" panels to behave differently and don't mind managing 2 settings yourself, you could play with different combinations. -For instance, if `"testing.openTesting"` is set to `"openOnTestFailure"`, and you want your terminal panel to still reveal when any tests run, your setting would look like this: `"jest.outputConfig": {revealWithFocus: "test-results"}` +For instance, if `"testing.openTesting"` is set to `"openOnTestFailure"`, and you want your terminal panel to still reveal when any tests run, your setting would look like this: `"jest.outputConfig": {revealWithFocus: "terminal"}`. + +_Validation and Diagnosis_ + +The extension features output config diagnosis information in the jest terminal, as well as the built-in conflict detection and quick fixes to assist with the transition. + + +**Common Issues** -_Built-in Validation_ +Upon upgrading to v6.2, some users, frequently with auto run modes (e.g., 'watch', 'on-save'), might experience frequent "TEST RESULTS" panel automatically grabbing focus whenever files are saved or tests are run. -The extension also features built-in conflict detection and quick fixes to assist. +This is due to the extension generates a default `jest.outputConfig`, if none is existing in your settings, to match the existing `testing.openTesting` setting, which defaults to `"openOnTestStart"`. If this is not your desired output experience, you can easily disable `testing.openTesting` in your settings.json: +```json +"testing.openTesting": "neverOpen" +``` +Then use the `jest.outputConfig` to find-tune the output experience you prefer. **Examples** -- Choose a passive output experience that is identical to the previous version. +- Choose a passive output experience that is identical to the previous version: no automatic focus switch, no automatic clear. ```json "testing.openTesting": "neverOpen", "jest.outputConfig": "neutral" @@ -421,20 +432,17 @@ The extension also features built-in conflict detection and quick fixes to assis "testing.openTesting": "neverOpen", "jest.outputConfig": "terminal-based" ``` -- Choose a test-results-based experience and switch focus to it when test fails. +- Choose a test-results-based experience and switch focus to it when test run starts. ```json "testing.openTesting": "neverOpen", - "jest.outputConfig": { - "revealOn": "error", - "revealWithFocus": "test-results", - } + "jest.outputConfig": "test-results-based" ``` - alternatively: +- Choose a test-results-based experience and switch focus to it when test fails. ```json - "testing.openTesting": "openOnTestFailure", + "testing.openTesting": "neverOpen", "jest.outputConfig": { "revealOn": "error", - "revealWithFocus": "test-results" + "revealWithFocus": "test-results", } ``` - Clear the terminal output on each run but do not automatically switch focus to any panel. @@ -556,10 +564,11 @@ While the concepts of performance and automation are generally clear, "completen 2. If you modify the source or test code, potential failures in other tests may remain hidden until they are explicitly run. 3. Tests bearing dynamic names, like those using test.each with variables or template literals, won't be translated. As a result, they must be executed through higher-level constructs, such as describe blocks with static names or entire test suites. - - -**Migration Guide** -Starting from v6.1.0, if no runMode is defined in settings.json, the extension will automatically generate one using legacy settings (`autoRun`, `showCoverageOnLoad`). To migrate, simply use the `"Jest: Save Current RunMode"` command from the command palette to update the setting, then remove the deprecated settings. +> [!NOTE] +> +> **Migration Guide** +> +> Starting from v6.1.0, if no runMode is defined in settings.json, the extension will automatically generate one using legacy settings (`autoRun`, `showCoverageOnLoad`). To migrate, simply use the `"Jest: Save Current RunMode"` command from the command palette to update the setting, then remove the deprecated settings. --- diff --git a/package.json b/package.json index 0cac7412..2d5e9217 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-jest", "displayName": "Jest", "description": "Use Facebook's Jest With Pleasure.", - "version": "6.2.1", + "version": "6.2.2", "publisher": "Orta", "engines": { "vscode": "^1.68.1" @@ -406,6 +406,10 @@ "command": "io.orta.jest.save-output-config", "title": "Jest: Save Current Output Config" }, + { + "command": "io.orta.jest.disable-auto-focus", + "title": "Jest: Disable Auto Focus Test Output" + }, { "command": "io.orta.jest.run-all-tests", "title": "Jest: Run All Tests" diff --git a/release-notes/release-note-v6.md b/release-notes/release-note-v6.md index bb9679d6..6123c9b2 100644 --- a/release-notes/release-note-v6.md +++ b/release-notes/release-note-v6.md @@ -3,13 +3,15 @@ Release Notes --- -- [v6.2.1](#v621) +- [v6.2.2](#v622) - [CHANGELOG](#changelog) +- [v6.2.1](#v621) + - [CHANGELOG](#changelog-1) - [v6.2.0](#v620) - [New Features Summary](#new-features-summary) - [Bug Fixes and Technical Debt Reduction](#bug-fixes-and-technical-debt-reduction) - [Breaking Changes](#breaking-changes) - - [CHANGELOG](#changelog-1) + - [CHANGELOG](#changelog-2) - [v6.1 (pre-release)](#v61-pre-release) - [Main Features](#main-features) - [1. Enhanced Test Execution Control with "runMode"](#1-enhanced-test-execution-control-with-runmode) @@ -29,10 +31,37 @@ Release Notes - [3. Control extension activation within each folder](#3-control-extension-activation-within-each-folder) - [4. Auto clear output upon test run](#4-auto-clear-output-upon-test-run) - [Fixes](#fixes) - - [CHANGELOG](#changelog-2) + - [CHANGELOG](#changelog-3) --- +## v6.2.2 +This release is a patch release with the following changes: + +**Enhancement** + +- Improved output config validation logic and showed warning if detected upon starting up. ([#1119](https://github.com/jest-community/vscode-jest/pull/1119) - @connectdotz) + +- Added more diagnosis and fix-it instructions in the jest terminal: ([#1119](https://github.com/jest-community/vscode-jest/pull/1119) - @connectdotz) + + - Display critical settings such as "jest.runMode", "jest.outputConfig" and "testing.openTesting" settings + - Provide warning messages for common output issues, such as aggressive auto-focus for auto run modes, when detected. Provides quick fix instructions to address them. + + +**Bug Fixes** +- Fixed an outputConfig initialization bug that did not honor "testing.openTesting": "openOnTestFailure" setting correctly. ([#1119](https://github.com/jest-community/vscode-jest/pull/1119) - @connectdotz) + +**New Command** +- Added a new command `"Jest: Disable Auto Focus Test Output"` to easily disable TEST RESULTS panel auto focus. It will set the output to the "neutral" mode, i.e., no auto focusing. ([#1119](https://github.com/jest-community/vscode-jest/pull/1119) - @connectdotz) + + +**Documentation** +- Minor docs updates for the migration guides. ([#1116](https://github.com/jest-community/vscode-jest/pull/1116) - @pmcelhaney) +- Minor update for the output config info in README and release notes. ([#1119](https://github.com/jest-community/vscode-jest/pull/1119) - @connectdotz) + +### CHANGELOG +- [v6.2.2](https://github.com/jest-community/vscode-jest/releases/tag/v6.2.2) + ## v6.2.1 This release is a patch release with the following bug fix: @@ -161,7 +190,25 @@ Here are a few scenarios and how to configure them: "jest.outputConfig": "neutral" } -// Auto-focus on "TEST RESULTS" when errors occur, ideal for on-demand testing +// or semi-minimal interaction, suitable for watch/on-save modes prefer to see output if there is errors +{ + "testing.openTesting": "neverOpen", + "jest.outputConfig": { + "revealOn": "error", + "revealWithFocus": "test-results" + } +} + +// Auto-focus on "TEST RESULTS" when run starts, ideal for on-demand testing +{ + "testing.openTesting": "neverOpen", + "jest.outputConfig": { + "revealOn": "run", + "revealWithFocus": "test-results" + } +} + +// or Auto-focus on "TEST RESULTS" when errors occurred, for on-demand testing prefer to only show output with errors { "testing.openTesting": "neverOpen", "jest.outputConfig": { diff --git a/src/JestExt/core.ts b/src/JestExt/core.ts index 9d2337d0..aaa26355 100644 --- a/src/JestExt/core.ts +++ b/src/JestExt/core.ts @@ -19,7 +19,7 @@ import { CoverageMapData } from 'istanbul-lib-coverage'; import { Logging } from '../logging'; import { createProcessSession, ProcessSession } from './process-session'; import { JestExtContext, JestSessionEvents, JestExtSessionContext, JestRunEvent } from './types'; -import { extensionName, SupportedLanguageIds } from '../appGlobals'; +import { extensionName, OUTPUT_CONFIG_HELP_URL, SupportedLanguageIds } from '../appGlobals'; import { createJestExtContext, getExtensionResourceSettings, prefixWorkspace } from './helper'; import { PluginResourceSettings } from '../Settings'; import { WizardTaskId } from '../setup-wizard'; @@ -38,6 +38,8 @@ interface JestCommandSettings { jestCommandLine: string; } +const AUTO_FOCUS_WARNING = `The TEST RESULTS panel has auto-focus enabled, which may cause frequent focus shifts during the current run mode. If this becomes a problem, you can disable the auto-focus using the command "Jest: Disable Auto Focus Test Output". Alternatively, click on the action link below. For more details, see ${OUTPUT_CONFIG_HELP_URL}`; + /** extract lines starts and end with [] */ export class JestExt { coverageMapProvider: CoverageMapProvider; @@ -139,8 +141,12 @@ export class JestExt { if (pluginSettings.enable === false) { throw new Error(`Jest is disabled for workspace ${workspaceFolder.name}`); } + const { outputConfig, openTesting } = outputManager.outputConfigs(); this.output.write( - `RunMode: ${JSON.stringify(pluginSettings.runMode.config, undefined, 4)}`, + 'Critical Settings:\r\n' + + `jest.runMode: ${JSON.stringify(pluginSettings.runMode.config, undefined, 4)}\r\n` + + `jest.outputConfig: ${JSON.stringify(outputConfig, undefined, 4)}\r\n` + + `testing.openTesting: ${JSON.stringify(openTesting, undefined, 4)}\r\n`, 'info' ); return pluginSettings; @@ -309,6 +315,7 @@ export class JestExt { // update visible editors that belong to this folder this.updateVisibleTextEditors(); + this.warnAutoRunAutoFocus(); } catch (e) { this.outputActionMessages( `Failed to start jest session: ${e}`, @@ -341,6 +348,24 @@ export class JestExt { } } + // check if output config has conflict, especially for auto-run modes + private async warnAutoRunAutoFocus(): Promise { + if ( + this.extContext.settings.runMode.config.deferred || + this.extContext.settings.runMode.config.type === 'on-demand' || + outputManager.isAutoFocus() === false + ) { + return; + } + const cmdLink = executableTerminalLinkProvider.executableLink( + this.extContext.workspace.name, + `${extensionName}.disable-auto-focus` + ); + + this.output.write(AUTO_FOCUS_WARNING, 'warn'); + this.output.write(`Disable Auto Focus: \u2192 ${cmdLink}\r\n`, 'info'); + } + private updateTestFileEditor(editor: vscode.TextEditor): void { if (!this.isTestFileEditor(editor)) { return; diff --git a/src/appGlobals.ts b/src/appGlobals.ts index 35ee7b5a..922efdb7 100644 --- a/src/appGlobals.ts +++ b/src/appGlobals.ts @@ -7,3 +7,8 @@ export const SupportedLanguageIds = [ 'typescriptreact', 'vue', ]; + +export const REPO_BASE_URL = 'https://github.com/jest-community/vscode-jest'; +export const TROUBLESHOOTING_URL = `${REPO_BASE_URL}#troubleshooting`; +export const LONG_RUN_TROUBLESHOOTING_URL = `${REPO_BASE_URL}#what-to-do-with-long-running-tests-warning`; +export const OUTPUT_CONFIG_HELP_URL = `${REPO_BASE_URL}#outputconfig`; diff --git a/src/extension-manager.ts b/src/extension-manager.ts index c48c704f..54a642b0 100644 --- a/src/extension-manager.ts +++ b/src/extension-manager.ts @@ -17,6 +17,7 @@ import { enabledWorkspaceFolders } from './workspace-manager'; import { VirtualFolderBasedCache } from './virtual-workspace-folder'; import { updateSetting } from './Settings'; import { showQuickFix } from './quick-fix'; +import { outputManager } from './output-manager'; export type GetJestExtByURI = (uri: vscode.Uri) => JestExt[]; @@ -518,6 +519,7 @@ export class ExtensionManager { activate(): void { this.showReleaseMessage(); + outputManager.validate(); if (vscode.window.activeTextEditor?.document.uri) { this.onExtensionByUri(vscode.window.activeTextEditor?.document.uri, (ext) => { @@ -529,7 +531,7 @@ export class ExtensionManager { const ReleaseNoteBase = 'https://github.com/jest-community/vscode-jest/blob/master/release-notes'; const ReleaseNotes: Record = { - '6.2.1': `${ReleaseNoteBase}/release-note-v6.md#v621`, + '6.2.2': `${ReleaseNoteBase}/release-note-v6.md#v622`, '6.2.0': `${ReleaseNoteBase}/release-note-v6.md#v620`, '6.1.0': `${ReleaseNoteBase}/release-note-v6.md#v610-pre-release`, '6.0.0': `${ReleaseNoteBase}/release-note-v6.md#v600-pre-release`, diff --git a/src/messaging.ts b/src/messaging.ts deleted file mode 100644 index cb101561..00000000 --- a/src/messaging.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * collection of functions to show messages with actions in a consistent manner - */ - -import * as vscode from 'vscode'; - -export const TROUBLESHOOTING_URL = - 'https://github.com/jest-community/vscode-jest/tree/master#troubleshooting'; -export const LONG_RUN_TROUBLESHOOTING_URL = - 'https://github.com/jest-community/vscode-jest/tree/master#what-to-do-with-long-running-tests-warning'; - -// -// internal methods -// -function _extractActionTitles(actions?: MessageAction[]): string[] { - return actions ? actions.map((a) => a.title) : []; -} -// expose the internal function so we can unit testing it - -const doNothing = () => {}; -export function _handleMessageActions(actions?: MessageAction[]): (action?: string) => void { - if (!actions || actions.length <= 0) { - return doNothing; - } - return (action?: string) => { - if (!action) { - return; - } - const found = actions.filter((a) => a.title === action); - if (found.length === 1) { - found[0].action(); - } else { - throw Error( - `expect exactly one matched action '${action}' but found ${found.length} match(es)` - ); - } - }; -} - -export interface MessageAction { - title: string; - action: () => void; -} - -export function systemErrorMessage(message: string, ...actions: MessageAction[]): void { - vscode.window - .showErrorMessage(message, ..._extractActionTitles(actions)) - .then(_handleMessageActions(actions)); -} - -export function systemWarningMessage(message: string, ...actions: MessageAction[]): void { - vscode.window - .showWarningMessage(message, ..._extractActionTitles(actions)) - .then(_handleMessageActions(actions)); -} - -// common actions -export const showTroubleshootingAction: MessageAction = { - title: 'Help', - action: () => - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(TROUBLESHOOTING_URL)), -}; -export const showLongRunTroubleshootingAction: MessageAction = { - title: 'Help', - action: () => - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(LONG_RUN_TROUBLESHOOTING_URL)), -}; diff --git a/src/output-manager.ts b/src/output-manager.ts index 72892d96..25c3afb2 100644 --- a/src/output-manager.ts +++ b/src/output-manager.ts @@ -1,7 +1,7 @@ import * as vscode from 'vscode'; import { AutoRevealOutputType, JestOutputSetting, JestRawOutputSetting } from './Settings/types'; import { ExtOutputTerminal } from './JestExt/output-terminal'; -import { extensionName } from './appGlobals'; +import { OUTPUT_CONFIG_HELP_URL, extensionName } from './appGlobals'; export type OutputConfig = Required; export const DefaultJestOutputSetting: OutputConfig = { @@ -9,19 +9,24 @@ export const DefaultJestOutputSetting: OutputConfig = { revealWithFocus: 'none', clearOnRun: 'none', }; - -const OUTPUT_CONFIG_HELP_URL = 'https://github.com/jest-community/vscode-jest#outputconfig'; +export interface OutputConfigs { + outputConfig: OutputConfig; + openTesting: string | undefined; +} export class OutputManager { - private config: OutputConfig; + private config!: OutputConfig; + private openTesting: string | undefined; + private skipValidation = false; constructor() { - this.config = this.getConfig(); + this.initConfigs(); } - private getConfig(): OutputConfig { + private initConfigs(): void { + this.openTesting = vscode.workspace.getConfiguration('testing').get('openTesting'); const config = vscode.workspace.getConfiguration('jest').get('outputConfig'); - return config + this.config = config ? this.resolveSetting(config) : { ...DefaultJestOutputSetting, ...this.fromLegacySettings() }; } @@ -59,11 +64,10 @@ export class OutputManager { const vscodeConfig = vscode.workspace.getConfiguration('jest', scope); const autoClearTerminal = vscodeConfig.get('autoClearTerminal'); const autoRevealOutput = vscodeConfig.get('autoRevealOutput'); - const openTesting = vscode.workspace.getConfiguration('testing').get('openTesting'); const config = {} as JestRawOutputSetting; - switch (openTesting) { + switch (this.openTesting) { case 'neverOpen': case 'openExplorerOnTestStart': // no-op @@ -76,22 +80,19 @@ export class OutputManager { config.revealWithFocus = 'test-results'; break; default: - console.warn(`Unrecognized "testing.openTesting" setting: ${openTesting}`); + console.warn(`Unrecognized "testing.openTesting" setting: ${this.openTesting}`); } - switch (autoRevealOutput) { - case undefined: - // no-op - break; - case 'on-run': - case 'on-exec-error': - config.revealOn = 'run'; - break; - case 'off': - config.revealOn = 'demand'; - config.revealWithFocus = 'none'; - break; + if (autoRevealOutput === 'off') { + config.revealOn = 'demand'; + config.revealWithFocus = 'none'; + if (this.openTesting !== 'neverOpen') { + console.warn( + 'The "autoRevealOutput" setting is set to "off", but "testing.openTesting" is not set to "neverOpen".' + ); + } } + config.clearOnRun = autoClearTerminal ? 'terminal' : 'none'; return config; } @@ -155,6 +156,7 @@ export class OutputManager { await vscode.workspace .getConfiguration('testing') .update('openTesting', value, vscode.ConfigurationTarget.Workspace); + console.warn(`set "testing.openTesting" to "${value}"`); } @@ -164,33 +166,58 @@ export class OutputManager { vscode.commands.registerCommand(`${extensionName}.save-output-config`, async () => this.save() ), + vscode.commands.registerCommand(`${extensionName}.disable-auto-focus`, async () => + this.disableAutoFocus() + ), ]; } - /** - * Check if "testing.openTesting" setting conflict with outputConfig. - * This occurred when "testing.openTesting" is not set to "neverOpen" - * and the 'revealWithFocus' is not set to "test-results" - * - * @returns boolean - */ - private isTestingSettingValid(): boolean { - const testingSetting = vscode.workspace.getConfiguration('testing').get('openTesting'); - return testingSetting === 'neverOpen' || this.config.revealWithFocus === 'test-results'; + public isAutoFocus(): boolean { + return this.config.revealWithFocus !== 'none' || this.openTesting !== 'neverOpen'; + } + public async disableAutoFocus(): Promise { + this.skipValidation = true; + await this.updateTestResultsSettings(); + this.config.revealWithFocus = 'none'; + await this.save(); + this.skipValidation = false; + } + + /** returns a readonly settings related to jest output */ + public outputConfigs(): OutputConfigs { + return { outputConfig: { ...this.config }, openTesting: this.openTesting }; + } + + public isTestResultsConfigsValid(): boolean { + switch (this.openTesting) { + case 'openOnTestStart': + return this.config.revealWithFocus === 'test-results' && this.config.revealOn === 'run'; + case 'openOnTestFailure': + return this.config.revealWithFocus === 'test-results' && this.config.revealOn === 'error'; + default: + return true; + } } /** * Validates output settings for potential conflicts. * If conflict detected, show a warning message with options to update the settings. + * @returns true if no conflict detected or user has resolved the conflict; false otherwise; undefined if validation is skipped. * @returns void */ - public async validate(): Promise { - if (this.isTestingSettingValid()) { + public async validate(): Promise { + if (this.skipValidation) { + return; + } + + //check for conflicts between testing.openTesting and jest.outputConfig.revealWithFocus + if (this.isTestResultsConfigsValid()) { return true; } - const testingSetting = vscode.workspace.getConfiguration('testing').get('openTesting'); - const detail = `test-results panel setting "testing.openTesting: ${testingSetting}" conflicts with jest.outputConfig "revalWithFocus: ${this.config.revealWithFocus}".`; + const detail = + `Output Config Conflict Detected: test-results panel setting "testing.openTesting: ${this.openTesting}" ` + + `conflicts with jest.outputConfig:\r\n ${JSON.stringify(this.config, undefined, 4)}.`; console.warn(detail); const actions = { @@ -201,7 +228,7 @@ export class OutputManager { const buttons: string[] = [actions.fixIt, actions.help, actions.cancel]; const selection = await vscode.window.showWarningMessage( - `Output Config Conflict Detected`, + `Output Config Conflict Detected (see console log for more details)`, ...buttons ); switch (selection) { @@ -223,8 +250,8 @@ export class OutputManager { }, fixOutputConfig: { label: '$(sync-ignored) Fix outputConfig setting', - description: '(Extension will NOT manage the test-results panel)', - detail: 'Set "jest.outputConfig.revealWithFocus" to "test-results"', + description: '(Match output config with current test-results panel setting)', + detail: 'Set "jest.outputConfig.revealWithFocus" to "test-results" etc.', }, editSettings: { label: '$(tools) Edit Settings Manually', @@ -247,6 +274,7 @@ export class OutputManager { return true; case items.fixOutputConfig: this.config.revealWithFocus = 'test-results'; + this.config.revealOn = this.openTesting === 'openOnTestFailure' ? 'error' : 'run'; await this.save(); return true; case items.editSettings: @@ -264,7 +292,7 @@ export class OutputManager { e.affectsConfiguration('jest.outputConfig') || e.affectsConfiguration('testing.openTesting') ) { - this.config = this.getConfig(); + this.initConfigs(); this.validate(); } } diff --git a/src/quick-fix.ts b/src/quick-fix.ts index b36a2f80..b65e8ecd 100644 --- a/src/quick-fix.ts +++ b/src/quick-fix.ts @@ -1,7 +1,6 @@ import * as vscode from 'vscode'; -import { extensionName } from './appGlobals'; +import { LONG_RUN_TROUBLESHOOTING_URL, TROUBLESHOOTING_URL, extensionName } from './appGlobals'; import { WizardTaskId } from './setup-wizard'; -import * as messaging from './messaging'; export type QuickFixActionType = | 'help' @@ -44,10 +43,7 @@ export const showQuickFix = async (folderName: string, types: QuickFixActionType label: '$(info) Help', description: 'See troubleshooting guide', action: () => { - vscode.commands.executeCommand( - 'vscode.open', - vscode.Uri.parse(messaging.TROUBLESHOOTING_URL) - ); + vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(TROUBLESHOOTING_URL)); }, }); break; @@ -88,7 +84,7 @@ export const showQuickFix = async (folderName: string, types: QuickFixActionType action: () => { vscode.commands.executeCommand( 'vscode.open', - vscode.Uri.parse(messaging.LONG_RUN_TROUBLESHOOTING_URL) + vscode.Uri.parse(LONG_RUN_TROUBLESHOOTING_URL) ); }, }); diff --git a/tests/JestExt/core.test.ts b/tests/JestExt/core.test.ts index 49f87b23..a1cc4764 100644 --- a/tests/JestExt/core.test.ts +++ b/tests/JestExt/core.test.ts @@ -29,6 +29,8 @@ jest.mock('../../src/workspace-manager', () => ({ })); const mockOutputManager = { showOutputOn: jest.fn(), + isAutoFocus: jest.fn(), + outputConfigs: jest.fn(), }; jest.mock('../../src/output-manager', () => ({ outputManager: mockOutputManager, @@ -42,7 +44,6 @@ import { updateCurrentDiagnostics, updateDiagnostics } from '../../src/diagnosti import { CoverageMapProvider } from '../../src/Coverage'; import * as helper from '../../src/helpers'; import { resultsWithLowerCaseWindowsDriveLetters } from '../../src/TestResults'; -import * as messaging from '../../src/messaging'; import { PluginResourceSettings } from '../../src/Settings'; import * as extHelper from '../../src/JestExt/helper'; import { workspaceLogging } from '../../src/logging'; @@ -150,7 +151,9 @@ describe('JestExt', () => { jestCommandLine: 'jest', }; getConfiguration.mockReturnValue({}); + mockOutputManager.outputConfigs.mockReturnValue({}); + vscode.window.visibleTextEditors = []; (createProcessSession as jest.Mocked).mockReturnValue(mockProcessSession); (ProjectWorkspace as jest.Mocked).mockImplementation(mockProjectWorkspace); (workspaceLogging as jest.Mocked).mockImplementation(mockWorkspaceLogging); @@ -239,8 +242,6 @@ describe('JestExt', () => { testNamePattern, workspaceFolder ); - - expect(messaging.systemWarningMessage).not.toHaveBeenCalled(); }); describe('can fallback to workspace config if no folder config found', () => { const defaultConfig = { name: 'vscode-jest-tests.v2' }; @@ -812,6 +813,39 @@ describe('JestExt', () => { expect(update.state).toEqual('initial'); expect(update.mode.config.coverage).toEqual(true); }); + describe('emit auto-focus warnings for auto-run modes', () => { + it.each` + case | runMode | isAutoFocus | showWarning + ${1} | ${'watch'} | ${true} | ${true} + ${2} | ${'watch'} | ${false} | ${false} + ${3} | ${'on-save'} | ${true} | ${true} + ${4} | ${'on-save'} | ${false} | ${false} + ${5} | ${'on-demand'} | ${true} | ${false} + ${6} | ${{ type: 'watch', deferred: true }} | ${true} | ${false} + ${7} | ${{ type: 'watch', deferred: false }} | ${true} | ${true} + `( + 'case $case: showWarning: $showWarning', + async ({ runMode, isAutoFocus, showWarning }) => { + expect.hasAssertions(); + + const sut = newJestExt({ settings: { runMode: new RunMode(runMode) } }); + mockOutputManager.isAutoFocus.mockReturnValueOnce(isAutoFocus); + + await sut.startSession(); + if (showWarning) { + expect(mockOutputTerminal.write).toHaveBeenCalledWith( + expect.stringContaining('auto-focus'), + 'warn' + ); + } else { + expect(mockOutputTerminal.write).not.toHaveBeenCalledWith( + expect.stringContaining('auto focus'), + 'warn' + ); + } + } + ); + }); }); describe('stopSession', () => { it('will fire event', async () => { @@ -1281,11 +1315,11 @@ describe('JestExt', () => { } else { expect(updateSettingSpy).not.toHaveBeenCalled(); } - if (!validationResult) { + if (validationResult !== 'pass') { if (updateSettings) { - expect(messaging.systemErrorMessage).not.toHaveBeenCalled(); + expect(mockOutputTerminal.write).not.toHaveBeenCalledWith(expect.anything(), 'error'); } else { - expect(messaging.systemErrorMessage).toHaveBeenCalled(); + expect(mockOutputTerminal.write).toHaveBeenCalledWith(expect.anything(), 'error'); expect(sbUpdateMock).toHaveBeenCalledWith({ state: 'exec-error' }); } } diff --git a/tests/JestExt/process-listeners.test.ts b/tests/JestExt/process-listeners.test.ts index ca780528..f13487c8 100644 --- a/tests/JestExt/process-listeners.test.ts +++ b/tests/JestExt/process-listeners.test.ts @@ -10,7 +10,6 @@ import { DEFAULT_LONG_RUN_THRESHOLD, } from '../../src/JestExt/process-listeners'; import { cleanAnsi, toErrorString } from '../../src/helpers'; -import * as messaging from '../../src/messaging'; import { extensionName } from '../../src/appGlobals'; class DummyListener extends AbstractProcessListener { @@ -477,26 +476,34 @@ describe('jest process listeners', () => { ); }); describe('upon process exit', () => { - it('do nothing if not a watch process', () => { + it('not report error if not a watch process', () => { expect.hasAssertions(); mockProcess.request = { type: 'all-tests' }; - (messaging as jest.Mocked).systemErrorMessage = jest.fn(); const listener = new RunTestListener(mockSession); listener.onEvent(mockProcess, 'processClose', 1); - expect(messaging.systemErrorMessage).not.toHaveBeenCalled(); + expect(mockSession.context.onRunEvent.fire).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'exit', + error: undefined, + }) + ); }); - it('do nothing if watch run exit due to on-demand stop', () => { + it('not report error if watch run exit due to on-demand stop', () => { expect.hasAssertions(); mockProcess.request = { type: 'watch-tests' }; mockProcess.stopReason = 'on-demand'; - (messaging as jest.Mocked).systemErrorMessage = jest.fn(); const listener = new RunTestListener(mockSession); listener.onEvent(mockProcess, 'processClose', 1); - expect(messaging.systemErrorMessage).not.toHaveBeenCalled(); + expect(mockSession.context.onRunEvent.fire).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'exit', + error: undefined, + }) + ); }); describe('if watch exit not caused by on-demand stop', () => { beforeEach(() => { diff --git a/tests/extension-manager.test.ts b/tests/extension-manager.test.ts index 83745afa..5f18b748 100644 --- a/tests/extension-manager.test.ts +++ b/tests/extension-manager.test.ts @@ -13,6 +13,7 @@ import { startWizard } from '../src/setup-wizard'; import { VirtualWorkspaceFolder } from '../src/virtual-workspace-folder'; import { updateSetting } from '../src/Settings'; import { showQuickFix } from '../src/quick-fix'; +import { outputManager } from '../src/output-manager'; const mockEnabledWorkspaceFolders = jest.fn(); jest.mock('../src/workspace-manager', () => ({ @@ -1246,6 +1247,7 @@ describe('ExtensionManager', () => { get: jest.fn((key) => map.get(key)), update: jest.fn((key: string, value: boolean) => map.set(key, value)), }; + outputManager.validate = jest.fn(); extensionManager = createExtensionManager(['ws-1', 'ws-2'], { globalState }); ext1 = extensionManager.getByName('ws-1'); @@ -1265,6 +1267,7 @@ describe('ExtensionManager', () => { extensionManager.activate(); expect(ext1.activate).not.toHaveBeenCalled(); expect(ext2.activate).toHaveBeenCalled(); + expect(outputManager.validate).toHaveBeenCalled(); }); it('without active editor => do nothing', () => { (vscode.window.activeTextEditor as any) = undefined; diff --git a/tests/messaging.test.ts b/tests/messaging.test.ts deleted file mode 100644 index 453ec384..00000000 --- a/tests/messaging.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -jest.unmock('events'); -jest.unmock('../src/messaging'); - -import * as messaging from '../src/messaging'; -import { window, commands, Uri } from 'vscode'; - -describe('test system messaging', () => { - const mockShowErrorMessage = window.showErrorMessage as jest.Mock; - const mockShowWarningMessage = window.showWarningMessage as jest.Mock; - const mockExecCommands = commands.executeCommand as jest.Mock; - const mockUriParse = Uri.parse as jest.Mock; - - const thenable = () => { - /* do nothing */ - }; - beforeEach(() => { - jest.resetAllMocks(); - - mockShowErrorMessage.mockReturnValue({ then: thenable }); - mockShowWarningMessage.mockReturnValue({ then: thenable }); - }); - it('can show system message without actions', () => { - const validate = (mockF: jest.Mock) => { - expect(mockF.mock.calls.length).toBe(1); - const args = mockF.mock.calls[0]; - expect(args.length).toBe(1); - expect(args[0]).toBe('an error'); - }; - - messaging.systemWarningMessage('an error'); - validate(mockShowWarningMessage); - - messaging.systemErrorMessage('an error'); - validate(mockShowErrorMessage); - }); - - it('can show system message with actions', () => { - const action1: messaging.MessageAction = { title: 'action1', action: () => {} }; - const action2: messaging.MessageAction = { title: 'action2', action: () => {} }; - - const validate = (mockF: jest.Mock) => { - expect(mockF.mock.calls.length).toBe(1); - const args = mockF.mock.calls[0]; - expect(args.length).toBe(3); - expect(args[0]).toBe('an error'); - expect(args[1]).toBe('action1'); - expect(args[2]).toBe('action2'); - }; - - messaging.systemWarningMessage('an error', action1, action2); - validate(mockShowWarningMessage); - - messaging.systemErrorMessage('an error', action1, action2); - validate(mockShowErrorMessage); - }); - it('can open troubleshooting url via action', () => { - messaging.showTroubleshootingAction.action(); - expect(mockExecCommands.mock.calls.length).toBe(1); - expect(mockUriParse.mock.calls[0][0]).toBe(messaging.TROUBLESHOOTING_URL); - }); - it('can open longRunTroubleshooting url via action', () => { - messaging.showLongRunTroubleshootingAction.action(); - expect(mockExecCommands.mock.calls.length).toBe(1); - expect(mockUriParse.mock.calls[0][0]).toBe(messaging.LONG_RUN_TROUBLESHOOTING_URL); - }); - it('can handle user actions', () => { - let a = messaging._handleMessageActions(); - expect(a).not.toBeNull(); - a(); - - let done = false; - const action: messaging.MessageAction = { - title: 'action1', - action: () => { - done = true; - }, - }; - a = messaging._handleMessageActions([action]); - a(); - expect(done).toBe(false); - - a('action1'); - expect(done).toBe(true); - }); -}); diff --git a/tests/output-manager.test.ts b/tests/output-manager.test.ts index ec6c31a7..5ee0efa8 100644 --- a/tests/output-manager.test.ts +++ b/tests/output-manager.test.ts @@ -9,22 +9,31 @@ const mockWorkspace = { }; (vscode.workspace as jest.Mocked) = mockWorkspace; -jest.dontMock('../src/output-manager'); +// jest.dontMock('../src/output-manager'); +jest.unmock('../src/output-manager'); import { OutputManager, DefaultJestOutputSetting } from '../src/output-manager'; describe('OutputManager', () => { - const getOutputConfig = (om: OutputManager) => { - mockConfig.update.mockClear(); - om.save(); - const config = mockConfig.update.mock.calls[0][1]; - return config; + const mockWorkspaceConfig = (outputConfig?: any, openTesting = 'openOnTestStart') => { + mockConfig.get.mockImplementation((key: string) => { + if (key === 'outputConfig') { + return outputConfig; + } + if (key === 'openTesting') { + return openTesting; + } + return undefined; + }); }; + let showWarningMessageSpy: any; beforeEach(() => { jest.clearAllMocks(); - showWarningMessageSpy = jest.spyOn(vscode.window, 'showWarningMessage'); + showWarningMessageSpy = vscode.window.showWarningMessage as jest.Mocked; + // returns default config + mockWorkspaceConfig(); }); describe('constructor', () => { @@ -32,7 +41,7 @@ describe('OutputManager', () => { describe('can resolve outputConfig settings', () => { it.each` case | outputConfig | expected - ${1} | ${undefined} | ${DefaultJestOutputSetting} + ${1} | ${undefined} | ${{ ...DefaultJestOutputSetting, revealWithFocus: 'test-results' }} ${2} | ${'neutral'} | ${DefaultJestOutputSetting} ${3} | ${'terminal-based'} | ${{ revealOn: 'run', revealWithFocus: 'terminal', clearOnRun: 'none' }} ${4} | ${'test-results-based'} | ${{ revealOn: 'run', revealWithFocus: 'test-results', clearOnRun: 'none' }} @@ -41,9 +50,9 @@ describe('OutputManager', () => { ${7} | ${{ revealWithFocus: 'terminal', clearOnRun: 'terminal' }} | ${{ revealOn: 'run', revealWithFocus: 'terminal', clearOnRun: 'terminal' }} ${8} | ${'wrong-type'} | ${DefaultJestOutputSetting} `('case $case', ({ outputConfig, expected }) => { - mockConfig.get.mockImplementationOnce(() => outputConfig); + mockWorkspaceConfig(outputConfig); const om = new OutputManager(); - const config = getOutputConfig(om); + const { outputConfig: config } = om.outputConfigs(); expect(config).toEqual(expected); }); }); @@ -58,7 +67,7 @@ describe('OutputManager', () => { return undefined; }); const om = new OutputManager(); - const config = getOutputConfig(om); + const { outputConfig: config } = om.outputConfigs(); expect(config).toEqual({ revealOn: 'run', revealWithFocus: 'terminal', @@ -76,8 +85,8 @@ describe('OutputManager', () => { ${4} | ${'neverOpen'} | ${undefined} | ${undefined} | ${DefaultJestOutputSetting} ${5} | ${'neverOpen'} | ${true} | ${undefined} | ${{ revealOn: 'run', revealWithFocus: 'none', clearOnRun: 'terminal' }} ${6} | ${'openOnTestFailure'} | ${undefined} | ${undefined} | ${{ revealOn: 'error', revealWithFocus: 'test-results', clearOnRun: 'none' }} - ${7} | ${'openOnTestFailure'} | ${undefined} | ${'on-run'} | ${{ revealOn: 'run', revealWithFocus: 'test-results', clearOnRun: 'none' }} - ${8} | ${'openOnTestFailure'} | ${undefined} | ${'on-exec-error'} | ${{ revealOn: 'run', revealWithFocus: 'test-results', clearOnRun: 'none' }} + ${7} | ${'openOnTestFailure'} | ${undefined} | ${'on-run'} | ${{ revealOn: 'error', revealWithFocus: 'test-results', clearOnRun: 'none' }} + ${8} | ${'openOnTestFailure'} | ${undefined} | ${'on-exec-error'} | ${{ revealOn: 'error', revealWithFocus: 'test-results', clearOnRun: 'none' }} ${9} | ${'openOnTestFailure'} | ${true} | ${'off'} | ${{ revealOn: 'demand', revealWithFocus: 'none', clearOnRun: 'terminal' }} ${10} | ${'whatever'} | ${undefined} | ${undefined} | ${DefaultJestOutputSetting} ${11} | ${'openOnTestStart'} | ${undefined} | ${'whatever'} | ${{ revealOn: 'run', revealWithFocus: 'test-results', clearOnRun: 'none' }} @@ -97,7 +106,7 @@ describe('OutputManager', () => { } }); const om = new OutputManager(); - const config = getOutputConfig(om); + const { outputConfig: config } = om.outputConfigs(); expect(config).toEqual(expected); }); }); @@ -181,7 +190,7 @@ describe('OutputManager', () => { ${3} | ${'test-results'} | ${false} | ${true} ${4} | ${'both'} | ${true} | ${true} `('case $case', ({ clearOnRun, clearTerminal, clearTestResults }) => { - mockConfig.get.mockImplementationOnce(() => ({ ...DefaultJestOutputSetting, clearOnRun })); + mockWorkspaceConfig({ ...DefaultJestOutputSetting, clearOnRun }); const om = new OutputManager(); om.clearOutputOnRun(mockTerminalOutput); if (clearTerminal) { @@ -197,23 +206,97 @@ describe('OutputManager', () => { }); }); + describe('autoFocus', () => { + it.each` + case | outputConfig | openTesting | expected + ${1} | ${undefined} | ${'openOnTestStart'} | ${true} + ${2} | ${undefined} | ${'neverOpen'} | ${false} + ${3} | ${{ revealWithFocus: 'none' }} | ${'openOnTestStart'} | ${true} + ${4} | ${{ revealWithFocus: 'none' }} | ${'neverOpen'} | ${false} + ${5} | ${{ revealWithFocus: 'test-results' }} | ${'neverOpen'} | ${true} + ${6} | ${{ revealWithFocus: 'terminal' }} | ${'neverOpen'} | ${true} + `('case $case: isAutoFocus = $expected', ({ outputConfig, openTesting, expected }) => { + mockConfig.get.mockImplementation((key: string) => { + switch (key) { + case 'outputConfig': + return outputConfig; + case 'openTesting': + return openTesting; + } + }); + const om = new OutputManager(); + const result = om.isAutoFocus(); + expect(result).toEqual(expected); + }); + describe('disableAutoFocus', () => { + it('disableAutoFocus() will update both openTesting and outputConfig settings', async () => { + const om = new OutputManager(); + await om.disableAutoFocus(); + expect(mockConfig.update).toHaveBeenCalledWith( + 'openTesting', + 'neverOpen', + vscode.ConfigurationTarget.Workspace + ); + expect(mockConfig.update).toHaveBeenCalledWith( + 'outputConfig', + expect.objectContaining({ revealWithFocus: 'none' }) + ); + }); + it('during the update, validation will be skipped', async () => { + const om = new OutputManager(); + + let validateCount = 0; + mockConfig.update.mockImplementation(async () => { + // check if validation is skipped + await expect(om.validate()).resolves.toBeUndefined(); + validateCount++; + }); + + await om.disableAutoFocus(); + expect(validateCount).toEqual(2); + + mockConfig.update.mockReset(); + }); + }); + }); + describe('register', () => { - it('will register onDidChangeConfiguration and a save command', async () => { + it('will register onDidChangeConfiguration and commands', async () => { const om = new OutputManager(); const disposables = om.register(); - expect(disposables).toHaveLength(2); + expect(disposables).toHaveLength(3); expect(mockWorkspace.onDidChangeConfiguration).toHaveBeenCalled(); expect(vscode.commands.registerCommand).toHaveBeenCalledWith( expect.stringContaining('save-output-config'), expect.anything() ); + expect(vscode.commands.registerCommand).toHaveBeenCalledWith( + expect.stringContaining('disable-auto-focus'), + expect.anything() + ); const onDidChangeConfiguration = mockWorkspace.onDidChangeConfiguration.mock.calls[0][0]; expect(onDidChangeConfiguration).not.toBeUndefined(); - const saveCommand = (vscode.commands.registerCommand as jest.Mocked).mock.calls[0][1]; + expect(vscode.commands.registerCommand).toHaveBeenCalledTimes(2); + let matched = 0; const saveSpy = jest.spyOn(om, 'save'); - await saveCommand(); - expect(saveSpy).toHaveBeenCalled(); + const disableAutoFocusSpy = jest.spyOn(om, 'disableAutoFocus'); + + for (const [command, func] of (vscode.commands.registerCommand as jest.Mocked).mock + .calls) { + if (command.includes('save-output-config')) { + matched |= 0x01; + await func(); + expect(saveSpy).toHaveBeenCalled(); + } else if (command.includes('disable-auto-focus')) { + matched |= 0x02; + await func(); + expect(disableAutoFocusSpy).toHaveBeenCalled(); + } else { + throw new Error(`Unexpected command: ${command}`); + } + } + expect(matched).toBe(0x03); }); describe('onDidChangeConfiguration', () => { let om: OutputManager; @@ -226,26 +309,19 @@ describe('OutputManager', () => { mockChangeEvent = { affectsConfiguration: jest.fn() }; }); it('no-op if no outputConfig related changes detected', () => { - mockConfig.get.mockImplementationOnce(() => ({ revealOn: 'error' })); + mockWorkspaceConfig({ revealOn: 'error' }); mockChangeEvent.affectsConfiguration.mockReturnValue(false); onDidChangeConfiguration.call(om, mockChangeEvent); - const config = getOutputConfig(om); + const { outputConfig: config } = om.outputConfigs(); expect(config.revealOn).not.toBe('error'); }); it('if outputConfig related changes detected, will load new config', () => { - mockConfig.get.mockImplementation((key: string) => { - if (key === 'openTesting') { - return 'neverOpen'; - } - if (key === 'outputConfig') { - return { revealOn: 'error' }; - } - }); + mockWorkspaceConfig({ revealOn: 'error' }, 'neverOpen'); mockChangeEvent.affectsConfiguration.mockReturnValue(true); onDidChangeConfiguration.call(om, mockChangeEvent); - const config = getOutputConfig(om); + const { outputConfig: config } = om.outputConfigs(); expect(config.revealOn).toBe('error'); expect(showWarningMessageSpy).not.toHaveBeenCalled(); }); @@ -267,6 +343,39 @@ describe('OutputManager', () => { }); describe('validation and fix', () => { + describe('isTestResultsConfigsValid', () => { + it.each` + case | outputConfig | openTesting | expected + ${1} | ${undefined} | ${'openOnTestStart'} | ${true} + ${2} | ${undefined} | ${'neverOpen'} | ${true} + ${3} | ${{ revealWithFocus: 'none' }} | ${'neverOpen'} | ${true} + ${4} | ${{ revealWithFocus: 'none' }} | ${'openOnTestStart'} | ${false} + ${5} | ${{ revealWithFocus: 'none' }} | ${'openOnTestFailure'} | ${false} + ${6} | ${{ revealWithFocus: 'none' }} | ${'openExplorerOnTestStart'} | ${true} + ${7} | ${{ revealWithFocus: 'test-results' }} | ${'neverOpen'} | ${true} + ${8} | ${{ revealWithFocus: 'test-results' }} | ${'openOnTestStart'} | ${true} + ${9} | ${{ revealWithFocus: 'test-results' }} | ${'openOnTestFailure'} | ${false} + ${10} | ${{ revealWithFocus: 'test-results', revealOn: 'error' }} | ${'openOnTestFailure'} | ${true} + ${11} | ${{ revealWithFocus: 'test-results', revealOn: 'error' }} | ${'openOnTestStart'} | ${false} + ${12} | ${{ revealWithFocus: 'test-results', revealOn: 'demand' }} | ${'openOnTestStart'} | ${false} + ${13} | ${{ revealWithFocus: 'test-results', revealOn: 'demand' }} | ${'openOnTestFailure'} | ${false} + ${14} | ${{ revealWithFocus: 'terminal' }} | ${'neverOpen'} | ${true} + ${15} | ${{ revealWithFocus: 'terminal' }} | ${'openOnTestStart'} | ${false} + ${16} | ${{ revealWithFocus: 'terminal' }} | ${'openOnTestFailure'} | ${false} + ${17} | ${{ revealWithFocus: 'terminal' }} | ${'openExplorerOnTestStart'} | ${true} + `('case $case: isAutoFocus = $expected', ({ outputConfig, openTesting, expected }) => { + mockConfig.get.mockImplementation((key: string) => { + switch (key) { + case 'outputConfig': + return outputConfig; + case 'openTesting': + return openTesting; + } + }); + const om = new OutputManager(); + expect(om.isTestResultsConfigsValid()).toEqual(expected); + }); + }); it('when no conflict, will return true', async () => { mockConfig.get.mockImplementation((key: string) => { if (key === 'openTesting') { @@ -282,14 +391,7 @@ describe('OutputManager', () => { }); describe('when conflict detected', () => { beforeEach(() => { - mockConfig.get.mockImplementation((key: string) => { - if (key === 'openTesting') { - return 'openOnTestStart'; - } - if (key === 'outputConfig') { - return { revealOn: 'error' }; - } - }); + mockWorkspaceConfig({ revealOn: 'error' }); }); it('will show warning message', async () => { showWarningMessageSpy.mockResolvedValue(undefined); @@ -361,6 +463,10 @@ describe('OutputManager', () => { const item = items.find((item) => item.label.includes('Fix test-results')); return item; }); + mockConfig.update.mockImplementation(() => { + return Promise.resolve(); + }); + const om = new OutputManager(); await expect(om.validate()).resolves.toEqual(true); expect(showWarningMessageSpy).toHaveBeenCalled();