[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
12

Configure Feed

Select the types of activity you want to include in your feed.

fix(browser): resolved failure to find arbitrarily-named snapshot files when using `expect(...).toMatchFileSnapshot()` matcher. (#4839)

Co-authored-by: Zac Mullett <zac@Zacs-Air.modem>
Co-authored-by: Vladimir <sleuths.slews0s@icloud.com>

authored by

Zac Mullett
Zac Mullett
Vladimir
and committed by
GitHub
(Jan 4, 2024, 12:23 PM +0100) b8140fca 345a25d6

+61 -15
-2
packages/snapshot/src/manager.ts
··· 3 3 4 4 export class SnapshotManager { 5 5 summary: SnapshotSummary = undefined! 6 - resolvedPaths = new Set<string>() 7 6 extension = '.snap' 8 7 9 8 constructor(public options: Omit<SnapshotStateOptions, 'snapshotEnvironment'>) { ··· 30 29 }) 31 30 32 31 const path = resolver(testPath, this.extension) 33 - this.resolvedPaths.add(path) 34 32 return path 35 33 } 36 34
+17 -8
packages/vitest/src/api/setup.ts
··· 6 6 import { parse, stringify } from 'flatted' 7 7 import type { WebSocket } from 'ws' 8 8 import { WebSocketServer } from 'ws' 9 + import { isFileServingAllowed } from 'vite' 9 10 import type { ViteDevServer } from 'vite' 10 11 import type { StackTraceParserOptions } from '@vitest/utils/source-map' 11 12 import { API_PATH } from '../constants' 12 13 import type { Vitest } from '../node' 13 14 import type { File, ModuleGraphData, Reporter, TaskResultPack, UserConsoleLog } from '../types' 14 - import { getModuleGraph, isPrimitive } from '../utils' 15 + import { getModuleGraph, isPrimitive, stringifyReplace } from '../utils' 15 16 import type { WorkspaceProject } from '../node/workspace' 16 17 import { parseErrorStacktrace } from '../utils/source-map' 17 18 import type { TransformResultWithSource, WebSocketEvents, WebSocketHandlers } from './types' 18 19 19 - export function setup(vitestOrWorkspace: Vitest | WorkspaceProject, server?: ViteDevServer) { 20 + export function setup(vitestOrWorkspace: Vitest | WorkspaceProject, _server?: ViteDevServer) { 20 21 const ctx = 'ctx' in vitestOrWorkspace ? vitestOrWorkspace.ctx : vitestOrWorkspace 21 22 22 23 const wss = new WebSocketServer({ noServer: true }) 23 24 24 25 const clients = new Map<WebSocket, BirpcReturn<WebSocketEvents, WebSocketHandlers>>() 25 26 26 - ;(server || ctx.server).httpServer?.on('upgrade', (request, socket, head) => { 27 + const server = _server || ctx.server 28 + 29 + server.httpServer?.on('upgrade', (request, socket, head) => { 27 30 if (!request.url) 28 31 return 29 32 ··· 37 40 }) 38 41 }) 39 42 43 + function checkFileAccess(path: string) { 44 + if (!isFileServingAllowed(path, server)) 45 + throw new Error(`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`) 46 + } 47 + 40 48 function setupClient(ws: WebSocket) { 41 49 const rpc = createBirpc<WebSocketEvents, WebSocketHandlers>( 42 50 { ··· 73 81 return ctx.snapshot.resolveRawPath(testPath, rawPath) 74 82 }, 75 83 async readSnapshotFile(snapshotPath) { 76 - if (!ctx.snapshot.resolvedPaths.has(snapshotPath) || !existsSync(snapshotPath)) 84 + checkFileAccess(snapshotPath) 85 + if (!existsSync(snapshotPath)) 77 86 return null 78 87 return fs.readFile(snapshotPath, 'utf-8') 79 88 }, ··· 88 97 return fs.writeFile(id, content, 'utf-8') 89 98 }, 90 99 async saveSnapshotFile(id, content) { 91 - if (!ctx.snapshot.resolvedPaths.has(id)) 92 - throw new Error(`Snapshot file "${id}" does not exist.`) 100 + checkFileAccess(id) 93 101 await fs.mkdir(dirname(id), { recursive: true }) 94 102 return fs.writeFile(id, content, 'utf-8') 95 103 }, 96 104 async removeSnapshotFile(id) { 97 - if (!ctx.snapshot.resolvedPaths.has(id) || !existsSync(id)) 105 + checkFileAccess(id) 106 + if (!existsSync(id)) 98 107 throw new Error(`Snapshot file "${id}" does not exist.`) 99 108 return fs.unlink(id) 100 109 }, ··· 140 149 post: msg => ws.send(msg), 141 150 on: fn => ws.on('message', fn), 142 151 eventNames: ['onUserConsoleLog', 'onFinished', 'onCollected', 'onCancel'], 143 - serialize: stringify, 152 + serialize: (data: any) => stringify(data, stringifyReplace), 144 153 deserialize: parse, 145 154 }, 146 155 )
+1
packages/vitest/src/utils/index.ts
··· 9 9 export * from './timers' 10 10 export * from './env' 11 11 export * from './modules' 12 + export * from './serialization' 12 13 13 14 export const isWindows = isNode && process.platform === 'win32' 14 15 export function getRunMode() {
+22
packages/vitest/src/utils/serialization.ts
··· 1 + // Serialization support utils. 2 + 3 + function cloneByOwnProperties(value: any) { 4 + // Clones the value's properties into a new Object. The simpler approach of 5 + // Object.assign() won't work in the case that properties are not enumerable. 6 + return Object.getOwnPropertyNames(value) 7 + .reduce((clone, prop) => ({ 8 + ...clone, 9 + [prop]: value[prop], 10 + }), {}) 11 + } 12 + 13 + /** 14 + * Replacer function for serialization methods such as JS.stringify() or 15 + * flatted.stringify(). 16 + */ 17 + export function stringifyReplace(key: string, value: any) { 18 + if (value instanceof Error) 19 + return cloneByOwnProperties(value) 20 + else 21 + return value 22 + }
+1 -1
test/benchmark/package.json
··· 3 3 "type": "module", 4 4 "private": true, 5 5 "scripts": { 6 - "test": "node --test specs/ && echo '1'", 6 + "test": "node --test specs/* && echo '1'", 7 7 "bench:json": "vitest bench --reporter=json", 8 8 "bench": "vitest bench" 9 9 },
+6 -2
test/browser/specs/runner.test.mjs
··· 28 28 const failedTests = getFailed(browserResultJson.testResults) 29 29 30 30 await test('tests are actually running', async () => { 31 - assert.ok(browserResultJson.testResults.length === 9, 'Not all the tests have been run') 31 + assert.ok(browserResultJson.testResults.length === 10, 'Not all the tests have been run') 32 32 assert.ok(passedTests.length === 8, 'Some tests failed') 33 - assert.ok(failedTests.length === 1, 'Some tests have passed but should fail') 33 + assert.ok(failedTests.length === 2, 'Some tests have passed but should fail') 34 34 35 35 assert.doesNotMatch(stderr, /Unhandled Error/, 'doesn\'t have any unhandled errors') 36 36 }) ··· 80 80 assert.ok(stderr.includes('Vitest encountered a \`confirm\("test"\)\`'), 'prints warning for confirm') 81 81 assert.ok(stderr.includes('Vitest encountered a \`prompt\("test"\)\`'), 'prints warning for prompt') 82 82 }) 83 + 84 + await test('snapshot inaccessible file debuggability', () => { 85 + assert.ok(stdout.includes('Access denied to "/inaccesible/path".'), 'file security enforcement explained') 86 + })
+1
test/browser/test/__snapshots__/custom/my_snapshot
··· 1 + my snapshot content
+1 -1
test/browser/test/__snapshots__/snapshot.test.ts.snap
··· 1 1 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 2 3 - exports[`file snapshot 1`] = `1`; 3 + exports[`snapshot 1`] = `1`;
+6
test/browser/test/failing.snaphot.test.ts
··· 1 + import { expect, test } from 'vitest' 2 + 3 + test('file snapshot', async () => { 4 + await expect('inaccessible snapshot content') 5 + .toMatchFileSnapshot('/inaccesible/path') 6 + })
+6 -1
test/browser/test/snapshot.test.ts
··· 4 4 expect(1).toMatchInlineSnapshot('1') 5 5 }) 6 6 7 - test('file snapshot', () => { 7 + test('snapshot', () => { 8 8 expect(1).toMatchSnapshot() 9 9 }) 10 + 11 + test('file snapshot', async () => { 12 + await expect('my snapshot content') 13 + .toMatchFileSnapshot('./__snapshots__/custom/my_snapshot') 14 + })