diff --git a/docs/config/update.md b/docs/config/update.md
index 8668b8e20478..a154000bd696 100644
--- a/docs/config/update.md
+++ b/docs/config/update.md
@@ -5,8 +5,11 @@ outline: deep
# update {#update}
-- **Type:** `boolean`
+- **Type:** `boolean | 'new' | 'all'`
- **Default:** `false`
-- **CLI:** `-u`, `--update`, `--update=false`
+- **CLI:** `-u`, `--update`, `--update=false`, `--update=new`
-Update snapshot files. This will update all changed snapshots and delete obsolete ones.
+Update snapshot files. The behaviour depends on the value:
+
+- `true` or `'all'`: updates all changed snapshots and delete obsolete ones
+- `new`: generates new snapshots without changing or deleting obsolete ones
diff --git a/docs/guide/cli-generated.md b/docs/guide/cli-generated.md
index 0b1340c77418..9003015fe1a1 100644
--- a/docs/guide/cli-generated.md
+++ b/docs/guide/cli-generated.md
@@ -13,10 +13,10 @@ Path to config file
### update
-- **CLI:** `-u, --update`
+- **CLI:** `-u, --update [type]`
- **Config:** [update](/config/update)
-Update snapshot
+Update snapshot (accepts boolean, "new" or "all")
### watch
diff --git a/packages/browser/src/client/client.ts b/packages/browser/src/client/client.ts
index 84e6a260e3c3..b489d6046625 100644
--- a/packages/browser/src/client/client.ts
+++ b/packages/browser/src/client/client.ts
@@ -18,7 +18,7 @@ export const RPC_ID: string
const METHOD = getBrowserState().method
export const ENTRY_URL: string = `${
location.protocol === 'https:' ? 'wss:' : 'ws:'
-}//${HOST}/__vitest_browser_api__?type=${PAGE_TYPE}&rpcId=${RPC_ID}&sessionId=${getBrowserState().sessionId}&projectName=${getBrowserState().config.name || ''}&method=${METHOD}&token=${(window as any).VITEST_API_TOKEN || '0'}`
+}//${HOST}/__vitest_browser_api__?type=${PAGE_TYPE}&rpcId=${RPC_ID}&sessionId=${getBrowserState().sessionId}&projectName=${encodeURIComponent(getBrowserState().config.name || '')}&method=${METHOD}&token=${(window as any).VITEST_API_TOKEN || '0'}`
const onCancelCallbacks: ((reason: CancelReason) => void)[] = []
diff --git a/packages/mocker/src/node/hoistMocks.ts b/packages/mocker/src/node/hoistMocks.ts
index 1ececc2e63b3..81d012052791 100644
--- a/packages/mocker/src/node/hoistMocks.ts
+++ b/packages/mocker/src/node/hoistMocks.ts
@@ -107,7 +107,8 @@ export function hoistMocks(
} = options
// hoist at the start of the file, after the hashbang
- let hoistIndex = hashbangRE.exec(code)?.[0].length ?? 0
+ const hashbangEnd = hashbangRE.exec(code)?.[0].length ?? 0
+ let hoistIndex = hashbangEnd
let hoistedModuleImported = false
@@ -535,11 +536,12 @@ export function hoistMocks(
const utilityImports = [...usedUtilityExports]
// "vi" or "vitest" is imported from a module other than "vitest"
if (utilityImports.some(name => idToImportMap.has(name))) {
- s.prepend(API_NOT_FOUND_CHECK(utilityImports))
+ s.appendLeft(hashbangEnd, API_NOT_FOUND_CHECK(utilityImports))
}
// if "vi" or "vitest" are not imported at all, import them
else if (utilityImports.length) {
- s.prepend(
+ s.appendLeft(
+ hashbangEnd,
`import { ${[...usedUtilityExports].join(', ')} } from ${JSON.stringify(
hoistedModule,
)}\n`,
diff --git a/packages/vitest/src/node/cli/cli-config.ts b/packages/vitest/src/node/cli/cli-config.ts
index c7e0711453b8..f7c2efb49c88 100644
--- a/packages/vitest/src/node/cli/cli-config.ts
+++ b/packages/vitest/src/node/cli/cli-config.ts
@@ -78,7 +78,8 @@ export const cliOptionsConfig: VitestCLIOptions = {
},
update: {
shorthand: 'u',
- description: 'Update snapshot',
+ description: 'Update snapshot (accepts boolean, "new" or "all")',
+ argument: '[type]',
},
watch: {
shorthand: 'w',
diff --git a/packages/vitest/src/node/config/resolveConfig.ts b/packages/vitest/src/node/config/resolveConfig.ts
index ada18c1f6bea..c61021cadb3d 100644
--- a/packages/vitest/src/node/config/resolveConfig.ts
+++ b/packages/vitest/src/node/config/resolveConfig.ts
@@ -508,7 +508,9 @@ export function resolveConfig(
expand: resolved.expandSnapshotDiff ?? false,
snapshotFormat: resolved.snapshotFormat || {},
updateSnapshot:
- isCI && !UPDATE_SNAPSHOT ? 'none' : UPDATE_SNAPSHOT ? 'all' : 'new',
+ UPDATE_SNAPSHOT === 'all' || UPDATE_SNAPSHOT === 'new'
+ ? UPDATE_SNAPSHOT
+ : isCI && !UPDATE_SNAPSHOT ? 'none' : UPDATE_SNAPSHOT ? 'all' : 'new',
resolveSnapshotPath: options.resolveSnapshotPath,
// resolved inside the worker
snapshotEnvironment: null as any,
diff --git a/packages/vitest/src/node/types/config.ts b/packages/vitest/src/node/types/config.ts
index aeb025a06eb2..e65d5b8656dd 100644
--- a/packages/vitest/src/node/types/config.ts
+++ b/packages/vitest/src/node/types/config.ts
@@ -365,7 +365,7 @@ export interface InlineConfig {
*
* @default false
*/
- update?: boolean
+ update?: boolean | 'all' | 'new'
/**
* Watch mode
diff --git a/test/browser/fixtures/project-name-encoding/basic.test.ts b/test/browser/fixtures/project-name-encoding/basic.test.ts
new file mode 100644
index 000000000000..29093bc5f5ab
--- /dev/null
+++ b/test/browser/fixtures/project-name-encoding/basic.test.ts
@@ -0,0 +1,5 @@
+import { expect, test } from 'vitest'
+
+test('basic test', () => {
+ expect(true).toBe(true)
+})
diff --git a/test/browser/fixtures/project-name-encoding/vitest.config.ts b/test/browser/fixtures/project-name-encoding/vitest.config.ts
new file mode 100644
index 000000000000..0afbb6b84a76
--- /dev/null
+++ b/test/browser/fixtures/project-name-encoding/vitest.config.ts
@@ -0,0 +1,11 @@
+import { defineConfig } from 'vitest/config'
+
+export default defineConfig({
+ test: {
+ name: 'Components & Hooks',
+ browser: {
+ enabled: true,
+ headless: true,
+ },
+ },
+})
diff --git a/test/browser/fixtures/user-event/wheel.test.ts b/test/browser/fixtures/user-event/wheel.test.ts
index 950d9e4af5b8..9d6dc4f99dec 100644
--- a/test/browser/fixtures/user-event/wheel.test.ts
+++ b/test/browser/fixtures/user-event/wheel.test.ts
@@ -25,7 +25,7 @@ describe.for([
await (testType === 'userEvent' ? userEvent.wheel(selector, options) : selector.wheel(options))
- expect(wheel).toHaveBeenCalledOnce()
+ await expect.poll(() => wheel).toHaveBeenCalledOnce()
expect(wheel.mock.calls[0][0].deltaX).toBe(deltaX)
expect(wheel.mock.calls[0][0].deltaY).toBe(deltaY)
})
diff --git a/test/browser/specs/project-name-encoding.test.ts b/test/browser/specs/project-name-encoding.test.ts
new file mode 100644
index 000000000000..3c6dcea1a39f
--- /dev/null
+++ b/test/browser/specs/project-name-encoding.test.ts
@@ -0,0 +1,21 @@
+import { expect, test } from 'vitest'
+import { instances, provider, runBrowserTests } from './utils'
+
+test('runs tests correctly when project name contains special characters', async () => {
+ const { stderr, stdout, exitCode, ctx } = await runBrowserTests({
+ root: './fixtures/project-name-encoding',
+ browser: {
+ provider,
+ instances,
+ },
+ })
+
+ expect(stderr).toBe('')
+ expect(exitCode).toBe(0)
+
+ const projectName = ctx.config.name
+
+ instances.forEach(({ browser }) => {
+ expect(stdout).toReportPassedTest('basic.test.ts', `${projectName} (${browser})`)
+ })
+})
diff --git a/test/browser/specs/to-match-screenshot.test.ts b/test/browser/specs/to-match-screenshot.test.ts
index 1591bb5bbacb..f5e196222dd2 100644
--- a/test/browser/specs/to-match-screenshot.test.ts
+++ b/test/browser/specs/to-match-screenshot.test.ts
@@ -1,11 +1,11 @@
import type { ViteUserConfig } from 'vitest/config'
import type { TestFsStructure } from '../../test-utils'
import { platform } from 'node:os'
-import { resolve } from 'node:path'
import { describe, expect, test } from 'vitest'
-import { runVitestCli, useFS } from '../../test-utils'
+import { runInlineTests } from '../../test-utils'
import { extractToMatchScreenshotPaths } from '../fixtures/expect-dom/utils'
import utilsContent from '../fixtures/expect-dom/utils?raw'
+import { provider } from '../settings'
const testFilename = 'basic.test.ts'
const testName = 'screenshot-snapshot'
@@ -27,57 +27,46 @@ test('${testName}', async ({ expect }) => {
const browser = 'chromium'
-async function runInlineTests(
+async function runBrowserTests(
structure: TestFsStructure,
config: ViteUserConfig['test'] = {},
) {
- const root = resolve(process.cwd(), `vitest-test-${crypto.randomUUID()}`)
-
- const fs = useFS(root, {
+ return runInlineTests({
...structure,
- 'vitest.config.ts': `
- import { playwright } from '@vitest/browser-playwright'
- export default {
- test: {
- browser: {
- enabled: true,
- screenshotFailures: false,
- provider: playwright(),
- headless: true,
- instances: [{ browser: ${JSON.stringify(browser)} }],
+ 'vitest.config.js': `
+ import { playwright } from '@vitest/browser-playwright'
+ export default {
+ test: {
+ browser: {
+ enabled: true,
+ screenshotFailures: false,
+ provider: playwright(),
+ headless: true,
+ instances: [{ browser: ${JSON.stringify(browser)} }],
+ },
+ reporters: ['verbose'],
+ ...${JSON.stringify(config)},
},
- reporters: ['verbose'],
- ...${JSON.stringify(config)},
- },
- }`,
- })
-
- const vitest = await runVitestCli({
- nodeOptions: {
- env: {
- CI: 'false',
- GITHUB_ACTIONS: undefined,
- NO_COLOR: 'true',
- },
+ }`,
+ }, {
+ $cliOptions: {
+ watch: true,
},
- }, '--root', root, '--watch')
-
- return {
- fs,
- root,
- ...vitest,
- }
+ })
}
-describe('--watch', () => {
+describe.runIf(provider.name === 'playwright')('--watch', () => {
test(
'fails when creating a snapshot for the first time and does NOT update it afterwards',
async () => {
- const { fs, stderr, vitest } = await runInlineTests(
+ const { fs, stderr, vitest } = await runBrowserTests(
{
[testFilename]: testContent,
'utils.ts': utilsContent,
},
+ {
+ update: 'new',
+ },
)
const [referencePath] = extractToMatchScreenshotPaths(stderr, testName)
@@ -102,11 +91,14 @@ describe('--watch', () => {
test(
'creates a reference and fails when changing the DOM content',
async () => {
- const { fs, stderr, vitest } = await runInlineTests(
+ const { fs, stderr, vitest } = await runBrowserTests(
{
[testFilename]: testContent,
'utils.ts': utilsContent,
},
+ {
+ update: 'new',
+ },
)
expect(stderr).toContain(`No existing reference screenshot found; a new one was created. Review it before running tests again.\n\nReference screenshot:`)
@@ -126,7 +118,7 @@ describe('--watch', () => {
test(
'creates snapshot and does NOT update it if reference matches',
async () => {
- const { fs, stderr, vitest } = await runInlineTests(
+ const { fs, stderr, vitest } = await runBrowserTests(
{
[testFilename]: testContent,
'utils.ts': utilsContent,
@@ -174,7 +166,7 @@ describe('--watch', () => {
test(
'creates snapshot and updates it if reference mismatches',
async () => {
- const { fs, stderr, vitest } = await runInlineTests(
+ const { fs, stderr, vitest } = await runBrowserTests(
{
[testFilename]: testContent,
'utils.ts': utilsContent,
diff --git a/test/cli/test/config-loader.test.ts b/test/cli/test/config-loader.test.ts
index e5700e171018..a7c86b124228 100644
--- a/test/cli/test/config-loader.test.ts
+++ b/test/cli/test/config-loader.test.ts
@@ -1,33 +1,28 @@
import { expect, test } from 'vitest'
-import { runVitestCli } from '../../test-utils'
+import { runVitest } from '../../test-utils'
const isTypeStrippingSupported = !!process.features.typescript
-test('configLoader default', async () => {
- const { vitest, exitCode } = await runVitestCli(
- 'run',
- '--root',
- 'fixtures/config-loader',
- )
- if (!isTypeStrippingSupported) {
- expect(vitest.stderr).toContain('failed to load config')
- expect(exitCode).not.toBe(0)
- }
- else {
- expect(exitCode).toBe(0)
- }
+test.runIf(isTypeStrippingSupported)('configLoader native', async () => {
+ const { stderr, exitCode } = await runVitest({
+ root: 'fixtures/config-loader',
+ $cliOptions: {
+ configLoader: 'native',
+ },
+ })
+ expect(stderr).toBe('')
+ expect(exitCode).toBe(0)
})
test('configLoader runner', async () => {
- const { vitest, exitCode } = await runVitestCli(
- 'run',
- '--root',
- 'fixtures/config-loader',
- '--configLoader',
- 'runner',
- )
+ const { vitest, exitCode } = await runVitest({
+ root: 'fixtures/config-loader',
+ $cliOptions: {
+ configLoader: 'runner',
+ },
+ })
expect(vitest.stderr).toBe('')
- expect(vitest.stdout).toContain('✓ node')
- expect(vitest.stdout).toContain('✓ browser (chromium)')
+ expect(vitest.stdout).toContain('✓ |node|')
+ expect(vitest.stdout).toContain('✓ |browser (chromium)|')
expect(exitCode).toBe(0)
})
diff --git a/test/cli/test/projects.test.ts b/test/cli/test/projects.test.ts
index a18ab39f9bcc..4ca11b2e5fbf 100644
--- a/test/cli/test/projects.test.ts
+++ b/test/cli/test/projects.test.ts
@@ -121,7 +121,7 @@ it('correctly inherits the root config', async () => {
it('fails if workspace is empty', async () => {
const { stderr } = await runVitest({
projects: [],
- })
+ }, [], { fails: true })
expect(stderr).toContain('No projects were found. Make sure your configuration is correct. The projects definition: [].')
})
@@ -133,7 +133,7 @@ it('fails if workspace is filtered by the project', async () => {
projects: [
'./vitest.config.js',
],
- })
+ }, [], { fails: true })
expect(stderr).toContain(`No projects were found. Make sure your configuration is correct. The filter matched no projects: non-existing. The projects definition: [
"./vitest.config.js"
].`)
diff --git a/test/cli/test/unhandled-rejections.test.ts b/test/cli/test/unhandled-rejections.test.ts
index 27e5d3f2235e..891ba38e0be7 100644
--- a/test/cli/test/unhandled-rejections.test.ts
+++ b/test/cli/test/unhandled-rejections.test.ts
@@ -43,7 +43,7 @@ describe('dangerouslyIgnoreUnhandledErrors', () => {
new Promise((_, reject) => reject(new Error("intentional unhandled error")))
`,
- }, config)
+ }, config, { fails: true })
}
})
@@ -59,7 +59,7 @@ test('unhandled rejections of main thread are reported even when no reporter is
config: false,
globalSetup: ['setup-unhandled-rejections.js'],
reporters: [{ onInit: () => {} }],
- })
+ }, { fails: true })
expect(exitCode).toBe(1)
expect(stderr).toContain('Unhandled Rejection')
diff --git a/test/core/test/injector-mock.test.ts b/test/core/test/injector-mock.test.ts
index eca95b745938..56c66b36a330 100644
--- a/test/core/test/injector-mock.test.ts
+++ b/test/core/test/injector-mock.test.ts
@@ -915,21 +915,55 @@ export default (function getRandom() {
// #8002
test('with hashbang', () => {
expect(
- hoistSimpleCodeWithoutMocks(
+ hoistSimpleCode(
`#!/usr/bin/env node
+vi.mock('foo');
console.log("it can parse the hashbang")`,
),
- ).toMatchInlineSnapshot(`undefined`)
+ ).toMatchInlineSnapshot(`
+ "#!/usr/bin/env node
+ import { vi } from "vitest"
+ vi.mock('foo');
+ console.log("it can parse the hashbang")"
+ `)
})
test('import hoisted after hashbang', () => {
expect(
- hoistSimpleCodeWithoutMocks(
+ hoistSimpleCode(
`#!/usr/bin/env node
+vi.mock('foo');
console.log(foo);
import foo from "foo"`,
),
- ).toMatchInlineSnapshot(`undefined`)
+ ).toMatchInlineSnapshot(`
+ "#!/usr/bin/env node
+ import { vi } from "vitest"
+ vi.mock('foo');
+ const __vi_import_0__ = await import("foo");
+ console.log(__vi_import_0__.default);"
+ `)
+ })
+
+ test('import hoisted after hashbang', () => {
+ expect(
+ hoistSimpleCode(
+ `#!/usr/bin/env node
+import { vi } from './proxy'
+vi.mock('foo');
+console.log(foo);
+import foo from "foo"`,
+ ),
+ ).toMatchInlineSnapshot(`
+ "#!/usr/bin/env node
+
+ if (typeof globalThis["vi"] === "undefined") { throw new Error("There are some problems in resolving the mocks API.\\nYou may encounter this issue when importing the mocks API from another module other than 'vitest'.\\nTo fix this issue you can either:\\n- import the mocks API directly from 'vitest'\\n- enable the 'globals' option") }
+ __vi_import_0__.vi.mock('foo');
+ const __vi_import_0__ = await import("./proxy");
+ const __vi_import_1__ = await import("foo");
+
+ console.log(__vi_import_1__.default);"
+ `)
})
// #10289
diff --git a/test/core/test/on-failed.test.ts b/test/core/test/on-failed.test.ts
index 12281177847d..c674e6d12608 100644
--- a/test/core/test/on-failed.test.ts
+++ b/test/core/test/on-failed.test.ts
@@ -7,8 +7,6 @@ it.fails('on-failed', () => {
const square4 = 4 ** 2
onTestFailed(() => {
- // eslint-disable-next-line no-console
- console.log('Unexpected error encountered, internal states:', { square3, square4 })
collected.push({ square3, square4 })
})
diff --git a/test/core/test/snapshot-file.test.ts b/test/core/test/snapshot-file.test.ts
index 5a852473d0d8..f6e54c942f5f 100644
--- a/test/core/test/snapshot-file.test.ts
+++ b/test/core/test/snapshot-file.test.ts
@@ -8,11 +8,14 @@ function objectToCSS(selector: string, obj: Record) {
}
describe('snapshots', () => {
- const files = import.meta.glob('./fixtures/snapshots/**/input.json', { as: 'raw' })
+ const files = import.meta.glob('./fixtures/snapshots/**/input.json', {
+ query: '?raw',
+ import: 'default',
+ })
for (const [path, file] of Object.entries(files)) {
test(path, async () => {
- const entries = JSON.parse(await file()) as any[]
+ const entries = JSON.parse(await file() as string) as any[]
await expect(entries.map(i => objectToCSS(i[0], i[1])).join('\n'))
.toMatchFileSnapshot(path.replace('input.json', 'output.css'))
})
diff --git a/test/core/test/tab-effect.spec.mjs b/test/core/test/tab-effect.spec.mjs
index 26718e1a757c..af0a1b1766ff 100644
--- a/test/core/test/tab-effect.spec.mjs
+++ b/test/core/test/tab-effect.spec.mjs
@@ -6,7 +6,6 @@ const helloWorld = () => {
return joinPath('hello', 'world')
}
-test('Are you mocking me?', () => {
// note there are NO indents in this file
// except the next line
// test pass with spaces, test fails with tab
@@ -15,5 +14,7 @@ return {
join: vi.fn().mockReturnValue('goodbye world')
}
})
+
+test('Are you mocking me?', () => {
expect(helloWorld()).toBe('goodbye world')
})
diff --git a/test/core/test/wait.test.ts b/test/core/test/wait.test.ts
index ee4cb456dcc6..30cfe247ce66 100644
--- a/test/core/test/wait.test.ts
+++ b/test/core/test/wait.test.ts
@@ -143,8 +143,8 @@ describe('waitUntil', () => {
await expect(
vi.waitUntil(callback, {
- timeout: 1000,
- interval: 600,
+ timeout: 500,
+ interval: 400,
}),
).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Timed out in waitUntil!]`)
diff --git a/test/core/vite.config.ts b/test/core/vite.config.ts
index ff2ee56b30fc..92e17c3f36cf 100644
--- a/test/core/vite.config.ts
+++ b/test/core/vite.config.ts
@@ -158,6 +158,9 @@ export default defineConfig({
if (log.startsWith(`[vitest]`) && log.includes(`did not use 'function' or 'class' in its implementation`)) {
return false
}
+ if (log.startsWith('Importing from') && log.includes('is deprecated since Vitest 4.1')) {
+ return false
+ }
},
projects: [
project('threads', 'red'),
diff --git a/test/test-utils/index.ts b/test/test-utils/index.ts
index ff69b11b96f3..41324254f8ba 100644
--- a/test/test-utils/index.ts
+++ b/test/test-utils/index.ts
@@ -4,6 +4,7 @@ import type { SerializedConfig, WorkerGlobalState } from 'vitest'
import type { TestProjectConfiguration } from 'vitest/config'
import type {
TestCase,
+ CliOptions as TestCliOptions,
TestCollection,
TestModule,
TestSpecification,
@@ -37,7 +38,7 @@ export interface VitestRunnerCLIOptions {
export interface RunVitestConfig extends TestUserConfig {
$viteConfig?: Omit
- $cliOptions?: TestUserConfig
+ $cliOptions?: TestCliOptions
}
/**