Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: custom "snapshotEnvironment" option #5449

Merged
merged 4 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions docs/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2200,3 +2200,31 @@ The `location` property has `column` and `line` values that correspond to the `t
::: tip
This option has no effect if you do not use custom code that relies on this.
:::

### snapshotEnvironment <Version>1.6.0</Version> {#snapshotEnvironment}

- **Type:** `string`

Path to a custom snapshot environment implementation. This is useful if you are running your tests in an environment that doesn't support Node.js APIs. This option doesn't have any effect on a browser runner.

This object should have the shape of `SnapshotEnvironment` and is used to resolve and read/write snapshot files:

```ts
export interface SnapshotEnvironment {
getVersion: () => string
getHeader: () => string
resolvePath: (filepath: string) => Promise<string>
resolveRawPath: (testPath: string, rawPath: string) => Promise<string>
saveSnapshotFile: (filepath: string, snapshot: string) => Promise<void>
readSnapshotFile: (filepath: string) => Promise<string | null>
removeSnapshotFile: (filepath: string) => Promise<void>
}
```

You can extend default `VitestSnapshotEnvironment` from `vitest/snapshot` entry point if you need to overwrite only a part of the API.

::: warning
This is a low-level option and should be used only for advanced cases where you don't have access to default Node.js APIs.

If you just need to configure snapshots feature, use [`snapshotFormat`](#snapshotformat) or [`resolveSnapshotPath`](#resolvesnapshotpath) options.
:::
9 changes: 6 additions & 3 deletions packages/browser/src/client/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { ResolvedConfig } from 'vitest'
import type { VitestExecutor } from 'vitest/execute'
import { rpc } from './rpc'
import { getConfig, importId } from './utils'
import { BrowserSnapshotEnvironment } from './snapshot'
import { VitestBrowserSnapshotEnvironment } from './snapshot'

interface BrowserRunnerOptions {
config: ResolvedConfig
Expand Down Expand Up @@ -92,7 +92,10 @@ export async function initiateRunner() {
if (cachedRunner)
return cachedRunner
const config = getConfig()
const [{ VitestTestRunner, NodeBenchmarkRunner }, { takeCoverageInsideWorker, loadDiffConfig, loadSnapshotSerializers }] = await Promise.all([
const [
{ VitestTestRunner, NodeBenchmarkRunner },
{ takeCoverageInsideWorker, loadDiffConfig, loadSnapshotSerializers },
] = await Promise.all([
importId('vitest/runners') as Promise<typeof import('vitest/runners')>,
importId('vitest/browser') as Promise<typeof import('vitest/browser')>,
])
Expand All @@ -101,7 +104,7 @@ export async function initiateRunner() {
takeCoverage: () => takeCoverageInsideWorker(config.coverage, { executeId: importId }),
})
if (!config.snapshotOptions.snapshotEnvironment)
config.snapshotOptions.snapshotEnvironment = new BrowserSnapshotEnvironment()
config.snapshotOptions.snapshotEnvironment = new VitestBrowserSnapshotEnvironment()
const runner = new BrowserRunner({
config,
})
Expand Down
11 changes: 8 additions & 3 deletions packages/browser/src/client/snapshot.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { SnapshotEnvironment } from 'vitest'
import { rpc } from './rpc'
import type { VitestClient } from '@vitest/ws-client'
import type { SnapshotEnvironment } from 'vitest/snapshot'

export class BrowserSnapshotEnvironment implements SnapshotEnvironment {
export class VitestBrowserSnapshotEnvironment implements SnapshotEnvironment {
getVersion(): string {
return '1'
}
Expand Down Expand Up @@ -30,3 +30,8 @@ export class BrowserSnapshotEnvironment implements SnapshotEnvironment {
return rpc().removeSnapshotFile(filepath)
}
}

function rpc(): VitestClient['rpc'] {
// @ts-expect-error not typed global
return globalThis.__vitest_worker__.rpc
}
4 changes: 4 additions & 0 deletions packages/vitest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@
"./reporters": {
"types": "./dist/reporters.d.ts",
"default": "./dist/reporters.js"
},
"./snapshot": {
"types": "./dist/snapshot.d.ts",
"default": "./dist/snapshot.js"
}
},
"main": "./dist/index.js",
Expand Down
3 changes: 3 additions & 0 deletions packages/vitest/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ const entries = {
'workers/vmForks': 'src/runtime/workers/vmForks.ts',

'workers/runVmTests': 'src/runtime/runVmTests.ts',

'snapshot': 'src/snapshot.ts',
}

const dtsEntries = {
Expand All @@ -56,6 +58,7 @@ const dtsEntries = {
execute: 'src/public/execute.ts',
reporters: 'src/public/reporters.ts',
workers: 'src/workers.ts',
snapshot: 'src/snapshot.ts',
}

const external = [
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/snapshot.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './dist/snapshot.js'
1 change: 1 addition & 0 deletions packages/vitest/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export * from './integrations/chai'
export * from './integrations/vi'
export * from './integrations/utils'
export { inject } from './integrations/inject'
// TODO: remove in 2.0.0, import from vitest/snapshot directly
export type { SnapshotEnvironment } from '@vitest/snapshot/environment'

export * from './types'
Expand Down
11 changes: 4 additions & 7 deletions packages/vitest/src/integrations/snapshot/environments/node.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import { NodeSnapshotEnvironment } from '@vitest/snapshot/environment'
import type { WorkerRPC } from '../../../types/worker'

export class VitestSnapshotEnvironment extends NodeSnapshotEnvironment {
constructor(private rpc: WorkerRPC) {
super()
}
import { getWorkerState } from '../../../utils'

export class VitestNodeSnapshotEnvironment extends NodeSnapshotEnvironment {
getHeader(): string {
return `// Vitest Snapshot v${this.getVersion()}, https://vitest.dev/guide/snapshot.html`
}

