Skip to content

Commit 121b161

Browse files
authoredOct 18, 2024··
fix(vitest): print warnings form Vite plugins (#6724)
1 parent 0a2132b commit 121b161

File tree

12 files changed

+187
-25
lines changed

12 files changed

+187
-25
lines changed
 

‎packages/browser/src/node/index.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ export async function createBrowserServer(
3232

3333
const logLevel = (process.env.VITEST_BROWSER_DEBUG as 'info') ?? 'info'
3434

35-
const logger = createViteLogger(logLevel)
35+
const logger = createViteLogger(project.logger, logLevel, {
36+
allowClearScreen: false,
37+
})
3638

3739
const vite = await createViteServer({
3840
...project.options, // spread project config inlined in root workspace config

‎packages/browser/src/node/plugin.ts

+20-14
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { fileURLToPath } from 'node:url'
22
import { createRequire } from 'node:module'
33
import { lstatSync, readFileSync } from 'node:fs'
44
import type { Stats } from 'node:fs'
5-
import { basename, extname, resolve } from 'pathe'
5+
import { basename, dirname, extname, resolve } from 'pathe'
66
import sirv from 'sirv'
77
import type { WorkspaceProject } from 'vitest/node'
88
import { getFilePoolName, resolveApiServerConfig, resolveFsAllow, distDir as vitestDist } from 'vitest/node'
@@ -23,6 +23,12 @@ export default (browserServer: BrowserServer, base = '/'): Plugin[] => {
2323
const distRoot = resolve(pkgRoot, 'dist')
2424
const project = browserServer.project
2525

26+
function isPackageExists(pkg: string, root: string) {
27+
return browserServer.project.ctx.packageInstaller.isPackageExists?.(pkg, {
28+
paths: [root],
29+
})
30+
}
31+
2632
return [
2733
{
2834
enforce: 'pre',
@@ -211,14 +217,14 @@ export default (browserServer: BrowserServer, base = '/'): Plugin[] => {
211217
const coverage = project.ctx.config.coverage
212218
const provider = coverage.provider
213219
if (provider === 'v8') {
214-
const path = tryResolve('@vitest/coverage-v8', [project.ctx.config.root])
220+
const path = tryResolve('@vitest/coverage-v8', [project.config.root])
215221
if (path) {
216222
entries.push(path)
217223
exclude.push('@vitest/coverage-v8/browser')
218224
}
219225
}
220226
else if (provider === 'istanbul') {
221-
const path = tryResolve('@vitest/coverage-istanbul', [project.ctx.config.root])
227+
const path = tryResolve('@vitest/coverage-istanbul', [project.config.root])
222228
if (path) {
223229
entries.push(path)
224230
exclude.push('@vitest/coverage-istanbul')
@@ -239,18 +245,18 @@ export default (browserServer: BrowserServer, base = '/'): Plugin[] => {
239245
'@vitest/browser > @testing-library/dom',
240246
]
241247

242-
const react = tryResolve('vitest-browser-react', [project.ctx.config.root])
243-
if (react) {
244-
include.push(react)
245-
}
246-
const vue = tryResolve('vitest-browser-vue', [project.ctx.config.root])
247-
if (vue) {
248-
include.push(vue)
249-
}
248+
const fileRoot = browserTestFiles[0] ? dirname(browserTestFiles[0]) : project.config.root
250249

251-
const svelte = tryResolve('vitest-browser-svelte', [project.ctx.config.root])
250+
const svelte = isPackageExists('vitest-browser-svelte', fileRoot)
252251
if (svelte) {
253-
exclude.push(svelte)
252+
exclude.push('vitest-browser-svelte')
253+
}
254+
255+
// since we override the resolution in the esbuild plugin, Vite can no longer optimizer it
256+
// have ?. until Vitest 3.0 for backwards compatibility
257+
const vueTestUtils = isPackageExists('@vue/test-utils', fileRoot)
258+
if (vueTestUtils) {
259+
include.push('@vue/test-utils')
254260
}
255261

256262
return {
@@ -398,7 +404,7 @@ export default (browserServer: BrowserServer, base = '/'): Plugin[] => {
398404
{
399405
name: 'test-utils-rewrite',
400406
setup(build) {
401-
build.onResolve({ filter: /@vue\/test-utils/ }, (args) => {
407+
build.onResolve({ filter: /^@vue\/test-utils$/ }, (args) => {
402408
const _require = getRequire()
403409
// resolve to CJS instead of the browser because the browser version expects a global Vue object
404410
const resolved = _require.resolve(args.path, {

‎packages/vitest/src/node/create.ts

-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ export async function createVitest(
3131
options.config = configPath
3232

3333
const config: ViteInlineConfig = {
34-
logLevel: 'error',
3534
configFile: configPath,
3635
// this will make "mode": "test" | "benchmark" inside defineConfig
3736
mode: options.mode || mode,

‎packages/vitest/src/node/packageInstaller.ts

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import { isCI } from '../utils/env'
77
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
88

99
export class VitestPackageInstaller {
10+
isPackageExists(name: string, options?: { paths?: string[] }) {
11+
return isPackageExists(name, options)
12+
}
13+
1014
async ensureInstalled(dependency: string, root: string, version?: string) {
1115
if (process.env.VITEST_SKIP_INSTALL_CHECKS) {
1216
return true

‎packages/vitest/src/node/plugins/index.ts

+9
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { resolveApiServerConfig } from '../config/resolveConfig'
1111
import { Vitest } from '../core'
1212
import { generateScopedClassName } from '../../integrations/css/css-modules'
1313
import { defaultPort } from '../../constants'
14+
import { createViteLogger } from '../viteLogger'
1415
import { SsrReplacerPlugin } from './ssrReplacer'
1516
import { CSSEnablerPlugin } from './cssEnabler'
1617
import { CoverageTransform } from './coverageTransform'
@@ -132,6 +133,14 @@ export async function VitestPlugin(
132133
},
133134
}
134135

136+
config.customLogger = createViteLogger(
137+
ctx.logger,
138+
viteConfig.logLevel || 'warn',
139+
{
140+
allowClearScreen: false,
141+
},
142+
)
143+
135144
// If "coverage.exclude" is not defined by user, add "test.include" to "coverage.exclude" automatically
136145
if (userConfig.coverage?.enabled && !userConfig.coverage.exclude && userConfig.include && config.test) {
137146
config.test.coverage = {

‎packages/vitest/src/node/plugins/utils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export function resolveOptimizerConfig(
7575

7676
// `optimizeDeps.disabled` is deprecated since v5.1.0-beta.1
7777
// https://github.com/vitejs/vite/pull/15184
78-
if (major >= 5 && minor >= 1) {
78+
if ((major >= 5 && minor >= 1) || major >= 6) {
7979
if (newConfig.optimizeDeps.disabled) {
8080
newConfig.optimizeDeps.noDiscovery = true
8181
newConfig.optimizeDeps.include = []

‎packages/vitest/src/node/plugins/workspace.ts

+8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { configDefaults } from '../../defaults'
66
import { generateScopedClassName } from '../../integrations/css/css-modules'
77
import type { WorkspaceProject } from '../workspace'
88
import type { ResolvedConfig, UserWorkspaceConfig } from '../types/config'
9+
import { createViteLogger } from '../viteLogger'
910
import { CoverageTransform } from './coverageTransform'
1011
import { CSSEnablerPlugin } from './cssEnabler'
1112
import { SsrReplacerPlugin } from './ssrReplacer'
@@ -124,6 +125,13 @@ export function WorkspaceVitestPlugin(
124125
}
125126
}
126127
}
128+
config.customLogger = createViteLogger(
129+
project.logger,
130+
viteConfig.logLevel || 'warn',
131+
{
132+
allowClearScreen: false,
133+
},
134+
)
127135

128136
return config
129137
},

‎packages/vitest/src/node/vite.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@ export async function createViteServer(inlineConfig: InlineConfig) {
1515
error(...args)
1616
}
1717

18-
const server = await createServer({
19-
logLevel: 'error',
20-
...inlineConfig,
21-
})
18+
const server = await createServer(inlineConfig)
2219

2320
console.error = error
2421
return server
+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import type { LogErrorOptions, LogLevel, LogType, Logger, LoggerOptions } from 'vite'
2+
import type { RollupError } from 'rollup'
3+
import colors from 'tinyrainbow'
4+
import type { Logger as VitestLogger } from './logger'
5+
6+
const LogLevels: Record<LogLevel, number> = {
7+
silent: 0,
8+
error: 1,
9+
warn: 2,
10+
info: 3,
11+
}
12+
13+
function clearScreen(logger: VitestLogger) {
14+
const repeatCount = process.stdout.rows - 2
15+
const blank = repeatCount > 0 ? '\n'.repeat(repeatCount) : ''
16+
logger.clearScreen(blank)
17+
}
18+
19+
let lastType: LogType | undefined
20+
let lastMsg: string | undefined
21+
let sameCount = 0
22+
23+
// Only initialize the timeFormatter when the timestamp option is used, and
24+
// reuse it across all loggers
25+
let timeFormatter: Intl.DateTimeFormat
26+
function getTimeFormatter() {
27+
timeFormatter ??= new Intl.DateTimeFormat(undefined, {
28+
hour: 'numeric',
29+
minute: 'numeric',
30+
second: 'numeric',
31+
})
32+
return timeFormatter
33+
}
34+
35+
// This is copy-pasted and needs to be synced from time to time. Ideally, Vite's `createLogger` should accept a custom `console`
36+
// https://github.com/vitejs/vite/blob/main/packages/vite/src/node/logger.ts?rgh-link-date=2024-10-16T23%3A29%3A19Z
37+
// When Vitest supports only Vite 6 and above, we can use Vite's `createLogger({ console })`
38+
// https://github.com/vitejs/vite/pull/18379
39+
export function createViteLogger(
40+
console: VitestLogger,
41+
level: LogLevel = 'info',
42+
options: LoggerOptions = {},
43+
): Logger {
44+
const loggedErrors = new WeakSet<Error | RollupError>()
45+
const { prefix = '[vite]', allowClearScreen = true } = options
46+
const thresh = LogLevels[level]
47+
const canClearScreen
48+
= allowClearScreen && process.stdout.isTTY && !process.env.CI
49+
const clear = canClearScreen ? clearScreen : () => {}
50+
51+
function format(type: LogType, msg: string, options: LogErrorOptions = {}) {
52+
if (options.timestamp) {
53+
let tag = ''
54+
if (type === 'info') {
55+
tag = colors.cyan(colors.bold(prefix))
56+
}
57+
else if (type === 'warn') {
58+
tag = colors.yellow(colors.bold(prefix))
59+
}
60+
else {
61+
tag = colors.red(colors.bold(prefix))
62+
}
63+
const environment = (options as any).environment ? `${(options as any).environment} ` : ''
64+
return `${colors.dim(getTimeFormatter().format(new Date()))} ${tag} ${environment}${msg}`
65+
}
66+
else {
67+
return msg
68+
}
69+
}
70+
71+
function output(type: LogType, msg: string, options: LogErrorOptions = {}) {
72+
if (thresh >= LogLevels[type]) {
73+
const method = type === 'info' ? 'log' : type
74+
75+
if (options.error) {
76+
loggedErrors.add(options.error)
77+
}
78+
if (canClearScreen) {
79+
if (type === lastType && msg === lastMsg) {
80+
sameCount++
81+
clear(console)
82+
console[method](
83+
format(type, msg, options),
84+
colors.yellow(`(x${sameCount + 1})`),
85+
)
86+
}
87+
else {
88+
sameCount = 0
89+
lastMsg = msg
90+
lastType = type
91+
if (options.clear) {
92+
clear(console)
93+
}
94+
console[method](format(type, msg, options))
95+
}
96+
}
97+
else {
98+
console[method](format(type, msg, options))
99+
}
100+
}
101+
}
102+
103+
const warnedMessages = new Set<string>()
104+
105+
const logger: Logger = {
106+
hasWarned: false,
107+
info(msg, opts) {
108+
output('info', msg, opts)
109+
},
110+
warn(msg, opts) {
111+
logger.hasWarned = true
112+
output('warn', msg, opts)
113+
},
114+
warnOnce(msg, opts) {
115+
if (warnedMessages.has(msg)) {
116+
return
117+
}
118+
logger.hasWarned = true
119+
output('warn', msg, opts)
120+
warnedMessages.add(msg)
121+
},
122+
error(msg, opts) {
123+
logger.hasWarned = true
124+
output('error', msg, opts)
125+
},
126+
clearScreen(type) {
127+
if (thresh >= LogLevels[type]) {
128+
clear(console)
129+
}
130+
},
131+
hasErrorLogged(error) {
132+
return loggedErrors.has(error)
133+
},
134+
}
135+
136+
return logger
137+
}

‎packages/vitest/src/node/workspace.ts

-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ export async function initializeProject(
7171
const config: ViteInlineConfig = {
7272
...options,
7373
root,
74-
logLevel: 'error',
7574
configFile,
7675
// this will make "mode": "test" | "benchmark" inside defineConfig
7776
mode: options.test?.mode || options.mode || ctx.config.mode,

‎packages/vitest/src/public/node.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export { createDebugger } from '../utils/debugger'
1818
export { resolveFsAllow } from '../node/plugins/utils'
1919
export { resolveApiServerConfig, resolveConfig } from '../node/config/resolveConfig'
2020
export { TestSpecification } from '../node/spec'
21+
export { createViteLogger } from '../node/viteLogger'
2122

2223
export { GitNotFoundError, FilesNotFoundError as TestsNotFoundError } from '../node/errors'
2324

@@ -54,7 +55,6 @@ export {
5455
isFileServingAllowed,
5556
parseAst,
5657
parseAstAsync,
57-
createLogger as createViteLogger,
5858
} from 'vite'
5959
/** @deprecated use `createViteServer` instead */
6060
export const createServer = _createServer

‎test/coverage-test/utils.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import { stripVTControlCharacters } from 'node:util'
55
import { normalize } from 'pathe'
66
import libCoverage from 'istanbul-lib-coverage'
77
import type { FileCoverageData } from 'istanbul-lib-coverage'
8-
import type { TestFunction, UserConfig } from 'vitest'
8+
import type { TestFunction } from 'vitest'
99
import { vi, describe as vitestDescribe, test as vitestTest } from 'vitest'
10+
import type { UserConfig } from 'vitest/node'
1011
import * as testUtils from '../test-utils'
1112

1213
export function test(name: string, fn: TestFunction, skip = false) {
@@ -55,7 +56,7 @@ export async function runVitest(config: UserConfig, options = { throwOnError: tr
5556

5657
if (options.throwOnError) {
5758
if (result.stderr !== '') {
58-
throw new Error(result.stderr)
59+
throw new Error(`stderr:\n${result.stderr}\n\nstdout:\n${result.stdout}`)
5960
}
6061
}
6162

0 commit comments

Comments
 (0)
Please sign in to comment.