Skip to content

Commit

Permalink
feat!: move snapshot implementation into @vitest/snapshot (#3032)
Browse files Browse the repository at this point in the history
  • Loading branch information
sheremet-va committed Mar 29, 2023
1 parent 446308d commit 6aff017
Show file tree
Hide file tree
Showing 49 changed files with 1,197 additions and 1,082 deletions.
14 changes: 5 additions & 9 deletions packages/browser/src/client/main.ts
Expand Up @@ -3,11 +3,11 @@ import { createClient } from '@vitest/ws-client'
import type { ResolvedConfig } from 'vitest'
import type { VitestRunner } from '@vitest/runner'
import { createBrowserRunner } from './runner'
import { BrowserSnapshotEnvironment } from './snapshot'
import { importId } from './utils'
import { setupConsoleLogSpy } from './logger'
import { createSafeRpc, rpc, rpcDone } from './rpc'
import { setupDialogsSpy } from './dialog'
import { BrowserSnapshotEnvironment } from './snapshot'

// @ts-expect-error mocking some node apis
globalThis.process = { env: {}, argv: [], cwd: () => '/', stdout: { write: () => {} }, nextTick: cb => cb() }
Expand Down Expand Up @@ -75,19 +75,17 @@ ws.addEventListener('open', async () => {

await setupConsoleLogSpy()
setupDialogsSpy()
await runTests(paths, config)
await runTests(paths, config!)
})

let hasSnapshot = false
async function runTests(paths: string[], config: any) {
async function runTests(paths: string[], config: ResolvedConfig) {
// need to import it before any other import, otherwise Vite optimizer will hang
const viteClientPath = '/@vite/client'
await import(viteClientPath)

const {
startTests,
setupCommonEnv,
setupSnapshotEnvironment,
takeCoverageInsideWorker,
} = await importId('vitest/browser') as typeof import('vitest/browser')

Expand All @@ -101,10 +99,8 @@ async function runTests(paths: string[], config: any) {
runner = new BrowserRunner({ config, browserHashMap })
}

if (!hasSnapshot) {
setupSnapshotEnvironment(new BrowserSnapshotEnvironment())
hasSnapshot = true
}
if (!config.snapshotOptions.snapshotEnvironment)
config.snapshotOptions.snapshotEnvironment = new BrowserSnapshotEnvironment()

try {
await setupCommonEnv(config)
Expand Down
8 changes: 8 additions & 0 deletions packages/browser/src/client/snapshot.ts
Expand Up @@ -2,6 +2,14 @@ import { rpc } from './rpc'
import type { SnapshotEnvironment } from '#types'

export class BrowserSnapshotEnvironment implements SnapshotEnvironment {
getVersion(): string {
return '1'
}

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

readSnapshotFile(filepath: string): Promise<string | null> {
return rpc().readFile(filepath)
}
Expand Down
22 changes: 1 addition & 21 deletions packages/runner/src/utils/error.ts
Expand Up @@ -2,27 +2,7 @@ import { deepClone, format, getOwnProperties, getType, stringify } from '@vitest
import type { DiffOptions } from '@vitest/utils/diff'
import { unifiedDiff } from '@vitest/utils/diff'

export interface ParsedStack {
method: string
file: string
line: number
column: number
}

export interface ErrorWithDiff extends Error {
name: string
nameStr?: string
stack?: string
stackStr?: string
stacks?: ParsedStack[]
showDiff?: boolean
diff?: string
actual?: any
expected?: any
operator?: string
type?: string
frame?: string
}
export type { ParsedStack, ErrorWithDiff } from '@vitest/utils'

const IS_RECORD_SYMBOL = '@@__IMMUTABLE_RECORD__@@'
const IS_COLLECTION_SYMBOL = '@@__IMMUTABLE_ITERABLE__@@'
Expand Down
71 changes: 71 additions & 0 deletions packages/snapshot/README.md
@@ -0,0 +1,71 @@
# @vitest/snapshot

Lightweight implementation of Jest's snapshots.

## Usage

```js
import { SnapshotClient } from '@vitest/snapshot'
import { NodeSnapshotEnvironment } from '@vitest/snapshot/environment'
import { SnapshotManager } from '@vitest/snapshot/manager'

export class CustomSnapshotClient extends SnapshotClient {
// by default, @vitest/snapshot checks equality with `!==`
// you need to provide your own equality check implementation
// this function is called when `.toMatchSnapshot({ property: 1 })` is called
equalityCheck(received, expected) {
return equals(received, expected, [iterableEquality, subsetEquality])
}
}

const client = new CustomSnapshotClient()
// class that implements snapshot saving and reading
// by default uses fs module, but you can provide your own implementation depending on the environment
const environment = new NodeSnapshotEnvironment()

const getCurrentFilepath = () => '/file.spec.ts'
const getCurrentTestName = () => 'test1'

const wrapper = (received) => {
function __INLINE_SNAPSHOT__(inlineSnapshot, message) {
client.assert({
received,
message,
isInline: true,
inlineSnapshot,
// you need to implement this yourselves,
// this depends on your runner
filepath: getCurrentFilepath(),
name: getCurrentTestName(),
})
}
return {
// the name is hard-coded, it should be inside another function, so Vitest can find the actual test file where it was called (parses call stack trace + 2)
// you can override this behaviour in SnapshotState's `_inferInlineSnapshotStack` method by providing your own SnapshotState to SnapshotClient constructor
toMatchInlineSnapshot: (...args) => __INLINE_SNAPSHOT__(...args),
}
}

const options = {
updateSnapshot: 'new',
snapshotEnvironment: environment,
}

await client.setTest(getCurrentFilepath(), getCurrentTestName(), options)

// uses "pretty-format", so it requires quotes
// also naming is hard-coded when parsing test files
wrapper('text 1').toMatchInlineSnapshot()
wrapper('text 2').toMatchInlineSnapshot('"text 2"')

const result = await client.resetCurrent() // this saves files and returns SnapshotResult

// you can use manager to manage several clients
const manager = new SnapshotManager(options)
manager.add(result)

// do something
// and then read the summary

console.log(manager.summary)
```
50 changes: 50 additions & 0 deletions packages/snapshot/package.json
@@ -0,0 +1,50 @@
{
"name": "@vitest/snapshot",
"type": "module",
"version": "0.29.3",
"description": "Vitest Snapshot Resolver",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/vitest-dev/vitest.git",
"directory": "packages/snapshot"
},
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./environment": {
"types": "./dist/environment.d.ts",
"import": "./dist/environment.js"
},
"./manager": {
"types": "./dist/manager.d.ts",
"import": "./dist/manager.js"
},
"./*": "./*"
},
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist",
"*.d.ts"
],
"scripts": {
"build": "rimraf dist && rollup -c",
"dev": "rollup -c --watch",
"prepublishOnly": "pnpm build"
},
"dependencies": {
"magic-string": "^0.27.0",
"pathe": "^1.1.0",
"pretty-format": "^27.5.1"
},
"devDependencies": {
"@types/natural-compare": "^1.4.1",
"@vitest/utils": "workspace:*",
"natural-compare": "^1.4.0"
}
}
63 changes: 63 additions & 0 deletions packages/snapshot/rollup.config.js
@@ -0,0 +1,63 @@
import { builtinModules } from 'module'
import esbuild from 'rollup-plugin-esbuild'
import nodeResolve from '@rollup/plugin-node-resolve'
import dts from 'rollup-plugin-dts'
import commonjs from '@rollup/plugin-commonjs'
import { defineConfig } from 'rollup'
import pkg from './package.json'

const external = [
...builtinModules,
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {}),
]

const entries = {
index: 'src/index.ts',
environment: 'src/environment.ts',
manager: 'src/manager.ts',
}

const plugins = [
nodeResolve({
preferBuiltins: true,
}),
commonjs(),
esbuild({
target: 'node14',
}),
]

export default defineConfig([
{
input: entries,
output: {
dir: 'dist',
format: 'esm',
entryFileNames: '[name].js',
chunkFileNames: 'chunk-[name].js',
},
external,
plugins,
onwarn,
},
{
input: entries,
output: {
dir: 'dist',
entryFileNames: '[name].d.ts',
format: 'esm',
},
external,
plugins: [
dts({ respectExternal: true }),
],
onwarn,
},
])

function onwarn(message) {
if (['EMPTY_BUNDLE', 'CIRCULAR_DEPENDENCY'].includes(message.code))
return
console.error(message)
}

0 comments on commit 6aff017

Please sign in to comment.