From 641f277e2e3f419844f45a2d9644d1a852d6e3f3 Mon Sep 17 00:00:00 2001 From: Mathias Lorenzen Date: Sat, 30 Sep 2023 02:41:08 +0200 Subject: [PATCH 1/2] feat: update test statuses as they are received --- .eslintrc.js | 1 + .hintrc | 8 ++++++ src/JestExt/core.ts | 7 ++--- src/JestExt/process-listeners.ts | 24 ++++++++++++---- src/JestExt/types.ts | 9 ++++-- src/TestResults/TestResult.ts | 35 ++++++++++------------- src/TestResults/TestResultProvider.ts | 8 ++---- src/helpers.ts | 6 +++- src/reporter.ts | 20 +++++++++++-- src/test-provider/test-item-data.ts | 1 - src/test-provider/test-provider-helper.ts | 7 ++--- tsconfig.json | 1 + 12 files changed, 80 insertions(+), 47 deletions(-) create mode 100644 .hintrc diff --git a/.eslintrc.js b/.eslintrc.js index 0886390c1..303ad2bec 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -21,6 +21,7 @@ module.exports = { '@typescript-eslint/ban-types': 'off', // customize argument ignore pattern 'no-unused-vars': 'off', + '@typescript-config/strict': 'off', '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], }, overrides: [ diff --git a/.hintrc b/.hintrc new file mode 100644 index 000000000..12c4655b4 --- /dev/null +++ b/.hintrc @@ -0,0 +1,8 @@ +{ + "extends": [ + "development" + ], + "hints": { + "typescript-config/strict": "off" + } +} \ No newline at end of file diff --git a/src/JestExt/core.ts b/src/JestExt/core.ts index ae628fbc4..46c951223 100644 --- a/src/JestExt/core.ts +++ b/src/JestExt/core.ts @@ -1,6 +1,4 @@ import * as vscode from 'vscode'; -import { JestTotalResults } from 'jest-editor-support'; - import { statusBar, StatusBar, Mode, StatusBarUpdate, SBTestStats } from '../StatusBar'; import { TestResultProvider, @@ -44,6 +42,7 @@ import { MessageAction } from '../messaging'; import { getExitErrorDef } from '../errors'; import { WorkspaceManager, isInFolder } from '../workspace-manager'; import { ansiEsc, JestOutputTerminal } from './output-terminal'; +import type { AggregatedResult } from '@jest/reporters'; interface RunTestPickItem extends vscode.QuickPickItem { id: DebugTestIdentifier; @@ -864,10 +863,10 @@ export class JestExt { this.coverageOverlay.updateVisibleEditors(); }); } - private updateWithData(data: JestTotalResults, process: JestProcessInfo): void { + private updateWithData(data: AggregatedResult, process: JestProcessInfo): void { const noAnsiData = resultsWithoutAnsiEscapeSequence(data); const normalizedData = resultsWithLowerCaseWindowsDriveLetters(noAnsiData); - this._updateCoverageMap(normalizedData.coverageMap); + this._updateCoverageMap(normalizedData.coverageMap?.data); const statusList = this.testResultProvider.updateTestResults(normalizedData, process); diff --git a/src/JestExt/process-listeners.ts b/src/JestExt/process-listeners.ts index 1ba28d7a4..f159115a0 100644 --- a/src/JestExt/process-listeners.ts +++ b/src/JestExt/process-listeners.ts @@ -1,13 +1,13 @@ import * as vscode from 'vscode'; -import { JestTotalResults } from 'jest-editor-support'; import { cleanAnsi, toErrorString } from '../helpers'; import { JestProcess, JestProcessEvent } from '../JestProcessManagement'; import { ListenerSession, ListTestFilesCallback } from './process-session'; import { Logging } from '../logging'; -import { JestRunEvent } from './types'; +import { JestRunEvent, TestResultJestRunEventArguments } from './types'; import { MonitorLongRun } from '../Settings'; import { extensionName } from '../appGlobals'; import { RunShell } from './run-shell'; +import type { AggregatedResult } from '@jest/reporters'; // command not found error for anything but "jest", as it most likely not be caused by env issue const POSSIBLE_ENV_ERROR_REGEX = @@ -45,7 +45,7 @@ export class AbstractProcessListener { break; } case 'executableJSON': { - this.onExecutableJSON(jestProcess, args[0] as JestTotalResults); + this.onExecutableJSON(jestProcess, args[0] as AggregatedResult); break; } case 'executableOutput': { @@ -85,7 +85,7 @@ export class AbstractProcessListener { this.CmdNotFoundEnv = true; } } - protected onExecutableJSON(_process: JestProcess, _data: JestTotalResults): void { + protected onExecutableJSON(_process: JestProcess, _data: AggregatedResult): void { // no default behavior... } protected onExecutableOutput(_process: JestProcess, _data: string, _raw: string): void { @@ -180,6 +180,7 @@ const IS_OUTSIDE_REPOSITORY_REGEXP = const WATCH_IS_NOT_SUPPORTED_REGEXP = /^s*--watch is not supported without git\/hg, please use --watchAlls*/im; const RUN_EXEC_ERROR = /onRunComplete: execError: (.*)/im; +const ON_TEST_RESULT = /onTestResult: test: (.*)/im; const RUN_START_TEST_SUITES_REGEX = /onRunStart: numTotalTestSuites: ((\d)+)/im; const CONTROL_MESSAGES = /^(onRunStart|onRunComplete|Test results written to)[^\n]+\n/gim; @@ -299,7 +300,7 @@ export class RunTestListener extends AbstractProcessListener { } //=== event handlers === - protected onExecutableJSON(process: JestProcess, data: JestTotalResults): void { + protected onExecutableJSON(process: JestProcess, data: AggregatedResult): void { this.session.context.updateWithData(data, process); } @@ -314,6 +315,8 @@ export class RunTestListener extends AbstractProcessListener { this.onRunEvent.fire({ type: 'data', process, text: message, raw: cleaned }); + this.handleTestResult(process, message); + this.handleRunComplete(process, message); this.handleWatchNotSupportedError(process, message); @@ -335,6 +338,17 @@ export class RunTestListener extends AbstractProcessListener { this.onRunEvent.fire({ type: 'start', process }); } } + protected handleTestResult(process: JestProcess, output: string) { + if (output.includes('onTestResult')) { + const data = output.match(ON_TEST_RESULT)?.[1]; + if (!data) { + return; + } + + const parsedData = JSON.parse(data) as TestResultJestRunEventArguments; + this.onExecutableJSON(process, parsedData.aggregatedResult); + } + } protected handleRunComplete(process: JestProcess, output: string): void { if (output.includes('onRunComplete')) { this.runEnded(); diff --git a/src/JestExt/types.ts b/src/JestExt/types.ts index 4189a2dce..93c065bde 100644 --- a/src/JestExt/types.ts +++ b/src/JestExt/types.ts @@ -1,4 +1,4 @@ -import { JestTotalResults, ProjectWorkspace } from 'jest-editor-support'; +import { ProjectWorkspace } from 'jest-editor-support'; import * as vscode from 'vscode'; import { LoggingFactory } from '../logging'; @@ -8,6 +8,8 @@ import { JestProcessInfo } from '../JestProcessManagement'; import { JestOutputTerminal } from './output-terminal'; import { TestIdentifier } from '../TestResults'; +import type { AggregatedResult } from '@jest/reporters'; + export enum WatchMode { None = 'none', Watch = 'watch', @@ -31,6 +33,9 @@ export interface JestExtSessionContext extends JestExtContext { export interface RunEventBase { process: JestProcessInfo; } +export type TestResultJestRunEventArguments = { + aggregatedResult: AggregatedResult; +}; export type JestRunEvent = RunEventBase & ( | { type: 'scheduled' } @@ -47,7 +52,7 @@ export interface JestSessionEvents { onTestSessionStopped: vscode.EventEmitter; } export interface JestExtProcessContextRaw extends JestExtContext { - updateWithData: (data: JestTotalResults, process: JestProcessInfo) => void; + updateWithData: (data: AggregatedResult, process: JestProcessInfo) => void; onRunEvent: vscode.EventEmitter; } export type JestExtProcessContext = Readonly; diff --git a/src/TestResults/TestResult.ts b/src/TestResults/TestResult.ts index ec1d66295..ac71080b9 100644 --- a/src/TestResults/TestResult.ts +++ b/src/TestResults/TestResult.ts @@ -1,9 +1,9 @@ import { TestReconciliationStateType } from './TestReconciliationState'; -import { JestFileResults, JestTotalResults } from 'jest-editor-support'; -import { FileCoverage } from 'istanbul-lib-coverage'; +import { CoverageMap, FileCoverage } from 'istanbul-lib-coverage'; import * as path from 'path'; import { cleanAnsi, toLowerCaseDriveLetter } from '../helpers'; import { MatchEvent } from './match-node'; +import type { AggregatedResult, TestResult as JestTestResult } from '@jest/reporters'; export interface Location { /** Zero-based column number */ @@ -43,12 +43,12 @@ export interface TestResult extends LocationRange { assertionHistory?: MatchEvent[]; } -function testResultWithLowerCaseWindowsDriveLetter(testResult: JestFileResults): JestFileResults { - const newFilePath = toLowerCaseDriveLetter(testResult.name); +function testResultWithLowerCaseWindowsDriveLetter(testResult: JestTestResult): JestTestResult { + const newFilePath = toLowerCaseDriveLetter(testResult.testFilePath); if (newFilePath) { return { ...testResult, - name: newFilePath, + testFilePath: newFilePath, }; } @@ -56,8 +56,8 @@ function testResultWithLowerCaseWindowsDriveLetter(testResult: JestFileResults): } export const testResultsWithLowerCaseWindowsDriveLetters = ( - testResults: JestFileResults[] -): JestFileResults[] => { + testResults: JestTestResult[] +): JestTestResult[] => { if (!testResults) { return testResults; } @@ -77,20 +77,15 @@ function fileCoverageWithLowerCaseWindowsDriveLetter(fileCoverage: FileCoverage) return fileCoverage; } -// TODO should fix jest-editor-support type declaration, the coverageMap should not be "any" -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const coverageMapWithLowerCaseWindowsDriveLetters = (data: JestTotalResults): any => { +export const coverageMapWithLowerCaseWindowsDriveLetters = (data: AggregatedResult): CoverageMap | undefined => { if (!data.coverageMap) { return; } // eslint-disable-next-line @typescript-eslint/no-explicit-any const result: any = {}; - const filePaths = Object.keys(data.coverageMap); - for (const filePath of filePaths) { - const newFileCoverage = fileCoverageWithLowerCaseWindowsDriveLetter( - data.coverageMap[filePath] as FileCoverage - ); + for (const filePath of data.coverageMap.files()) { + const newFileCoverage = fileCoverageWithLowerCaseWindowsDriveLetter(data.coverageMap.fileCoverageFor(filePath)); result[newFileCoverage.path] = newFileCoverage; } @@ -105,8 +100,8 @@ export const coverageMapWithLowerCaseWindowsDriveLetters = (data: JestTotalResul * @param data Parsed JSON results */ export const resultsWithLowerCaseWindowsDriveLetters = ( - data: JestTotalResults -): JestTotalResults => { + data: AggregatedResult +): AggregatedResult => { if (path.sep === '\\') { return { ...data, @@ -121,7 +116,7 @@ export const resultsWithLowerCaseWindowsDriveLetters = ( /** * Removes ANSI escape sequence characters from test results in order to get clean messages */ -export const resultsWithoutAnsiEscapeSequence = (data: JestTotalResults): JestTotalResults => { +export const resultsWithoutAnsiEscapeSequence = (data: AggregatedResult): AggregatedResult => { if (!data || !data.testResults) { return data; } @@ -130,8 +125,8 @@ export const resultsWithoutAnsiEscapeSequence = (data: JestTotalResults): JestTo ...data, testResults: data.testResults.map((result) => ({ ...result, - message: cleanAnsi(result.message), - assertionResults: result.assertionResults.map((assertion) => ({ + failureMessage: cleanAnsi(result.failureMessage), + testResults: result.testResults.map((assertion) => ({ ...assertion, failureMessages: assertion.failureMessages.map((message) => cleanAnsi(message)), })), diff --git a/src/TestResults/TestResultProvider.ts b/src/TestResults/TestResultProvider.ts index be6bd5c55..853663a66 100644 --- a/src/TestResults/TestResultProvider.ts +++ b/src/TestResults/TestResultProvider.ts @@ -1,6 +1,5 @@ import { TestReconciler, - JestTotalResults, TestFileAssertionStatus, IParseResults, parse, @@ -9,6 +8,7 @@ import { ItBlock, SnapshotParserOptions, } from 'jest-editor-support'; +import type { AggregatedResult } from '@jest/reporters'; import { TestReconciliationState, TestReconciliationStateType } from './TestReconciliationState'; import { TestResult, TestResultStatusInfo } from './TestResult'; import * as match from './match-by-context'; @@ -361,16 +361,12 @@ export class TestResultProvider { * @param filePath * @returns valid sorted test result or undefined if the file is not a test. */ - getSortedResults(filePath: string): SortedTestResults | undefined { if (this.isTestFile(filePath) === 'no') { return; } const record = this.testSuites.get(filePath) ?? this.addTestSuiteRecord(filePath); - if (record.sorted) { - return record.sorted; - } const sorted: SortedTestResults = { fail: [], @@ -398,7 +394,7 @@ export class TestResultProvider { return sorted; } - updateTestResults(data: JestTotalResults, process: JestProcessInfo): TestFileAssertionStatus[] { + updateTestResults(data: AggregatedResult, process: JestProcessInfo): TestFileAssertionStatus[] { const results = this.reconciler.updateFileWithJestStatus(data); results?.forEach((r) => { const record = this.testSuites.get(r.file) ?? this.addTestSuiteRecord(r.file); diff --git a/src/helpers.ts b/src/helpers.ts index 2a2de0258..e63dfe1dc 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -134,7 +134,11 @@ export function escapeRegExp(str: string): string { /** * ANSI colors/characters cleaning based on http://stackoverflow.com/questions/25245716/remove-all-ansi-colors-styles-from-strings */ -export function cleanAnsi(str: string): string { +export function cleanAnsi(str: string|undefined|null): string { + if(!str) { + return ""; + } + return str.replace( // eslint-disable-next-line no-control-regex /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, diff --git a/src/reporter.ts b/src/reporter.ts index fad88f4bc..1d2fd09ec 100644 --- a/src/reporter.ts +++ b/src/reporter.ts @@ -1,10 +1,17 @@ // Custom Jest reporter used by jest-vscode extension -import type { AggregatedResult } from '@jest/test-result'; -import { Reporter, TestContext } from '@jest/reporters'; +import { + Reporter, + TestContext, + ReporterOnStartOptions, + Test, + TestResult, + AggregatedResult, +} from '@jest/reporters'; +import type { TestResultJestRunEventArguments } from './JestExt'; class VSCodeJestReporter implements Reporter { - onRunStart(aggregatedResults: AggregatedResult): void { + onRunStart(aggregatedResults: AggregatedResult, _options: ReporterOnStartOptions): void { process.stderr.write( `onRunStart: numTotalTestSuites: ${aggregatedResults.numTotalTestSuites}\r\n` ); @@ -18,9 +25,16 @@ class VSCodeJestReporter implements Reporter { process.stderr.write('onRunComplete\r\n'); } } + getLastError(): Error | undefined { return; } + + onTestResult(_test: Test, _testResult: TestResult, aggregatedResult: AggregatedResult): void { + process.stderr.write( + `onTestResult: test: ${JSON.stringify({ aggregatedResult: aggregatedResult } as TestResultJestRunEventArguments)}\r\n` + ); + } } module.exports = VSCodeJestReporter; diff --git a/src/test-provider/test-item-data.ts b/src/test-provider/test-item-data.ts index a1bd65396..b5a7c1ec5 100644 --- a/src/test-provider/test-item-data.ts +++ b/src/test-provider/test-item-data.ts @@ -532,7 +532,6 @@ const isEmpty = (node?: ItemNodeType): boolean => { return true; }; -// type AssertNode = NodeType; abstract class TestResultData extends TestItemDataBase { constructor(readonly context: JestTestProviderContext, name: string) { super(context, name); diff --git a/src/test-provider/test-provider-helper.ts b/src/test-provider/test-provider-helper.ts index 5b3179bc1..92defc955 100644 --- a/src/test-provider/test-provider-helper.ts +++ b/src/test-provider/test-provider-helper.ts @@ -191,11 +191,8 @@ export class JestTestRun implements JestExtOutput, TestRunProtocol { return this.options.end(); } - if (this.parentRun) { - this.parentRun.end(); - if (isVscodeRun(this.parentRun)) { - this.parentRun = undefined; - } + if (this.parentRun && isVscodeRun(this.parentRun)) { + this.parentRun = undefined; } this.options?.onEnd?.(); diff --git a/tsconfig.json b/tsconfig.json index ece4a80ab..8594b8999 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ "noUnusedLocals": true, "allowSyntheticDefaultImports": true, "noUnusedParameters": true, + "forceConsistentCasingInFileNames": true, "target": "es6", "outDir": "out", "lib": ["es6", "es7", "es2020", "es2021"], From 02859d1e73eeb20f3beda1abde24876ffafdccdd Mon Sep 17 00:00:00 2001 From: Mathias Lykkegaard Lorenzen Date: Sat, 30 Sep 2023 02:55:06 +0200 Subject: [PATCH 2/2] Update .eslintrc.js --- .eslintrc.js | 1 - 1 file changed, 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 303ad2bec..0886390c1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -21,7 +21,6 @@ module.exports = { '@typescript-eslint/ban-types': 'off', // customize argument ignore pattern 'no-unused-vars': 'off', - '@typescript-config/strict': 'off', '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], }, overrides: [