diff --git a/packages/vite-node/package.json b/packages/vite-node/package.json index 8eef343563b6..7315f04a97d7 100644 --- a/packages/vite-node/package.json +++ b/packages/vite-node/package.json @@ -34,6 +34,11 @@ "types": "./dist/utils.d.ts", "require": "./dist/utils.cjs", "import": "./dist/utils.mjs" + }, + "./hmr": { + "types": "./dist/hmr.d.ts", + "require": "./dist/hmr.cjs", + "import": "./dist/hmr.mjs" } }, "main": "./dist/index.mjs", diff --git a/packages/vite-node/rollup.config.js b/packages/vite-node/rollup.config.js index ab44d5e95134..c24b4b11364e 100644 --- a/packages/vite-node/rollup.config.js +++ b/packages/vite-node/rollup.config.js @@ -14,6 +14,7 @@ const entries = { client: 'src/client.ts', utils: 'src/utils.ts', cli: 'src/cli.ts', + hmr: 'src/hmr/index.ts', } const external = [ @@ -23,6 +24,7 @@ const external = [ 'birpc', 'vite', 'url', + 'events', ] const plugins = [ diff --git a/packages/vite-node/src/cli.ts b/packages/vite-node/src/cli.ts index ad3daeb585f8..7cce96604c01 100644 --- a/packages/vite-node/src/cli.ts +++ b/packages/vite-node/src/cli.ts @@ -1,11 +1,12 @@ import cac from 'cac' -import { cyan, dim, red } from 'kolorist' +import { red } from 'kolorist' import { createServer } from 'vite' import { version } from '../package.json' import { ViteNodeServer } from './server' import { ViteNodeRunner } from './client' import type { ViteNodeServerOptions } from './types' import { toArray } from './utils' +import { createHotContext, handleMessage, viteNodeHmrPlugin } from './hmr' const cli = cac('vite-node') @@ -49,6 +50,9 @@ async function run(files: string[], options: CliOptions = {}) { logLevel: 'error', configFile: options.config, root: options.root, + plugins: [ + options.watch && viteNodeHmrPlugin(), + ], }) await server.pluginContainer.buildStart({}) @@ -63,6 +67,9 @@ async function run(files: string[], options: CliOptions = {}) { resolveId(id, importer) { return node.resolveId(id, importer) }, + createHotContext(runner, url) { + return createHotContext(runner, server.emitter, files, url) + }, }) // provide the vite define variable in this context @@ -74,18 +81,8 @@ async function run(files: string[], options: CliOptions = {}) { if (!options.watch) await server.close() - server.watcher.on('change', async (path) => { - console.log(`${cyan('[vite-node]')} File change detected. ${dim(path)}`) - - // invalidate module cache but not node_modules - Array.from(runner.moduleCache.keys()) - .forEach((i) => { - if (!i.includes('node_modules')) - runner.moduleCache.delete(i) - }) - - for (const file of files) - await runner.executeFile(file) + server.emitter?.on('message', (payload) => { + handleMessage(runner, server.emitter, files, payload) }) } diff --git a/packages/vite-node/src/client.ts b/packages/vite-node/src/client.ts index 706286fb1401..6cd38a905409 100644 --- a/packages/vite-node/src/client.ts +++ b/packages/vite-node/src/client.ts @@ -5,7 +5,7 @@ import { dirname, extname, isAbsolute, resolve } from 'pathe' import { isNodeBuiltin } from 'mlly' import createDebug from 'debug' import { isPrimitive, mergeSlashes, normalizeModuleId, normalizeRequestId, slash, toFilePath } from './utils' -import type { ModuleCache, ViteNodeRunnerOptions } from './types' +import type { HotContext, ModuleCache, ViteNodeRunnerOptions } from './types' const debugExecute = createDebug('vite-node:client:execute') const debugNative = createDebug('vite-node:client:native') @@ -161,6 +161,7 @@ export class ViteNodeRunner { // disambiguate the `:/` on windows: see nodejs/node#31710 const url = pathToFileURL(fsPath).href + const meta = { url } const exports: any = Object.create(null) exports[Symbol.toStringTag] = 'Module' @@ -177,6 +178,18 @@ export class ViteNodeRunner { }, } + // Vite hot context + let hotContext: HotContext | undefined + if (this.options.createHotContext) { + Object.defineProperty(meta, 'hot', { + enumerable: true, + get: () => { + hotContext ||= this.options.createHotContext?.(this, `/@fs/${fsPath}`) + return hotContext + }, + }) + } + // Be careful when changing this // changing context will change amount of code added on line :114 (vm.runInThisContext) // this messes up sourcemaps for coverage @@ -187,8 +200,7 @@ export class ViteNodeRunner { __vite_ssr_dynamic_import__: request, __vite_ssr_exports__: exports, __vite_ssr_exportAll__: (obj: any) => exportAll(exports, obj), - __vite_ssr_import_meta__: { url }, - + __vite_ssr_import_meta__: meta, __vitest_resolve_id__: resolveId, // cjs compact diff --git a/packages/vite-node/src/hmr/emitter.ts b/packages/vite-node/src/hmr/emitter.ts new file mode 100644 index 000000000000..02904391e25d --- /dev/null +++ b/packages/vite-node/src/hmr/emitter.ts @@ -0,0 +1,42 @@ +import { EventEmitter } from 'events' +import type { HMRPayload, Plugin } from 'vite' + +export type EventType = string | symbol +export type Handler = (event: T) => void +export interface Emitter> { + on(type: Key, handler: Handler): void + off(type: Key, handler?: Handler): void + emit(type: Key, event: Events[Key]): void + emit(type: undefined extends Events[Key] ? Key : never): void +} + +export type HMREmitter = Emitter<{ + 'message': HMRPayload +}> & EventEmitter + +declare module 'vite' { + interface ViteDevServer { + emitter: HMREmitter + } +} + +export function createHmrEmitter(): HMREmitter { + const emitter = new EventEmitter() + return emitter +} + +export function viteNodeHmrPlugin(): Plugin { + const emitter = createHmrEmitter() + return { + name: 'vite-node:hmr', + + configureServer(server) { + const _send = server.ws.send + server.emitter = emitter + server.ws.send = function (payload: HMRPayload) { + _send(payload) + emitter.emit('message', payload) + } + }, + } +} diff --git a/packages/vite-node/src/hmr/hmr.ts b/packages/vite-node/src/hmr/hmr.ts new file mode 100644 index 000000000000..347206f0923e --- /dev/null +++ b/packages/vite-node/src/hmr/hmr.ts @@ -0,0 +1,308 @@ +/* eslint-disable no-console */ +import type { ErrorPayload, FullReloadPayload, HMRPayload, PrunePayload, Update, UpdatePayload } from 'vite/types/hmrPayload' +import { cyan } from 'kolorist' +import createDebug from 'debug' +import type { ViteNodeRunner } from '../client' +import type { HotContext } from '../types' +import type { HMREmitter } from './emitter' + +const debugHmr = createDebug('vite-node:hmr') + +export interface CustomEventMap { + 'vite:beforeUpdate': UpdatePayload + 'vite:beforePrune': PrunePayload + 'vite:beforeFullReload': FullReloadPayload + 'vite:error': ErrorPayload +} + +export type InferCustomEventPayload = + T extends keyof CustomEventMap ? CustomEventMap[T] : any + +export interface HotModule { + id: string + callbacks: HotCallback[] +} + +export interface HotCallback { + // the dependencies must be fetchable paths + deps: string[] + fn: (modules: object[]) => void +} + +interface CacheData { + hotModulesMap: Map + dataMap: Map + disposeMap: Map void | Promise> + pruneMap: Map void | Promise> + customListenersMap: Map void)[]> + ctxToListenersMap: Map< + string, + Map void)[]> + > + messageBuffer: string[] + isFirstUpdate: boolean + pending: boolean + queued: Promise<(() => void) | undefined>[] +} + +const cache: WeakMap = new WeakMap() + +export function getCache(runner: ViteNodeRunner): CacheData { + if (!cache.has(runner)) { + cache.set(runner, { + hotModulesMap: new Map(), + dataMap: new Map(), + disposeMap: new Map(), + pruneMap: new Map(), + customListenersMap: new Map(), + ctxToListenersMap: new Map(), + messageBuffer: [], + isFirstUpdate: false, + pending: false, + queued: [], + }) + } + return cache.get(runner) as CacheData +} + +export function sendMessageBuffer(runner: ViteNodeRunner, emitter: HMREmitter) { + const maps = getCache(runner) + maps.messageBuffer.forEach(msg => emitter.emit('custom', msg)) + maps.messageBuffer.length = 0 +} + +export async function reload(runner: ViteNodeRunner, files: string[]) { + // invalidate module cache but not node_modules + Array.from(runner.moduleCache.keys()) + .forEach((i) => { + if (!i.includes('node_modules')) + runner.moduleCache.delete(i) + }) + + return Promise.all(files.map(file => runner.executeId(file))) +} + +function notifyListeners( + runner: ViteNodeRunner, + event: T, + data: InferCustomEventPayload, +): void +function notifyListeners(runner: ViteNodeRunner, event: string, data: any): void { + const maps = getCache(runner) + const cbs = maps.customListenersMap.get(event) + if (cbs) + cbs.forEach(cb => cb(data)) +} + +async function queueUpdate(runner: ViteNodeRunner, p: Promise<(() => void) | undefined>) { + const maps = getCache(runner) + maps.queued.push(p) + if (!maps.pending) { + maps.pending = true + await Promise.resolve() + maps.pending = false + const loading = [...maps.queued] + maps.queued = [] + ;(await Promise.all(loading)).forEach(fn => fn && fn()) + } +} + +async function fetchUpdate(runner: ViteNodeRunner, { path, acceptedPath }: Update) { + const maps = getCache(runner) + const mod = maps.hotModulesMap.get(path) + + if (!mod) { + // In a code-splitting project, + // it is common that the hot-updating module is not loaded yet. + // https://github.com/vitejs/vite/issues/721 + return + } + + const moduleMap = new Map() + const isSelfUpdate = path === acceptedPath + + // make sure we only import each dep once + const modulesToUpdate = new Set() + if (isSelfUpdate) { + // self update - only update self + modulesToUpdate.add(path) + } + else { + // dep update + for (const { deps } of mod.callbacks) { + deps.forEach((dep) => { + if (acceptedPath === dep) + modulesToUpdate.add(dep) + }) + } + } + + // determine the qualified callbacks before we re-import the modules + const qualifiedCallbacks = mod.callbacks.filter(({ deps }) => { + return deps.some(dep => modulesToUpdate.has(dep)) + }) + + await Promise.all( + Array.from(modulesToUpdate).map(async (dep) => { + const disposer = maps.disposeMap.get(dep) + if (disposer) + await disposer(maps.dataMap.get(dep)) + try { + const newMod = await reload(runner, [dep]) + moduleMap.set(dep, newMod) + } + catch (e: any) { + warnFailedFetch(e, dep) + } + }), + ) + + return () => { + for (const { deps, fn } of qualifiedCallbacks) + fn(deps.map(dep => moduleMap.get(dep))) + + const loggedPath = isSelfUpdate ? path : `${acceptedPath} via ${path}` + console.log(`${cyan('[vite-node]')} hot updated: ${loggedPath}`) + } +} + +function warnFailedFetch(err: Error, path: string | string[]) { + if (!err.message.match('fetch')) + console.error(err) + + console.error( + `[hmr] Failed to reload ${path}. ` + + 'This could be due to syntax errors or importing non-existent ' + + 'modules. (see errors above)', + ) +} + +export async function handleMessage(runner: ViteNodeRunner, emitter: HMREmitter, files: string[], payload: HMRPayload) { + const maps = getCache(runner) + switch (payload.type) { + case 'connected': + sendMessageBuffer(runner, emitter) + break + case 'update': + notifyListeners(runner, 'vite:beforeUpdate', payload) + if (maps.isFirstUpdate) { + reload(runner, files) + maps.isFirstUpdate = true + } + payload.updates.forEach((update) => { + if (update.type === 'js-update') { + queueUpdate(runner, fetchUpdate(runner, update)) + } + else { + // css-update + console.error(`${cyan('[vite-node]')} no support css hmr.}`) + } + }) + break + case 'full-reload': + reload(runner, files) + break + case 'prune': + payload.paths.forEach((path) => { + const fn = maps.pruneMap.get(path) + if (fn) + fn(maps.dataMap.get(path)) + }) + break + case 'error': { + notifyListeners(runner, 'vite:error', payload) + const err = payload.err + console.error(`${cyan('[vite-node]')} Internal Server Error\n${err.message}\n${err.stack}`) + break + } + } +} + +export function createHotContext( + runner: ViteNodeRunner, + emitter: HMREmitter, + files: string[], + ownerPath: string, +): HotContext { + debugHmr('createHotContext', ownerPath) + const maps = getCache(runner) + if (!maps.dataMap.has(ownerPath)) + maps.dataMap.set(ownerPath, {}) + + // when a file is hot updated, a new context is created + // clear its stale callbacks + const mod = maps.hotModulesMap.get(ownerPath) + if (mod) + mod.callbacks = [] + + const newListeners = new Map() + maps.ctxToListenersMap.set(ownerPath, newListeners) + + function acceptDeps(deps: string[], callback: HotCallback['fn'] = () => {}) { + const mod: HotModule = maps.hotModulesMap.get(ownerPath) || { + id: ownerPath, + callbacks: [], + } + mod.callbacks.push({ + deps, + fn: callback, + }) + maps.hotModulesMap.set(ownerPath, mod) + } + + const hot: HotContext = { + get data() { + return maps.dataMap.get(ownerPath) + }, + + accept(deps?: any, callback?: any) { + if (typeof deps === 'function' || !deps) { + // self-accept: hot.accept(() => {}) + acceptDeps([ownerPath], ([mod]) => deps && deps(mod)) + } + else if (typeof deps === 'string') { + // explicit deps + acceptDeps([deps], ([mod]) => callback && callback(mod)) + } + else if (Array.isArray(deps)) { + acceptDeps(deps, callback) + } + else { + throw new TypeError('invalid hot.accept() usage.') + } + }, + + dispose(cb) { + maps.disposeMap.set(ownerPath, cb) + }, + + // @ts-expect-error untyped + prune(cb: (data: any) => void) { + maps.pruneMap.set(ownerPath, cb) + }, + + invalidate() { + return reload(runner, files) + }, + + on( + event: T, + cb: (payload: InferCustomEventPayload) => void, + ): void { + const addToMap = (map: Map) => { + const existing = map.get(event) || [] + existing.push(cb) + map.set(event, existing) + } + addToMap(maps.customListenersMap) + addToMap(newListeners) + }, + + send(event: T, data?: InferCustomEventPayload): void { + maps.messageBuffer.push(JSON.stringify({ type: 'custom', event, data })) + sendMessageBuffer(runner, emitter) + }, + } + + return hot +} diff --git a/packages/vite-node/src/hmr/index.ts b/packages/vite-node/src/hmr/index.ts new file mode 100644 index 000000000000..1862f7e41ddb --- /dev/null +++ b/packages/vite-node/src/hmr/index.ts @@ -0,0 +1,2 @@ +export * from './emitter' +export * from './hmr' diff --git a/packages/vite-node/src/types.ts b/packages/vite-node/src/types.ts index ab84483655cb..f0e594e67fd0 100644 --- a/packages/vite-node/src/types.ts +++ b/packages/vite-node/src/types.ts @@ -1,4 +1,5 @@ -import type { ModuleCacheMap } from './client' +import type { ViteHotContext } from 'vite/types/hot' +import type { ModuleCacheMap, ViteNodeRunner } from './client' export type Nullable = T | null | undefined export type Arrayable = T | Array @@ -32,10 +33,14 @@ export interface FetchResult { map?: RawSourceMap } +export type HotContext = Omit + export type FetchFunction = (id: string) => Promise export type ResolveIdFunction = (id: string, importer?: string) => Promise +export type CreateHotContextFunction = (runner: ViteNodeRunner, url: string) => HotContext + export interface ModuleCache { promise?: Promise exports?: any @@ -46,6 +51,7 @@ export interface ViteNodeRunnerOptions { root: string fetchModule: FetchFunction resolveId?: ResolveIdFunction + createHotContext?: CreateHotContextFunction base?: string moduleCache?: ModuleCacheMap interopDefault?: boolean diff --git a/packages/vitest/src/constants.ts b/packages/vitest/src/constants.ts index 19f4e8d57bb2..001aa7b90831 100644 --- a/packages/vitest/src/constants.ts +++ b/packages/vitest/src/constants.ts @@ -10,11 +10,17 @@ export const API_PATH = '/__vitest_api__' export const configFiles = [ 'vitest.config.ts', + 'vitest.config.mts', + 'vitest.config.cts', 'vitest.config.js', 'vitest.config.mjs', + 'vitest.config.cjs', 'vite.config.ts', + 'vite.config.mts', + 'vite.config.cts', 'vite.config.js', 'vite.config.mjs', + 'vite.config.cjs', ] export const globalApis = [ diff --git a/packages/vitest/src/integrations/snapshot/chai.ts b/packages/vitest/src/integrations/snapshot/chai.ts index 595bee4344b3..1fb14f045bd3 100644 --- a/packages/vitest/src/integrations/snapshot/chai.ts +++ b/packages/vitest/src/integrations/snapshot/chai.ts @@ -10,15 +10,27 @@ export function getSnapshotClient(): SnapshotClient { return _client } -const getErrorString = (expected: () => void) => { +const getErrorMessage = (err: unknown) => { + if (err instanceof Error) + return err.message + + return err +} + +const getErrorString = (expected: () => void | Error, promise: string | undefined) => { + if (typeof expected !== 'function') { + if (!promise) + throw new Error(`expected must be a function, received ${typeof expected}`) + + // when "promised", it receives thrown error + return getErrorMessage(expected) + } + try { expected() } catch (e) { - if (e instanceof Error) - return e.message - - return e + return getErrorMessage(e) } throw new Error('snapshot function didn\'t threw') @@ -81,9 +93,10 @@ export const SnapshotPlugin: ChaiPlugin = (chai, utils) => { function (this: Record, message?: string) { const expected = utils.flag(this, 'object') const test = utils.flag(this, 'vitest-test') + const promise = utils.flag(this, 'promise') as string | undefined const errorMessage = utils.flag(this, 'message') getSnapshotClient().assert({ - received: getErrorString(expected), + received: getErrorString(expected, promise), test, message, errorMessage, @@ -97,9 +110,10 @@ export const SnapshotPlugin: ChaiPlugin = (chai, utils) => { const expected = utils.flag(this, 'object') const error = utils.flag(this, 'error') const test = utils.flag(this, 'vitest-test') + const promise = utils.flag(this, 'promise') as string | undefined const errorMessage = utils.flag(this, 'message') getSnapshotClient().assert({ - received: getErrorString(expected), + received: getErrorString(expected, promise), test, message, inlineSnapshot, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4ce0033b38b7..f7fe25d5f0f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -163,7 +163,7 @@ importers: lit: 2.2.5 devDependencies: '@vitest/ui': link:../../packages/ui - happy-dom: 5.3.4 + happy-dom: 6.0.0 vite: 2.9.12 vitest: link:../../packages/vitest @@ -894,11 +894,9 @@ importers: test/vite-node: specifiers: - vite: ^2.9.10 vite-node: workspace:* vitest: workspace:* devDependencies: - vite: 2.9.12 vite-node: link:../../packages/vite-node vitest: link:../../packages/vitest @@ -3749,7 +3747,7 @@ packages: '@babel/runtime': 7.17.9 '@emotion/is-prop-valid': 1.1.2 '@mui/types': 7.1.4 - '@mui/utils': 5.8.4_react@17.0.2 + '@mui/utils': 5.8.6_react@17.0.2 '@popperjs/core': 2.11.5 clsx: 1.1.1 prop-types: 15.8.1 @@ -3864,22 +3862,6 @@ packages: react-transition-group: 4.4.2_sfoxds7t5ydpegc3knd667wn6m dev: false - /@mui/private-theming/5.8.4_react@17.0.2: - resolution: {integrity: sha512-3Lp0VAEjtQygJ70MWEyHkKvg327O6YoBH6ZNEy6fIsrK6gmRIj+YrlvJ7LQCbowY+qDGnbdMrTBd1hfThlI8lg==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.17.9 - '@mui/utils': 5.8.6_react@17.0.2 - prop-types: 15.8.1 - react: 17.0.2 - dev: false - /@mui/private-theming/5.8.6_react@17.0.2: resolution: {integrity: sha512-yHsJk1qU9r/q0DlnxGRJPHyM0Y/nUv8FTNgDTiI9I58GWuVuZqeTUr7JRvPh6ybeP/FLtW5eXEavRK9wxVk4uQ==} engines: {node: '>=12.0.0'} @@ -3936,10 +3918,10 @@ packages: '@babel/runtime': 7.17.9 '@emotion/react': 11.9.0_3dj5wppwohj5ocihzt4m54mr2a '@emotion/styled': 11.8.1_3zgpe2oef7sbs566rsy6a7qm7i - '@mui/private-theming': 5.8.4_react@17.0.2 + '@mui/private-theming': 5.8.6_react@17.0.2 '@mui/styled-engine': 5.8.0_bgqmsvm4hz6izcmpcwescmz73y '@mui/types': 7.1.4 - '@mui/utils': 5.8.4_react@17.0.2 + '@mui/utils': 5.8.6_react@17.0.2 clsx: 1.1.1 csstype: 3.1.0 prop-types: 15.8.1 @@ -4007,20 +3989,6 @@ packages: react-is: 17.0.2 dev: false - /@mui/utils/5.8.4_react@17.0.2: - resolution: {integrity: sha512-BHYErfrjqqh76KaDAm8wZlhEip1Uj7Cmco65NcsF3BWrAl3FWngACpaPZeEbTgmaEwyWAQEE6LZhsmy43hfyqQ==} - engines: {node: '>=12.0.0'} - peerDependencies: - react: ^17.0.0 || ^18.0.0 - dependencies: - '@babel/runtime': 7.17.9 - '@types/prop-types': 15.7.5 - '@types/react-is': 17.0.3 - prop-types: 15.8.1 - react: 17.0.2 - react-is: 17.0.2 - dev: false - /@mui/utils/5.8.6_react@17.0.2: resolution: {integrity: sha512-QM2Sd1xZo2jOt2Vz5Rmro+pi2FLJyiv4+OjxkUwXR3oUM65KSMAMLl/KNYU55s3W3DLRFP5MVwE4FhAbHseHAg==} engines: {node: '>=12.0.0'} @@ -12880,8 +12848,8 @@ packages: - encoding dev: true - /happy-dom/5.3.4: - resolution: {integrity: sha512-EGVX/03ZQcWWqjorPHbUNuVM47bc56K5QtUZUAwEjc1D3e3BzrZO4TtBt+FrM0DfrirBc/Fbtz4FzpD1ohIcqQ==} + /happy-dom/6.0.0: + resolution: {integrity: sha512-Nk+hyCemZGIyQ0gDLyb6K1PFOHepfec101dG93z5g8+LIYSUZWT3FDzWUIUwfYkcAnaRCM9hMDgmR2pFLUYE9w==} dependencies: css.escape: 1.5.1 he: 1.2.0 diff --git a/test/core/test/__snapshots__/snapshot.test.ts.snap b/test/core/test/__snapshots__/snapshot.test.ts.snap index 3287a50633fc..9178db8a946e 100644 --- a/test/core/test/__snapshots__/snapshot.test.ts.snap +++ b/test/core/test/__snapshots__/snapshot.test.ts.snap @@ -36,6 +36,8 @@ exports[`throwing 3`] = ` } `; +exports[`throwing 4`] = `"omega"`; + exports[`with big array 1`] = ` { "this": { diff --git a/test/core/test/snapshot-inline.test.ts b/test/core/test/snapshot-inline.test.ts index fda2031e98a2..4a8bbb22fc2a 100644 --- a/test/core/test/snapshot-inline.test.ts +++ b/test/core/test/snapshot-inline.test.ts @@ -59,7 +59,7 @@ test('template literal', () => { `) }) -test('throwing inline snapshots', () => { +test('throwing inline snapshots', async () => { expect(() => { throw new Error('omega') }).toThrowErrorMatchingInlineSnapshot('"omega"') @@ -99,6 +99,16 @@ test('throwing inline snapshots', () => { with newlines" `) + + await expect(async () => { + throw new Error('omega') + }).rejects.toThrowErrorMatchingInlineSnapshot('"omega"') +}) + +test('throwing expect should be a function', async () => { + expect(() => { + expect(new Error('omega')).toThrowErrorMatchingInlineSnapshot() + }).toThrow(/expected must be a function/) }) test('properties inline snapshot', () => { diff --git a/test/core/test/snapshot.test.ts b/test/core/test/snapshot.test.ts index 2fed1d2106c6..4c71e84124d9 100644 --- a/test/core/test/snapshot.test.ts +++ b/test/core/test/snapshot.test.ts @@ -30,7 +30,7 @@ test('with big string', () => { }).toMatchSnapshot() }) -test('throwing', () => { +test('throwing', async () => { expect(() => { throw new Error('omega') }).toThrowErrorMatchingSnapshot() @@ -44,6 +44,16 @@ test('throwing', () => { // eslint-disable-next-line no-throw-literal throw { error: 'omega' } }).toThrowErrorMatchingSnapshot() + + await expect(async () => { + throw new Error('omega') + }).rejects.toThrowErrorMatchingSnapshot() +}) + +test('throwing expect should be a function', async () => { + expect(() => { + expect(new Error('omega')).toThrowErrorMatchingSnapshot() + }).toThrow(/expected must be a function/) }) test('properties snapshot', () => { diff --git a/test/vite-node/package.json b/test/vite-node/package.json index 6cc633488489..847c24e5adb3 100644 --- a/test/vite-node/package.json +++ b/test/vite-node/package.json @@ -3,10 +3,12 @@ "private": true, "scripts": { "test": "vitest", - "coverage": "vitest run --coverage" + "coverage": "vitest run --coverage", + "dev": "vite-node --watch ./src/*", + "debug:dev": "cross-env DEBUG=vite-node:* node --inspect-brk ../../packages/vite-node/dist/cli.cjs --watch ./src/*", + "debug": "node --inspect-brk ../../packages/vite-node/dist/cli.cjs" }, "devDependencies": { - "vite": "^2.9.10", "vite-node": "workspace:*", "vitest": "workspace:*" } diff --git a/test/vite-node/src/deps.ts b/test/vite-node/src/deps.ts new file mode 100644 index 000000000000..285eb842f8ad --- /dev/null +++ b/test/vite-node/src/deps.ts @@ -0,0 +1,11 @@ +// eslint-disable-next-line no-console +console.log('deps') + +export {} + +if (import.meta.hot) { + import.meta.hot.accept(() => { + // eslint-disable-next-line no-console + console.log('[deps.ts] hot reload!') + }) +} diff --git a/test/vite-node/src/main.ts b/test/vite-node/src/main.ts new file mode 100644 index 000000000000..601823a63f0e --- /dev/null +++ b/test/vite-node/src/main.ts @@ -0,0 +1,12 @@ +/* eslint-disable no-console */ +import { a } from './testMod' + +console.log('[main.js] load!') +console.log('[main.js] hello world') +console.log('[main.js]', a) + +if (import.meta.hot) { + import.meta.hot.accept(() => { + console.log('[main.ts] hot reload!') + }) +} diff --git a/test/vite-node/src/testMod.ts b/test/vite-node/src/testMod.ts new file mode 100644 index 000000000000..2e6630f2fbf4 --- /dev/null +++ b/test/vite-node/src/testMod.ts @@ -0,0 +1,6 @@ +export const a = 'hello testModule' + +// eslint-disable-next-line no-console +console.log('[testModule.js] load!') + +import('./deps')