diff --git a/packages/browser/src/node/rpc.ts b/packages/browser/src/node/rpc.ts index 3566a7848176..80af962ed797 100644 --- a/packages/browser/src/node/rpc.ts +++ b/packages/browser/src/node/rpc.ts @@ -409,13 +409,11 @@ export function setupBrowserRpc(globalServer: ParentBrowserProject, defaultMocke function cloneByOwnProperties(value: any) { // Clones the value's properties into a new Object. The simpler approach of // Object.assign() won't work in the case that properties are not enumerable. - return Object.getOwnPropertyNames(value).reduce( - (clone, prop) => ({ - ...clone, - [prop]: value[prop], - }), - {}, - ) + const clone: Record = {} + for (const prop of Object.getOwnPropertyNames(value)) { + clone[prop] = value[prop] + } + return clone } /** diff --git a/packages/runner/src/utils/collect.ts b/packages/runner/src/utils/collect.ts index 0a727488708f..961503a3d6cb 100644 --- a/packages/runner/src/utils/collect.ts +++ b/packages/runner/src/utils/collect.ts @@ -18,6 +18,10 @@ export function interpretTaskModes( allowOnly?: boolean, ): void { const matchedLocations: number[] = [] + const testLocationsSet = testLocations !== undefined && testLocations.length !== 0 + ? new Set(testLocations) + : undefined + const testIdsSet = testIds ? new Set(testIds) : undefined const traverseSuite = (suite: Suite, parentIsOnly?: boolean, parentMatchedWithLocation?: boolean) => { const suiteIsOnly = parentIsOnly || suite.mode === 'only' @@ -54,8 +58,8 @@ export function interpretTaskModes( // Match test location against provided locations, only run if present // in `testLocations`. Note: if `includeTaskLocation` is not enabled, // all test will be skipped. - if (testLocations !== undefined && testLocations.length !== 0) { - if (t.location && testLocations?.includes(t.location.line)) { + if (testLocationsSet !== undefined) { + if (t.location && testLocationsSet.has(t.location.line)) { t.mode = 'run' matchedLocations.push(t.location.line) hasLocationMatch = true @@ -72,7 +76,7 @@ export function interpretTaskModes( if (namePattern && !getTaskFullName(t).match(namePattern)) { t.mode = 'skip' } - if (testIds && !testIds.includes(t.id)) { + if (testIdsSet && !testIdsSet.has(t.id)) { t.mode = 'skip' } if (testTagsFilter && !testTagsFilter(t.tags || [])) { diff --git a/packages/ui/client/components/dashboard/ErrorEntry.vue b/packages/ui/client/components/dashboard/ErrorEntry.vue index a593e55ca102..c6151b80de50 100644 --- a/packages/ui/client/components/dashboard/ErrorEntry.vue +++ b/packages/ui/client/components/dashboard/ErrorEntry.vue @@ -20,13 +20,13 @@ defineProps<{ This error originated in {{ error.VITEST_TEST_PATH }} test file. It doesn't mean the error was thrown inside the file itself, but while it was running.

- The latest test that might've caused the error is {{ error.VITEST_TEST_NAME }}. It might mean one of the following:
+ The last test to run before this error was "{{ error.VITEST_TEST_NAME }}. This means either:
diff --git a/packages/vitest/src/node/ast-collect.ts b/packages/vitest/src/node/ast-collect.ts index 724d27fae3c4..78488ccc5c3d 100644 --- a/packages/vitest/src/node/ast-collect.ts +++ b/packages/vitest/src/node/ast-collect.ts @@ -49,6 +49,16 @@ interface LocalCallDefinition { const debug = createDebugger('vitest:ast-collect-info') const verbose = createDebugger('vitest:ast-collect-verbose') +const INTERMEDIATE_CALL_PROPERTIES = new Set([ + 'each', + 'for', + 'skipIf', + 'runIf', + 'extend', + 'scoped', + 'override', +]) + function isTestFunctionName(name: string) { return name === 'it' || name === 'test' || name.startsWith('test') || name.endsWith('Test') } @@ -153,7 +163,7 @@ function astParseFile(filepath: string, code: string) { const properties = getProperties(callee) const property = callee?.property?.name // intermediate calls like .each(), .for() will be picked up in the next iteration - if (property && ['each', 'for', 'skipIf', 'runIf', 'extend', 'scoped', 'override'].includes(property)) { + if (property && INTERMEDIATE_CALL_PROPERTIES.has(property)) { return } // skip properties on return values of calls - e.g., test('name', fn).skip() diff --git a/packages/vitest/src/node/pools/pool.ts b/packages/vitest/src/node/pools/pool.ts index eeccdc8aa825..bb6cab29c8b8 100644 --- a/packages/vitest/src/node/pools/pool.ts +++ b/packages/vitest/src/node/pools/pool.ts @@ -346,7 +346,7 @@ function deepEqual(obj1: any, obj2: any): boolean { } for (const key of keys1) { - if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) { + if (!Object.prototype.hasOwnProperty.call(obj2, key) || !deepEqual(obj1[key], obj2[key])) { return false } } diff --git a/packages/vitest/src/node/printError.ts b/packages/vitest/src/node/printError.ts index 3fc6f50ae78c..53135a0eaba0 100644 --- a/packages/vitest/src/node/printError.ts +++ b/packages/vitest/src/node/printError.ts @@ -237,11 +237,11 @@ function printErrorInner( if (testName) { logger.error( c.red( - `The latest test that might've caused the error is "${c.bold( + `The last test to run before this error was "${c.bold( testName, - )}". It might mean one of the following:` - + '\n- The error was thrown, while Vitest was running this test.' - + '\n- If the error occurred after the test had been completed, this was the last documented test before it was thrown.', + )}". This means either:` + + '\n- the error was thrown while Vitest was running this test, or' + + '\n- the error was thrown after the test completed, and this was the most recent test at that point.', ), ) } diff --git a/packages/vitest/src/node/reporters/base.ts b/packages/vitest/src/node/reporters/base.ts index b1543f1f0308..b5442314c44f 100644 --- a/packages/vitest/src/node/reporters/base.ts +++ b/packages/vitest/src/node/reporters/base.ts @@ -679,10 +679,21 @@ export abstract class BaseReporter implements Reporter { return } - const dangerImports = allImports.filter(imp => imp.totalTime >= thresholds.danger) - const warnImports = allImports.filter(imp => imp.totalTime >= thresholds.warn) - const hasDangerImports = dangerImports.length > 0 - const hasWarnImports = warnImports.length > 0 + let dangerImportsCount = 0 + let hasWarnImports = false + let totalSelfTime = 0 + let totalTotalTime = 0 + for (const imp of allImports) { + if (imp.totalTime >= thresholds.danger) { + dangerImportsCount++ + } + if (imp.totalTime >= thresholds.warn) { + hasWarnImports = true + } + totalSelfTime += imp.selfTime + totalTotalTime += imp.totalTime + } + const hasDangerImports = dangerImportsCount > 0 // Determine if we should print const shouldFail = failOnDanger && hasDangerImports @@ -695,9 +706,6 @@ export abstract class BaseReporter implements Reporter { const maxTotalTime = sortedImports[0].totalTime const limit = this.ctx.config.experimental.importDurations.limit const topImports = sortedImports.slice(0, limit) - - const totalSelfTime = allImports.reduce((sum, imp) => sum + imp.selfTime, 0) - const totalTotalTime = allImports.reduce((sum, imp) => sum + imp.totalTime, 0) const slowestImport = sortedImports[0] this.log() @@ -750,7 +758,7 @@ export abstract class BaseReporter implements Reporter { if (shouldFail) { this.log() this.ctx.logger.error( - `ERROR: ${dangerImports.length} import(s) exceeded the danger threshold of ${thresholds.danger}ms`, + `ERROR: ${dangerImportsCount} import(s) exceeded the danger threshold of ${thresholds.danger}ms`, ) process.exitCode = 1 } @@ -993,7 +1001,7 @@ function deepEqual(a: any, b: any): boolean { } for (const key of keysA) { - if (!keysB.includes(key) || !deepEqual(a[key], b[key])) { + if (!Object.prototype.hasOwnProperty.call(b, key) || !deepEqual(a[key], b[key])) { return false } } diff --git a/test/browser/specs/errors.test.ts b/test/browser/specs/errors.test.ts index 22851a1ab85b..f0c40b6f84af 100644 --- a/test/browser/specs/errors.test.ts +++ b/test/browser/specs/errors.test.ts @@ -11,7 +11,7 @@ test('prints correct unhandled error stack', async () => { expect(stderr).toContain('throw-unhandled-error.test.ts:9:10') expect(stderr).toContain('This error originated in "throw-unhandled-error.test.ts" test file.') - expect(stderr).toContain('The latest test that might\'ve caused the error is "unhandled exception".') + expect(stderr).toContain('The last test to run before this error was "unhandled exception".') if (instances.some(({ browser }) => browser === 'webkit')) { expect(stderr).toContain('throw-unhandled-error.test.ts:9:20') diff --git a/test/e2e/test/reporters/__snapshots__/junit.test.ts.snap b/test/e2e/test/reporters/__snapshots__/junit.test.ts.snap index b8e60be0cea9..1271a53c0863 100644 --- a/test/e2e/test/reporters/__snapshots__/junit.test.ts.snap +++ b/test/e2e/test/reporters/__snapshots__/junit.test.ts.snap @@ -73,9 +73,9 @@ Error: uncaught timer ❯ Timeout._onTimeout multi.test.ts:7:11 This error originated in "multi.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. -The latest test that might've caused the error is "triggers two rejections and one uncaught exception". It might mean one of the following: -- The error was thrown, while Vitest was running this test. -- If the error occurred after the test had been completed, this was the last documented test before it was thrown. +The last test to run before this error was "triggers two rejections and one uncaught exception". This means either: +- the error was thrown while Vitest was running this test, or +- the error was thrown after the test completed, and this was the most recent test at that point. @@ -84,9 +84,9 @@ Error: first rejection ❯ multi.test.ts:4:23 This error originated in "multi.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. -The latest test that might've caused the error is "triggers two rejections and one uncaught exception". It might mean one of the following: -- The error was thrown, while Vitest was running this test. -- If the error occurred after the test had been completed, this was the last documented test before it was thrown. +The last test to run before this error was "triggers two rejections and one uncaught exception". This means either: +- the error was thrown while Vitest was running this test, or +- the error was thrown after the test completed, and this was the most recent test at that point. @@ -95,9 +95,9 @@ Error: second rejection ❯ multi.test.ts:5:23 This error originated in "multi.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. -The latest test that might've caused the error is "triggers two rejections and one uncaught exception". It might mean one of the following: -- The error was thrown, while Vitest was running this test. -- If the error occurred after the test had been completed, this was the last documented test before it was thrown. +The last test to run before this error was "triggers two rejections and one uncaught exception". This means either: +- the error was thrown while Vitest was running this test, or +- the error was thrown after the test completed, and this was the most recent test at that point. @@ -234,9 +234,9 @@ Error: rejection from space-1 ❯ test/reject.test.ts:4:23 This error originated in "test/reject.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. -The latest test that might've caused the error is "triggers a rejection from project 1". It might mean one of the following: -- The error was thrown, while Vitest was running this test. -- If the error occurred after the test had been completed, this was the last documented test before it was thrown. +The last test to run before this error was "triggers a rejection from project 1". This means either: +- the error was thrown while Vitest was running this test, or +- the error was thrown after the test completed, and this was the most recent test at that point. @@ -245,9 +245,9 @@ Error: rejection from space-2 ❯ test/reject.test.ts:4:23 This error originated in "test/reject.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. -The latest test that might've caused the error is "triggers a rejection from project 2". It might mean one of the following: -- The error was thrown, while Vitest was running this test. -- If the error occurred after the test had been completed, this was the last documented test before it was thrown. +The last test to run before this error was "triggers a rejection from project 2". This means either: +- the error was thrown while Vitest was running this test, or +- the error was thrown after the test completed, and this was the most recent test at that point.