resolvePath(filepath: string): Promise<string> {
return this.rpc.resolveSnapshotPath(filepath)
const rpc = getWorkerState().rpc
return rpc.resolveSnapshotPath(filepath)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { SnapshotEnvironment } from '@vitest/snapshot/environment'
import type { VitestExecutor } from '../../../runtime/execute'
import type { ResolvedConfig } from '../../../types'

export async function resolveSnapshotEnvironment(
config: ResolvedConfig,
executor: VitestExecutor,
): Promise<SnapshotEnvironment> {
if (!config.snapshotEnvironment) {
const { VitestNodeSnapshotEnvironment } = await import('./node')
return new VitestNodeSnapshotEnvironment()
}

const mod = await executor.executeId(config.snapshotEnvironment)
if (typeof mod.default !== 'object' || !mod.default)
throw new Error('Snapshot environment module must have a default export object with a shape of `SnapshotEnvironment`')
return mod.default
}
1 change: 1 addition & 0 deletions packages/vitest/src/node/cli/cli-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -634,4 +634,5 @@ export const cliOptionsConfig: VitestCLIOptions = {
deps: null,
name: null,
includeTaskLocation: null,
snapshotEnvironment: null,
}
3 changes: 3 additions & 0 deletions packages/vitest/src/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,9 @@ export function resolveConfig(
if (resolved.runner)
resolved.runner = resolvePath(resolved.runner, resolved.root)

if (resolved.snapshotEnvironment)
resolved.snapshotEnvironment = resolvePath(resolved.snapshotEnvironment, resolved.root)

resolved.testNamePattern = resolved.testNamePattern
? resolved.testNamePattern instanceof RegExp
? resolved.testNamePattern
Expand Down
2 changes: 1 addition & 1 deletion packages/vitest/src/runtime/runBaseTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { closeInspector } from './inspector'
export async function run(files: string[], config: ResolvedConfig, environment: ResolvedTestEnvironment, executor: VitestExecutor): Promise<void> {
const workerState = getWorkerState()

await setupGlobalEnv(config, environment)
await setupGlobalEnv(config, environment, executor)
await startCoverageInsideWorker(config.coverage, executor)

if (config.chaiConfig)
Expand Down
11 changes: 7 additions & 4 deletions packages/vitest/src/runtime/runVmTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { setupChaiConfig } from '../integrations/chai/config'
import { startCoverageInsideWorker, stopCoverageInsideWorker } from '../integrations/coverage'
import type { ResolvedConfig } from '../types'
import { getWorkerState } from '../utils/global'
import { VitestSnapshotEnvironment } from '../integrations/snapshot/environments/node'
import * as VitestIndex from '../index'
import { resolveSnapshotEnvironment } from '../integrations/snapshot/environments/resolveSnapshotEnvironment'
import type { VitestExecutor } from './execute'
import { resolveTestRunner } from './runners'
import { setupCommonEnv } from './setup-common'
Expand All @@ -27,8 +27,6 @@ export async function run(files: string[], config: ResolvedConfig, executor: Vit
enumerable: false,
})

config.snapshotOptions.snapshotEnvironment = new VitestSnapshotEnvironment(workerState.rpc)

setupColors(createColors(isatty(1)))

if (workerState.environment.transformMode === 'web') {
Expand All @@ -55,7 +53,12 @@ export async function run(files: string[], config: ResolvedConfig, executor: Vit
if (config.chaiConfig)
setupChaiConfig(config.chaiConfig)

const runner = await resolveTestRunner(config, executor)
const [runner, snapshotEnvironment] = await Promise.all([
resolveTestRunner(config, executor),
resolveSnapshotEnvironment(config, executor),
])

config.snapshotOptions.snapshotEnvironment = snapshotEnvironment

workerState.onCancel.then((reason) => {
closeInspector(config)
Expand Down
7 changes: 4 additions & 3 deletions packages/vitest/src/runtime/setup-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ import { isatty } from 'node:tty'
import { installSourcemapsSupport } from 'vite-node/source-map'
import { createColors, setupColors } from '@vitest/utils'
import type { EnvironmentOptions, ResolvedConfig, ResolvedTestEnvironment } from '../types'
import { VitestSnapshotEnvironment } from '../integrations/snapshot/environments/node'
import { getSafeTimers, getWorkerState } from '../utils'
import * as VitestIndex from '../index'
import { expect } from '../integrations/chai'
import { resolveSnapshotEnvironment } from '../integrations/snapshot/environments/resolveSnapshotEnvironment'
import { setupCommonEnv } from './setup-common'
import type { VitestExecutor } from './execute'

// this should only be used in Node
let globalSetup = false
export async function setupGlobalEnv(config: ResolvedConfig, { environment }: ResolvedTestEnvironment) {
export async function setupGlobalEnv(config: ResolvedConfig, { environment }: ResolvedTestEnvironment, executor: VitestExecutor) {
await setupCommonEnv(config)

Object.defineProperty(globalThis, '__vitest_index__', {
Expand All @@ -24,7 +25,7 @@ export async function setupGlobalEnv(config: ResolvedConfig, { environment }: Re
const state = getWorkerState()

if (!state.config.snapshotOptions.snapshotEnvironment)
state.config.snapshotOptions.snapshotEnvironment = new VitestSnapshotEnvironment(state.rpc)
state.config.snapshotOptions.snapshotEnvironment = await resolveSnapshotEnvironment(config, executor)

if (globalSetup)
return
Expand Down
4 changes: 4 additions & 0 deletions packages/vitest/src/snapshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type { SnapshotEnvironment } from '@vitest/snapshot/environment'
export {
VitestNodeSnapshotEnvironment as VitestSnapshotEnvironment,
} from './integrations/snapshot/environments/node'
5 changes: 5 additions & 0 deletions packages/vitest/src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,11 @@ export interface InlineConfig {
*/
resolveSnapshotPath?: (path: string, extension: string) => string

/**
* Path to a custom snapshot environment module that has a defualt export of `SnapshotEnvironment` object.
*/
snapshotEnvironment?: string

/**
* Pass with no tests
*/
Expand Down
1 change: 1 addition & 0 deletions test/browser/custom-snapshot-env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
throw new Error('This file should not be executed')
2 changes: 2 additions & 0 deletions test/browser/vitest.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export default defineConfig({
},
test: {
include: ['test/**.test.{ts,js}'],
// having a snapshot environment doesn't affect browser tests
snapshotEnvironment: './custom-snapshot-env.ts',
browser: {
enabled: true,
name: browser,
Expand Down
33 changes: 33 additions & 0 deletions test/snapshots/test/custom-environment.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { readFileSync, rmSync, writeFileSync } from 'node:fs'
import { afterEach, expect, test } from 'vitest'
import { dirname, resolve } from 'pathe'
import { runVitest } from '../../test-utils'

const testFileName = resolve(import.meta.dirname, './fixtures/custom-snapshot-environment/test/snapshots.test.ts')
const snapshotFile = resolve(dirname(testFileName), './__snapshots__/snapshots.test.ts.snap')
const testFile = readFileSync(testFileName, 'utf-8')

afterEach(() => {
writeFileSync(testFileName, testFile)
rmSync(snapshotFile)
})

test('custom environment resolved correctly', async () => {
const { stdout, stderr } = await runVitest({
root: 'test/fixtures/custom-snapshot-environment',
update: true,
})

const snapshotLogs = stdout.split('\n').filter(i => i.startsWith('## ')).join('\n')
expect(stderr).toBe('')
expect(snapshotLogs).toMatchInlineSnapshot(`
"## resolvePath test/fixtures/custom-snapshot-environment/test/snapshots.test.ts
## readSnapshotFile test/fixtures/custom-snapshot-environment/test/__snapshots__/snapshots.test.ts.snap
## getHeader
## getVersion
## readSnapshotFile test/fixtures/custom-snapshot-environment/test/__snapshots__/snapshots.test.ts.snap
## saveSnapshotFile test/fixtures/custom-snapshot-environment/test/__snapshots__/snapshots.test.ts.snap
## readSnapshotFile test/fixtures/custom-snapshot-environment/test/snapshots.test.ts
## saveSnapshotFile test/fixtures/custom-snapshot-environment/test/snapshots.test.ts"
`)
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { relative as _relative } from 'pathe'
import { VitestSnapshotEnvironment } from 'vitest/snapshot'

function relative(file: string) {
return _relative(process.cwd(), file)
}

class CustomSnapshotEnvironment extends VitestSnapshotEnvironment {
getVersion(): string {
console.log('## getVersion')
return super.getVersion()
}

getHeader() {
console.log('## getHeader')
return super.getHeader()
}

resolvePath(filepath: string) {
console.log('## resolvePath', relative(filepath))
return super.resolvePath(filepath)
}

resolveRawPath(testPath: string, rawPath: string) {
console.log('## resolveRawPath', relative(testPath), relative(rawPath))
return super.resolveRawPath(testPath, rawPath)
}

saveSnapshotFile(filepath: string, snapshot: string) {
console.log('## saveSnapshotFile', relative(filepath))
return super.saveSnapshotFile(filepath, snapshot)
}

readSnapshotFile(filepath: string) {
console.log('## readSnapshotFile', relative(filepath))
return super.readSnapshotFile(filepath)
}

removeSnapshotFile(filepath: string) {
console.log('## removeSnapshotFile', relative(filepath))
return super.removeSnapshotFile(filepath)
}
}

export default new CustomSnapshotEnvironment()
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {test, expect} from 'vitest'

test('regular snapshot', () => {
expect({ a: 1 }).toMatchSnapshot()
})

test('inline snapshot', () => {
expect({ a: 1 }).toMatchInlineSnapshot()
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
snapshotEnvironment: './snapshot-environment.ts'
}
})
Loading